From 858e88afe13d0ddf22e1990f412be4cf69ce85a8 Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Mon, 19 Apr 2021 12:19:50 -0500 Subject: [PATCH] [GUI] Add PreferencePack support Preference Packs are collections of preferences that can be applied en mass to the user's current setup. Any preference that can be stored in user.cfg can be stored in a preference pack, and they are designed to be easy to distribute. Support is also added for saving a subset of current preferences into a new preference pack in order to facilitate easy creation of new "themes", etc. --- src/Gui/Application.cpp | 12 + src/Gui/Application.h | 3 + src/Gui/CMakeLists.txt | 8 + src/Gui/DlgCreateNewPreferencePack.ui | 107 +++++ src/Gui/DlgCreateNewPreferencePackImp.cpp | 153 ++++++ src/Gui/DlgCreateNewPreferencePackImp.h | 78 +++ src/Gui/DlgGeneral.ui | 292 +++++++++-- src/Gui/DlgGeneralImp.cpp | 100 ++++ src/Gui/DlgGeneralImp.h | 9 + src/Gui/DlgPreferencesImp.cpp | 13 + src/Gui/DlgPreferencesImp.h | 4 +- src/Gui/PreferencePackManager.cpp | 454 ++++++++++++++++++ src/Gui/PreferencePackManager.h | 190 ++++++++ .../Appearance/Arch_Colors.cfg | 21 + .../Appearance/CMakeLists.txt | 26 + .../Appearance/Console_Colors.cfg | 15 + .../Appearance/Draft_Colors.cfg | 17 + .../Appearance/Editor_Colors.cfg | 26 + .../Appearance/Path_Colors.cfg | 21 + .../Appearance/Sketcher_Colors.cfg | 32 ++ .../Appearance/Start_Colors.cfg | 20 + .../Appearance/TechDraw_Colors.cfg | 31 ++ .../Appearance/Window_Colors.cfg | 31 ++ .../Behavior/CMakeLists.txt | 18 + .../Behavior/Main_window_layout.cfg | 26 + .../PreferencePackTemplates/CMakeLists.txt | 2 + src/Gui/PreferencePacks/CMakeLists.txt | 28 ++ .../FreeCAD Classic Colors.cfg | 134 ++++++ src/Gui/PreferencePacks/package.xml | 19 + 29 files changed, 1858 insertions(+), 32 deletions(-) create mode 100644 src/Gui/DlgCreateNewPreferencePack.ui create mode 100644 src/Gui/DlgCreateNewPreferencePackImp.cpp create mode 100644 src/Gui/DlgCreateNewPreferencePackImp.h create mode 100644 src/Gui/PreferencePackManager.cpp create mode 100644 src/Gui/PreferencePackManager.h create mode 100644 src/Gui/PreferencePackTemplates/Appearance/Arch_Colors.cfg create mode 100644 src/Gui/PreferencePackTemplates/Appearance/CMakeLists.txt create mode 100644 src/Gui/PreferencePackTemplates/Appearance/Console_Colors.cfg create mode 100644 src/Gui/PreferencePackTemplates/Appearance/Draft_Colors.cfg create mode 100644 src/Gui/PreferencePackTemplates/Appearance/Editor_Colors.cfg create mode 100644 src/Gui/PreferencePackTemplates/Appearance/Path_Colors.cfg create mode 100644 src/Gui/PreferencePackTemplates/Appearance/Sketcher_Colors.cfg create mode 100644 src/Gui/PreferencePackTemplates/Appearance/Start_Colors.cfg create mode 100644 src/Gui/PreferencePackTemplates/Appearance/TechDraw_Colors.cfg create mode 100644 src/Gui/PreferencePackTemplates/Appearance/Window_Colors.cfg create mode 100644 src/Gui/PreferencePackTemplates/Behavior/CMakeLists.txt create mode 100644 src/Gui/PreferencePackTemplates/Behavior/Main_window_layout.cfg create mode 100644 src/Gui/PreferencePackTemplates/CMakeLists.txt create mode 100644 src/Gui/PreferencePacks/CMakeLists.txt create mode 100644 src/Gui/PreferencePacks/FreeCAD Classic Colors/FreeCAD Classic Colors.cfg create mode 100644 src/Gui/PreferencePacks/package.xml diff --git a/src/Gui/Application.cpp b/src/Gui/Application.cpp index d59831672b..e159c30a36 100644 --- a/src/Gui/Application.cpp +++ b/src/Gui/Application.cpp @@ -44,6 +44,7 @@ #endif #include +#include #include #if defined(HAVE_QT5_OPENGL) #include @@ -72,6 +73,7 @@ #include "WidgetFactory.h" #include "Command.h" #include "Macro.h" +#include "PreferencePackManager.h" #include "ProgressBar.h" #include "Workbench.h" #include "WorkbenchManager.h" @@ -159,6 +161,9 @@ struct ApplicationP macroMngr = new MacroManager(); else macroMngr = nullptr; + + // Create the Theme Manager + prefPackManager = new PreferencePackManager(); } ~ApplicationP() @@ -172,6 +177,7 @@ struct ApplicationP Gui::Document* activeDocument; Gui::Document* editDocument; MacroManager* macroMngr; + PreferencePackManager* prefPackManager; /// List of all registered views std::list passive; bool isClosing; @@ -1616,6 +1622,12 @@ CommandManager &Application::commandManager(void) return d->commandManager; } +Gui::PreferencePackManager* Application::prefPackManager(void) +{ + return d->prefPackManager; +} + + //************************************************************************** // Init, Destruct and singleton diff --git a/src/Gui/Application.h b/src/Gui/Application.h index 0f596d3276..05b976bfdc 100644 --- a/src/Gui/Application.h +++ b/src/Gui/Application.h @@ -43,6 +43,7 @@ class MacroManager; class MDIView; class MainWindow; class MenuItem; +class PreferencePackManager; class ViewProvider; class ViewProviderDocumentObject; @@ -216,6 +217,8 @@ public: void createStandardOperations(); //@} + Gui::PreferencePackManager* prefPackManager(void); + /** @name Init, Destruct an Access methods */ //@{ /// some kind of singelton diff --git a/src/Gui/CMakeLists.txt b/src/Gui/CMakeLists.txt index e682cc825b..ac49841c8a 100644 --- a/src/Gui/CMakeLists.txt +++ b/src/Gui/CMakeLists.txt @@ -1,5 +1,7 @@ #add_subdirectory(Icons) add_subdirectory(Stylesheets) +add_subdirectory(PreferencePacks) +add_subdirectory(PreferencePackTemplates) if(WIN32) add_definitions(-DFCGui -DQIIS_MAKEDLL -DQSINT_MAKEDLL -DOVR_OS_WIN32 -DQUARTER_INTERNAL -DQUARTER_MAKE_DLL -DCOIN_DLL) @@ -306,6 +308,7 @@ SET(Gui_UIC_SRCS DlgAuthorization.ui DlgChooseIcon.ui DlgCommands.ui + DlgCreateNewPreferencePack.ui DlgCustomizeSpNavSettings.ui DlgDisplayProperties.ui DlgEditor.ui @@ -403,6 +406,7 @@ SET(Dialog_CPP_SRCS Clipping.cpp DemoMode.cpp DlgActivateWindowImp.cpp + DlgCreateNewPreferencePackImp.cpp DlgUnitsCalculatorImp.cpp DlgDisplayPropertiesImp.cpp DlgInputDialogImp.cpp @@ -440,6 +444,7 @@ SET(Dialog_HPP_SRCS Clipping.h DemoMode.h DlgActivateWindowImp.h + DlgCreateNewPreferencePackImp.h DlgUnitsCalculatorImp.h DlgDisplayPropertiesImp.h DlgInputDialogImp.h @@ -499,6 +504,7 @@ SET(Dialog_SRCS DlgCheckableMessageBox.ui DlgTreeWidget.ui DlgExpressionInput.ui + DlgCreateNewPreferencePack.ui DownloadManager.ui DownloadItem.ui DocumentRecovery.ui @@ -1139,6 +1145,7 @@ SET(FreeCADGui_CPP_SRCS resource.cpp Control.cpp SpaceballEvent.cpp + PreferencePackManager.cpp Thumbnail.cpp Utilities.cpp WaitCursor.cpp @@ -1171,6 +1178,7 @@ SET(FreeCADGui_SRCS Qt4All.h Control.h SpaceballEvent.h + PreferencePackManager.h Thumbnail.h Utilities.h WaitCursor.h diff --git a/src/Gui/DlgCreateNewPreferencePack.ui b/src/Gui/DlgCreateNewPreferencePack.ui new file mode 100644 index 0000000000..5165aeffe1 --- /dev/null +++ b/src/Gui/DlgCreateNewPreferencePack.ui @@ -0,0 +1,107 @@ + + + Gui::Dialog::DlgCreateNewPreferencePack + + + Qt::ApplicationModal + + + + 0 + 0 + 580 + 520 + + + + Create New Preference Pack + + + true + + + + + + + + Name + + + + + + + + + + + + 50 + + + 250 + + + true + + + + Property group templates + + + + + Template Type + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + Gui::Dialog::DlgCreateNewPreferencePack + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Gui::Dialog::DlgCreateNewPreferencePack + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/Gui/DlgCreateNewPreferencePackImp.cpp b/src/Gui/DlgCreateNewPreferencePackImp.cpp new file mode 100644 index 0000000000..498199f7b6 --- /dev/null +++ b/src/Gui/DlgCreateNewPreferencePackImp.cpp @@ -0,0 +1,153 @@ +/*************************************************************************** + * Copyright (c) 2021 Chris Hennes * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" +#ifndef _PreComp_ +#endif + +#include "DlgCreateNewPreferencePackImp.h" +#include "ui_DlgCreateNewPreferencePack.h" + +#include + +using namespace Gui::Dialog; + +const auto TemplateRole = Qt::UserRole; + +/* TRANSLATOR Gui::Dialog::DlgCreateNewPreferencePackImp */ + +/** + * Constructs a Gui::Dialog::DlgCreateNewPreferencePackImp as a child of 'parent' + */ +DlgCreateNewPreferencePackImp::DlgCreateNewPreferencePackImp(QWidget* parent) + : QDialog(parent) + , ui(new Ui_DlgCreateNewPreferencePack) +{ + ui->setupUi(this); + + QRegExp validNames(QString::fromUtf8("[^/\\\\?%*:|\"<>]+")); + _nameValidator.setRegExp(validNames); + ui->lineEdit->setValidator(&_nameValidator); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &DlgCreateNewPreferencePackImp::onItemChanged); +} + + +DlgCreateNewPreferencePackImp::~DlgCreateNewPreferencePackImp() +{ +} + +void DlgCreateNewPreferencePackImp::setPreferencePackTemplates(const std::vector& availableTemplates) +{ + ui->treeWidget->clear(); + _groups.clear(); + + ui->treeWidget->header()->setDefaultSectionSize(250); + + _templates = availableTemplates; + for (const auto &t : _templates) { + + QTreeWidgetItem* group; + if (auto foundGroup = _groups.find(t.group); foundGroup != _groups.end()) { + group = foundGroup->second; + } + else { + group = new QTreeWidgetItem(ui->treeWidget, QStringList(QString::fromStdString(t.group))); + group->setCheckState(0, Qt::Checked); + group->setExpanded(true); + _groups.insert(std::make_pair(t.group, group)); + } + + QStringList itemColumns; + itemColumns.push_back(QString::fromStdString(t.name)); + switch (t.type) { + case Gui::PreferencePack::Type::Appearance: itemColumns.push_back(tr("Appearance")); break; + case Gui::PreferencePack::Type::Behavior: itemColumns.push_back(tr("Behavior")); break; + case Gui::PreferencePack::Type::Combination: itemColumns.push_back(tr("Combination")); break; + } + auto newItem = new QTreeWidgetItem(group, itemColumns); + newItem->setCheckState(0, Qt::Checked); + if (group->checkState(0) != newItem->checkState(0)) + group->setCheckState(0, Qt::PartiallyChecked); + newItem->setData(0, TemplateRole, QVariant::fromValue(t)); + group->addChild(newItem); + } +} + +std::vector DlgCreateNewPreferencePackImp::selectedTemplates() const +{ + std::vector results; + + for (const auto& group : _groups) + for (int childIndex = 0; childIndex < group.second->childCount(); ++childIndex) + if (auto child = group.second->child(childIndex); child->checkState(0) == Qt::Checked) + if (child->data(0, TemplateRole).canConvert()) + results.push_back(child->data(0, TemplateRole).value()); + + return results; +} + +std::string DlgCreateNewPreferencePackImp::preferencePackName() const +{ + return ui->lineEdit->text().toStdString(); +} + +void DlgCreateNewPreferencePackImp::onItemChanged(QTreeWidgetItem* item, int column) +{ + Q_UNUSED(column); + const QSignalBlocker blocker(ui->treeWidget); + if (auto group = item->parent(); group) { + // Child clicked + bool firstItemChecked = false; + for (int childIndex = 0; childIndex < group->childCount(); ++childIndex) { + auto child = group->child(childIndex); + if (childIndex == 0) { + firstItemChecked = child->checkState(0) == Qt::Checked; + } + else { + bool thisItemChecked = child->checkState(0) == Qt::Checked; + if (firstItemChecked != thisItemChecked) { + group->setCheckState(0, Qt::PartiallyChecked); + return; + } + } + } + group->setCheckState(0, firstItemChecked ? Qt::Checked : Qt::Unchecked); + } + else { + // Group clicked: + auto groupCheckState = item->checkState(0); + for (int childIndex = 0; childIndex < item->childCount(); ++childIndex) { + auto child = item->child(childIndex); + child->setCheckState(0, groupCheckState); + } + } +} + +void DlgCreateNewPreferencePackImp::on_lineEdit_textEdited(const QString& text) +{ + ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(text.isEmpty()); +} + + +#include "moc_DlgCreateNewPreferencePackImp.cpp" diff --git a/src/Gui/DlgCreateNewPreferencePackImp.h b/src/Gui/DlgCreateNewPreferencePackImp.h new file mode 100644 index 0000000000..6e4f75058b --- /dev/null +++ b/src/Gui/DlgCreateNewPreferencePackImp.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (c) 2021 Chris Hennes * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#ifndef GUI_DIALOG_DLGCREATENEWTHEMEIMP_H +#define GUI_DIALOG_DLGCREATENEWTHEMEIMP_H + +#include +#include +#include + +#include "PreferencePackManager.h" + +class QTreeWidgetItem; + +namespace Gui { + +namespace Dialog { + +class Ui_DlgCreateNewPreferencePack; + +/** + * \class DlgCreateNewPreferencePackImp + * + * A dialog to request a preferencePack name and a set of preferencePack templates. + * + * \author Chris Hennes + */ +class GuiExport DlgCreateNewPreferencePackImp : public QDialog +{ + Q_OBJECT + +public: + + DlgCreateNewPreferencePackImp(QWidget* parent = nullptr); + ~DlgCreateNewPreferencePackImp(); + + void setPreferencePackTemplates(const std::vector &availableTemplates); + + std::vector selectedTemplates() const; + std::string preferencePackName() const; + +protected Q_SLOTS: + + void onItemChanged(QTreeWidgetItem* item, int column); + + void on_lineEdit_textEdited(const QString &text); + +private: + std::unique_ptr ui; + std::map _groups; + std::vector _templates; + QRegExpValidator _nameValidator; +}; + +} // namespace Dialog +} // namespace Gui + +#endif // GUI_DIALOG_DLGCREATENEWTHEMEIMP_H diff --git a/src/Gui/DlgGeneral.ui b/src/Gui/DlgGeneral.ui index 0b83dac463..411e8cb717 100644 --- a/src/Gui/DlgGeneral.ui +++ b/src/Gui/DlgGeneral.ui @@ -14,7 +14,16 @@ General - + + 9 + + + 9 + + + 9 + + 9 @@ -32,7 +41,16 @@ Language - + + 11 + + + 11 + + + 11 + + 11 @@ -43,7 +61,16 @@ 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -65,6 +92,146 @@ + + + + Preference Packs + + + + + + + + QAbstractItemView::NoEditTriggers + + + false + + + false + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + false + + + true + + + false + + + false + + + true + + + true + + + 75 + + + 200 + + + true + + + false + + + 16 + + + 24 + + + + Name + + + + 75 + true + + + + + + Type + + + + 75 + true + + + + + + + + + + + + 0 + 0 + + + + Run + + + + + + + Save new... + + + + + + + + 0 + 0 + + + + Manage... + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + @@ -74,7 +241,16 @@ 6 - + + 11 + + + 11 + + + 11 + + 11 @@ -82,7 +258,16 @@ 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -131,7 +316,16 @@ 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -152,7 +346,16 @@ See the FreeCAD Wiki for details about the image. 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -176,7 +379,16 @@ See the FreeCAD Wiki for details about the image. 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -201,7 +413,16 @@ this according to your screen size or personal taste 6 - + + 0 + + + 0 + + + 0 + + 0 @@ -214,7 +435,7 @@ this according to your screen size or personal taste - Customize how tree view is shown in the panel (restart required). + Customize how tree view is shown in the panel (restart required). 'ComboView': combine tree view and property view into one panel. 'TreeView and PropertyView': split tree view and property view into separate panel. @@ -233,7 +454,16 @@ this according to your screen size or personal taste Start up - + + 11 + + + 11 + + + 11 + + 11 @@ -262,7 +492,16 @@ display the splash screen - + + 0 + + + 0 + + + 0 + + 0 @@ -294,7 +533,16 @@ after FreeCAD launches Python console - + + 11 + + + 11 + + + 11 + + 11 @@ -323,19 +571,6 @@ horizontal space in Python console - - - - Qt::Vertical - - - - 352 - 221 - - - - @@ -353,11 +588,6 @@ horizontal space in Python console QSpinBox
Gui/PrefWidgets.h
- - Gui::PrefComboBox - QComboBox -
Gui/PrefWidgets.h
-
Languages diff --git a/src/Gui/DlgGeneralImp.cpp b/src/Gui/DlgGeneralImp.cpp index 39075aefa9..b4bd12ebea 100644 --- a/src/Gui/DlgGeneralImp.cpp +++ b/src/Gui/DlgGeneralImp.cpp @@ -33,11 +33,16 @@ #include "ui_DlgGeneral.h" #include "Action.h" #include "Application.h" +#include "Command.h" #include "DockWindowManager.h" #include "MainWindow.h" #include "PrefWidgets.h" #include "PythonConsole.h" #include "Language/Translator.h" +#include "Gui/PreferencePackManager.h" +#include "DlgPreferencesImp.h" + +#include "DlgCreateNewPreferencePackImp.h" using namespace Gui::Dialog; @@ -82,6 +87,15 @@ DlgGeneralImp::DlgGeneralImp( QWidget* parent ) else ui->AutoloadModuleCombo->addItem(px, it.key(), QVariant(it.value())); } + + recreatePreferencePackMenu(); + connect(ui->PreferencePacks, &QTableWidget::itemSelectionChanged, this, &DlgGeneralImp::preferencePackSelectionChanged); + connect(ui->ApplyPreferencePack, &QPushButton::clicked, this, &DlgGeneralImp::applyPreferencePackClicked); + connect(ui->SaveNewPreferencePack, &QPushButton::clicked, this, &DlgGeneralImp::saveAsNewPreferencePack); + + // Future work: the Add-On Manager will be modified to include a section for Preference Packs, at which point this + // button will be enabled to open the Add-On Manager to that tab. + ui->ManagePreferencePacks->hide(); } /** @@ -299,4 +313,90 @@ void DlgGeneralImp::changeEvent(QEvent *e) } } +void DlgGeneralImp::recreatePreferencePackMenu() +{ + // Populate the Preference Packs list + auto appearancePacks = Application::Instance->prefPackManager()->preferencePackNames(PreferencePack::Type::Appearance); + auto behaviorPacks = Application::Instance->prefPackManager()->preferencePackNames(PreferencePack::Type::Behavior); + auto combinationPacks = Application::Instance->prefPackManager()->preferencePackNames(PreferencePack::Type::Combination); + + ui->PreferencePacks->setRowCount(appearancePacks.size() + behaviorPacks.size() + combinationPacks.size()); + + int row = 0; + for (const auto& pack : appearancePacks) { + auto name = new QTableWidgetItem(QString::fromStdString(pack)); + ui->PreferencePacks->setItem(row, 0, name); + auto kind = new QTableWidgetItem(tr("Appearance")); + ui->PreferencePacks->setItem(row, 1, kind); + ++row; + } + for (const auto& pack : behaviorPacks) { + auto name = new QTableWidgetItem(QString::fromStdString(pack)); + ui->PreferencePacks->setItem(row, 0, name); + auto kind = new QTableWidgetItem(tr("Behavior")); + ui->PreferencePacks->setItem(row, 1, kind); + ++row; + } + for (const auto& pack : combinationPacks) { + auto name = new QTableWidgetItem(QString::fromStdString(pack)); + ui->PreferencePacks->setItem(row, 0, name); + auto kind = new QTableWidgetItem(tr("Combination")); + ui->PreferencePacks->setItem(row, 1, kind); + ++row; + } + ui->PreferencePacks->setRangeSelected(QTableWidgetSelectionRange(), true); + ui->ApplyPreferencePack->setEnabled(false); +} + +void DlgGeneralImp::preferencePackSelectionChanged() +{ + if (ui->PreferencePacks->selectedItems().isEmpty()) + ui->ApplyPreferencePack->setEnabled(false); + else + ui->ApplyPreferencePack->setEnabled(true); +} + +void DlgGeneralImp::saveAsNewPreferencePack() +{ + // Create and run a modal New PreferencePack dialog box + newPreferencePackDialog = std::make_unique(this); + newPreferencePackDialog->setPreferencePackTemplates(Application::Instance->prefPackManager()->templateFiles()); + connect(newPreferencePackDialog.get(), &DlgCreateNewPreferencePackImp::accepted, this, &DlgGeneralImp::newPreferencePackDialogAccepted); + newPreferencePackDialog->open(); +} + +void DlgGeneralImp::newPreferencePackDialogAccepted() +{ + auto preferencePackTemplates = Application::Instance->prefPackManager()->templateFiles(); + auto selection = newPreferencePackDialog->selectedTemplates(); + std::vector selectedTemplates; + std::copy_if(preferencePackTemplates.begin(), preferencePackTemplates.end(), std::back_inserter(selectedTemplates), [selection](PreferencePackManager::TemplateFile& t) { + for (const auto& item : selection) + if (item.group == t.group && item.name == t.name) + return true; + return false; + }); + auto preferencePackName = newPreferencePackDialog->preferencePackName(); + Application::Instance->prefPackManager()->save(preferencePackName, selectedTemplates); + Application::Instance->prefPackManager()->rescan(); + recreatePreferencePackMenu(); +} + +void DlgGeneralImp::applyPreferencePackClicked() +{ + auto selectedPreferencePacks = ui->PreferencePacks->selectedItems(); + + for (const auto pack : selectedPreferencePacks) { + if (pack->column() == 0) { + auto packName = pack->text().toStdString(); + if (Application::Instance->prefPackManager()->apply(packName)) { + auto parentDialog = qobject_cast (this->window()); + if (parentDialog) + parentDialog->reload(); + } + } + } +} + + #include "moc_DlgGeneralImp.cpp" diff --git a/src/Gui/DlgGeneralImp.h b/src/Gui/DlgGeneralImp.h index eac13fa8b8..6c928efd3e 100644 --- a/src/Gui/DlgGeneralImp.h +++ b/src/Gui/DlgGeneralImp.h @@ -32,6 +32,7 @@ class QTabWidget; namespace Gui { namespace Dialog { class Ui_DlgGeneral; +class DlgCreateNewPreferencePackImp; /** This class implements the settings for the application. * You can change window style, size of pixmaps, size of recent file list and so on @@ -51,11 +52,19 @@ public: protected: void changeEvent(QEvent *e); +protected Q_SLOTS: + void preferencePackSelectionChanged(); + void applyPreferencePackClicked(); + void recreatePreferencePackMenu(); + void newPreferencePackDialogAccepted(); + private: void setRecentFileSize(); + void saveAsNewPreferencePack(); private: std::unique_ptr ui; + std::unique_ptr newPreferencePackDialog; }; } // namespace Dialog diff --git a/src/Gui/DlgPreferencesImp.cpp b/src/Gui/DlgPreferencesImp.cpp index d547121d94..92a1f1f559 100644 --- a/src/Gui/DlgPreferencesImp.cpp +++ b/src/Gui/DlgPreferencesImp.cpp @@ -466,4 +466,17 @@ void DlgPreferencesImp::changeEvent(QEvent *e) } } +void DlgPreferencesImp::reload() +{ + for (int i = 0; i < ui->tabWidgetStack->count(); i++) { + QTabWidget* tabWidget = (QTabWidget*)ui->tabWidgetStack->widget(i); + for (int j = 0; j < tabWidget->count(); j++) { + PreferencePage* page = qobject_cast(tabWidget->widget(j)); + if (page) + page->loadSettings(); + } + } + applyChanges(); +} + #include "moc_DlgPreferencesImp.cpp" diff --git a/src/Gui/DlgPreferencesImp.h b/src/Gui/DlgPreferencesImp.h index 172baba36d..5ac6fcb190 100644 --- a/src/Gui/DlgPreferencesImp.h +++ b/src/Gui/DlgPreferencesImp.h @@ -113,11 +113,13 @@ class GuiExport DlgPreferencesImp : public QDialog public: static void addPage(const std::string& className, const std::string& group); static void removePage(const std::string& className, const std::string& group); + static void reloadSettings(); DlgPreferencesImp(QWidget* parent = 0, Qt::WindowFlags fl = Qt::WindowFlags()); ~DlgPreferencesImp(); void accept(); + void reload(); void activateGroupPage(const QString& group, int id); protected: @@ -135,11 +137,11 @@ private: /** @name for internal use only */ //@{ void setupPages(); + void reloadPages(); QTabWidget* createTabForGroup(const std::string& groupName); void createPageInGroup(QTabWidget* tabWidget, const std::string& pageName); void applyChanges(); void restoreDefaults(); - void reloadPages(); //@} private: diff --git a/src/Gui/PreferencePackManager.cpp b/src/Gui/PreferencePackManager.cpp new file mode 100644 index 0000000000..3f71314702 --- /dev/null +++ b/src/Gui/PreferencePackManager.cpp @@ -0,0 +1,454 @@ +/*************************************************************************** + * Copyright (c) 2021 Chris Hennes * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + + +#include "PreCompiled.h" + +#ifndef _PreComp_ +# include +# include +# include +#endif + +#include +#include + +#include "PreferencePackManager.h" +#include "App/Metadata.h" +#include "Base/Parameter.h" +#include "Base/Interpreter.h" +#include "Base/Console.h" + +#include + +#include // For generating a timestamped filename + + +using namespace Gui; +using namespace xercesc; +namespace fs = boost::filesystem; + +PreferencePack::PreferencePack(const fs::path& path, const App::Metadata& metadata) : + _path(path), _metadata(metadata) +{ + if (!fs::exists(_path)) { + throw std::runtime_error{ "Cannot access " + path.string() }; + } + + auto qssPaths = QDir::searchPaths(QString::fromUtf8("qss")); + auto cssPaths = QDir::searchPaths(QString::fromUtf8("css")); + + qssPaths.append(QString::fromStdString(_path.string())); + cssPaths.append(QString::fromStdString(_path.string())); + + QDir::setSearchPaths(QString::fromUtf8("qss"), qssPaths); + QDir::setSearchPaths(QString::fromUtf8("css"), cssPaths); +} + +std::string PreferencePack::name() const +{ + return _metadata.name(); +} + +bool PreferencePack::apply() const +{ + // Run the pre.FCMacro, if it exists: if it raises an exception, abort the process + auto preMacroPath = _path / "pre.FCMacro"; + if (fs::exists(preMacroPath)) { + try { + Base::Interpreter().runFile(preMacroPath.string().c_str(), false); + } + catch (...) { + Base::Console().Message("PreferencePack application aborted by the preferencePack's pre.FCMacro"); + return false; + } + } + + // Back up the old config file + auto savedPreferencePacksDirectory = fs::path(App::Application::getUserAppDataDir()) / "SavedPreferencePacks"; + auto backupFile = savedPreferencePacksDirectory / "user.cfg.backup"; + try { + fs::remove(backupFile); + } + catch (...) {} + App::GetApplication().GetUserParameter().SaveDocument(backupFile.string().c_str()); + + // Apply the config settings + applyConfigChanges(); + + // Run the Post.FCMacro, if it exists + auto postMacroPath = _path / "post.FCMacro"; + if (fs::exists(postMacroPath)) { + try { + Base::Interpreter().runFile(postMacroPath.string().c_str(), false); + } + catch (...) { + Base::Console().Message("PreferencePack application reverted by the preferencePack's post.FCMacro"); + App::GetApplication().GetUserParameter().LoadDocument(backupFile.string().c_str()); + return false; + } + } + + return true; +} + +PreferencePack::Type PreferencePack::type() const +{ + auto typeList = _metadata["type"]; + if (typeList.empty()) + return Type::Combination; + + auto typeString = typeList.front().contents; + if (typeString == "appearance") + return Type::Appearance; + else if (typeString == "behavior" || typeString == "behaviour") + return Type::Behavior; + else + return Type::Combination; +} + +void PreferencePack::applyConfigChanges() const +{ + auto configFile = _path / (_metadata.name() + ".cfg"); + if (fs::exists(configFile)) { + ParameterManager newParameters; + newParameters.LoadDocument(configFile.string().c_str()); + auto baseAppGroup = App::GetApplication().GetUserParameter().GetGroup("BaseApp"); + newParameters.GetGroup("BaseApp")->copyTo(baseAppGroup); + } +} + + + +PreferencePackManager::PreferencePackManager() +{ + auto modPath = fs::path(App::Application::getUserAppDataDir()) / "Mod"; + auto savedPath = fs::path(App::Application::getUserAppDataDir()) / "SavedPreferencePacks"; + auto resourcePath = fs::path(App::Application::getResourceDir()) / "Gui" / "PreferencePacks"; + _preferencePackPaths.push_back(resourcePath); + _preferencePackPaths.push_back(modPath); + _preferencePackPaths.push_back(savedPath); + rescan(); + + // Housekeeping: + DeleteOldBackups(); +} + +void PreferencePackManager::rescan() +{ + std::lock_guard lock(_mutex); + for (const auto& path : _preferencePackPaths) { + if (fs::exists(path) && fs::is_directory(path)) { + FindPreferencePacksInPackage(path); + for (const auto& mod : fs::directory_iterator(path)) { + if (fs::is_directory(mod)) { + FindPreferencePacksInPackage(mod); + } + } + } + } +} + +void Gui::PreferencePackManager::FindPreferencePacksInPackage(const fs::path& mod) +{ + auto packageMetadataFile = mod / "package.xml"; + if (fs::exists(packageMetadataFile) && fs::is_regular_file(packageMetadataFile)) { + try { + App::Metadata metadata(packageMetadataFile); + auto content = metadata.content(); + for (const auto& item : content) { + if (item.first == "preferencepack") { + PreferencePack newPreferencePack(mod / item.second.name(), item.second); + _preferencePacks.insert(std::make_pair(newPreferencePack.name(), newPreferencePack)); + } + } + } + catch (...) { + // Failed to read the metadata, or to create the preferencePack based on it... + Base::Console().Error(("Failed to read " + packageMetadataFile.string()).c_str()); + } + } +} + +std::vector PreferencePackManager::preferencePackNames(PreferencePack::Type type) const +{ + std::lock_guard lock(_mutex); + std::vector names; + for (const auto& preferencePack : _preferencePacks) + if (preferencePack.second.type() == type) + names.push_back(preferencePack.first); + return names; +} + +bool PreferencePackManager::apply(const std::string& preferencePackName) const +{ + std::lock_guard lock(_mutex); + if (auto preferencePack = _preferencePacks.find(preferencePackName); preferencePack != _preferencePacks.end()) { + BackupCurrentConfig(); + return preferencePack->second.apply(); + } + else { + throw std::runtime_error("No such Preference Pack: " + preferencePackName); + } +} + +void copyTemplateParameters(Base::Reference templateGroup, const std::string& path, Base::Reference outputGroup) +{ + auto userParameterHandle = App::GetApplication().GetParameterGroupByPath(path.c_str()); + + auto boolMap = templateGroup->GetBoolMap(); + for (const auto& kv : boolMap) { + auto currentValue = userParameterHandle->GetBool(kv.first.c_str(), kv.second); + outputGroup->SetBool(kv.first.c_str(), currentValue); + } + + auto intMap = templateGroup->GetIntMap(); + for (const auto& kv : intMap) { + auto currentValue = userParameterHandle->GetInt(kv.first.c_str(), kv.second); + outputGroup->SetInt(kv.first.c_str(), currentValue); + } + + auto uintMap = templateGroup->GetUnsignedMap(); + for (const auto& kv : uintMap) { + auto currentValue = userParameterHandle->GetUnsigned(kv.first.c_str(), kv.second); + outputGroup->SetUnsigned(kv.first.c_str(), currentValue); + } + + auto floatMap = templateGroup->GetFloatMap(); + for (const auto& kv : floatMap) { + auto currentValue = userParameterHandle->GetFloat(kv.first.c_str(), kv.second); + outputGroup->SetFloat(kv.first.c_str(), currentValue); + } + + auto asciiMap = templateGroup->GetASCIIMap(); + for (const auto& kv : asciiMap) { + auto currentValue = userParameterHandle->GetASCII(kv.first.c_str(), kv.second.c_str()); + outputGroup->SetASCII(kv.first.c_str(), currentValue.c_str()); + } + + // Recurse... + auto templateSubgroups = templateGroup->GetGroups(); + for (auto& templateSubgroup : templateSubgroups) { + std::string sgName = templateSubgroup->GetGroupName(); + auto outputSubgroupHandle = outputGroup->GetGroup(sgName.c_str()); + copyTemplateParameters(templateSubgroup, path + "/" + sgName, outputSubgroupHandle); + } +} + +void copyTemplateParameters(/*const*/ ParameterManager& templateParameterManager, ParameterManager& outputParameterManager) +{ + auto groups = templateParameterManager.GetGroups(); + for (auto& group : groups) { + std::string name = group->GetGroupName(); + auto groupHandle = outputParameterManager.GetGroup(name.c_str()); + copyTemplateParameters(group, "User parameter:" + name, groupHandle); + } +} + +void PreferencePackManager::save(const std::string& name, const std::vector& templates) +{ + if (templates.empty()) + return; + + std::lock_guard lock(_mutex); + auto savedPreferencePacksDirectory = fs::path(App::Application::getUserAppDataDir()) / "SavedPreferencePacks"; + fs::path preferencePackDirectory(savedPreferencePacksDirectory / name); + if (fs::exists(preferencePackDirectory) && !fs::is_directory(preferencePackDirectory)) + throw std::runtime_error("Cannot create " + savedPreferencePacksDirectory.string() + ": file with that name exists already"); + + if (!fs::exists(preferencePackDirectory)) + fs::create_directories(preferencePackDirectory); + + // Create or update the saved user preferencePacks package.xml metadata file + std::unique_ptr metadata; + if (fs::exists(savedPreferencePacksDirectory / "package.xml")) { + metadata = std::make_unique(savedPreferencePacksDirectory / "package.xml"); + } + else { + // Create and set all of the required metadata to make it easier for PreferencePack authors to copy this + // file into their preferencePack distributions. + metadata = std::make_unique(); + metadata->setName("User-Saved PreferencePacks"); + metadata->setDescription("Generated automatically -- edits may be lost when saving new preferencePacks"); + metadata->setVersion(1); + metadata->addMaintainer(App::Meta::Contact("No Maintainer", "email@freecadweb.org")); + metadata->addLicense(App::Meta::License("(Unspecified)", "(Unspecified)")); + metadata->addUrl(App::Meta::Url("https://github.com/FreeCAD/FreeCAD", App::Meta::UrlType::repository)); + } + App::Metadata newPreferencePackMetadata; + newPreferencePackMetadata.setName(name); + newPreferencePackMetadata.setVersion(1); + + auto templateType = templates.front().type; + for (const auto& t : templates) { + if (t.type != templateType) { + templateType = PreferencePack::Type::Combination; + break; + } + } + std::string typeString; + switch (templateType) { + case PreferencePack::Type::Appearance: typeString = "appearance"; break; + case PreferencePack::Type::Behavior: typeString = "behavior"; break; + case PreferencePack::Type::Combination: typeString = "combination"; break; + } + newPreferencePackMetadata.addGenericMetadata("type", App::Meta::GenericMetadata(typeString)); + + metadata->addContentItem("preferencepack", newPreferencePackMetadata); + metadata->write(savedPreferencePacksDirectory / "package.xml"); + + // Create the config file + ParameterManager outputParameterManager; + outputParameterManager.CreateDocument(); + for (const auto& t : templates) { + ParameterManager templateParameterManager; + templateParameterManager.LoadDocument(t.path.string().c_str()); + copyTemplateParameters(templateParameterManager, outputParameterManager); + } + auto cfgFilename = savedPreferencePacksDirectory / name / (name + ".cfg"); + outputParameterManager.SaveDocument(cfgFilename.string().c_str()); +} + +// Needed until we support only C++20 and above and can use std::string's built-in ends_with() +bool fc_ends_with(std::string_view str, std::string_view suffix) +{ + return str.size() >= suffix.size() && str.compare(str.size() - suffix.size(), suffix.size(), suffix) == 0; +} + +std::vector scanForTemplateFolders(const std::string& groupName, const fs::path& entry) +{ + // From this location, find the folder(s) called "PreferencePackTemplates" + std::vector templateFolders; + if (fs::exists(entry)) { + if (fs::is_directory(entry)) { + if (entry.filename() == "PreferencePackTemplates" || + entry.filename() == "preference_pack_templates") { + templateFolders.push_back(entry); + } + else { + std::string subgroupName = groupName + "/" + entry.filename().string(); + for (const auto& subentry : fs::directory_iterator(entry)) { + auto contents = scanForTemplateFolders(subgroupName, subentry); + std::copy(contents.begin(), contents.end(), std::back_inserter(templateFolders)); + } + } + } + } + return templateFolders; +} + +std::vector scanForTemplateFiles(const std::string& groupName, const fs::path& entry) +{ + auto templateFolders = scanForTemplateFolders(groupName, entry); + + std::vector templateFiles; + for (const auto& dir : templateFolders) { + auto templateDirs = std::vector>({ + std::make_pair(dir / "Appearance", PreferencePack::Type::Appearance), + std::make_pair(dir / "appearance", PreferencePack::Type::Appearance), + std::make_pair(dir / "Behavior", PreferencePack::Type::Behavior), + std::make_pair(dir / "behavior", PreferencePack::Type::Behavior), + std::make_pair(dir / "Behaviour", PreferencePack::Type::Behavior), + std::make_pair(dir / "behaviour", PreferencePack::Type::Behavior) }); + for (const auto& templateDir : templateDirs) { + if (!fs::exists(templateDir.first) || !fs::is_directory(templateDir.first)) + continue; + for (const auto& entry : fs::directory_iterator(templateDir.first)) { + if (entry.path().extension() == ".cfg") { + auto name = entry.path().filename().stem().string(); + std::replace(name.begin(), name.end(), '_', ' '); + // Make sure we don't insert the same thing twice... + if (std::find_if(templateFiles.begin(), templateFiles.end(), [groupName, name](const auto &rhs)->bool { + return groupName == rhs.group && name == rhs.name; + } ) != templateFiles.end()) + continue; + templateFiles.push_back({ groupName, name, entry, templateDir.second }); + } + } + } + } + return templateFiles; +} + +std::vector PreferencePackManager::templateFiles(bool rescan) +{ + std::lock_guard lock(_mutex); + if (!_templateFiles.empty() && !rescan) + return _templateFiles; + + // Locate all of the template files available on this system + // Template files end in ".cfg" -- They are located in: + // * $INSTALL_DIR/data/Gui/PreferencePackTemplates/(Appearance|Behavior)/* + // * $DATA_DIR/Mod/**/PreferencePackTemplates/(Appearance|Behavior)/* + // (alternate spellings are provided for packages using CamelCase and snake_case, and both major English dialects) + + auto resourcePath = fs::path(App::Application::getResourceDir()) / "Gui"; + auto modPath = fs::path(App::Application::getUserAppDataDir()) / "Mod"; + + std::string group = "Built-In"; + if (fs::exists(resourcePath) && fs::is_directory(resourcePath)) { + const auto localFiles = scanForTemplateFiles(group, resourcePath); + std::copy(localFiles.begin(), localFiles.end(), std::back_inserter(_templateFiles)); + } + + if (fs::exists(modPath) && fs::is_directory(modPath)) { + for (const auto& mod : fs::directory_iterator(modPath)) { + group = mod.path().filename().string(); + const auto localFiles = scanForTemplateFiles(group, mod); + std::copy(localFiles.begin(), localFiles.end(), std::back_inserter(_templateFiles)); + } + } + + return _templateFiles; +} + +void Gui::PreferencePackManager::BackupCurrentConfig() const +{ + auto backupDirectory = fs::path(App::Application::getUserAppDataDir()) / "SavedPreferencePacks" / "Backups"; + fs::create_directories(backupDirectory); + + // Create a timestamped filename: + auto time = std::time(nullptr); + std::ostringstream timestampStream; + timestampStream << "user." << time << ".cfg"; + auto filename = backupDirectory / timestampStream.str(); + + // Save the current config: + App::GetApplication().GetUserParameter().SaveDocument(filename.string().c_str()); +} + +void Gui::PreferencePackManager::DeleteOldBackups() const +{ + constexpr auto oneWeek = 60.0 * 60.0 * 24.0 * 7.0; + const auto now = std::time(nullptr); + auto backupDirectory = fs::path(App::Application::getUserAppDataDir()) / "SavedPreferencePacks" / "Backups"; + if (fs::exists(backupDirectory) && fs::is_directory(backupDirectory)) { + for (const auto& backup : fs::directory_iterator(backupDirectory)) { + if (std::difftime(now, fs::last_write_time(backup)) > oneWeek) { + try { + fs::remove(backup); + } + catch (...) {} + } + } + } +} \ No newline at end of file diff --git a/src/Gui/PreferencePackManager.h b/src/Gui/PreferencePackManager.h new file mode 100644 index 0000000000..33bc43bdf6 --- /dev/null +++ b/src/Gui/PreferencePackManager.h @@ -0,0 +1,190 @@ +/*************************************************************************** + * Copyright (c) 2021 Chris Hennes * + * * + * This file is part of the FreeCAD CAx development system. * + * * + * This library is free software; you can redistribute it and/or * + * modify it under the terms of the GNU Library General Public * + * License as published by the Free Software Foundation; either * + * version 2 of the License, or (at your option) any later version. * + * * + * This library 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 Library General Public License for more details. * + * * + * You should have received a copy of the GNU Library General Public * + * License along with this library; see the file COPYING.LIB. If not, * + * write to the Free Software Foundation, Inc., 59 Temple Place, * + * Suite 330, Boston, MA 02111-1307, USA * + * * + ***************************************************************************/ + +#ifndef BASE_THEMEMANAGER_H +#define BASE_THEMEMANAGER_H + +#include +#include +#include + +#include "App/Metadata.h" + +namespace Gui { + + /** + * \class PreferencePack A collection of user preferences stored in files on disk + */ + class PreferencePack { + + public: + + /** + * Construct a preferencePack from a directory + * + * \param path A path to a mod directory that contains a preferencePack + * \param metadata The metadata from the package.xml file describing this preferencePack + */ + PreferencePack(const boost::filesystem::path& path, const App::Metadata& metadata); + + ~PreferencePack() = default; + + /** + * Get the name of the PreferencePack + */ + std::string name() const; + + /** + * Apply the PreferencePack over the top of the current preferences set + * \returns True if the preferencePack was applied, or false if not + */ + bool apply() const; + + enum class Type { + Appearance, + Behavior, + Combination + }; + + /** + * Get the type of PreferencePack (appearance, behavior, or a combination of the two) + */ + Type type() const; + + private: + + void applyConfigChanges() const; + + boost::filesystem::path _path; + App::Metadata _metadata; + + }; + + + + + /** + * \class PreferencePackManager handles storable and loadable collections of user preferences + * + * This class provides some additional utility functions for allowing users to save their current + * preferences as a PreferencePack based on a set of template files provided either in the main + * FreeCAD distribution, or inside various installed mods. + */ + class PreferencePackManager { + public: + PreferencePackManager(); + ~PreferencePackManager() = default; + + /** + * Rescan the preferencePack directory and update the available PreferencePacks + */ + void rescan(); + + /** + * Get an alphabetical list of names of all installed PreferencePacks of a given type + */ + std::vector preferencePackNames(PreferencePack::Type type) const; + + /** + * Apply the named preferencePack + * \return True if the preferencePack was applied, or false if it was not + */ + bool apply(const std::string& preferencePackName) const; + + /** + * \struct TemplateFile A file containing a set of preferences that can be saved into + * a PreferencePack + * + * PreferencePacks can contain any parameters at all, but inside FreeCAD there is no + * centralized list of all of these parameters, and at any given time the user.cfg file + * usually does not store a value for all possible parameters. Instead, it simply allows + * calling code to use whatever default values that code sets. This is all completely + * hidden from the users: FreeCAD behaves as though those values exist in the config file. + * + * When a user saves their current configuration as a pack, they likely expect that saved + * configuration to include those default values, so that if they later apply their saved + * configuration those defaults are restored. To enable this a set of template files + * listing default values for various types of parameters can be used. Each file is + * presented to the user as a checkable box when saving a new preferences pack, and the + * intention is that these files will list out the various user-facing parameters that + * someone might want to save into a preferences pack. + * + * These files are themselves valid user.cfg files, that if loaded and applied will result + * in the default values of their contained variables being set. For this to work, these + * files should be kept up-to-date with the default values set in the code. They are not + * required to contain an exhaustive listing of all possible parameters: in most cases it + * is enough that they list the variables that a user would expect for a given name. For + * example, "Sketcher Colors.cfg" should list out all of the default colors used in + * Sketcher that a user can set in Preferences, but it is not necessary that it contain any + * color that is only used internally, and it should not include things like fonts, or + * behavior information. + * + * Template files are always located in a directory hierarchy that differentiates between + * templates that only affect appearance, and those that affect behavior. + * + * The base FreeCAD installation includes default templates in: + * $INSTALL_DIR/data/Gui/PreferencePackTemplates/(Appearance|Behavior)/ + * + * External add-ons are also searched for any directory called PreferencePackTemplates or + * preference_pack_templates -- either of which is expected to contain appearance and/or + * behavior subdirectories. In this way external add-ons can allow a user to easily save + * their preferences to a PreferencePack, or even to add additional templates representing + * sets of core FreeCAD preferences. + */ + struct TemplateFile { + std::string group; // Generally the Add-On/Mod/Package name + std::string name; + boost::filesystem::path path; + PreferencePack::Type type; + }; + + /** + * Save current settings as a (possibly new) preferencePack + * + * If the named preferencePack does not exist, this creates it on disk. If it does exist, this overwrites the original. + */ + void save(const std::string& name, const std::vector& templates); + + + std::vector templateFiles(bool rescan = false); + + private: + + void FindPreferencePacksInPackage(const boost::filesystem::path& mod); + + void BackupCurrentConfig() const; + + void DeleteOldBackups() const; + + std::vector _preferencePackPaths; + std::vector _templateFiles; + std::map _preferencePacks; + mutable std::mutex _mutex; + + }; + +} + +Q_DECLARE_METATYPE(Gui::PreferencePackManager::TemplateFile) // So it can be used with QVariant + + +#endif diff --git a/src/Gui/PreferencePackTemplates/Appearance/Arch_Colors.cfg b/src/Gui/PreferencePackTemplates/Appearance/Arch_Colors.cfg new file mode 100644 index 0000000000..9b7d90987c --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/Arch_Colors.cfg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/Appearance/CMakeLists.txt b/src/Gui/PreferencePackTemplates/Appearance/CMakeLists.txt new file mode 100644 index 0000000000..6cb3d4c0dc --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/CMakeLists.txt @@ -0,0 +1,26 @@ + +SET(PreferencePackTemplates_Files + Arch_Colors.cfg + Console_Colors.cfg + Draft_Colors.cfg + Editor_Colors.cfg + Path_Colors.cfg + Sketcher_Colors.cfg + Start_Colors.cfg + TechDraw_Colors.cfg + Window_Colors.cfg +) + +ADD_CUSTOM_TARGET(PreferencePackTemplates_data ALL + SOURCES ${PreferencePackTemplates_Files} +) + +fc_copy_sources(PreferencePackTemplates_data "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/Gui/PreferencePackTemplates/Appearance" + ${PreferencePackTemplates_Files}) + +INSTALL( + FILES + ${PreferencePackTemplates_Files} + DESTINATION + ${CMAKE_INSTALL_DATADIR}/Gui/PreferencePackTemplates/Appearance +) \ No newline at end of file diff --git a/src/Gui/PreferencePackTemplates/Appearance/Console_Colors.cfg b/src/Gui/PreferencePackTemplates/Appearance/Console_Colors.cfg new file mode 100644 index 0000000000..7e4f4c2d20 --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/Console_Colors.cfg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/Appearance/Draft_Colors.cfg b/src/Gui/PreferencePackTemplates/Appearance/Draft_Colors.cfg new file mode 100644 index 0000000000..7f68a8e53c --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/Draft_Colors.cfg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/Appearance/Editor_Colors.cfg b/src/Gui/PreferencePackTemplates/Appearance/Editor_Colors.cfg new file mode 100644 index 0000000000..93ecd1cff1 --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/Editor_Colors.cfg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/Appearance/Path_Colors.cfg b/src/Gui/PreferencePackTemplates/Appearance/Path_Colors.cfg new file mode 100644 index 0000000000..0780cf0255 --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/Path_Colors.cfg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/Appearance/Sketcher_Colors.cfg b/src/Gui/PreferencePackTemplates/Appearance/Sketcher_Colors.cfg new file mode 100644 index 0000000000..44b4d9f535 --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/Sketcher_Colors.cfg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/Appearance/Start_Colors.cfg b/src/Gui/PreferencePackTemplates/Appearance/Start_Colors.cfg new file mode 100644 index 0000000000..2465eae672 --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/Start_Colors.cfg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/Appearance/TechDraw_Colors.cfg b/src/Gui/PreferencePackTemplates/Appearance/TechDraw_Colors.cfg new file mode 100644 index 0000000000..7a1525ad91 --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/TechDraw_Colors.cfg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/Appearance/Window_Colors.cfg b/src/Gui/PreferencePackTemplates/Appearance/Window_Colors.cfg new file mode 100644 index 0000000000..3821bc48a2 --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Appearance/Window_Colors.cfg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/Behavior/CMakeLists.txt b/src/Gui/PreferencePackTemplates/Behavior/CMakeLists.txt new file mode 100644 index 0000000000..7a6174cc9e --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Behavior/CMakeLists.txt @@ -0,0 +1,18 @@ + +SET(PreferencePackBehaviorTemplates_Files + Main_window_layout.cfg +) + +ADD_CUSTOM_TARGET(PreferencePackBehaviorTemplates_data ALL + SOURCES ${PreferencePackBehaviorTemplates_Files} +) + +fc_copy_sources(PreferencePackBehaviorTemplates_data "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/Gui/PreferencePackTemplates/Behavior" + ${PreferencePackBehaviorTemplates_Files}) + +INSTALL( + FILES + ${PreferencePackBehaviorTemplates_Files} + DESTINATION + ${CMAKE_INSTALL_DATADIR}/Gui/PreferencePackTemplates/Behavior +) \ No newline at end of file diff --git a/src/Gui/PreferencePackTemplates/Behavior/Main_window_layout.cfg b/src/Gui/PreferencePackTemplates/Behavior/Main_window_layout.cfg new file mode 100644 index 0000000000..1e9c8d023c --- /dev/null +++ b/src/Gui/PreferencePackTemplates/Behavior/Main_window_layout.cfg @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePackTemplates/CMakeLists.txt b/src/Gui/PreferencePackTemplates/CMakeLists.txt new file mode 100644 index 0000000000..b5c71577cf --- /dev/null +++ b/src/Gui/PreferencePackTemplates/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(Appearance) +add_subdirectory(Behavior) \ No newline at end of file diff --git a/src/Gui/PreferencePacks/CMakeLists.txt b/src/Gui/PreferencePacks/CMakeLists.txt new file mode 100644 index 0000000000..e7c235b9e0 --- /dev/null +++ b/src/Gui/PreferencePacks/CMakeLists.txt @@ -0,0 +1,28 @@ + +SET(PreferencePacks_Files +"package.xml" +) + +SET(PreferencePacks_Directories +"FreeCAD Classic Colors" +) + +ADD_CUSTOM_TARGET(PreferencePacks_data ALL +SOURCES ${PreferencePacks_Files} ${PreferencePacks_Directories} +) + +FILE(COPY ${PreferencePacks_Files} ${PreferencePacks_Directories} DESTINATION "${CMAKE_BINARY_DIR}/${CMAKE_INSTALL_DATADIR}/Gui/PreferencePacks") + +INSTALL( +FILES + ${PreferencePacks_Files} +DESTINATION + ${CMAKE_INSTALL_DATADIR}/Gui/PreferencePacks +) + +INSTALL( +DIRECTORY + ${PreferencePacks_Directories} +DESTINATION + ${CMAKE_INSTALL_DATADIR}/Gui/PreferencePacks +) \ No newline at end of file diff --git a/src/Gui/PreferencePacks/FreeCAD Classic Colors/FreeCAD Classic Colors.cfg b/src/Gui/PreferencePacks/FreeCAD Classic Colors/FreeCAD Classic Colors.cfg new file mode 100644 index 0000000000..c18797a155 --- /dev/null +++ b/src/Gui/PreferencePacks/FreeCAD Classic Colors/FreeCAD Classic Colors.cfg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Gui/PreferencePacks/package.xml b/src/Gui/PreferencePacks/package.xml new file mode 100644 index 0000000000..4791afca43 --- /dev/null +++ b/src/Gui/PreferencePacks/package.xml @@ -0,0 +1,19 @@ + + + Built-In Preference Packs + Preference Packs included with the FreeCAD distribution + 1.0.0 + No Maintainer + LGPL2 + https://github.com/FreeCAD/FreeCAD + + + + FreeCAD Classic Colors + FreeCAD default colors for core app and included Mods. + 1.0.0 + appearance + + + +