/**************************************************************************** * Copyright (c) 2022 Zheng Lei (realthunder) * * * * This file is part of the FreeCAD CAx development system. * * * * This library is free software; you can redistribute it and/or * * modify it under the terms of the GNU Library General Public * * License as published by the Free Software Foundation; either * * version 2 of the License, or (at your option) any later version. * * * * This library is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU Library General Public License for more details. * * * * You should have received a copy of the GNU Library General Public * * License along with this library; see the file COPYING.LIB. If not, * * write to the Free Software Foundation, Inc., 59 Temple Place, * * Suite 330, Boston, MA 02111-1307, USA * * * ****************************************************************************/ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "OverlayManager.h" #include #include #include #include #include "Application.h" #include "BitmapFactory.h" #include "Control.h" #include "MainWindow.h" #include "MDIView.h" #include "NaviCube.h" #include "OverlayParams.h" #include "OverlayWidgets.h" #include "TaskView/TaskView.h" #include "Tree.h" #include "TreeParams.h" #include "View3DInventorViewer.h" FC_LOG_LEVEL_INIT("Dock", true, true); using namespace Gui; static std::array _Overlays; static inline OverlayTabWidget* findTabWidget(QWidget* widget = nullptr, bool filterDialog = false) { if (!widget) { widget = qApp->focusWidget(); } for (auto w = widget; w; w = w->parentWidget()) { auto tabWidget = qobject_cast(w); if (tabWidget) { return tabWidget; } auto proxy = qobject_cast(w); if (proxy) { return proxy->getOwner(); } if (filterDialog && w->windowType() != Qt::Widget) { break; } } return nullptr; } class OverlayStyleSheet: public ParameterGrp::ObserverType { public: OverlayStyleSheet() { handle = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/MainWindow" ); update(); handle->Attach(this); } static OverlayStyleSheet* instance() { static OverlayStyleSheet* instance; if (!instance) { instance = new OverlayStyleSheet; } return instance; } void OnChange(Base::Subject&, const char* sReason) { if (!sReason) { return; } if (strcmp(sReason, "StyleSheet") == 0 || strcmp(sReason, "OverlayActiveStyleSheet") == 0) { OverlayManager::instance()->refresh(nullptr, true); } } void update() { activeStyleSheet.clear(); QString overlayStylesheetFileName = detectOverlayStyleSheetFileName(); loadFromFile(overlayStylesheetFileName); // If after loading the result is still empty we need to apply some defaults if (activeStyleSheet.isEmpty()) { activeStyleSheet = _default; } activeStyleSheet = Application::Instance->replaceVariablesInQss(activeStyleSheet); } ParameterGrp::handle handle; QString activeStyleSheet; bool hideTab = false; private: QString detectOverlayStyleSheetFileName() const { QString mainStyleSheet = QString::fromUtf8(handle->GetASCII("StyleSheet").c_str()); QString overlayStyleSheet = QString::fromUtf8( handle->GetASCII("OverlayActiveStyleSheet").c_str() ); if (overlayStyleSheet.isEmpty()) { // User did not choose any stylesheet, we need to choose one based on main stylesheet if (mainStyleSheet.contains(QStringLiteral("light"), Qt::CaseInsensitive)) { overlayStyleSheet = QStringLiteral("overlay:Light Theme + Dark Background.qss"); } else { // by default FreeCAD uses somewhat dark background for 3D View. // if user has not explicitly selected light theme, the "Dark Outline" looks best overlayStyleSheet = QStringLiteral("overlay:Dark Theme + Dark Background.qss"); } } else if (!overlayStyleSheet.isEmpty() && !QFile::exists(overlayStyleSheet)) { // User did choose one of predefined stylesheets, we need to qualify it with namespace overlayStyleSheet = QStringLiteral("overlay:%1").arg(overlayStyleSheet); } return overlayStyleSheet; } void loadFromFile(const QString& name) { if (!QFile::exists(name)) { return; } QFile file(name); if (file.open(QFile::ReadOnly)) { activeStyleSheet = QTextStream(&file).readAll(); } } static const QString _default; }; const QString OverlayStyleSheet::_default = QStringLiteral("overlay:Light Theme + Dark Background.qss"); // ----------------------------------------------------------- struct OverlayInfo { const char* name; OverlayTabWidget* tabWidget; Qt::DockWidgetArea dockArea; std::unordered_map& overlayMap; ParameterGrp::handle hGrp; fastsignals::scoped_connection conn; OverlayInfo( QWidget* parent, const char* name, Qt::DockWidgetArea pos, std::unordered_map& map ) : name(name) , dockArea(pos) , overlayMap(map) { tabWidget = new OverlayTabWidget(parent, dockArea); tabWidget->setObjectName(QString::fromUtf8(name)); tabWidget->getProxyWidget()->setObjectName(tabWidget->objectName() + QStringLiteral("Proxy")); tabWidget->setMovable(true); hGrp = App::GetApplication() .GetUserParameter() .GetGroup("BaseApp") ->GetGroup("MainWindow") ->GetGroup("DockWindows") ->GetGroup(name); conn = App::GetApplication().GetUserParameter().signalParamChanged.connect( [this](ParameterGrp* Param, ParameterGrp::ParamType, const char* Name, const char*) { if (hGrp == Param && Name && !tabWidget->isSaving()) { // This will prevent saving settings which will mess up the // just restored ones tabWidget->restore(nullptr); OverlayManager::instance()->reload(); } } ); } bool addWidget(QDockWidget* dock, bool forced = true) { if (!dock) { return false; } if (tabWidget->dockWidgetIndex(dock) >= 0) { return false; } overlayMap[dock] = this; bool visible = dock->isVisible(); auto focus = qApp->focusWidget(); if (focus && findTabWidget(focus) != tabWidget) { focus = nullptr; } tabWidget->addWidget(dock, dock->windowTitle()); if (focus) { tabWidget->setCurrent(dock); focus = qApp->focusWidget(); if (focus) { focus->clearFocus(); } } if (forced) { auto mw = getMainWindow(); for (auto d : mw->findChildren()) { if (mw->dockWidgetArea(d) == dockArea && d->toggleViewAction()->isChecked()) { addWidget(d, false); } } if (visible) { dock->show(); tabWidget->setCurrent(dock); } } else { tabWidget->saveTabs(); } return true; } void removeWidget() { if (!tabWidget->count()) { return; } tabWidget->hide(); QPointer focus = qApp->focusWidget(); QDockWidget* lastDock = tabWidget->currentDockWidget(); if (lastDock) { tabWidget->removeWidget(lastDock); } while (tabWidget->count()) { QDockWidget* dock = tabWidget->dockWidget(0); if (!dock) { tabWidget->removeTab(0); continue; } tabWidget->removeWidget(dock, lastDock); lastDock = dock; } if (focus) { focus->setFocus(); } tabWidget->saveTabs(); } void save() {} void restore() { tabWidget->restore(hGrp); for (int i = 0, c = tabWidget->count(); i < c; ++i) { auto dock = tabWidget->dockWidget(i); if (dock) { overlayMap[dock] = this; } } } }; enum class ToggleMode { Unset, Set, Toggle, Transparent, Check, }; class OverlayManager::Private { public: QPointer lastIntercept; QTimer _timer; QTimer _reloadTimer; bool mouseTransparent = false; bool intercepting = false; std::unordered_map _overlayMap; OverlayInfo _left; OverlayInfo _right; OverlayInfo _top; OverlayInfo _bottom; std::array _overlayInfos; QCursor _cursor; QPoint _lastPos; QAction _actClose; QAction _actFloat; QAction _actOverlay; QList _actions; QPointer _trackingWidget; OverlayTabWidget* _trackingOverlay = nullptr; bool updateStyle = false; QTime wheelDelay; QPoint wheelPos; std::map _dockWidgetNameMap; bool raising = false; OverlayManager::ReloadMode curReloadMode = OverlayManager::ReloadMode::ReloadPending; Private(OverlayManager* host, QWidget* parent) : _left(parent, "OverlayLeft", Qt::LeftDockWidgetArea, _overlayMap) , _right(parent, "OverlayRight", Qt::RightDockWidgetArea, _overlayMap) , _top(parent, "OverlayTop", Qt::TopDockWidgetArea, _overlayMap) , _bottom(parent, "OverlayBottom", Qt::BottomDockWidgetArea, _overlayMap) , _overlayInfos({&_left, &_right, &_top, &_bottom}) , _actions({&_actOverlay, &_actFloat, &_actClose}) { _Overlays = { OverlayTabWidget::_LeftOverlay, OverlayTabWidget::_RightOverlay, OverlayTabWidget::_TopOverlay, OverlayTabWidget::_BottomOverlay }; connect(&_timer, &QTimer::timeout, [this]() { onTimer(); }); _timer.setSingleShot(true); _reloadTimer.setSingleShot(true); connect(&_reloadTimer, &QTimer::timeout, [this]() { for (auto& o : _overlayInfos) { o->tabWidget->restore(nullptr); // prevent saving setting first o->removeWidget(); } for (auto& o : _overlayInfos) { o->restore(); } refresh(); }); connect(qApp, &QApplication::focusChanged, host, &OverlayManager::onFocusChanged); Application::Instance->signalActivateView.connect([this](const MDIView*) { refresh(); }); Application::Instance->signalInEdit.connect([this](const ViewProviderDocumentObject&) { refresh(); }); Application::Instance->signalResetEdit.connect([this](const ViewProviderDocumentObject&) { refresh(); }); Application::Instance->signalActivateWorkbench.connect([this](const char*) { refresh(); }); _actOverlay.setData(QStringLiteral("OBTN Overlay")); _actFloat.setData(QStringLiteral("OBTN Float")); _actClose.setData(QStringLiteral("OBTN Close")); retranslate(); refreshIcons(); for (auto action : _actions) { QObject::connect(action, &QAction::triggered, host, &OverlayManager::onAction); } for (auto o : _overlayInfos) { for (auto action : o->tabWidget->actions()) { QObject::connect(action, &QAction::triggered, host, &OverlayManager::onAction); } o->tabWidget->setTitleBar(createTitleBar(o->tabWidget)); } QIcon px = BitmapFactory().pixmap("cursor-through"); _cursor = QCursor(px.pixmap(32, 32), 10, 9); } void refreshIcons() { _actFloat.setIcon(BitmapFactory().pixmap("qss:overlay/icons/float.svg")); _actOverlay.setIcon(BitmapFactory().pixmap("qss:overlay/icons/overlay.svg")); _actClose.setIcon(BitmapFactory().pixmap("qss:overlay/icons/close.svg")); for (OverlayTabWidget* tabWidget : _Overlays) { tabWidget->refreshIcons(); for (auto handle : tabWidget->findChildren()) { handle->refreshIcons(); } } } void interceptEvent(QWidget*, QEvent*); void setMouseTransparent(bool enabled) { if (mouseTransparent == enabled) { return; } mouseTransparent = enabled; for (OverlayTabWidget* tabWidget : _Overlays) { tabWidget->setAttribute(Qt::WA_TransparentForMouseEvents, enabled); tabWidget->touch(); } refresh(); if (!enabled) { qApp->restoreOverrideCursor(); } else { qApp->setOverrideCursor(_cursor); } } bool toggleOverlay(QDockWidget* dock, ToggleMode toggle, int dockPos = Qt::NoDockWidgetArea) { if (!dock) { return false; } auto it = _overlayMap.find(dock); if (it != _overlayMap.end()) { auto o = it->second; switch (toggle) { case ToggleMode::Transparent: o->tabWidget->setTransparent(!o->tabWidget->isTransparent()); break; case ToggleMode::Unset: case ToggleMode::Toggle: _overlayMap.erase(it); o->tabWidget->removeWidget(dock); return false; default: break; } return true; } if (toggle == ToggleMode::Unset) { return false; } if (dockPos == Qt::NoDockWidgetArea) { dockPos = getMainWindow()->dockWidgetArea(dock); } OverlayInfo* o; switch (dockPos) { case Qt::LeftDockWidgetArea: o = &_left; break; case Qt::RightDockWidgetArea: o = &_right; break; case Qt::TopDockWidgetArea: o = &_top; break; case Qt::BottomDockWidgetArea: o = &_bottom; break; default: return false; } if (toggle == ToggleMode::Check && !o->tabWidget->count()) { return false; } if (o->addWidget(dock)) { if (toggle == ToggleMode::Transparent) { o->tabWidget->setTransparent(true); } } refresh(); return true; } void refresh(QWidget* widget = nullptr, bool refreshStyle = false) { if (refreshStyle) { OverlayStyleSheet::instance()->update(); updateStyle = true; } if (widget) { auto tabWidget = findTabWidget(widget); if (tabWidget && tabWidget->count()) { for (auto o : _overlayInfos) { if (tabWidget == o->tabWidget) { tabWidget->touch(); break; } } } } _timer.start(OverlayParams::getDockOverlayDelay()); } void save() { _left.save(); _right.save(); _top.save(); _bottom.save(); } void restore() { _left.restore(); _right.restore(); _top.restore(); _bottom.restore(); refresh(); } void onTimer() { auto mdi = getMainWindow() ? getMainWindow()->getMdiArea() : nullptr; if (!mdi) { return; } auto focus = findTabWidget(qApp->focusWidget()); if (focus && !focus->getSplitter()->isVisible()) { focus = nullptr; } auto active = findTabWidget(qApp->widgetAt(QCursor::pos())); if (active && !active->getSplitter()->isVisible()) { active = nullptr; } OverlayTabWidget* reveal = nullptr; bool updateFocus = false; bool updateActive = false; for (auto o : _overlayInfos) { if (o->tabWidget->isTouched() || updateStyle) { if (o->tabWidget == focus) { updateFocus = true; } else if (o->tabWidget == active) { updateActive = true; } else { o->tabWidget->setOverlayMode(true); } } if (!o->tabWidget->getRevealTime().isNull()) { if (o->tabWidget->getRevealTime() <= QTime::currentTime()) { o->tabWidget->setRevealTime(QTime()); } else { reveal = o->tabWidget; } } } updateStyle = false; if (focus) { if (focus->isOverlaid(OverlayTabWidget::QueryOption::TransparencyChanged) || updateFocus) { focus->setOverlayMode(false); focus->raise(); if (reveal == focus) { reveal = nullptr; } } else { focus->updateSplitterHandles(); } } if (active) { if (active != focus && (active->isOverlaid(OverlayTabWidget::QueryOption::TransparencyChanged) || updateActive)) { active->setOverlayMode(false); } active->raise(); if (reveal == active) { reveal = nullptr; } } if (reveal && !reveal->splitter->isVisible()) { reveal->setOverlayMode(false); reveal->raise(); } for (auto o : _overlayInfos) { if (o->tabWidget != focus && o->tabWidget != active && o->tabWidget != reveal && o->tabWidget->count() && !o->tabWidget->isOverlaid(OverlayTabWidget::QueryOption::TransparencyNotChanged)) { o->tabWidget->setOverlayMode(true); } } int w = mdi->geometry().width(); int h = mdi->geometry().height(); auto tabbar = mdi->findChild(); if (tabbar) { h -= tabbar->height(); } int naviCubeSize = NaviCube::getNaviCubeSize(); int naviCorner = OverlayParams::getDockOverlayCheckNaviCube() ? App::GetApplication() .GetParameterGroupByPath("User parameter:BaseApp/Preferences/NaviCube") ->GetInt("CornerNaviCube", 1) : -1; QRect rect; QRect rectBottom(0, 0, 0, 0); rect = _bottom.tabWidget->getRect(); QSize ofs = _bottom.tabWidget->getOffset(); int delta = _bottom.tabWidget->getSizeDelta(); h -= ofs.height(); const int paddedCubeSize = naviCubeSize + 10; if (naviCorner == 2) { ofs.setWidth(ofs.width() + paddedCubeSize); } int bw = w - 10 - ofs.width() - delta; if (naviCorner == 3) { bw -= paddedCubeSize; } if (bw < 10) { bw = 10; } // Bottom width is maintain the same to reduce QTextEdit re-layout // which may be expensive if there are lots of text, e.g. for // ReportView or PythonConsole. _bottom.tabWidget->setRect(QRect(ofs.width(), h - rect.height(), bw, rect.height())); if (_bottom.tabWidget->count() && _bottom.tabWidget->isVisible() && _bottom.tabWidget->getState() <= OverlayTabWidget::State::Normal) { rectBottom = _bottom.tabWidget->getRect(); } QRect rectLeft(0, 0, 0, 0); rect = _left.tabWidget->getRect(); ofs = _left.tabWidget->getOffset(); if (naviCorner == 0) { ofs.setWidth(ofs.width() + paddedCubeSize); } delta = _left.tabWidget->getSizeDelta() + rectBottom.height(); if (naviCorner == 2 && paddedCubeSize > rectBottom.height()) { delta += paddedCubeSize - rectBottom.height(); } int lh = std::max(h - ofs.width() - delta, 10); _left.tabWidget->setRect(QRect(ofs.height(), ofs.width(), rect.width(), lh)); if (_left.tabWidget->count() && _left.tabWidget->isVisible() && _left.tabWidget->getState() <= OverlayTabWidget::State::Normal) { rectLeft = _left.tabWidget->getRect(); } QRect rectRight(0, 0, 0, 0); rect = _right.tabWidget->getRect(); ofs = _right.tabWidget->getOffset(); if (naviCorner == 1) { ofs.setWidth(ofs.width() + paddedCubeSize); } delta = _right.tabWidget->getSizeDelta() + rectBottom.height(); if (naviCorner == 3 && paddedCubeSize > rectBottom.height()) { delta += paddedCubeSize - rectBottom.height(); } int rh = std::max(h - ofs.width() - delta, 10); w -= ofs.height(); _right.tabWidget->setRect(QRect(w - rect.width(), ofs.width(), rect.width(), rh)); if (_right.tabWidget->count() && _right.tabWidget->isVisible() && _right.tabWidget->getState() <= OverlayTabWidget::State::Normal) { rectRight = _right.tabWidget->getRect(); } rect = _top.tabWidget->getRect(); ofs = _top.tabWidget->getOffset(); delta = _top.tabWidget->getSizeDelta(); if (naviCorner == 0) { rectLeft.setWidth(std::max(rectLeft.width(), paddedCubeSize)); } else if (naviCorner == 1) { rectRight.setWidth(std::max(rectRight.width(), paddedCubeSize)); } int tw = w - rectLeft.width() - rectRight.width() - ofs.width() - delta; _top.tabWidget->setRect(QRect(rectLeft.width() - ofs.width(), ofs.height(), tw, rect.height())); } void setOverlayMode(OverlayMode mode) { switch (mode) { case OverlayManager::OverlayMode::DisableAll: case OverlayManager::OverlayMode::EnableAll: { auto docks = getMainWindow()->findChildren(); // put visible dock widget first std::sort(docks.begin(), docks.end(), [](const QDockWidget* a, const QDockWidget* b) { return !a->visibleRegion().isEmpty() && b->visibleRegion().isEmpty(); }); for (auto dock : docks) { if (mode == OverlayManager::OverlayMode::DisableAll) { toggleOverlay(dock, ToggleMode::Unset); } else { toggleOverlay(dock, ToggleMode::Set); } } return; } case OverlayManager::OverlayMode::ToggleAll: for (auto o : _overlayInfos) { if (o->tabWidget->count()) { setOverlayMode(OverlayManager::OverlayMode::DisableAll); return; } } setOverlayMode(OverlayManager::OverlayMode::EnableAll); return; case OverlayManager::OverlayMode::TransparentAll: { bool found = false; for (auto o : _overlayInfos) { if (o->tabWidget->count()) { found = true; } } if (!found) { setOverlayMode(OverlayManager::OverlayMode::EnableAll); } } // fall through case OverlayManager::OverlayMode::TransparentNone: for (auto o : _overlayInfos) { o->tabWidget->setTransparent(mode == OverlayManager::OverlayMode::TransparentAll); } refresh(); return; case OverlayManager::OverlayMode::ToggleTransparentAll: for (auto o : _overlayInfos) { if (o->tabWidget->count() && o->tabWidget->isTransparent()) { setOverlayMode(OverlayManager::OverlayMode::TransparentNone); return; } } setOverlayMode(OverlayManager::OverlayMode::TransparentAll); return; case OverlayManager::OverlayMode::ToggleLeft: if (OverlayTabWidget::_LeftOverlay->isVisible()) { OverlayTabWidget::_LeftOverlay->setState(OverlayTabWidget::State::Hidden); } else { OverlayTabWidget::_LeftOverlay->setState(OverlayTabWidget::State::Showing); } break; case OverlayManager::OverlayMode::ToggleRight: if (OverlayTabWidget::_RightOverlay->isVisible()) { OverlayTabWidget::_RightOverlay->setState(OverlayTabWidget::State::Hidden); } else { OverlayTabWidget::_RightOverlay->setState(OverlayTabWidget::State::Showing); } break; case OverlayManager::OverlayMode::ToggleTop: if (OverlayTabWidget::_TopOverlay->isVisible()) { OverlayTabWidget::_TopOverlay->setState(OverlayTabWidget::State::Hidden); } else { OverlayTabWidget::_TopOverlay->setState(OverlayTabWidget::State::Showing); } break; case OverlayManager::OverlayMode::ToggleBottom: if (OverlayTabWidget::_BottomOverlay->isVisible()) { OverlayTabWidget::_BottomOverlay->setState(OverlayTabWidget::State::Hidden); } else { OverlayTabWidget::_BottomOverlay->setState(OverlayTabWidget::State::Showing); } break; default: break; } ToggleMode m; QDockWidget* dock = nullptr; for (auto w = qApp->widgetAt(QCursor::pos()); w; w = w->parentWidget()) { dock = qobject_cast(w); if (dock) { break; } auto tabWidget = qobject_cast(w); if (tabWidget) { dock = tabWidget->currentDockWidget(); if (dock) { break; } } } if (!dock) { for (auto w = qApp->focusWidget(); w; w = w->parentWidget()) { dock = qobject_cast(w); if (dock) { break; } } } switch (mode) { case OverlayManager::OverlayMode::ToggleActive: m = ToggleMode::Toggle; break; case OverlayManager::OverlayMode::ToggleTransparent: m = ToggleMode::Transparent; break; case OverlayManager::OverlayMode::EnableActive: m = ToggleMode::Set; break; case OverlayManager::OverlayMode::DisableActive: m = ToggleMode::Unset; break; default: return; } toggleOverlay(dock, m); } void onToggleDockWidget(QDockWidget* dock, int checked) { if (!dock) { return; } auto it = _overlayMap.find(dock); if (it == _overlayMap.end()) { return; } OverlayTabWidget* tabWidget = it->second->tabWidget; int index = tabWidget->dockWidgetIndex(dock); if (index < 0) { return; } auto sizes = tabWidget->getSplitter()->sizes(); while (index >= sizes.size()) { sizes.append(0); } if (checked < -1) { checked = 0; } else if (checked == 3) { checked = 1; sizes[index] = 0; // force expand the tab in full } else if (checked <= 1) { if (sizes[index] != 0 && tabWidget->isHidden()) { checked = 1; } else { // child widget inside splitter by right shouldn't been hidden, so // we ignore the given toggle bit, but rely on its splitter size to // decide. checked = sizes[index] == 0 ? 1 : 0; } } if (sizes[index] == 0) { if (!checked) { return; } tabWidget->setCurrent(dock); tabWidget->onCurrentChanged(tabWidget->dockWidgetIndex(dock)); } else if (!checked) { if (sizes[index] > 0 && sizes.size() > 1) { int newtotal = 0; auto newsizes = sizes; newsizes[index] = 0; for (int i = 0; i < sizes.size(); ++i) { if (i != index) { auto d = tabWidget->dockWidget(i); auto it = tabWidget->_sizemap.find(d); if (it == tabWidget->_sizemap.end()) { newsizes[i] = 0; } else { if (newtotal == 0) { tabWidget->setCurrent(d); } newsizes[i] = it->second; newtotal += it->second; } } } if (!newtotal) { int expand = 0; for (int i = 0; i < sizes.size(); ++i) { if (i != index && sizes[i] > 0) { ++expand; break; } } if (expand) { int expansion = sizes[index]; int step = expansion / expand; for (int i = 0; i < sizes.size(); ++i) { if (i == index) { newsizes[i] = 0; } else if (--expand) { expansion -= step; newsizes[i] += step; } else { newsizes[i] += expansion; break; } } } newsizes[index] = 0; } tabWidget->splitter->setSizes(newsizes); tabWidget->saveTabs(); } } for (int i = 0; i < sizes.size(); ++i) { if (sizes[i]) { tabWidget->_sizemap[tabWidget->dockWidget(i)] = sizes[i]; } } if (checked) { tabWidget->setRevealTime( QTime::currentTime().addMSecs(OverlayParams::getDockOverlayRevealDelay()) ); } refresh(); } void onFocusChanged(QWidget*, QWidget*) { refresh(); } void setupTitleBar(QDockWidget* dock) { if (!dock->titleBarWidget()) { dock->setTitleBarWidget(createTitleBar(dock)); } } QWidget* createTitleBar(QWidget* parent) { OverlayTitleBar* widget = new OverlayTitleBar(parent); widget->setObjectName(QStringLiteral("OverlayTitle")); QList actions; if (auto tabWidget = qobject_cast(parent)) { actions = tabWidget->actions(); } else if (auto dockWidget = qobject_cast(parent)) { const QDockWidget::DockWidgetFeatures features = dockWidget->features(); actions.append(&_actOverlay); if (features.testFlag(QDockWidget::DockWidgetFloatable)) { actions.append(&_actFloat); } if (features.testFlag(QDockWidget::DockWidgetClosable)) { actions.append(&_actClose); } } else { actions = _actions; } widget->setTitleItem(OverlayTabWidget::prepareTitleWidget(widget, actions)); return widget; } void onAction(QAction* action) { if (action == &_actOverlay) { OverlayManager::instance()->setOverlayMode(OverlayManager::OverlayMode::ToggleActive); } else if (action == &_actFloat || action == &_actClose) { for (auto w = qApp->widgetAt(QCursor::pos()); w; w = w->parentWidget()) { auto dock = qobject_cast(w); if (!dock) { continue; } setFocusView(); if (action == &_actClose) { dock->toggleViewAction()->activate(QAction::Trigger); } else { auto it = _overlayMap.find(dock); if (it != _overlayMap.end()) { it->second->tabWidget->removeWidget(dock); getMainWindow()->addDockWidget(it->second->dockArea, dock); _overlayMap.erase(it); dock->show(); dock->setFloating(true); refresh(); } else { dock->setFloating(!dock->isFloating()); } } return; } } else { auto tabWidget = qobject_cast(action->parent()); if (tabWidget) { tabWidget->onAction(action); } } } void retranslate() { _actOverlay.setToolTip(QObject::tr("Toggle overlay")); _actFloat.setToolTip(QObject::tr("Toggle floating window")); _actClose.setToolTip(QObject::tr("Close dock window")); } void floatDockWidget(QDockWidget* dock) { setFocusView(); auto it = _overlayMap.find(dock); if (it != _overlayMap.end()) { it->second->tabWidget->removeWidget(dock); _overlayMap.erase(it); } dock->setFloating(true); dock->show(); } // Warning, the caller may be deleted during the call. So do not pass // parameter using reference, pass by value instead. void dragDockWidget(QPoint pos, QWidget* srcWidget, QPoint dragOffset, QSize dragSize, bool drop) { if (!getMainWindow()) { return; } auto mdi = getMainWindow()->getMdiArea(); if (!mdi) { return; } auto dock = qobject_cast(srcWidget); if (dock && dock->isFloating()) { dock->move(pos - dragOffset); } OverlayTabWidget* src = nullptr; int srcIndex = -1; if (dock) { auto it = _overlayMap.find(dock); if (it != _overlayMap.end()) { src = it->second->tabWidget; srcIndex = src->dockWidgetIndex(dock); } } else { src = qobject_cast(srcWidget); if (!src) { return; } for (int size : src->getSplitter()->sizes()) { ++srcIndex; if (size) { dock = src->dockWidget(srcIndex); break; } } if (!dock) { return; } } OverlayTabWidget* tabWidget = nullptr; int resizeOffset = 0; int index = -1; QRect rect; QRect rectMain(getMainWindow()->mapToGlobal(QPoint()), getMainWindow()->size()); QRect rectMdi(mdi->mapToGlobal(QPoint()), mdi->size()); for (OverlayTabWidget* overlay : _Overlays) { rect = QRect(mdi->mapToGlobal(overlay->rectOverlay.topLeft()), overlay->rectOverlay.size()); QSize size(rect.width() * 3 / 4, rect.height() * 3 / 4); QSize sideSize(rect.width() / 4, rect.height() / 4); int dockArea = overlay->getDockArea(); if (dockArea == Qt::BottomDockWidgetArea) { if (pos.y() < rect.bottom() && rect.bottom() - pos.y() < sideSize.height()) { rect.setTop(rect.bottom() - OverlayParams::getDockOverlayMinimumSize()); rect.setBottom(rectMain.bottom()); rect.setLeft(rectMdi.left()); rect.setRight(rectMdi.right()); tabWidget = overlay; index = -2; break; } } if (dockArea == Qt::LeftDockWidgetArea) { if (pos.x() >= rect.left() && pos.x() - rect.left() < sideSize.width()) { rect.setRight(rect.left() + OverlayParams::getDockOverlayMinimumSize()); rect.setLeft(rectMain.left()); rect.setTop(rectMdi.top()); rect.setBottom(rectMdi.bottom()); tabWidget = overlay; index = -2; break; } } else if (dockArea == Qt::RightDockWidgetArea) { if (pos.x() < rect.right() && rect.right() - pos.x() < sideSize.width()) { rect.setLeft(rect.right() - OverlayParams::getDockOverlayMinimumSize()); rect.setRight(rectMain.right()); rect.setTop(rectMdi.top()); rect.setBottom(rectMdi.bottom()); tabWidget = overlay; index = -2; break; } } else if (dockArea == Qt::TopDockWidgetArea) { if (pos.y() >= rect.top() && pos.y() - rect.top() < sideSize.height()) { rect.setBottom(rect.top() + OverlayParams::getDockOverlayMinimumSize()); rect.setTop(rectMain.top()); rect.setLeft(rectMdi.left()); rect.setRight(rectMdi.right()); tabWidget = overlay; index = -2; break; } } switch (dockArea) { case Qt::LeftDockWidgetArea: rect.setWidth(size.width()); break; case Qt::RightDockWidgetArea: rect.setLeft(rect.right() - size.width()); break; case Qt::TopDockWidgetArea: rect.setHeight(size.height()); break; default: rect.setTop(rect.bottom() - size.height()); break; } if (!rect.contains(pos)) { continue; } tabWidget = overlay; index = -1; int i = -1; for (int size : overlay->getSplitter()->sizes()) { ++i; auto handle = overlay->getSplitter()->handle(i); QWidget* w = overlay->dockWidget(i); if (!handle || !w) { continue; } if (handle->rect().contains(handle->mapFromGlobal(pos))) { QPoint pt = handle->mapToGlobal(QPoint()); QSize s = handle->size(); if (!size) { size = OverlayParams::getDockOverlayMinimumSize(); } if (tabWidget != src) { size /= 2; } if (overlay->getSplitter()->orientation() == Qt::Vertical) { s.setHeight(s.height() + size); } else { s.setWidth(s.width() + size); } rect = QRect(pt, s); index = i; break; } if (!size) { continue; } if (w->rect().contains(w->mapFromGlobal(pos))) { QPoint pt = overlay->getSplitter()->mapToGlobal(w->pos()); rect = QRect(pt, w->size()); if (tabWidget != src) { if (overlay->getSplitter()->orientation() == Qt::Vertical) { if (pos.y() > pt.y() + size / 2) { rect.setTop(rect.top() + size / 2); resizeOffset = -1; ++i; } else { rect.setHeight(size / 2); } } else if (pos.x() > pt.x() + size / 2) { rect.setLeft(rect.left() + size / 2); resizeOffset = -1; ++i; } else { rect.setWidth(size / 2); } } index = i; break; } } break; }; OverlayTabWidget* dst = nullptr; int dstIndex = -1; QDockWidget* dstDock = nullptr; Qt::DockWidgetArea dstDockArea {}; if (!tabWidget) { rect = QRect(pos - dragOffset, dragSize); if (rect.width() < 50) { rect.setWidth(50); } if (rect.height() < 50) { rect.setHeight(50); } for (auto dockWidget : getMainWindow()->findChildren()) { if (dockWidget == dock || !dockWidget->isVisible() || dockWidget->isFloating() || _overlayMap.contains(dockWidget)) { continue; } if (dockWidget->rect().contains(dockWidget->mapFromGlobal(pos))) { dstDock = dockWidget; dstDockArea = getMainWindow()->dockWidgetArea(dstDock); rect = QRect(dockWidget->mapToGlobal(QPoint()), dockWidget->size()); break; } } } else { dst = tabWidget; dstIndex = index; if (dstIndex == -1) { rect = QRect( mdi->mapToGlobal(tabWidget->rectOverlay.topLeft()), tabWidget->rectOverlay.size() ); } } bool outside = false; if (!rectMain.contains(pos)) { outside = true; if (drop) { if (!dock->isFloating()) { if (src) { _overlayMap.erase(dock); src->removeWidget(dock); } setFocusView(); dock->setFloating(true); dock->move(pos - dragOffset); dock->show(); } if (OverlayTabWidget::_DragFloating) { OverlayTabWidget::_DragFloating->hide(); } } else if (!dock->isFloating()) { if (!OverlayTabWidget::_DragFloating) { OverlayTabWidget::_DragFloating = new QDockWidget(getMainWindow()); OverlayTabWidget::_DragFloating->setFloating(true); } OverlayTabWidget::_DragFloating->resize(dock->size()); OverlayTabWidget::_DragFloating->setWindowTitle(dock->windowTitle()); OverlayTabWidget::_DragFloating->show(); OverlayTabWidget::_DragFloating->move(pos - dragOffset); } if (OverlayTabWidget::_DragFrame) { OverlayTabWidget::_DragFrame->hide(); } return; } else if (!drop && OverlayTabWidget::_DragFrame && !OverlayTabWidget::_DragFrame->isVisible()) { OverlayTabWidget::_DragFrame->raise(); OverlayTabWidget::_DragFrame->show(); if (OverlayTabWidget::_DragFloating) { OverlayTabWidget::_DragFloating->hide(); } } int insertDock = 0; // 0: tabify, -1: insert before, 1: insert after if (!dst && dstDock) { switch (dstDockArea) { case Qt::LeftDockWidgetArea: case Qt::RightDockWidgetArea: if (pos.y() < rect.top() + rect.height() / 4) { insertDock = -1; rect.setBottom(rect.top() + rect.height() / 2); } else if (pos.y() > rect.bottom() - rect.height() / 4) { insertDock = 1; int height = rect.height(); rect.setTop(rect.bottom() - height / 4); rect.setHeight(height / 2); } break; default: if (pos.x() < rect.left() + rect.width() / 4) { insertDock = -1; rect.setRight(rect.left() + rect.width() / 2); } else if (pos.x() > rect.right() - rect.width() / 4) { insertDock = 1; int width = rect.width(); rect.setLeft(rect.right() - width / 4); rect.setWidth(width / 2); } break; } } if (!drop) { if (!OverlayTabWidget::_DragFrame) { OverlayTabWidget::_DragFrame = new OverlayDragFrame(getMainWindow()); } rect = QRect(getMainWindow()->mapFromGlobal(rect.topLeft()), rect.size()); OverlayTabWidget::_DragFrame->setGeometry(rect); if (!outside && !OverlayTabWidget::_DragFrame->isVisible()) { OverlayTabWidget::_DragFrame->raise(); OverlayTabWidget::_DragFrame->show(); } return; } if (src && src == dst && dstIndex != -2) { auto splitter = src->getSplitter(); if (dstIndex == -1) { src->tabBar()->moveTab(srcIndex, 0); src->setCurrentIndex(0); src->onCurrentChanged(0); } else if (srcIndex != dstIndex) { auto sizes = splitter->sizes(); src->tabBar()->moveTab(srcIndex, dstIndex); splitter->setSizes(sizes); src->onSplitterResize(dstIndex); src->saveTabs(); } return; } if (src) { _overlayMap.erase(dock); src->removeWidget(dock); } setFocusView(); if (!dst) { if (dstDock) { dock->setFloating(false); if (insertDock == 0) { getMainWindow()->tabifyDockWidget(dstDock, dock); } else { std::map docks; for (auto dockWidget : getMainWindow()->findChildren()) { if (dockWidget == dock || !dockWidget->isVisible() || getMainWindow()->dockWidgetArea(dockWidget) != dstDockArea) { continue; } auto pos = dockWidget->mapToGlobal(QPoint(0, 0)); if (dstDockArea == Qt::LeftDockWidgetArea || dstDockArea == Qt::RightDockWidgetArea) { docks[pos.y()] = dockWidget; } else { docks[pos.x()] = dockWidget; } } auto it = docks.begin(); for (; it != docks.end(); ++it) { if (it->second == dstDock) { break; } } if (insertDock > 0 && it != docks.end()) { ++it; } for (auto iter = it; iter != docks.end(); ++iter) { getMainWindow()->removeDockWidget(iter->second); } getMainWindow()->addDockWidget(dstDockArea, dock); dock->show(); for (auto iter = it; iter != docks.end(); ++iter) { getMainWindow()->addDockWidget(dstDockArea, iter->second); iter->second->show(); } } } else { dock->setFloating(true); if (OverlayTabWidget::_DragFrame) { dock->setGeometry(QRect( OverlayTabWidget::_DragFrame->mapToGlobal(QPoint()), OverlayTabWidget::_DragFrame->size() )); } } dock->show(); } else if (dstIndex == -2) { getMainWindow()->addDockWidget(dst->getDockArea(), dock); dock->setFloating(false); } else { auto sizes = dst->getSplitter()->sizes(); for (auto o : _overlayInfos) { if (o->tabWidget == dst) { o->addWidget(dock, false); break; } } index = dst->dockWidgetIndex(dock); if (index >= 0) { if (dstIndex < 0) { dst->tabBar()->moveTab(index, 0); dst->setCurrentIndex(0); dst->onCurrentChanged(0); } else { dst->tabBar()->moveTab(index, dstIndex); int size = sizes[dstIndex + resizeOffset]; if (size) { size /= 2; sizes[dstIndex + resizeOffset] = size; } else { size = 50; } sizes.insert(dstIndex, size); dst->setCurrentIndex(dstIndex); dst->getSplitter()->setSizes(sizes); dst->onSplitterResize(dstIndex); dst->saveTabs(); } dst->setRevealTime( QTime::currentTime().addMSecs(OverlayParams::getDockOverlayRevealDelay()) ); } } refresh(); } void raiseAll() { if (raising) { return; } Base::StateLocker guard(raising); for (OverlayTabWidget* tabWidget : _Overlays) { if (tabWidget->isVisible()) { tabWidget->raise(); } } } void registerDockWidget(const QString& name, OverlayTabWidget* widget) { if (name.size()) { _dockWidgetNameMap[name] = widget; } } void unregisterDockWidget(const QString& name, OverlayTabWidget* widget) { auto it = _dockWidgetNameMap.find(name); if (it != _dockWidgetNameMap.end() && it->second == widget) { _dockWidgetNameMap.erase(it); } } void reload(OverlayManager::ReloadMode mode) { if (mode == OverlayManager::ReloadMode::ReloadResume) { curReloadMode = mode = OverlayManager::ReloadMode::ReloadPending; } if (mode == OverlayManager::ReloadMode::ReloadPending) { if (curReloadMode != OverlayManager::ReloadMode::ReloadPause) { FC_LOG("reload pending"); _reloadTimer.start(100); } } curReloadMode = mode; if (mode == OverlayManager::ReloadMode::ReloadPause) { FC_LOG("reload paused"); _reloadTimer.stop(); } } }; static OverlayManager* _instance; OverlayManager* OverlayManager::instance() { if (_instance == 0) { _instance = new OverlayManager; } return _instance; } void OverlayManager::destruct() { delete _instance; _instance = 0; } OverlayManager::OverlayManager() { d = new Private(this, getMainWindow()); qApp->installEventFilter(this); } OverlayManager::~OverlayManager() { delete d; } void OverlayManager::setOverlayMode(OverlayMode mode) { d->setOverlayMode(mode); } void OverlayManager::initDockWidget(QDockWidget* dw) { QObject::connect(dw->toggleViewAction(), &QAction::triggered, this, &OverlayManager::onToggleDockWidget); QObject::connect(dw, &QDockWidget::visibilityChanged, this, &OverlayManager::onDockVisibleChange); QObject::connect(dw, &QDockWidget::featuresChanged, this, &OverlayManager::onDockFeaturesChange); if (auto widget = dw->widget()) { QObject::connect( widget, &QWidget::windowTitleChanged, this, &OverlayManager::onDockWidgetTitleChange ); } QString name = dw->objectName(); if (name.size()) { auto it = d->_dockWidgetNameMap.find(dw->objectName()); if (it != d->_dockWidgetNameMap.end()) { for (auto o : d->_overlayInfos) { if (o->tabWidget == it->second) { o->addWidget(dw, true); d->onToggleDockWidget(dw, 3); break; } } d->refresh(); } } } void OverlayManager::setupDockWidget(QDockWidget* dw, int dockArea) { (void)dockArea; d->setupTitleBar(dw); } void OverlayManager::unsetupDockWidget(QDockWidget* dw) { d->toggleOverlay(dw, ToggleMode::Unset); } void OverlayManager::onToggleDockWidget(bool checked) { auto action = qobject_cast(sender()); if (!action) { return; } d->onToggleDockWidget(qobject_cast(action->parent()), checked); } void OverlayManager::onDockVisibleChange(bool visible) { auto dock = qobject_cast(sender()); if (!dock) { return; } FC_TRACE( "dock " << dock->objectName().toUtf8().constData() << " visible change " << visible << ", " << dock->isVisible() ); } void OverlayManager::onDockFeaturesChange(QDockWidget::DockWidgetFeatures features) { Q_UNUSED(features); auto dw = qobject_cast(sender()); if (!dw) { return; } // Rebuild the title widget as it may have a different set of buttons shown. if (auto* titleBarWidget = qobject_cast(dw->titleBarWidget())) { dw->setTitleBarWidget(nullptr); delete titleBarWidget; } setupTitleBar(dw); } void OverlayManager::onTaskViewUpdate() { auto taskview = qobject_cast(sender()); if (!taskview) { return; } QDockWidget* dock = nullptr; for (QWidget* w = taskview; w; w = w->parentWidget()) { if ((dock = qobject_cast(w))) { break; } } if (dock) { auto it = d->_overlayMap.find(dock); if (it == d->_overlayMap.end() || it->second->tabWidget->count() < 2 || it->second->tabWidget->getAutoMode() != OverlayTabWidget::AutoMode::TaskShow) { return; } d->onToggleDockWidget(dock, taskview->isEmpty() ? -2 : 2); } } void OverlayManager::onDockWidgetTitleChange(const QString& title) { if (title.isEmpty()) { return; } auto widget = qobject_cast(sender()); QDockWidget* dock = nullptr; for (QWidget* w = widget; w; w = w->parentWidget()) { if ((dock = qobject_cast(w))) { break; } } if (!dock) { return; } auto tabWidget = findTabWidget(dock); if (!tabWidget) { return; } int index = tabWidget->dockWidgetIndex(dock); if (index >= 0) { tabWidget->setTabText(index, title); } } void OverlayManager::retranslate() { d->retranslate(); } void OverlayManager::refreshIcons() { d->refreshIcons(); } void OverlayManager::reload(ReloadMode mode) { d->reload(mode); } void OverlayManager::raiseAll() { d->raiseAll(); } static inline bool isNear(const QPoint& a, const QPoint& b, int tol = 16) { QPoint d = a - b; return d.x() * d.x() + d.y() * d.y() < tol; } bool OverlayManager::eventFilter(QObject* o, QEvent* ev) { if (d->intercepting || !getMainWindow() || !o->isWidgetType()) { return false; } auto mdi = getMainWindow()->getMdiArea(); if (!mdi) { return false; } switch (ev->type()) { case QEvent::Enter: if (Selection().hasPreselection() && !qobject_cast(o) && !isUnderOverlay()) { Selection().rmvPreselect(); } break; case QEvent::ZOrderChange: { if (!d->raising && getMainWindow() && o == mdi) { // On Windows, for some reason, it will raise mdi window on tab // change in any docked widget, which will then obscure any overlay // docked widget here. for (auto child : getMainWindow()->children()) { if (child == mdi || qobject_cast(child)) { QMetaObject::invokeMethod(this, "raiseAll", Qt::QueuedConnection); break; } if (qobject_cast(child)) { break; } } } break; } case QEvent::Resize: { if (getMainWindow() && o == mdi) { refresh(); } return false; } case QEvent::KeyPress: { QKeyEvent* ke = static_cast(ev); bool accepted = false; if (ke->modifiers() == Qt::NoModifier && ke->key() == Qt::Key_Escape) { if (d->mouseTransparent) { d->setMouseTransparent(false); accepted = true; } else if (OverlayTabWidget::_Dragging && OverlayTabWidget::_Dragging != o) { if (auto titleBar = qobject_cast(OverlayTabWidget::_Dragging)) { titleBar->endDrag(); } else if (auto splitHandle = qobject_cast(OverlayTabWidget::_Dragging)) { splitHandle->endDrag(); } } else if (!OverlayTabWidget::_Dragging) { for (OverlayTabWidget* tabWidget : _Overlays) { if (tabWidget->onEscape()) { accepted = true; } } } } if (accepted) { ke->accept(); return true; } break; } case QEvent::Paint: if (auto widget = qobject_cast(o)) { // QAbstractItemView optimize redraw using its item delegate's // visualRect(). However, if we are using QGraphicsEffects, the // effect may touch areas outside of visualRect(), so // OverlayTabWidget offers a timer for a delayed redraw. widget = qobject_cast(widget->parentWidget()); if (widget) { auto tabWidget = findTabWidget(widget, true); if (tabWidget) { tabWidget->scheduleRepaint(); } } } break; // case QEvent::NativeGesture: case QEvent::Wheel: if (!OverlayParams::getDockOverlayWheelPassThrough()) { return false; } // fall through case QEvent::ContextMenu: { auto cev = static_cast(ev); if (cev->reason() != QContextMenuEvent::Mouse) { return false; } } // fall through case QEvent::MouseButtonDblClick: case QEvent::MouseButtonRelease: case QEvent::MouseButtonPress: case QEvent::MouseMove: { if (OverlayTabWidget::_Dragging && OverlayTabWidget::_Dragging != o) { if (auto titleBar = qobject_cast(OverlayTabWidget::_Dragging)) { titleBar->endDrag(); } else if (auto splitHandle = qobject_cast(OverlayTabWidget::_Dragging)) { splitHandle->endDrag(); } } QWidget* grabber = QWidget::mouseGrabber(); d->lastIntercept = nullptr; if (d->mouseTransparent || (grabber && grabber != d->_trackingOverlay)) { return false; } if (qobject_cast(o)) { return false; } if (ev->type() != QEvent::Wheel) { if (qobject_cast(o)) { return false; } } else if (qobject_cast(o)) { return false; } if (d->_trackingWidget) { if (!isTreeViewDragging()) { d->interceptEvent(d->_trackingWidget, ev); } if (isTreeViewDragging() || (ev->type() == QEvent::MouseButtonRelease && QApplication::mouseButtons() == Qt::NoButton)) { d->_trackingWidget = nullptr; if (d->_trackingOverlay == grabber && ev->type() == QEvent::MouseButtonRelease) { d->_trackingOverlay = nullptr; // Must not release mouse here, because otherwise the event // will find its way to the actual widget under cursor. // Instead, return false here to let OverlayTabWidget::event() // release the mouse. return false; } if (d->_trackingOverlay && grabber == d->_trackingOverlay) { d->_trackingOverlay->releaseMouse(); } d->_trackingOverlay = nullptr; } // Must return true here to filter the event, otherwise ContextMenu // event may be routed to the actual widget. Other types of event // probably do not matter. return true; } else if (ev->type() != QEvent::MouseButtonPress && ev->type() != QEvent::MouseButtonDblClick && QApplication::mouseButtons() != Qt::NoButton) { return false; } if (isTreeViewDragging()) { return false; } OverlayTabWidget* activeTabWidget = nullptr; int hit = 0; QPoint pos = QCursor::pos(); if (OverlayParams::getDockOverlayAutoMouseThrough() && ev->type() != QEvent::Wheel && pos == d->_lastPos) { hit = 1; } else if (ev->type() == QEvent::Wheel && !d->wheelDelay.isNull() && (isNear(pos, d->wheelPos) || d->wheelDelay > QTime::currentTime())) { d->wheelDelay = QTime::currentTime().addMSecs( OverlayParams::getDockOverlayWheelDelay() ); d->wheelPos = pos; return false; } else { for (auto widget = qApp->widgetAt(pos); widget; widget = widget->parentWidget()) { int type = widget->windowType(); if (type != Qt::Widget && type != Qt::Window) { if (type != Qt::SubWindow) { hit = -1; } break; } if (ev->type() == QEvent::Wheel) { if (qobject_cast(widget)) { activeTabWidget = qobject_cast(widget->parentWidget()); } else if (qobject_cast(widget)) { auto parent = widget->parentWidget(); if (parent) { activeTabWidget = qobject_cast( parent->parentWidget() ); } } if (activeTabWidget) { break; } } if (auto tabWidget = qobject_cast(widget)) { if (tabWidget->testAlpha(pos, ev->type() == QEvent::Wheel ? 4 : 1) == 0) { activeTabWidget = tabWidget; } break; } } if (activeTabWidget) { hit = OverlayParams::getDockOverlayAutoMouseThrough(); d->_lastPos = pos; } } for (OverlayTabWidget* tabWidget : _Overlays) { if (tabWidget->getProxyWidget()->hitTest(pos) == OverlayProxyWidget::HitTest::HitInner) { if ((ev->type() == QEvent::MouseButtonRelease || ev->type() == QEvent::MouseButtonPress) && static_cast(ev)->button() == Qt::LeftButton) { if (ev->type() == QEvent::MouseButtonRelease) { tabWidget->getProxyWidget()->onMousePress(); } return true; } } } if (hit <= 0) { d->_lastPos.setX(std::numeric_limits::max()); if (ev->type() == QEvent::Wheel) { d->wheelDelay = QTime::currentTime().addMSecs( OverlayParams::getDockOverlayWheelDelay() ); d->wheelPos = pos; } return false; } auto hitWidget = mdi->childAt(mdi->mapFromGlobal(pos)); if (!hitWidget) { return false; } if (!activeTabWidget) { activeTabWidget = findTabWidget(qApp->widgetAt(QCursor::pos())); } if (!activeTabWidget || !activeTabWidget->isTransparent()) { return false; } ev->accept(); d->interceptEvent(hitWidget, ev); if (ev->isAccepted() && ev->type() == QEvent::MouseButtonPress) { hitWidget->setFocus(); d->_trackingWidget = hitWidget; d->_trackingOverlay = activeTabWidget; d->_trackingOverlay->grabMouse(); } return true; } default: break; } return false; } namespace { class MouseGrabberGuard { public: explicit MouseGrabberGuard(QWidget* grabber) { if (grabber && grabber == QWidget::mouseGrabber()) { _grabber = grabber; _grabber->releaseMouse(); } } ~MouseGrabberGuard() { if (_grabber) { _grabber->grabMouse(); } } QPointer _grabber; }; } // anonymous namespace void OverlayManager::Private::interceptEvent(QWidget* widget, QEvent* ev) { Base::StateLocker guard(this->intercepting); MouseGrabberGuard grabberGuard(_trackingOverlay); lastIntercept = nullptr; auto getChildAt = [](QWidget* w, const QPoint& pos) { QWidget* res = w; for (; w; w = w->childAt(w->mapFromGlobal(pos))) { if (auto scrollArea = qobject_cast(w)) { return scrollArea->viewport(); } res = w; } return res; }; switch (ev->type()) { case QEvent::MouseButtonRelease: case QEvent::MouseButtonPress: case QEvent::MouseMove: case QEvent::MouseButtonDblClick: { auto me = static_cast(ev); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QPointF screenPos = me->screenPos(); QPoint point = me->globalPos(); #else QPointF screenPos = me->globalPosition(); QPoint point = screenPos.toPoint(); #endif lastIntercept = getChildAt(widget, point); QMouseEvent mouseEvent( ev->type(), lastIntercept->mapFromGlobal(point), screenPos, me->button(), me->buttons(), me->modifiers() ); QApplication::sendEvent(lastIntercept, &mouseEvent); break; } case QEvent::Wheel: { auto we = static_cast(ev); QPoint globalPos = we->globalPosition().toPoint(); lastIntercept = getChildAt(widget, globalPos); // For some reason in case of 3D View we have to target it directly instead of targeting // the viewport of QAbstractScrollArea like it works for all other widgets of that kind. // That's why for this event we have to traverse up the widget tree to find if it is // part of the 3D view. for (auto parent = lastIntercept; parent; parent = parent->parentWidget()) { if (qobject_cast(parent)) { lastIntercept = parent; break; } } QWheelEvent wheelEvent( lastIntercept->mapFromGlobal(globalPos), globalPos, we->pixelDelta(), we->angleDelta(), we->buttons(), we->modifiers(), we->phase(), we->inverted(), we->source() ); QApplication::sendEvent(lastIntercept, &wheelEvent); break; } case QEvent::ContextMenu: { auto ce = static_cast(ev); lastIntercept = getChildAt(widget, ce->globalPos()); QContextMenuEvent contextMenuEvent( ce->reason(), lastIntercept->mapFromGlobal(ce->globalPos()), ce->globalPos() ); QApplication::sendEvent(lastIntercept, &contextMenuEvent); break; } default: break; } } void OverlayManager::refresh(QWidget* widget, bool refreshStyle) { d->refresh(widget, refreshStyle); } void OverlayManager::setMouseTransparent(bool enabled) { d->setMouseTransparent(enabled); } bool OverlayManager::isMouseTransparent() const { return d->mouseTransparent; } bool OverlayManager::isUnderOverlay() const { return OverlayParams::getDockOverlayAutoMouseThrough() && findTabWidget(qApp->widgetAt(QCursor::pos()), true); } void OverlayManager::save() { d->save(); } void OverlayManager::restore() { d->restore(); if (Control().taskPanel()) { connect( Control().taskPanel(), &TaskView::TaskView::taskUpdate, this, &OverlayManager::onTaskViewUpdate ); } } void OverlayManager::setupTitleBar(QDockWidget* dock) { d->setupTitleBar(dock); } void OverlayManager::onFocusChanged(QWidget* old, QWidget* now) { d->onFocusChanged(old, now); } void OverlayManager::onAction() { QAction* action = qobject_cast(sender()); if (action) { d->onAction(action); } } void OverlayManager::dragDockWidget( const QPoint& pos, QWidget* src, const QPoint& offset, const QSize& size, bool drop ) { d->dragDockWidget(pos, src, offset, size, drop); } void OverlayManager::floatDockWidget(QDockWidget* dock) { d->floatDockWidget(dock); } void OverlayManager::registerDockWidget(const QString& name, OverlayTabWidget* widget) { d->registerDockWidget(name, widget); } void OverlayManager::unregisterDockWidget(const QString& name, OverlayTabWidget* widget) { d->unregisterDockWidget(name, widget); } QWidget* OverlayManager::getLastMouseInterceptWidget() const { return d->lastIntercept; } const QString& OverlayManager::getStyleSheet() const { return OverlayStyleSheet::instance()->activeStyleSheet; } bool OverlayManager::getHideTab() const { return OverlayStyleSheet::instance()->hideTab; } void OverlayManager::setFocusView() { auto view = getMainWindow()->activeWindow(); if (!view) { view = Application::Instance->activeView(); } if (view) { view->setFocus(); } } #include "moc_OverlayManager.cpp"