File indexing completed on 2024-05-12 16:58:29

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(), _hook, mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers());
0224 
0225             QCoreApplication::sendEvent(_splitter.data(), &copy);
0226 
0227         } else {
0228             // map event position to current splitter and post.
0229             QMouseEvent copy(mouseEvent->type(),
0230                              _splitter.data()->mapFromGlobal(mouseEvent->globalPos()),
0231                              mouseEvent->button(),
0232                              mouseEvent->buttons(),
0233                              mouseEvent->modifiers());
0234 
0235             QCoreApplication::sendEvent(_splitter.data(), &copy);
0236         }
0237 
0238         // release grab on mouse-Release
0239         if (event->type() == QEvent::MouseButtonRelease && mouseGrabber() == this) {
0240             releaseMouse();
0241         }
0242 
0243         return true;
0244     }
0245 
0246     case QEvent::Timer:
0247         if (static_cast<QTimerEvent *>(event)->timerId() != _timerId) {
0248             return QWidget::event(event);
0249         }
0250 
0251         /*
0252         Fall through is intended.
0253         We somehow lost a QEvent::Leave before timeout. We fix it from here
0254         */
0255 
0256         Q_FALLTHROUGH();
0257 
0258     case QEvent::HoverLeave:
0259     case QEvent::Leave: {
0260         if (mouseGrabber() == this) {
0261             return true;
0262         }
0263 
0264         // reset splitter
0265         if (isVisible() && !rect().contains(mapFromGlobal(QCursor::pos()))) {
0266             clearSplitter();
0267         }
0268         return true;
0269     }
0270 
0271     default:
0272         return QWidget::event(event);
0273     }
0274 }
0275 
0276 //____________________________________________________________________
0277 void SplitterProxy::setSplitter(QWidget *widget)
0278 {
0279     // check if changed
0280     if (_splitter.data() == widget) {
0281         return;
0282     }
0283 
0284     // get cursor position
0285     const QPoint position(QCursor::pos());
0286 
0287     // store splitter and hook
0288     _splitter = widget;
0289     _hook = _splitter.data()->mapFromGlobal(position);
0290 
0291     // adjust rect
0292     QRect rect(0, 0, 2 * StyleConfigData::splitterProxyWidth(), 2 * StyleConfigData::splitterProxyWidth());
0293     rect.moveCenter(parentWidget()->mapFromGlobal(position));
0294     setGeometry(rect);
0295     setCursor(_splitter.data()->cursor().shape());
0296 
0297     // show
0298     raise();
0299     show();
0300 
0301     // timer used to automatically hide proxy in case leave events are lost
0302     if (!_timerId) {
0303         _timerId = startTimer(150);
0304     }
0305 }
0306 
0307 //____________________________________________________________________
0308 void SplitterProxy::clearSplitter()
0309 {
0310     // check if changed
0311     if (!_splitter) {
0312         return;
0313     }
0314 
0315     // release mouse
0316     if (mouseGrabber() == this) {
0317         releaseMouse();
0318     }
0319 
0320     // send hover event
0321     if (_splitter) {
0322         // SplitterProxy intercepts HoverLeave/HoverMove events to _splitter,
0323         // but this is meant to reach it directly. Unset _splitter to stop interception.
0324         auto splitter = _splitter;
0325         _splitter.clear();
0326         QHoverEvent hoverEvent(qobject_cast<QSplitterHandle *>(splitter.data()) ? QEvent::HoverLeave : QEvent::HoverMove,
0327                                splitter.data()->mapFromGlobal(QCursor::pos()),
0328                                _hook);
0329         QCoreApplication::sendEvent(splitter.data(), &hoverEvent);
0330     }
0331 
0332     // kill timer if any
0333     if (_timerId) {
0334         killTimer(_timerId);
0335         _timerId = 0;
0336     }
0337 
0338     // hide
0339     parentWidget()->setUpdatesEnabled(false);
0340     // Note: This sends a synthetic mouse event to the widget below (to get focus), which might be
0341     // another SplitterHandle, therefore enabling this SplitterProxy again!
0342     hide();
0343     parentWidget()->setUpdatesEnabled(true);
0344 }
0345 
0346 }