Introduce QWidget::setScreen

Follows the QWindow semantics, and is a replacement for creating
a QWidget with a QDesktopScreenWidget as the parent.

We can now remove much of the special handling of QDesktopWidget and
the Qt::Desktop window type, and get rid of QDesktopScreenWidget.

Add a manual test that allows local testing. Our CI environments
only have a single screen, and no multi-head display server setup
which is the primary case where QWidget::setScreen is interesting.
For the more common case of a virtual desktop, QWidget::setScreen
has no real impact (just as QWindow::setScreen doesn't).

Change-Id: Id0099e069d316741bacd8c795c396ccad37be297
Fixes: QTBUG-85483
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
This commit is contained in:
Volker Hilsheimer 2020-08-12 13:21:42 +02:00
parent c4366ff018
commit c54a5b8380
14 changed files with 239 additions and 64 deletions

View File

@ -2173,7 +2173,7 @@
\value SplashScreen Indicates that the window is a splash screen.
This is the default type for QSplashScreen.
\value Desktop Indicates that this widget is the desktop. This
\omitvalue Desktop Indicates that this widget is the desktop. This
is the type for QDesktopWidget.
\value SubWindow Indicates that this widget is a sub-window, such

View File

@ -707,7 +707,7 @@ QAccessibleInterface *QAccessible::queryAccessibleInterface(QObject *object)
QAccessiblePlugin *factory = qAccessiblePlugins()->value(cn);
if (factory) {
QAccessibleInterface *result = factory->create(cn, object);
if (result) { // Need this condition because of QDesktopScreenWidget
if (result) {
QAccessibleCache::instance()->insert(object, result);
Q_ASSERT(QAccessibleCache::instance()->containsObject(object));
}

View File

@ -222,8 +222,6 @@ QAccessibleInterface *qAccessibleFactory(const QString &classname, QObject *obje
iface = new QAccessibleDockWidget(widget);
#endif
} else if (classname == QLatin1String("QDesktopScreenWidget")) {
iface = nullptr;
} else if (classname == QLatin1String("QWidget")) {
iface = new QAccessibleWidget(widget);
} else if (classname == QLatin1String("QWindowContainer")) {

View File

@ -48,26 +48,6 @@
QT_BEGIN_NAMESPACE
QDesktopScreenWidget::QDesktopScreenWidget(QScreen *screen, const QRect &geometry)
: QWidget(nullptr, Qt::Desktop)
{
setVisible(false);
if (QWindow *winHandle = windowHandle())
winHandle->setScreen(screen);
setGeometry(geometry);
}
QScreen *QDesktopScreenWidget::screen() const
{
const QDesktopWidgetPrivate *desktopWidgetP
= static_cast<const QDesktopWidgetPrivate *>(qt_widget_private(QApplication::desktop()));
for (auto it : qAsConst(desktopWidgetP->screenWidgets)) {
if (it.second == this)
return it.first;
}
return nullptr;
}
QDesktopWidgetPrivate::~QDesktopWidgetPrivate()
{
qDeleteAll(screenWidgets.values());
@ -81,15 +61,19 @@ void QDesktopWidgetPrivate::updateScreens()
// Re-build our screens list. This is the easiest way to later compute which signals to emit.
// Create new screen widgets as necessary.
// Furthermore, we note which screens have changed, and compute the overall virtual geometry.
QFlatMap<QScreen*, QDesktopScreenWidget*> newScreenWidgets;
QFlatMap<QScreen*, QWidget*> newScreenWidgets;
QRegion virtualGeometry;
for (QScreen *screen : screenList) {
const QRect screenGeometry = screen->geometry();
QDesktopScreenWidget *screenWidget = screenWidgets.value(screen);
QWidget *screenWidget = screenWidgets.value(screen);
if (!screenWidget) {
// a new screen, create a widget and connect the signals.
screenWidget = new QDesktopScreenWidget(screen, screenGeometry);
screenWidget = new QWidget(nullptr, Qt::Desktop);
screenWidget->setVisible(false);
screenWidget->setScreen(screen);
screenWidget->setGeometry(screenGeometry);
screenWidget->setObjectName(QLatin1String("qt_desktop_widget_%1").arg(screen->name()));
QObjectPrivate::connect(screen, &QScreen::geometryChanged,
this, &QDesktopWidgetPrivate::updateScreens, Qt::QueuedConnection);
QObjectPrivate::connect(screen, &QObject::destroyed,
@ -105,7 +89,7 @@ void QDesktopWidgetPrivate::updateScreens()
Q_ASSERT(screenWidgets.size() == screenList.length());
q->setGeometry(virtualGeometry.boundingRect());
// Delete the QDesktopScreenWidget that are not used any more.
// Delete the screen widgets that are not used any more.
for (auto it : qAsConst(newScreenWidgets)) {
if (!screenWidgets.contains(it.first))
delete it.second;

View File

@ -77,26 +77,18 @@ private:
friend class QApplicationPrivate;
};
class QDesktopScreenWidget : public QWidget {
Q_OBJECT
public:
explicit QDesktopScreenWidget(QScreen *, const QRect &geometry);
QScreen *screen() const;
};
class QDesktopWidgetPrivate : public QWidgetPrivate {
Q_DECLARE_PUBLIC(QDesktopWidget)
public:
~QDesktopWidgetPrivate();
void updateScreens();
QDesktopScreenWidget *widgetForScreen(QScreen *qScreen) const
QWidget *widgetForScreen(QScreen *qScreen) const
{
return screenWidgets.value(qScreen);
}
QFlatMap<QScreen*, QDesktopScreenWidget*> screenWidgets;
QFlatMap<QScreen*, QWidget*> screenWidgets;
};
QT_END_NAMESPACE

View File

@ -42,7 +42,6 @@
#include "qapplication_p.h"
#include "qbrush.h"
#include "qcursor.h"
#include "qdesktopwidget_p.h"
#include "qevent.h"
#include "qlayout.h"
#if QT_CONFIG(menu)
@ -990,13 +989,6 @@ void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
if (allWidgets)
allWidgets->insert(q);
QScreen *targetScreen = nullptr;
if (parentWidget && parentWidget->windowType() == Qt::Desktop) {
const QDesktopScreenWidget *sw = qobject_cast<const QDesktopScreenWidget *>(parentWidget);
targetScreen = sw ? sw->screen() : nullptr;
parentWidget = nullptr;
}
q->data = &data;
#if QT_CONFIG(thread)
@ -1006,12 +998,6 @@ void QWidgetPrivate::init(QWidget *parentWidget, Qt::WindowFlags f)
}
#endif
if (targetScreen) {
topData()->initialScreen = targetScreen;
if (QWindow *window = q->windowHandle())
window->setScreen(targetScreen);
}
data.fstrut_dirty = true;
data.winid = 0;
@ -2424,8 +2410,7 @@ bool QWidgetPrivate::setScreen(QScreen *screen)
return false;
const QScreen *currentScreen = windowHandle() ? windowHandle()->screen() : nullptr;
if (currentScreen != screen) {
if (!windowHandle()) // Try to create a window handle if not created.
createWinId();
topData()->initialScreen = screen;
if (windowHandle())
windowHandle()->setScreen(screen);
return true;
@ -2514,6 +2499,24 @@ QScreen *QWidget::screen() const
return QGuiApplication::primaryScreen();
}
/*!
Sets the screen on which the widget should be shown to \a screen.
Setting the screen only makes sense for windows. If necessary, the widget's
window will get recreated on \a screen.
\note If the screen is part of a virtual desktop of multiple screens,
the window will not move automatically to \a newScreen. To place the
window relative to the screen, use the screen's topLeft() position.
\sa QWindow::setScreen
*/
void QWidget::setScreen(QScreen *screen)
{
Q_D(QWidget);
d->setScreen(screen);
}
#ifndef QT_NO_STYLE_STYLESHEET
/*!
@ -10508,8 +10511,7 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
if (newparent && newparent->windowType() == Qt::Desktop) {
// make sure the widget is created on the same screen as the
// programmer specified desktop widget
const QDesktopScreenWidget *sw = qobject_cast<const QDesktopScreenWidget *>(newparent);
targetScreen = sw ? sw->screen() : nullptr;
targetScreen = newparent->screen();
newparent = nullptr;
}
@ -10539,10 +10541,8 @@ void QWidgetPrivate::setParent_sys(QWidget *newparent, Qt::WindowFlags f)
if (!newparent) {
f |= Qt::Window;
if (!targetScreen) {
if (parent)
targetScreen = q->parentWidget()->window()->screen();
}
if (parent)
targetScreen = q->parentWidget()->window()->screen();
}
bool explicitlyHidden = q->testAttribute(Qt::WA_WState_Hidden) && q->testAttribute(Qt::WA_WState_ExplicitShowHide);

View File

@ -598,11 +598,10 @@ public:
QWindow *windowHandle() const;
QScreen *screen() const;
void setScreen(QScreen *);
static QWidget *createWindowContainer(QWindow *window, QWidget *parent=nullptr, Qt::WindowFlags flags=Qt::WindowFlags());
friend class QDesktopScreenWidget;
Q_SIGNALS:
void windowTitleChanged(const QString &title);
void windowIconChanged(const QIcon &icon);

View File

@ -2377,7 +2377,7 @@ void QMenuPrivate::popup(const QPoint &p, QAction *atAction, PositionFunction po
updateLayoutDirection();
// Ensure that we get correct sizeHints by placing this window on the correct screen.
// However if the QMenu was constructed with a QDesktopScreenWidget as its parent,
// However if the QMenu was constructed with a Qt::Desktop widget as its parent,
// then initialScreenIndex was set, so we should respect that for the lifetime of this menu.
// However if eventLoop exists, then exec() already did this by calling createWinId(); so leave it alone. (QTBUG-76162)
if (!eventLoop) {

View File

@ -196,6 +196,7 @@ private slots:
void activation();
#endif
void reparent();
void setScreen();
void windowState();
void showMaximized();
void showFullScreen();
@ -2943,6 +2944,29 @@ void tst_QWidget::reparent()
QTRY_COMPARE(childTLW.pos(), tlwPos);
}
void tst_QWidget::setScreen()
{
const auto screens = QApplication::screens();
if (screens.count() < 2)
QSKIP("This test tests nothing on a machine with a single screen.");
QScreen *screen0 = screens.at(0);
QScreen *screen1 = screens.at(1);
QWidget window;
window.setWindowFlags(Qt::CustomizeWindowHint | Qt::FramelessWindowHint);
window.setScreen(screen0);
QCOMPARE(window.screen(), screen0);
window.setScreen(screen1);
QCOMPARE(window.screen(), screen1);
// calling setScreen on a widget that is not a window does nothing
QWidget child(&window);
const QScreen *childScreen = child.screen();
child.setScreen(childScreen == screen0 ? screen1 : screen0);
QCOMPARE(child.screen(), childScreen);
}
// Qt/Embedded does it differently.
void tst_QWidget::icon()
{

View File

@ -3,3 +3,4 @@
# add_subdirectory(qtooltip) # special case broken in dev
add_subdirectory(sizeonhide)
add_subdirectory(layoutreplace)
add_subdirectory(setscreen)

View File

@ -1,2 +1,2 @@
TEMPLATE = subdirs
SUBDIRS = qtooltip sizeonhide layoutreplace
SUBDIRS = qtooltip sizeonhide layoutreplace setscreen

View File

@ -0,0 +1,18 @@
# Generated from setscreen.pro.
#####################################################################
## setscreen Binary:
#####################################################################
qt_add_manual_test(setscreen
GUI
SOURCES
main.cpp
PUBLIC_LIBRARIES
Qt::CorePrivate
Qt::Gui
Qt::Widgets
)
#### Keys ignored in scope 1:.:.:setscreen.pro:<TRUE>:
# TEMPLATE = "app"

View File

@ -0,0 +1,156 @@
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the test suite of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <QtWidgets>
class ScreenWidget : public QWidget
{
public:
ScreenWidget(QWidget *parent)
: QWidget(parent, Qt::Window)
{
textEdit = new QTextEdit;
textEdit->setReadOnly(true);
QHBoxLayout *layout = new QHBoxLayout;
layout->addWidget(textEdit);
setLayout(layout);
}
void updateText()
{
QString text = "<html><body>";
text += QString("<p>Screen: %1\n</p>").arg(screen()->name());
text += QString("<p>DPR: %1\n</p>").arg(screen()->devicePixelRatio());
text += QString("</body></html>");
textEdit->setText(text);
}
private:
QTextEdit *textEdit;
};
class Controller : public QDialog
{
Q_OBJECT
public:
Controller()
{
QPushButton *screenButton = new QPushButton;
screenButton->setText("Show on Screen");
screenButton->setEnabled(false);
connect(screenButton, &QAbstractButton::clicked, this, &Controller::setScreen);
QPushButton *desktopButton = new QPushButton;
desktopButton->setText("Show on Desktop");
desktopButton->setEnabled(false);
connect(desktopButton, &QAbstractButton::clicked, this, &Controller::setDesktop);
QPushButton *exitButton = new QPushButton;
exitButton->setText("E&xit");
connect(exitButton, &QAbstractButton::clicked, QApplication::instance(), &QCoreApplication::quit);
QHBoxLayout *actionLayout = new QHBoxLayout;
actionLayout->addWidget(screenButton);
actionLayout->addWidget(desktopButton);
actionLayout->addWidget(exitButton);
QGroupBox *radioGroup = new QGroupBox;
radioGroup->setTitle(QLatin1String("Select target screen"));
QVBoxLayout *groupLayout = new QVBoxLayout;
const auto screens = QGuiApplication::screens();
int count = 0;
for (const auto &screen : screens) {
QRadioButton *choice = new QRadioButton;
choice->setText(QString(QLatin1String("%1: %2")).arg(count).arg(screen->name()));
connect(choice, &QAbstractButton::toggled, this, [=](bool on){
if (on)
targetScreen = count;
screenButton->setEnabled(targetScreen != -1);
desktopButton->setEnabled(targetScreen != -1);
});
groupLayout->addWidget(choice);
++count;
}
radioGroup->setLayout(groupLayout);
QVBoxLayout *layout = new QVBoxLayout;
layout->addWidget(radioGroup);
layout->addLayout(actionLayout);
setLayout(layout);
}
private slots:
void setScreen()
{
QScreen *screen = QGuiApplication::screens().at(targetScreen);
if (!widget) {
widget = new ScreenWidget(this);
widget->setAttribute(Qt::WA_DeleteOnClose);
widget->setWindowTitle("Normal Window");
}
widget->setScreen(screen);
widget->show();
widget->updateText();
}
void setDesktop()
{
QScreen *screen = QGuiApplication::screens().at(targetScreen);
QWidget *desktop = QApplication::desktop(screen);
if (!desktopChild) {
desktopChild = new ScreenWidget(desktop);
desktopChild->setAttribute(Qt::WA_DeleteOnClose);
desktopChild->setWindowTitle("Child of a Desktop");
} else {
desktopChild->setParent(desktop);
}
desktopChild->show();
desktopChild->updateText();
}
private:
QPointer<ScreenWidget> widget = nullptr;
QPointer<ScreenWidget> desktopChild = nullptr;
int targetScreen = -1;
};
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Controller controller;
controller.show();
return app.exec();
}
#include "main.moc"

View File

@ -0,0 +1,3 @@
TEMPLATE = app
SOURCES = main.cpp
QT += widgets