Files
create/src/Gui/WorkbenchSelector.cpp
2024-06-29 18:15:52 -05:00

432 lines
14 KiB
C++

/***************************************************************************
* Copyright (c) 2024 Pierre-Louis Boyer <development[at]Ondsel.com> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#include "PreCompiled.h"
#ifndef _PreComp_
# include <QAbstractItemView>
# include <QActionGroup>
# include <QApplication>
# include <QMenuBar>
# include <QScreen>
# include <QStatusBar>
# include <QToolBar>
# include <QLayout>
# include <QTimer>
#endif
#include "Base/Tools.h"
#include "Action.h"
#include "BitmapFactory.h"
#include "Command.h"
#include "DlgPreferencesImp.h"
#include "MainWindow.h"
#include "WorkbenchSelector.h"
#include "ToolBarAreaWidget.h"
using namespace Gui;
WorkbenchComboBox::WorkbenchComboBox(WorkbenchGroup* aGroup, QWidget* parent) : QComboBox(parent)
{
setIconSize(QSize(16, 16));
setToolTip(aGroup->toolTip());
setStatusTip(aGroup->action()->statusTip());
setWhatsThis(aGroup->action()->whatsThis());
refreshList(aGroup->getEnabledWbActions());
connect(aGroup, &WorkbenchGroup::workbenchListRefreshed, this, &WorkbenchComboBox::refreshList);
connect(aGroup->groupAction(), &QActionGroup::triggered, this, [this, aGroup](QAction* action) {
setCurrentIndex(aGroup->actions().indexOf(action));
});
connect(this, qOverload<int>(&WorkbenchComboBox::activated), aGroup, [aGroup](int index) {
aGroup->actions()[index]->trigger();
});
}
void WorkbenchComboBox::showPopup()
{
int rows = count();
if (rows > 0) {
int height = view()->sizeHintForRow(0);
int maxHeight = QApplication::primaryScreen()->size().height();
view()->setMinimumHeight(qMin(height * rows, maxHeight/2));
}
QComboBox::showPopup();
}
void WorkbenchComboBox::refreshList(QList<QAction*> actionList)
{
clear();
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches");
auto itemStyle = static_cast<WorkbenchItemStyle>(hGrp->GetInt("WorkbenchSelectorItem", 0));
for (QAction* action : actionList) {
QIcon icon = action->icon();
if (icon.isNull() || itemStyle == WorkbenchItemStyle::TextOnly) {
addItem(action->text());
}
else if (itemStyle == WorkbenchItemStyle::IconOnly) {
addItem(icon, {}); // empty string to ensure that only icon is displayed
}
else {
addItem(icon, action->text());
}
if (action->isChecked()) {
this->setCurrentIndex(this->count() - 1);
}
}
}
WorkbenchTabWidget::WorkbenchTabWidget(WorkbenchGroup* aGroup, QWidget* parent)
: QWidget(parent)
, wbActionGroup(aGroup)
{
setToolTip(aGroup->toolTip());
setStatusTip(aGroup->action()->statusTip());
setWhatsThis(aGroup->action()->whatsThis());
setObjectName(QString::fromLatin1("WbTabBar"));
tabBar = new WbTabBar(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")) {
// 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<QToolBar*>(parent)) {
connect(toolBar, &QToolBar::topLevelChanged, this, &WorkbenchTabWidget::updateLayout);
connect(toolBar, &QToolBar::orientationChanged, this, &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);
}
WorkbenchItemStyle Gui::WorkbenchTabWidget::itemStyle() const
{
ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/Workbenches");
return static_cast<WorkbenchItemStyle>(hGrp->GetInt("WorkbenchSelectorItem", 0));
}
void WorkbenchTabWidget::updateLayout()
{
if (!parentWidget()) {
setToolBarArea(Gui::ToolBarArea::TopToolBarArea);
return;
}
if (auto toolBar = qobject_cast<QToolBar*>(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);
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);
}
updateLayout();
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)
{
// 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;
}
tabBar->setItemStyle(itemStyle());
// 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)
{
auto itemStyle = this->itemStyle();
// 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 == WorkbenchItemStyle::IconOnly) {
tabBar->insertTab(tabIndex, icon, {}); // empty string to ensure only icon is displayed
}
else {
tabBar->insertTab(tabIndex, icon, action->text());
}
tabBar->setTabToolTip(tabIndex, action->toolTip());
if (action->isChecked()) {
tabBar->setCurrentIndex(tabIndex);
}
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.
for (auto action : wbActionGroup->getDisabledWbActions()) {
if (action->text() == QString::fromLatin1("<none>")) {
continue;
}
menu->addAction(action);
}
menu->addSeparator();
QAction* preferencesAction = menu->addAction(tr("Preferences"));
connect(preferencesAction, &QAction::triggered, this, []() {
Gui::Dialog::DlgPreferencesImp cDlg(getMainWindow());
cDlg.activateGroupPage(QString::fromUtf8("Workbenches"), 0);
cDlg.exec();
});
}
void WorkbenchTabWidget::adjustSize()
{
QWidget::adjustSize();
parentWidget()->adjustSize();
}
#include "moc_WorkbenchSelector.cpp"