// SPDX-License-Identifier: LGPL-2.1-or-later /**************************************************************************** * Copyright (c) 2020 Chris Hennes (chennes@pioneerlibrarysystem.org) * * Copyright (c) 2023 FreeCAD Project Association * * * * 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 * * . * * * ***************************************************************************/ #include #include #include #include #include #include #include #include #include #include "DlgSettingsWorkbenchesImp.h" #include "ui_DlgSettingsWorkbenches.h" using namespace Gui::Dialog; namespace Gui::Dialog { class wbListItem: public QWidget { Q_OBJECT public: explicit wbListItem( const QString& wbName, bool enabled, bool startupWb, bool autoLoad, int index, QWidget* parent = nullptr ); ~wbListItem() override; bool isEnabled(); bool isAutoLoading(); void setStartupWb(bool val); void setShortcutLabel(int index); protected Q_SLOTS: void onLoadClicked(); void onWbToggled(bool checked); Q_SIGNALS: void wbToggled(const QString& wbName, bool enabled); private: QCheckBox* enableCheckBox; QCheckBox* autoloadCheckBox; QLabel* iconLabel; QLabel* textLabel; QLabel* shortcutLabel; QLabel* loadLabel; QPushButton* loadButton; }; } // namespace Gui::Dialog wbListItem::wbListItem( const QString& wbName, bool enabled, bool startupWb, bool autoLoad, int index, QWidget* parent ) : QWidget(parent) { this->setObjectName(wbName); auto wbTooltip = Application::Instance->workbenchToolTip(wbName); auto wbDisplayName = Application::Instance->workbenchMenuText(wbName); // 1: Enable checkbox enableCheckBox = new QCheckBox(this); enableCheckBox->setToolTip( tr("Toggles the visibility of %1 in the available workbenches").arg(wbDisplayName) ); enableCheckBox->setChecked(enabled); if (startupWb) { enableCheckBox->setChecked(true); enableCheckBox->setEnabled(false); enableCheckBox->setToolTip(tr("This is the current startup module, and must be enabled")); } connect(enableCheckBox, &QCheckBox::toggled, this, [this](bool checked) { onWbToggled(checked); }); QWidget* subWidget = new QWidget(this); // 2: Workbench Icon auto wbIcon = Application::Instance->workbenchIcon(wbName); iconLabel = new QLabel(wbDisplayName, this); iconLabel->setPixmap(wbIcon.scaled( QSize(20, 20), Qt::AspectRatioMode::KeepAspectRatio, Qt::TransformationMode::SmoothTransformation )); iconLabel->setToolTip(wbTooltip); iconLabel->setContentsMargins(5, 0, 0, 5); // Left, top, right, bottom iconLabel->setEnabled(enableCheckBox->isChecked()); // 3: Workbench Display Name textLabel = new QLabel(wbDisplayName, this); textLabel->setToolTip(wbTooltip); QFont font = textLabel->font(); font.setBold(true); textLabel->setFont(font); textLabel->setEnabled(enableCheckBox->isChecked()); // 4: shortcut shortcutLabel = new QLabel(QStringLiteral("(W, %1)").arg(index + 1), this); shortcutLabel->setToolTip(tr("Shortcut to activate this workbench")); shortcutLabel->setEnabled(enableCheckBox->isChecked()); shortcutLabel->setVisible(index < 9); auto subLayout = new QHBoxLayout(subWidget); subLayout->addWidget(iconLabel); subLayout->addWidget(textLabel); subLayout->addWidget(shortcutLabel); subLayout->setAlignment(Qt::AlignLeft); subLayout->setContentsMargins(5, 0, 0, 5); subWidget->setMinimumSize(250, 0); subWidget->setAttribute(Qt::WA_TranslucentBackground); // 5: Autoloaded checkBox. autoloadCheckBox = new QCheckBox(this); autoloadCheckBox->setText(tr("Auto-load")); autoloadCheckBox->setToolTip(tr("Loads %1 automatically when FreeCAD starts").arg(wbDisplayName)); autoloadCheckBox->setEnabled(enableCheckBox->isChecked()); if (startupWb) { // Figure out whether to check and/or disable this checkBox: autoloadCheckBox->setChecked(true); autoloadCheckBox->setEnabled(false); autoloadCheckBox->setToolTip(tr("This is the current startup module, and must be autoloaded.")); } else if (autoLoad) { autoloadCheckBox->setChecked(true); } // 6: Load button/loaded indicator loadLabel = new QLabel(tr("Loaded"), this); loadLabel->setAlignment(Qt::AlignCenter); loadLabel->setEnabled(enableCheckBox->isChecked()); loadButton = new QPushButton(tr("Load"), this); loadButton->setToolTip( tr("To preserve resources, FreeCAD does not load workbenches until they are used. Loading " "them may provide access to additional preferences related to their functionality.") ); loadButton->setEnabled(enableCheckBox->isChecked()); connect(loadButton, &QPushButton::clicked, this, [this]() { onLoadClicked(); }); if (WorkbenchManager::instance()->getWorkbench(wbName.toStdString())) { loadButton->setVisible(false); } else { loadLabel->setVisible(false); } auto layout = new QHBoxLayout(this); layout->addWidget(enableCheckBox); layout->addWidget(subWidget); layout->addWidget(autoloadCheckBox); layout->addWidget(loadButton); layout->addWidget(loadLabel); layout->setAlignment(Qt::AlignLeft); layout->setContentsMargins(10, 0, 0, 0); } wbListItem::~wbListItem() = default; bool wbListItem::isEnabled() { return enableCheckBox->isChecked(); } bool wbListItem::isAutoLoading() { return autoloadCheckBox->isChecked(); } void wbListItem::setStartupWb(bool val) { if (val) { autoloadCheckBox->setChecked(true); } enableCheckBox->setEnabled(!val); autoloadCheckBox->setEnabled(!val && textLabel->isEnabled()); } void wbListItem::setShortcutLabel(int index) { shortcutLabel->setText(QStringLiteral("(W, %1)").arg(index + 1)); shortcutLabel->setVisible(index < 9); } void wbListItem::onLoadClicked() { // activate selected workbench Workbench* originalActiveWB = WorkbenchManager::instance()->active(); Application::Instance->activateWorkbench(objectName().toStdString().c_str()); Application::Instance->activateWorkbench(originalActiveWB->name().c_str()); // replace load button with loaded indicator loadButton->setVisible(false); loadLabel->setVisible(true); } void wbListItem::onWbToggled(bool checked) { // activate/deactivate the widgets iconLabel->setEnabled(checked); textLabel->setEnabled(checked); shortcutLabel->setEnabled(checked); loadLabel->setEnabled(checked); loadButton->setEnabled(checked); autoloadCheckBox->setEnabled(checked); // Reset the start combo items. Q_EMIT wbToggled(objectName(), checked); } /* TRANSLATOR Gui::Dialog::DlgSettingsWorkbenchesImp */ /** * Constructs a DlgSettingsWorkbenchesImp */ DlgSettingsWorkbenchesImp::DlgSettingsWorkbenchesImp(QWidget* parent) : PreferencePage(parent) , ui(new Ui_DlgSettingsWorkbenches) { ui->setupUi(this); ui->wbList->setDragDropMode(QAbstractItemView::InternalMove); ui->wbList->setSelectionMode(QAbstractItemView::SingleSelection); ui->wbList->viewport()->setAcceptDrops(true); ui->wbList->setDropIndicatorShown(true); ui->wbList->setDragEnabled(true); ui->wbList->setDefaultDropAction(Qt::MoveAction); QAction* sortAction = new QAction(tr("Sort Alphabetically"), this); connect(sortAction, &QAction::triggered, this, &DlgSettingsWorkbenchesImp::sortEnabledWorkbenches); QMenu* contextMenu = new QMenu(ui->wbList); contextMenu->addAction(sortAction); ui->wbList->setContextMenuPolicy(Qt::CustomContextMenu); connect( ui->wbList, &QListWidget::customContextMenuRequested, this, [this, contextMenu](const QPoint& pos) { contextMenu->exec(ui->wbList->mapToGlobal(pos)); } ); connect( ui->wbList->model(), &QAbstractItemModel::rowsMoved, this, &DlgSettingsWorkbenchesImp::wbItemMoved ); connect( ui->AutoloadModuleCombo, qOverload(&QComboBox::activated), this, &DlgSettingsWorkbenchesImp::onStartWbChanged ); connect(ui->CheckBox_WbByTab, &QCheckBox::toggled, this, &DlgSettingsWorkbenchesImp::onWbByTabToggled); } /** * Destroys the object and frees any allocated resources */ DlgSettingsWorkbenchesImp::~DlgSettingsWorkbenchesImp() = default; void DlgSettingsWorkbenchesImp::saveSettings() { std::ostringstream orderedStr, disabledStr, autoloadStr; auto addStrToOss = [](std::string wbName, std::ostringstream& oss) { if (oss.str().find(wbName) == std::string::npos) { if (!oss.str().empty()) { oss << ","; } oss << wbName; } }; for (int i = 0; i < ui->wbList->count(); i++) { wbListItem* wbItem = qobject_cast(ui->wbList->itemWidget(ui->wbList->item(i))); if (!wbItem) { continue; } std::string wbName = wbItem->objectName().toStdString(); if (wbItem->isEnabled()) { addStrToOss(wbName, orderedStr); } else { addStrToOss(wbName, disabledStr); } if (wbItem->isAutoLoading()) { addStrToOss(wbName, autoloadStr); } } if (orderedStr.str().empty()) { // make sure that we have at least one enabled workbench. This // should not be necessary because startup wb cannot be disabled. orderedStr << "NoneWorkbench"; } else { if (!disabledStr.str().empty()) { disabledStr << ","; } disabledStr << "NoneWorkbench"; } ParameterGrp::handle hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Workbenches" ); hGrp->SetASCII("Ordered", orderedStr.str().c_str()); hGrp->SetASCII("Disabled", disabledStr.str().c_str()); // Update the list of workbenches in the WorkbenchGroup and in the WorkbenchComboBox & workbench // QMenu Application::Instance->signalRefreshWorkbenches(); App::GetApplication() .GetParameterGroupByPath("User parameter:BaseApp/Preferences/General") ->SetASCII("BackgroundAutoloadModules", autoloadStr.str().c_str()); saveWorkbenchSelector(); int index = ui->AutoloadModuleCombo->currentIndex(); QVariant data = ui->AutoloadModuleCombo->itemData(index); QString startWbName = data.toString(); App::GetApplication() .GetParameterGroupByPath("User parameter:BaseApp/Preferences/General") ->SetASCII("AutoloadModule", startWbName.toLatin1()); ui->CheckBox_WbByTab->onSave(); } void DlgSettingsWorkbenchesImp::loadSettings() { loadWorkbenchSelector(); // There are two different "autoload" settings: the first, in FreeCAD since 2004, // controls the module the user sees first when starting FreeCAD, and defaults to the Start workbench std::string start = App::Application::Config()["StartWorkbench"]; _startupModule = App::GetApplication() .GetParameterGroupByPath("User parameter:BaseApp/Preferences/General") ->GetASCII("AutoloadModule", start.c_str()); // The second autoload setting does a background autoload of any number of other modules std::string autoloadCSV = App::GetApplication() .GetParameterGroupByPath("User parameter:BaseApp/Preferences/General") ->GetASCII("BackgroundAutoloadModules", ""); // Tokenize the comma-separated list _backgroundAutoloadedModules.clear(); std::stringstream stream(autoloadCSV); std::string workbench; while (std::getline(stream, workbench, ',')) { _backgroundAutoloadedModules.push_back(workbench); } buildWorkbenchList(); // We set the startup setting after building the list so that we can put only the enabled wb. setStartWorkbenchComboItems(); { QSignalBlocker sigblk(ui->CheckBox_WbByTab); ui->CheckBox_WbByTab->onRestore(); } } void DlgSettingsWorkbenchesImp::resetSettingsToDefaults() { ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Workbenches" ); hGrp->RemoveASCII("Ordered"); hGrp->RemoveASCII("Disabled"); hGrp->RemoveASCII("WorkbenchSelectorType"); hGrp->RemoveASCII("WorkbenchSelectorItem"); hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/General"); hGrp->RemoveASCII("BackgroundAutoloadModules"); hGrp->RemoveASCII("AutoloadModule"); hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/MainWindow" ); hGrp->RemoveASCII("WSPosition"); // finally reset all the parameters associated to Gui::Pref* widgets PreferencePage::resetSettingsToDefaults(); hGrp = App::GetApplication().GetParameterGroupByPath("User parameter:BaseApp/Preferences/View"); if (ui->CheckBox_WbByTab->isChecked() != hGrp->GetBool("SaveWBbyTab", 0)) { requireRestart(); } } /** Build the list of unloaded workbenches. */ void DlgSettingsWorkbenchesImp::buildWorkbenchList() { QSignalBlocker sigblk(ui->wbList); ui->wbList->clear(); QStringList enabledWbs = getEnabledWorkbenches(); QStringList disabledWbs = getDisabledWorkbenches(); // First we add the enabled wbs in their saved order. for (const auto& wbName : enabledWbs) { addWorkbench(wbName, true); } // Second we add workbenches that are disabled in alphabetical order. for (const auto& wbName : disabledWbs) { if (wbName.toStdString() != "NoneWorkbench") { addWorkbench(wbName, false); } } } void DlgSettingsWorkbenchesImp::addWorkbench(const QString& wbName, bool enabled) { const bool isStartupWb = wbName.toStdString() == _startupModule; const bool autoLoad = std::ranges::find(_backgroundAutoloadedModules, wbName.toStdString()) != _backgroundAutoloadedModules.end(); const auto widget = new wbListItem(wbName, enabled, isStartupWb, autoLoad, ui->wbList->count(), this); connect(widget, &wbListItem::wbToggled, this, &DlgSettingsWorkbenchesImp::wbToggled); const auto wItem = new QListWidgetItem(); wItem->setSizeHint(widget->sizeHint()); ui->wbList->addItem(wItem); ui->wbList->setItemWidget(wItem, widget); } QStringList DlgSettingsWorkbenchesImp::getEnabledWorkbenches() { QStringList disabled_wbs_list = getDisabledWorkbenches(); QStringList enabled_wbs_list; QStringList wbs_ordered_list; QString wbs_ordered; ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Workbenches" ); wbs_ordered = QString::fromStdString(hGrp->GetASCII("Ordered", "")); wbs_ordered_list = wbs_ordered.split(QLatin1String(","), Qt::SkipEmptyParts); QStringList workbenches = Application::Instance->workbenches(); workbenches.sort(); // First we add the wb that are ordered. for (auto& wbName : wbs_ordered_list) { if (workbenches.contains(wbName) && !disabled_wbs_list.contains(wbName)) { // Some wb may have been removed enabled_wbs_list.append(wbName); } else { Base::Console().log( "Ignoring unknown %s workbench found in user preferences.\n", wbName.toStdString().c_str() ); } } // Then we add the wbs that are not ordered and not disabled in alphabetical order for (auto& wbName : workbenches) { if (!enabled_wbs_list.contains(wbName) && !disabled_wbs_list.contains(wbName)) { enabled_wbs_list.append(wbName); } } return enabled_wbs_list; } QStringList DlgSettingsWorkbenchesImp::getDisabledWorkbenches() { QString disabled_wbs; QStringList unfiltered_disabled_wbs_list; QStringList disabled_wbs_list; ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Workbenches" ); disabled_wbs = QString::fromStdString(hGrp->GetASCII( "Disabled", "NoneWorkbench,TestWorkbench,InspectionWorkbench,RobotWorkbench,OpenSCADWorkbench" )); unfiltered_disabled_wbs_list = disabled_wbs.split(QLatin1String(","), Qt::SkipEmptyParts); QStringList workbenches = Application::Instance->workbenches(); for (auto& wbName : unfiltered_disabled_wbs_list) { if (workbenches.contains(wbName)) { // Some wb may have been removed disabled_wbs_list.append(wbName); } else { Base::Console().log( "Ignoring unknown %s workbench found in user preferences.\n", wbName.toStdString().c_str() ); } } disabled_wbs_list.sort(); return disabled_wbs_list; } /** * Sets the strings of the subwidgets using the current language. */ void DlgSettingsWorkbenchesImp::changeEvent(QEvent* e) { if (e->type() == QEvent::LanguageChange) { ui->retranslateUi(this); translateWorkbenchSelector(); } else { QWidget::changeEvent(e); } } void DlgSettingsWorkbenchesImp::saveWorkbenchSelector() { // save workbench selector type ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Workbenches" ); int prevIndex = hGrp->GetInt("WorkbenchSelectorType", 0); int index = ui->WorkbenchSelectorType->currentIndex(); if (prevIndex != index) { hGrp->SetInt("WorkbenchSelectorType", index); requireRestart(); } // save workbench selector items style prevIndex = hGrp->GetInt("WorkbenchSelectorItem", 0); index = ui->WorkbenchSelectorItem->currentIndex(); if (prevIndex != index) { hGrp->SetInt("WorkbenchSelectorItem", index); requireRestart(); } } void DlgSettingsWorkbenchesImp::loadWorkbenchSelector() { // workbench selector type setup ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Workbenches" ); int widgetTypeIndex = hGrp->GetInt("WorkbenchSelectorType", 0); ui->WorkbenchSelectorType->clear(); ui->WorkbenchSelectorType->addItem(tr("ComboBox")); ui->WorkbenchSelectorType->addItem(tr("TabBar")); ui->WorkbenchSelectorType->setCurrentIndex(widgetTypeIndex); // workbench selector items style int itemStyleIndex = hGrp->GetInt("WorkbenchSelectorItem", 0); ui->WorkbenchSelectorItem->clear(); ui->WorkbenchSelectorItem->addItem(tr("Icon and text")); ui->WorkbenchSelectorItem->addItem(tr("Icon")); ui->WorkbenchSelectorItem->addItem(tr("Text")); ui->WorkbenchSelectorItem->setCurrentIndex(itemStyleIndex); } void DlgSettingsWorkbenchesImp::translateWorkbenchSelector() { ui->WorkbenchSelectorType->setItemText(0, tr("ComboBox")); ui->WorkbenchSelectorType->setItemText(1, tr("TabBar")); ui->WorkbenchSelectorItem->setItemText(0, tr("Icon and text")); ui->WorkbenchSelectorItem->setItemText(1, tr("Icon")); ui->WorkbenchSelectorItem->setItemText(2, tr("Text")); } void DlgSettingsWorkbenchesImp::wbToggled(const QString& wbName, bool enabled) { setStartWorkbenchComboItems(); // reorder the list of items. int wbIndex = 0; for (int i = 0; i < ui->wbList->count(); i++) { wbListItem* wbItem = qobject_cast(ui->wbList->itemWidget(ui->wbList->item(i))); if (wbItem && wbItem->objectName() == wbName) { wbIndex = i; } } int destinationIndex = ui->wbList->count(); for (int i = 0; i < ui->wbList->count(); i++) { wbListItem* wbItem = qobject_cast(ui->wbList->itemWidget(ui->wbList->item(i))); if (wbItem && !wbItem->isEnabled() && (enabled || ((wbItem->objectName()).toStdString() > wbName.toStdString()))) { // If the wb was enabled, then it was in the disabled wbs. So it moves to the row of the // currently first disabled wb If the wb was disabled. Then it goes to the disabled wb // where it belongs alphabetically. destinationIndex = i; break; } } ui->wbList->model()->moveRow(QModelIndex(), wbIndex, QModelIndex(), destinationIndex); } void DlgSettingsWorkbenchesImp::setStartWorkbenchComboItems() { ui->AutoloadModuleCombo->clear(); // fills the combo box with activated workbenches. QStringList enabledWbs; for (int i = 0; i < ui->wbList->count(); i++) { wbListItem* wbItem = qobject_cast(ui->wbList->itemWidget(ui->wbList->item(i))); if (wbItem && wbItem->isEnabled()) { enabledWbs << wbItem->objectName(); } } QMap menuText; for (const auto& it : enabledWbs) { QString text = Application::Instance->workbenchMenuText(it); menuText[text] = it; } { // add special workbench to selection QPixmap px = Application::Instance->workbenchIcon(QStringLiteral("NoneWorkbench")); QString key = QStringLiteral(""); QString value = QStringLiteral("$LastModule"); if (px.isNull()) { ui->AutoloadModuleCombo->addItem(key, QVariant(value)); } else { ui->AutoloadModuleCombo->addItem(px, key, QVariant(value)); } } for (QMap::Iterator it = menuText.begin(); it != menuText.end(); ++it) { QPixmap px = Application::Instance->workbenchIcon(it.value()); if (px.isNull()) { ui->AutoloadModuleCombo->addItem(it.key(), QVariant(it.value())); } else { ui->AutoloadModuleCombo->addItem(px, it.key(), QVariant(it.value())); } } ui->AutoloadModuleCombo->setCurrentIndex( ui->AutoloadModuleCombo->findData(QString::fromStdString(_startupModule)) ); } void DlgSettingsWorkbenchesImp::wbItemMoved() { for (int i = 0; i < ui->wbList->count(); i++) { wbListItem* wbItem = qobject_cast(ui->wbList->itemWidget(ui->wbList->item(i))); if (wbItem) { wbItem->setShortcutLabel(i); } } } void DlgSettingsWorkbenchesImp::onStartWbChanged(int index) { // Update _startupModule QVariant data = ui->AutoloadModuleCombo->itemData(index); QString wbName = data.toString(); _startupModule = wbName.toStdString(); // Change wb that user can't deactivate. for (int i = 0; i < ui->wbList->count(); i++) { wbListItem* wbItem = qobject_cast(ui->wbList->itemWidget(ui->wbList->item(i))); if (wbItem) { wbItem->setStartupWb(wbItem->objectName() == wbName); } } } void DlgSettingsWorkbenchesImp::onWbByTabToggled(bool val) { Q_UNUSED(val); requireRestart(); } void DlgSettingsWorkbenchesImp::sortEnabledWorkbenches() { ParameterGrp::handle hGrp; hGrp = App::GetApplication().GetParameterGroupByPath( "User parameter:BaseApp/Preferences/Workbenches" ); hGrp->SetASCII("Ordered", ""); buildWorkbenchList(); } #include "moc_DlgSettingsWorkbenchesImp.cpp" #include "DlgSettingsWorkbenchesImp.moc"