File indexing completed on 2024-04-28 05:26:22

0001 /*
0002  * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa <hugo.pereira@free.fr>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "breezesplitterproxy.h"
0008 
0009 #include "breezestyleconfigdata.h"
0010 
0011 #include <QCoreApplication>
0012 #include <QDebug>
0013 #include <QPainter>
0014 
0015 // Q_FALLTHROUGH() for Qt < 5.8
0016 #ifndef Q_FALLTHROUGH
0017 #if defined(__has_cpp_attribute)
0018 #if __has_cpp_attribute(fallthrough)
0019 #define Q_FALLTHROUGH() [[fallthrough]]
0020 #elif __has_cpp_attribute(clang::fallthrough)
0021 #define Q_FALLTHROUGH() [[clang::fallthrough]]
0022 #elif __has_cpp_attribute(gnu::fallthrough)
0023 #define Q_FALLTHROUGH() [[gnu::fallthrough]]
0024 #endif
0025 #endif
0026 #ifndef Q_FALLTHROUGH
0027 #if __GNUC__ >= 7
0028 #define Q_FALLTHROUGH() __attribute__((fallthrough))
0029 #elif (__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 5)
0030 #define Q_FALLTHROUGH() [[clang::fallthrough]]
0031 #else
0032 #define Q_FALLTHROUGH()
0033 #endif
0034 #endif
0035 #endif
0036 
0037 namespace Breeze
0038 {
0039 //____________________________________________________________________
0040 void SplitterFactory::setEnabled(bool value)
0041 {
0042     if (_enabled != value) {
0043         // store
0044         _enabled = value;
0045 
0046         // assign to existing splitters
0047         for (WidgetMap::iterator iter = _widgets.begin(); iter != _widgets.end(); ++iter) {
0048             if (iter.value()) {
0049                 iter.value().data()->setEnabled(value);
0050             }
0051         }
0052     }
0053 }
0054 
0055 //____________________________________________________________________
0056 bool SplitterFactory::registerWidget(QWidget *widget)
0057 {
0058     // check widget type
0059     if (qobject_cast<QMainWindow *>(widget)) {
0060         WidgetMap::iterator iter(_widgets.find(widget));
0061         if (iter == _widgets.end() || !iter.value()) {
0062             widget->installEventFilter(&_addEventFilter);
0063             SplitterProxy *proxy(new SplitterProxy(widget, _enabled));
0064             widget->removeEventFilter(&_addEventFilter);
0065 
0066             widget->installEventFilter(proxy);
0067             _widgets.insert(widget, proxy);
0068 
0069         } else {
0070             widget->removeEventFilter(iter.value().data());
0071             widget->installEventFilter(iter.value().data());
0072         }
0073 
0074         return true;
0075 
0076     } else if (qobject_cast<QSplitterHandle *>(widget)) {
0077         QWidget *window(widget->window());
0078         WidgetMap::iterator iter(_widgets.find(window));
0079         if (iter == _widgets.end() || !iter.value()) {
0080             window->installEventFilter(&_addEventFilter);
0081             SplitterProxy *proxy(new SplitterProxy(window, _enabled));
0082             window->removeEventFilter(&_addEventFilter);
0083 
0084             widget->installEventFilter(proxy);
0085             _widgets.insert(window, proxy);
0086 
0087         } else {
0088             widget->removeEventFilter(iter.value().data());
0089             widget->installEventFilter(iter.value().data());
0090         }
0091 
0092         return true;
0093 
0094     } else {
0095         return false;
0096     }
0097 }
0098 
0099 //____________________________________________________________________
0100 void SplitterFactory::unregisterWidget(QWidget *widget)
0101 {
0102     WidgetMap::iterator iter(_widgets.find(widget));
0103     if (iter != _widgets.end()) {
0104         if (iter.value()) {
0105             iter.value().data()->deleteLater();
0106         }
0107         _widgets.erase(iter);
0108     }
0109 }
0110 
0111 //____________________________________________________________________
0112 SplitterProxy::SplitterProxy(QWidget *parent, bool enabled)
0113     : QWidget(parent)
0114     , _enabled(enabled)
0115     , _timerId(0)
0116 {
0117     setAttribute(Qt::WA_TranslucentBackground, true);
0118     setAttribute(Qt::WA_OpaquePaintEvent, false);
0119     hide();
0120 }
0121 
0122 //____________________________________________________________________
0123 void SplitterProxy::setEnabled(bool value)
0124 {
0125     // make sure status has changed
0126     if (_enabled != value) {
0127         _enabled = value;
0128         if (!_enabled) {
0129             clearSplitter();
0130         }
0131     }
0132 }
0133 
0134 //____________________________________________________________________
0135 bool SplitterProxy::eventFilter(QObject *object, QEvent *event)
0136 {
0137     // do nothing if disabled
0138     if (!_enabled) {
0139         return false;
0140     }
0141 
0142     // do nothing in case of mouse grab
0143     if (mouseGrabber()) {
0144         return false;
0145     }
0146 
0147     switch (event->type()) {
0148     case QEvent::HoverEnter:
0149         if (!isVisible()) {
0150             // cast to splitter handle
0151             if (QSplitterHandle *handle = qobject_cast<QSplitterHandle *>(object)) {
0152                 setSplitter(handle);
0153             }
0154         }
0155 
0156         return false;
0157 
0158     case QEvent::HoverMove:
0159     case QEvent::HoverLeave:
0160         return isVisible() && object == _splitter.data();
0161 
0162     case QEvent::MouseMove:
0163     case QEvent::Timer:
0164     case QEvent::Move:
0165         return false;
0166 
0167     case QEvent::CursorChange:
0168         if (QWidget *window = qobject_cast<QMainWindow *>(object)) {
0169             if (window->cursor().shape() == Qt::SplitHCursor || window->cursor().shape() == Qt::SplitVCursor) {
0170                 setSplitter(window);
0171             }
0172         }
0173         return false;
0174 
0175     case QEvent::WindowDeactivate:
0176     case QEvent::MouseButtonRelease:
0177         clearSplitter();
0178         return false;
0179 
0180     default:
0181         return false;
0182     }
0183 }
0184 
0185 //____________________________________________________________________
0186 bool SplitterProxy::event(QEvent *event)
0187 {
0188     switch (event->type()) {
0189 #if 0
0190     case QEvent::Paint:
0191     {
0192         QPainter painter( this );
0193         painter.setClipRegion( static_cast<QPaintEvent*>( event )->region() );
0194         painter.setRenderHints( QPainter::Antialiasing );
0195         painter.setPen( Qt::red );
0196         painter.drawRect( QRectF( rect() ).adjusted( 0.5, 0.5, -0.5, -0.5 ) );
0197         return true;
0198     }
0199 #endif
0200 
0201     case QEvent::MouseMove:
0202     case QEvent::MouseButtonPress:
0203     case QEvent::MouseButtonRelease: {
0204         // check splitter
0205         if (!_splitter) {
0206             return false;
0207         }
0208 
0209         event->accept();
0210 
0211         // grab on mouse press
0212         if (event->type() == QEvent::MouseButtonPress) {
0213             grabMouse();
0214             resize(1, 1);
0215         }
0216 
0217         // cast to mouse event
0218         QMouseEvent *mouseEvent(static_cast<QMouseEvent *>(event));
0219 
0220         // get relevant position to post mouse drag event to application
0221         if (event->type() == QEvent::MouseButtonPress) {
0222             // use hook, to make sure splitter is properly dragged
0223             QMouseEvent copy(mouseEvent->type(),
0224                              _hook,
0225 #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
0226                              mouseEvent->globalPosition().toPoint(),
0227 #endif
0228                              mouseEvent->button(),
0229                              mouseEvent->buttons(),
0230                              mouseEvent->modifiers());
0231 
0232             QCoreApplication::sendEvent(_splitter.data(), &copy);
0233 
0234         } else {
0235             // map event position to current splitter and post.
0236             QMouseEvent copy(mouseEvent->type(),
0237 #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
0238                              _splitter.data()->mapFromGlobal(mouseEvent->globalPosition().toPoint()),
0239 #else
0240                              _splitter.data()->mapFromGlobal(mouseEvent->globalPos()),
0241 #endif
0242 #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
0243                              mouseEvent->globalPosition().toPoint(),
0244 #endif
0245                              mouseEvent->button(),
0246                              mouseEvent->buttons(),
0247                              mouseEvent->modifiers());
0248 
0249             QCoreApplication::sendEvent(_splitter.data(), &copy);
0250         }
0251 
0252         // release grab on mouse-Release
0253         if (event->type() == QEvent::MouseButtonRelease && mouseGrabber() == this) {
0254             releaseMouse();
0255         }
0256 
0257         return true;
0258     }
0259 
0260     case QEvent::Timer:
0261         if (static_cast<QTimerEvent *>(event)->timerId() != _timerId) {
0262             return QWidget::event(event);
0263         }
0264 
0265         /*
0266         Fall through is intended.
0267         We somehow lost a QEvent::Leave before timeout. We fix it from here
0268         */
0269 
0270         Q_FALLTHROUGH();
0271 
0272     case QEvent::HoverLeave:
0273     case QEvent::Leave: {
0274         if (mouseGrabber() == this) {
0275             return true;
0276         }
0277 
0278         // reset splitter
0279         if (isVisible() && !rect().contains(mapFromGlobal(QCursor::pos()))) {
0280             clearSplitter();
0281         }
0282         return true;
0283     }
0284 
0285     default:
0286         return QWidget::event(event);
0287     }
0288 }
0289 
0290 //____________________________________________________________________
0291 void SplitterProxy::setSplitter(QWidget *widget)
0292 {
0293     // check if changed
0294     if (_splitter.data() == widget) {
0295         return;
0296     }
0297 
0298     // get cursor position
0299     const QPoint position(QCursor::pos());
0300 
0301     // store splitter and hook
0302     _splitter = widget;
0303     _hook = _splitter.data()->mapFromGlobal(position);
0304 
0305     // adjust rect
0306     QRect rect(0, 0, 2 * StyleConfigData::splitterProxyWidth(), 2 * StyleConfigData::splitterProxyWidth());
0307     rect.moveCenter(parentWidget()->mapFromGlobal(position));
0308     setGeometry(rect);
0309     setCursor(_splitter.data()->cursor().shape());
0310 
0311     // show
0312     raise();
0313     show();
0314 
0315     // timer used to automatically hide proxy in case leave events are lost
0316     if (!_timerId) {
0317         _timerId = startTimer(150);
0318     }
0319 }
0320 
0321 //____________________________________________________________________
0322 void SplitterProxy::clearSplitter()
0323 {
0324     // check if changed
0325     if (!_splitter) {
0326         return;
0327     }
0328 
0329     // release mouse
0330     if (mouseGrabber() == this) {
0331         releaseMouse();
0332     }
0333 
0334     // send hover event
0335     if (_splitter) {
0336         // SplitterProxy intercepts HoverLeave/HoverMove events to _splitter,
0337         // but this is meant to reach it directly. Unset _splitter to stop interception.
0338         auto splitter = _splitter;
0339         _splitter.clear();
0340         QHoverEvent hoverEvent(qobject_cast<QSplitterHandle *>(splitter.data()) ? QEvent::HoverLeave : QEvent::HoverMove,
0341                                splitter.data()->mapFromGlobal(QCursor::pos()),
0342 #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)
0343                                splitter.data()->mapFromGlobal(QCursor::pos()),
0344 #endif
0345                                _hook);
0346         QCoreApplication::sendEvent(splitter.data(), &hoverEvent);
0347     }
0348 
0349     // kill timer if any
0350     if (_timerId) {
0351         killTimer(_timerId);
0352         _timerId = 0;
0353     }
0354 
0355     // hide
0356     parentWidget()->setUpdatesEnabled(false);
0357     // Note: This sends a synthetic mouse event to the widget below (to get focus), which might be
0358     // another SplitterHandle, therefore enabling this SplitterProxy again!
0359     hide();
0360     parentWidget()->setUpdatesEnabled(true);
0361 }
0362 
0363 }