diff --git a/sources/main.cpp b/sources/main.cpp index d38eca388..3c065899f 100644 --- a/sources/main.cpp +++ b/sources/main.cpp @@ -22,6 +22,8 @@ #include "logging/eventloopwatchdog.h" #include "logging/qetlogger.h" #include "machine_info.h" +#include "diagram.h" +#include "palettegraphicsview.h" #include "qet.h" #include "qetapp.h" #include "qetmessagebox.h" @@ -144,6 +146,22 @@ int main(int argc, char **argv) #endif } + // Re-apply the sheet background last picked in the diagram editor, so + // every project opened from here on -- existing or new, whichever one + // it is -- draws that background instead of the built-in default that + // would otherwise force the user to pick it again after each start. + // + // Done here rather than in main()'s first lines on purpose: the + // headless export and scripting runs above return before reaching + // this point and must keep rendering on plain white. It also has to + // happen before QETApp is constructed below, since that constructor + // already loads the projects given on the command line. + { + const QetSettings::SheetBackground sheet_background = QetSettings::sheetBackground(); + PaletteGraphicsView::setCustomBackgroundColor(sheet_background.custom); + Diagram::background_color = sheet_background.color; + } + // Resolve the logger's state (log directory, session filename, open // file handle) explicitly here, immediately before installing the // handler -- not implicitly on whichever thread happens to log diff --git a/sources/ui/diagrambgcolorbutton.cpp b/sources/ui/diagrambgcolorbutton.cpp index 608ccac25..dce538faf 100644 --- a/sources/ui/diagrambgcolorbutton.cpp +++ b/sources/ui/diagrambgcolorbutton.cpp @@ -23,6 +23,7 @@ #include "../qetdiagrameditor.h" #include "../projectview.h" #include "../qetproject.h" +#include "../utils/qetsettings.h" #include #include @@ -62,8 +63,28 @@ DiagramBgColorToolButton::DiagramBgColorToolButton(QETDiagramEditor *editor, QWi setStatusTip(tr("Choisir la couleur de fond du folio", "status bar tip")); - m_is_system_color = true; - m_current = QApplication::palette().color(QPalette::Base); + //main() already restored the stored choice onto the global + //Diagram::background_color and PaletteGraphicsView flag; mirror it + //here so the button opens on what the editor actually draws, rather + //than always claiming "system colour" as it used to. + if (PaletteGraphicsView::customBackgroundColor()) { + m_is_system_color = false; + m_current = Diagram::background_color; + } else { + m_is_system_color = true; + m_current = QApplication::palette().color(QPalette::Base); + } + for (const QString &name : QetSettings::sheetBackgroundRecentColors()) + { + const QColor c(name); + if (c.isValid()) { + m_recent.append(c); + } + } + while (m_recent.size() > MAX_RECENT) { + m_recent.removeLast(); + } + setMenu(new QMenu(this)); rebuildMenu(); setSwatch(m_current); @@ -123,6 +144,7 @@ void DiagramBgColorToolButton::applyColor(const QColor &color) PaletteGraphicsView::setCustomBackgroundColor(true); Diagram::background_color = color; + QetSettings::setSheetBackground(QetSettings::SheetBackground{color, true}); QETDiagramEditor *editor = m_editor; if (!editor) { @@ -146,6 +168,8 @@ void DiagramBgColorToolButton::applySystemColor() PaletteGraphicsView::setCustomBackgroundColor(false); Diagram::background_color = Qt::white; + QetSettings::setSheetBackground( + QetSettings::SheetBackground{QColor(Qt::white), false}); QETDiagramEditor *editor = m_editor; if (!editor) { @@ -187,6 +211,21 @@ void DiagramBgColorToolButton::rememberRecent(const QColor &color) m_recent.removeLast(); } rebuildMenu(); + persistRecent(); +} + +/** + @brief DiagramBgColorToolButton::persistRecent + Write the "recently used" colours back to the settings, so they are + still there on the next start instead of starting out empty. +*/ +void DiagramBgColorToolButton::persistRecent() +{ + QStringList names; + for (const QColor &c : std::as_const(m_recent)) { + names << c.name(); + } + QetSettings::setSheetBackgroundRecentColors(names); } /** diff --git a/sources/ui/diagrambgcolorbutton.h b/sources/ui/diagrambgcolorbutton.h index 6da4d8806..ceb04cb13 100644 --- a/sources/ui/diagrambgcolorbutton.h +++ b/sources/ui/diagrambgcolorbutton.h @@ -52,6 +52,7 @@ class DiagramBgColorToolButton : public QToolButton void applySystemColor(); void chooseOtherColor(); void rememberRecent(const QColor &color); + void persistRecent(); void setSwatch(const QColor &color); static QIcon swatchIcon(const QColor &color); diff --git a/sources/utils/qetsettings.cpp b/sources/utils/qetsettings.cpp index 0c97f9816..d15cdcb3e 100644 --- a/sources/utils/qetsettings.cpp +++ b/sources/utils/qetsettings.cpp @@ -152,4 +152,67 @@ namespace QetSettings QSettings settings; settings.setValue("scripting/enabled", enabled); } + + /** + * @brief setSheetBackground + * Store the sheet background last picked in the diagram editor, so the + * next start opens on it for every project, old or new. @sa sheetBackground + * @param background + */ + void setSheetBackground(const SheetBackground &background) + { + QSettings settings; + //HexRgb, not HexArgb, on purpose: Diagram::background_color is + //temporarily given an alpha of 0 by the SVG export to make the + //background transparent, and that transient value must never end + //up here as a stored transparent sheet. + settings.setValue(QStringLiteral("diagrameditor/sheet_background_color"), + background.color.name(QColor::HexRgb)); + settings.setValue(QStringLiteral("diagrameditor/sheet_background_custom"), + background.custom); + } + + /** + * @brief sheetBackground + * @return the sheet background stored by setSheetBackground(), or the + * built-in default (white, following the system) when there is none. + * @sa setSheetBackground + */ + SheetBackground sheetBackground() + { + QSettings settings; + SheetBackground background; + background.custom = settings.value( + QStringLiteral("diagrameditor/sheet_background_custom"), + false).toBool(); + const QColor stored(settings.value( + QStringLiteral("diagrameditor/sheet_background_color"), + QStringLiteral("#ffffff")).toString()); + background.color = stored.isValid() ? stored : QColor(Qt::white); + return background; + } + + /** + * @brief sheetBackgroundRecentColors + * @return the "recently used" sheet background colours, most recent + * first. Empty until the user has picked a non-preset colour. + * @sa setSheetBackgroundRecentColors + */ + QStringList sheetBackgroundRecentColors() + { + QSettings settings; + return settings.value(QStringLiteral("diagrameditor/sheet_background_recent")).toStringList(); + } + + /** + * @brief setSheetBackgroundRecentColors + * Store the "recently used" sheet background colours, most recent first. + * @sa sheetBackgroundRecentColors + * @param colors + */ + void setSheetBackgroundRecentColors(const QStringList &colors) + { + QSettings settings; + settings.setValue(QStringLiteral("diagrameditor/sheet_background_recent"), colors); + } } diff --git a/sources/utils/qetsettings.h b/sources/utils/qetsettings.h index 0108b0474..5712aaf37 100644 --- a/sources/utils/qetsettings.h +++ b/sources/utils/qetsettings.h @@ -19,6 +19,8 @@ #define QETSETTINGS_H #include +#include +#include /** *Interface to use QSettings in QElectroTech @@ -36,6 +38,28 @@ namespace QetSettings bool scriptingEnabled(); void setScriptingEnabled(bool enabled); bool scriptingForcedByEnvironment(); + + /** + @brief The sheet (folio) background the diagram editors draw. + + Both halves are needed, not just the colour: with custom == false + the sheet follows the system ("Couleur systeme") and the views are + free to invert the lightness on a dark palette, while the very same + #ffffff with custom == true is an explicit "always white" that + must not be inverted. Storing only the colour would collapse the + two on the next start. + */ + struct SheetBackground + { + QColor color; + bool custom = false; + }; + + SheetBackground sheetBackground(); + void setSheetBackground(const SheetBackground &background); + + QStringList sheetBackgroundRecentColors(); + void setSheetBackgroundRecentColors(const QStringList &colors); } #endif // QETSETTINGS_H diff --git a/tests/qttest/CMakeLists.txt b/tests/qttest/CMakeLists.txt index 0e8720c12..c5c13e6ce 100644 --- a/tests/qttest/CMakeLists.txt +++ b/tests/qttest/CMakeLists.txt @@ -170,6 +170,17 @@ add_test(NAME tst_scriptingsetting COMMAND tst_scriptingsetting) target_include_directories(tst_scriptingsetting PRIVATE ${QET_DIR}/sources) target_link_libraries(tst_scriptingsetting PRIVATE Qt::Test Qt::Gui) +# QetSettings::sheetBackground() -- the sheet (folio) background the diagram +# editor remembers between runs. Compiles qetsettings.cpp alone for the same +# reason as the test above; Qt::Gui because the pair carries a QColor. +add_executable( + tst_sheetbackgroundsetting + tst_sheetbackgroundsetting.cpp + ${QET_DIR}/sources/utils/qetsettings.cpp) +add_test(NAME tst_sheetbackgroundsetting COMMAND tst_sheetbackgroundsetting) +target_include_directories(tst_sheetbackgroundsetting PRIVATE ${QET_DIR}/sources) +target_link_libraries(tst_sheetbackgroundsetting PRIVATE Qt::Test Qt::Gui) + # CrashHandler::formatInt() -- the async-signal-safe decimal formatter the # signal handler uses for the "Signal: N" line of a crash dump. Compiles # crashhandler.cpp and logring.cpp alongside; the handler deliberately diff --git a/tests/qttest/tst_sheetbackgroundsetting.cpp b/tests/qttest/tst_sheetbackgroundsetting.cpp new file mode 100644 index 000000000..beb639b8a --- /dev/null +++ b/tests/qttest/tst_sheetbackgroundsetting.cpp @@ -0,0 +1,160 @@ +/* + 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 . +*/ + +/* + QetSettings::sheetBackground() -- the sheet (folio) background the + diagram editor remembers between runs, so the user does not have to + pick it again on every start. + + Two properties are pinned here, and both are why this is a pair of + values rather than a single colour: + + * The custom flag. "#ffffff" picked explicitly ("always white, never + inverted on a dark palette") and "#ffffff" left to follow the + system (where the views invert the lightness) are one colour and + two behaviours. Storing only the colour would silently turn one + into the other on the next start -- which is exactly the reported + bug, one step removed. + * The alpha being dropped. The SVG export gives + Diagram::background_color an alpha of 0 to render a transparent + background, and that transient value must not be persisted as a + permanently transparent sheet. + + This test owns its own QSettings scope (organization + application + name), so it cannot read or write the configuration of whoever runs + it. +*/ + +#include "utils/qetsettings.h" + +#include +#include + +class TstSheetBackgroundSetting : public QObject +{ + Q_OBJECT + + private slots: + void initTestCase(); + void init(); + + void defaultsToWhiteFollowingTheSystem(); + void colourAndCustomFlagAreStoredTogether(); + void sameColourStillDistinguishesTheTwoModes(); + void alphaIsNotStored(); + void unreadableColourFallsBackToWhite(); + void recentColorsDefaultToEmpty(); + void recentColorsKeepTheirOrder(); +}; + +void TstSheetBackgroundSetting::initTestCase() +{ + // A scope of this test's own: whatever this writes must not land in + // the configuration of the account running the suite. + QCoreApplication::setOrganizationName( + QStringLiteral("QElectroTech-tst_sheetbackgroundsetting")); + QCoreApplication::setApplicationName( + QStringLiteral("tst_sheetbackgroundsetting")); + QSettings settings; + settings.clear(); +} + +void TstSheetBackgroundSetting::init() +{ + QSettings settings; + settings.remove(QStringLiteral("diagrameditor/sheet_background_color")); + settings.remove(QStringLiteral("diagrameditor/sheet_background_custom")); + settings.remove(QStringLiteral("diagrameditor/sheet_background_recent")); + settings.sync(); +} + +void TstSheetBackgroundSetting::defaultsToWhiteFollowingTheSystem() +{ + // An untouched installation, i.e. every existing one after an upgrade: + // white sheet, following the system so the views may still invert it. + const auto background = QetSettings::sheetBackground(); + QVERIFY(background.color.isValid()); + QCOMPARE(background.color, QColor(Qt::white)); + QVERIFY(!background.custom); +} + +void TstSheetBackgroundSetting::colourAndCustomFlagAreStoredTogether() +{ + QetSettings::setSheetBackground(QetSettings::SheetBackground{QColor("#404040"), true}); + + const auto background = QetSettings::sheetBackground(); + QCOMPARE(background.color, QColor("#404040")); + QVERIFY(background.custom); +} + +void TstSheetBackgroundSetting::sameColourStillDistinguishesTheTwoModes() +{ + // The reported bug in miniature: white is white either way, yet one + // of the two must survive a restart as an explicit choice and the + // other as "follow the system". + QetSettings::setSheetBackground(QetSettings::SheetBackground{QColor("#ffffff"), true}); + QVERIFY(QetSettings::sheetBackground().custom); + + QetSettings::setSheetBackground(QetSettings::SheetBackground{QColor("#ffffff"), false}); + QVERIFY(!QetSettings::sheetBackground().custom); +} + +void TstSheetBackgroundSetting::alphaIsNotStored() +{ + // What the SVG export leaves on Diagram::background_color once it has + // asked for a transparent background. + QColor transparent(QStringLiteral("#12ab34")); + transparent.setAlpha(0); + QetSettings::setSheetBackground(QetSettings::SheetBackground{transparent, true}); + + const auto background = QetSettings::sheetBackground(); + QCOMPARE(background.color.name(), QStringLiteral("#12ab34")); + QCOMPARE(background.color.alpha(), 255); +} + +void TstSheetBackgroundSetting::unreadableColourFallsBackToWhite() +{ + // A hand-edited or truncated settings file must not blank the sheet. + QSettings settings; + settings.setValue(QStringLiteral("diagrameditor/sheet_background_color"), + QStringLiteral("not a colour")); + settings.sync(); + + const auto background = QetSettings::sheetBackground(); + QCOMPARE(background.color, QColor(Qt::white)); +} + +void TstSheetBackgroundSetting::recentColorsDefaultToEmpty() +{ + QVERIFY(QetSettings::sheetBackgroundRecentColors().isEmpty()); +} + +void TstSheetBackgroundSetting::recentColorsKeepTheirOrder() +{ + // Most recent first, as the menu lists them. + QetSettings::setSheetBackgroundRecentColors({QStringLiteral("#111111"), + QStringLiteral("#222222")}); + QCOMPARE(QetSettings::sheetBackgroundRecentColors(), + QStringList({QStringLiteral("#111111"), QStringLiteral("#222222")})); + + QetSettings::setSheetBackgroundRecentColors(QStringList()); + QVERIFY(QetSettings::sheetBackgroundRecentColors().isEmpty()); +} + +QTEST_MAIN(TstSheetBackgroundSetting) +#include "tst_sheetbackgroundsetting.moc"