1
/***************************************************************************
2
* Copyright (c) 2024 Pierre-Louis Boyer <development[at]Ondsel.com> *
4
* This file is part of FreeCAD. *
6
* FreeCAD is free software: you can redistribute it and/or modify it *
7
* under the terms of the GNU Lesser General Public License as *
8
* published by the Free Software Foundation, either version 2.1 of the *
9
* License, or (at your option) any later version. *
11
* FreeCAD is distributed in the hope that it will be useful, but *
12
* WITHOUT ANY WARRANTY; without even the implied warranty of *
13
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
14
* Lesser General Public License for more details. *
16
* You should have received a copy of the GNU Lesser General Public *
17
* License along with FreeCAD. If not, see *
18
* <https://www.gnu.org/licenses/>. *
20
***************************************************************************/
23
#include "PreCompiled.h"
26
# include <QAbstractItemView>
27
# include <QActionGroup>
28
# include <QApplication>
37
#include "Base/Tools.h"
39
#include "BitmapFactory.h"
41
#include "DlgPreferencesImp.h"
42
#include "MainWindow.h"
43
#include "WorkbenchSelector.h"
44
#include "ToolBarAreaWidget.h"
49
WorkbenchComboBox::WorkbenchComboBox(WorkbenchGroup* aGroup, QWidget* parent) : QComboBox(parent)
51
setIconSize(QSize(16, 16));
52
setToolTip(aGroup->toolTip());
53
setStatusTip(aGroup->action()->statusTip());
54
setWhatsThis(aGroup->action()->whatsThis());
55
refreshList(aGroup->getEnabledWbActions());
56
connect(aGroup, &WorkbenchGroup::workbenchListRefreshed, this, &WorkbenchComboBox::refreshList);
57
connect(aGroup->groupAction(), &QActionGroup::triggered, this, [this, aGroup](QAction* action) {
58
setCurrentIndex(aGroup->actions().indexOf(action));
60
connect(this, qOverload<int>(&WorkbenchComboBox::activated), aGroup, [aGroup](int index) {
61
aGroup->actions()[index]->trigger();
65
void WorkbenchComboBox::showPopup()
69
int height = view()->sizeHintForRow(0);
70
int maxHeight = QApplication::primaryScreen()->size().height();
71
view()->setMinimumHeight(qMin(height * rows, maxHeight/2));
74
QComboBox::showPopup();
77
void WorkbenchComboBox::refreshList(QList<QAction*> actionList)
81
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches");
83
auto itemStyle = static_cast<WorkbenchItemStyle>(hGrp->GetInt("WorkbenchSelectorItem", 0));
85
for (QAction* action : actionList) {
86
QIcon icon = action->icon();
88
if (icon.isNull() || itemStyle == WorkbenchItemStyle::TextOnly) {
89
addItem(action->text());
91
else if (itemStyle == WorkbenchItemStyle::IconOnly) {
92
addItem(icon, {}); // empty string to ensure that only icon is displayed
95
addItem(icon, action->text());
98
if (action->isChecked()) {
99
this->setCurrentIndex(this->count() - 1);
104
WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent)
106
, wbActionGroup(aGroup)
108
setToolTip(aGroup->toolTip());
109
setStatusTip(aGroup->action()->statusTip());
110
setWhatsThis(aGroup->action()->whatsThis());
111
setObjectName(QString::fromLatin1("WbTabBar"));
113
tabBar = new WbTabBar(this);
114
moreButton = new QToolButton(this);
115
layout = new QBoxLayout(QBoxLayout::LeftToRight, this);
117
layout->setContentsMargins(0, 0, 0, 0);
118
layout->addWidget(tabBar);
119
layout->addWidget(moreButton);
120
layout->setAlignment(moreButton, Qt::AlignCenter);
124
moreButton->setIcon(Gui::BitmapFactory().iconFromTheme("list-add"));
125
moreButton->setToolButtonStyle(Qt::ToolButtonIconOnly);
126
moreButton->setPopupMode(QToolButton::InstantPopup);
127
moreButton->setMenu(new QMenu(moreButton));
128
moreButton->setObjectName(QString::fromLatin1("WbTabBarMore"));
130
if (parent->inherits("QToolBar")) {
131
// when toolbar is created it is not yet placed in its designated area
132
// therefore we need to wait a bit and then update layout when it is ready
133
// this is prone to race conditions, but Qt does not supply any event that
134
// informs us about toolbar changing its placement.
136
// previous implementation saved that information to user settings and
137
// restored last layout but this creates issues when default workbench has
138
// different layout than last visited one
139
QTimer::singleShot(500, [this]() { updateLayout(); });
142
tabBar->setDocumentMode(true);
143
tabBar->setUsesScrollButtons(true);
144
tabBar->setDrawBase(true);
145
tabBar->setIconSize(QSize(16, 16));
147
updateWorkbenchList();
149
connect(aGroup, &WorkbenchGroup::workbenchListRefreshed, this, &WorkbenchTabWidget::updateWorkbenchList);
150
connect(aGroup->groupAction(), &QActionGroup::triggered, this, &WorkbenchTabWidget::handleWorkbenchSelection);
151
connect(tabBar, &QTabBar::currentChanged, this, &WorkbenchTabWidget::handleTabChange);
153
if (auto toolBar = qobject_cast<QToolBar*>(parent)) {
154
connect(toolBar, &QToolBar::topLevelChanged, this, &WorkbenchTabWidget::updateLayout);
155
connect(toolBar, &QToolBar::orientationChanged, this, &WorkbenchTabWidget::updateLayout);
159
inline Qt::LayoutDirection WorkbenchTabWidget::direction() const
164
void WorkbenchTabWidget::setDirection(Qt::LayoutDirection direction)
166
_direction = direction;
168
Q_EMIT directionChanged(direction);
171
inline int WorkbenchTabWidget::temporaryWorkbenchTabIndex() const
173
if (direction() == Qt::RightToLeft) {
177
int nextTabIndex = tabBar->count();
179
return temporaryWorkbenchAction ? nextTabIndex - 1 : nextTabIndex;
182
QAction* WorkbenchTabWidget::workbenchActivateActionByTabIndex(int tabIndex) const
184
if (temporaryWorkbenchAction && tabIndex == temporaryWorkbenchTabIndex()) {
185
return temporaryWorkbenchAction;
188
auto it = tabIndexToAction.find(tabIndex);
190
if (it != tabIndexToAction.end()) {
197
int WorkbenchTabWidget::tabIndexForWorkbenchActivateAction(QAction* workbenchActivateAction) const
199
if (workbenchActivateAction == temporaryWorkbenchAction) {
200
return temporaryWorkbenchTabIndex();
203
return actionToTabIndex.at(workbenchActivateAction);
206
WorkbenchItemStyle Gui::WorkbenchTabWidget::itemStyle() const
208
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches");
209
return static_cast<WorkbenchItemStyle>(hGrp->GetInt("WorkbenchSelectorItem", 0));
212
void WorkbenchTabWidget::updateLayout()
214
if (!parentWidget()) {
215
setToolBarArea(Gui::ToolBarArea::TopToolBarArea);
219
if (auto toolBar = qobject_cast<QToolBar*>(parentWidget())) {
220
setSizePolicy(toolBar->sizePolicy());
221
tabBar->setSizePolicy(toolBar->sizePolicy());
223
if (toolBar->isFloating()) {
224
setToolBarArea(toolBar->orientation() == Qt::Horizontal ? Gui::ToolBarArea::TopToolBarArea : Gui::ToolBarArea::LeftToolBarArea);
229
auto toolBarArea = Gui::ToolBarManager::getInstance()->toolBarArea(parentWidget());
231
setToolBarArea(toolBarArea);
233
tabBar->setSelectionBehaviorOnRemove(
234
direction() == Qt::LeftToRight
235
? QTabBar::SelectLeftTab
236
: QTabBar::SelectRightTab
240
void WorkbenchTabWidget::handleWorkbenchSelection(QAction* selectedWorkbenchAction)
242
if (wbActionGroup->getDisabledWbActions().contains(selectedWorkbenchAction)) {
243
if (temporaryWorkbenchAction == selectedWorkbenchAction) {
247
setTemporaryWorkbenchTab(selectedWorkbenchAction);
252
tabBar->setCurrentIndex(tabIndexForWorkbenchActivateAction(selectedWorkbenchAction));
255
void WorkbenchTabWidget::setTemporaryWorkbenchTab(QAction* workbenchActivateAction)
257
auto temporaryTabIndex = temporaryWorkbenchTabIndex();
259
if (temporaryWorkbenchAction) {
260
temporaryWorkbenchAction = nullptr;
261
tabBar->removeTab(temporaryTabIndex);
264
temporaryWorkbenchAction = workbenchActivateAction;
266
if (!workbenchActivateAction) {
270
addWorkbenchTab(workbenchActivateAction, temporaryTabIndex);
275
void WorkbenchTabWidget::handleTabChange(int selectedTabIndex)
277
// Prevents from rapid workbench changes on initialization as this can cause
278
// some serious race conditions.
279
if (isInitializing) {
283
if (auto workbenchActivateAction = workbenchActivateActionByTabIndex(selectedTabIndex)) {
284
workbenchActivateAction->trigger();
287
if (selectedTabIndex != temporaryWorkbenchTabIndex()) {
288
setTemporaryWorkbenchTab(nullptr);
294
void WorkbenchTabWidget::updateWorkbenchList()
296
if (isInitializing) {
300
tabBar->setItemStyle(itemStyle());
302
// As clearing and adding tabs can cause changing current tab in QTabBar.
303
// This in turn will cause workbench to change, so we need to prevent
304
// processing of such events until the QTabBar is fully prepared.
305
Base::StateLocker lock(isInitializing);
307
actionToTabIndex.clear();
308
tabIndexToAction.clear();
310
// tabs->clear() (QTabBar has no clear)
311
for (int i = tabBar->count() - 1; i >= 0; --i) {
312
tabBar->removeTab(i);
315
for (QAction* action : wbActionGroup->getEnabledWbActions()) {
316
addWorkbenchTab(action);
319
if (temporaryWorkbenchAction != nullptr) {
320
setTemporaryWorkbenchTab(temporaryWorkbenchAction);
327
int WorkbenchTabWidget::addWorkbenchTab(QAction* action, int tabIndex)
329
auto itemStyle = this->itemStyle();
331
// if tabIndex is negative we assume that tab must be placed at the end of tabBar (default behavior)
333
tabIndex = tabBar->count();
336
// for the maps we consider order in which tabs have been added
337
// that's why here we use tabBar->count() instead of tabIndex
338
actionToTabIndex[action] = tabBar->count();
339
tabIndexToAction[tabBar->count()] = action;
341
QIcon icon = action->icon();
342
if (icon.isNull() || itemStyle == WorkbenchItemStyle::TextOnly) {
343
tabBar->insertTab(tabIndex, action->text());
345
else if (itemStyle == WorkbenchItemStyle::IconOnly) {
346
tabBar->insertTab(tabIndex, icon, {}); // empty string to ensure only icon is displayed
349
tabBar->insertTab(tabIndex, icon, action->text());
352
tabBar->setTabToolTip(tabIndex, action->toolTip());
354
if (action->isChecked()) {
355
tabBar->setCurrentIndex(tabIndex);
361
void WorkbenchTabWidget::setToolBarArea(Gui::ToolBarArea area)
364
case Gui::ToolBarArea::LeftToolBarArea:
365
case Gui::ToolBarArea::RightToolBarArea: {
366
setDirection(Qt::LeftToRight);
367
layout->setDirection(direction() == Qt::LeftToRight ? QBoxLayout::TopToBottom : QBoxLayout::BottomToTop);
368
tabBar->setShape(area == Gui::ToolBarArea::LeftToolBarArea ? QTabBar::RoundedWest : QTabBar::RoundedEast);
372
case Gui::ToolBarArea::TopToolBarArea:
373
case Gui::ToolBarArea::BottomToolBarArea:
374
case Gui::ToolBarArea::LeftMenuToolBarArea:
375
case Gui::ToolBarArea::RightMenuToolBarArea:
376
case Gui::ToolBarArea::StatusBarToolBarArea: {
378
area == Gui::ToolBarArea::TopToolBarArea ||
379
area == Gui::ToolBarArea::LeftMenuToolBarArea ||
380
area == Gui::ToolBarArea::RightMenuToolBarArea;
382
bool isRightAligned =
383
area == Gui::ToolBarArea::RightMenuToolBarArea ||
384
area == Gui::ToolBarArea::StatusBarToolBarArea;
386
setDirection(isRightAligned ? Qt::RightToLeft : Qt::LeftToRight);
387
layout->setDirection(direction() == Qt::LeftToRight ? QBoxLayout::LeftToRight : QBoxLayout::RightToLeft);
388
tabBar->setShape(isTop ? QTabBar::RoundedNorth : QTabBar::RoundedSouth);
399
void WorkbenchTabWidget::buildPrefMenu()
401
auto menu = moreButton->menu();
405
// Add disabled workbenches, sorted alphabetically.
406
for (auto action : wbActionGroup->getDisabledWbActions()) {
407
if (action->text() == QString::fromLatin1("<none>")) {
411
menu->addAction(action);
414
menu->addSeparator();
416
QAction* preferencesAction = menu->addAction(tr("Preferences"));
417
connect(preferencesAction, &QAction::triggered, this, []() {
418
Gui::Dialog::DlgPreferencesImp cDlg(getMainWindow());
419
cDlg.activateGroupPage(QString::fromUtf8("Workbenches"), 0);
424
void WorkbenchTabWidget::adjustSize()
426
QWidget::adjustSize();
428
parentWidget()->adjustSize();
431
#include "moc_WorkbenchSelector.cpp"