/* * SPDX-FileCopyrightText: 2014 Hugo Pereira Da Costa * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "breezesplitterproxy.h" #include "breezestyleconfigdata.h" #include #include #include // Q_FALLTHROUGH() for Qt < 5.8 #ifndef Q_FALLTHROUGH #if defined(__has_cpp_attribute) #if __has_cpp_attribute(fallthrough) #define Q_FALLTHROUGH() [[fallthrough]] #elif __has_cpp_attribute(clang::fallthrough) #define Q_FALLTHROUGH() [[clang::fallthrough]] #elif __has_cpp_attribute(gnu::fallthrough) #define Q_FALLTHROUGH() [[gnu::fallthrough]] #endif #endif #ifndef Q_FALLTHROUGH #if __GNUC__ >= 7 #define Q_FALLTHROUGH() __attribute__((fallthrough)) #elif (__clang_major__ > 3) || (__clang_major__ == 3 && __clang_minor__ >= 5) #define Q_FALLTHROUGH() [[clang::fallthrough]] #else #define Q_FALLTHROUGH() #endif #endif #endif namespace Breeze { SplitterFactory::SplitterFactory() : QObject() , _enabled(false) { } //____________________________________________________________________ void SplitterFactory::setEnabled(bool value) { if (_enabled != value) { // store _enabled = value; // assign to existing splitters for (WidgetMap::iterator iter = _widgets.begin(); iter != _widgets.end(); ++iter) { if (iter.value()) { iter.value().data()->setEnabled(value); } } } } //____________________________________________________________________ bool SplitterFactory::registerWidget(QWidget *widget) { // check widget type if (qobject_cast(widget)) { WidgetMap::iterator iter(_widgets.find(widget)); if (iter == _widgets.end() || !iter.value()) { widget->installEventFilter(&_addEventFilter); SplitterProxy *proxy(new SplitterProxy(widget, _enabled)); widget->removeEventFilter(&_addEventFilter); widget->installEventFilter(proxy); _widgets.insert(widget, proxy); } else { widget->removeEventFilter(iter.value().data()); widget->installEventFilter(iter.value().data()); } return true; } else if (qobject_cast(widget)) { QWidget *window(widget->window()); WidgetMap::iterator iter(_widgets.find(window)); if (iter == _widgets.end() || !iter.value()) { window->installEventFilter(&_addEventFilter); SplitterProxy *proxy(new SplitterProxy(window, _enabled)); window->removeEventFilter(&_addEventFilter); widget->installEventFilter(proxy); _widgets.insert(window, proxy); } else { widget->removeEventFilter(iter.value().data()); widget->installEventFilter(iter.value().data()); } return true; } else { return false; } } //____________________________________________________________________ void SplitterFactory::unregisterWidget(QWidget *widget) { WidgetMap::iterator iter(_widgets.find(widget)); if (iter != _widgets.end()) { if (iter.value()) { iter.value().data()->deleteLater(); } _widgets.erase(iter); } } //____________________________________________________________________ SplitterProxy::SplitterProxy(QWidget *parent, bool enabled) : QWidget(parent) , _enabled(enabled) , _timerId(0) { setAttribute(Qt::WA_TranslucentBackground, true); setAttribute(Qt::WA_OpaquePaintEvent, false); hide(); } //____________________________________________________________________ void SplitterProxy::setEnabled(bool value) { // make sure status has changed if (_enabled != value) { _enabled = value; if (!_enabled) { clearSplitter(); } } } //____________________________________________________________________ bool SplitterProxy::eventFilter(QObject *object, QEvent *event) { // do nothing if disabled if (!_enabled) { return false; } // do nothing in case of mouse grab if (mouseGrabber()) { return false; } switch (event->type()) { case QEvent::HoverEnter: if (!isVisible()) { // cast to splitter handle if (QSplitterHandle *handle = qobject_cast(object)) { setSplitter(handle); } } return false; case QEvent::HoverMove: case QEvent::HoverLeave: return isVisible() && object == _splitter.data(); case QEvent::MouseMove: case QEvent::Timer: case QEvent::Move: return false; case QEvent::CursorChange: if (QWidget *window = qobject_cast(object)) { if (window->cursor().shape() == Qt::SplitHCursor || window->cursor().shape() == Qt::SplitVCursor) { setSplitter(window); } } return false; case QEvent::WindowDeactivate: case QEvent::MouseButtonRelease: clearSplitter(); return false; default: return false; } } //____________________________________________________________________ bool SplitterProxy::event(QEvent *event) { switch (event->type()) { #if 0 case QEvent::Paint: { QPainter painter( this ); painter.setClipRegion( static_cast( event )->region() ); painter.setRenderHints( QPainter::Antialiasing ); painter.setPen( Qt::red ); painter.drawRect( QRectF( rect() ).adjusted( 0.5, 0.5, -0.5, -0.5 ) ); return true; } #endif case QEvent::MouseMove: case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: { // check splitter if (!_splitter) { return false; } event->accept(); // grab on mouse press if (event->type() == QEvent::MouseButtonPress) { grabMouse(); resize(1, 1); } // cast to mouse event QMouseEvent *mouseEvent(static_cast(event)); // get relevant position to post mouse drag event to application if (event->type() == QEvent::MouseButtonPress) { // use hook, to make sure splitter is properly dragged QMouseEvent copy(mouseEvent->type(), _hook, #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) mouseEvent->globalPosition().toPoint(), #endif mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()); QCoreApplication::sendEvent(_splitter.data(), ©); } else { // map event position to current splitter and post. QMouseEvent copy(mouseEvent->type(), #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) _splitter.data()->mapFromGlobal(mouseEvent->globalPosition().toPoint()), #else _splitter.data()->mapFromGlobal(mouseEvent->globalPos()), #endif #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) mouseEvent->globalPosition().toPoint(), #endif mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()); QCoreApplication::sendEvent(_splitter.data(), ©); } // release grab on mouse-Release if (event->type() == QEvent::MouseButtonRelease && mouseGrabber() == this) { releaseMouse(); } return true; } case QEvent::Timer: if (static_cast(event)->timerId() != _timerId) { return QWidget::event(event); } /* Fall through is intended. We somehow lost a QEvent::Leave before timeout. We fix it from here */ Q_FALLTHROUGH(); case QEvent::HoverLeave: case QEvent::Leave: { if (mouseGrabber() == this) { return true; } // reset splitter if (isVisible() && !rect().contains(mapFromGlobal(QCursor::pos()))) { clearSplitter(); } return true; } default: return QWidget::event(event); } } //____________________________________________________________________ void SplitterProxy::setSplitter(QWidget *widget) { // check if changed if (_splitter.data() == widget) { return; } // get cursor position const QPoint position(QCursor::pos()); // store splitter and hook _splitter = widget; _hook = _splitter.data()->mapFromGlobal(position); // adjust rect QRect rect(0, 0, 2 * StyleConfigData::splitterProxyWidth(), 2 * StyleConfigData::splitterProxyWidth()); rect.moveCenter(parentWidget()->mapFromGlobal(position)); setGeometry(rect); setCursor(_splitter.data()->cursor().shape()); // show raise(); show(); // timer used to automatically hide proxy in case leave events are lost if (!_timerId) { _timerId = startTimer(150); } } //____________________________________________________________________ void SplitterProxy::clearSplitter() { // check if changed if (!_splitter) { return; } // release mouse if (mouseGrabber() == this) { releaseMouse(); } // send hover event if (_splitter) { // SplitterProxy intercepts HoverLeave/HoverMove events to _splitter, // but this is meant to reach it directly. Unset _splitter to stop interception. auto splitter = _splitter; _splitter.clear(); QHoverEvent hoverEvent(qobject_cast(splitter.data()) ? QEvent::HoverLeave : QEvent::HoverMove, splitter.data()->mapFromGlobal(QCursor::pos()), #if QT_VERSION >= QT_VERSION_CHECK(6, 4, 0) splitter.data()->mapFromGlobal(QCursor::pos()), #endif _hook); QCoreApplication::sendEvent(splitter.data(), &hoverEvent); } // kill timer if any if (_timerId) { killTimer(_timerId); _timerId = 0; } // hide parentWidget()->setUpdatesEnabled(false); // Note: This sends a synthetic mouse event to the widget below (to get focus), which might be // another SplitterHandle, therefore enabling this SplitterProxy again! hide(); parentWidget()->setUpdatesEnabled(true); } }