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(), ©); 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(), ©); 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 }