mirror-chatterino2/platform/borderless/qwinwidget.cpp
2017-04-12 17:46:44 +02:00

556 lines
18 KiB
C++

/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
**
** This file is part of the Qt Solutions component.
**
** $QT_BEGIN_LICENSE:BSD$
** You may use this file under the terms of the BSD license as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names
** of its contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
/* */
/* */
/* File is originally from
* https://github.com/qtproject/qt-solutions/tree/master/qtwinmigrate/src */
/* */
/* It has been modified to support borderless window (HTTRANSPARENT) & to remove
* pre Qt5 cruft */
/* */
/* */
#include "QWinWidget.h"
#include <qt_windows.h>
#include <QApplication>
#include <QEvent>
#include <QFocusEvent>
#include <QWindow>
/*!
\class QWinWidget qwinwidget.h
\brief The QWinWidget class is a Qt widget that can be child of a
native Win32 widget.
The QWinWidget class is the bridge between an existing application
user interface developed using native Win32 APIs or toolkits like
MFC, and Qt based GUI elements.
Using QWinWidget as the parent of QDialogs will ensure that
modality, placement and stacking works properly throughout the
entire application. If the child widget is a top level window that
uses the \c WDestructiveClose flag, QWinWidget will destroy itself
when the child window closes down.
Applications moving to Qt can use QWinWidget to add new
functionality, and gradually replace the existing interface.
*/
QWinWidget::QWinWidget()
: QWidget(nullptr)
, m_Layout()
, p_Widget(nullptr)
, m_ParentNativeWindowHandle(nullptr)
, _prevFocus(nullptr)
, _reenableParent(false)
{
// Create a native window and give it geometry values * devicePixelRatio for
// HiDPI support
p_ParentWinNativeWindow = new WinNativeWindow(
1 * window()->devicePixelRatio(), 1 * window()->devicePixelRatio(),
1 * window()->devicePixelRatio(), 1 * window()->devicePixelRatio());
// If you want to set a minimize size for your app, do so here
// p_ParentWinNativeWindow->setMinimumSize(1024 *
// window()->devicePixelRatio(), 768 * window()->devicePixelRatio());
// If you want to set a maximum size for your app, do so here
// p_ParentWinNativeWindow->setMaximumSize(1024 *
// window()->devicePixelRatio(), 768 * window()->devicePixelRatio());
// Save the native window handle for shorthand use
m_ParentNativeWindowHandle = p_ParentWinNativeWindow->hWnd;
Q_ASSERT(m_ParentNativeWindowHandle);
// Create the child window & embed it into the native one
if (m_ParentNativeWindowHandle) {
SetWindowLong((HWND)winId(), GWL_STYLE,
WS_CHILD | WS_CLIPCHILDREN | WS_CLIPSIBLINGS);
QWindow *window = windowHandle();
window->setProperty("_q_embedded_native_parent_handle",
(WId)m_ParentNativeWindowHandle);
SetParent((HWND)winId(), m_ParentNativeWindowHandle);
window->setFlags(Qt::FramelessWindowHint);
QEvent e(QEvent::EmbeddingControl);
QApplication::sendEvent(this, &e);
}
// Pass along our window handle & widget pointer to WinFramelessWidget so we
// can exchange messages
p_ParentWinNativeWindow->childWindow = (HWND)winId();
p_ParentWinNativeWindow->childWidget = this;
// Clear margins & spacing & add the layout to prepare for the MainAppWidget
setContentsMargins(0, 0, 0, 0);
setLayout(&m_Layout);
m_Layout.setContentsMargins(0, 0, 0, 0);
m_Layout.setSpacing(0);
// Create the true app widget
// p_Widget = new Widget(this);
// m_Layout.addWidget(p_Widget);
// p_Widget->setParent(this, Qt::Widget);
// p_Widget->setVisible(true);
// Update the BORDERWIDTH value if needed for HiDPI displays
BORDERWIDTH = BORDERWIDTH * window()->devicePixelRatio();
// Update the TOOLBARHEIGHT value to match the height of toolBar * if
// needed, the HiDPI display
// if (p_Widget->toolBar) {
// TOOLBARHEIGHT =
// p_Widget->toolBar->height() * window()->devicePixelRatio();
// }
// You need to keep the native window in sync with the Qt window & children,
// so wire min/max/close buttons to
// slots inside of QWinWidget. QWinWidget can then talk with the native
// window as needed
// if (p_Widget->minimizeButton) {
// connect(p_Widget->minimizeButton, &QPushButton::clicked, this,
// &QWinWidget::onMinimizeButtonClicked);
// }
// if (p_Widget->maximizeButton) {
// connect(p_Widget->maximizeButton, &QPushButton::clicked, this,
// &QWinWidget::onMaximizeButtonClicked);
// }
// if (p_Widget->closeButton) {
// connect(p_Widget->closeButton, &QPushButton::clicked, this,
// &QWinWidget::onCloseButtonClicked);
// }
// Send the parent native window a WM_SIZE message to update the widget size
SendMessage(m_ParentNativeWindowHandle, WM_SIZE, 0, 0);
}
/*!
Destroys this object, freeing all allocated resources.
*/
QWinWidget::~QWinWidget()
{
}
/*!
Returns the handle of the native Win32 parent window.
*/
HWND
QWinWidget::getParentWindow() const
{
return m_ParentNativeWindowHandle;
}
/*!
\reimp
*/
void
QWinWidget::childEvent(QChildEvent *e)
{
QObject *obj = e->child();
if (obj->isWidgetType()) {
if (e->added()) {
if (obj->isWidgetType()) {
obj->installEventFilter(this);
}
} else if (e->removed() && _reenableParent) {
_reenableParent = false;
EnableWindow(m_ParentNativeWindowHandle, true);
obj->removeEventFilter(this);
}
}
QWidget::childEvent(e);
}
/*! \internal */
void
QWinWidget::saveFocus()
{
if (!_prevFocus)
_prevFocus = ::GetFocus();
if (!_prevFocus)
_prevFocus = getParentWindow();
}
/*!
Shows this widget. Overrides QWidget::show().
\sa showCentered()
*/
void
QWinWidget::show()
{
ShowWindow(m_ParentNativeWindowHandle, true);
saveFocus();
QWidget::show();
}
/*!
Centers this widget over the native parent window. Use this
function to have Qt toplevel windows (i.e. dialogs) positioned
correctly over their native parent windows.
\code
QWinWidget qwin(hParent);
qwin.center();
QMessageBox::information(&qwin, "Caption", "Information Text");
\endcode
This will center the message box over the client area of hParent.
*/
void
QWinWidget::center()
{
const QWidget *child = findChild<QWidget *>();
if (child && !child->isWindow()) {
qWarning("QWinWidget::center: Call this function only for QWinWidgets "
"with toplevel children");
}
RECT r;
GetWindowRect(m_ParentNativeWindowHandle, &r);
setGeometry((r.right - r.left) / 2 + r.left, (r.bottom - r.top) / 2 + r.top,
0, 0);
}
/*!
\obsolete
Call center() instead.
*/
void
QWinWidget::showCentered()
{
center();
show();
}
void
QWinWidget::setGeometry(int x, int y, int w, int h)
{
p_ParentWinNativeWindow->setGeometry(
x * window()->devicePixelRatio(), y * window()->devicePixelRatio(),
w * window()->devicePixelRatio(), h * window()->devicePixelRatio());
}
/*!
Sets the focus to the window that had the focus before this widget
was shown, or if there was no previous window, sets the focus to
the parent window.
*/
void
QWinWidget::resetFocus()
{
if (_prevFocus)
::SetFocus(_prevFocus);
else
::SetFocus(getParentWindow());
}
// Tell the parent native window to minimize
void
QWinWidget::onMinimizeButtonClicked()
{
SendMessage(m_ParentNativeWindowHandle, WM_SYSCOMMAND, SC_MINIMIZE, 0);
}
// Tell the parent native window to maximize or restore as appropriate
void
QWinWidget::onMaximizeButtonClicked()
{
if (p_Widget->maximizeButton->isChecked()) {
SendMessage(m_ParentNativeWindowHandle, WM_SYSCOMMAND, SC_MAXIMIZE, 0);
} else {
SendMessage(m_ParentNativeWindowHandle, WM_SYSCOMMAND, SC_RESTORE, 0);
}
}
void
QWinWidget::onCloseButtonClicked()
{
if (true /* put your check for it if it safe to close your app here */) // eg, does the user need to save a document
{
// Safe to close, so hide the parent window
ShowWindow(m_ParentNativeWindowHandle, false);
// And then quit
QApplication::quit();
} else {
// Do nothing, and thus, don't actually close the window
}
}
bool
QWinWidget::nativeEvent(const QByteArray &, void *message, long *result)
{
MSG *msg = (MSG *)message;
if (msg->message == WM_SETFOCUS) {
Qt::FocusReason reason;
if (::GetKeyState(VK_LBUTTON) < 0 || ::GetKeyState(VK_RBUTTON) < 0)
reason = Qt::MouseFocusReason;
else if (::GetKeyState(VK_SHIFT) < 0)
reason = Qt::BacktabFocusReason;
else
reason = Qt::TabFocusReason;
QFocusEvent e(QEvent::FocusIn, reason);
QApplication::sendEvent(this, &e);
}
// Only close if safeToClose clears()
if (msg->message == WM_CLOSE) {
if (true /* put your check for it if it safe to close your app here */) // eg, does the user need to save a document
{
// Safe to close, so hide the parent window
ShowWindow(m_ParentNativeWindowHandle, false);
// And then quit
QApplication::quit();
} else {
*result = 0; // Set the message to 0 to ignore it, and thus, don't
// actually close
return true;
}
}
// Double check WM_SIZE messages to see if the parent native window is
// maximized
if (msg->message == WM_SIZE) {
if (p_Widget && p_Widget->maximizeButton) {
// Get the window state
WINDOWPLACEMENT wp;
GetWindowPlacement(m_ParentNativeWindowHandle, &wp);
// If we're maximized,
if (wp.showCmd == SW_MAXIMIZE) {
// Maximize button should show as Restore
p_Widget->maximizeButton->setChecked(true);
} else {
// Maximize button should show as Maximize
p_Widget->maximizeButton->setChecked(false);
}
}
}
// Pass NCHITTESTS on the window edges as determined by BORDERWIDTH &
// TOOLBARHEIGHT through to the parent native window
if (msg->message == WM_NCHITTEST) {
RECT WindowRect;
int x, y;
GetWindowRect(msg->hwnd, &WindowRect);
x = GET_X_LPARAM(msg->lParam) - WindowRect.left;
y = GET_Y_LPARAM(msg->lParam) - WindowRect.top;
if (x >= BORDERWIDTH &&
x <= WindowRect.right - WindowRect.left - BORDERWIDTH &&
y >= BORDERWIDTH && y <= TOOLBARHEIGHT) {
if (false) { // if (p_Widget->toolBar) {
// If the mouse is over top of the toolbar area BUT is actually
// positioned over a child widget of the toolbar,
// Then we don't want to enable dragging. This allows for
// buttons in the toolbar, eg, a Maximize button, to keep the
// mouse interaction
if (QApplication::widgetAt(QCursor::pos()) != p_Widget->toolBar)
return false;
} else {
// The mouse is over the toolbar area & is NOT over a child of
// the toolbar, so pass this message
// through to the native window for HTCAPTION dragging
*result = HTTRANSPARENT;
return true;
}
} else if (x < BORDERWIDTH && y < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH &&
y < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH &&
y > WindowRect.bottom - WindowRect.top - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x < BORDERWIDTH &&
y > WindowRect.bottom - WindowRect.top - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (y < BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (x > WindowRect.right - WindowRect.left - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
} else if (y > WindowRect.bottom - WindowRect.top - BORDERWIDTH) {
*result = HTTRANSPARENT;
return true;
}
return false;
}
return false;
}
/*!
\reimp
*/
bool
QWinWidget::eventFilter(QObject *o, QEvent *e)
{
QWidget *w = (QWidget *)o;
switch (e->type()) {
case QEvent::WindowDeactivate:
if (w->isModal() && w->isHidden())
BringWindowToTop(m_ParentNativeWindowHandle);
break;
case QEvent::Hide:
if (_reenableParent) {
EnableWindow(m_ParentNativeWindowHandle, true);
_reenableParent = false;
}
resetFocus();
if (w->testAttribute(Qt::WA_DeleteOnClose) && w->isWindow())
deleteLater();
break;
case QEvent::Show:
if (w->isWindow()) {
saveFocus();
hide();
if (w->isModal() && !_reenableParent) {
EnableWindow(m_ParentNativeWindowHandle, false);
_reenableParent = true;
}
}
break;
case QEvent::Close: {
::SetActiveWindow(m_ParentNativeWindowHandle);
if (w->testAttribute(Qt::WA_DeleteOnClose))
deleteLater();
break;
}
default:
break;
}
return QWidget::eventFilter(o, e);
}
/*! \reimp
*/
void
QWinWidget::focusInEvent(QFocusEvent *e)
{
QWidget *candidate = this;
switch (e->reason()) {
case Qt::TabFocusReason:
case Qt::BacktabFocusReason:
while (!(candidate->focusPolicy() & Qt::TabFocus)) {
candidate = candidate->nextInFocusChain();
if (candidate == this) {
candidate = 0;
break;
}
}
if (candidate) {
candidate->setFocus(e->reason());
if (e->reason() == Qt::BacktabFocusReason ||
e->reason() == Qt::TabFocusReason) {
candidate->setAttribute(Qt::WA_KeyboardFocusChange);
candidate->window()->setAttribute(
Qt::WA_KeyboardFocusChange);
}
if (e->reason() == Qt::BacktabFocusReason)
QWidget::focusNextPrevChild(false);
}
break;
default:
break;
}
}
/*! \reimp
*/
bool
QWinWidget::focusNextPrevChild(bool next)
{
QWidget *curFocus = focusWidget();
if (!next) {
if (!curFocus->isWindow()) {
QWidget *nextFocus = curFocus->nextInFocusChain();
QWidget *prevFocus = 0;
QWidget *topLevel = 0;
while (nextFocus != curFocus) {
if (nextFocus->focusPolicy() & Qt::TabFocus) {
prevFocus = nextFocus;
topLevel = 0;
}
nextFocus = nextFocus->nextInFocusChain();
}
if (!topLevel) {
return QWidget::focusNextPrevChild(false);
}
}
} else {
QWidget *nextFocus = curFocus;
while (1 && nextFocus != 0) {
nextFocus = nextFocus->nextInFocusChain();
if (nextFocus->focusPolicy() & Qt::TabFocus) {
return QWidget::focusNextPrevChild(true);
}
}
}
::SetFocus(m_ParentNativeWindowHandle);
return true;
}