Merge pull request #1365 from ispyisail/feature/customise-dialog

Add a Customise window for toolbars, shortcuts and gestures
This commit is contained in:
ispyisail
2026-10-07 20:57:15 +13:00
committed by GitHub
16 changed files with 1576 additions and 39 deletions
+6
View File
@@ -301,6 +301,8 @@ set(QET_SRC_FILES
${QET_DIR}/sources/shortcutmanager.h
${QET_DIR}/sources/shortcutbarsettings.cpp
${QET_DIR}/sources/shortcutbarsettings.h
${QET_DIR}/sources/diagramtoolbarsettings.cpp
${QET_DIR}/sources/diagramtoolbarsettings.h
${QET_DIR}/sources/toolbarsettings.cpp
${QET_DIR}/sources/toolbarsettings.h
${QET_DIR}/sources/diagramcontexttoolbar.cpp
@@ -860,6 +862,10 @@ set(QET_SRC_FILES
${QET_DIR}/sources/ui/configpage/shortcutsconfigpage.h
${QET_DIR}/sources/ui/configpage/shortcutbarconfigpage.cpp
${QET_DIR}/sources/ui/configpage/shortcutbarconfigpage.h
${QET_DIR}/sources/ui/customizedialog.cpp
${QET_DIR}/sources/ui/customizedialog.h
${QET_DIR}/sources/ui/configpage/toolbarcommandsconfigpage.cpp
${QET_DIR}/sources/ui/configpage/toolbarcommandsconfigpage.h
${QET_DIR}/sources/ui/configpage/toolbarsconfigpage.cpp
${QET_DIR}/sources/ui/configpage/toolbarsconfigpage.h
${QET_DIR}/sources/ui/configpage/gesturesconfigpage.cpp
+352
View File
@@ -0,0 +1,352 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "diagramtoolbarsettings.h"
#include "shortcutmanager.h"
#include <QApplication>
#include <QCoreApplication>
#include <QSettings>
#include <QToolBar>
namespace {
const QString CONTENTS = QStringLiteral("diagrameditor/toolbars/");
const QString CUSTOM_NAMES = QStringLiteral("diagrameditor/custom_toolbars/names");
const QString CUSTOM_TITLES = QStringLiteral("diagrameditor/custom_toolbars/titles");
//Set on a window by markWindow()
const char *const MARKED = "qetDiagramToolbars";
}
/**
@return every toolbar, built-in first, then the user's own in the order
they were added
*/
QList<DiagramToolbarSettings::Toolbar> DiagramToolbarSettings::toolbars()
{
QList<Toolbar> list;
for (const QString &name : builtInNames()) {
list << Toolbar{name, builtInTitle(name), false};
}
return list << customToolbars();
}
/**
@return the object names of the toolbars the diagram editor always has.
The scripts toolbar is not one: it is filled from the scripts folder.
*/
QStringList DiagramToolbarSettings::builtInNames()
{
return {QStringLiteral("toolbar"),
QStringLiteral("display"),
QStringLiteral("diagram"),
QStringLiteral("adding"),
QStringLiteral("diagram_depth_toolbar")};
}
QString DiagramToolbarSettings::builtInTitle(const QString &name)
{
if (name == QLatin1String("toolbar"))
return QCoreApplication::translate("QETDiagramEditor", "Outils");
if (name == QLatin1String("display"))
return QCoreApplication::translate("QETDiagramEditor", "Affichage");
if (name == QLatin1String("diagram"))
return QCoreApplication::translate("QETDiagramEditor", "Schéma");
if (name == QLatin1String("adding"))
return QCoreApplication::translate("QETDiagramEditor", "Ajouter");
if (name == QLatin1String("diagram_depth_toolbar"))
return QCoreApplication::translate("QETDiagramEditor", "Profondeur", "toolbar title");
return QString();
}
/**
@return the toolbars the user added, in the order they were added
*/
QList<DiagramToolbarSettings::Toolbar> DiagramToolbarSettings::customToolbars()
{
QSettings settings;
const QStringList names = settings.value(CUSTOM_NAMES).toStringList();
const QStringList titles = settings.value(CUSTOM_TITLES).toStringList();
QList<Toolbar> list;
for (int i = 0 ; i < names.size() ; ++i) {
list << Toolbar{names.at(i), titles.value(i, names.at(i)), true};
}
return list;
}
/**
@brief DiagramToolbarSettings::setCustomToolbars
Store the user's own toolbars. The contents of one that is no longer
in @a toolbars are removed with it.
*/
void DiagramToolbarSettings::setCustomToolbars(const QList<Toolbar> &toolbars)
{
QStringList names, titles;
for (const Toolbar &toolbar : toolbars) {
names << toolbar.name;
titles << toolbar.title;
}
for (const Toolbar &old : customToolbars()) {
if (!names.contains(old.name)) {
removeIds(old.name);
}
}
QSettings settings;
if (names.isEmpty()) {
settings.remove(CUSTOM_NAMES);
settings.remove(CUSTOM_TITLES);
} else {
settings.setValue(CUSTOM_NAMES, names);
settings.setValue(CUSTOM_TITLES, titles);
}
}
/**
@return an object name for a new toolbar that none of @a existing has
and none had before (stored positions stay with the toolbar they
belonged to)
*/
QString DiagramToolbarSettings::newCustomName(const QList<Toolbar> &existing)
{
QStringList taken;
for (const Toolbar &toolbar : existing) taken << toolbar.name;
for (const Toolbar &toolbar : customToolbars()) taken << toolbar.name;
for (int i = 1 ; ; ++i) {
const QString name = QStringLiteral("custom_%1").arg(i);
if (!taken.contains(name)) return name;
}
}
/**
@return the ids toolbar @a name holds: the user's list if they saved
one, the defaults otherwise. A saved empty list stays empty.
*/
QStringList DiagramToolbarSettings::ids(const QString &name)
{
QSettings settings;
const QString key = CONTENTS + name;
if (!settings.contains(key)) {
return defaultIds(name);
}
return settings.value(key).toStringList();
}
/**
@return what toolbar @a name holds for a new user: the toolbars as
they were built before they could be changed. A user's own toolbar
starts empty.
*/
QStringList DiagramToolbarSettings::defaultIds(const QString &name)
{
const QString sep = separatorId();
if (name == QLatin1String("toolbar")) {
return {QStringLiteral("diagrameditor.new_file"),
QStringLiteral("diagrameditor.open_file"),
QStringLiteral("diagrameditor.save_file"),
QStringLiteral("diagrameditor.save_file_as"),
QStringLiteral("diagrameditor.close_file"),
QStringLiteral("diagrameditor.print"),
QStringLiteral("diagrameditor.export_to_pdf"),
sep,
QStringLiteral("diagrameditor.undo"),
QStringLiteral("diagrameditor.redo"),
sep,
QStringLiteral("diagrameditor.cut"),
QStringLiteral("diagrameditor.copy"),
QStringLiteral("diagrameditor.paste"),
QStringLiteral("diagrameditor.duplicate"),
sep,
QStringLiteral("diagrameditor.delete_selection"),
QStringLiteral("diagrameditor.rotate_selection")};
}
if (name == QLatin1String("display")) {
return {QStringLiteral("diagrameditor.mode_selection"),
QStringLiteral("diagrameditor.mode_visualise"),
sep,
QStringLiteral("widget:handler_size"),
sep,
QStringLiteral("diagrameditor.draw_grid"),
QStringLiteral("widget:text_grid"),
QStringLiteral("diagrameditor.draw_guides"),
QStringLiteral("widget:background_color"),
sep,
QStringLiteral("diagrameditor.zoom_content"),
QStringLiteral("diagrameditor.zoom_fit"),
QStringLiteral("diagrameditor.zoom_reset")};
}
if (name == QLatin1String("diagram")) {
return {QStringLiteral("diagrameditor.edit_diagram_properties"),
QStringLiteral("diagrameditor.conductor_reset"),
QStringLiteral("diagrameditor.auto_conductor"),
QStringLiteral("diagrameditor.auto_break_conductor"),
QStringLiteral("widget:conductor_color")};
}
if (name == QLatin1String("adding")) {
//add_pdf is skipped when built without QtPdf, as before
return {QStringLiteral("diagrameditor.add_text"),
QStringLiteral("diagrameditor.add_image"),
QStringLiteral("diagrameditor.add_pdf"),
QStringLiteral("diagrameditor.add_line"),
QStringLiteral("diagrameditor.add_rectangle"),
QStringLiteral("diagrameditor.add_ellipse"),
QStringLiteral("diagrameditor.add_arc"),
QStringLiteral("diagrameditor.add_polyline"),
QStringLiteral("diagrameditor.add_path"),
QStringLiteral("diagrameditor.add_fillet"),
QStringLiteral("diagrameditor.add_terminal_strip")};
}
if (name == QLatin1String("diagram_depth_toolbar")) {
return {QStringLiteral("depth.forward"),
QStringLiteral("depth.raise"),
QStringLiteral("depth.lower"),
QStringLiteral("depth.backward")};
}
return {};
}
/**
@brief DiagramToolbarSettings::setIds
Save @a ids for toolbar @a name. Saving the defaults removes the key, so
a later change of defaults still reaches this user.
*/
void DiagramToolbarSettings::setIds(const QString &name, const QStringList &ids)
{
if (ids == defaultIds(name)) {
removeIds(name);
} else {
QSettings().setValue(CONTENTS + name, ids);
}
}
void DiagramToolbarSettings::removeIds(const QString &name)
{
QSettings().remove(CONTENTS + name);
}
QString DiagramToolbarSettings::separatorId()
{
return QStringLiteral("separator");
}
/**
@return the toolbar buttons that are widgets, not commands. Each can be
on one toolbar at a time.
*/
QStringList DiagramToolbarSettings::widgetIds()
{
return {QStringLiteral("widget:handler_size"),
QStringLiteral("widget:text_grid"),
QStringLiteral("widget:background_color"),
QStringLiteral("widget:conductor_color")};
}
bool DiagramToolbarSettings::isWidget(const QString &id)
{
return id.startsWith(QLatin1String("widget:"));
}
QString DiagramToolbarSettings::widgetTitle(const QString &id)
{
if (id == QLatin1String("widget:handler_size"))
return QCoreApplication::translate("DiagramToolbarSettings", "Taille des poignées");
if (id == QLatin1String("widget:text_grid"))
return QCoreApplication::translate("DiagramToolbarSettings", "Grille des textes");
if (id == QLatin1String("widget:background_color"))
return QCoreApplication::translate("DiagramToolbarSettings", "Couleur de fond du folio");
if (id == QLatin1String("widget:conductor_color"))
return QCoreApplication::translate("DiagramToolbarSettings", "Couleur des conducteurs");
return id;
}
/**
@return every command that can go on a toolbar: the diagram editor's,
its stored scripts', and the depth commands it shares with the element
editor
*/
QStringList DiagramToolbarSettings::availableCommandIds()
{
QStringList ids;
for (const ShortcutManager::ShortcutInfo &info :
ShortcutManager::instance().allShortcuts())
{
if (info.id.startsWith(QLatin1String("diagrameditor."))
|| info.id.startsWith(QLatin1String("depth."))) {
ids << info.id;
}
}
return ids;
}
/**
@brief DiagramToolbarSettings::fill
Empty @a toolbar and put the commands of @a ids on it, in order.
@a resolve gives the window's action for an id, a widget id included;
an id it has no action for is skipped. A separator never starts or
ends the toolbar and never follows another: those are left out, as
Qt would otherwise draw them.
*/
void DiagramToolbarSettings::fill(QToolBar *toolbar, const QStringList &ids,
const std::function<QAction *(const QString &)> &resolve)
{
//Separators belong to the toolbar that made them; commands do not
const QList<QAction *> old_actions = toolbar->actions();
toolbar->clear();
for (QAction *action : old_actions) {
if (action->isSeparator() && action->parent() == toolbar) delete action;
}
bool pending_separator = false;
for (const QString &id : ids)
{
if (id == separatorId()) {
pending_separator = !toolbar->actions().isEmpty();
continue;
}
QAction *action = resolve(id);
if (!action) {
continue;
}
if (pending_separator) {
toolbar->addSeparator();
pending_separator = false;
}
toolbar->addAction(action);
}
}
/**
@brief DiagramToolbarSettings::markWindow
Mark @a window as one whose toolbars applyToAll() rebuilds. It must
have a rebuildToolBars() slot or invokable.
*/
void DiagramToolbarSettings::markWindow(QObject *window)
{
window->setProperty(MARKED, true);
}
/**
@brief DiagramToolbarSettings::applyToAll
Rebuild the toolbars of every open window marked by markWindow(),
after the settings change.
*/
void DiagramToolbarSettings::applyToAll()
{
for (QWidget *widget : QApplication::topLevelWidgets()) {
if (widget->property(MARKED).toBool()) {
QMetaObject::invokeMethod(widget, "rebuildToolBars");
}
}
}
+79
View File
@@ -0,0 +1,79 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef DIAGRAMTOOLBARSETTINGS_H
#define DIAGRAMTOOLBARSETTINGS_H
#include <QList>
#include <QString>
#include <QStringList>
#include <functional>
class QAction;
class QToolBar;
/**
@brief What each of the diagram editor's toolbars holds.
A toolbar is a list of ids, in order: ShortcutManager command ids, the
separator id, and the ids of the few toolbar buttons that are widgets
rather than commands (the colour buttons, the text grid, the handle
size). Stored in QSettings, one key per toolbar, by the toolbar's
object name; a toolbar the user never changed uses the defaults below,
which are the toolbars as they were before they could be changed.
The user can also add toolbars of their own. They start empty.
An id no command carries any more is skipped, so a list saved by
another version still opens.
*/
class DiagramToolbarSettings
{
public:
struct Toolbar {
QString name; ///< object name, used for storage and by QMainWindow::saveState()
QString title; ///< shown to the user
bool custom = false;
};
static QList<Toolbar> toolbars();
static QStringList builtInNames();
static QString builtInTitle(const QString &name);
static QList<Toolbar> customToolbars();
static void setCustomToolbars(const QList<Toolbar> &toolbars);
static QString newCustomName(const QList<Toolbar> &existing);
static QStringList ids(const QString &name);
static QStringList defaultIds(const QString &name);
static void setIds(const QString &name, const QStringList &ids);
static void removeIds(const QString &name);
static QString separatorId();
static QStringList widgetIds();
static bool isWidget(const QString &id);
static QString widgetTitle(const QString &id);
static QStringList availableCommandIds();
static void fill(QToolBar *toolbar, const QStringList &ids,
const std::function<QAction *(const QString &)> &resolve);
static void markWindow(QObject *window);
static void applyToAll();
};
#endif // DIAGRAMTOOLBARSETTINGS_H
+34
View File
@@ -44,6 +44,9 @@
#include "ui/configpage/gesturesconfigpage.h"
#include "ui/configpage/shortcutbarconfigpage.h"
#include "ui/configpage/toolbarsconfigpage.h"
#include "ui/configpage/toolbarcommandsconfigpage.h"
#include "ui/customizedialog.h"
#include <QTabWidget>
#include "machine_info.h"
#include "TerminalStrip/ui/terminalstripeditorwindow.h"
#include "qetversion.h"
@@ -2229,6 +2232,7 @@ void QETApp::configureQET()
cd.addPage(new ShortcutBarConfigPage());
cd.addPage(new GesturesConfigPage());
cd.addPage(new ToolbarsConfigPage());
cd.addPage(new ToolbarCommandsConfigPage());
#ifdef QET_SPACEMOUSE_SUPPORT
cd.addPage(new SpaceMouseConfigPage());
#endif
@@ -2256,6 +2260,36 @@ void QETApp::configureQET()
#endif
}
/**
@brief QETApp::customizeQET
Open the Customise window: the toolbar, shortcut bar, keyboard and
gesture pages of the configuration dialog, as tabs of one window.
@param tab : the tab to show first
*/
void QETApp::customizeQET(int tab)
{
QWidget *parent_widget = qApp->activeWindow();
CustomizeDialog dialog;
//Same reason as the configuration dialog (#527)
dialog.setWindowModality(Qt::ApplicationModal);
dialog.addPage(new ToolbarsConfigPage());
dialog.addPage(new ToolbarCommandsConfigPage());
dialog.addPage(new ShortcutBarConfigPage());
dialog.addPage(new ShortcutsConfigPage());
dialog.addPage(new GesturesConfigPage());
if (auto *tabs = dialog.findChild<QTabWidget *>(QStringLiteral("customizeTabs"))) {
tabs->setCurrentIndex(tab);
}
QET::trackDialogGeometry(&dialog);
if (parent_widget) {
dialog.setParent(parent_widget, dialog.windowFlags());
}
dialog.exec();
dialog.setParent(nullptr, dialog.windowFlags());
}
/**
@brief QETApp::exportConfiguration
Save the settings of QElectroTech to a file the user chooses, to keep
+1
View File
@@ -294,6 +294,7 @@ class QETApp : public QObject
void openTitleBlockTemplate(const QString &);
void openTitleBlockTemplateFiles(const QStringList &);
void configureQET();
void customizeQET(int tab = 0);
/// Save the settings to a file (discussion #610)
void exportConfiguration();
/// Replace the settings with a saved file, then close QElectroTech
+75 -39
View File
@@ -27,9 +27,11 @@
#endif
#include <QCoreApplication>
#include <QToolButton>
#include <QWidgetAction>
#include "ElementsCollection/elementscollectionwidget.h"
#include "ElementsCollection/elementpickerpopup.h"
#include "shortcutbarsettings.h"
#include "diagramtoolbarsettings.h"
#include "qetgraphicsitem/conductor.h"
#include "itemgroups.h"
#include "commandsearchpopup.h"
@@ -1316,50 +1318,27 @@ void QETDiagramEditor::setUpToolBar()
diagram_tool_bar = new QToolBar(tr("Schéma"), this);
diagram_tool_bar -> setObjectName("diagram");
main_tool_bar -> addActions(m_file_actions_group.actions());
main_tool_bar -> addAction(m_print);
main_tool_bar -> addAction(m_export_to_pdf);
main_tool_bar -> addSeparator();
main_tool_bar -> addAction(undo);
main_tool_bar -> addAction(redo);
main_tool_bar -> addSeparator();
main_tool_bar -> addAction(m_cut);
main_tool_bar -> addAction(m_copy);
main_tool_bar -> addAction(m_paste);
main_tool_bar -> addAction(m_duplicate);
main_tool_bar -> addSeparator();
main_tool_bar -> addAction(m_delete_selection);
main_tool_bar -> addAction(m_rotate_selection);
// Modes selection / visualisation et zoom
view_tool_bar -> addAction(m_mode_selection);
view_tool_bar -> addAction(m_mode_visualise);
view_tool_bar -> addSeparator();
view_tool_bar -> addWidget(new DiagramEditorHandlerSizeWidget(this));
view_tool_bar -> addSeparator();
view_tool_bar -> addAction(m_draw_grid);
view_tool_bar -> addWidget(m_text_grid_button);
view_tool_bar -> addAction(m_draw_guides);
view_tool_bar -> addWidget(m_background_color_button);
view_tool_bar -> addSeparator();
view_tool_bar -> addActions(m_zoom_action_toolBar);
diagram_tool_bar -> addAction (m_edit_diagram_properties);
diagram_tool_bar -> addAction (m_conductor_reset);
diagram_tool_bar -> addAction (m_auto_conductor);
diagram_tool_bar -> addAction (m_auto_break_conductor);
//Sits with the conductor actions it works alongside: it colours
//the selected conductors and sets the colour of the next one drawn.
m_conductor_color_button = new ConductorColorToolButton(this, this);
diagram_tool_bar -> addWidget (m_conductor_color_button);
m_add_item_tool_bar = new QToolBar(tr("Ajouter"), this);
m_add_item_tool_bar->setObjectName("adding");
m_add_item_tool_bar->addActions(m_add_item_actions_group.actions());
m_depth_tool_bar = new QToolBar(tr("Profondeur", "toolbar title"));
m_depth_tool_bar->setObjectName("diagram_depth_toolbar");
m_depth_tool_bar->addActions(m_depth_action_group->actions());
//The toolbar buttons that are widgets, not commands. Each is held by
//an action of this window, so it survives being taken off a toolbar.
auto add_widget = [this](const QString &id, QWidget *widget) {
auto *action = new QWidgetAction(this);
action->setDefaultWidget(widget);
action->setText(DiagramToolbarSettings::widgetTitle(id));
m_toolbar_widgets.insert(id, action);
};
add_widget(QStringLiteral("widget:handler_size"), new DiagramEditorHandlerSizeWidget(this));
add_widget(QStringLiteral("widget:text_grid"), m_text_grid_button);
add_widget(QStringLiteral("widget:background_color"), m_background_color_button);
//Sits with the conductor actions it works alongside: it colours
//the selected conductors and sets the colour of the next one drawn.
m_conductor_color_button = new ConductorColorToolButton(this, this);
add_widget(QStringLiteral("widget:conductor_color"), m_conductor_color_button);
addToolBar(Qt::TopToolBarArea, main_tool_bar);
addToolBar(Qt::TopToolBarArea, view_tool_bar);
@@ -1367,6 +1346,9 @@ void QETDiagramEditor::setUpToolBar()
addToolBar(Qt::TopToolBarArea, m_add_item_tool_bar);
addToolBar(Qt::TopToolBarArea, m_depth_tool_bar);
DiagramToolbarSettings::markWindow(this);
rebuildToolBars();
m_scripts_tool_bar = new QToolBar(tr("Scripts", "toolbar title"), this);
m_scripts_tool_bar->setObjectName("scripts");
addToolBar(Qt::TopToolBarArea, m_scripts_tool_bar);
@@ -1376,6 +1358,59 @@ void QETDiagramEditor::setUpToolBar()
#endif
}
/**
@brief QETDiagramEditor::rebuildToolBars
Fill every toolbar from DiagramToolbarSettings, adding the user's own
toolbars that this window does not have yet and removing those that
were deleted. Called when the window is built, when the settings
change, and when the stored scripts change (a script can be on any
toolbar).
*/
void QETDiagramEditor::rebuildToolBars()
{
const QList<DiagramToolbarSettings::Toolbar> custom = DiagramToolbarSettings::customToolbars();
QStringList custom_names;
for (const DiagramToolbarSettings::Toolbar &toolbar : custom) {
custom_names << toolbar.name;
}
for (QToolBar *toolbar : m_custom_tool_bars) {
if (!custom_names.contains(toolbar->objectName())) {
removeToolBar(toolbar);
toolbar->deleteLater();
}
}
m_custom_tool_bars.erase(std::remove_if(m_custom_tool_bars.begin(), m_custom_tool_bars.end(),
[&custom_names](QToolBar *toolbar) { return !custom_names.contains(toolbar->objectName()); }),
m_custom_tool_bars.end());
for (const DiagramToolbarSettings::Toolbar &toolbar : custom)
{
auto existing = std::find_if(m_custom_tool_bars.cbegin(), m_custom_tool_bars.cend(),
[&toolbar](QToolBar *t) { return t->objectName() == toolbar.name; });
if (existing != m_custom_tool_bars.cend()) {
(*existing)->setWindowTitle(toolbar.title);
continue;
}
auto *new_tool_bar = new QToolBar(toolbar.title, this);
new_tool_bar->setObjectName(toolbar.name);
new_tool_bar->setMovable(!ToolbarSettings::locked());
addToolBar(Qt::TopToolBarArea, new_tool_bar);
m_custom_tool_bars << new_tool_bar;
}
auto resolve = [this](const QString &id) -> QAction * {
if (DiagramToolbarSettings::isWidget(id)) {
return m_toolbar_widgets.value(id);
}
return ShortcutManager::instance().action(id, this);
};
const QList<QToolBar *> built_in {main_tool_bar, view_tool_bar, diagram_tool_bar,
m_add_item_tool_bar, m_depth_tool_bar};
for (QToolBar *toolbar : built_in + m_custom_tool_bars) {
DiagramToolbarSettings::fill(toolbar, DiagramToolbarSettings::ids(toolbar->objectName()), resolve);
}
}
/**
@brief QETDiagramEditor::setUpMenu
*/
@@ -4039,6 +4074,7 @@ void QETDiagramEditor::rebuildScriptActions()
if (has_scripts != m_had_scripts) m_scripts_tool_bar->setVisible(has_scripts);
m_had_scripts = has_scripts;
rebuildToolBars();
updateScriptActions();
}
+5
View File
@@ -140,6 +140,7 @@ class QETDiagramEditor : public QETMainWindow
void rememberPlacedElement(const ElementsLocation &location);
void showElementPicker();
void showShortcutBar();
void rebuildToolBars();
bool repeatLastCommand();
void generateTerminalBlock();
void setWindowedMode();
@@ -328,6 +329,10 @@ class QETDiagramEditor : public QETMainWindow
*m_add_item_tool_bar = nullptr,
*m_depth_tool_bar = nullptr,
*m_scripts_tool_bar = nullptr; ///< One button per stored script
/// Toolbars the user added (DiagramToolbarSettings)
QList<QToolBar *> m_custom_tool_bars;
/// The toolbar buttons that are widgets, by DiagramToolbarSettings id
QHash<QString, QAction *> m_toolbar_widgets;
#ifdef QET_HAS_SCRIPTING
QMenu *m_scripts_menu = nullptr;
QList<QAction *> m_script_actions; ///< One per stored script, rebuilt when the folder changes
+22
View File
@@ -93,6 +93,11 @@ void QETMainWindow::initCommonActions()
}
});
customize_action_ = new QAction(QET::Icons::ConfigureToolbars, tr("&Personnaliser..."), this);
ShortcutManager::instance().registerAction(customize_action_, "mainwindow.customize", tr("Général"), QKeySequence());
customize_action_ -> setStatusTip(tr("Barres d'outils, barre de raccourcis, clavier et gestes de la souris, dans une seule fenêtre", "status bar tip"));
connect(customize_action_, &QAction::triggered, qet_app, [qet_app]() { qet_app->customizeQET(); });
export_config_action_ = new QAction(QET::Icons::DocumentExport, tr("Enregistrer la configuration sous..."), this);
export_config_action_ -> setStatusTip(tr("Enregistre les réglages de QElectroTech dans un fichier", "status bar tip"));
connect(export_config_action_, &QAction::triggered, qet_app, &QETApp::exportConfiguration);
@@ -190,6 +195,7 @@ void QETMainWindow::initCommonMenus()
settings_menu_ = new QMenu(tr("&Configuration", "window menu"), this);
settings_menu_ -> addAction(fullscreen_action_);
settings_menu_ -> addAction(configure_action_);
settings_menu_ -> addAction(customize_action_);
settings_menu_ -> addSeparator();
settings_menu_ -> addAction(export_config_action_);
settings_menu_ -> addAction(import_config_action_);
@@ -274,6 +280,22 @@ void QETMainWindow::updateFullScreenAction()
ShortcutManager::instance().registerAction(fullscreen_action_, "mainwindow.fullscreen", tr("Général"), Qt::CTRL | Qt::SHIFT | Qt::Key_F);
}
/**
@brief QETMainWindow::createPopupMenu
The menu shown on a right-click on a toolbar or a dock title, and the
Configuration > Afficher submenu: Qt's list of toolbars and docks,
then Personnaliser..., as in most applications with toolbars.
*/
QMenu *QETMainWindow::createPopupMenu()
{
QMenu *menu = QMainWindow::createPopupMenu();
if (menu) {
menu -> addSeparator();
menu -> addAction(customize_action_);
}
return menu;
}
/**
Check whether a sub menu dedicated to docks and toolbars can be inserted on
top of the settings menu.
+2
View File
@@ -43,6 +43,7 @@ class QETMainWindow : public QMainWindow {
protected:
void activateMenuBar();
bool event(QEvent *) override;
QMenu *createPopupMenu() override;
void dragEnterEvent(QDragEnterEvent *e) override;
void dropEvent(QDropEvent *e) override;
virtual void firstActivation(QEvent *);
@@ -56,6 +57,7 @@ class QETMainWindow : public QMainWindow {
// attributes
protected:
QAction *configure_action_; ///< Launch the QElectroTech configuration dialog
QAction *customize_action_; ///< Launch the Customise window (toolbars, keys, gestures)
QAction *export_config_action_; ///< Save the settings to a file
QAction *import_config_action_; ///< Replace the settings with a saved file
QAction *fullscreen_action_; ///< Toggle full screen
@@ -0,0 +1,423 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "toolbarcommandsconfigpage.h"
#include "../../qeticons.h"
#include "../../shortcutmanager.h"
#include <QAction>
#include <QComboBox>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QInputDialog>
#include <QLabel>
#include <QLineEdit>
#include <QListWidget>
#include <QPushButton>
#include <QTimer>
#include <QVBoxLayout>
ToolbarCommandsConfigPage::ToolbarCommandsConfigPage(QWidget *parent) :
ConfigPage(parent)
{
const QStringList available = DiagramToolbarSettings::availableCommandIds();
for (const ShortcutManager::ShortcutInfo &info :
ShortcutManager::instance().allShortcuts()) {
if (available.contains(info.id)) {
m_descriptions.insert(info.id, info.description);
}
}
m_custom = DiagramToolbarSettings::customToolbars();
for (const DiagramToolbarSettings::Toolbar &toolbar : DiagramToolbarSettings::toolbars()) {
m_pending.insert(toolbar.name, DiagramToolbarSettings::ids(toolbar.name));
}
auto *explanation = new QLabel(
tr("Choisissez les commandes de chaque barre d'outils de l'éditeur "
"de schémas. Glissez une commande de la liste de gauche vers la "
"barre, ou double-cliquez dessus ; Suppr la retire."), this);
explanation->setWordWrap(true);
m_toolbar = new QComboBox(this);
m_toolbar->setObjectName(QStringLiteral("toolbarCombo"));
auto *new_toolbar = new QPushButton(tr("Nouvelle barre…"), this);
new_toolbar->setObjectName(QStringLiteral("newToolbarButton"));
m_rename = new QPushButton(tr("Renommer…"), this);
m_delete = new QPushButton(tr("Supprimer la barre"), this);
m_delete->setObjectName(QStringLiteral("deleteToolbarButton"));
auto *reset = new QPushButton(tr("Valeurs par défaut"), this);
reset->setObjectName(QStringLiteral("resetButton"));
m_filter = new QLineEdit(this);
m_filter->setPlaceholderText(tr("Rechercher une commande"));
m_filter->setClearButtonEnabled(true);
m_available = new QListWidget(this);
m_available->setObjectName(QStringLiteral("availableList"));
m_available->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_available->setSortingEnabled(true);
m_available->setDragDropMode(QAbstractItemView::DragOnly);
m_available->setDefaultDropAction(Qt::MoveAction);
m_chosen = new QListWidget(this);
m_chosen->setObjectName(QStringLiteral("chosenList"));
m_chosen->setSelectionMode(QAbstractItemView::ExtendedSelection);
//Takes commands dragged from the list on the left, and reorders
m_chosen->setDragDropMode(QAbstractItemView::DragDrop);
m_chosen->setDefaultDropAction(Qt::MoveAction);
auto *add = new QPushButton(tr("Ajouter →"), this);
auto *separator = new QPushButton(tr("Séparateur"), this);
separator->setObjectName(QStringLiteral("separatorButton"));
auto *remove = new QPushButton(tr("← Retirer"), this);
auto *up = new QPushButton(tr("Monter"), this);
auto *down = new QPushButton(tr("Descendre"), this);
auto *buttons = new QVBoxLayout();
buttons->addStretch();
buttons->addWidget(add);
buttons->addWidget(separator);
buttons->addWidget(remove);
buttons->addSpacing(12);
buttons->addWidget(up);
buttons->addWidget(down);
buttons->addStretch();
auto *grid = new QGridLayout();
grid->addWidget(new QLabel(tr("Commandes disponibles"), this), 0, 0);
grid->addWidget(new QLabel(tr("Dans la barre, dans l'ordre"), this), 0, 2);
grid->addWidget(m_filter, 1, 0);
grid->addWidget(m_available, 2, 0);
grid->addLayout(buttons, 2, 1);
grid->addWidget(m_chosen, 1, 2, 2, 1);
auto *toolbar_row = new QHBoxLayout();
toolbar_row->addWidget(new QLabel(tr("Barre d'outils :"), this));
toolbar_row->addWidget(m_toolbar, 1);
toolbar_row->addWidget(new_toolbar);
toolbar_row->addWidget(m_rename);
toolbar_row->addWidget(m_delete);
toolbar_row->addWidget(reset);
auto *layout = new QVBoxLayout(this);
layout->addWidget(explanation);
layout->addLayout(toolbar_row);
layout->addLayout(grid);
connect(m_toolbar, qOverload<int>(&QComboBox::currentIndexChanged), this, [this]() {
storeToolbar();
showToolbar();
});
connect(m_filter, &QLineEdit::textChanged, this, &ToolbarCommandsConfigPage::fillAvailable);
connect(add, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::addSelected);
connect(separator, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::addSeparator);
connect(remove, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::removeSelected);
connect(up, &QPushButton::clicked, this, [this]() { moveSelected(-1); });
connect(down, &QPushButton::clicked, this, [this]() { moveSelected(1); });
connect(reset, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::resetToolbar);
connect(new_toolbar, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::newToolbar);
connect(m_rename, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::renameToolbar);
connect(m_delete, &QPushButton::clicked, this, &ToolbarCommandsConfigPage::deleteToolbar);
connect(m_available, &QListWidget::itemDoubleClicked, this, &ToolbarCommandsConfigPage::addSelected);
connect(m_chosen, &QListWidget::itemDoubleClicked, this, &ToolbarCommandsConfigPage::removeSelected);
//A command dropped from the left is no longer available. Later,
//not now: a dropped row is inserted before its data is set.
connect(m_chosen->model(), &QAbstractItemModel::rowsInserted, this, [this]() {
if (m_chosen->property("filling").toBool()) return;
QTimer::singleShot(0, this, [this]() {
storeToolbar();
fillAvailable();
});
});
auto *delete_key = new QAction(m_chosen);
delete_key->setShortcut(QKeySequence::Delete);
delete_key->setShortcutContext(Qt::WidgetShortcut);
m_chosen->addAction(delete_key);
connect(delete_key, &QAction::triggered, this, &ToolbarCommandsConfigPage::removeSelected);
fillToolbarCombo(DiagramToolbarSettings::builtInNames().constFirst());
}
/**
@brief ToolbarCommandsConfigPage::applyConf
Save the user's own toolbars and every toolbar's contents, then
rebuild the toolbars of the open diagram editors.
*/
void ToolbarCommandsConfigPage::applyConf()
{
storeToolbar();
DiagramToolbarSettings::setCustomToolbars(m_custom);
for (const DiagramToolbarSettings::Toolbar &toolbar : DiagramToolbarSettings::toolbars()) {
DiagramToolbarSettings::setIds(toolbar.name, m_pending.value(toolbar.name));
}
DiagramToolbarSettings::applyToAll();
}
QString ToolbarCommandsConfigPage::title() const
{
return tr("Contenu des barres", "configuration page title: what each toolbar holds");
}
QIcon ToolbarCommandsConfigPage::icon() const
{
return QET::Icons::ConfigureToolbars;
}
/**
@brief ToolbarCommandsConfigPage::fillToolbarCombo
List the built-in toolbars and the user's own, and show @a current.
*/
void ToolbarCommandsConfigPage::fillToolbarCombo(const QString &current)
{
const QSignalBlocker blocker(m_toolbar);
m_toolbar->clear();
for (const QString &name : DiagramToolbarSettings::builtInNames()) {
m_toolbar->addItem(DiagramToolbarSettings::builtInTitle(name), name);
}
for (const DiagramToolbarSettings::Toolbar &toolbar : std::as_const(m_custom)) {
m_toolbar->addItem(toolbar.title, toolbar.name);
}
const int index = m_toolbar->findData(current);
m_toolbar->setCurrentIndex(index < 0 ? 0 : index);
showToolbar();
}
/**
@brief ToolbarCommandsConfigPage::showToolbar
Fill both lists for the toolbar chosen in the combo box.
*/
void ToolbarCommandsConfigPage::showToolbar()
{
m_shown = m_toolbar->currentData().toString();
const bool custom = !DiagramToolbarSettings::builtInNames().contains(m_shown);
m_rename->setEnabled(custom);
m_delete->setEnabled(custom);
m_chosen->setProperty("filling", true);
m_chosen->clear();
for (const QString &id : m_pending.value(m_shown)) {
m_chosen->addItem(makeItem(id));
}
m_chosen->setProperty("filling", false);
fillAvailable();
}
/**
@brief ToolbarCommandsConfigPage::storeToolbar
Keep the shown toolbar's list, in the order on screen.
*/
void ToolbarCommandsConfigPage::storeToolbar()
{
if (m_shown.isEmpty()) {
return;
}
QStringList ids;
for (int i = 0 ; i < m_chosen->count() ; ++i) {
ids << m_chosen->item(i)->data(Qt::UserRole).toString();
}
m_pending.insert(m_shown, ids);
}
/**
@brief ToolbarCommandsConfigPage::fillAvailable
List what can still go on the shown toolbar and matches the search: a
command not already on it, and a widget button on no toolbar at all
(a widget can only be in one place).
*/
void ToolbarCommandsConfigPage::fillAvailable()
{
QStringList on_shown;
for (int i = 0 ; i < m_chosen->count() ; ++i) {
on_shown << m_chosen->item(i)->data(Qt::UserRole).toString();
}
QStringList on_any = on_shown;
for (auto it = m_pending.cbegin() ; it != m_pending.cend() ; ++it) {
if (it.key() != m_shown) on_any << it.value();
}
QStringList ids;
for (auto it = m_descriptions.cbegin() ; it != m_descriptions.cend() ; ++it) {
if (!on_shown.contains(it.key())) ids << it.key();
}
for (const QString &id : DiagramToolbarSettings::widgetIds()) {
if (!on_any.contains(id)) ids << id;
}
m_available->clear();
const QString filter = m_filter->text().trimmed();
for (const QString &id : std::as_const(ids)) {
QListWidgetItem *item = makeItem(id);
if (filter.isEmpty() || item->text().contains(filter, Qt::CaseInsensitive)) {
m_available->addItem(item);
} else {
delete item;
}
}
}
void ToolbarCommandsConfigPage::addSelected()
{
const QList<QListWidgetItem *> selected = m_available->selectedItems();
if (selected.isEmpty()) {
return;
}
m_chosen->setProperty("filling", true);
for (QListWidgetItem *item : selected) {
m_chosen->addItem(m_available->takeItem(m_available->row(item)));
}
m_chosen->setProperty("filling", false);
storeToolbar();
fillAvailable();
}
/**
@brief ToolbarCommandsConfigPage::addSeparator
Insert a separator after the selected row, or at the end.
*/
void ToolbarCommandsConfigPage::addSeparator()
{
const int row = m_chosen->currentRow();
m_chosen->setProperty("filling", true);
m_chosen->insertItem(row < 0 ? m_chosen->count() : row + 1,
makeItem(DiagramToolbarSettings::separatorId()));
m_chosen->setProperty("filling", false);
storeToolbar();
}
void ToolbarCommandsConfigPage::removeSelected()
{
for (QListWidgetItem *item : m_chosen->selectedItems()) {
delete m_chosen->takeItem(m_chosen->row(item));
}
storeToolbar();
fillAvailable();
}
/**
@brief ToolbarCommandsConfigPage::moveSelected
Move the selected command @a step rows, keeping it selected.
*/
void ToolbarCommandsConfigPage::moveSelected(int step)
{
const int row = m_chosen->currentRow();
const int target = row + step;
if (row < 0 || target < 0 || target >= m_chosen->count()) {
return;
}
m_chosen->setProperty("filling", true);
QListWidgetItem *item = m_chosen->takeItem(row);
m_chosen->insertItem(target, item);
m_chosen->setProperty("filling", false);
m_chosen->setCurrentRow(target);
storeToolbar();
}
/**
@brief ToolbarCommandsConfigPage::resetToolbar
Give the shown toolbar its defaults back; the user's own toolbar is
emptied.
*/
void ToolbarCommandsConfigPage::resetToolbar()
{
m_pending.insert(m_shown, DiagramToolbarSettings::defaultIds(m_shown));
//A widget the defaults put here leaves the toolbar it was moved to
for (const QString &id : DiagramToolbarSettings::defaultIds(m_shown)) {
if (!DiagramToolbarSettings::isWidget(id)) continue;
for (auto it = m_pending.begin() ; it != m_pending.end() ; ++it) {
if (it.key() != m_shown) it.value().removeAll(id);
}
}
showToolbar();
}
void ToolbarCommandsConfigPage::newToolbar()
{
bool ok = false;
const QString title = QInputDialog::getText(this, tr("Nouvelle barre d'outils"),
tr("Nom de la barre d'outils :"),
QLineEdit::Normal, QString(), &ok).trimmed();
if (!ok || title.isEmpty()) {
return;
}
storeToolbar();
DiagramToolbarSettings::Toolbar toolbar;
toolbar.name = DiagramToolbarSettings::newCustomName(m_custom);
toolbar.title = title;
toolbar.custom = true;
m_custom << toolbar;
m_pending.insert(toolbar.name, QStringList());
fillToolbarCombo(toolbar.name);
}
void ToolbarCommandsConfigPage::renameToolbar()
{
for (DiagramToolbarSettings::Toolbar &toolbar : m_custom) {
if (toolbar.name != m_shown) continue;
bool ok = false;
const QString title = QInputDialog::getText(this, tr("Renommer la barre d'outils"),
tr("Nom de la barre d'outils :"),
QLineEdit::Normal, toolbar.title, &ok).trimmed();
if (ok && !title.isEmpty()) {
storeToolbar();
toolbar.title = title;
fillToolbarCombo(m_shown);
}
return;
}
}
void ToolbarCommandsConfigPage::deleteToolbar()
{
const QString name = m_shown;
for (int i = 0 ; i < m_custom.size() ; ++i) {
if (m_custom.at(i).name == name) {
m_custom.removeAt(i);
m_pending.remove(name);
m_shown.clear();
fillToolbarCombo(DiagramToolbarSettings::builtInNames().constFirst());
return;
}
}
}
/**
@brief ToolbarCommandsConfigPage::makeItem
@return a list item for @a id with the command's text and icon. An id
no live action carries (a command from a build without it, or a script
since removed) is still listed, by its id, so saving does not silently
drop it.
*/
QListWidgetItem *ToolbarCommandsConfigPage::makeItem(const QString &id) const
{
QString text = m_descriptions.value(id, id);
QString tip;
QIcon icon;
if (id == DiagramToolbarSettings::separatorId()) {
text = tr("─── Séparateur ───");
} else if (DiagramToolbarSettings::isWidget(id)) {
text = DiagramToolbarSettings::widgetTitle(id);
} else if (QAction *action = ShortcutManager::instance().action(id, nullptr)) {
icon = action->icon();
tip = action->statusTip();
}
auto *item = new QListWidgetItem(icon, text);
item->setData(Qt::UserRole, id);
//Tells apart commands with the same name ("Ajouter une ligne")
item->setToolTip(tip);
return item;
}
@@ -0,0 +1,78 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TOOLBARCOMMANDSCONFIGPAGE_H
#define TOOLBARCOMMANDSCONFIGPAGE_H
#include "configpage.h"
#include "../../diagramtoolbarsettings.h"
#include <QHash>
class QComboBox;
class QLineEdit;
class QListWidget;
class QListWidgetItem;
class QPushButton;
/**
@brief The ToolbarCommandsConfigPage class
Choose what each of the diagram editor's toolbars holds, and add,
rename or delete toolbars of one's own. Commands are dragged or added
from the list on the left. Changes are kept per toolbar while the
dialog is open, then saved and applied to the open windows by
applyConf().
*/
class ToolbarCommandsConfigPage : public ConfigPage
{
Q_OBJECT
public:
explicit ToolbarCommandsConfigPage(QWidget *parent = nullptr);
void applyConf() override;
QString title() const override;
QIcon icon() const override;
private:
void fillToolbarCombo(const QString &current);
void showToolbar();
void storeToolbar();
void fillAvailable();
void addSelected();
void addSeparator();
void removeSelected();
void moveSelected(int step);
void resetToolbar();
void newToolbar();
void renameToolbar();
void deleteToolbar();
QListWidgetItem *makeItem(const QString &id) const;
QComboBox *m_toolbar;
QLineEdit *m_filter;
QListWidget *m_available;
QListWidget *m_chosen;
QPushButton *m_rename;
QPushButton *m_delete;
QString m_shown;
QList<DiagramToolbarSettings::Toolbar> m_custom;
QHash<QString, QStringList> m_pending;
QHash<QString, QString> m_descriptions;
};
#endif // TOOLBARCOMMANDSCONFIGPAGE_H
+77
View File
@@ -0,0 +1,77 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "customizedialog.h"
#include "configpage/configpage.h"
#include <QDialogButtonBox>
#include <QIcon>
#include <QTabWidget>
#include <QVBoxLayout>
CustomizeDialog::CustomizeDialog(QWidget *parent) :
QDialog(parent)
{
setWindowTitle(tr("Personnaliser", "window title"));
m_tabs = new QTabWidget(this);
m_tabs->setObjectName(QStringLiteral("customizeTabs"));
m_tabs->setDocumentMode(true);
auto *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
connect(buttons, &QDialogButtonBox::accepted, this, [this]() {
applyConf();
accept();
});
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
auto *layout = new QVBoxLayout(this);
layout->addWidget(m_tabs);
layout->addWidget(buttons);
resize(1000, 700);
}
/**
@brief CustomizeDialog::addPage
Add @a page as a tab, named after its title. The dialog takes it.
*/
void CustomizeDialog::addPage(ConfigPage *page)
{
if (!page || m_pages.contains(page)) {
return;
}
m_pages << page;
m_tabs->addTab(page, page->icon(), page->title());
}
QList<ConfigPage *> CustomizeDialog::pages() const
{
return m_pages;
}
/**
@brief CustomizeDialog::applyConf
Apply every page, in tab order.
*/
void CustomizeDialog::applyConf()
{
for (ConfigPage *page : std::as_const(m_pages)) {
page->applyConf();
}
}
+51
View File
@@ -0,0 +1,51 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef CUSTOMIZEDIALOG_H
#define CUSTOMIZEDIALOG_H
#include <QDialog>
#include <QList>
class ConfigPage;
class QTabWidget;
/**
@brief The CustomizeDialog class
Everything about how the user works in one window, one tab per
configuration page: toolbars, what they hold, the shortcut bar, the
keyboard and the mouse gestures, like SolidWorks' Tools > Customize.
The pages are the ones of the configuration dialog, which still shows
them too. OK applies every page, Cancel none.
*/
class CustomizeDialog : public QDialog
{
Q_OBJECT
public:
explicit CustomizeDialog(QWidget *parent = nullptr);
void addPage(ConfigPage *page);
QList<ConfigPage *> pages() const;
void applyConf();
private:
QTabWidget *m_tabs;
QList<ConfigPage *> m_pages;
};
#endif // CUSTOMIZEDIALOG_H
+33
View File
@@ -298,6 +298,26 @@ set_tests_properties(tst_toolbarsettings PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM
target_include_directories(tst_toolbarsettings PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_toolbarsettings PRIVATE Qt::Test Qt::Widgets Qt::Svg)
# DiagramToolbarSettings and its configuration page: unchanged toolbars
# store nothing and build as before, unknown ids and stray separators are
# skipped, the user's own toolbars are kept, a widget is in one place only.
add_executable(
tst_diagramtoolbarsettings
tst_diagramtoolbarsettings.cpp
${QET_DIR}/sources/diagramtoolbarsettings.cpp
${QET_DIR}/sources/diagramtoolbarsettings.h
${QET_DIR}/sources/shortcutmanager.cpp
${QET_DIR}/sources/ui/configpage/toolbarcommandsconfigpage.cpp
${QET_DIR}/sources/ui/configpage/toolbarcommandsconfigpage.h
${QET_DIR}/sources/ui/configpage/configpage.h
${QET_DIR}/sources/qeticons.cpp
${QET_DIR}/sources/qetpalette.cpp
${QET_DIR}/sources/qetstyle.cpp)
add_test(NAME tst_diagramtoolbarsettings COMMAND tst_diagramtoolbarsettings)
set_tests_properties(tst_diagramtoolbarsettings PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
target_include_directories(tst_diagramtoolbarsettings PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_diagramtoolbarsettings PRIVATE Qt::Test Qt::Widgets Qt::Svg)
# GestureSettings, the gesture ring's 4 / 8 directions and empty slots, and
# its configuration page: the ring follows the shortcut bar until the user
# picks commands, then keeps each one in its direction.
@@ -321,6 +341,19 @@ set_tests_properties(tst_gesturesettings PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM
target_include_directories(tst_gesturesettings PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_gesturesettings PRIVATE Qt::Test Qt::Widgets Qt::Svg Qt::Xml pugixml::pugixml)
# CustomizeDialog: the configuration pages as tabs of one window; OK
# applies every page once, Cancel none.
add_executable(
tst_customizedialog
tst_customizedialog.cpp
${QET_DIR}/sources/ui/customizedialog.cpp
${QET_DIR}/sources/ui/customizedialog.h
${QET_DIR}/sources/ui/configpage/configpage.h)
add_test(NAME tst_customizedialog COMMAND tst_customizedialog)
set_tests_properties(tst_customizedialog PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen")
target_include_directories(tst_customizedialog PRIVATE ${QET_DIR}/sources)
target_link_libraries(tst_customizedialog PRIVATE Qt::Test Qt::Widgets)
# scriptheader.h is header-only: the // ==QETScript== block that turns a
# stored script into a button; and ShortcutManager::unregisterAction(), which
# takes a deleted script's command out of the lists again.
+93
View File
@@ -0,0 +1,93 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "ui/customizedialog.h"
#include "ui/configpage/configpage.h"
#include <QDialogButtonBox>
#include <QIcon>
#include <QPushButton>
#include <QTabWidget>
#include <QTest>
/// A configuration page that counts how often it was applied
class CountingPage : public ConfigPage
{
public:
CountingPage(const QString &title) : ConfigPage(nullptr), m_title(title) {}
void applyConf() override { ++applied; }
QString title() const override { return m_title; }
QIcon icon() const override { return QIcon(); }
int applied = 0;
private:
QString m_title;
};
/**
CustomizeDialog: one tab per page in the order added, OK applies every
page once, Cancel applies none.
*/
class tst_customizedialog : public QObject
{
Q_OBJECT
private slots:
void tabsInOrder()
{
CustomizeDialog dialog;
auto *a = new CountingPage(QStringLiteral("Barres d'outils"));
auto *b = new CountingPage(QStringLiteral("Clavier"));
dialog.addPage(a);
dialog.addPage(b);
dialog.addPage(a); // twice: ignored
dialog.addPage(nullptr);
auto *tabs = dialog.findChild<QTabWidget *>(QStringLiteral("customizeTabs"));
QVERIFY(tabs);
QCOMPARE(tabs->count(), 2);
QCOMPARE(tabs->tabText(0), QStringLiteral("Barres d'outils"));
QCOMPARE(tabs->tabText(1), QStringLiteral("Clavier"));
QCOMPARE(tabs->widget(1), b);
}
void okAppliesEveryPage()
{
CustomizeDialog dialog;
auto *a = new CountingPage(QStringLiteral("a"));
auto *b = new CountingPage(QStringLiteral("b"));
dialog.addPage(a);
dialog.addPage(b);
dialog.show();
dialog.findChild<QDialogButtonBox *>()->button(QDialogButtonBox::Ok)->click();
QCOMPARE(a->applied, 1);
QCOMPARE(b->applied, 1);
QCOMPARE(dialog.result(), int(QDialog::Accepted));
}
void cancelAppliesNothing()
{
CustomizeDialog dialog;
auto *a = new CountingPage(QStringLiteral("a"));
dialog.addPage(a);
dialog.show();
dialog.findChild<QDialogButtonBox *>()->button(QDialogButtonBox::Cancel)->click();
QCOMPARE(a->applied, 0);
QCOMPARE(dialog.result(), int(QDialog::Rejected));
}
};
QTEST_MAIN(tst_customizedialog)
#include "tst_customizedialog.moc"
+245
View File
@@ -0,0 +1,245 @@
/*
Copyright 2006-2026 The QElectroTech Team
This file is part of QElectroTech.
QElectroTech is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
QElectroTech 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
*/
#include "diagramtoolbarsettings.h"
#include "shortcutmanager.h"
#include "ui/configpage/toolbarcommandsconfigpage.h"
#include <QAction>
#include <QComboBox>
#include <QListWidget>
#include <QPushButton>
#include <QSettings>
#include <QStandardPaths>
#include <QTest>
#include <QToolBar>
#include <QWidget>
/**
A top-level window standing in for the diagram editor: counts the
calls applyToAll() makes.
*/
class RebuildCounter : public QWidget
{
Q_OBJECT
public:
int rebuilds = 0;
public slots:
void rebuildToolBars() { ++rebuilds; }
};
/**
DiagramToolbarSettings and its configuration page: an unchanged
toolbar stores nothing and is built as before, unknown ids and stray
separators are skipped, the user's own toolbars are kept and removed
with their contents, and the page saves what it shows.
*/
class tst_diagramtoolbarsettings : public QObject
{
Q_OBJECT
QObject m_owner;
QHash<QString, QAction *> m_actions;
/// The action for @a id, made on first use, as the editor's resolver
QAction *resolve(const QString &id)
{
if (id.startsWith(QLatin1String("missing."))) return nullptr;
if (!m_actions.contains(id)) {
auto *action = new QAction(id, &m_owner);
m_actions.insert(id, action);
}
return m_actions.value(id);
}
static QStringList shape(QToolBar *toolbar)
{
QStringList list;
for (QAction *action : toolbar->actions()) {
list << (action->isSeparator() ? QStringLiteral("|") : action->text());
}
return list;
}
static void clearSettings()
{
QSettings settings;
settings.remove(QStringLiteral("diagrameditor/toolbars"));
settings.remove(QStringLiteral("diagrameditor/custom_toolbars"));
}
private slots:
void initTestCase()
{
QStandardPaths::setTestModeEnabled(true);
clearSettings();
//A few of the diagram editor's commands, as it registers them
for (const QString &id : {QStringLiteral("diagrameditor.copy"),
QStringLiteral("diagrameditor.paste"),
QStringLiteral("diagrameditor.zoom_fit"),
QStringLiteral("depth.raise"),
QStringLiteral("elementeditor.copy")}) {
ShortcutManager::instance().registerAction(
new QAction(id, &m_owner), id, QStringLiteral("test"), QKeySequence());
}
}
void init() { clearSettings(); }
void defaultsStoreNothing()
{
for (const QString &name : DiagramToolbarSettings::builtInNames()) {
QVERIFY(!DiagramToolbarSettings::defaultIds(name).isEmpty());
QCOMPARE(DiagramToolbarSettings::ids(name), DiagramToolbarSettings::defaultIds(name));
DiagramToolbarSettings::setIds(name, DiagramToolbarSettings::defaultIds(name));
}
QVERIFY(!QSettings().contains(QStringLiteral("diagrameditor/toolbars/toolbar")));
DiagramToolbarSettings::setIds(QStringLiteral("toolbar"), {QStringLiteral("diagrameditor.copy")});
QCOMPARE(DiagramToolbarSettings::ids(QStringLiteral("toolbar")),
QStringList{QStringLiteral("diagrameditor.copy")});
//Saving an empty toolbar keeps it empty, not back to the defaults
DiagramToolbarSettings::setIds(QStringLiteral("toolbar"), {});
QVERIFY(DiagramToolbarSettings::ids(QStringLiteral("toolbar")).isEmpty());
}
/// The defaults are built exactly as the toolbars were hand-built
/// before: the main toolbar's three separators in place
void defaultsBuildAsBefore()
{
QToolBar toolbar;
DiagramToolbarSettings::fill(&toolbar, DiagramToolbarSettings::defaultIds(QStringLiteral("toolbar")),
[this](const QString &id) { return resolve(id); });
QCOMPARE(toolbar.actions().size(), 18);
QCOMPARE(shape(&toolbar).mid(6, 2),
(QStringList{QStringLiteral("diagrameditor.export_to_pdf"), QStringLiteral("|")}));
QCOMPARE(shape(&toolbar).count(QStringLiteral("|")), 3);
}
void fillSkipsUnknownAndStraySeparators()
{
const QString sep = DiagramToolbarSettings::separatorId();
QToolBar toolbar;
toolbar.addAction(QStringLiteral("left over"));
DiagramToolbarSettings::fill(&toolbar,
{sep, QStringLiteral("a"), sep, sep, QStringLiteral("missing.b"),
QStringLiteral("c"), sep, QStringLiteral("missing.d"), sep},
[this](const QString &id) { return resolve(id); });
QCOMPARE(shape(&toolbar), (QStringList{QStringLiteral("a"), QStringLiteral("|"), QStringLiteral("c")}));
DiagramToolbarSettings::fill(&toolbar, {}, [this](const QString &id) { return resolve(id); });
QVERIFY(toolbar.actions().isEmpty());
}
void customToolbarsKeptAndRemoved()
{
using T = DiagramToolbarSettings::Toolbar;
const QString first = DiagramToolbarSettings::newCustomName({});
QCOMPARE(first, QStringLiteral("custom_1"));
DiagramToolbarSettings::setCustomToolbars({T{first, QStringLiteral("Mine"), true},
T{QStringLiteral("custom_2"), QStringLiteral("Other"), true}});
DiagramToolbarSettings::setIds(first, {QStringLiteral("diagrameditor.copy")});
QCOMPARE(DiagramToolbarSettings::customToolbars().size(), 2);
QCOMPARE(DiagramToolbarSettings::customToolbars().first().title, QStringLiteral("Mine"));
QCOMPARE(DiagramToolbarSettings::toolbars().size(),
DiagramToolbarSettings::builtInNames().size() + 2);
QCOMPARE(DiagramToolbarSettings::newCustomName({}), QStringLiteral("custom_3"));
//A new toolbar starts empty
QVERIFY(DiagramToolbarSettings::ids(QStringLiteral("custom_2")).isEmpty());
DiagramToolbarSettings::setCustomToolbars({T{QStringLiteral("custom_2"), QStringLiteral("Other"), true}});
QCOMPARE(DiagramToolbarSettings::customToolbars().size(), 1);
QVERIFY(!QSettings().contains(QStringLiteral("diagrameditor/toolbars/") + first));
DiagramToolbarSettings::setCustomToolbars({});
QVERIFY(!QSettings().contains(QStringLiteral("diagrameditor/custom_toolbars/names")));
}
void availableCommandsAreTheDiagramEditors()
{
const QStringList ids = DiagramToolbarSettings::availableCommandIds();
QVERIFY(ids.contains(QStringLiteral("diagrameditor.copy")));
QVERIFY(ids.contains(QStringLiteral("depth.raise")));
QVERIFY(!ids.contains(QStringLiteral("elementeditor.copy")));
}
void pageSavesAndRebuilds()
{
RebuildCounter marked, unmarked;
DiagramToolbarSettings::markWindow(&marked);
ToolbarCommandsConfigPage page;
auto *combo = page.findChild<QComboBox *>(QStringLiteral("toolbarCombo"));
auto *available = page.findChild<QListWidget *>(QStringLiteral("availableList"));
auto *chosen = page.findChild<QListWidget *>(QStringLiteral("chosenList"));
QVERIFY(combo && available && chosen);
QCOMPARE(combo->count(), DiagramToolbarSettings::builtInNames().size());
QCOMPARE(chosen->count(), DiagramToolbarSettings::defaultIds(QStringLiteral("toolbar")).size());
//OK without a change stores nothing
page.applyConf();
QVERIFY(!QSettings().contains(QStringLiteral("diagrameditor/toolbars/toolbar")));
QCOMPARE(marked.rebuilds, 1);
QCOMPARE(unmarked.rebuilds, 0);
//Remove everything but the first command, add a separator
while (chosen->count() > 1) delete chosen->takeItem(1);
chosen->setCurrentRow(0);
page.findChild<QPushButton *>(QStringLiteral("separatorButton"))->click();
page.applyConf();
QCOMPARE(DiagramToolbarSettings::ids(QStringLiteral("toolbar")),
(QStringList{QStringLiteral("diagrameditor.new_file"), DiagramToolbarSettings::separatorId()}));
QCOMPARE(marked.rebuilds, 2);
}
/// A widget button can be in one place only: once moved to another
/// toolbar it is no longer offered, and resetting its own toolbar
/// takes it back
void widgetInOnePlace()
{
ToolbarCommandsConfigPage page;
auto *combo = page.findChild<QComboBox *>(QStringLiteral("toolbarCombo"));
auto *available = page.findChild<QListWidget *>(QStringLiteral("availableList"));
auto *chosen = page.findChild<QListWidget *>(QStringLiteral("chosenList"));
auto offered = [available](const QString &id) {
for (int i = 0 ; i < available->count() ; ++i)
if (available->item(i)->data(Qt::UserRole).toString() == id) return true;
return false;
};
const QString colour = QStringLiteral("widget:conductor_color");
//On "diagram" by default, so not offered for "toolbar"
QVERIFY(!offered(colour));
//Taken off "diagram" (switching toolbars keeps the change)
combo->setCurrentIndex(combo->findData(QStringLiteral("diagram")));
for (int i = chosen->count() - 1 ; i >= 0 ; --i) {
if (chosen->item(i)->data(Qt::UserRole).toString() == colour) delete chosen->takeItem(i);
}
combo->setCurrentIndex(combo->findData(QStringLiteral("toolbar")));
QVERIFY(offered(colour));
//Reset "diagram": the colour button goes back there
combo->setCurrentIndex(combo->findData(QStringLiteral("diagram")));
page.findChild<QPushButton *>(QStringLiteral("resetButton"))->click();
QCOMPARE(chosen->count(), DiagramToolbarSettings::defaultIds(QStringLiteral("diagram")).size());
combo->setCurrentIndex(combo->findData(QStringLiteral("toolbar")));
QVERIFY(!offered(colour));
}
};
QTEST_MAIN(tst_diagramtoolbarsettings)
#include "tst_diagramtoolbarsettings.moc"