diff --git a/src/Gui/MainWindow.cpp b/src/Gui/MainWindow.cpp index 63672bfbe4..cab7f03451 100644 --- a/src/Gui/MainWindow.cpp +++ b/src/Gui/MainWindow.cpp @@ -813,14 +813,6 @@ QMenu* MainWindow::createPopupMenu () populateDockWindowMenu(menu); menu->addSeparator(); populateToolBarMenu(menu); - QMenu *undockMenu = new QMenu(menu); - ToolBarManager::getInstance()->populateUndockMenu(undockMenu); - if (undockMenu->actions().isEmpty()) { - delete undockMenu; - } - else { - menu->addMenu(undockMenu); - } menu->addSeparator(); Workbench* wb = WorkbenchManager::instance()->active(); if (wb) { diff --git a/src/Gui/ToolBarAreaWidget.cpp b/src/Gui/ToolBarAreaWidget.cpp index d3a80ae6f5..21c03a3318 100644 --- a/src/Gui/ToolBarAreaWidget.cpp +++ b/src/Gui/ToolBarAreaWidget.cpp @@ -27,6 +27,7 @@ #include "MainWindow.h" #include "ToolBarAreaWidget.h" +#include "ToolBarManager.h" #include using namespace Gui; @@ -53,6 +54,10 @@ void ToolBarAreaWidget::addWidget(QWidget* widget) return; } + if (auto toolbar = qobject_cast(widget)) { + toolbar->updateCustomGripVisibility(); + } + _layout->addWidget(widget); adjustParent(); @@ -80,6 +85,10 @@ void ToolBarAreaWidget::insertWidget(int index, QWidget* widget) _layout->insertWidget(index, widget); + if (auto toolbar = qobject_cast(widget)) { + toolbar->updateCustomGripVisibility(); + } + adjustParent(); saveState(); } @@ -88,6 +97,10 @@ void ToolBarAreaWidget::removeWidget(QWidget* widget) { _layout->removeWidget(widget); + if (auto toolbar = qobject_cast(widget)) { + toolbar->updateCustomGripVisibility(); + } + QString name = widget->objectName(); if (!name.isEmpty()) { Base::ConnectionBlocker block(_conn); diff --git a/src/Gui/ToolBarManager.cpp b/src/Gui/ToolBarManager.cpp index 221bde476d..eb8c4d50f4 100644 --- a/src/Gui/ToolBarManager.cpp +++ b/src/Gui/ToolBarManager.cpp @@ -22,13 +22,17 @@ #include "PreCompiled.h" #ifndef _PreComp_ -#include -#include -#include -#include -#include -#include -#include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include +# include #endif #include @@ -41,6 +45,7 @@ #include "Command.h" #include "MainWindow.h" #include "OverlayWidgets.h" +#include "WidgetFactory.h" using namespace Gui; @@ -162,6 +167,219 @@ QList ToolBarItem::getItems() const // ----------------------------------------------------------- +ToolBar::ToolBar() + : QToolBar() +{ + setupConnections(); +} + +ToolBar::ToolBar(QWidget* parent) + : QToolBar(parent) +{ + setupConnections(); +} + +void ToolBar::undock() +{ + { + // We want to block only some signals - topLevelChanged should still be propagated + QSignalBlocker blocker(this); + + if (auto area = ToolBarManager::getInstance()->toolBarAreaWidget(this)) { + area->removeWidget(this); + getMainWindow()->addToolBar(this); + } + + setWindowFlags(Qt::Tool + | Qt::FramelessWindowHint + | Qt::X11BypassWindowManagerHint); + adjustSize(); + setVisible(true); + } + + Q_EMIT topLevelChanged(true); +} + +void ToolBar::updateCustomGripVisibility() +{ + auto area = ToolBarManager::getInstance()->toolBarAreaWidget(this); + auto grip = findChild(); + + auto customGripIsRequired = isMovable() && area; + + if (grip && !customGripIsRequired) { + grip->detach(); + grip->deleteLater(); + } else if (!grip && customGripIsRequired) { + grip = new ToolBarGrip(this); + grip->attach(); + } else { + // either grip is present and should be present + // or is not present and should not be - nothing to do + return; + } +} + +void Gui::ToolBar::setupConnections() +{ + connect(this, &QToolBar::topLevelChanged, this, &ToolBar::updateCustomGripVisibility); + connect(this, &QToolBar::movableChanged, this, &ToolBar::updateCustomGripVisibility); +} + +// ----------------------------------------------------------- + +ToolBarGrip::ToolBarGrip(QToolBar * parent) + : QWidget(parent) +{ + updateSize(); +} + +void ToolBarGrip::attach() +{ + if (isAttached()) { + return; + } + + auto parent = qobject_cast(parentWidget()); + + if (!parent) { + return; + } + + auto actions = parent->actions(); + + _action = parent->insertWidget( + // ensure that grip is always placed as the first widget in the toolbar + actions.isEmpty() ? nullptr : actions[0], + this + ); + + setCursor(Qt::OpenHandCursor); + setMouseTracking(true); + setVisible(true); +} + +void ToolBarGrip::detach() +{ + if (!isAttached()) { + return; + } + + auto parent = qobject_cast(parentWidget()); + + if (!parent) { + return; + } + + parent->removeAction(_action); +} + +bool ToolBarGrip::isAttached() const +{ + return _action != nullptr; +} + +void ToolBarGrip::paintEvent(QPaintEvent*) +{ + QPainter painter(this); + + if (auto toolbar = qobject_cast(parentWidget())) { + QStyle *style = toolbar->style(); + QStyleOptionToolBar opt; + + toolbar->initStyleOption(&opt); + + opt.features = QStyleOptionToolBar::Movable; + opt.rect = rect(); + + style->drawPrimitive(QStyle::PE_IndicatorToolBarHandle, &opt, &painter, toolbar); + } +} + +void ToolBarGrip::mouseMoveEvent(QMouseEvent *me) +{ + auto toolbar = qobject_cast(parentWidget()); + if (!toolbar) { + return; + } + + auto area = ToolBarManager::getInstance()->toolBarAreaWidget(toolbar); + if (!area) { + return; + } + + QPoint pos = me->globalPos(); + QRect rect(toolbar->mapToGlobal(QPoint(0,0)), toolbar->size()); + + // if mouse did not leave the area of toolbar do not continue with undocking it + if (rect.contains(pos)) { + return; + } + + toolbar->undock(); + + // After removing from area, this grip will be deleted. In order to + // continue toolbar dragging (because the mouse button is still pressed), + // we fake mouse events and send to toolbar. For some reason, + // send/postEvent() does not work, only timer works. + QPointer tb(toolbar); + QTimer::singleShot(0, [tb] { + auto modifiers = QApplication::queryKeyboardModifiers(); + auto buttons = QApplication::mouseButtons(); + if (buttons != Qt::LeftButton + || QWidget::mouseGrabber() + || modifiers != Qt::NoModifier + || !tb) { + return; + } + + QPoint pos(10, 10); + QPoint globalPos(tb->mapToGlobal(pos)); + QMouseEvent mouseEvent( + QEvent::MouseButtonPress, + pos, globalPos, Qt::LeftButton, buttons, modifiers); + QApplication::sendEvent(tb, &mouseEvent); + + // Mouse follow the mouse press event with mouse move with some offset + // in order to activate toolbar dragging. + QPoint offset(30, 30); + QMouseEvent mouseMoveEvent( + QEvent::MouseMove, + pos+offset, globalPos+offset, + Qt::LeftButton, buttons, modifiers); + QApplication::sendEvent(tb, &mouseMoveEvent); + }); +} + +void ToolBarGrip::mousePressEvent(QMouseEvent *) +{ + setCursor(Qt::ClosedHandCursor); +} + +void ToolBarGrip::mouseReleaseEvent(QMouseEvent *) +{ + setCursor(Qt::OpenHandCursor); +} + +void ToolBarGrip::updateSize() +{ + auto parent = qobject_cast(parentWidget()); + + if (!parent) { + return; + } + + QStyle *style = parent->style(); + QStyleOptionToolBar opt; + + parent->initStyleOption(&opt); + opt.features = QStyleOptionToolBar::Movable; + + setFixedWidth(style->subElementRect(QStyle::SE_ToolBarHandle, &opt, parent).width() + 4); +} + +// ----------------------------------------------------------- + ToolBarManager* ToolBarManager::_instance = nullptr; // NOLINT ToolBarManager* ToolBarManager::getInstance() @@ -189,6 +407,8 @@ ToolBarManager::ToolBarManager() setupConnection(); setupTimer(); setupMenuBarTimer(); + + setupWidgetProducers(); } ToolBarManager::~ToolBarManager() = default; @@ -304,6 +524,11 @@ void ToolBarManager::setupMenuBarTimer() }); } +void Gui::ToolBarManager::setupWidgetProducers() +{ + new WidgetProducer; +} + ToolBarArea ToolBarManager::toolBarArea(QWidget *widget) const { if (auto toolBar = qobject_cast(widget)) { @@ -327,15 +552,24 @@ ToolBarArea ToolBarManager::toolBarArea(QWidget *widget) const } } - for (auto &areaWidget : { statusBarAreaWidget, menuBarLeftAreaWidget, menuBarRightAreaWidget }) { - if (areaWidget->indexOf(widget) >= 0) { - return areaWidget->area(); - } + if (auto areaWidget = toolBarAreaWidget(widget)) { + return areaWidget->area(); } return ToolBarArea::NoToolBarArea; } +ToolBarAreaWidget* ToolBarManager::toolBarAreaWidget(QWidget* widget) const +{ + for (auto &areaWidget : { statusBarAreaWidget, menuBarLeftAreaWidget, menuBarRightAreaWidget }) { + if (areaWidget->indexOf(widget) >= 0) { + return areaWidget; + } + } + + return nullptr; +} + namespace { QPointer createActionWidget() { @@ -427,23 +661,29 @@ void ToolBarManager::setup(ToolBarItem* toolBarItems) int top_width = 0; bool nameAsToolTip = App::GetApplication().GetUserParameter().GetGroup("BaseApp") - ->GetGroup("Preferences")->GetGroup("MainWindow")->GetBool("ToolBarNameAsToolTip",true); + ->GetGroup("Preferences") + ->GetGroup("MainWindow") + ->GetBool("ToolBarNameAsToolTip", true); + QList items = toolBarItems->getItems(); - QList toolbars = toolBars(); + QList toolbars = toolBars(); + for (ToolBarItem* it : items) { // search for the toolbar QString name = QString::fromUtf8(it->command().c_str()); this->toolbarNames << name; - QToolBar* toolbar = findToolBar(toolbars, name); + ToolBar* toolbar = findToolBar(toolbars, name); std::string toolbarName = it->command(); bool toolbar_added = false; if (!toolbar) { - toolbar = getMainWindow()->addToolBar( - QApplication::translate("Workbench", - toolbarName.c_str())); // i18n + toolbar = new ToolBar(getMainWindow()); + toolbar->setWindowTitle(QApplication::translate("Workbench", toolbarName.c_str())); toolbar->setObjectName(name); - if (nameAsToolTip){ + + getMainWindow()->addToolBar(toolbar); + + if (nameAsToolTip) { auto tooltip = QChar::fromLatin1('[') + QApplication::translate("Workbench", toolbarName.c_str()) + QChar::fromLatin1(']'); @@ -470,7 +710,8 @@ void ToolBarManager::setup(ToolBarItem* toolBarItems) // Enable automatic handling of visibility via, for example, (contextual) menu toolbar->toggleViewAction()->setVisible(true); } - else { // ToolBarItem::DefaultVisibility::Unavailable + else { + // ToolBarItem::DefaultVisibility::Unavailable // Prevent that the action to show/hide a toolbar appears on the (contextual) menus. // This is also managed by the client code for a toolbar with custom policy toolbar->toggleViewAction()->setVisible(false); @@ -595,9 +836,10 @@ void ToolBarManager::saveState() const return value == ToolBarItem::DefaultVisibility::Unavailable; }; - QList toolbars = toolBars(); + QList toolbars = toolBars(); for (const QString& it : toolbarNames) { - QToolBar* toolbar = findToolBar(toolbars, it); + ToolBar* toolbar = findToolBar(toolbars, it); + if (toolbar) { if (ignoreSave(toolbar->toggleViewAction())) { continue; @@ -614,7 +856,7 @@ void ToolBarManager::restoreState() const std::map sbToolBars; std::map mbRightToolBars; std::map mbLeftToolBars; - QList toolbars = toolBars(); + QList toolbars = toolBars(); for (const QString& it : toolbarNames) { QToolBar* toolbar = findToolBar(toolbars, it); if (toolbar) { @@ -782,67 +1024,9 @@ bool ToolBarManager::addToolBarToArea(QObject *source, QMouseEvent *ev) return false; } -void ToolBarManager::populateUndockMenu(QMenu *menu, ToolBarAreaWidget *area) -{ - menu->setTitle(tr("Undock toolbars")); - auto tooltip = QObject::tr("Undock from toolbar area"); - - auto addMenuUndockItem = [&](QToolBar *toolbar, int, ToolBarAreaWidget *area) { - auto toggleViewAction = toolbar->toggleViewAction(); - auto undockAction = new QAction(menu); - - undockAction->setText(toggleViewAction->text()); - undockAction->setToolTip(tooltip); - - menu->addAction(undockAction); - QObject::connect(undockAction, &QAction::triggered, [area, toolbar]() { - if (toolbar->parentWidget() == getMainWindow()) { - return; - } - - auto pos = toolbar->mapToGlobal(QPoint(0, 0)); - auto yOffset = toolbar->height(); - - // if widget is on the bottom move it up instead - if (area->area() == Gui::ToolBarArea::StatusBarToolBarArea) { - yOffset *= -1; - } - - { - // Block signals caused by manually floating the widget - QSignalBlocker blocker(toolbar); - - area->removeWidget(toolbar); - getMainWindow()->addToolBar(toolbar); - - // this will make toolbar floating, there is no better way to do that. - toolbar->setWindowFlags(Qt::Tool - | Qt::FramelessWindowHint - | Qt::X11BypassWindowManagerHint); - toolbar->move(pos.x(), pos.y() + yOffset); - toolbar->adjustSize(); - toolbar->setVisible(true); - } - - // but don't block actual information about widget being floated - Q_EMIT toolbar->topLevelChanged(true); - }); - }; - - if (area) { - area->foreachToolBar(addMenuUndockItem); - } - else { - statusBarAreaWidget->foreachToolBar(addMenuUndockItem); - menuBarLeftAreaWidget->foreachToolBar(addMenuUndockItem); - menuBarRightAreaWidget->foreachToolBar(addMenuUndockItem); - } -} - bool ToolBarManager::showContextMenu(QObject *source) { QMenu menu; - QMenu menuUndock; QLayout* layout = nullptr; ToolBarAreaWidget* area = nullptr; if (getMainWindow()->statusBar() == source) { @@ -872,12 +1056,7 @@ bool ToolBarManager::showContextMenu(QObject *source) } area->foreachToolBar(addMenuVisibleItem); - populateUndockMenu(&menuUndock, area); - if (!menuUndock.actions().empty()) { - menu.addSeparator(); - menu.addMenu(&menuUndock); - } menu.exec(QCursor::pos()); return true; } @@ -990,8 +1169,8 @@ bool ToolBarManager::eventFilter(QObject *source, QEvent *ev) void ToolBarManager::retranslate() const { - QList toolbars = toolBars(); - for (QToolBar* it : toolbars) { + QList toolbars = toolBars(); + for (ToolBar* it : toolbars) { QByteArray toolbarName = it->objectName().toUtf8(); it->setWindowTitle(QApplication::translate("Workbench", (const char*)toolbarName)); } @@ -1009,16 +1188,17 @@ void Gui::ToolBarManager::setToolBarsLocked(bool locked) const setMovable(!locked); } -void Gui::ToolBarManager::setMovable(bool moveable) const +void Gui::ToolBarManager::setMovable(bool movable) const { for (auto& tb : toolBars()) { - tb->setMovable(moveable); + tb->setMovable(movable); + tb->updateCustomGripVisibility(); } } -QToolBar* ToolBarManager::findToolBar(const QList& toolbars, const QString& item) const +ToolBar* ToolBarManager::findToolBar(const QList& toolbars, const QString& item) const { - for (QToolBar* it : toolbars) { + for (ToolBar* it : toolbars) { if (it->objectName() == item) { return it; } @@ -1038,12 +1218,14 @@ QAction* ToolBarManager::findAction(const QList& acts, const QString& return nullptr; // no item with the user data found } -QList ToolBarManager::toolBars() const +QList ToolBarManager::toolBars() const { auto mw = getMainWindow(); - QList tb; - QList bars = getMainWindow()->findChildren(); - for (QToolBar* it : bars) { + + QList tb; + QList bars = getMainWindow()->findChildren(); + + for (ToolBar* it : bars) { auto parent = it->parentWidget(); if (parent == mw || parent == mw->statusBar() diff --git a/src/Gui/ToolBarManager.h b/src/Gui/ToolBarManager.h index e245a6660b..43f2dcfad7 100644 --- a/src/Gui/ToolBarManager.h +++ b/src/Gui/ToolBarManager.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -88,6 +89,53 @@ private: QList _items; }; +class ToolBarGrip: public QWidget +{ + Q_OBJECT + +public: + explicit ToolBarGrip(QToolBar *); + + void attach(); + void detach(); + + bool isAttached() const; + +protected: + void paintEvent(QPaintEvent*); + void mouseMoveEvent(QMouseEvent *); + void mousePressEvent(QMouseEvent *); + void mouseReleaseEvent(QMouseEvent *); + + void updateSize(); + +private: + QPointer _action = nullptr; +}; + +/** + * QToolBar from Qt lacks few abilities like ability to float toolbar from code. + * This class allows us to provide custom behaviors for toolbars if needed. + */ +class GuiExport ToolBar: public QToolBar +{ + Q_OBJECT + + friend class ToolBarGrip; + +public: + ToolBar(); + explicit ToolBar(QWidget* parent); + + virtual ~ToolBar() = default; + + void undock(); + void updateCustomGripVisibility(); + +protected: + void setupConnections(); +}; + /** * The ToolBarManager class is responsible for the creation of toolbars and appending them * to the main window. @@ -121,13 +169,12 @@ public: void setState(const QList& names, State state); void setState(const QString& name, State state); - - int toolBarIconSize(QWidget *widget=nullptr) const; + + int toolBarIconSize(QWidget *widget = nullptr) const; void setupToolBarIconSize(); - void populateUndockMenu(QMenu *menu, ToolBarAreaWidget *area = nullptr); - ToolBarArea toolBarArea(QWidget* toolBar) const; + ToolBarAreaWidget* toolBarAreaWidget(QWidget* toolBar) const; protected: void setup(ToolBarItem*, QToolBar*) const; @@ -145,8 +192,8 @@ protected: bool eventFilter(QObject *source, QEvent *ev) override; /** Returns a list of all currently existing toolbars. */ - QList toolBars() const; - QToolBar* findToolBar(const QList&, const QString&) const; + QList toolBars() const; + ToolBar* findToolBar(const QList&, const QString&) const; QAction* findAction(const QList&, const QString&) const; ToolBarManager(); ~ToolBarManager() override; @@ -160,6 +207,8 @@ private: void setupSizeTimer(); void setupResizeTimer(); void setupMenuBarTimer(); + void setupWidgetProducers(); + void addToMenu(QLayout* layout, QWidget* area, QMenu* menu); QLayout* findLayoutOfObject(QObject* source, QWidget* area) const; ToolBarAreaWidget* findToolBarAreaWidget() const; diff --git a/src/Gui/resource.cpp b/src/Gui/resource.cpp index d7961ab35d..ee3ff5d8ab 100644 --- a/src/Gui/resource.cpp +++ b/src/Gui/resource.cpp @@ -59,6 +59,7 @@ #include "InputField.h" #include "QuantitySpinBox.h" #include "PrefWidgets.h" +#include "ToolBarManager.h" using namespace Gui; using namespace Gui::Dialog; diff --git a/src/Mod/BIM/BimStatusBar.py b/src/Mod/BIM/BimStatusBar.py index e9f329cc48..0a361999c9 100644 --- a/src/Mod/BIM/BimStatusBar.py +++ b/src/Mod/BIM/BimStatusBar.py @@ -117,7 +117,7 @@ def setStatusIcons(show=True): if statuswidget: statuswidget.show() else: - statuswidget = QtGui.QToolBar() + statuswidget = FreeCADGui.UiLoader().createWidget("Gui::ToolBar") statuswidget.setObjectName("BIMStatusWidget") s = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/General").GetInt("ToolbarIconSize", 24) statuswidget.setIconSize(QtCore.QSize(s,s)) diff --git a/src/Mod/BIM/bimcommands/BimIfcExplorer.py b/src/Mod/BIM/bimcommands/BimIfcExplorer.py index 30997d22cd..76cb68e543 100644 --- a/src/Mod/BIM/bimcommands/BimIfcExplorer.py +++ b/src/Mod/BIM/bimcommands/BimIfcExplorer.py @@ -106,7 +106,7 @@ class BIM_IfcExplorer: self.dialog.setObjectName("IfcExplorer") self.dialog.setWindowTitle(translate("BIM", "Ifc Explorer")) self.dialog.resize(720, 540) - toolbar = QtGui.QToolBar() + toolbar = FreeCADGui.UiLoader().createWidget("Gui::ToolBar") layout = QtGui.QVBoxLayout(self.dialog) layout.addWidget(toolbar) diff --git a/src/Mod/BIM/bimcommands/BimViews.py b/src/Mod/BIM/bimcommands/BimViews.py index 85ecc03056..ba04e0b648 100644 --- a/src/Mod/BIM/bimcommands/BimViews.py +++ b/src/Mod/BIM/bimcommands/BimViews.py @@ -81,7 +81,7 @@ class BIM_Views: size = FreeCAD.ParamGet( "User parameter:BaseApp/Preferences/General" ).GetInt("ToolbarIconSize", 24) - toolbar = QtGui.QToolBar() + toolbar = FreeCADGui.UiLoader().createWidget("Gui::ToolBar") toolbar.setIconSize(QtCore.QSize(size, size)) dialog.horizontalLayout.addWidget(toolbar) for button in [ diff --git a/src/Mod/Draft/DraftGui.py b/src/Mod/Draft/DraftGui.py index c5fc3c2d33..487d8f703d 100644 --- a/src/Mod/Draft/DraftGui.py +++ b/src/Mod/Draft/DraftGui.py @@ -205,7 +205,7 @@ class DraftToolBar: # add only a dummy widget, since widgets are created on demand self.baseWidget = DraftBaseWidget() - self.tray = QtWidgets.QToolBar(None) + self.tray = FreeCADGui.UiLoader().createWidget("Gui::ToolBar") self.tray.setObjectName("Draft tray") self.tray.setWindowTitle("Draft tray") self.toptray = self.tray