diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index eee53308af..ddac2f35bd 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -627,20 +627,29 @@ WorkbenchGroup::WorkbenchGroup ( Command* pcCmd, QObject * parent ) this, &WorkbenchGroup::onWorkbenchActivated); } +QAction* WorkbenchGroup::getOrCreateAction(const QString& wbName) { + if (!actionByWorkbenchName.contains(wbName)) { + actionByWorkbenchName[wbName] = new QAction; + } + + return actionByWorkbenchName[wbName]; +} + void WorkbenchGroup::addTo(QWidget *widget) { if (widget->inherits("QToolBar")) { ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); - QWidget* wbSel; + + QWidget* workbenchSelectorWidget; if (hGrp->GetInt("WorkbenchSelectorType", 0) == 0) { - wbSel = new WorkbenchComboBox(this, widget); + workbenchSelectorWidget = new WorkbenchComboBox(this, widget); } else { - wbSel = new WorkbenchTabWidget(this, widget); + workbenchSelectorWidget = new WorkbenchTabWidget(this, widget); } - static_cast(widget)->addWidget(wbSel); + static_cast(widget)->addWidget(workbenchSelectorWidget); } else if (widget->inherits("QMenu")) { auto menu = qobject_cast(widget); @@ -656,13 +665,13 @@ void WorkbenchGroup::addTo(QWidget *widget) void WorkbenchGroup::refreshWorkbenchList() { - QStringList enabled_wbs_list = DlgSettingsWorkbenchesImp::getEnabledWorkbenches(); + QStringList enabledWbNames = DlgSettingsWorkbenchesImp::getEnabledWorkbenches(); // Clear the actions. for (QAction* action : actions()) { groupAction()->removeAction(action); - delete action; } + enabledWbsActions.clear(); disabledWbsActions.clear(); @@ -670,12 +679,16 @@ void WorkbenchGroup::refreshWorkbenchList() // Create action list of enabled wb int index = 0; - for (const auto& wbName : enabled_wbs_list) { + for (const auto& wbName : enabledWbNames) { QString name = Application::Instance->workbenchMenuText(wbName); QPixmap px = Application::Instance->workbenchIcon(wbName); QString tip = Application::Instance->workbenchToolTip(wbName); - QAction* action = groupAction()->addAction(name); + QAction* action = getOrCreateAction(wbName); + + groupAction()->addAction(action); + + action->setText(name); action->setCheckable(true); action->setData(QVariant(index)); // set the index action->setObjectName(wbName); @@ -693,13 +706,17 @@ void WorkbenchGroup::refreshWorkbenchList() } // Also create action list of disabled wbs - QStringList disabled_wbs_list = DlgSettingsWorkbenchesImp::getDisabledWorkbenches(); - for (const auto& wbName : disabled_wbs_list) { + QStringList disabledWbNames = DlgSettingsWorkbenchesImp::getDisabledWorkbenches(); + for (const auto& wbName : disabledWbNames) { QString name = Application::Instance->workbenchMenuText(wbName); QPixmap px = Application::Instance->workbenchIcon(wbName); QString tip = Application::Instance->workbenchToolTip(wbName); - QAction* action = groupAction()->addAction(name); + QAction* action = getOrCreateAction(wbName); + + groupAction()->addAction(action); + + action->setText(name); action->setCheckable(true); action->setData(QVariant(index)); // set the index action->setObjectName(wbName); diff --git a/src/Gui/Action.h b/src/Gui/Action.h index 54f83c4acd..1ae1062a15 100644 --- a/src/Gui/Action.h +++ b/src/Gui/Action.h @@ -28,6 +28,7 @@ #include #include #include +#include #include namespace Gui @@ -188,13 +189,16 @@ class GuiExport WorkbenchGroup : public ActionGroup { Q_OBJECT + QAction* getOrCreateAction(const QString& wbName); + public: /** * Creates an action for the command \a pcCmd to load the workbench \a name * when it gets activated. */ - WorkbenchGroup (Command* pcCmd, QObject * parent); - void addTo (QWidget * widget) override; + WorkbenchGroup(Command* pcCmd, QObject* parent); + + void addTo(QWidget * widget) override; void refreshWorkbenchList(); void slotActivateWorkbench(const char*); @@ -212,6 +216,8 @@ private: QList enabledWbsActions; QList disabledWbsActions; + QMap actionByWorkbenchName; + Q_DISABLE_COPY(WorkbenchGroup) }; diff --git a/src/Gui/ToolBarManager.cpp b/src/Gui/ToolBarManager.cpp index 8bb99d26d8..c0b8f13e53 100644 --- a/src/Gui/ToolBarManager.cpp +++ b/src/Gui/ToolBarManager.cpp @@ -167,11 +167,13 @@ QList ToolBarItem::getItems() const namespace Gui { -class ToolBarArea : public QWidget +class ToolBarAreaWidget : public QWidget { using inherited = QWidget; + public: - ToolBarArea(QWidget *parent, + ToolBarAreaWidget(QWidget *parent, + ToolBarArea area, const ParameterGrp::handle& hParam, boost::signals2::scoped_connection &conn, QTimer *timer = nullptr) @@ -179,34 +181,45 @@ public: , _sizingTimer(timer) , _hParam(hParam) , _conn(conn) + , _area(area) { _layout = new QHBoxLayout(this); _layout->setContentsMargins(QMargins()); } - void addWidget(QWidget *w) + void addWidget(QWidget *widget) { - if (_layout->indexOf(w) < 0) { - _layout->addWidget(w); - adjustParent(); - QString name = w->objectName(); - if (!name.isEmpty()) { - Base::ConnectionBlocker block(_conn); - _hParam->SetInt(w->objectName().toUtf8().constData(), _layout->count()-1); - } + if (_layout->indexOf(widget) > 0) { + return; + } + + _layout->addWidget(widget); + adjustParent(); + + QString name = widget->objectName(); + + if (!name.isEmpty()) { + Base::ConnectionBlocker block(_conn); + _hParam->SetInt(widget->objectName().toUtf8().constData(), _layout->count() - 1); } } - void insertWidget(int idx, QWidget *w) + void insertWidget(int index, QWidget *widget) { - int index = _layout->indexOf(w); - if (index == idx) { + int currentIndex = _layout->indexOf(widget); + + // we are inserting widget at the same place, this is no-op + if (currentIndex == index) { return; } - if (index > 0) { - _layout->removeWidget(w); + + // widget already exists in the area, we need to first remove it and then recreate + if (currentIndex > 0) { + _layout->removeWidget(widget); } - _layout->insertWidget(idx, w); + + _layout->insertWidget(index, widget); + adjustParent(); saveState(); } @@ -218,20 +231,23 @@ public: } } - void removeWidget(QWidget *w) + void removeWidget(QWidget *widget) { - _layout->removeWidget(w); - QString name = w->objectName(); + _layout->removeWidget(widget); + + QString name = widget->objectName(); if (!name.isEmpty()) { Base::ConnectionBlocker block(_conn); _hParam->RemoveInt(name.toUtf8().constData()); } + adjustParent(); } QWidget *widgetAt(int index) const { auto item = _layout->itemAt(index); + return item ? item->widget() : nullptr; } @@ -240,9 +256,14 @@ public: return _layout->count(); } - int indexOf(QWidget *w) const + int indexOf(QWidget *widget) const { - return _layout->indexOf(w); + return _layout->indexOf(widget); + } + + ToolBarArea area() const + { + return _area; } template @@ -250,10 +271,12 @@ public: { for (int i = 0, c = _layout->count(); i < c; ++i) { auto toolbar = qobject_cast(widgetAt(i)); + if (!toolbar || toolbar->objectName().isEmpty() || toolbar->objectName().startsWith(QStringLiteral("*"))) { continue; } + func(toolbar, i, this); } } @@ -261,10 +284,12 @@ public: void saveState() { Base::ConnectionBlocker block(_conn); + for (auto &v : _hParam->GetIntMap()) { _hParam->RemoveInt(v.first.c_str()); } - foreachToolBar([this](QToolBar *toolbar, int idx, ToolBarArea*) { + + foreachToolBar([this](QToolBar *toolbar, int idx, ToolBarAreaWidget*) { _hParam->SetInt(toolbar->objectName().toUtf8().constData(), idx); }); } @@ -281,17 +306,19 @@ public: for (const auto &[name, visible] : _hParam->GetBoolMap()) { auto widget = findChild(QString::fromUtf8(name.c_str())); + if (widget) { widget->setVisible(visible); } } - } + }; private: QHBoxLayout *_layout; QPointer _sizingTimer; ParameterGrp::handle _hParam; boost::signals2::scoped_connection &_conn; + ToolBarArea _area; }; } // namespace Gui @@ -343,10 +370,10 @@ void ToolBarManager::setupStatusBar() { if (auto sb = getMainWindow()->statusBar()) { sb->installEventFilter(this); - statusBarArea = new ToolBarArea(sb, hStatusBar, connParam); - statusBarArea->setObjectName(QStringLiteral("StatusBarArea")); - sb->insertPermanentWidget(2, statusBarArea); - statusBarArea->show(); + statusBarAreaWidget = new ToolBarAreaWidget(sb, ToolBarArea::StatusBarToolBarArea, hStatusBar, connParam); + statusBarAreaWidget->setObjectName(QStringLiteral("StatusBarArea")); + sb->insertPermanentWidget(2, statusBarAreaWidget); + statusBarAreaWidget->show(); } } @@ -354,14 +381,14 @@ void ToolBarManager::setupMenuBar() { if (auto mb = getMainWindow()->menuBar()) { mb->installEventFilter(this); - menuBarLeftArea = new ToolBarArea(mb, hMenuBarLeft, connParam, &menuBarTimer); - menuBarLeftArea->setObjectName(QStringLiteral("MenuBarLeftArea")); - mb->setCornerWidget(menuBarLeftArea, Qt::TopLeftCorner); - menuBarLeftArea->show(); - menuBarRightArea = new ToolBarArea(mb, hMenuBarRight, connParam, &menuBarTimer); - menuBarRightArea->setObjectName(QStringLiteral("MenuBarRightArea")); - mb->setCornerWidget(menuBarRightArea, Qt::TopRightCorner); - menuBarRightArea->show(); + menuBarLeftAreaWidget = new ToolBarAreaWidget(mb, ToolBarArea::LeftMenuToolBarArea, hMenuBarLeft, connParam, &menuBarTimer); + menuBarLeftAreaWidget->setObjectName(QStringLiteral("MenuBarLeftArea")); + mb->setCornerWidget(menuBarLeftAreaWidget, Qt::TopLeftCorner); + menuBarLeftAreaWidget->show(); + menuBarRightAreaWidget = new ToolBarAreaWidget(mb, ToolBarArea::RightMenuToolBarArea, hMenuBarRight, connParam, &menuBarTimer); + menuBarRightAreaWidget->setObjectName(QStringLiteral("MenuBarRightArea")); + mb->setCornerWidget(menuBarRightAreaWidget, Qt::TopRightCorner); + menuBarRightAreaWidget->show(); } } @@ -440,6 +467,38 @@ void ToolBarManager::setupMenuBarTimer() }); } +ToolBarArea ToolBarManager::toolBarArea(QWidget *widget) const +{ + if (auto toolBar = qobject_cast(widget)) { + if (toolBar->isFloating()) { + return ToolBarArea::NoToolBarArea; + } + + auto qtToolBarArea = getMainWindow()->toolBarArea(toolBar); + switch (qtToolBarArea) { + case Qt::LeftToolBarArea: + return ToolBarArea::LeftToolBarArea; + case Qt::RightToolBarArea: + return ToolBarArea::RightToolBarArea; + case Qt::TopToolBarArea: + return ToolBarArea::TopToolBarArea; + case Qt::BottomToolBarArea: + return ToolBarArea::BottomToolBarArea; + default: + // no-op + break; + } + } + + for (auto &areaWidget : { statusBarAreaWidget, menuBarLeftAreaWidget, menuBarRightAreaWidget }) { + if (areaWidget->indexOf(widget) >= 0) { + return areaWidget->area(); + } + } + + return ToolBarArea::NoToolBarArea; +} + namespace { QPointer createActionWidget() { @@ -470,7 +529,7 @@ int ToolBarManager::toolBarIconSize(QWidget *widget) const { int s = _toolBarIconSize; if (widget) { - if (widget->parentWidget() == statusBarArea) { + if (widget->parentWidget() == statusBarAreaWidget) { if (_statusBarIconSize > 0) { s = _statusBarIconSize; } @@ -478,8 +537,8 @@ int ToolBarManager::toolBarIconSize(QWidget *widget) const s *= 0.6; } } - else if (widget->parentWidget() == menuBarLeftArea - || widget->parentWidget() == menuBarRightArea) { + else if (widget->parentWidget() == menuBarLeftAreaWidget + || widget->parentWidget() == menuBarRightAreaWidget) { if (_menuBarIconSize > 0) { s = _menuBarIconSize; } @@ -508,11 +567,11 @@ void ToolBarManager::setToolBarIconSize(QToolBar *toolbar) { int s = toolBarIconSize(toolbar); toolbar->setIconSize(QSize(s, s)); - if (toolbar->parentWidget() == menuBarLeftArea) { - menuBarLeftArea->adjustParent(); + if (toolbar->parentWidget() == menuBarLeftAreaWidget) { + menuBarLeftAreaWidget->adjustParent(); } - else if (toolbar->parentWidget() == menuBarRightArea) { - menuBarRightArea->adjustParent(); + else if (toolbar->parentWidget() == menuBarRightAreaWidget) { + menuBarRightAreaWidget->adjustParent(); } } @@ -750,9 +809,9 @@ void ToolBarManager::restoreState() const setMovable(!areToolBarsLocked()); - statusBarArea->restoreState(sbToolBars); - menuBarRightArea->restoreState(mbRightToolBars); - menuBarLeftArea->restoreState(mbLeftToolBars); + statusBarAreaWidget->restoreState(sbToolBars); + menuBarRightAreaWidget->restoreState(mbRightToolBars); + menuBarLeftAreaWidget->restoreState(mbLeftToolBars); } bool ToolBarManager::addToolBarToArea(QObject *source, QMouseEvent *ev) @@ -776,7 +835,7 @@ bool ToolBarManager::addToolBarToArea(QObject *source, QMouseEvent *ev) } static QPointer tbPlaceholder; - static QPointer lastArea; + static QPointer lastArea; static int tbIndex = -1; if (ev->type() == QEvent::MouseMove) { if (tb->orientation() != Qt::Horizontal @@ -798,11 +857,11 @@ bool ToolBarManager::addToolBarToArea(QObject *source, QMouseEvent *ev) } QPoint pos = QCursor::pos(); - ToolBarArea *area = nullptr; + ToolBarAreaWidget *area = nullptr; if (statusBar) { QRect rect(statusBar->mapToGlobal(QPoint(0,0)), statusBar->size()); if (rect.contains(pos)) { - area = statusBarArea; + area = statusBarAreaWidget; } } if (!area) { @@ -812,10 +871,10 @@ bool ToolBarManager::addToolBarToArea(QObject *source, QMouseEvent *ev) QRect rect(menuBar->mapToGlobal(QPoint(0,0)), menuBar->size()); if (rect.contains(pos)) { if (pos.x() - rect.left() < menuBar->width()/2) { - area = menuBarLeftArea; + area = menuBarLeftAreaWidget; } else { - area = menuBarRightArea; + area = menuBarRightAreaWidget; } } else { @@ -833,11 +892,11 @@ bool ToolBarManager::addToolBarToArea(QObject *source, QMouseEvent *ev) int idx = 0; for (int c = area->count(); idx < c ;++idx) { - auto w = area->widgetAt(idx); - if (!w || w->isHidden()) { + auto widget = area->widgetAt(idx); + if (!widget || widget->isHidden()) { continue; } - int p = w->mapToGlobal(w->rect().center()).x(); + int p = widget->mapToGlobal(widget->rect().center()).x(); if (pos.x() < p) { break; } @@ -886,40 +945,60 @@ bool ToolBarManager::addToolBarToArea(QObject *source, QMouseEvent *ev) return false; } -void ToolBarManager::populateUndockMenu(QMenu *menu, ToolBarArea *area) +void ToolBarManager::populateUndockMenu(QMenu *menu, ToolBarAreaWidget *area) { menu->setTitle(tr("Undock toolbars")); - auto tooltip = QObject::tr("Undock from status bar"); - auto addMenuUndockItem = [&](QToolBar *toolbar, int, ToolBarArea *area) { - auto *action = toolbar->toggleViewAction(); + 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(action->text()); + + 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)); - QSignalBlocker blocker(toolbar); - area->removeWidget(toolbar); - getMainWindow()->addToolBar(toolbar); - toolbar->setWindowFlags(Qt::Tool - | Qt::FramelessWindowHint - | Qt::X11BypassWindowManagerHint); - toolbar->move(pos.x(), pos.y()-toolbar->height()-10); - toolbar->adjustSize(); - toolbar->setVisible(true); + 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) { + + if (area) { area->foreachToolBar(addMenuUndockItem); } else { - statusBarArea->foreachToolBar(addMenuUndockItem); - menuBarLeftArea->foreachToolBar(addMenuUndockItem); - menuBarRightArea->foreachToolBar(addMenuUndockItem); + statusBarAreaWidget->foreachToolBar(addMenuUndockItem); + menuBarLeftAreaWidget->foreachToolBar(addMenuUndockItem); + menuBarRightAreaWidget->foreachToolBar(addMenuUndockItem); } } @@ -928,13 +1007,13 @@ bool ToolBarManager::showContextMenu(QObject *source) QMenu menu; QMenu menuUndock; QLayout* layout = nullptr; - ToolBarArea* area = nullptr; + ToolBarAreaWidget* area = nullptr; if (getMainWindow()->statusBar() == source) { - area = statusBarArea; + area = statusBarAreaWidget; layout = findLayoutOfObject(source, area); } else if (getMainWindow()->menuBar() == source) { - area = findToolBarArea(); + area = findToolBarAreaWidget(); if (!area) { return false; } @@ -943,7 +1022,7 @@ bool ToolBarManager::showContextMenu(QObject *source) return false; } - auto addMenuVisibleItem = [&](QToolBar *toolbar, int, ToolBarArea *) { + auto addMenuVisibleItem = [&](QToolBar *toolbar, int, ToolBarAreaWidget *) { auto action = toolbar->toggleViewAction(); if ((action->isVisible() || toolbar->isVisible()) && action->text().size()) { action->setVisible(true); @@ -979,18 +1058,19 @@ QLayout* ToolBarManager::findLayoutOfObject(QObject* source, QWidget* area) cons return layout; } -ToolBarArea* ToolBarManager::findToolBarArea() const +ToolBarAreaWidget* ToolBarManager::findToolBarAreaWidget() const { - ToolBarArea* area = nullptr; + ToolBarAreaWidget* area = nullptr; + QPoint pos = QCursor::pos(); - QRect rect(menuBarLeftArea->mapToGlobal(QPoint(0,0)), menuBarLeftArea->size()); + QRect rect(menuBarLeftAreaWidget->mapToGlobal(QPoint(0,0)), menuBarLeftAreaWidget->size()); if (rect.contains(pos)) { - area = menuBarLeftArea; + area = menuBarLeftAreaWidget; } else { - rect = QRect(menuBarRightArea->mapToGlobal(QPoint(0,0)), menuBarRightArea->size()); + rect = QRect(menuBarRightAreaWidget->mapToGlobal(QPoint(0,0)), menuBarRightAreaWidget->size()); if (rect.contains(pos)) { - area = menuBarRightArea; + area = menuBarRightAreaWidget; } } @@ -1042,7 +1122,7 @@ bool ToolBarManager::eventFilter(QObject *source, QEvent *ev) case QEvent::Hide: if (auto toolbar = qobject_cast(source)) { auto parent = toolbar->parentWidget(); - if (parent == menuBarLeftArea || parent == menuBarRightArea) { + if (parent == menuBarLeftAreaWidget || parent == menuBarRightAreaWidget) { menuBarTimer.start(10); } } @@ -1130,9 +1210,9 @@ QList ToolBarManager::toolBars() const auto parent = it->parentWidget(); if (parent == mw || parent == mw->statusBar() - || parent == statusBarArea - || parent == menuBarLeftArea - || parent == menuBarRightArea) { + || parent == statusBarAreaWidget + || parent == menuBarLeftAreaWidget + || parent == menuBarRightAreaWidget) { tb.push_back(it); it->installEventFilter(const_cast(this)); } diff --git a/src/Gui/ToolBarManager.h b/src/Gui/ToolBarManager.h index c2e695bdf3..f5ee13cc6f 100644 --- a/src/Gui/ToolBarManager.h +++ b/src/Gui/ToolBarManager.h @@ -41,7 +41,20 @@ class QToolBar; namespace Gui { -class ToolBarArea; +// Qt treats area as Flag so in theory toolbar could be in multiple areas at once. +// We don't do that here so simple enum should suffice. +enum GuiExport ToolBarArea { + NoToolBarArea, + LeftToolBarArea, + RightToolBarArea, + TopToolBarArea, + BottomToolBarArea, + LeftMenuToolBarArea, + RightMenuToolBarArea, + StatusBarToolBarArea, +}; + +class ToolBarAreaWidget; class GuiExport ToolBarItem { @@ -123,7 +136,9 @@ public: int toolBarIconSize(QWidget *widget=nullptr) const; void setupToolBarIconSize(); - void populateUndockMenu(QMenu *menu, ToolBarArea *area = nullptr); + void populateUndockMenu(QMenu *menu, ToolBarAreaWidget *area = nullptr); + + ToolBarArea toolBarArea(QWidget* toolBar) const; protected: void setup(ToolBarItem*, QToolBar*) const; @@ -158,7 +173,7 @@ private: void setupMenuBarTimer(); void addToMenu(QLayout* layout, QWidget* area, QMenu* menu); QLayout* findLayoutOfObject(QObject* source, QWidget* area) const; - ToolBarArea* findToolBarArea() const; + ToolBarAreaWidget* findToolBarAreaWidget() const; private: QStringList toolbarNames; @@ -169,9 +184,9 @@ private: QTimer sizeTimer; QTimer resizeTimer; boost::signals2::scoped_connection connParam; - ToolBarArea *statusBarArea = nullptr; - ToolBarArea *menuBarLeftArea = nullptr; - ToolBarArea *menuBarRightArea = nullptr; + ToolBarAreaWidget *statusBarAreaWidget = nullptr; + ToolBarAreaWidget *menuBarLeftAreaWidget = nullptr; + ToolBarAreaWidget *menuBarRightAreaWidget = nullptr; ParameterGrp::handle hGeneral; ParameterGrp::handle hPref; ParameterGrp::handle hStatusBar; diff --git a/src/Gui/WorkbenchSelector.cpp b/src/Gui/WorkbenchSelector.cpp index 6ef25ce62e..8a08a950c2 100644 --- a/src/Gui/WorkbenchSelector.cpp +++ b/src/Gui/WorkbenchSelector.cpp @@ -31,16 +31,18 @@ # include # include # include +# include +# include #endif +#include "Base/Tools.h" #include "Action.h" #include "BitmapFactory.h" #include "Command.h" -#include "PreferencePages/DlgSettingsWorkbenchesImp.h" #include "DlgPreferencesImp.h" #include "MainWindow.h" -#include "WorkbenchManager.h" #include "WorkbenchSelector.h" +#include "ToolBarManager.h" using namespace Gui; @@ -77,17 +79,18 @@ void WorkbenchComboBox::refreshList(QList actionList) { clear(); - ParameterGrp::handle hGrp; - hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); - int itemStyleIndex = hGrp->GetInt("WorkbenchSelectorItem", 0); + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); + + auto itemStyle = static_cast(hGrp->GetInt("WorkbenchSelectorItem", 0)); for (QAction* action : actionList) { QIcon icon = action->icon(); - if (icon.isNull() || itemStyleIndex == 2) { + + if (icon.isNull() || itemStyle == WorkbenchItemStyle::TextOnly) { addItem(action->text()); } - else if (itemStyleIndex == 1) { - addItem(icon, QString::fromLatin1("")); + else if (itemStyle == WorkbenchItemStyle::IconOnly) { + addItem(icon, {}); // empty string to ensure that only icon is displayed } else { addItem(icon, action->text()); @@ -101,156 +104,307 @@ void WorkbenchComboBox::refreshList(QList actionList) WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) - : QTabBar(parent) + : QWidget(parent) , wbActionGroup(aGroup) { setToolTip(aGroup->toolTip()); setStatusTip(aGroup->action()->statusTip()); setWhatsThis(aGroup->action()->whatsThis()); - - QAction* moreAction = new QAction(this); - menu = new QMenu(this); - moreAction->setMenu(menu); - connect(moreAction, &QAction::triggered, [this]() { - menu->popup(QCursor::pos()); - }); - connect(menu, &QMenu::aboutToHide, this, [this]() { - // if the more tab did not triggered a disabled workbench, make sure we reselect the correct tab. - std::string activeWbName = WorkbenchManager::instance()->activeName(); - for (int i = 0; i < count(); ++i) { - if (wbActionGroup->actions()[i]->objectName().toStdString() == activeWbName) { - setCurrentIndex(i + 1); - break; - } - } - }); - - if (parent->inherits("QToolBar")) { - // set the initial orientation. We cannot do updateLayoutAndTabOrientation(false); - // because on init the toolbar area is always TopToolBarArea. - ParameterGrp::handle hGrp; - hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); - std::string orientation = hGrp->GetASCII("TabBarOrientation", "North"); - - this->setShape(orientation == "North" ? QTabBar::RoundedNorth : - orientation == "South" ? QTabBar::RoundedSouth : - orientation == "East" ? QTabBar::RoundedEast : - QTabBar::RoundedWest); - } - - setDocumentMode(true); - setUsesScrollButtons(true); - setDrawBase(true); setObjectName(QString::fromLatin1("WbTabBar")); - setIconSize(QSize(16, 16)); - refreshList(aGroup->getEnabledWbActions()); - connect(aGroup, &WorkbenchGroup::workbenchListRefreshed, this, &WorkbenchTabWidget::refreshList); - connect(aGroup->groupAction(), &QActionGroup::triggered, this, [this, aGroup](QAction* action) { - int index = aGroup->actions().indexOf(action) + 1; - if (index > this->count() - 1) { - index = 0; - } - setCurrentIndex(index); - }); - connect(this, qOverload(&QTabBar::tabBarClicked), aGroup, [aGroup, moreAction](int index) { - if(index == 0) { - moreAction->trigger(); - } - else if (index <= aGroup->getEnabledWbActions().size()) { - aGroup->actions()[index - 1]->trigger(); - } - }); + tabBar = new QTabBar(this); + moreButton = new QToolButton(this); + layout = new QBoxLayout(QBoxLayout::LeftToRight, this); + + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(tabBar); + layout->addWidget(moreButton); + layout->setAlignment(moreButton, Qt::AlignCenter); + + setLayout(layout); + + moreButton->setIcon(Gui::BitmapFactory().iconFromTheme("list-add")); + moreButton->setToolButtonStyle(Qt::ToolButtonIconOnly); + moreButton->setPopupMode(QToolButton::InstantPopup); + moreButton->setMenu(new QMenu(moreButton)); + moreButton->setObjectName(QString::fromLatin1("WbTabBarMore")); if (parent->inherits("QToolBar")) { - // Connect toolbar orientation changed - QToolBar* tb = qobject_cast(parent); - connect(tb, &QToolBar::topLevelChanged, this, &WorkbenchTabWidget::updateLayoutAndTabOrientation); + // when toolbar is created it is not yet placed in its designated area + // therefore we need to wait a bit and then update layout when it is ready + // this is prone to race conditions, but Qt does not supply any event that + // informs us about toolbar changing its placement. + // + // previous implementation saved that information to user settings and + // restored last layout but this creates issues when default workbench has + // different layout than last visited one + QTimer::singleShot(500, [this]() { updateLayout(); }); + } + + tabBar->setDocumentMode(true); + tabBar->setUsesScrollButtons(true); + tabBar->setDrawBase(true); + tabBar->setIconSize(QSize(16, 16)); + + updateWorkbenchList(); + + connect(aGroup, &WorkbenchGroup::workbenchListRefreshed, this, &WorkbenchTabWidget::updateWorkbenchList); + connect(aGroup->groupAction(), &QActionGroup::triggered, this, &WorkbenchTabWidget::handleWorkbenchSelection); + connect(tabBar, &QTabBar::currentChanged, this, &WorkbenchTabWidget::handleTabChange); + + if (auto toolBar = qobject_cast(parent)) { + connect(toolBar, &QToolBar::topLevelChanged, this, &WorkbenchTabWidget::updateLayout); + connect(toolBar, &QToolBar::orientationChanged, this, &WorkbenchTabWidget::updateLayout); } } -void WorkbenchTabWidget::refreshList(QList actionList) +inline Qt::LayoutDirection WorkbenchTabWidget::direction() const { - // tabs->clear() (QTabBar has no clear) - for (int i = count() - 1; i >= 0; --i) { - removeTab(i); - } - - ParameterGrp::handle hGrp; - hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); - int itemStyleIndex = hGrp->GetInt("WorkbenchSelectorItem", 0); - - QIcon icon = Gui::BitmapFactory().iconFromTheme("list-add"); - if (itemStyleIndex == 2) { - addTab(QString::fromLatin1("+")); - } - else { - addTab(icon, QString::fromLatin1("")); - } - - for (QAction* action : actionList) { - QIcon icon = action->icon(); - if (icon.isNull() || itemStyleIndex == 2) { - addTab(action->text()); - } - else if (itemStyleIndex == 1) { - addTab(icon, QString::fromLatin1("")); - } - else { - addTab(icon, action->text()); - } - - if (action->isChecked()) { - setCurrentIndex(count() - 1); - } - } - - buildPrefMenu(); + return _direction; } -void WorkbenchTabWidget::updateLayoutAndTabOrientation(bool floating) +void WorkbenchTabWidget::setDirection(Qt::LayoutDirection direction) { - auto parent = parentWidget(); - if (!parent || !parent->inherits("QToolBar")) { + _direction = direction; + + Q_EMIT directionChanged(direction); +} + +inline int WorkbenchTabWidget::temporaryWorkbenchTabIndex() const +{ + if (direction() == Qt::RightToLeft) { + return 0; + } + + int nextTabIndex = tabBar->count(); + + return temporaryWorkbenchAction ? nextTabIndex - 1 : nextTabIndex; +} + +QAction* WorkbenchTabWidget::workbenchActivateActionByTabIndex(int tabIndex) const +{ + if (temporaryWorkbenchAction && tabIndex == temporaryWorkbenchTabIndex()) { + return temporaryWorkbenchAction; + } + + auto it = tabIndexToAction.find(tabIndex); + + if (it != tabIndexToAction.end()) { + return it->second; + } + + return nullptr; +} + +int WorkbenchTabWidget::tabIndexForWorkbenchActivateAction(QAction* workbenchActivateAction) const +{ + if (workbenchActivateAction == temporaryWorkbenchAction) { + return temporaryWorkbenchTabIndex(); + } + + return actionToTabIndex.at(workbenchActivateAction); +} + +void WorkbenchTabWidget::updateLayout() +{ + if (!parentWidget()) { + setToolBarArea(Gui::ToolBarArea::TopToolBarArea); return; } - ParameterGrp::handle hGrp = App::GetApplication() - .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); + if (auto toolBar = qobject_cast(parentWidget())) { + setSizePolicy(toolBar->sizePolicy()); + tabBar->setSizePolicy(toolBar->sizePolicy()); - Qt::ToolBarArea area; - parent = parent->parentWidget(); + if (toolBar->isFloating()) { + setToolBarArea(toolBar->orientation() == Qt::Horizontal ? Gui::ToolBarArea::TopToolBarArea : Gui::ToolBarArea::LeftToolBarArea); + return; + } + } - if (floating) { - area = Qt::TopToolBarArea; + auto toolBarArea = Gui::ToolBarManager::getInstance()->toolBarArea(parentWidget()); + + setToolBarArea(toolBarArea); + + tabBar->setSelectionBehaviorOnRemove( + direction() == Qt::LeftToRight + ? QTabBar::SelectLeftTab + : QTabBar::SelectRightTab + ); +} + +void WorkbenchTabWidget::handleWorkbenchSelection(QAction* selectedWorkbenchAction) +{ + if (wbActionGroup->getDisabledWbActions().contains(selectedWorkbenchAction)) { + if (temporaryWorkbenchAction == selectedWorkbenchAction) { + return; + } + + setTemporaryWorkbenchTab(selectedWorkbenchAction); } - else if (parent && parent->parentWidget() == getMainWindow()->statusBar()) { - area = Qt::BottomToolBarArea; + + updateLayout(); + + tabBar->setCurrentIndex(tabIndexForWorkbenchActivateAction(selectedWorkbenchAction)); +} + +void WorkbenchTabWidget::setTemporaryWorkbenchTab(QAction* workbenchActivateAction) +{ + auto temporaryTabIndex = temporaryWorkbenchTabIndex(); + + if (temporaryWorkbenchAction) { + temporaryWorkbenchAction = nullptr; + tabBar->removeTab(temporaryTabIndex); } - else if (parent && parent->parentWidget() == getMainWindow()->menuBar()) { - area = Qt::TopToolBarArea; + + temporaryWorkbenchAction = workbenchActivateAction; + + if (!workbenchActivateAction) { + return; + } + + addWorkbenchTab(workbenchActivateAction, temporaryTabIndex); + + adjustSize(); +} + +void WorkbenchTabWidget::handleTabChange(int selectedTabIndex) +{ + // Prevents from rapid workbench changes on initialization as this can cause + // some serious race conditions. + if (isInitializing) { + return; + } + + if (auto workbenchActivateAction = workbenchActivateActionByTabIndex(selectedTabIndex)) { + workbenchActivateAction->trigger(); + } + + if (selectedTabIndex != temporaryWorkbenchTabIndex()) { + setTemporaryWorkbenchTab(nullptr); + } + + adjustSize(); +} + +void WorkbenchTabWidget::updateWorkbenchList() +{ + if (isInitializing) { + return; + } + + // As clearing and adding tabs can cause changing current tab in QTabBar. + // This in turn will cause workbench to change, so we need to prevent + // processing of such events until the QTabBar is fully prepared. + Base::StateLocker lock(isInitializing); + + actionToTabIndex.clear(); + tabIndexToAction.clear(); + + // tabs->clear() (QTabBar has no clear) + for (int i = tabBar->count() - 1; i >= 0; --i) { + tabBar->removeTab(i); + } + + for (QAction* action : wbActionGroup->getEnabledWbActions()) { + addWorkbenchTab(action); + } + + if (temporaryWorkbenchAction != nullptr) { + setTemporaryWorkbenchTab(temporaryWorkbenchAction); + } + + buildPrefMenu(); + adjustSize(); +} + +int WorkbenchTabWidget::addWorkbenchTab(QAction* action, int tabIndex) +{ + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); + auto itemStyle = static_cast(hGrp->GetInt("WorkbenchSelectorItem", 0)); + + // if tabIndex is negative we assume that tab must be placed at the end of tabBar (default behavior) + if (tabIndex < 0) { + tabIndex = tabBar->count(); + } + + // for the maps we consider order in which tabs have been added + // that's why here we use tabBar->count() instead of tabIndex + actionToTabIndex[action] = tabBar->count(); + tabIndexToAction[tabBar->count()] = action; + + QIcon icon = action->icon(); + if (icon.isNull() || itemStyle == WorkbenchItemStyle::TextOnly) { + tabBar->insertTab(tabIndex, action->text()); + } + else if (itemStyle == IconOnly) { + tabBar->insertTab(tabIndex, icon, {}); // empty string to ensure only icon is displayed } else { - QToolBar* tb = qobject_cast(parentWidget()); - area = getMainWindow()->toolBarArea(tb); + tabBar->insertTab(tabIndex, icon, action->text()); } - if (area == Qt::LeftToolBarArea || area == Qt::RightToolBarArea) { - setShape(area == Qt::LeftToolBarArea ? QTabBar::RoundedWest : QTabBar::RoundedEast); - hGrp->SetASCII("TabBarOrientation", area == Qt::LeftToolBarArea ? "West" : "East"); + tabBar->setTabToolTip(tabIndex, action->toolTip()); + + if (action->isChecked()) { + tabBar->setCurrentIndex(tabIndex); } - else { - setShape(area == Qt::TopToolBarArea ? QTabBar::RoundedNorth : QTabBar::RoundedSouth); - hGrp->SetASCII("TabBarOrientation", area == Qt::TopToolBarArea ? "North" : "South"); + + return tabIndex; +} + +void WorkbenchTabWidget::setToolBarArea(Gui::ToolBarArea area) +{ + switch (area) { + case Gui::ToolBarArea::LeftToolBarArea: + case Gui::ToolBarArea::RightToolBarArea: { + setDirection(Qt::LeftToRight); + layout->setDirection(direction() == Qt::LeftToRight ? QBoxLayout::TopToBottom : QBoxLayout::BottomToTop); + tabBar->setShape(area == Gui::ToolBarArea::LeftToolBarArea ? QTabBar::RoundedWest : QTabBar::RoundedEast); + break; + } + + case Gui::ToolBarArea::TopToolBarArea: + case Gui::ToolBarArea::BottomToolBarArea: + case Gui::ToolBarArea::LeftMenuToolBarArea: + case Gui::ToolBarArea::RightMenuToolBarArea: + case Gui::ToolBarArea::StatusBarToolBarArea: { + bool isTop = + area == Gui::ToolBarArea::TopToolBarArea || + area == Gui::ToolBarArea::LeftMenuToolBarArea || + area == Gui::ToolBarArea::RightMenuToolBarArea; + + bool isRightAligned = + area == Gui::ToolBarArea::RightMenuToolBarArea || + area == Gui::ToolBarArea::StatusBarToolBarArea; + + setDirection(isRightAligned ? Qt::RightToLeft : Qt::LeftToRight); + layout->setDirection(direction() == Qt::LeftToRight ? QBoxLayout::LeftToRight : QBoxLayout::RightToLeft); + tabBar->setShape(isTop ? QTabBar::RoundedNorth : QTabBar::RoundedSouth); + break; + } + default: + // no-op + break; } + + adjustSize(); } void WorkbenchTabWidget::buildPrefMenu() { + auto menu = moreButton->menu(); + menu->clear(); // Add disabled workbenches, sorted alphabetically. - menu->addActions(wbActionGroup->getDisabledWbActions()); + for (auto action : wbActionGroup->getDisabledWbActions()) { + if (action->text() == QString::fromLatin1("")) { + continue; + } + + menu->addAction(action); + } menu->addSeparator(); @@ -262,4 +416,11 @@ void WorkbenchTabWidget::buildPrefMenu() }); } +void WorkbenchTabWidget::adjustSize() +{ + QWidget::adjustSize(); + + parentWidget()->adjustSize(); +} + #include "moc_WorkbenchSelector.cpp" diff --git a/src/Gui/WorkbenchSelector.h b/src/Gui/WorkbenchSelector.h index f5bd696955..b60724fd9b 100644 --- a/src/Gui/WorkbenchSelector.h +++ b/src/Gui/WorkbenchSelector.h @@ -27,12 +27,22 @@ #include #include #include +#include +#include #include +#include +#include namespace Gui { class WorkbenchGroup; +enum WorkbenchItemStyle { + IconAndText = 0, + IconOnly = 1, + TextOnly = 2 +}; + class GuiExport WorkbenchComboBox : public QComboBox { Q_OBJECT @@ -49,22 +59,55 @@ private: }; -class GuiExport WorkbenchTabWidget : public QTabBar +class GuiExport WorkbenchTabWidget : public QWidget { Q_OBJECT + Q_PROPERTY(Qt::LayoutDirection direction READ direction WRITE setDirection NOTIFY directionChanged) + + int addWorkbenchTab(QAction* workbenchActivateAction, int index = -1); + + void setTemporaryWorkbenchTab(QAction* workbenchActivateAction); + int temporaryWorkbenchTabIndex() const; + + QAction* workbenchActivateActionByTabIndex(int tabIndex) const; + int tabIndexForWorkbenchActivateAction(QAction* workbenchActivateAction) const; public: explicit WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent = nullptr); - void updateLayoutAndTabOrientation(bool); + void setToolBarArea(Gui::ToolBarArea area); void buildPrefMenu(); + Qt::LayoutDirection direction() const; + void setDirection(Qt::LayoutDirection direction); + + void adjustSize(); + public Q_SLOTS: - void refreshList(QList); + void handleWorkbenchSelection(QAction* selectedWorkbenchAction); + void handleTabChange(int selectedTabIndex); + + void updateLayout(); + void updateWorkbenchList(); + +Q_SIGNALS: + void directionChanged(const Qt::LayoutDirection&); private: + bool isInitializing = false; + WorkbenchGroup* wbActionGroup; - QMenu* menu; + QToolButton* moreButton; + QTabBar* tabBar; + QBoxLayout* layout; + + Qt::LayoutDirection _direction = Qt::LeftToRight; + + // this action is used for workbenches that are typically disabled + QAction* temporaryWorkbenchAction = nullptr; + + std::map actionToTabIndex; + std::map tabIndexToAction; };