From 90dc3e33706aaa6cfc1559f66fb7f1b83b953a27 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Mon, 6 May 2024 23:34:09 +0200 Subject: [PATCH 1/7] Gui: Add our ToolBarArea enumeration This refactors implementation of toolbars in menu / status bar a bit. It introduces enum with all possible areas like it is in Qt that can be later used to decide what to do based on toolbar placement. --- src/Gui/ToolBarManager.cpp | 252 ++++++++++++++++++++++++------------- src/Gui/ToolBarManager.h | 27 +++- 2 files changed, 187 insertions(+), 92 deletions(-) diff --git a/src/Gui/ToolBarManager.cpp b/src/Gui/ToolBarManager.cpp index a69d283a7b..c3de923293 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(); } } @@ -751,9 +810,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) @@ -777,7 +836,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 @@ -799,11 +858,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) { @@ -813,10 +872,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 { @@ -834,11 +893,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; } @@ -887,40 +946,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); } } @@ -929,13 +1008,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; } @@ -944,7 +1023,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); @@ -980,18 +1059,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; } } @@ -1043,7 +1123,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); } } @@ -1131,9 +1211,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; From 3c7487638b0dc47b730e56cdda94d0afebd9548a Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Mon, 29 Apr 2024 23:17:04 +0200 Subject: [PATCH 2/7] Gui: Move more button to the end WB TabBar This changes back placement of the "more" button of the WB TabBar to be at the end, where it should be naturally placed. In order to ensure that it is always visible the control was reworked to show this button always after the tab bar widget which now is dynamically sized. This is behavior that is well known from browsers. This commit also ensures that active workbench is always visible in the TabBar by adding additional temporary tab when necessary. This tab will automatically dissapear when not needed. Fixes: #13720 Fixes: #13630 --- src/Gui/WorkbenchSelector.cpp | 150 ++++++++++++++++++++-------------- src/Gui/WorkbenchSelector.h | 13 ++- 2 files changed, 101 insertions(+), 62 deletions(-) diff --git a/src/Gui/WorkbenchSelector.cpp b/src/Gui/WorkbenchSelector.cpp index 6ef25ce62e..252cd19e5a 100644 --- a/src/Gui/WorkbenchSelector.cpp +++ b/src/Gui/WorkbenchSelector.cpp @@ -31,6 +31,7 @@ # include # include # include +# include #endif #include "Action.h" @@ -101,29 +102,30 @@ 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()); + setObjectName(QString::fromLatin1("WbTabBar")); - 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; - } - } - }); + tabBar = new QTabBar(this); + moreButton = new QToolButton(this); + + auto layout = new QHBoxLayout(this); + + layout->setContentsMargins(0, 0, 0, 0); + layout->addWidget(tabBar); + layout->addWidget(moreButton); + + 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")) { // set the initial orientation. We cannot do updateLayoutAndTabOrientation(false); @@ -132,33 +134,47 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); std::string orientation = hGrp->GetASCII("TabBarOrientation", "North"); - this->setShape(orientation == "North" ? QTabBar::RoundedNorth : + tabBar->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)); + tabBar->setDocumentMode(true); + tabBar->setUsesScrollButtons(true); + tabBar->setDrawBase(true); + tabBar->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; + connect(aGroup->groupAction(), &QActionGroup::triggered, this, [this](QAction* action) { + if (wbActionGroup->getDisabledWbActions().contains(action)) { + if (additionalWorkbenchAction == action) { + return; + } + + if (additionalWorkbenchAction) { + tabBar->removeTab(tabBar->count() - 1); + } + + additionalWorkbenchAction = action; + + addWorkbenchTab(action); + tabBar->setCurrentIndex(tabBar->count() - 1); + + return; } - setCurrentIndex(index); + + tabBar->setCurrentIndex(actionToTabIndex[action]); }); - 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(); + + connect(tabBar, qOverload(&QTabBar::currentChanged), aGroup, [this](int index) { + tabIndexToAction[index]->trigger(); + + if (index != tabBar->count() - 1 && additionalWorkbenchAction) { + tabBar->removeTab(tabBar->count() - 1); + additionalWorkbenchAction = nullptr; } }); @@ -171,41 +187,47 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) void WorkbenchTabWidget::refreshList(QList actionList) { + actionToTabIndex.clear(); + // tabs->clear() (QTabBar has no clear) - for (int i = count() - 1; i >= 0; --i) { - removeTab(i); + for (int i = tabBar->count() - 1; i >= 0; --i) { + tabBar->removeTab(i); } + for (QAction* action : actionList) { + addWorkbenchTab(action); + } + + if (additionalWorkbenchAction != nullptr) { + addWorkbenchTab(additionalWorkbenchAction); + } + + buildPrefMenu(); +} + +void WorkbenchTabWidget::addWorkbenchTab(QAction* action) +{ 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("+")); + actionToTabIndex[action] = tabBar->count(); + tabIndexToAction[tabBar->count()] = action; + + QIcon icon = action->icon(); + if (icon.isNull() || itemStyleIndex == 2) { + tabBar->addTab(action->text()); + } + else if (itemStyleIndex == 1) { + tabBar->addTab(icon, QString::fromLatin1("")); } else { - addTab(icon, QString::fromLatin1("")); + tabBar->addTab(icon, action->text()); } - 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); - } + if (action->isChecked()) { + tabBar->setCurrentIndex(tabBar->count() - 1); } - - buildPrefMenu(); } void WorkbenchTabWidget::updateLayoutAndTabOrientation(bool floating) @@ -236,21 +258,29 @@ void WorkbenchTabWidget::updateLayoutAndTabOrientation(bool floating) } if (area == Qt::LeftToolBarArea || area == Qt::RightToolBarArea) { - setShape(area == Qt::LeftToolBarArea ? QTabBar::RoundedWest : QTabBar::RoundedEast); + tabBar->setShape(area == Qt::LeftToolBarArea ? QTabBar::RoundedWest : QTabBar::RoundedEast); hGrp->SetASCII("TabBarOrientation", area == Qt::LeftToolBarArea ? "West" : "East"); } else { - setShape(area == Qt::TopToolBarArea ? QTabBar::RoundedNorth : QTabBar::RoundedSouth); + tabBar->setShape(area == Qt::TopToolBarArea ? QTabBar::RoundedNorth : QTabBar::RoundedSouth); hGrp->SetASCII("TabBarOrientation", area == Qt::TopToolBarArea ? "North" : "South"); } } 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(); diff --git a/src/Gui/WorkbenchSelector.h b/src/Gui/WorkbenchSelector.h index f5bd696955..f29637d624 100644 --- a/src/Gui/WorkbenchSelector.h +++ b/src/Gui/WorkbenchSelector.h @@ -27,7 +27,9 @@ #include #include #include +#include #include +#include namespace Gui { @@ -49,7 +51,7 @@ private: }; -class GuiExport WorkbenchTabWidget : public QTabBar +class GuiExport WorkbenchTabWidget : public QWidget { Q_OBJECT @@ -61,10 +63,17 @@ public: public Q_SLOTS: void refreshList(QList); + void addWorkbenchTab(QAction* workbenchActivateAction); private: WorkbenchGroup* wbActionGroup; - QMenu* menu; + QToolButton* moreButton; + QTabBar* tabBar; + // this action is used for workbenches that are typically disabled + QAction* additionalWorkbenchAction = nullptr; + + std::map actionToTabIndex; + std::map tabIndexToAction; }; From eb99c8bcda0dfdecaad8daeb1ec7e80aea51a170 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Fri, 3 May 2024 22:38:23 +0200 Subject: [PATCH 3/7] Gui: Add proper tooltip to Workbench TabBar tab --- src/Gui/WorkbenchSelector.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Gui/WorkbenchSelector.cpp b/src/Gui/WorkbenchSelector.cpp index 252cd19e5a..1ab0a62933 100644 --- a/src/Gui/WorkbenchSelector.cpp +++ b/src/Gui/WorkbenchSelector.cpp @@ -225,6 +225,8 @@ void WorkbenchTabWidget::addWorkbenchTab(QAction* action) tabBar->addTab(icon, action->text()); } + tabBar->setTabToolTip(tabBar->count() - 1, action->toolTip()); + if (action->isChecked()) { tabBar->setCurrentIndex(tabBar->count() - 1); } From 727078bbd3cf8b97e029379fa0027fad380c668a Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sat, 4 May 2024 00:45:05 +0200 Subject: [PATCH 4/7] Gui: Adjust sizing of Workbench TabBar when changing orientation This should fix issues when toolbar containing Workbench TabBar suddenly (or not) changes orientation. It also fixes size policies so toolbar resizes properly and does not cause window to grow. Fixes: #13286 --- src/Gui/WorkbenchSelector.cpp | 216 +++++++++++++++++++++------------- src/Gui/WorkbenchSelector.h | 19 ++- 2 files changed, 152 insertions(+), 83 deletions(-) diff --git a/src/Gui/WorkbenchSelector.cpp b/src/Gui/WorkbenchSelector.cpp index 1ab0a62933..dfec90806b 100644 --- a/src/Gui/WorkbenchSelector.cpp +++ b/src/Gui/WorkbenchSelector.cpp @@ -34,14 +34,14 @@ # 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; @@ -113,11 +113,12 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) tabBar = new QTabBar(this); moreButton = new QToolButton(this); - auto layout = new QHBoxLayout(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); @@ -134,10 +135,12 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); std::string orientation = hGrp->GetASCII("TabBarOrientation", "North"); - tabBar->setShape(orientation == "North" ? QTabBar::RoundedNorth : - orientation == "South" ? QTabBar::RoundedSouth : - orientation == "East" ? QTabBar::RoundedEast : - QTabBar::RoundedWest); + setToolBarArea( + orientation == "North" ? Gui::ToolBarArea::TopToolBarArea : + orientation == "South" ? Gui::ToolBarArea::BottomToolBarArea : + orientation == "East" ? Gui::ToolBarArea::LeftToolBarArea : + Gui::ToolBarArea::RightToolBarArea + ); } tabBar->setDocumentMode(true); @@ -145,48 +148,91 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) tabBar->setDrawBase(true); tabBar->setIconSize(QSize(16, 16)); - refreshList(aGroup->getEnabledWbActions()); + updateWorkbenchList(); - connect(aGroup, &WorkbenchGroup::workbenchListRefreshed, this, &WorkbenchTabWidget::refreshList); - connect(aGroup->groupAction(), &QActionGroup::triggered, this, [this](QAction* action) { - if (wbActionGroup->getDisabledWbActions().contains(action)) { - if (additionalWorkbenchAction == action) { - return; - } + connect(aGroup, &WorkbenchGroup::workbenchListRefreshed, this, &WorkbenchTabWidget::updateWorkbenchList); + connect(aGroup->groupAction(), &QActionGroup::triggered, this, &WorkbenchTabWidget::handleWorkbenchSelection); + connect(tabBar, &QTabBar::currentChanged, this, &WorkbenchTabWidget::handleTabChange); - if (additionalWorkbenchAction) { - tabBar->removeTab(tabBar->count() - 1); - } - - additionalWorkbenchAction = action; - - addWorkbenchTab(action); - tabBar->setCurrentIndex(tabBar->count() - 1); - - return; - } - - tabBar->setCurrentIndex(actionToTabIndex[action]); - }); - - connect(tabBar, qOverload(&QTabBar::currentChanged), aGroup, [this](int index) { - tabIndexToAction[index]->trigger(); - - if (index != tabBar->count() - 1 && additionalWorkbenchAction) { - tabBar->removeTab(tabBar->count() - 1); - additionalWorkbenchAction = nullptr; - } - }); - - if (parent->inherits("QToolBar")) { - // Connect toolbar orientation changed - QToolBar* tb = qobject_cast(parent); - connect(tb, &QToolBar::topLevelChanged, this, &WorkbenchTabWidget::updateLayoutAndTabOrientation); + 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) +void WorkbenchTabWidget::updateLayout() { + if (!parentWidget()) { + setToolBarArea(Gui::ToolBarArea::TopToolBarArea); + return; + } + + if (auto toolBar = qobject_cast(parentWidget())) { + setSizePolicy(toolBar->sizePolicy()); + tabBar->setSizePolicy(toolBar->sizePolicy()); + + if (toolBar->isFloating()) { + setToolBarArea(toolBar->orientation() == Qt::Horizontal ? Gui::ToolBarArea::TopToolBarArea : Gui::ToolBarArea::LeftToolBarArea); + return; + } + } + + auto toolBarArea = Gui::ToolBarManager::getInstance()->toolBarArea(parentWidget()); + + setToolBarArea(toolBarArea); +} + +void WorkbenchTabWidget::handleWorkbenchSelection(QAction* selectedWorkbenchAction) +{ + if (wbActionGroup->getDisabledWbActions().contains(selectedWorkbenchAction)) { + if (additionalWorkbenchAction == selectedWorkbenchAction) { + return; + } + + if (additionalWorkbenchAction) { + tabBar->removeTab(tabBar->count() - 1); + } + + additionalWorkbenchAction = selectedWorkbenchAction; + + addWorkbenchTab(selectedWorkbenchAction); + tabBar->setCurrentIndex(tabBar->count() - 1); + + return; + } + + updateLayout(); + + tabBar->setCurrentIndex(actionToTabIndex[selectedWorkbenchAction]); +} + +void WorkbenchTabWidget::handleTabChange(int selectedTabIndex) +{ + // Prevents from rapid workbench changes on initialization as this can cause + // some serious race conditions. + if (isInitializing) { + return; + } + + if (tabIndexToAction.find(selectedTabIndex) != tabIndexToAction.end()) { + tabIndexToAction[selectedTabIndex]->trigger(); + } + + if (selectedTabIndex != tabBar->count() - 1 && additionalWorkbenchAction) { + tabBar->removeTab(tabBar->count() - 1); + additionalWorkbenchAction = nullptr; + } + + adjustSize(); +} + +void WorkbenchTabWidget::updateWorkbenchList() +{ + // 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(); // tabs->clear() (QTabBar has no clear) @@ -194,7 +240,7 @@ void WorkbenchTabWidget::refreshList(QList actionList) tabBar->removeTab(i); } - for (QAction* action : actionList) { + for (QAction* action : wbActionGroup->getEnabledWbActions()) { addWorkbenchTab(action); } @@ -203,6 +249,7 @@ void WorkbenchTabWidget::refreshList(QList actionList) } buildPrefMenu(); + adjustSize(); } void WorkbenchTabWidget::addWorkbenchTab(QAction* action) @@ -211,8 +258,10 @@ void WorkbenchTabWidget::addWorkbenchTab(QAction* action) hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); int itemStyleIndex = hGrp->GetInt("WorkbenchSelectorItem", 0); - actionToTabIndex[action] = tabBar->count(); - tabIndexToAction[tabBar->count()] = action; + auto tabIndex = tabBar->count(); + + actionToTabIndex[action] = tabIndex; + tabIndexToAction[tabIndex] = action; QIcon icon = action->icon(); if (icon.isNull() || itemStyleIndex == 2) { @@ -225,48 +274,48 @@ void WorkbenchTabWidget::addWorkbenchTab(QAction* action) tabBar->addTab(icon, action->text()); } - tabBar->setTabToolTip(tabBar->count() - 1, action->toolTip()); + tabBar->setTabToolTip(tabIndex, action->toolTip()); if (action->isChecked()) { - tabBar->setCurrentIndex(tabBar->count() - 1); + tabBar->setCurrentIndex(tabIndex); } } -void WorkbenchTabWidget::updateLayoutAndTabOrientation(bool floating) +void WorkbenchTabWidget::setToolBarArea(Gui::ToolBarArea area) { - auto parent = parentWidget(); - if (!parent || !parent->inherits("QToolBar")) { - return; + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); + + switch (area) { + case Gui::ToolBarArea::LeftToolBarArea: + case Gui::ToolBarArea::RightToolBarArea: { + layout->setDirection(QBoxLayout::TopToBottom); + tabBar->setShape(area == Gui::ToolBarArea::LeftToolBarArea ? QTabBar::RoundedWest : QTabBar::RoundedEast); + hGrp->SetASCII("TabBarOrientation", area == Gui::ToolBarArea::LeftToolBarArea ? "West" : "East"); + break; + } + + case Gui::ToolBarArea::TopToolBarArea: + case Gui::ToolBarArea::BottomToolBarArea: + case Gui::ToolBarArea::LeftMenuToolBarArea: + case Gui::ToolBarArea::RightMenuToolBarArea: + case Gui::ToolBarArea::StatusBarToolBarArea: { + layout->setDirection(QBoxLayout::LeftToRight); + + bool isTop = + area == Gui::ToolBarArea::TopToolBarArea || + area == Gui::ToolBarArea::LeftMenuToolBarArea || + area == Gui::ToolBarArea::RightMenuToolBarArea; + + tabBar->setShape(isTop ? QTabBar::RoundedNorth : QTabBar::RoundedSouth); + hGrp->SetASCII("TabBarOrientation", isTop ? "North" : "South"); + break; + } + default: + // no-op + break; } - ParameterGrp::handle hGrp = App::GetApplication() - .GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); - - Qt::ToolBarArea area; - parent = parent->parentWidget(); - - if (floating) { - area = Qt::TopToolBarArea; - } - else if (parent && parent->parentWidget() == getMainWindow()->statusBar()) { - area = Qt::BottomToolBarArea; - } - else if (parent && parent->parentWidget() == getMainWindow()->menuBar()) { - area = Qt::TopToolBarArea; - } - else { - QToolBar* tb = qobject_cast(parentWidget()); - area = getMainWindow()->toolBarArea(tb); - } - - if (area == Qt::LeftToolBarArea || area == Qt::RightToolBarArea) { - tabBar->setShape(area == Qt::LeftToolBarArea ? QTabBar::RoundedWest : QTabBar::RoundedEast); - hGrp->SetASCII("TabBarOrientation", area == Qt::LeftToolBarArea ? "West" : "East"); - } - else { - tabBar->setShape(area == Qt::TopToolBarArea ? QTabBar::RoundedNorth : QTabBar::RoundedSouth); - hGrp->SetASCII("TabBarOrientation", area == Qt::TopToolBarArea ? "North" : "South"); - } + adjustSize(); } void WorkbenchTabWidget::buildPrefMenu() @@ -294,4 +343,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 f29637d624..aae32512e4 100644 --- a/src/Gui/WorkbenchSelector.h +++ b/src/Gui/WorkbenchSelector.h @@ -28,7 +28,9 @@ #include #include #include +#include #include +#include #include namespace Gui @@ -55,20 +57,31 @@ class GuiExport WorkbenchTabWidget : public QWidget { Q_OBJECT + void addWorkbenchTab(QAction* workbenchActivateAction); + public: explicit WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent = nullptr); - void updateLayoutAndTabOrientation(bool); + void setToolBarArea(Gui::ToolBarArea area); void buildPrefMenu(); + + void adjustSize(); + public Q_SLOTS: - void refreshList(QList); - void addWorkbenchTab(QAction* workbenchActivateAction); + void handleWorkbenchSelection(QAction* selectedWorkbenchAction); + void handleTabChange(int selectedTabIndex); + + void updateLayout(); + void updateWorkbenchList(); private: + bool isInitializing = false; + WorkbenchGroup* wbActionGroup; QToolButton* moreButton; QTabBar* tabBar; + QBoxLayout* layout; // this action is used for workbenches that are typically disabled QAction* additionalWorkbenchAction = nullptr; From 75d43f860768e2bae689345abee7fddbad104daf Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sun, 5 May 2024 13:42:37 +0200 Subject: [PATCH 5/7] Gui: Use RTL layout when Wb Tab Bar is placed in right corner Right corner is placed to the right edge of screen, so its natural growth occours on the left side. Basically it is Right to Left order and so in that case the "end" is actually on left and so TabBar should grow in that direction. Unfortunately it is not possible to simply use RTL Qt feature to handle that case as it would result in reverse order of workbenches (people will still read it in LTR order) and icons on the right which is not wanted. That's custom support is introduced. --- src/Gui/Action.cpp | 9 +- src/Gui/WorkbenchSelector.cpp | 162 +++++++++++++++++++++++++--------- src/Gui/WorkbenchSelector.h | 25 +++++- 3 files changed, 148 insertions(+), 48 deletions(-) diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index eee53308af..3806703c8f 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -632,15 +632,16 @@ 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); diff --git a/src/Gui/WorkbenchSelector.cpp b/src/Gui/WorkbenchSelector.cpp index dfec90806b..dc8ca097a4 100644 --- a/src/Gui/WorkbenchSelector.cpp +++ b/src/Gui/WorkbenchSelector.cpp @@ -80,15 +80,17 @@ void WorkbenchComboBox::refreshList(QList actionList) ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); - int itemStyleIndex = hGrp->GetInt("WorkbenchSelectorItem", 0); + + 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()); @@ -149,7 +151,7 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) 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); @@ -160,7 +162,54 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) } } -void WorkbenchTabWidget::updateLayout() +inline Qt::LayoutDirection WorkbenchTabWidget::direction() const +{ + return _direction; +} + +void WorkbenchTabWidget::setDirection(Qt::LayoutDirection direction) +{ + _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); @@ -180,30 +229,47 @@ void WorkbenchTabWidget::updateLayout() auto toolBarArea = Gui::ToolBarManager::getInstance()->toolBarArea(parentWidget()); setToolBarArea(toolBarArea); + + tabBar->setSelectionBehaviorOnRemove( + direction() == Qt::LeftToRight + ? QTabBar::SelectLeftTab + : QTabBar::SelectRightTab + ); } -void WorkbenchTabWidget::handleWorkbenchSelection(QAction* selectedWorkbenchAction) +void WorkbenchTabWidget::handleWorkbenchSelection(QAction* selectedWorkbenchAction) { if (wbActionGroup->getDisabledWbActions().contains(selectedWorkbenchAction)) { - if (additionalWorkbenchAction == selectedWorkbenchAction) { + if (temporaryWorkbenchAction == selectedWorkbenchAction) { return; } - if (additionalWorkbenchAction) { - tabBar->removeTab(tabBar->count() - 1); - } - - additionalWorkbenchAction = selectedWorkbenchAction; - - addWorkbenchTab(selectedWorkbenchAction); - tabBar->setCurrentIndex(tabBar->count() - 1); - - return; + setTemporaryWorkbenchTab(selectedWorkbenchAction); } updateLayout(); - tabBar->setCurrentIndex(actionToTabIndex[selectedWorkbenchAction]); + tabBar->setCurrentIndex(tabIndexForWorkbenchActivateAction(selectedWorkbenchAction)); +} + +void WorkbenchTabWidget::setTemporaryWorkbenchTab(QAction* workbenchActivateAction) +{ + auto temporaryTabIndex = temporaryWorkbenchTabIndex(); + + if (temporaryWorkbenchAction) { + temporaryWorkbenchAction = nullptr; + tabBar->removeTab(temporaryTabIndex); + } + + temporaryWorkbenchAction = workbenchActivateAction; + + if (!workbenchActivateAction) { + return; + } + + addWorkbenchTab(workbenchActivateAction, temporaryTabIndex); + + adjustSize(); } void WorkbenchTabWidget::handleTabChange(int selectedTabIndex) @@ -214,13 +280,12 @@ void WorkbenchTabWidget::handleTabChange(int selectedTabIndex) return; } - if (tabIndexToAction.find(selectedTabIndex) != tabIndexToAction.end()) { - tabIndexToAction[selectedTabIndex]->trigger(); + if (auto workbenchActivateAction = workbenchActivateActionByTabIndex(selectedTabIndex)) { + workbenchActivateAction->trigger(); } - if (selectedTabIndex != tabBar->count() - 1 && additionalWorkbenchAction) { - tabBar->removeTab(tabBar->count() - 1); - additionalWorkbenchAction = nullptr; + if (selectedTabIndex != temporaryWorkbenchTabIndex()) { + setTemporaryWorkbenchTab(nullptr); } adjustSize(); @@ -229,11 +294,12 @@ void WorkbenchTabWidget::handleTabChange(int selectedTabIndex) void WorkbenchTabWidget::updateWorkbenchList() { // 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 + // 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) { @@ -244,34 +310,39 @@ void WorkbenchTabWidget::updateWorkbenchList() addWorkbenchTab(action); } - if (additionalWorkbenchAction != nullptr) { - addWorkbenchTab(additionalWorkbenchAction); + if (temporaryWorkbenchAction != nullptr) { + setTemporaryWorkbenchTab(temporaryWorkbenchAction); } buildPrefMenu(); adjustSize(); } -void WorkbenchTabWidget::addWorkbenchTab(QAction* action) +int WorkbenchTabWidget::addWorkbenchTab(QAction* action, int tabIndex) { ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); - int itemStyleIndex = hGrp->GetInt("WorkbenchSelectorItem", 0); + auto itemStyle = static_cast(hGrp->GetInt("WorkbenchSelectorItem", 0)); - auto tabIndex = tabBar->count(); + // if tabIndex is negative we assume that tab must be placed at the end of tabBar (default behavior) + if (tabIndex < 0) { + tabIndex = tabBar->count(); + } - actionToTabIndex[action] = tabIndex; - tabIndexToAction[tabIndex] = action; + // 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() || itemStyleIndex == 2) { - tabBar->addTab(action->text()); + if (icon.isNull() || itemStyle == WorkbenchItemStyle::TextOnly) { + tabBar->insertTab(tabIndex, action->text()); } - else if (itemStyleIndex == 1) { - tabBar->addTab(icon, QString::fromLatin1("")); + else if (itemStyle == IconOnly) { + tabBar->insertTab(tabIndex, icon, {}); // empty string to ensure only icon is displayed } else { - tabBar->addTab(icon, action->text()); + tabBar->insertTab(tabIndex, icon, action->text()); } tabBar->setTabToolTip(tabIndex, action->toolTip()); @@ -279,6 +350,8 @@ void WorkbenchTabWidget::addWorkbenchTab(QAction* action) if (action->isChecked()) { tabBar->setCurrentIndex(tabIndex); } + + return tabIndex; } void WorkbenchTabWidget::setToolBarArea(Gui::ToolBarArea area) @@ -286,9 +359,10 @@ void WorkbenchTabWidget::setToolBarArea(Gui::ToolBarArea area) ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); switch (area) { - case Gui::ToolBarArea::LeftToolBarArea: + case Gui::ToolBarArea::LeftToolBarArea: case Gui::ToolBarArea::RightToolBarArea: { - layout->setDirection(QBoxLayout::TopToBottom); + setDirection(Qt::LeftToRight); + layout->setDirection(direction() == Qt::LeftToRight ? QBoxLayout::TopToBottom : QBoxLayout::BottomToTop); tabBar->setShape(area == Gui::ToolBarArea::LeftToolBarArea ? QTabBar::RoundedWest : QTabBar::RoundedEast); hGrp->SetASCII("TabBarOrientation", area == Gui::ToolBarArea::LeftToolBarArea ? "West" : "East"); break; @@ -299,13 +373,17 @@ void WorkbenchTabWidget::setToolBarArea(Gui::ToolBarArea area) case Gui::ToolBarArea::LeftMenuToolBarArea: case Gui::ToolBarArea::RightMenuToolBarArea: case Gui::ToolBarArea::StatusBarToolBarArea: { - layout->setDirection(QBoxLayout::LeftToRight); - - bool isTop = + 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); hGrp->SetASCII("TabBarOrientation", isTop ? "North" : "South"); break; diff --git a/src/Gui/WorkbenchSelector.h b/src/Gui/WorkbenchSelector.h index aae32512e4..b60724fd9b 100644 --- a/src/Gui/WorkbenchSelector.h +++ b/src/Gui/WorkbenchSelector.h @@ -37,6 +37,12 @@ namespace Gui { class WorkbenchGroup; +enum WorkbenchItemStyle { + IconAndText = 0, + IconOnly = 1, + TextOnly = 2 +}; + class GuiExport WorkbenchComboBox : public QComboBox { Q_OBJECT @@ -56,8 +62,15 @@ private: class GuiExport WorkbenchTabWidget : public QWidget { Q_OBJECT + Q_PROPERTY(Qt::LayoutDirection direction READ direction WRITE setDirection NOTIFY directionChanged) - void addWorkbenchTab(QAction* workbenchActivateAction); + 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); @@ -65,6 +78,8 @@ public: void setToolBarArea(Gui::ToolBarArea area); void buildPrefMenu(); + Qt::LayoutDirection direction() const; + void setDirection(Qt::LayoutDirection direction); void adjustSize(); @@ -75,6 +90,9 @@ public Q_SLOTS: void updateLayout(); void updateWorkbenchList(); +Q_SIGNALS: + void directionChanged(const Qt::LayoutDirection&); + private: bool isInitializing = false; @@ -82,8 +100,11 @@ private: QToolButton* moreButton; QTabBar* tabBar; QBoxLayout* layout; + + Qt::LayoutDirection _direction = Qt::LeftToRight; + // this action is used for workbenches that are typically disabled - QAction* additionalWorkbenchAction = nullptr; + QAction* temporaryWorkbenchAction = nullptr; std::map actionToTabIndex; std::map tabIndexToAction; From 33c6b044612a9e2c553b5ee3289a6041c2ba8cb3 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sun, 5 May 2024 16:00:08 +0200 Subject: [PATCH 6/7] Gui: Fix wrong orientation of workbench tab bar after start This replaces old mechanism that was based on storing tab bar orientation in user settings with one that delays initialization by half of a second to ensure that toolbar is placed where in right place. --- src/Gui/WorkbenchSelector.cpp | 37 +++++++++++++++-------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/src/Gui/WorkbenchSelector.cpp b/src/Gui/WorkbenchSelector.cpp index dc8ca097a4..8a08a950c2 100644 --- a/src/Gui/WorkbenchSelector.cpp +++ b/src/Gui/WorkbenchSelector.cpp @@ -32,6 +32,7 @@ # include # include # include +# include #endif #include "Base/Tools.h" @@ -78,8 +79,7 @@ void WorkbenchComboBox::refreshList(QList actionList) { clear(); - ParameterGrp::handle hGrp; - hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); + ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); auto itemStyle = static_cast(hGrp->GetInt("WorkbenchSelectorItem", 0)); @@ -114,7 +114,6 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) tabBar = new QTabBar(this); moreButton = new QToolButton(this); - layout = new QBoxLayout(QBoxLayout::LeftToRight, this); layout->setContentsMargins(0, 0, 0, 0); @@ -131,18 +130,15 @@ WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent) moreButton->setObjectName(QString::fromLatin1("WbTabBarMore")); 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"); - - setToolBarArea( - orientation == "North" ? Gui::ToolBarArea::TopToolBarArea : - orientation == "South" ? Gui::ToolBarArea::BottomToolBarArea : - orientation == "East" ? Gui::ToolBarArea::LeftToolBarArea : - Gui::ToolBarArea::RightToolBarArea - ); + // 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); @@ -293,6 +289,10 @@ void WorkbenchTabWidget::handleTabChange(int selectedTabIndex) 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. @@ -320,8 +320,7 @@ void WorkbenchTabWidget::updateWorkbenchList() int WorkbenchTabWidget::addWorkbenchTab(QAction* action, int tabIndex) { - ParameterGrp::handle hGrp; - hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); + 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) @@ -356,15 +355,12 @@ int WorkbenchTabWidget::addWorkbenchTab(QAction* action, int tabIndex) void WorkbenchTabWidget::setToolBarArea(Gui::ToolBarArea area) { - ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches"); - 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); - hGrp->SetASCII("TabBarOrientation", area == Gui::ToolBarArea::LeftToolBarArea ? "West" : "East"); break; } @@ -385,7 +381,6 @@ void WorkbenchTabWidget::setToolBarArea(Gui::ToolBarArea area) setDirection(isRightAligned ? Qt::RightToLeft : Qt::LeftToRight); layout->setDirection(direction() == Qt::LeftToRight ? QBoxLayout::LeftToRight : QBoxLayout::RightToLeft); tabBar->setShape(isTop ? QTabBar::RoundedNorth : QTabBar::RoundedSouth); - hGrp->SetASCII("TabBarOrientation", isTop ? "North" : "South"); break; } default: From 52560ce650f6d3a17fcb9dd78abbee1ea8fa7151 Mon Sep 17 00:00:00 2001 From: Kacper Donat Date: Sun, 5 May 2024 16:55:55 +0200 Subject: [PATCH 7/7] Gui: Reuse QActions for workbench activation This fixes segfault that can occour due to keeping reference to QAction that is supposed to change workbench. --- src/Gui/Action.cpp | 30 +++++++++++++++++++++++------- src/Gui/Action.h | 10 ++++++++-- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/Gui/Action.cpp b/src/Gui/Action.cpp index 3806703c8f..ddac2f35bd 100644 --- a/src/Gui/Action.cpp +++ b/src/Gui/Action.cpp @@ -627,6 +627,14 @@ 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")) { @@ -657,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(); @@ -671,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); @@ -694,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) };