Warning, file /plasma/oxygen/kstyle/oxygenwindowmanager.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0003     SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0004     SPDX-FileCopyrightText: 2007 Thomas Luebking <thomas.luebking@web.de>
0005     SPDX-License-Identifier: GPL-2.0-or-later AND MIT
0006 */
0007 
0008 #include "oxygenwindowmanager.h"
0009 #include "oxygenhelper.h"
0010 #include "oxygenpropertynames.h"
0011 #include "oxygenstyleconfigdata.h"
0012 
0013 #include <QApplication>
0014 #include <QComboBox>
0015 #include <QDialog>
0016 #include <QDockWidget>
0017 #include <QGraphicsView>
0018 #include <QGroupBox>
0019 #include <QLabel>
0020 #include <QListView>
0021 #include <QMainWindow>
0022 #include <QMdiSubWindow>
0023 #include <QMenuBar>
0024 #include <QMouseEvent>
0025 #include <QProgressBar>
0026 #include <QQuickWindow>
0027 #include <QScrollBar>
0028 #include <QStatusBar>
0029 #include <QStyle>
0030 #include <QStyleOptionGroupBox>
0031 #include <QTabBar>
0032 #include <QTabWidget>
0033 #include <QToolBar>
0034 #include <QToolButton>
0035 #include <QTreeView>
0036 
0037 #include <QTextStream>
0038 // needed to deal with device pixel ratio
0039 #include <QWindow>
0040 
0041 namespace Oxygen
0042 {
0043 
0044 //* provide application-wise event filter
0045 /**
0046 it us used to unlock dragging and make sure event look is properly restored
0047 after a drag has occurred
0048 */
0049 class AppEventFilter : public QObject
0050 {
0051 public:
0052     //* constructor
0053     explicit AppEventFilter(WindowManager *parent)
0054         : QObject(parent)
0055         , _parent(parent)
0056     {
0057     }
0058 
0059     //* event filter
0060     bool eventFilter(QObject *object, QEvent *event) override
0061     {
0062         if (event->type() == QEvent::MouseButtonRelease) {
0063             // stop drag timer
0064             if (_parent->_dragTimer.isActive()) {
0065                 _parent->resetDrag();
0066             }
0067 
0068             // unlock
0069             if (_parent->isLocked()) {
0070                 _parent->setLocked(false);
0071             }
0072         }
0073 
0074         if (!_parent->enabled())
0075             return false;
0076 
0077         /*
0078         if a drag is in progress, the widget will not receive any event
0079         we trigger on the first MouseMove or MousePress events that are received
0080         by any widget in the application to detect that the drag is finished
0081         */
0082         if (_parent->useWMMoveResize() && _parent->_dragInProgress && _parent->_target
0083             && (event->type() == QEvent::MouseMove || event->type() == QEvent::MouseButtonPress)) {
0084             return appMouseEvent(object, event);
0085         }
0086 
0087         return false;
0088     }
0089 
0090 protected:
0091     //* application-wise event.
0092     /** needed to catch end of XMoveResize events */
0093     bool appMouseEvent(QObject *, QEvent *event)
0094     {
0095         Q_UNUSED(event);
0096 
0097         /*
0098         post some mouseRelease event to the target, in order to counter balance
0099         the mouse press that triggered the drag. Note that it triggers a resetDrag
0100         */
0101         QMouseEvent mouseEvent(QEvent::MouseButtonRelease, _parent->_dragPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
0102         qApp->sendEvent(_parent->_target.data(), &mouseEvent);
0103 
0104         return false;
0105     }
0106 
0107 private:
0108     //* parent
0109     WindowManager *_parent;
0110 };
0111 
0112 //_____________________________________________________________
0113 WindowManager::WindowManager(QObject *parent)
0114     : QObject(parent)
0115     , _enabled(true)
0116     , _useWMMoveResize(true)
0117     , _dragMode(StyleConfigData::WD_FULL)
0118     , _dragDistance(QApplication::startDragDistance())
0119     , _dragDelay(QApplication::startDragTime())
0120     , _dragAboutToStart(false)
0121     , _dragInProgress(false)
0122     , _locked(false)
0123     , _cursorOverride(false)
0124 {
0125     // install application wise event filter
0126     _appEventFilter = new AppEventFilter(this);
0127     qApp->installEventFilter(_appEventFilter);
0128 }
0129 
0130 //_____________________________________________________________
0131 void WindowManager::initialize(void)
0132 {
0133     setEnabled(StyleConfigData::windowDragMode() != StyleConfigData::WD_NONE);
0134     setDragMode(StyleConfigData::windowDragMode());
0135     setUseWMMoveResize(StyleConfigData::useWMMoveResize());
0136 
0137     setDragDistance(QApplication::startDragDistance());
0138     setDragDelay(QApplication::startDragTime());
0139 
0140     initializeWhiteList();
0141     initializeBlackList();
0142 }
0143 
0144 //_____________________________________________________________
0145 void WindowManager::registerWidget(QWidget *widget)
0146 {
0147     if (isBlackListed(widget) || isDragable(widget)) {
0148         /*
0149         install filter for dragable widgets.
0150         also install filter for blacklisted widgets
0151         to be able to catch the relevant events and prevent
0152         the drag to happen
0153         */
0154         widget->removeEventFilter(this);
0155         widget->installEventFilter(this);
0156     }
0157 }
0158 
0159 void WindowManager::registerQuickItem(QQuickItem *item)
0160 {
0161     if (!item)
0162         return;
0163 
0164     QQuickWindow *window = item->window();
0165     if (window) {
0166         QQuickItem *contentItem = window->contentItem();
0167         contentItem->setAcceptedMouseButtons(Qt::LeftButton);
0168         contentItem->removeEventFilter(this);
0169         contentItem->installEventFilter(this);
0170     }
0171 }
0172 
0173 //_____________________________________________________________
0174 void WindowManager::unregisterWidget(QWidget *widget)
0175 {
0176     if (widget) {
0177         widget->removeEventFilter(this);
0178     }
0179 }
0180 
0181 //_____________________________________________________________
0182 void WindowManager::initializeWhiteList(void)
0183 {
0184     _whiteList.clear();
0185 
0186     // add user specified whitelisted classnames
0187     _whiteList.insert(ExceptionId(QStringLiteral("MplayerWindow")));
0188     _whiteList.insert(ExceptionId(QStringLiteral("ViewSliders@kmix")));
0189     _whiteList.insert(ExceptionId(QStringLiteral("Sidebar_Widget@konqueror")));
0190 
0191     const QStringList whiteList = StyleConfigData::windowDragWhiteList();
0192     for (const QString &exception : whiteList) {
0193         ExceptionId id(exception);
0194         if (!id.className().isEmpty()) {
0195             _whiteList.insert(ExceptionId(exception));
0196         }
0197     }
0198 }
0199 
0200 //_____________________________________________________________
0201 void WindowManager::initializeBlackList(void)
0202 {
0203     _blackList.clear();
0204     _blackList.insert(ExceptionId(QStringLiteral("CustomTrackView@kdenlive")));
0205     _blackList.insert(ExceptionId(QStringLiteral("MuseScore")));
0206     _blackList.insert(ExceptionId(QStringLiteral("KGameCanvasWidget")));
0207 
0208     const QStringList blackList = StyleConfigData::windowDragBlackList();
0209     for (const QString &exception : blackList) {
0210         ExceptionId id(exception);
0211         if (!id.className().isEmpty()) {
0212             _blackList.insert(ExceptionId(exception));
0213         }
0214     }
0215 }
0216 
0217 //_____________________________________________________________
0218 bool WindowManager::eventFilter(QObject *object, QEvent *event)
0219 {
0220     if (!enabled())
0221         return false;
0222 
0223     switch (event->type()) {
0224     case QEvent::MouseButtonPress:
0225         return mousePressEvent(object, event);
0226         break;
0227 
0228     case QEvent::MouseMove:
0229         if (object == _target.data() || object == _quickTarget.data())
0230             return mouseMoveEvent(object, event);
0231         break;
0232 
0233     case QEvent::MouseButtonRelease:
0234         if (_target || _quickTarget)
0235             return mouseReleaseEvent(object, event);
0236 
0237         break;
0238 
0239     default:
0240         break;
0241     }
0242 
0243     return false;
0244 }
0245 
0246 //_____________________________________________________________
0247 void WindowManager::timerEvent(QTimerEvent *event)
0248 {
0249     if (event->timerId() == _dragTimer.timerId()) {
0250         _dragTimer.stop();
0251         if (_target) {
0252             startDrag(_target.data()->window()->windowHandle());
0253         } else if (_quickTarget) {
0254             startDrag(_quickTarget.data()->window());
0255         }
0256 
0257     } else {
0258         return QObject::timerEvent(event);
0259     }
0260 }
0261 
0262 //_____________________________________________________________
0263 bool WindowManager::mousePressEvent(QObject *object, QEvent *event)
0264 {
0265     // cast event and check buttons/modifiers
0266     QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
0267     if (!(mouseEvent->modifiers() == Qt::NoModifier && mouseEvent->button() == Qt::LeftButton)) {
0268         return false;
0269     }
0270     if (mouseEvent->source() != Qt::MouseEventNotSynthesized) {
0271         return false;
0272     }
0273 
0274     // check lock
0275     if (isLocked())
0276         return false;
0277     else
0278         setLocked(true);
0279 
0280     // check QQuickItem - we can immediately start drag, because QQuickWindow's contentItem
0281     // only receives mouse events that weren't handled by children
0282     if (QQuickItem *item = qobject_cast<QQuickItem *>(object)) {
0283         _quickTarget = item;
0284         _dragPoint = mouseEvent->pos();
0285         _globalDragPoint = mouseEvent->globalPos();
0286 
0287         if (_dragTimer.isActive())
0288             _dragTimer.stop();
0289         _dragTimer.start(_dragDelay, this);
0290 
0291         return true;
0292     }
0293 
0294     // cast to widget
0295     QWidget *widget = static_cast<QWidget *>(object);
0296 
0297     // check if widget can be dragged from current position
0298     if (isBlackListed(widget) || !canDrag(widget))
0299         return false;
0300 
0301     // retrieve widget's child at event position
0302     QPoint position(mouseEvent->pos());
0303     QWidget *child = widget->childAt(position);
0304     if (!canDrag(widget, child, position))
0305         return false;
0306 
0307     // save target and drag point
0308     _target = widget;
0309     _dragPoint = position;
0310     _globalDragPoint = mouseEvent->globalPos();
0311     _dragAboutToStart = true;
0312 
0313     // send a move event to the current child with same position
0314     // if received, it is caught to actually start the drag
0315     QPoint localPoint(_dragPoint);
0316     if (child)
0317         localPoint = child->mapFrom(widget, localPoint);
0318     else
0319         child = widget;
0320     QMouseEvent localMouseEvent(QEvent::MouseMove, localPoint, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier);
0321     qApp->sendEvent(child, &localMouseEvent);
0322 
0323     // never eat event
0324     return false;
0325 }
0326 
0327 //_____________________________________________________________
0328 bool WindowManager::mouseMoveEvent(QObject *object, QEvent *event)
0329 {
0330     Q_UNUSED(object);
0331     QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
0332 
0333     if (mouseEvent->source() != Qt::MouseEventNotSynthesized) {
0334         return false;
0335     }
0336 
0337     // stop timer
0338     if (_dragTimer.isActive())
0339         _dragTimer.stop();
0340 
0341     // cast event and check drag distance
0342     if (!_dragInProgress) {
0343         if (_dragAboutToStart) {
0344             if (mouseEvent->pos() == _dragPoint) {
0345                 // start timer,
0346                 _dragAboutToStart = false;
0347                 if (_dragTimer.isActive())
0348                     _dragTimer.stop();
0349                 _dragTimer.start(_dragDelay, this);
0350 
0351             } else
0352                 resetDrag();
0353 
0354         } else if (QPoint(mouseEvent->globalPos() - _globalDragPoint).manhattanLength() >= _dragDistance) {
0355             _dragTimer.start(0, this);
0356         }
0357 
0358         return true;
0359 
0360     } else if (!useWMMoveResize() && _target) {
0361         // use QWidget::move for the grabbing
0362         /* this works only if the sending object and the target are identical */
0363         QWidget *window(_target.data()->window());
0364         window->move(window->pos() + mouseEvent->pos() - _dragPoint);
0365         return true;
0366 
0367     } else
0368         return false;
0369 }
0370 
0371 //_____________________________________________________________
0372 bool WindowManager::mouseReleaseEvent(QObject *object, QEvent *event)
0373 {
0374     Q_UNUSED(object);
0375     Q_UNUSED(event);
0376     resetDrag();
0377     return false;
0378 }
0379 
0380 //_____________________________________________________________
0381 bool WindowManager::isDragable(QWidget *widget)
0382 {
0383     // check widget
0384     if (!widget)
0385         return false;
0386 
0387     // accepted default types
0388     if ((qobject_cast<QDialog *>(widget) && widget->isWindow()) || (qobject_cast<QMainWindow *>(widget) && widget->isWindow())
0389         || qobject_cast<QGroupBox *>(widget)) {
0390         return true;
0391     }
0392 
0393     // more accepted types, provided they are not dock widget titles
0394     if ((qobject_cast<QMenuBar *>(widget) || qobject_cast<QTabBar *>(widget) || qobject_cast<QStatusBar *>(widget) || qobject_cast<QToolBar *>(widget))
0395         && !isDockWidgetTitle(widget)) {
0396         return true;
0397     }
0398 
0399     if (widget->inherits("KScreenSaver") && widget->inherits("KCModule")) {
0400         return true;
0401     }
0402 
0403     if (isWhiteListed(widget)) {
0404         return true;
0405     }
0406 
0407     // flat toolbuttons
0408     if (QToolButton *toolButton = qobject_cast<QToolButton *>(widget)) {
0409         if (toolButton->autoRaise())
0410             return true;
0411     }
0412 
0413     // viewports
0414     /*
0415     one needs to check that
0416     1/ the widget parent is a scrollarea
0417     2/ it matches its parent viewport
0418     3/ the parent is not blacklisted
0419     */
0420     if (QListView *listView = qobject_cast<QListView *>(widget->parentWidget())) {
0421         if (listView->viewport() == widget && !isBlackListed(listView))
0422             return true;
0423     }
0424 
0425     if (QTreeView *treeView = qobject_cast<QTreeView *>(widget->parentWidget())) {
0426         if (treeView->viewport() == widget && !isBlackListed(treeView))
0427             return true;
0428     }
0429 
0430     /*
0431     catch labels in status bars.
0432     this is because of kstatusbar
0433     who captures buttonPress/release events
0434     */
0435     if (QLabel *label = qobject_cast<QLabel *>(widget)) {
0436         if (label->textInteractionFlags().testFlag(Qt::TextSelectableByMouse))
0437             return false;
0438 
0439         QWidget *parent = label->parentWidget();
0440         while (parent) {
0441             if (qobject_cast<QStatusBar *>(parent))
0442                 return true;
0443             parent = parent->parentWidget();
0444         }
0445     }
0446 
0447     return false;
0448 }
0449 
0450 //_____________________________________________________________
0451 bool WindowManager::isBlackListed(QWidget *widget)
0452 {
0453     // check against noAnimations propery
0454     QVariant propertyValue(widget->property(PropertyNames::noWindowGrab));
0455     if (propertyValue.isValid() && propertyValue.toBool())
0456         return true;
0457 
0458     // list-based blacklisted widgets
0459     QString appName(qApp->applicationName());
0460     for (const ExceptionId &id : std::as_const(_blackList)) {
0461         if (!id.appName().isEmpty() && id.appName() != appName)
0462             continue;
0463         if (id.className() == QStringLiteral("*") && !id.appName().isEmpty()) {
0464             // if application name matches and all classes are selected
0465             // disable the grabbing entirely
0466             setEnabled(false);
0467             return true;
0468         }
0469         if (widget->inherits(id.className().toLatin1().data()))
0470             return true;
0471     }
0472 
0473     return false;
0474 }
0475 
0476 //_____________________________________________________________
0477 bool WindowManager::isWhiteListed(QWidget *widget) const
0478 {
0479     QString appName(qApp->applicationName());
0480     for (const ExceptionId &id : std::as_const(_whiteList)) {
0481         if (!id.appName().isEmpty() && id.appName() != appName)
0482             continue;
0483         if (widget->inherits(id.className().toLatin1().data()))
0484             return true;
0485     }
0486 
0487     return false;
0488 }
0489 
0490 //_____________________________________________________________
0491 bool WindowManager::canDrag(QWidget *widget)
0492 {
0493     // check if enabled
0494     if (!enabled())
0495         return false;
0496 
0497     // assume isDragable widget is already passed
0498     // check some special cases where drag should not be effective
0499 
0500     // check mouse grabber
0501     if (QWidget::mouseGrabber())
0502         return false;
0503 
0504     /*
0505     check cursor shape.
0506     Assume that a changed cursor means that some action is in progress
0507     and should prevent the drag
0508     */
0509     if (widget->cursor().shape() != Qt::ArrowCursor)
0510         return false;
0511 
0512     // accept
0513     return true;
0514 }
0515 
0516 //_____________________________________________________________
0517 bool WindowManager::canDrag(QWidget *widget, QWidget *child, const QPoint &position)
0518 {
0519     // retrieve child at given position and check cursor again
0520     if (child && child->cursor().shape() != Qt::ArrowCursor)
0521         return false;
0522 
0523     /*
0524     check against children from which drag should never be enabled,
0525     even if mousePress/Move has been passed to the parent
0526     */
0527     if (child && (qobject_cast<QComboBox *>(child) || qobject_cast<QProgressBar *>(child) || qobject_cast<QScrollBar *>(child))) {
0528         return false;
0529     }
0530 
0531     // tool buttons
0532     if (QToolButton *toolButton = qobject_cast<QToolButton *>(widget)) {
0533         if (dragMode() == StyleConfigData::WD_MINIMAL && !qobject_cast<QToolBar *>(widget->parentWidget()))
0534             return false;
0535         return toolButton->autoRaise() && !toolButton->isEnabled();
0536     }
0537 
0538     // check menubar
0539     if (QMenuBar *menuBar = qobject_cast<QMenuBar *>(widget)) {
0540         // do not drag from menubars embedded in Mdi windows
0541         if (findParent<QMdiSubWindow *>(widget))
0542             return false;
0543 
0544         // check if there is an active action
0545         if (menuBar->activeAction() && menuBar->activeAction()->isEnabled())
0546             return false;
0547 
0548         // check if action at position exists and is enabled
0549         if (QAction *action = menuBar->actionAt(position)) {
0550             if (action->isSeparator())
0551                 return true;
0552             if (action->isEnabled())
0553                 return false;
0554         }
0555 
0556         // return true in all other cases
0557         return true;
0558     }
0559 
0560     /*
0561     in MINIMAL mode, anything that has not been already accepted
0562     and does not come from a toolbar is rejected
0563     */
0564     if (dragMode() == StyleConfigData::WD_MINIMAL) {
0565         if (qobject_cast<QToolBar *>(widget))
0566             return true;
0567         else
0568             return false;
0569     }
0570 
0571     /* following checks are relevant only for WD_FULL mode */
0572 
0573     // tabbar. Make sure no tab is under the cursor
0574     if (QTabBar *tabBar = qobject_cast<QTabBar *>(widget)) {
0575         return tabBar->tabAt(position) == -1;
0576     }
0577 
0578     /*
0579     check groupboxes
0580     prevent drag if unchecking grouboxes
0581     */
0582     if (QGroupBox *groupBox = qobject_cast<QGroupBox *>(widget)) {
0583         // non checkable group boxes are always ok
0584         if (!groupBox->isCheckable())
0585             return true;
0586 
0587         // gather options to retrieve checkbox subcontrol rect
0588         QStyleOptionGroupBox opt;
0589         opt.initFrom(groupBox);
0590         if (groupBox->isFlat())
0591             opt.features |= QStyleOptionFrame::Flat;
0592         opt.lineWidth = 1;
0593         opt.midLineWidth = 0;
0594         opt.text = groupBox->title();
0595         opt.textAlignment = groupBox->alignment();
0596         opt.subControls = (QStyle::SC_GroupBoxFrame | QStyle::SC_GroupBoxCheckBox);
0597         if (!groupBox->title().isEmpty())
0598             opt.subControls |= QStyle::SC_GroupBoxLabel;
0599 
0600         opt.state |= (groupBox->isChecked() ? QStyle::State_On : QStyle::State_Off);
0601 
0602         // check against groupbox checkbox
0603         if (groupBox->style()->subControlRect(QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxCheckBox, groupBox).contains(position)) {
0604             return false;
0605         }
0606 
0607         // check against groupbox label
0608         if (!groupBox->title().isEmpty()
0609             && groupBox->style()->subControlRect(QStyle::CC_GroupBox, &opt, QStyle::SC_GroupBoxLabel, groupBox).contains(position)) {
0610             return false;
0611         }
0612 
0613         return true;
0614     }
0615 
0616     // labels
0617     if (QLabel *label = qobject_cast<QLabel *>(widget)) {
0618         if (label->textInteractionFlags().testFlag(Qt::TextSelectableByMouse))
0619             return false;
0620     }
0621 
0622     // abstract item views
0623     QAbstractItemView *itemView(nullptr);
0624     if ((itemView = qobject_cast<QListView *>(widget->parentWidget())) || (itemView = qobject_cast<QTreeView *>(widget->parentWidget()))) {
0625         if (widget == itemView->viewport()) {
0626             // QListView
0627             if (itemView->frameShape() != QFrame::NoFrame)
0628                 return false;
0629             else if (itemView->selectionMode() != QAbstractItemView::NoSelection && itemView->selectionMode() != QAbstractItemView::SingleSelection
0630                      && itemView->model() && itemView->model()->rowCount())
0631                 return false;
0632             else if (itemView->model() && itemView->indexAt(position).isValid())
0633                 return false;
0634         }
0635 
0636     } else if ((itemView = qobject_cast<QAbstractItemView *>(widget->parentWidget()))) {
0637         if (widget == itemView->viewport()) {
0638             // QAbstractItemView
0639             if (itemView->frameShape() != QFrame::NoFrame)
0640                 return false;
0641             else if (itemView->indexAt(position).isValid())
0642                 return false;
0643         }
0644 
0645     } else if (QGraphicsView *graphicsView = qobject_cast<QGraphicsView *>(widget->parentWidget())) {
0646         if (widget == graphicsView->viewport()) {
0647             // QGraphicsView
0648             if (graphicsView->frameShape() != QFrame::NoFrame)
0649                 return false;
0650             else if (graphicsView->dragMode() != QGraphicsView::NoDrag)
0651                 return false;
0652             else if (graphicsView->itemAt(position))
0653                 return false;
0654         }
0655     }
0656 
0657     return true;
0658 }
0659 
0660 //____________________________________________________________
0661 void WindowManager::resetDrag(void)
0662 {
0663     if ((!useWMMoveResize()) && _target && _cursorOverride) {
0664         qApp->restoreOverrideCursor();
0665         _cursorOverride = false;
0666     }
0667 
0668     _target.clear();
0669     _quickTarget.clear();
0670     if (_dragTimer.isActive())
0671         _dragTimer.stop();
0672     _dragPoint = QPoint();
0673     _globalDragPoint = QPoint();
0674     _dragAboutToStart = false;
0675     _dragInProgress = false;
0676 }
0677 
0678 //____________________________________________________________
0679 void WindowManager::startDrag(QWindow *window)
0680 {
0681     if (!(enabled() && window))
0682         return;
0683     if (QWidget::mouseGrabber())
0684         return;
0685 
0686     _dragInProgress = window->startSystemMove();
0687 
0688     return;
0689 }
0690 
0691 //____________________________________________________________
0692 bool WindowManager::isDockWidgetTitle(const QWidget *widget) const
0693 {
0694     if (!widget)
0695         return false;
0696     if (const QDockWidget *dockWidget = qobject_cast<const QDockWidget *>(widget->parent())) {
0697         return widget == dockWidget->titleBarWidget();
0698 
0699     } else
0700         return false;
0701 }
0702 }