├── testfiles └── setup │ ├── data8 │ └── c │ │ └── -1xmvebl.d │ └── releases.json ├── resources ├── qtutilsicons.qrc ├── importplugin.h ├── icons │ └── hicolor │ │ └── scalable │ │ └── apps │ │ └── qtcreator.svg ├── qtconfigarguments.h ├── resources.h └── qtconfigarguments.cpp ├── misc ├── undefxmlparsermacros.h ├── desktoputils.h ├── adoptlocker.h ├── recentmenumanager.h ├── disablewarningsmoc.h ├── trylocker.h ├── conversion.h ├── xmlparsermacros.h ├── dialogutils.h ├── compat.h ├── desktoputils.cpp └── recentmenumanager.cpp ├── settingsdialog ├── optioncategoryfiltermodel.h ├── optionpage.ui.template ├── optioncategoryfiltermodel.cpp ├── optioncategorymodel.h ├── qtsettings.h ├── settingsdialog.h ├── qtenvoptionpage.ui ├── optioncategory.cpp ├── optioncategory.h ├── qtlanguageoptionpage.ui ├── optioncategorymodel.cpp ├── optionpage.cpp └── settingsdialog.ui ├── .gitignore ├── README.md ├── cmake ├── templates │ ├── jsincludes.h.in │ ├── webviewincludes.h.in │ ├── qtconfig.h.in │ ├── webviewdefs.h.in │ └── jsdefs.h.in └── modules │ ├── QtJsProviderConfig.cmake │ ├── QtWebViewProviderConfig.cmake │ └── QtGuiConfig.cmake ├── widgets ├── clearcombobox.h ├── clearplaintextedit.h ├── clearlineedit.h ├── clearlineedit.cpp ├── iconbutton.h ├── clearcombobox.cpp ├── clearspinbox.cpp ├── pathselection.h ├── clearspinbox.h ├── buttonoverlay.h ├── clearplaintextedit.cpp ├── iconbutton.cpp └── pathselection.cpp ├── .github └── stale.yml ├── global.h ├── scripts └── svg_to_png.sh ├── tests ├── dialogs.cpp ├── dbusnotification.cpp ├── setup.cpp └── buttonoverlay.cpp ├── aboutdialog ├── aboutdialog.h ├── aboutdialog.cpp └── aboutdialog.ui ├── paletteeditor ├── colorbutton.h ├── paletteeditor.ui ├── paletteeditor.h └── colorbutton.cpp ├── dbus └── org.freedesktop.Notifications.xml ├── enterpassworddialog └── enterpassworddialog.h ├── models ├── checklistmodel.h └── checklistmodel.cpp └── CMakeLists.txt /testfiles/setup/data8/c/-1xmvebl.d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Martchus/qtutilities/HEAD/testfiles/setup/data8/c/-1xmvebl.d -------------------------------------------------------------------------------- /resources/qtutilsicons.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | icons/hicolor/scalable/apps/qtcreator.svg 4 | 5 | 6 | -------------------------------------------------------------------------------- /misc/undefxmlparsermacros.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * \file undefxmlparsermacros.h 3 | * \brief Undefines macros to utilize XML parsing using QXmlStreamReader. 4 | * \sa For an example, see dbquery.cpp of the tageditor project. 5 | */ 6 | 7 | #ifdef iftag 8 | #undef iftag 9 | #endif 10 | #ifdef eliftag 11 | #undef eliftag 12 | #endif 13 | #ifdef else_skip 14 | #undef else_skip 15 | #endif 16 | #ifdef children 17 | #undef children 18 | #endif 19 | #ifdef text 20 | #undef text 21 | #endif 22 | #ifdef attribute 23 | #undef attribute 24 | #endif 25 | -------------------------------------------------------------------------------- /settingsdialog/optioncategoryfiltermodel.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOGS_OPTIONCATEGORYFILTERMODEL_H 2 | #define DIALOGS_OPTIONCATEGORYFILTERMODEL_H 3 | 4 | #include 5 | 6 | namespace QtUtilities { 7 | 8 | class OptionCategoryFilterModel : public QSortFilterProxyModel { 9 | Q_OBJECT 10 | public: 11 | explicit OptionCategoryFilterModel(QObject *parent = nullptr); 12 | 13 | protected: 14 | bool filterAcceptsRow(int source_row, const QModelIndex &sourceParent) const override; 15 | }; 16 | } // namespace QtUtilities 17 | 18 | #endif // DIALOGS_OPTIONCATEGORYFILTERMODEL_H 19 | -------------------------------------------------------------------------------- /settingsdialog/optionpage.ui.template: -------------------------------------------------------------------------------- 1 | 2 | 3 | NS::Class 4 | 5 | 6 | 7 | 0 8 | 0 9 | 345 10 | 146 11 | 12 | 13 | 14 | Window title 15 | 16 | 17 | QGroupBox { font-weight: bold }; 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.txt.user 19 | *.pro.user.* 20 | *.qbs.user 21 | *.qbs.user.* 22 | *.moc 23 | moc_*.cpp 24 | qrc_*.cpp 25 | ui_*.h 26 | Makefile* 27 | *-build-* 28 | 29 | # QtCreator 30 | 31 | *.autosave 32 | 33 | #QtCtreator Qml 34 | *.qmlproject.user 35 | *.qmlproject.user.* 36 | 37 | # Dolphin 38 | .directory 39 | 40 | # documentation 41 | /doc 42 | 43 | # clang-format 44 | /.clang-format 45 | 46 | # clangd 47 | compile_commands.json 48 | /.cache 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # qtutilities 2 | Common Qt related C++ classes and routines used by my applications such as dialogs, widgets and models. 3 | 4 | ## Build instructions 5 | Qt utilities depends on c++utilities and is built in the same way. So check out this repository for further 6 | instructions. The documentation for various CMake variables influencing the build is also provided in the 7 | c++utilities repository. 8 | 9 | The library also depends on the following Qt modules (only the latest Qt 5 and Qt 6 version tested): 10 | core, gui, widgets 11 | 12 | Some header files also require further Qt modules. 13 | 14 | ## Copyright notice and license 15 | Copyright © 2015-2025 Marius Kittler 16 | 17 | All code is licensed under [GPL-2-or-later](LICENSE). 18 | -------------------------------------------------------------------------------- /cmake/templates/jsincludes.h.in: -------------------------------------------------------------------------------- 1 | // Created via CMake from template jsincludes.h.in 2 | // WARNING! Any changes to this file will be overwritten by the next CMake run! 3 | 4 | #ifndef @META_PROJECT_VARNAME_UPPER@_JAVA_SCRIPT_INCLUDES 5 | #define @META_PROJECT_VARNAME_UPPER@_JAVA_SCRIPT_INCLUDES 6 | 7 | #include 8 | 9 | #if defined(@META_PROJECT_VARNAME_UPPER@_USE_JSENGINE) 10 | # include 11 | # include 12 | #elif defined(@META_PROJECT_VARNAME_UPPER@_USE_SCRIPT) 13 | # include 14 | # include 15 | #elif !defined(@META_PROJECT_VARNAME_UPPER@_NO_JSENGINE) 16 | # error "No definition for JavaScript provider present." 17 | #endif 18 | 19 | #endif // @META_PROJECT_VARNAME_UPPER@_JAVA_SCRIPT_INCLUDES 20 | -------------------------------------------------------------------------------- /widgets/clearcombobox.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_CLEARCOMBOBOX_H 2 | #define WIDGETS_CLEARCOMBOBOX_H 3 | 4 | #include "./buttonoverlay.h" 5 | 6 | #include 7 | 8 | namespace QtUtilities { 9 | 10 | class QT_UTILITIES_EXPORT ClearComboBox : public QComboBox, public ButtonOverlay { 11 | Q_OBJECT 12 | Q_PROPERTY(bool cleared READ isCleared) 13 | 14 | public: 15 | explicit ClearComboBox(QWidget *parent = nullptr); 16 | ~ClearComboBox() override; 17 | bool isCleared() const override; 18 | 19 | private Q_SLOTS: 20 | void handleTextChanged(const QString &text); 21 | void handleClearButtonClicked() override; 22 | void handleCustomLayoutCreated() override; 23 | }; 24 | 25 | } // namespace QtUtilities 26 | 27 | #endif // WIDGETS_CLEARCOMBOBOX_H 28 | -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - pinned 8 | - security 9 | - feature request 10 | - enhancement 11 | # Label to use when marking an issue as stale 12 | staleLabel: stale 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. Thank you 17 | for your contributions. 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | closeComment: false 20 | -------------------------------------------------------------------------------- /cmake/templates/webviewincludes.h.in: -------------------------------------------------------------------------------- 1 | // Created via CMake from template webviewincludes.h.in 2 | // WARNING! Any changes to this file will be overwritten by the next CMake run! 3 | 4 | #ifndef @META_PROJECT_VARNAME_UPPER@_WEB_VIEW_INCLUDES 5 | #define @META_PROJECT_VARNAME_UPPER@_WEB_VIEW_INCLUDES 6 | 7 | #include 8 | 9 | #if defined(@META_PROJECT_VARNAME_UPPER@_USE_WEBENGINE) 10 | # include 11 | # include 12 | # include 13 | #elif defined(@META_PROJECT_VARNAME_UPPER@_USE_WEBKIT) 14 | # include 15 | # include 16 | # include 17 | #elif !defined(@META_PROJECT_VARNAME_UPPER@_NO_WEBVIEW) 18 | # error "No definition for web view provider present." 19 | #endif 20 | 21 | #endif // @META_PROJECT_VARNAME_UPPER@_WEB_VIEW_INCLUDES 22 | -------------------------------------------------------------------------------- /widgets/clearplaintextedit.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_CLEARPLAINTEXTEDIT_H 2 | #define WIDGETS_CLEARPLAINTEXTEDIT_H 3 | 4 | #include "./buttonoverlay.h" 5 | 6 | #include 7 | 8 | namespace QtUtilities { 9 | 10 | class QT_UTILITIES_EXPORT ClearPlainTextEdit : public QPlainTextEdit, public ButtonOverlay { 11 | Q_OBJECT 12 | Q_PROPERTY(bool cleared READ isCleared) 13 | 14 | public: 15 | explicit ClearPlainTextEdit(QWidget *parent = nullptr); 16 | ~ClearPlainTextEdit() override; 17 | bool isCleared() const override; 18 | 19 | private Q_SLOTS: 20 | void handleTextChanged(); 21 | void handleClearButtonClicked() override; 22 | void handleCustomLayoutCreated() override; 23 | void handleScroll(); 24 | }; 25 | 26 | } // namespace QtUtilities 27 | 28 | #endif // WIDGETS_CLEARPLAINTEXTEDIT_H 29 | -------------------------------------------------------------------------------- /widgets/clearlineedit.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_CLEARLINEEDIT_H 2 | #define WIDGETS_CLEARLINEEDIT_H 3 | 4 | #include "./buttonoverlay.h" 5 | 6 | #include 7 | 8 | QT_FORWARD_DECLARE_CLASS(QHBoxLayout) 9 | 10 | namespace QtUtilities { 11 | 12 | class IconButton; 13 | 14 | class QT_UTILITIES_EXPORT ClearLineEdit : public QLineEdit, public ButtonOverlay { 15 | Q_OBJECT 16 | Q_PROPERTY(bool cleared READ isCleared) 17 | 18 | public: 19 | explicit ClearLineEdit(QWidget *parent = nullptr); 20 | ~ClearLineEdit() override; 21 | bool isCleared() const override; 22 | 23 | private Q_SLOTS: 24 | void handleTextChanged(const QString &text); 25 | void handleClearButtonClicked() override; 26 | void handleCustomLayoutCreated() override; 27 | }; 28 | } // namespace QtUtilities 29 | 30 | #endif // WIDGETS_CLEARLINEEDIT_H 31 | -------------------------------------------------------------------------------- /global.h: -------------------------------------------------------------------------------- 1 | // Created via CMake from template global.h.in 2 | // WARNING! Any changes to this file will be overwritten by the next CMake run! 3 | 4 | #ifndef QT_UTILITIES_GLOBAL 5 | #define QT_UTILITIES_GLOBAL 6 | 7 | #include "qtutilities-definitions.h" 8 | #include 9 | 10 | #ifdef QT_UTILITIES_STATIC 11 | #define QT_UTILITIES_EXPORT 12 | #define QT_UTILITIES_IMPORT 13 | #else 14 | #define QT_UTILITIES_EXPORT CPP_UTILITIES_GENERIC_LIB_EXPORT 15 | #define QT_UTILITIES_IMPORT CPP_UTILITIES_GENERIC_LIB_IMPORT 16 | #endif 17 | 18 | /*! 19 | * \def QT_UTILITIES_EXPORT 20 | * \brief Marks the symbol to be exported by the qtutilities library. 21 | */ 22 | 23 | /*! 24 | * \def QT_UTILITIES_IMPORT 25 | * \brief Marks the symbol to be imported from the qtutilities library. 26 | */ 27 | 28 | #endif // QT_UTILITIES_GLOBAL 29 | -------------------------------------------------------------------------------- /misc/desktoputils.h: -------------------------------------------------------------------------------- 1 | #ifndef DESKTOP_UTILS_DESKTOPSERVICES_H 2 | #define DESKTOP_UTILS_DESKTOPSERVICES_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | QT_FORWARD_DECLARE_CLASS(QObject) 13 | QT_FORWARD_DECLARE_CLASS(QString) 14 | 15 | namespace QtUtilities { 16 | 17 | QT_UTILITIES_EXPORT bool openLocalFileOrDir(const QString &path); 18 | QT_UTILITIES_EXPORT bool isPaletteDark(const QPalette &palette = QPalette()); 19 | QT_UTILITIES_EXPORT std::optional isDarkModeEnabled(); 20 | QT_UTILITIES_EXPORT QMetaObject::Connection onDarkModeChanged( 21 | std::function &&darkModeChangedCallback, QObject *context = nullptr, bool invokeImmediately = true); 22 | 23 | } // namespace QtUtilities 24 | 25 | #endif // DESKTOP_UTILS_DESKTOPSERVICES_H 26 | -------------------------------------------------------------------------------- /misc/adoptlocker.h: -------------------------------------------------------------------------------- 1 | #ifndef THEADING_UTILS_ADOPTLOCKER_H 2 | #define THEADING_UTILS_ADOPTLOCKER_H 3 | 4 | #include 5 | 6 | QT_FORWARD_DECLARE_CLASS(QMutex) 7 | 8 | namespace QtUtilities { 9 | 10 | /*! 11 | * \brief Like QMutexLocker, but assumes that the mutex has already been locked. 12 | */ 13 | template class AdoptLocker { 14 | public: 15 | /*! 16 | * \brief Constructs the locker for the specified \a mutex. 17 | */ 18 | AdoptLocker(Mutex &mutex) 19 | : m_mutex(mutex) 20 | { 21 | } 22 | 23 | /*! 24 | * \brief Unlocks the mutex specified when constructing the instance. 25 | */ 26 | ~AdoptLocker() 27 | { 28 | m_mutex.unlock(); 29 | } 30 | 31 | private: 32 | Mutex &m_mutex; 33 | }; 34 | } // namespace QtUtilities 35 | 36 | #endif // THEADING_UTILS_ADOPTLOCKER_H 37 | -------------------------------------------------------------------------------- /resources/importplugin.h: -------------------------------------------------------------------------------- 1 | #ifndef MISC_UTILS_IMPORT_PLUGIN_H 2 | #define MISC_UTILS_IMPORT_PLUGIN_H 3 | 4 | #include "resources/qtconfig.h" 5 | 6 | #ifdef QT_STATIC 7 | #if defined(QT_UTILITIES_GUI_QTWIDGETS) || defined(QT_UTILITIES_GUI_QTQUICK) 8 | #include 9 | #ifdef Q_OS_WIN32 10 | Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin) 11 | #endif 12 | #ifdef Q_OS_MACOS 13 | Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin) 14 | #endif 15 | #ifdef SVG_SUPPORT 16 | Q_IMPORT_PLUGIN(QSvgPlugin) 17 | #endif 18 | #ifdef SVG_ICON_SUPPORT 19 | Q_IMPORT_PLUGIN(QSvgIconPlugin) 20 | #endif 21 | #ifdef IMAGE_FORMAT_SUPPORT 22 | IMPORT_IMAGE_FORMAT_PLUGINS 23 | #endif 24 | IMPORT_WIDGET_STYLE_PLUGINS 25 | #endif // defined(QT_UTILITIES_GUI_QTWIDGETS) || defined(QT_UTILITIES_GUI_QTQUICK) 26 | IMPORT_TLS_PLUGINS 27 | IMPORT_NETWORK_INFORMATION_PLUGINS 28 | #endif // QT_STATIC 29 | 30 | #endif // MISC_UTILS_IMPORT_PLUGIN_H 31 | -------------------------------------------------------------------------------- /misc/recentmenumanager.h: -------------------------------------------------------------------------------- 1 | #ifndef MISC_UTILS_RECENTMENUMANAGER_H 2 | #define MISC_UTILS_RECENTMENUMANAGER_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | QT_FORWARD_DECLARE_CLASS(QMenu) 9 | QT_FORWARD_DECLARE_CLASS(QAction) 10 | 11 | namespace QtUtilities { 12 | 13 | class QT_UTILITIES_EXPORT RecentMenuManager : public QObject { 14 | Q_OBJECT 15 | 16 | public: 17 | RecentMenuManager(QMenu *menu, QObject *parent = nullptr); 18 | 19 | public Q_SLOTS: 20 | void restore(const QStringList &savedEntries); 21 | QStringList save(); 22 | void addEntry(const QString &path); 23 | void clearEntries(); 24 | 25 | Q_SIGNALS: 26 | void fileSelected(const QString &path); 27 | 28 | private Q_SLOTS: 29 | void handleActionTriggered(); 30 | 31 | private: 32 | QMenu *m_menu; 33 | QAction *m_sep; 34 | QAction *m_clearAction; 35 | }; 36 | } // namespace QtUtilities 37 | 38 | #endif // MISC_UTILS_RECENTMENUMANAGER_H 39 | -------------------------------------------------------------------------------- /cmake/templates/qtconfig.h.in: -------------------------------------------------------------------------------- 1 | // Created via CMake from template qtconfig.h.in 2 | // WARNING! Any changes to this file will be overwritten by the next CMake run! 3 | 4 | #ifndef @META_PROJECT_VARNAME_UPPER@_QT_CONFIG 5 | #define @META_PROJECT_VARNAME_UPPER@_QT_CONFIG 6 | 7 | #define QT_TRANSLATION_FILES {@QT_TRANSLATION_FILES_ARRAY@} 8 | #define APP_SPECIFIC_QT_TRANSLATION_FILES {@APP_SPECIFIC_QT_TRANSLATION_FILES_ARRAY@} 9 | #define ENABLE_QT_RESOURCES_OF_STATIC_DEPENDENCIES@ENABLE_QT_RESOURCES_OF_STATIC_DEPENDENCIES@ 10 | #cmakedefine SVG_SUPPORT 11 | #cmakedefine SVG_ICON_SUPPORT 12 | #cmakedefine IMAGE_FORMAT_SUPPORT 13 | #define IMPORT_IMAGE_FORMAT_PLUGINS @IMAGE_FORMAT_SUPPORT_ARRAY@ 14 | #define IMPORT_WIDGET_STYLE_PLUGINS @WIDGET_STYLE_PLUGINS_ARRAY@ 15 | #define IMPORT_TLS_PLUGINS @USED_TLS_PLUGINS_ARRAY@ 16 | #define IMPORT_NETWORK_INFORMATION_PLUGINS @USED_NETWORK_INFORMATION_PLUGINS_ARRAY@ 17 | @META_CUSTOM_QT_CONFIG@ 18 | #endif // @META_PROJECT_VARNAME_UPPER@_QT_CONFIG 19 | -------------------------------------------------------------------------------- /misc/disablewarningsmoc.h: -------------------------------------------------------------------------------- 1 | #ifndef QT_UTILITIES_DISABLE_WARNINGS_MOC_H 2 | #define QT_UTILITIES_DISABLE_WARNINGS_MOC_H 3 | 4 | #include // for QtCompilerDetection 5 | 6 | /*! 7 | * \file disablewarningsmoc.h 8 | * \brief Macro to disable warnings that sources generated by moc might generate. 9 | * \remarks These warnings are also worked around by disabling them for `mocs_compilation.cpp` 10 | * files via `set_source_files_properties` on CMake-level. The set of warnings should 11 | * be kept in-sync between the CMake-level workaround and this macro. 12 | */ 13 | 14 | #define QT_UTILITIES_DISABLE_WARNINGS_FOR_MOC_INCLUDE \ 15 | QT_WARNING_PUSH \ 16 | QT_WARNING_DISABLE_GCC("-Wsign-conversion") \ 17 | QT_WARNING_DISABLE_CLANG("-Wsign-conversion") 18 | #define QT_UTILITIES_RESTORE_WARNINGS_FOR_MOC_INCLUDE QT_WARNING_POP 19 | 20 | #endif // QT_UTILITIES_DISABLE_WARNINGS_MOC_H 21 | -------------------------------------------------------------------------------- /scripts/svg_to_png.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # abort on first error 4 | set -e 5 | 6 | if [[ ! $@ ]]; then 7 | echo 'Updates the the PNG icons of the projects in the specified directories.' 8 | echo 'However, no project directories have been specified.' 9 | exit -1 10 | fi 11 | 12 | # define array for commands to be executed 13 | cmds=() 14 | 15 | # iterate over specified source directories 16 | for srcdir in "$@"; do 17 | # find SVG icons 18 | for svg_icon_full_path in $(find "$srcdir" -iname '*.svg'); do 19 | prefix="${svg_icon_full_path%/scalable/*}" 20 | svg_icon="${svg_icon_full_path##*/scalable/}" 21 | # add inkscape command for each icon and size and ensure output directory exists 22 | for size in 16 32 48; do 23 | mkdir -p "${prefix}/${size}x${size}/${svg_icon%/*.svg}" 24 | cmds+=("inkscape --without-gui \"${svg_icon_full_path}\" --export-png=\"${prefix}/${size}x${size}/${svg_icon%.svg}.png\" --export-width=${size} --export-height=${size}") 25 | done 26 | done 27 | done 28 | 29 | # run commands 30 | function print_cmds { 31 | for cmd in "${cmds[@]}"; do 32 | echo "$cmd" 33 | done 34 | } 35 | echo "Executing the following commands:" 36 | print_cmds 37 | print_cmds | parallel 38 | -------------------------------------------------------------------------------- /misc/trylocker.h: -------------------------------------------------------------------------------- 1 | #ifndef THREADING_UTILS_TRYLOCKER_H 2 | #define THREADING_UTILS_TRYLOCKER_H 3 | 4 | #include 5 | 6 | QT_FORWARD_DECLARE_CLASS(QMutex) 7 | 8 | namespace QtUtilities { 9 | 10 | /*! 11 | * \brief Like QMutexLocker, but it just tries to lock the mutex. 12 | */ 13 | template class TryLocker { 14 | public: 15 | /*! 16 | * \brief Tries to lock the specified mutex. 17 | */ 18 | TryLocker(Mutex &mutex) 19 | : m_mutex(mutex.tryLock() ? &mutex : nullptr) 20 | { 21 | } 22 | 23 | /*! 24 | * \brief Unlocks the mutex specified when constructing. 25 | * \remarks Does nothing if the mutex couldn't be locked in the first place. 26 | */ 27 | ~TryLocker() 28 | { 29 | if (m_mutex) { 30 | m_mutex->unlock(); 31 | } 32 | } 33 | 34 | /*! 35 | * \brief Returns whether the mutex could be locked. 36 | */ 37 | bool isLocked() const 38 | { 39 | return m_mutex != nullptr; 40 | } 41 | 42 | /*! 43 | * \brief Returns whether the mutex could be locked. 44 | */ 45 | operator bool() const 46 | { 47 | return m_mutex != nullptr; 48 | } 49 | 50 | private: 51 | Mutex *m_mutex; 52 | }; 53 | } // namespace QtUtilities 54 | 55 | #endif // THREADING_UTILS_TRYLOCKER_H 56 | -------------------------------------------------------------------------------- /cmake/templates/webviewdefs.h.in: -------------------------------------------------------------------------------- 1 | // Created via CMake from template webviewdefs.h.in 2 | // WARNING! Any changes to this file will be overwritten by the next CMake run! 3 | 4 | #ifndef @META_PROJECT_VARNAME_UPPER@_WEB_VIEW_DEFINES 5 | #define @META_PROJECT_VARNAME_UPPER@_WEB_VIEW_DEFINES 6 | 7 | #include 8 | 9 | #if defined(@META_PROJECT_VARNAME_UPPER@_USE_WEBENGINE) 10 | # define @META_PROJECT_VARNAME_UPPER@_WEB_VIEW QWebEngineView 11 | # define @META_PROJECT_VARNAME_UPPER@_WEB_PAGE QWebEnginePage 12 | #elif defined(@META_PROJECT_VARNAME_UPPER@_USE_WEBKIT) 13 | # define @META_PROJECT_VARNAME_UPPER@_WEB_VIEW QWebView 14 | # define @META_PROJECT_VARNAME_UPPER@_WEB_PAGE QWebPage 15 | # define @META_PROJECT_VARNAME_UPPER@_WEB_FRAME QWebFrame 16 | #elif !defined(@META_PROJECT_VARNAME_UPPER@_NO_WEBVIEW) 17 | # error "No definition for web view provider present." 18 | #endif 19 | 20 | #ifdef @META_PROJECT_VARNAME_UPPER@_WEB_VIEW 21 | QT_FORWARD_DECLARE_CLASS(@META_PROJECT_VARNAME_UPPER@_WEB_VIEW) 22 | #endif 23 | #ifdef @META_PROJECT_VARNAME_UPPER@_WEB_PAGE 24 | QT_FORWARD_DECLARE_CLASS(@META_PROJECT_VARNAME_UPPER@_WEB_PAGE) 25 | #endif 26 | #ifdef @META_PROJECT_VARNAME_UPPER@_WEB_FRAME 27 | QT_FORWARD_DECLARE_CLASS(@META_PROJECT_VARNAME_UPPER@_WEB_FRAME) 28 | #endif 29 | 30 | #endif // @META_PROJECT_VARNAME_UPPER@_WEB_VIEW_DEFINES 31 | -------------------------------------------------------------------------------- /tests/dialogs.cpp: -------------------------------------------------------------------------------- 1 | #include "../settingsdialog/optioncategory.h" 2 | #include "../settingsdialog/optioncategorymodel.h" 3 | #include "../settingsdialog/qtsettings.h" 4 | #include "../settingsdialog/settingsdialog.h" 5 | 6 | #include "../misc/disablewarningsmoc.h" 7 | 8 | #include 9 | 10 | using namespace QtUtilities; 11 | 12 | class DialogsTests : public QObject { 13 | Q_OBJECT 14 | 15 | private Q_SLOTS: 16 | void testSettingsDialog(); 17 | }; 18 | 19 | void DialogsTests::testSettingsDialog() 20 | { 21 | // show single category 22 | auto settingsDlg = SettingsDialog(); 23 | auto qtSettings = QtSettings(); 24 | settingsDlg.setSingleCategory(qtSettings.category()); 25 | 26 | // add another empty category 27 | auto *const testCategory = new OptionCategory(); 28 | testCategory->setDisplayName(QStringLiteral("Test category")); 29 | testCategory->setIcon(QIcon::fromTheme(QStringLiteral("preferences"))); 30 | settingsDlg.showCategory(nullptr); // ensure no current category is shown anymore 31 | settingsDlg.setSingleCategory(nullptr); 32 | auto *const qtCategory = qtSettings.category(); 33 | settingsDlg.categoryModel()->setCategories(QList({ testCategory, qtCategory })); 34 | settingsDlg.showCategory(qtCategory); 35 | settingsDlg.show(); 36 | } 37 | 38 | QT_UTILITIES_DISABLE_WARNINGS_FOR_MOC_INCLUDE 39 | QTEST_MAIN(DialogsTests) 40 | #include "dialogs.moc" 41 | -------------------------------------------------------------------------------- /settingsdialog/optioncategoryfiltermodel.cpp: -------------------------------------------------------------------------------- 1 | #include "./optioncategoryfiltermodel.h" 2 | #include "./optioncategory.h" 3 | #include "./optioncategorymodel.h" 4 | 5 | namespace QtUtilities { 6 | 7 | /*! 8 | * \class OptionCategoryFilterModel 9 | * \brief The OptionCategoryFilterModel class is used by SettingsDialog to 10 | * filter option categories. 11 | */ 12 | 13 | /*! 14 | * \brief Constructs an option category filter model. 15 | */ 16 | OptionCategoryFilterModel::OptionCategoryFilterModel(QObject *parent) 17 | : QSortFilterProxyModel(parent) 18 | { 19 | } 20 | 21 | bool OptionCategoryFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const 22 | { 23 | if (QSortFilterProxyModel::filterAcceptsRow(sourceRow, sourceParent)) 24 | return true; 25 | if (auto *const model = qobject_cast(sourceModel())) { 26 | if (OptionCategory *category = model->category(sourceRow)) { 27 | return category->matches( 28 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 29 | filterRegularExpression().pattern() 30 | #elif (QT_VERSION >= QT_VERSION_CHECK(5, 12, 0)) 31 | !filterRegularExpression().pattern().isEmpty() ? filterRegularExpression().pattern() : filterRegExp().pattern() 32 | #else 33 | filterRegExp().pattern() 34 | #endif 35 | ); 36 | } 37 | } 38 | return false; 39 | } 40 | } // namespace QtUtilities 41 | -------------------------------------------------------------------------------- /misc/conversion.h: -------------------------------------------------------------------------------- 1 | #ifndef QT_UTILITIES_CONVERSION_H 2 | #define QT_UTILITIES_CONVERSION_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace QtUtilities { 12 | 13 | inline QByteArray toNativeFileName(const QString &fileName) 14 | { 15 | #if !defined(PLATFORM_WINDOWS) || !defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) 16 | return fileName.toLocal8Bit(); 17 | #else 18 | return fileName.toUtf8(); 19 | #endif 20 | } 21 | 22 | inline QString fromNativeFileName(const char *nativeFileName, int size = -1) 23 | { 24 | #if !defined(PLATFORM_WINDOWS) || !defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) 25 | return QString::fromLocal8Bit(nativeFileName, size); 26 | #else 27 | return QString::fromUtf8(nativeFileName, size); 28 | #endif 29 | } 30 | 31 | inline QString fromNativeFileName(const std::string &nativeFileName) 32 | { 33 | #if !defined(PLATFORM_WINDOWS) || !defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) 34 | return QString::fromLocal8Bit(nativeFileName.data(), static_cast(nativeFileName.size())); 35 | #else 36 | return QString::fromUtf8(nativeFileName.data(), static_cast(nativeFileName.size())); 37 | #endif 38 | } 39 | 40 | inline QString qstringFromStdStringView(std::string_view stringView) 41 | { 42 | return QString::fromUtf8(stringView.data(), static_cast(stringView.size())); 43 | } 44 | 45 | } // namespace QtUtilities 46 | 47 | #endif // QT_UTILITIES_CONVERSION_H 48 | -------------------------------------------------------------------------------- /aboutdialog/aboutdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOGS_ABOUTDIALOG_H 2 | #define DIALOGS_ABOUTDIALOG_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | QT_FORWARD_DECLARE_CLASS(QGraphicsScene) 11 | 12 | namespace QtUtilities { 13 | 14 | namespace Ui { 15 | class AboutDialog; 16 | } 17 | 18 | class QT_UTILITIES_EXPORT AboutDialog : public QDialog { 19 | Q_OBJECT 20 | 21 | public: 22 | explicit AboutDialog(QWidget *parent, const QString &applicationName, const QString &creator, const QString &version, 23 | const QString &website = QString(), const QString &description = QString(), const QImage &image = QImage()); 24 | explicit AboutDialog(QWidget *parent, const QString &applicationName, const QString &creator, const QString &version, 25 | const std::vector &dependencyVersions, const QString &website = QString(), const QString &description = QString(), 26 | const QImage &image = QImage()); 27 | explicit AboutDialog(QWidget *parent, const QString &website = QString(), const QString &description = QString(), const QImage &image = QImage()); 28 | ~AboutDialog() override; 29 | 30 | Q_SIGNALS: 31 | void retranslationRequired(); 32 | 33 | protected: 34 | bool event(QEvent *event) override; 35 | 36 | private Q_SLOTS: 37 | void linkActivated(const QString &link); 38 | 39 | private: 40 | std::unique_ptr m_ui; 41 | QGraphicsScene *m_iconScene; 42 | }; 43 | } // namespace QtUtilities 44 | 45 | #endif // DIALOGS_ABOUTDIALOG_H 46 | -------------------------------------------------------------------------------- /widgets/clearlineedit.cpp: -------------------------------------------------------------------------------- 1 | #include "./clearlineedit.h" 2 | 3 | #include 4 | #include 5 | 6 | namespace QtUtilities { 7 | 8 | /*! 9 | * \class ClearLineEdit 10 | * \brief A QLineEdit with an embedded button for clearing its contents. 11 | */ 12 | 13 | /*! 14 | * \brief Constructs a clear line edit. 15 | */ 16 | ClearLineEdit::ClearLineEdit(QWidget *parent) 17 | : QLineEdit(parent) 18 | , ButtonOverlay(this, this) 19 | { 20 | ButtonOverlay::setClearButtonEnabled(true); 21 | } 22 | 23 | /*! 24 | * \brief Destroys the clear combo box. 25 | */ 26 | ClearLineEdit::~ClearLineEdit() 27 | { 28 | } 29 | 30 | /*! 31 | * \brief Updates the visibility of the clear button. 32 | */ 33 | void ClearLineEdit::handleTextChanged(const QString &text) 34 | { 35 | updateClearButtonVisibility(!text.isEmpty()); 36 | } 37 | 38 | void ClearLineEdit::handleClearButtonClicked() 39 | { 40 | clear(); 41 | } 42 | 43 | void ClearLineEdit::handleCustomLayoutCreated() 44 | { 45 | const QStyle *const s = style(); 46 | QStyleOptionFrame opt; 47 | opt.initFrom(this); 48 | setContentsMarginsFromEditFieldRectAndFrameWidth(s->subElementRect(QStyle::SE_LineEditContents, &opt, this), 49 | s->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, m_widget), s->pixelMetric(QStyle::PM_LayoutVerticalSpacing, &opt, m_widget)); 50 | connect(this, &ClearLineEdit::textChanged, this, &ClearLineEdit::handleTextChanged); 51 | } 52 | 53 | bool ClearLineEdit::isCleared() const 54 | { 55 | return text().isEmpty(); 56 | } 57 | } // namespace QtUtilities 58 | -------------------------------------------------------------------------------- /widgets/iconbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_ICONBUTTON_H 2 | #define WIDGETS_ICONBUTTON_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace QtUtilities { 14 | 15 | class QT_UTILITIES_EXPORT IconButton : public QAbstractButton { 16 | Q_OBJECT 17 | Q_PROPERTY(QPixmap pixmap READ pixmap WRITE setPixmap) 18 | 19 | public: 20 | explicit IconButton(QWidget *parent = nullptr); 21 | ~IconButton() override; 22 | 23 | static IconButton *fromAction(QAction *action, std::uintptr_t id = 0); 24 | const QPixmap &pixmap() const; 25 | void setPixmap(const QPixmap &pixmap); 26 | QSize sizeHint() const override; 27 | 28 | static constexpr auto defaultPixmapSize = QSize(16, 16); 29 | 30 | protected: 31 | void paintEvent(QPaintEvent *event) override; 32 | void keyPressEvent(QKeyEvent *event) override; 33 | void keyReleaseEvent(QKeyEvent *event) override; 34 | 35 | private Q_SLOTS: 36 | void assignDataFromActionChangedSignal(); 37 | void assignDataFromAction(const QAction *action); 38 | 39 | private: 40 | QPixmap m_pixmap; 41 | }; 42 | 43 | /*! 44 | * \brief Returns the pixmap. 45 | */ 46 | inline const QPixmap &IconButton::pixmap() const 47 | { 48 | return m_pixmap; 49 | } 50 | 51 | /*! 52 | * \brief Sets the pixmap. 53 | */ 54 | inline void IconButton::setPixmap(const QPixmap &pixmap) 55 | { 56 | m_pixmap = pixmap; 57 | update(); 58 | } 59 | } // namespace QtUtilities 60 | 61 | #endif // WIDGETS_ICONBUTTON_H 62 | -------------------------------------------------------------------------------- /paletteeditor/colorbutton.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_COLORBUTTON_H 2 | #define WIDGETS_COLORBUTTON_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | namespace QtUtilities { 9 | 10 | /*! 11 | * \brief The ColorButton class is used by PaletteEditor. 12 | * 13 | * This is taken from qttools/src/shared/qtgradienteditor/qtcolorbutton.h. 14 | */ 15 | class QT_UTILITIES_EXPORT ColorButton : public QToolButton { 16 | Q_OBJECT 17 | Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged) 18 | Q_PROPERTY(bool backgroundCheckered READ isBackgroundCheckered WRITE setBackgroundCheckered) 19 | public: 20 | ColorButton(QWidget *parent = nullptr); 21 | ~ColorButton() override; 22 | 23 | bool isBackgroundCheckered() const; 24 | void setBackgroundCheckered(bool checkered); 25 | 26 | QColor color() const; 27 | 28 | public Q_SLOTS: 29 | void setColor(const QColor &color); 30 | 31 | Q_SIGNALS: 32 | void colorChanged(const QColor &color); 33 | 34 | protected: 35 | void paintEvent(QPaintEvent *event) override; 36 | void mousePressEvent(QMouseEvent *event) override; 37 | void mouseMoveEvent(QMouseEvent *event) override; 38 | #ifndef QT_NO_DRAGANDDROP 39 | void dragEnterEvent(QDragEnterEvent *event) override; 40 | void dragLeaveEvent(QDragLeaveEvent *event) override; 41 | void dropEvent(QDropEvent *event) override; 42 | #endif 43 | 44 | private: 45 | QScopedPointer d_ptr; 46 | Q_DECLARE_PRIVATE(ColorButton) 47 | Q_DISABLE_COPY(ColorButton) 48 | Q_PRIVATE_SLOT(d_func(), void slotEditColor()) 49 | }; 50 | } // namespace QtUtilities 51 | 52 | #endif // WIDGETS_COLORBUTTON_H 53 | -------------------------------------------------------------------------------- /misc/xmlparsermacros.h: -------------------------------------------------------------------------------- 1 | /*! 2 | * \file xmlparsermacros.h 3 | * \brief Macros to utilize XML parsing using QXmlStreamReader. 4 | * \sa For an example, see dbquery.cpp of the tageditor project. 5 | */ 6 | 7 | // ensure there are no conflicting macros defined 8 | #include "./undefxmlparsermacros.h" 9 | 10 | #define iftag(tagName) if (xmlReader.name() == QLatin1String(tagName)) 11 | #define eliftag(tagName) else if (xmlReader.name() == QLatin1String(tagName)) 12 | #define else_skip \ 13 | else \ 14 | { \ 15 | xmlReader.skipCurrentElement(); \ 16 | } 17 | #define children while (xmlReader.readNextStartElement()) 18 | #define text (xmlReader.readElementText(QXmlStreamReader::ErrorOnUnexpectedElement)) 19 | #define attribute(attributeName) (xmlReader.attributes().value(QLatin1String(attributeName))) 20 | #define attributeFlag(attributeName) \ 21 | (xmlReader.attributes().hasAttribute(QLatin1String(attributeName)) \ 22 | && xmlReader.attributes().value(QLatin1String(attributeName)) != QLatin1String("false")) 23 | -------------------------------------------------------------------------------- /dbus/org.freedesktop.Notifications.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /widgets/clearcombobox.cpp: -------------------------------------------------------------------------------- 1 | #include "./clearcombobox.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace QtUtilities { 8 | 9 | /*! 10 | * \class ClearComboBox 11 | * \brief A QComboBox with an embedded button for clearing its contents. 12 | */ 13 | 14 | /// \cond 15 | static inline auto *getComboBoxLineEdit(QComboBox *comboBox) 16 | { 17 | comboBox->setEditable(true); 18 | return comboBox->lineEdit(); 19 | } 20 | /// \endcond 21 | 22 | /*! 23 | * \brief Constructs a clear combo box. 24 | * \remarks The combo box is initialized to be editable and which must not be changed. 25 | */ 26 | ClearComboBox::ClearComboBox(QWidget *parent) 27 | : QComboBox(parent) 28 | , ButtonOverlay(this, getComboBoxLineEdit(this)) 29 | { 30 | ButtonOverlay::setClearButtonEnabled(true); 31 | } 32 | 33 | /*! 34 | * \brief Destroys the clear combo box. 35 | */ 36 | ClearComboBox::~ClearComboBox() 37 | { 38 | } 39 | 40 | /*! 41 | * \brief Updates the visibility of the clear button. 42 | */ 43 | void ClearComboBox::handleTextChanged(const QString &text) 44 | { 45 | updateClearButtonVisibility(!text.isEmpty()); 46 | } 47 | 48 | void ClearComboBox::handleClearButtonClicked() 49 | { 50 | clearEditText(); 51 | } 52 | 53 | void ClearComboBox::handleCustomLayoutCreated() 54 | { 55 | const QStyle *const s = style(); 56 | QStyleOptionComboBox opt; 57 | opt.initFrom(this); 58 | setContentsMarginsFromEditFieldRectAndFrameWidth( 59 | s->subControlRect(QStyle::CC_ComboBox, &opt, QStyle::SC_ComboBoxEditField, this), s->pixelMetric(QStyle::PM_ComboBoxFrameWidth, &opt, this)); 60 | connect(this, &ClearComboBox::currentTextChanged, this, &ClearComboBox::handleTextChanged); 61 | } 62 | 63 | bool ClearComboBox::isCleared() const 64 | { 65 | return currentText().isEmpty(); 66 | } 67 | } // namespace QtUtilities 68 | -------------------------------------------------------------------------------- /cmake/templates/jsdefs.h.in: -------------------------------------------------------------------------------- 1 | // Created via CMake from template jsdefs.h.in 2 | // WARNING! Any changes to this file will be overwritten by the next CMake run! 3 | 4 | #ifndef @META_PROJECT_VARNAME_UPPER@_JAVA_SCRIPT_DEFINES 5 | #define @META_PROJECT_VARNAME_UPPER@_JAVA_SCRIPT_DEFINES 6 | 7 | #include 8 | 9 | #if defined(@META_PROJECT_VARNAME_UPPER@_USE_JSENGINE) 10 | # define @META_PROJECT_VARNAME_UPPER@_JS_ENGINE QJSEngine 11 | # define @META_PROJECT_VARNAME_UPPER@_JS_VALUE QJSValue 12 | # define @META_PROJECT_VARNAME_UPPER@_JS_READONLY 13 | # define @META_PROJECT_VARNAME_UPPER@_JS_UNDELETABLE 14 | # define @META_PROJECT_VARNAME_UPPER@_JS_QOBJECT(engine, obj) engine.newQObject(obj) 15 | # define @META_PROJECT_VARNAME_UPPER@_JS_INT(value) value.toInt() 16 | # define @META_PROJECT_VARNAME_UPPER@_JS_IS_VALID_PROG(program) (!program.isError() && program.isCallable()) 17 | #elif defined(@META_PROJECT_VARNAME_UPPER@_USE_SCRIPT) 18 | # define @META_PROJECT_VARNAME_UPPER@_JS_ENGINE QScriptEngine 19 | # define @META_PROJECT_VARNAME_UPPER@_JS_VALUE QScriptValue 20 | # define @META_PROJECT_VARNAME_UPPER@_JS_READONLY ,QScriptValue::ReadOnly 21 | # define @META_PROJECT_VARNAME_UPPER@_JS_UNDELETABLE ,QScriptValue::Undeletable 22 | # define @META_PROJECT_VARNAME_UPPER@_JS_QOBJECT(engine, obj) engine.newQObject(obj, QScriptEngine::ScriptOwnership) 23 | # define @META_PROJECT_VARNAME_UPPER@_JS_INT(value) value.toInt32() 24 | # define @META_PROJECT_VARNAME_UPPER@_JS_IS_VALID_PROG(program) (!program.isError() && program.isFunction()) 25 | #elif !defined(@META_PROJECT_VARNAME_UPPER@_NO_JSENGINE) 26 | # error "No definition for JavaScript provider present." 27 | #endif 28 | 29 | #ifdef @META_PROJECT_VARNAME_UPPER@_JS_ENGINE 30 | QT_FORWARD_DECLARE_CLASS(@META_PROJECT_VARNAME_UPPER@_JS_ENGINE) 31 | #endif 32 | #ifdef @META_PROJECT_VARNAME_UPPER@_JS_VALUE 33 | QT_FORWARD_DECLARE_CLASS(@META_PROJECT_VARNAME_UPPER@_JS_VALUE) 34 | #endif 35 | 36 | #endif // @META_PROJECT_VARNAME_UPPER@_JAVA_SCRIPT_DEFINES 37 | -------------------------------------------------------------------------------- /widgets/clearspinbox.cpp: -------------------------------------------------------------------------------- 1 | #include "./clearspinbox.h" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace QtUtilities { 8 | 9 | /*! 10 | * \class ClearSpinBox 11 | * \brief A QSpinBox with an embedded button for clearing its contents and the 12 | * ability to hide 13 | * the minimum value. 14 | */ 15 | 16 | /*! 17 | * \brief Constructs a clear spin box. 18 | */ 19 | ClearSpinBox::ClearSpinBox(QWidget *parent) 20 | : QSpinBox(parent) 21 | , ButtonOverlay(this, lineEdit()) 22 | , m_minimumHidden(false) 23 | { 24 | ButtonOverlay::setClearButtonEnabled(true); 25 | } 26 | 27 | /*! 28 | * \brief Destroys the clear spin box. 29 | */ 30 | ClearSpinBox::~ClearSpinBox() 31 | { 32 | } 33 | 34 | /*! 35 | * \brief Updates the visibility of the clear button. 36 | */ 37 | void ClearSpinBox::handleValueChanged(int value) 38 | { 39 | updateClearButtonVisibility(value != minimum()); 40 | } 41 | 42 | void ClearSpinBox::handleClearButtonClicked() 43 | { 44 | setValue(minimum()); 45 | } 46 | 47 | void ClearSpinBox::handleCustomLayoutCreated() 48 | { 49 | const QStyle *const s = style(); 50 | QStyleOptionSpinBox opt; 51 | opt.initFrom(this); 52 | setContentsMarginsFromEditFieldRectAndFrameWidth( 53 | s->subControlRect(QStyle::CC_SpinBox, &opt, QStyle::SC_SpinBoxEditField, this), s->pixelMetric(QStyle::PM_SpinBoxFrameWidth, &opt, this)); 54 | connect(this, static_cast(&ClearSpinBox::valueChanged), this, &ClearSpinBox::handleValueChanged); 55 | } 56 | 57 | bool ClearSpinBox::isCleared() const 58 | { 59 | return value() == minimum(); 60 | } 61 | 62 | int ClearSpinBox::valueFromText(const QString &text) const 63 | { 64 | if (m_minimumHidden && text.isEmpty()) { 65 | return minimum(); 66 | } else { 67 | return QSpinBox::valueFromText(text); 68 | } 69 | } 70 | 71 | QString ClearSpinBox::textFromValue(int val) const 72 | { 73 | if (m_minimumHidden && (val == minimum())) { 74 | return QString(); 75 | } else { 76 | return QSpinBox::textFromValue(val); 77 | } 78 | } 79 | } // namespace QtUtilities 80 | -------------------------------------------------------------------------------- /widgets/pathselection.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_PATHSELECTION_H 2 | #define WIDGETS_PATHSELECTION_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | QT_FORWARD_DECLARE_CLASS(QPushButton) 9 | QT_FORWARD_DECLARE_CLASS(QCompleter) 10 | 11 | namespace QtUtilities { 12 | 13 | class ClearLineEdit; 14 | 15 | class QT_UTILITIES_EXPORT PathSelection : public QWidget { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit PathSelection(QWidget *parent = nullptr); 20 | 21 | ClearLineEdit *lineEdit(); 22 | const ClearLineEdit *lineEdit() const; 23 | void provideCustomFileMode(QFileDialog::FileMode customFileMode); 24 | void provideCustomFileDialog(QFileDialog *customFileDialog); 25 | 26 | protected: 27 | bool event(QEvent *event) override; 28 | bool eventFilter(QObject *obj, QEvent *event) override; 29 | 30 | private Q_SLOTS: 31 | void showFileDialog(); 32 | void setTexts(); 33 | 34 | private: 35 | ClearLineEdit *m_lineEdit; 36 | QPushButton *m_button; 37 | QFileDialog *m_customDialog; 38 | QFileDialog::FileMode m_customMode; 39 | static QCompleter *s_completer; 40 | }; 41 | 42 | /*! 43 | * \brief Returns the line edit with the selected path. 44 | */ 45 | inline ClearLineEdit *PathSelection::lineEdit() 46 | { 47 | return m_lineEdit; 48 | } 49 | 50 | /*! 51 | * \brief Returns the line edit with the selected path. 52 | */ 53 | inline const ClearLineEdit *PathSelection::lineEdit() const 54 | { 55 | return m_lineEdit; 56 | } 57 | 58 | /*! 59 | * \brief Can be used to provide a custom file mode. 60 | * 61 | * The default file mode is QFileDialog::Directory. 62 | */ 63 | inline void PathSelection::provideCustomFileMode(QFileDialog::FileMode customFileMode) 64 | { 65 | m_customMode = customFileMode; 66 | } 67 | 68 | /*! 69 | * \brief Can be used to provide a custom file dialog. 70 | * 71 | * The default file mode is ignored when a custom file dialog has been 72 | * specified. 73 | */ 74 | inline void PathSelection::provideCustomFileDialog(QFileDialog *customFileDialog) 75 | { 76 | m_customDialog = customFileDialog; 77 | } 78 | } // namespace QtUtilities 79 | 80 | #endif // WIDGETS_PATHSELECTION_H 81 | -------------------------------------------------------------------------------- /widgets/clearspinbox.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_CLEARSPINBOX_H 2 | #define WIDGETS_CLEARSPINBOX_H 3 | 4 | #include "./buttonoverlay.h" 5 | 6 | #include 7 | #include 8 | 9 | QT_FORWARD_DECLARE_CLASS(QHBoxLayout) 10 | 11 | namespace QtUtilities { 12 | 13 | class IconButton; 14 | 15 | class QT_UTILITIES_EXPORT ClearSpinBox : public QSpinBox, public ButtonOverlay { 16 | Q_OBJECT 17 | Q_PROPERTY(bool cleared READ isCleared) 18 | Q_PROPERTY(bool minimumHidden READ minimumHidden WRITE setMinimumHidden) 19 | Q_PROPERTY(QString placeholderText READ placeholderText WRITE setPlaceholderText) 20 | 21 | public: 22 | explicit ClearSpinBox(QWidget *parent = nullptr); 23 | ~ClearSpinBox() override; 24 | bool minimumHidden() const; 25 | void setMinimumHidden(bool value); 26 | QString placeholderText() const; 27 | void setPlaceholderText(const QString &placeholderText); 28 | bool isCleared() const override; 29 | 30 | protected: 31 | int valueFromText(const QString &text) const override; 32 | QString textFromValue(int val) const override; 33 | 34 | private Q_SLOTS: 35 | void handleValueChanged(int value); 36 | void handleClearButtonClicked() override; 37 | void handleCustomLayoutCreated() override; 38 | 39 | private: 40 | bool m_minimumHidden; 41 | }; 42 | 43 | /*! 44 | * \brief Returns whether the minimum value will be hidden. 45 | */ 46 | inline bool ClearSpinBox::minimumHidden() const 47 | { 48 | return m_minimumHidden; 49 | } 50 | 51 | /*! 52 | * \brief Sets whether the minimum value should be hidden. 53 | */ 54 | inline void ClearSpinBox::setMinimumHidden(bool value) 55 | { 56 | m_minimumHidden = value; 57 | } 58 | 59 | /*! 60 | * \brief Returns the placeholder text. 61 | * \sa QLineEdit::placeholderText() 62 | */ 63 | inline QString ClearSpinBox::placeholderText() const 64 | { 65 | return lineEdit()->placeholderText(); 66 | } 67 | 68 | /*! 69 | * \brief Sets the placeholder text. 70 | * \sa QLineEdit::setPlaceholderText() 71 | */ 72 | inline void ClearSpinBox::setPlaceholderText(const QString &placeholderText) 73 | { 74 | lineEdit()->setPlaceholderText(placeholderText); 75 | } 76 | } // namespace QtUtilities 77 | 78 | #endif // WIDGETS_CLEARSPINBOX_H 79 | -------------------------------------------------------------------------------- /misc/dialogutils.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOGS_DIALOGUTILS_H 2 | #define DIALOGS_DIALOGUTILS_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | QT_FORWARD_DECLARE_CLASS(QString) 9 | QT_FORWARD_DECLARE_CLASS(QWidget) 10 | QT_FORWARD_DECLARE_CLASS(QColor) 11 | QT_FORWARD_DECLARE_CLASS(QPalette) 12 | QT_FORWARD_DECLARE_CLASS(QPoint) 13 | QT_FORWARD_DECLARE_CLASS(QRect) 14 | 15 | namespace QtUtilities { 16 | 17 | /*! 18 | * \brief The DocumentStatus enum specifies the status of the document in a 19 | * window. 20 | */ 21 | enum class DocumentStatus { 22 | NoDocument, /**< There is no document opened. The document path is ignored in 23 | this case. */ 24 | Saved, /**< There is a document opened. All modifications have been saved yet. 25 | */ 26 | Unsaved /**< There is a document opened and there are unsaved modifications. 27 | */ 28 | }; 29 | 30 | QT_UTILITIES_EXPORT QString generateWindowTitle(DocumentStatus documentStatus, const QString &documentPath); 31 | 32 | #if defined(QT_UTILITIES_GUI_QTWIDGETS) || defined(QT_UTILITIES_GUI_QTQUICK) 33 | #ifdef Q_OS_WINDOWS 34 | [[deprecated]] QT_UTILITIES_EXPORT QColor windowFrameColor(); 35 | QT_UTILITIES_EXPORT QColor windowFrameColorForPalette(const QPalette &palette); 36 | [[deprecated]] QT_UTILITIES_EXPORT QColor instructionTextColor(); 37 | QT_UTILITIES_EXPORT QColor instructionTextColorForPalette(const QPalette &palette); 38 | #endif 39 | [[deprecated]] QT_UTILITIES_EXPORT const QString &dialogStyle(); 40 | QT_UTILITIES_EXPORT QString dialogStyleForPalette(const QPalette &palette); 41 | #ifdef QT_UTILITIES_GUI_QTWIDGETS 42 | QT_UTILITIES_EXPORT QRect availableScreenGeometryAtPoint(const QPoint &point); 43 | QT_UTILITIES_EXPORT void centerWidget(QWidget *widget, const QWidget *parent = nullptr, const QPoint *position = nullptr); 44 | QT_UTILITIES_EXPORT bool centerWidgetAvoidingOverflow(QWidget *widget, const QWidget *parent = nullptr, const QPoint *position = nullptr); 45 | QT_UTILITIES_EXPORT void cornerWidget(QWidget *widget, const QPoint *position = nullptr); 46 | QT_UTILITIES_EXPORT void makeHeading(QWidget *widget); 47 | QT_UTILITIES_EXPORT void updateStyle(QWidget *widget); 48 | #endif 49 | #endif 50 | 51 | } // namespace QtUtilities 52 | 53 | #endif // DIALOGS_DIALOGUTILS_H 54 | -------------------------------------------------------------------------------- /settingsdialog/optioncategorymodel.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOGS_OPTIONCATEGORYMODEL_H 2 | #define DIALOGS_OPTIONCATEGORYMODEL_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | #include 8 | 9 | namespace QtUtilities { 10 | 11 | class OptionPage; 12 | class OptionCategory; 13 | 14 | class QT_UTILITIES_EXPORT OptionCategoryModel : public QAbstractListModel { 15 | Q_OBJECT 16 | Q_PROPERTY(QList categories READ categories WRITE setCategories) 17 | public: 18 | explicit OptionCategoryModel(QObject *parent = nullptr); 19 | explicit OptionCategoryModel(const QList &categories, QObject *parent = nullptr); 20 | ~OptionCategoryModel() override; 21 | 22 | const QList &categories() const; 23 | OptionCategory *category(const QModelIndex &index) const; 24 | OptionCategory *category(int row) const; 25 | void setCategories(const QList &categories); 26 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 27 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 28 | 29 | private Q_SLOTS: 30 | void categoryChangedName(); 31 | void categoryChangedIcon(); 32 | 33 | private: 34 | QList m_categories; 35 | }; 36 | 37 | /*! 38 | * \brief Returns the categories. 39 | * \sa OptionCategoryModel::category() 40 | * \sa OptionCategoryModel::setCategories() 41 | */ 42 | inline const QList &OptionCategoryModel::categories() const 43 | { 44 | return m_categories; 45 | } 46 | 47 | /*! 48 | * \brief Returns the category for the specified model \a index. 49 | * \sa OptionCategoryModel::categories() 50 | * \sa OptionCategoryModel::setCategories() 51 | */ 52 | inline OptionCategory *OptionCategoryModel::category(const QModelIndex &index) const 53 | { 54 | return (index.isValid()) ? category(index.row()) : nullptr; 55 | } 56 | 57 | /*! 58 | * \brief Returns the category for the specified \a row. 59 | * \sa OptionCategoryModel::categories() 60 | * \sa OptionCategoryModel::setCategories() 61 | */ 62 | inline OptionCategory *OptionCategoryModel::category(int row) const 63 | { 64 | return row < m_categories.size() ? m_categories.at(row) : nullptr; 65 | } 66 | } // namespace QtUtilities 67 | 68 | #endif // DIALOGS_OPTIONCATEGORYMODEL_H 69 | -------------------------------------------------------------------------------- /testfiles/setup/releases.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "assets": [], 4 | "body": "v1.7.7-beta1", 5 | "created_at": "2025-05-06T08:50:41Z", 6 | "draft": false, 7 | "html_url": "https://github.com/Martchus/syncthingtray/releases/tag/v1.7.7-beta1", 8 | "name": "v1.7.7-beta1", 9 | "prerelease": true, 10 | "published_at": "2025-05-06T12:08:12Z", 11 | "tag_name": "v1.7.7-beta1" 12 | }, 13 | { 14 | "assets": [], 15 | "body": "v1.7.7-rc1", 16 | "created_at": "2025-05-06T08:50:41Z", 17 | "draft": false, 18 | "html_url": "https://github.com/Martchus/syncthingtray/releases/tag/v1.7.7-rc1", 19 | "name": "v1.7.7-rc1", 20 | "prerelease": true, 21 | "published_at": "2025-05-06T12:08:12Z", 22 | "tag_name": "v1.7.7-rc1" 23 | }, 24 | { 25 | "assets": [], 26 | "body": "v1.7.7-rc2", 27 | "created_at": "2025-05-06T08:50:41Z", 28 | "draft": false, 29 | "html_url": "https://github.com/Martchus/syncthingtray/releases/tag/v1.7.7-rc2", 30 | "name": "v1.7.7-rc2", 31 | "prerelease": true, 32 | "published_at": "2025-05-06T12:08:12Z", 33 | "tag_name": "v1.7.7-rc2" 34 | }, 35 | { 36 | "assets": [], 37 | "body": "v1.8.0-alpha1", 38 | "created_at": "2025-05-06T08:50:41Z", 39 | "draft": true, 40 | "html_url": "https://github.com/Martchus/syncthingtray/releases/tag/v1.8.0-alpha1", 41 | "name": "v1.8.0-alpha1", 42 | "prerelease": true, 43 | "published_at": "2025-05-06T12:08:12Z", 44 | "tag_name": "v1.8.0-alpha1" 45 | }, 46 | { 47 | "assets": [], 48 | "body": "v1.8.0", 49 | "created_at": "2025-05-06T08:50:41Z", 50 | "draft": true, 51 | "html_url": "https://github.com/Martchus/syncthingtray/releases/tag/v1.8.0", 52 | "name": "v1.8.0", 53 | "prerelease": true, 54 | "published_at": "2025-05-06T12:08:12Z", 55 | "tag_name": "v1.8.0" 56 | }, 57 | { 58 | "assets": [], 59 | "body": "v1.7.6", 60 | "created_at": "2025-05-06T08:50:41Z", 61 | "draft": false, 62 | "html_url": "https://github.com/Martchus/syncthingtray/releases/tag/v1.7.6", 63 | "name": "v1.7.6", 64 | "prerelease": false, 65 | "published_at": "2025-05-06T12:08:12Z", 66 | "tag_name": "v1.7.6" 67 | } 68 | ] 69 | -------------------------------------------------------------------------------- /widgets/buttonoverlay.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_BUTTONOVERLAY_H 2 | #define WIDGETS_BUTTONOVERLAY_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | QT_FORWARD_DECLARE_CLASS(QAction) 9 | QT_FORWARD_DECLARE_CLASS(QWidget) 10 | QT_FORWARD_DECLARE_CLASS(QHBoxLayout) 11 | QT_FORWARD_DECLARE_CLASS(QString) 12 | QT_FORWARD_DECLARE_CLASS(QPixmap) 13 | QT_FORWARD_DECLARE_CLASS(QMargins) 14 | QT_FORWARD_DECLARE_CLASS(QRect) 15 | QT_FORWARD_DECLARE_CLASS(QLineEdit) 16 | 17 | namespace QtUtilities { 18 | 19 | class IconButton; 20 | class ClearComboBox; 21 | class ClearSpinBox; 22 | class ClearPlainTextEdit; 23 | class ClearLineEdit; 24 | 25 | class QT_UTILITIES_EXPORT ButtonOverlay { 26 | // allow these derived classes to use private helpers provided by ButtonOverlay 27 | friend class ClearComboBox; 28 | friend class ClearSpinBox; 29 | friend class ClearPlainTextEdit; 30 | friend class ClearLineEdit; 31 | 32 | public: 33 | explicit ButtonOverlay(QWidget *widget); 34 | explicit ButtonOverlay(QWidget *widget, QLineEdit *lineEdit); 35 | virtual ~ButtonOverlay(); 36 | 37 | bool isUsingCustomLayout() const; 38 | QHBoxLayout *buttonLayout(); 39 | bool isClearButtonEnabled() const; 40 | void setClearButtonEnabled(bool enabled); 41 | bool isInfoButtonEnabled() const; 42 | void enableInfoButton(const QPixmap &pixmap, const QString &infoText); 43 | void disableInfoButton(); 44 | void addCustomButton(QWidget *button); 45 | void insertCustomButton(int index, QWidget *button); 46 | void removeCustomButton(QWidget *button); 47 | void addCustomAction(QAction *action); 48 | void insertCustomAction(int index, QAction *action); 49 | void removeCustomAction(QAction *action); 50 | virtual bool isCleared() const; 51 | 52 | protected: 53 | void updateClearButtonVisibility(bool visible); 54 | virtual void handleClearButtonClicked(); 55 | virtual void handleCustomLayoutCreated(); 56 | 57 | private: 58 | void fallbackToUsingCustomLayout(); 59 | QLineEdit *lineEditForWidget() const; 60 | void showInfo(); 61 | void setContentsMarginsFromEditFieldRectAndFrameWidth(const QRect &editFieldRect, int frameWidth, int padding = 0); 62 | 63 | QWidget *m_widget; 64 | QWidget *m_buttonWidget; 65 | QHBoxLayout *m_buttonLayout; 66 | IconButton *m_clearButton; 67 | void *m_infoButtonOrAction; 68 | }; 69 | 70 | } // namespace QtUtilities 71 | 72 | #endif // WIDGETS_BUTTONOVERLAY_H 73 | -------------------------------------------------------------------------------- /widgets/clearplaintextedit.cpp: -------------------------------------------------------------------------------- 1 | #include "./clearplaintextedit.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | namespace QtUtilities { 11 | 12 | /*! 13 | * \class ClearPlainTextEdit 14 | * \brief A QPlainTextEdit with an embedded button for clearing its contents. 15 | */ 16 | 17 | /*! 18 | * \brief Constructs a clear plain text edit. 19 | */ 20 | ClearPlainTextEdit::ClearPlainTextEdit(QWidget *parent) 21 | : QPlainTextEdit(parent) 22 | , ButtonOverlay(viewport()) 23 | { 24 | handleCustomLayoutCreated(); 25 | ButtonOverlay::setClearButtonEnabled(true); 26 | } 27 | 28 | /*! 29 | * \brief Destroys the clear plain text edit. 30 | */ 31 | ClearPlainTextEdit::~ClearPlainTextEdit() 32 | { 33 | } 34 | 35 | /*! 36 | * \brief Updates the visibility of the clear button. 37 | */ 38 | void ClearPlainTextEdit::handleTextChanged() 39 | { 40 | updateClearButtonVisibility(!document()->isEmpty()); 41 | } 42 | 43 | void ClearPlainTextEdit::handleClearButtonClicked() 44 | { 45 | // do no call clear() here to prevent clearing of undo history 46 | QTextCursor cursor(document()); 47 | cursor.select(QTextCursor::Document); 48 | cursor.removeSelectedText(); 49 | } 50 | 51 | void ClearPlainTextEdit::handleCustomLayoutCreated() 52 | { 53 | // set alignment to show buttons in the bottom right corner 54 | ButtonOverlay::buttonLayout()->setAlignment(Qt::AlignBottom | Qt::AlignRight); 55 | const QStyle *const s = style(); 56 | QStyleOptionFrame opt; 57 | opt.initFrom(this); 58 | setContentsMarginsFromEditFieldRectAndFrameWidth(s->subElementRect(QStyle::SE_FrameContents, &opt, this), 59 | s->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, m_widget), s->pixelMetric(QStyle::PM_LayoutVerticalSpacing, &opt, m_widget)); 60 | connect(this, &QPlainTextEdit::textChanged, this, &ClearPlainTextEdit::handleTextChanged); 61 | // ensure button layout is realigned when scrolling 62 | connect(verticalScrollBar(), &QScrollBar::actionTriggered, this, &ClearPlainTextEdit::handleScroll); 63 | connect(this, &QPlainTextEdit::cursorPositionChanged, this, &ClearPlainTextEdit::handleScroll); 64 | } 65 | 66 | void ClearPlainTextEdit::handleScroll() 67 | { 68 | buttonLayout()->update(); 69 | } 70 | 71 | bool ClearPlainTextEdit::isCleared() const 72 | { 73 | return document()->isEmpty(); 74 | } 75 | 76 | } // namespace QtUtilities 77 | -------------------------------------------------------------------------------- /settingsdialog/qtsettings.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOGS_QT_UTILITIES_QTSETTINGS_H 2 | #define DIALOGS_QT_UTILITIES_QTSETTINGS_H 3 | 4 | #include "../global.h" 5 | 6 | #if defined(QT_UTILITIES_GUI_QTWIDGETS) 7 | #include "./optionpage.h" 8 | #endif 9 | 10 | #include 11 | 12 | #include 13 | 14 | QT_FORWARD_DECLARE_CLASS(QFontDialog) 15 | QT_FORWARD_DECLARE_CLASS(QSettings) 16 | 17 | namespace QtUtilities { 18 | 19 | class OptionCategory; 20 | struct QtSettingsData; 21 | 22 | #if defined(QT_UTILITIES_GUI_QTWIDGETS) 23 | BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE_CUSTOM_CTOR(QtAppearanceOptionPage) 24 | public: 25 | explicit QtAppearanceOptionPage(QtSettingsData &settings, QWidget *parentWidget = nullptr); 26 | 27 | private: 28 | DECLARE_SETUP_WIDGETS 29 | QtSettingsData & m_settings; 30 | QFontDialog *m_fontDialog; 31 | END_DECLARE_OPTION_PAGE 32 | 33 | BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE_CUSTOM_CTOR(QtLanguageOptionPage) 34 | public: 35 | explicit QtLanguageOptionPage(QtSettingsData &settings, QWidget *parentWidget = nullptr); 36 | 37 | private: 38 | DECLARE_SETUP_WIDGETS 39 | QtSettingsData & m_settings; 40 | END_DECLARE_OPTION_PAGE 41 | 42 | BEGIN_DECLARE_UI_FILE_BASED_OPTION_PAGE_CUSTOM_CTOR(QtEnvOptionPage) 43 | public: 44 | explicit QtEnvOptionPage(QtSettingsData &settings, QWidget *parentWidget = nullptr); 45 | 46 | private: 47 | DECLARE_SETUP_WIDGETS 48 | QtSettingsData & m_settings; 49 | END_DECLARE_OPTION_PAGE 50 | #endif 51 | 52 | class QT_UTILITIES_EXPORT QtSettings { 53 | public: 54 | QtSettings(); 55 | ~QtSettings(); 56 | 57 | #if defined(QT_UTILITIES_GUI_QTWIDGETS) 58 | void disableNotices(); 59 | void setRetranslatable(bool retranslatable); 60 | #endif 61 | void restore(QSettings &settings); 62 | void save(QSettings &settings) const; 63 | void apply(); 64 | void reapplyDefaultIconTheme(bool isPaletteDark); 65 | void reevaluatePaletteAndDefaultIconTheme(); 66 | bool isPaletteDark(); 67 | bool hasCustomFont() const; 68 | bool hasLocaleChanged() const; 69 | operator QtSettingsData &() const; 70 | 71 | OptionCategory *category(); 72 | 73 | private: 74 | std::unique_ptr m_d; 75 | }; 76 | } // namespace QtUtilities 77 | 78 | #if defined(QT_UTILITIES_GUI_QTWIDGETS) 79 | DECLARE_EXTERN_UI_FILE_BASED_OPTION_PAGE(QtAppearanceOptionPage) 80 | DECLARE_EXTERN_UI_FILE_BASED_OPTION_PAGE(QtLanguageOptionPage) 81 | DECLARE_EXTERN_UI_FILE_BASED_OPTION_PAGE(QtEnvOptionPage) 82 | #endif 83 | 84 | #endif // DIALOGS_QT_UTILITIES_QTSETTINGS_H 85 | -------------------------------------------------------------------------------- /cmake/modules/QtJsProviderConfig.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR) 2 | 3 | # determines the JavaScript provider (either Qt Script or Qt Declarative) 4 | 5 | if (TARGET_CONFIG_DONE) 6 | message(FATAL_ERROR "Can not include QtJsProviderConfig module when targets are already configured.") 7 | endif () 8 | 9 | # configure the specified JavaScript provider 10 | set(JS_PROVIDER 11 | "qml" 12 | CACHE STRING "specifies the JavaScript provider: qml (default), script or none") 13 | if (JS_PROVIDER STREQUAL "script") 14 | set(JS_PROVIDER Script) 15 | set(JS_DEFINITION "${META_PROJECT_VARNAME_UPPER}_USE_SCRIPT") 16 | list(APPEND ADDITIONAL_QT_REPOS "script") 17 | message(STATUS "Using Qt Script as JavaScript provider.") 18 | elseif (JS_PROVIDER STREQUAL "qml") 19 | set(JS_PROVIDER Qml) 20 | set(JS_DEFINITION "${META_PROJECT_VARNAME_UPPER}_USE_JSENGINE") 21 | list(APPEND ADDITIONAL_QT_REPOS "declarative") 22 | message(STATUS "Using Qt QML as JavaScript provider.") 23 | elseif (JS_PROVIDER STREQUAL "none") 24 | set(JS_PROVIDER "") 25 | set(JS_DEFINITION "${META_PROJECT_VARNAME_UPPER}_NO_JSENGINE") 26 | message(STATUS "JavaScript provider has been disabled.") 27 | else () 28 | message(FATAL_ERROR "The specified JavaScript provider '${JS_PROVIDER}' is unknown.") 29 | endif () 30 | 31 | # add header files with some defines/includes to conveniently use the selected provider 32 | if (JS_PROVIDER) 33 | list(APPEND ADDITIONAL_QT_MODULES "${JS_PROVIDER}") 34 | 35 | if (META_JS_SRC_DIR) 36 | set(JS_HEADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${META_JS_SRC_DIR}") 37 | else () 38 | set(JS_HEADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/gui") 39 | endif () 40 | 41 | include(TemplateFinder) 42 | find_template_file("jsdefs.h" QT_UTILITIES JS_DEFS_H_TEMPLATE_FILE) 43 | configure_file( 44 | "${JS_DEFS_H_TEMPLATE_FILE}" "${JS_HEADER_DIR}/jsdefs.h" # simply add this to source to ease inclusion 45 | NEWLINE_STYLE UNIX # since this goes to sources ensure consistency 46 | ) 47 | find_template_file("jsincludes.h" QT_UTILITIES JS_INCLUDES_H_TEMPLATE_FILE) 48 | configure_file( 49 | "${JS_INCLUDES_H_TEMPLATE_FILE}" "${JS_HEADER_DIR}/jsincludes.h" # simply add this to source to ease inclusion 50 | NEWLINE_STYLE UNIX # since this goes to sources ensure consistency 51 | ) 52 | list(APPEND WIDGETS_FILES "${JS_HEADER_DIR}/jsdefs.h" "${JS_HEADER_DIR}/jsincludes.h") 53 | endif () 54 | 55 | list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${JS_DEFINITION}) 56 | -------------------------------------------------------------------------------- /settingsdialog/settingsdialog.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOGS_SETTINGSDIALOG_H 2 | #define DIALOGS_SETTINGSDIALOG_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace QtUtilities { 11 | 12 | class OptionCategoryModel; 13 | class OptionCategoryFilterModel; 14 | class OptionCategory; 15 | class OptionPage; 16 | 17 | namespace Ui { 18 | class SettingsDialog; 19 | } 20 | 21 | class QT_UTILITIES_EXPORT SettingsDialog : public QDialog { 22 | Q_OBJECT 23 | Q_PROPERTY(bool tabBarAlwaysVisible READ isTabBarAlwaysVisible WRITE setTabBarAlwaysVisible) 24 | 25 | public: 26 | explicit SettingsDialog(QWidget *parent = nullptr); 27 | ~SettingsDialog() override; 28 | bool isTabBarAlwaysVisible() const; 29 | void setTabBarAlwaysVisible(bool value); 30 | OptionCategoryModel *categoryModel(); 31 | OptionCategory *category(int categoryIndex) const; 32 | OptionPage *page(int categoryIndex, int pageIndex) const; 33 | void showCategory(OptionCategory *category); 34 | void translateCategory(OptionCategory *category, const std::function &translator); 35 | void setSingleCategory(OptionCategory *singleCategory); 36 | QWidget *cornerWidget(Qt::Corner corner = Qt::TopRightCorner) const; 37 | void setCornerWidget(QWidget *widget, Qt::Corner corner = Qt::TopRightCorner); 38 | void addHeadingWidget(QWidget *widget); 39 | void selectPage(int categoryIndex, int pageIndex); 40 | 41 | public Q_SLOTS: 42 | bool apply(); 43 | void reset(); 44 | 45 | Q_SIGNALS: 46 | void applied(); 47 | void resetted(); 48 | void retranslationRequired(); 49 | 50 | protected: 51 | bool event(QEvent *event) override; 52 | void showEvent(QShowEvent *event) override; 53 | 54 | private Q_SLOTS: 55 | void currentCategoryChanged(const QModelIndex &index); 56 | void updateTabWidget(); 57 | void retranslateTabWidget(); 58 | 59 | private: 60 | std::unique_ptr m_ui; 61 | OptionCategoryModel *m_categoryModel; 62 | OptionCategoryFilterModel *m_categoryFilterModel; 63 | OptionCategory *m_currentCategory; 64 | bool m_tabBarAlwaysVisible; 65 | }; 66 | 67 | /*! 68 | * \brief Returns whether the tab bar is always visible. 69 | * 70 | * The tab bar is always visible by default. 71 | * 72 | * \sa SettingsDialog::setTabBarAlwaysVisible() 73 | */ 74 | inline bool SettingsDialog::isTabBarAlwaysVisible() const 75 | { 76 | return m_tabBarAlwaysVisible; 77 | } 78 | 79 | /*! 80 | * \brief Returns the category model used by the settings dialog to manage the 81 | * categories. 82 | */ 83 | inline OptionCategoryModel *SettingsDialog::categoryModel() 84 | { 85 | return m_categoryModel; 86 | } 87 | } // namespace QtUtilities 88 | 89 | #endif // DIALOGS_SETTINGSDIALOG_H 90 | -------------------------------------------------------------------------------- /misc/compat.h: -------------------------------------------------------------------------------- 1 | #ifndef QT_UTILITIES_COMPAT_H 2 | #define QT_UTILITIES_COMPAT_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 11 | #define QT_UTILITIES_USE_Q_STRING_VIEW 12 | #endif 13 | // note: QStringView has been available since Qt 5.10 but for a consistent ABI/API regardless which 14 | // Qt 5.x version is used it makes sense to stick to QStringRef when using Qt 5. 15 | 16 | #ifdef QT_UTILITIES_USE_Q_STRING_VIEW 17 | #include 18 | #else 19 | #include 20 | #endif 21 | 22 | namespace QtUtilities { 23 | 24 | using Utf16CharType = 25 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 26 | char16_t 27 | #else 28 | ushort 29 | #endif 30 | ; 31 | 32 | using StringView = 33 | #ifdef QT_UTILITIES_USE_Q_STRING_VIEW 34 | QStringView 35 | #else 36 | QStringRef 37 | #endif 38 | ; 39 | 40 | /*! 41 | * \brief Makes either a QStringView or a QStringRef depending on the Qt version. 42 | */ 43 | inline StringView makeStringView(const QString &str) 44 | { 45 | return StringView( 46 | #ifndef QT_UTILITIES_USE_Q_STRING_VIEW 47 | & 48 | #endif 49 | str); 50 | } 51 | 52 | /*! 53 | * \brief Makes either a QStringView or a QStringRef depending on the Qt version, applying "mid()" parameters. 54 | */ 55 | template , std::is_signed, std::is_integral, std::is_signed> 57 | * = nullptr> 58 | inline StringView midRef(const QString &str, PosType1 pos, PosType2 n = -1) 59 | { 60 | #ifdef QT_UTILITIES_USE_Q_STRING_VIEW 61 | return QStringView(str).mid(pos, n); 62 | #else 63 | return str.midRef(pos, n); 64 | #endif 65 | } 66 | 67 | /*! 68 | * \brief Splits \a str into QStringViews, QStringRefs or QStrings depending on the Qt version. 69 | */ 70 | template inline auto splitRef(const QString &str, SplitArgs &&...args) 71 | { 72 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 73 | return QStringView(str).split(std::forward(args)...); 74 | #elif QT_VERSION >= QT_VERSION_CHECK(5, 4, 0) 75 | return str.splitRef(std::forward(args)...); 76 | #else 77 | return str.split(std::forward(args)...); 78 | #endif 79 | } 80 | 81 | } // namespace QtUtilities 82 | 83 | #if (QT_VERSION < QT_VERSION_CHECK(5, 10, 0)) 84 | QT_BEGIN_NAMESPACE 85 | /*! 86 | * \brief Provides a fallback for qEnvironmentVariable() when using old Qt versions. 87 | */ 88 | inline QString qEnvironmentVariable(const char *varName, const QString &defaultValue) 89 | { 90 | const auto val = qgetenv(varName); 91 | return !val.isEmpty() ? QString::fromLocal8Bit(val) : defaultValue; 92 | } 93 | QT_END_NAMESPACE 94 | #endif 95 | 96 | #endif // QT_UTILITIES_COMPAT_H 97 | -------------------------------------------------------------------------------- /settingsdialog/qtenvoptionpage.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | QtUtilities::QtEnvOptionPage 4 | 5 | 6 | 7 | 0 8 | 0 9 | 515 10 | 243 11 | 12 | 13 | 14 | Environment/paths 15 | 16 | 17 | 18 | 19 | 20 | QGroupBox { font-weight: bold }; 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | Additional plugin directory 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Additional icon theme search path 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | Additional translation search path 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | Qt::Vertical 61 | 62 | 63 | 64 | 20 65 | 40 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 75 | true 76 | 77 | 78 | 79 | These settings take effect after restarting the application. 80 | 81 | 82 | Qt::PlainText 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | QtUtilities::PathSelection 91 | QWidget 92 |
widgets/pathselection.h
93 | 1 94 |
95 |
96 | 97 | 98 |
99 | -------------------------------------------------------------------------------- /resources/icons/hicolor/scalable/apps/qtcreator.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /settingsdialog/optioncategory.cpp: -------------------------------------------------------------------------------- 1 | #include "./optioncategory.h" 2 | #include "./optionpage.h" 3 | 4 | #include 5 | #include 6 | 7 | namespace QtUtilities { 8 | 9 | /*! 10 | * \class OptionCategory 11 | * \brief The OptionCategory class wraps associated option pages. 12 | */ 13 | 14 | /*! 15 | * \brief Constructs a option category. 16 | */ 17 | OptionCategory::OptionCategory(QObject *parent) 18 | : QObject(parent) 19 | , m_currentIndex(0) 20 | { 21 | } 22 | 23 | /*! 24 | * \brief Destroys the option category. 25 | */ 26 | OptionCategory::~OptionCategory() 27 | { 28 | qDeleteAll(m_pages); 29 | } 30 | 31 | /*! 32 | * \brief Applies all pages. 33 | * \remarks Pages which have not been shown yet must have not been initialized anyways 34 | * and hence are skipped. 35 | * \sa OptionPage::apply() 36 | */ 37 | bool OptionCategory::applyAllPages() 38 | { 39 | for (OptionPage *page : m_pages) { 40 | if (!page->hasBeenShown()) { 41 | continue; 42 | } 43 | if (!page->apply()) { 44 | return false; 45 | } 46 | } 47 | return true; 48 | } 49 | 50 | /*! 51 | * \brief Resets all pages. 52 | * \remarks Pages which have not been shown yet must have not been initialized anyways 53 | * and hence are skipped. 54 | * \sa OptionPage::reset() 55 | */ 56 | void OptionCategory::resetAllPages() 57 | { 58 | for (OptionPage *page : m_pages) { 59 | if (page->hasBeenShown()) { 60 | page->reset(); 61 | } 62 | } 63 | } 64 | 65 | /*! 66 | * \brief Triggers retranslation of all pages. 67 | * \remarks Has no effect if the pages don't react to the LanguageChange event. 68 | */ 69 | void OptionCategory::retranslateAllPages() 70 | { 71 | auto event = QEvent(QEvent::LanguageChange); 72 | for (auto *const page : m_pages) { 73 | if (page->hasBeenShown()) { 74 | QCoreApplication::sendEvent(page->widget(), &event); 75 | } 76 | } 77 | } 78 | 79 | /*! 80 | * \brief Returns whether the option category matches the specified \a 81 | * searchKeyWord. 82 | */ 83 | bool OptionCategory::matches(const QString &searchKeyWord) const 84 | { 85 | for (OptionPage *page : m_pages) { 86 | if (page->matches(searchKeyWord)) { 87 | return true; 88 | } 89 | } 90 | return false; 91 | } 92 | 93 | /*! 94 | * \brief Assigns the specified \a pages to the category. 95 | * 96 | * Previously assigned pages get deleted. The pagesChanged() signal is emitted. 97 | * The category takes ownership over the given \a pages. 98 | */ 99 | void OptionCategory::assignPages(const QList &pages) 100 | { 101 | qDeleteAll(m_pages); 102 | emit pagesChanged(m_pages = pages); 103 | } 104 | 105 | /*! 106 | * \fn OptionCategory::displayNameChanged() 107 | * \brief Emitted when the display name changed. 108 | */ 109 | 110 | /*! 111 | * \fn OptionCategory::iconChanged() 112 | * \brief Emitted when the icon changed. 113 | */ 114 | 115 | /*! 116 | * \fn OptionCategory::pagesChanged() 117 | * \brief Emitted when the pages changed. 118 | */ 119 | } // namespace QtUtilities 120 | -------------------------------------------------------------------------------- /cmake/modules/QtWebViewProviderConfig.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR) 2 | 3 | # determines the web view provider (either Qt WebKit or Qt WebEngine) 4 | 5 | if (TARGET_CONFIG_DONE) 6 | message(FATAL_ERROR "Can not include QtWebViewProviderConfig module when targets are already configured.") 7 | endif () 8 | 9 | # include required modules 10 | include(QtLinkage) 11 | 12 | # check whether Qt WebEngine is present 13 | find_package("${QT_PACKAGE_PREFIX}WebEngineWidgets" "${META_QT_VERSION}") 14 | set(WEBVIEW_PROVIDER_DEFAULT "none") 15 | if ("${${QT_PACKAGE_PREFIX}WebEngineWidgets_FOUND}") 16 | set(WEBVIEW_PROVIDER_DEFAULT "webengine") 17 | endif () 18 | 19 | # configure the specified web view provider 20 | set(WEBVIEW_PROVIDER 21 | "${WEBVIEW_PROVIDER_DEFAULT}" 22 | CACHE STRING "specifies the web view provider: webengine (default), webkit or none") 23 | if (WEBVIEW_PROVIDER STREQUAL "webkit") 24 | set(WEBVIEW_PROVIDER WebKitWidgets) 25 | set(WEBVIEW_DEFINITION "${META_PROJECT_VARNAME_UPPER}_USE_WEBKIT") 26 | message(STATUS "Using Qt WebKit as web view provider.") 27 | elseif (WEBVIEW_PROVIDER STREQUAL "webengine") 28 | set(WEBVIEW_PROVIDER WebEngineWidgets) 29 | if (META_WEBVIEW_WITH_CORE) 30 | list(APPEND WEBVIEW_PROVIDER WebEngineCore) 31 | endif () 32 | set(WEBVIEW_DEFINITION "${META_PROJECT_VARNAME_UPPER}_USE_WEBENGINE") 33 | list(APPEND ADDITIONAL_QT_REPOS "webengine") 34 | message(STATUS "Using Qt WebEngine as web view provider.") 35 | elseif (WEBVIEW_PROVIDER STREQUAL "none") 36 | set(WEBVIEW_PROVIDER "") 37 | set(WEBVIEW_DEFINITION "${META_PROJECT_VARNAME_UPPER}_NO_WEBVIEW") 38 | message(STATUS "Built-in web view has been disabled.") 39 | else () 40 | message(FATAL_ERROR "The specified web view provider '${WEBVIEW_PROVIDER}' is unknown.") 41 | endif () 42 | 43 | # add header files with some defines/includes to conveniently use the selected provider 44 | if (WEBVIEW_PROVIDER) 45 | list(APPEND ADDITIONAL_QT_MODULES "${WEBVIEW_PROVIDER}") 46 | 47 | if (META_WEBVIEW_SRC_DIR) 48 | set(WEBVIEW_HEADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/${META_WEBVIEW_SRC_DIR}") 49 | else () 50 | set(WEBVIEW_HEADER_DIR "${CMAKE_CURRENT_SOURCE_DIR}/gui") 51 | endif () 52 | 53 | include(TemplateFinder) 54 | find_template_file("webviewdefs.h" QT_UTILITIES WEBVIEWDEFS_H_TEMPLATE_FILE) 55 | configure_file( 56 | "${WEBVIEWDEFS_H_TEMPLATE_FILE}" "${WEBVIEW_HEADER_DIR}/webviewdefs.h" # simply add this to source to ease inclusion 57 | NEWLINE_STYLE UNIX # since this goes to sources ensure consistency 58 | ) 59 | find_template_file("webviewincludes.h" QT_UTILITIES WEBVIEWINCLUDES_H_TEMPLATE_FILE) 60 | configure_file( 61 | "${WEBVIEWINCLUDES_H_TEMPLATE_FILE}" "${WEBVIEW_HEADER_DIR}/webviewincludes.h" # simply add this to source to ease 62 | # inclusion 63 | NEWLINE_STYLE UNIX # since this goes to sources ensure consistency 64 | ) 65 | list(APPEND WIDGETS_FILES "${WEBVIEW_HEADER_DIR}/webviewdefs.h" "${WEBVIEW_HEADER_DIR}/webviewincludes.h") 66 | endif () 67 | 68 | list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${WEBVIEW_DEFINITION}) 69 | -------------------------------------------------------------------------------- /settingsdialog/optioncategory.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOGS_OPTIONSCATEGORY_H 2 | #define DIALOGS_OPTIONSCATEGORY_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)) 11 | Q_MOC_INCLUDE("settingsdialog/optionpage.h") 12 | #endif 13 | 14 | namespace QtUtilities { 15 | 16 | class OptionPage; 17 | 18 | class QT_UTILITIES_EXPORT OptionCategory : public QObject { 19 | Q_OBJECT 20 | Q_PROPERTY(QString displayName READ displayName WRITE setDisplayName NOTIFY displayNameChanged) 21 | Q_PROPERTY(QIcon icon READ icon WRITE setIcon NOTIFY iconChanged) 22 | Q_PROPERTY(QList pages READ pages WRITE assignPages NOTIFY pagesChanged) 23 | 24 | public: 25 | explicit OptionCategory(QObject *parent = nullptr); 26 | ~OptionCategory() override; 27 | 28 | const QString &displayName() const; 29 | void setDisplayName(const QString &displayName); 30 | const QIcon &icon() const; 31 | void setIcon(const QIcon &icon); 32 | const QList &pages() const; 33 | void assignPages(const QList &pages); 34 | bool applyAllPages(); 35 | void resetAllPages(); 36 | void retranslateAllPages(); 37 | bool matches(const QString &searchKeyWord) const; 38 | int currentIndex() const; 39 | void setCurrentIndex(int currentIndex); 40 | 41 | Q_SIGNALS: 42 | void displayNameChanged(const QString &displayName); 43 | void iconChanged(const QIcon &icon); 44 | void pagesChanged(const QList &pages); 45 | 46 | private: 47 | QString m_displayName; 48 | QIcon m_icon; 49 | QList m_pages; 50 | int m_currentIndex; 51 | }; 52 | 53 | /*! 54 | * \brief Returns the display name of the category. 55 | */ 56 | inline const QString &OptionCategory::displayName() const 57 | { 58 | return m_displayName; 59 | } 60 | 61 | /*! 62 | * \brief Sets the display name of the category. 63 | */ 64 | inline void OptionCategory::setDisplayName(const QString &displayName) 65 | { 66 | emit displayNameChanged(m_displayName = displayName); 67 | } 68 | 69 | /*! 70 | * \brief Returns the icon of the category. 71 | */ 72 | inline const QIcon &OptionCategory::icon() const 73 | { 74 | return m_icon; 75 | } 76 | 77 | /*! 78 | * \brief Sets the icon of the category. 79 | */ 80 | inline void OptionCategory::setIcon(const QIcon &icon) 81 | { 82 | emit iconChanged(m_icon = icon); 83 | } 84 | 85 | /*! 86 | * \brief Returns the assigned pages. 87 | */ 88 | inline const QList &OptionCategory::pages() const 89 | { 90 | return m_pages; 91 | } 92 | 93 | /*! 94 | * \brief Returns the index of the currently shown page. 95 | * \remarks The returned index might be invalid/out of range. 96 | * \sa setCurrentIndex() 97 | */ 98 | inline int OptionCategory::currentIndex() const 99 | { 100 | return m_currentIndex; 101 | } 102 | 103 | /*! 104 | * \brief Sets the current index. 105 | * \sa currentIndex() 106 | */ 107 | inline void OptionCategory::setCurrentIndex(int currentIndex) 108 | { 109 | m_currentIndex = currentIndex; 110 | } 111 | } // namespace QtUtilities 112 | 113 | #endif // DIALOGS_OPTIONSCATEGORY_H 114 | -------------------------------------------------------------------------------- /enterpassworddialog/enterpassworddialog.h: -------------------------------------------------------------------------------- 1 | #ifndef DIALOGS_ENTERPASSWORDDIALOG_H 2 | #define DIALOGS_ENTERPASSWORDDIALOG_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | #include 9 | 10 | namespace QtUtilities { 11 | 12 | namespace Ui { 13 | class EnterPasswordDialog; 14 | } 15 | 16 | class QT_UTILITIES_EXPORT EnterPasswordDialog : public QDialog { 17 | Q_OBJECT 18 | Q_PROPERTY(QString userName READ userName) 19 | Q_PROPERTY(QString password READ password) 20 | Q_PROPERTY(QString description READ description WRITE setDescription) 21 | Q_PROPERTY(bool promtForUserName READ promtForUserName WRITE setPromptForUserName) 22 | Q_PROPERTY(bool isVerificationRequired READ isVerificationRequired WRITE setVerificationRequired) 23 | Q_PROPERTY(bool isPasswordRequired READ isPasswordRequired WRITE setPasswordRequired) 24 | Q_PROPERTY(QString instruction READ instruction WRITE setInstruction) 25 | Q_PROPERTY(bool isCapslockPressed READ isCapslockPressed) 26 | 27 | public: 28 | explicit EnterPasswordDialog(QWidget *parent = nullptr); 29 | ~EnterPasswordDialog() override; 30 | const QString &userName() const; 31 | const QString &password() const; 32 | QString description() const; 33 | void setDescription(const QString &description = QString()); 34 | bool promtForUserName() const; 35 | void setPromptForUserName(bool prompt); 36 | bool isVerificationRequired() const; 37 | void setVerificationRequired(bool value); 38 | bool isPasswordRequired() const; 39 | void setPasswordRequired(bool value); 40 | const QString &instruction() const; 41 | void setInstruction(const QString &value); 42 | static bool isCapslockPressed(); 43 | 44 | protected: 45 | bool event(QEvent *event) override; 46 | bool eventFilter(QObject *sender, QEvent *event) override; 47 | 48 | private Q_SLOTS: 49 | void updateShowPassword(); 50 | void confirm(); 51 | void abort(); 52 | 53 | private: 54 | std::unique_ptr m_ui; 55 | QString m_userName; 56 | QString m_password; 57 | QString m_instruction; 58 | bool m_capslockPressed; 59 | }; 60 | 61 | /*! 62 | * \brief Returns the entered user name. 63 | */ 64 | inline const QString &EnterPasswordDialog::userName() const 65 | { 66 | return m_userName; 67 | } 68 | 69 | /*! 70 | * \brief Returns the entered password. 71 | */ 72 | inline const QString &EnterPasswordDialog::password() const 73 | { 74 | return m_password; 75 | } 76 | 77 | /*! 78 | * \brief Returns the instruction text. 79 | * 80 | * The instruction text is displayed at the top of the dialog. 81 | * If the instruction text is empty the default text "Enter the new password" 82 | * or "Enter the password" (depending on whether the verification is required or 83 | * not) displayed. 84 | * 85 | * \sa EnterPasswordDialog::setInstruction() 86 | */ 87 | inline const QString &EnterPasswordDialog::instruction() const 88 | { 89 | return m_instruction; 90 | } 91 | 92 | /*! 93 | * \brief Clears all results and sets the dialog status to QDialog::Rejected. 94 | * 95 | * This private slot is called when m_ui->abortPushButton is clicked. 96 | */ 97 | inline void EnterPasswordDialog::abort() 98 | { 99 | m_password.clear(); 100 | done(QDialog::Rejected); 101 | } 102 | } // namespace QtUtilities 103 | 104 | #endif // DIALOGS_ENTERPASSWORDDIALOG_H 105 | -------------------------------------------------------------------------------- /cmake/modules/QtGuiConfig.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR) 2 | 3 | if (NOT BASIC_PROJECT_CONFIG_DONE) 4 | message(FATAL_ERROR "Before including the QtGuiConfig module, the BasicConfig module must be included.") 5 | endif () 6 | if (QT_CONFIGURED) 7 | message(FATAL_ERROR "The QtGuiConfig module can not be included when Qt usage has already been configured.") 8 | endif () 9 | if (TARGET_CONFIG_DONE) 10 | message(FATAL_ERROR "Can not include QtGuiConfig module when targets are already configured.") 11 | endif () 12 | 13 | if (NOT WIDGETS_GUI AND NOT QUICK_GUI) 14 | message(STATUS "GUI is completely disabled.") 15 | return() 16 | endif () 17 | 18 | list(APPEND ADDITIONAL_QT_MODULES Gui) 19 | 20 | # enable Qt Widgets GUI 21 | if (WIDGETS_GUI) 22 | list(APPEND META_PRIVATE_COMPILE_DEFINITIONS GUI_QTWIDGETS) 23 | list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME_UPPER}_GUI_QTWIDGETS) 24 | list(APPEND WIDGETS_FILES ${WIDGETS_HEADER_FILES} ${WIDGETS_SRC_FILES} ${WIDGETS_RES_FILES} ${WIDGETS_UI_FILES}) 25 | list(APPEND ADDITIONAL_HEADER_FILES ${WIDGETS_HEADER_FILES}) 26 | if (WIDGETS_FILES OR META_HAS_WIDGETS_GUI) 27 | list(APPEND ADDITIONAL_QT_MODULES Widgets) 28 | message(STATUS "Building with Qt Widgets GUI.") 29 | else () 30 | message(STATUS "Qt Widgets GUI is not available.") 31 | endif () 32 | else () 33 | message(STATUS "Building WITHOUT Qt Widgets GUI.") 34 | endif () 35 | 36 | # enable Qt Quick GUI 37 | if (QUICK_GUI) 38 | list(APPEND META_PRIVATE_COMPILE_DEFINITIONS GUI_QTQUICK) 39 | list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME_UPPER}_GUI_QTQUICK) 40 | list(APPEND QML_FILES ${QML_HEADER_FILES} ${QML_SRC_FILES} ${QML_RES_FILES}) 41 | list(APPEND ADDITIONAL_HEADER_FILES ${QML_HEADER_FILES}) 42 | if (QML_FILES OR META_HAS_QUICK_GUI) 43 | list(APPEND ADDITIONAL_QT_MODULES Qml Quick) 44 | list(APPEND ADDITIONAL_QT_REPOS "declarative") 45 | message(STATUS "Building with Qt Quick GUI.") 46 | 47 | # enable QML debugging 48 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 49 | list(APPEND META_PRIVATE_COMPILE_DEFINITIONS QT_QML_DEBUG) 50 | endif () 51 | 52 | # enable Qt Quick Controls 2 53 | if (META_USE_QQC2) 54 | list(APPEND ADDITIONAL_QT_MODULES QuickControls2) 55 | endif () 56 | else () 57 | message(STATUS "Qt Quick GUI is not available.") 58 | endif () 59 | else () 60 | message(STATUS "Building WITHOUT Qt Quick GUI.") 61 | endif () 62 | 63 | # set platform-specific GUI-type 64 | if (WIN32) 65 | # set "GUI-type" to WIN32 to hide console under Windows 66 | set(GUI_TYPE WIN32) 67 | # add option for building CLI-wrapper 68 | option(BUILD_CLI_WRAPPER "whether to build a CLI wrapper" ON) 69 | elseif (APPLE) 70 | # make the GUI application a "bundle" under MacOSX 71 | set(GUI_TYPE MACOSX_BUNDLE) 72 | endif () 73 | 74 | # add source files required by both GUI variants 75 | list(APPEND SRC_FILES ${GUI_SRC_FILES}) 76 | list(APPEND ADDITIONAL_HEADER_FILES ${GUI_HEADER_FILES}) 77 | 78 | # add option for enabling/disabling static Qt plugins 79 | option(SVG_SUPPORT "whether to link against the SVG image format plugin (only relevant when using static Qt)" ON) 80 | option(SVG_ICON_SUPPORT "whether to link against the SVG icon engine (only relevant when using static Qt)" ON) 81 | set(IMAGE_FORMAT_SUPPORT 82 | "Gif;ICO;Jpeg" 83 | CACHE STRING "specifies the image format plugins to link against (only relevant when using static Qt)") 84 | 85 | # always enable the Svg module under Android 86 | if (ANDROID) 87 | list(APPEND ADDITIONAL_QT_MODULES Svg) 88 | endif () 89 | -------------------------------------------------------------------------------- /models/checklistmodel.h: -------------------------------------------------------------------------------- 1 | #ifndef MODELS_CHECKLISTMODEL_H 2 | #define MODELS_CHECKLISTMODEL_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | #include 8 | 9 | QT_FORWARD_DECLARE_CLASS(QSettings) 10 | 11 | namespace QtUtilities { 12 | 13 | class ChecklistModel; 14 | 15 | class QT_UTILITIES_EXPORT ChecklistItem { 16 | friend class ChecklistModel; 17 | 18 | public: 19 | ChecklistItem(const QVariant &id = QVariant(), const QString &label = QString(), Qt::CheckState checked = Qt::Unchecked); 20 | 21 | const QVariant &id() const; 22 | const QString &label() const; 23 | Qt::CheckState checkState() const; 24 | bool isChecked() const; 25 | 26 | private: 27 | QVariant m_id; 28 | QString m_label; 29 | Qt::CheckState m_checkState; 30 | }; 31 | 32 | inline ChecklistItem::ChecklistItem(const QVariant &id, const QString &label, Qt::CheckState checkState) 33 | : m_id(id) 34 | , m_label(label) 35 | , m_checkState(checkState) 36 | { 37 | } 38 | 39 | /*! 40 | * \brief Returns the ID of the item. 41 | */ 42 | inline const QVariant &ChecklistItem::id() const 43 | { 44 | return m_id; 45 | } 46 | 47 | /*! 48 | * \brief Returns the label. 49 | */ 50 | inline const QString &ChecklistItem::label() const 51 | { 52 | return m_label; 53 | } 54 | 55 | /*! 56 | * \brief Returns the check state. 57 | */ 58 | inline Qt::CheckState ChecklistItem::checkState() const 59 | { 60 | return m_checkState; 61 | } 62 | 63 | /*! 64 | * \brief Returns whether the item is checked. 65 | */ 66 | 67 | inline bool ChecklistItem::isChecked() const 68 | { 69 | return m_checkState == Qt::Checked; 70 | } 71 | 72 | class QT_UTILITIES_EXPORT ChecklistModel : public QAbstractListModel { 73 | Q_OBJECT 74 | public: 75 | explicit ChecklistModel(QObject *parent = nullptr); 76 | 77 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 78 | Qt::ItemFlags flags(const QModelIndex &index) const override; 79 | QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; 80 | QMap itemData(const QModelIndex &index) const override; 81 | bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::DisplayRole) override; 82 | bool setItemData(const QModelIndex &index, const QMap &roles) override; 83 | bool setChecked(int row, bool checked); 84 | bool setChecked(int row, Qt::CheckState checked); 85 | virtual QString labelForId(const QVariant &id) const; 86 | Qt::DropActions supportedDropActions() const override; 87 | bool insertRows(int row, int count, const QModelIndex &parent) override; 88 | bool removeRows(int row, int count, const QModelIndex &parent) override; 89 | const QList &items() const; 90 | void setItems(const QList &items); 91 | void restore(QSettings &settings, const QString &name); 92 | void save(QSettings &settings, const QString &name) const; 93 | QVariantList toVariantList() const; 94 | void applyVariantList(const QVariantList &checkedIds); 95 | static constexpr int idRole(); 96 | 97 | private: 98 | QList m_items; 99 | }; 100 | 101 | /*! 102 | * \brief Returns the items. 103 | */ 104 | inline const QList &ChecklistModel::items() const 105 | { 106 | return m_items; 107 | } 108 | 109 | /*! 110 | * \brief Sets the checked state of the specified item. 111 | */ 112 | inline bool ChecklistModel::setChecked(int row, bool checked) 113 | { 114 | return setChecked(row, checked ? Qt::Checked : Qt::Unchecked); 115 | } 116 | 117 | /*! 118 | * \brief Returns the role used to get or set the item ID. 119 | */ 120 | constexpr int ChecklistModel::idRole() 121 | { 122 | return Qt::UserRole + 1; 123 | } 124 | } // namespace QtUtilities 125 | 126 | #endif // MODELS_CHECKLISTMODEL_H 127 | -------------------------------------------------------------------------------- /settingsdialog/qtlanguageoptionpage.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | QtUtilities::QtLanguageOptionPage 4 | 5 | 6 | Localization 7 | 8 | 9 | 10 | .. 11 | 12 | 13 | QGroupBox { font-weight: bold }; 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | false 22 | 23 | 24 | 25 | 0 26 | 0 27 | 28 | 29 | 30 | true 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 60 39 | 0 40 | 41 | 42 | 43 | Locale 44 | 45 | 46 | Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 47 | 48 | 49 | 50 | 51 | 52 | 53 | Use system default 54 | 55 | 56 | 57 | 58 | 59 | 60 | true 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | Qt::RichText 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | Qt::Vertical 80 | 81 | 82 | 83 | 20 84 | 40 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 75 94 | true 95 | 96 | 97 | 98 | These settings take effect after restarting the application. 99 | 100 | 101 | Qt::PlainText 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | localeCheckBox 111 | toggled(bool) 112 | localeComboBox 113 | setDisabled(bool) 114 | 115 | 116 | 159 117 | 39 118 | 119 | 120 | 75 121 | 39 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /widgets/iconbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "./iconbutton.h" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | using namespace CppUtilities; 11 | 12 | namespace QtUtilities { 13 | 14 | /*! 15 | * \class IconButton 16 | * \brief A simple QAbstractButton implementation displaying a QPixmap. 17 | */ 18 | 19 | /*! 20 | * \brief Constructs an icon button. 21 | */ 22 | IconButton::IconButton(QWidget *parent) 23 | : QAbstractButton(parent) 24 | { 25 | setCursor(Qt::ArrowCursor); 26 | setFocusPolicy(Qt::NoFocus); 27 | } 28 | 29 | /*! 30 | * \brief Destroys the icon button. 31 | */ 32 | IconButton::~IconButton() 33 | { 34 | } 35 | 36 | /*! 37 | * \brief Creates an IconButton for the specified \a action. 38 | * \remarks Calling this function on the same action twice with the same \a id yields the 39 | * same instance. 40 | */ 41 | IconButton *IconButton::fromAction(QAction *action, std::uintptr_t id) 42 | { 43 | const auto propertyName = argsToString("iconButton-", id); 44 | const auto existingIconButton = action->property(propertyName.data()); 45 | if (!existingIconButton.isNull()) { 46 | return existingIconButton.value(); 47 | } 48 | auto *const iconButton = new IconButton; 49 | iconButton->assignDataFromAction(action); 50 | action->setProperty(propertyName.data(), QVariant::fromValue(iconButton)); 51 | connect(action, &QAction::changed, iconButton, &IconButton::assignDataFromActionChangedSignal); 52 | connect(iconButton, &IconButton::clicked, action, &QAction::trigger); 53 | return iconButton; 54 | } 55 | 56 | /*! 57 | * \brief Internally called to assign data from a QAction to the icon button. 58 | */ 59 | void IconButton::assignDataFromActionChangedSignal() 60 | { 61 | assignDataFromAction(qobject_cast(QObject::sender())); 62 | } 63 | 64 | /*! 65 | * \brief Internally called to assign data from a QAction to the icon button. 66 | */ 67 | void IconButton::assignDataFromAction(const QAction *action) 68 | { 69 | auto const icon = action->icon(); 70 | const auto sizes = icon.availableSizes(); 71 | const auto text = action->text(); 72 | setPixmap(icon.pixmap(sizes.empty() ? defaultPixmapSize : sizes.front())); 73 | setToolTip(text.isEmpty() ? action->toolTip() : text); 74 | } 75 | 76 | QSize IconButton::sizeHint() const 77 | { 78 | #if QT_VERSION >= 0x050100 79 | const qreal pixmapRatio = m_pixmap.devicePixelRatio(); 80 | #else 81 | const qreal pixmapRatio = 1.0; 82 | #endif 83 | return QSize(static_cast(m_pixmap.width() / pixmapRatio), static_cast(m_pixmap.height() / pixmapRatio)); 84 | } 85 | 86 | void IconButton::paintEvent(QPaintEvent *) 87 | { 88 | #if QT_VERSION >= 0x050100 89 | const qreal pixmapRatio = m_pixmap.devicePixelRatio(); 90 | #else 91 | const qreal pixmapRatio = 1.0; 92 | #endif 93 | auto painter = QStylePainter(this); 94 | auto pixmapRect = QRect(0, 0, static_cast(m_pixmap.width() / pixmapRatio), static_cast(m_pixmap.height() / pixmapRatio)); 95 | pixmapRect.moveCenter(rect().center()); 96 | painter.drawPixmap(pixmapRect, m_pixmap); 97 | if (hasFocus()) { 98 | auto focusOption = QStyleOptionFocusRect(); 99 | focusOption.initFrom(this); 100 | focusOption.rect = pixmapRect; 101 | #ifdef Q_OS_MAC 102 | focusOption.rect.adjust(-4, -4, 4, 4); 103 | painter.drawControl(QStyle::CE_FocusFrame, focusOption); 104 | #else 105 | painter.drawPrimitive(QStyle::PE_FrameFocusRect, focusOption); 106 | #endif 107 | } 108 | } 109 | 110 | void IconButton::keyPressEvent(QKeyEvent *event) 111 | { 112 | QAbstractButton::keyPressEvent(event); 113 | if (!event->modifiers() && (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return)) { 114 | click(); 115 | } 116 | event->accept(); 117 | } 118 | 119 | void IconButton::keyReleaseEvent(QKeyEvent *event) 120 | { 121 | QAbstractButton::keyReleaseEvent(event); 122 | event->accept(); 123 | } 124 | 125 | } // namespace QtUtilities 126 | -------------------------------------------------------------------------------- /settingsdialog/optioncategorymodel.cpp: -------------------------------------------------------------------------------- 1 | #include "./optioncategorymodel.h" 2 | #include "./optioncategory.h" 3 | 4 | #ifdef QT_UTILITIES_GUI_QTWIDGETS 5 | #include 6 | #include 7 | #endif 8 | 9 | #include 10 | 11 | namespace QtUtilities { 12 | 13 | /*! 14 | * \class OptionCategoryModel 15 | * \brief The OptionCategoryModel class is used by SettingsDialog to store and 16 | * display option categories. 17 | */ 18 | 19 | /*! 20 | * \brief Constructs an option category model. 21 | */ 22 | OptionCategoryModel::OptionCategoryModel(QObject *parent) 23 | : QAbstractListModel(parent) 24 | { 25 | } 26 | 27 | /*! 28 | * \brief Constructs an option category model with the specified \a categories. 29 | * \remarks The model takes ownership over the given categories. 30 | */ 31 | OptionCategoryModel::OptionCategoryModel(const QList &categories, QObject *parent) 32 | : QAbstractListModel(parent) 33 | , m_categories(categories) 34 | { 35 | for (OptionCategory *category : std::as_const(m_categories)) { 36 | category->setParent(this); 37 | } 38 | } 39 | 40 | /*! 41 | * \brief Destroys the option category model. 42 | */ 43 | OptionCategoryModel::~OptionCategoryModel() 44 | { 45 | } 46 | 47 | /*! 48 | * \brief Sets the \a categories for the model. 49 | * 50 | * The model takes ownership over the given \a categories. 51 | */ 52 | void OptionCategoryModel::setCategories(const QList &categories) 53 | { 54 | beginResetModel(); 55 | qDeleteAll(m_categories); 56 | m_categories = categories; 57 | for (OptionCategory *const category : std::as_const(m_categories)) { 58 | category->setParent(this); 59 | connect(category, &OptionCategory::displayNameChanged, this, &OptionCategoryModel::categoryChangedName); 60 | connect(category, &OptionCategory::iconChanged, this, &OptionCategoryModel::categoryChangedIcon); 61 | } 62 | endResetModel(); 63 | } 64 | 65 | int OptionCategoryModel::rowCount(const QModelIndex &parent) const 66 | { 67 | return parent.isValid() ? 0 : static_cast(m_categories.size()); 68 | } 69 | 70 | QVariant OptionCategoryModel::data(const QModelIndex &index, int role) const 71 | { 72 | if (!index.isValid() || index.row() >= m_categories.size()) { 73 | return QVariant(); 74 | } 75 | switch (role) { 76 | case Qt::DisplayRole: 77 | return m_categories.at(index.row())->displayName(); 78 | case Qt::DecorationRole: { 79 | const QIcon &icon = m_categories.at(index.row())->icon(); 80 | if (!icon.isNull()) { 81 | return icon.pixmap( 82 | #ifdef QT_UTILITIES_GUI_QTWIDGETS 83 | QApplication::style()->pixelMetric(QStyle::PM_LargeIconSize) 84 | #else 85 | QSize(32, 32) 86 | #endif 87 | ); 88 | } 89 | } 90 | } 91 | return QVariant(); 92 | } 93 | 94 | /*! 95 | * \brief Handles the change of name of a category. 96 | */ 97 | void OptionCategoryModel::categoryChangedName() 98 | { 99 | const auto *const senderCategory = qobject_cast(QObject::sender()); 100 | if (!senderCategory) { 101 | return; 102 | } 103 | for (int i = 0, end = static_cast(m_categories.size()); i < end; ++i) { 104 | if (senderCategory == m_categories.at(i)) { 105 | QModelIndex index = this->index(i); 106 | emit dataChanged(index, index, QVector({ Qt::DisplayRole })); 107 | } 108 | } 109 | } 110 | 111 | /*! 112 | * \brief Handles the a changed icon of a category. 113 | */ 114 | void OptionCategoryModel::categoryChangedIcon() 115 | { 116 | const auto *const senderCategory = qobject_cast(QObject::sender()); 117 | if (!senderCategory) { 118 | return; 119 | } 120 | for (int i = 0, end = static_cast(m_categories.size()); i < end; ++i) { 121 | if (senderCategory == m_categories.at(i)) { 122 | QModelIndex index = this->index(i); 123 | emit dataChanged(index, index, QVector({ Qt::DecorationRole })); 124 | } 125 | } 126 | } 127 | } // namespace QtUtilities 128 | -------------------------------------------------------------------------------- /settingsdialog/optionpage.cpp: -------------------------------------------------------------------------------- 1 | #include "./optionpage.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace QtUtilities { 13 | 14 | /*! 15 | * \class OptionPage 16 | * \brief The OptionPage class is the base class for SettingsDialog pages. 17 | * 18 | * The specified \a parentWindow might be used by some implementations as parent 19 | * when showing dialogs. 20 | */ 21 | 22 | /*! 23 | * \brief Constructs a option page. 24 | */ 25 | OptionPage::OptionPage(QWidget *parentWindow) 26 | : m_parentWindow(parentWindow) 27 | , m_shown(false) 28 | , m_keywordsInitialized(false) 29 | { 30 | } 31 | 32 | /*! 33 | * \brief Destroys the option page. 34 | */ 35 | OptionPage::~OptionPage() 36 | { 37 | } 38 | 39 | /*! 40 | * \brief Returns the widget for the option page. 41 | * 42 | * If the widget has not been constructed yet, a new widget will be 43 | * constructed using the OptionPage::setupWidget() method and the 44 | * current configuration is applied. 45 | * 46 | * The option page keeps ownership over the returned widget. 47 | */ 48 | QWidget *OptionPage::widget() 49 | { 50 | if (!m_widget) { 51 | m_widget.reset(setupWidget()); // ensure widget has been created 52 | } 53 | if (!m_shown) { 54 | m_shown = true; 55 | reset(); // show current configuration if not shown yet 56 | } 57 | return m_widget.get(); 58 | } 59 | 60 | /*! 61 | * \brief Returns whether the pages matches the specified 62 | * \a searchKeyWord. 63 | */ 64 | bool OptionPage::matches(const QString &searchKeyWord) 65 | { 66 | if (searchKeyWord.isEmpty()) { 67 | return true; 68 | } 69 | if (!m_keywordsInitialized) { 70 | if (!m_widget) { 71 | m_widget.reset(setupWidget()); // ensure widget has been created 72 | } 73 | m_keywords << m_widget->windowTitle(); 74 | // find common subwidgets 75 | for (const QLabel *label : m_widget->findChildren()) 76 | m_keywords << label->text(); 77 | for (const QCheckBox *checkbox : m_widget->findChildren()) 78 | m_keywords << checkbox->text(); 79 | for (const QRadioButton *checkbox : m_widget->findChildren()) 80 | m_keywords << checkbox->text(); 81 | for (const QPushButton *pushButton : m_widget->findChildren()) 82 | m_keywords << pushButton->text(); 83 | for (const QGroupBox *groupBox : m_widget->findChildren()) 84 | m_keywords << groupBox->title(); 85 | m_keywordsInitialized = true; 86 | } 87 | for (const QString &keyword : std::as_const(m_keywords)) 88 | if (keyword.contains(searchKeyWord, Qt::CaseInsensitive)) 89 | return true; 90 | return false; 91 | } 92 | 93 | /*! 94 | * \brief Emits the paletteChanged() signal. 95 | */ 96 | bool OptionPageWidget::event(QEvent *event) 97 | { 98 | switch (event->type()) { 99 | case QEvent::PaletteChange: 100 | emit paletteChanged(); 101 | break; 102 | case QEvent::LanguageChange: 103 | emit retranslationRequired(); 104 | break; 105 | default:; 106 | } 107 | return QWidget::event(event); 108 | } 109 | 110 | /*! 111 | * \fn OptionPage::apply() 112 | * \brief Applies altered settings. 113 | * \remarks 114 | * The SettingsDialog and any other classes/functions of this library will not call 115 | * this method if the option page has not been shown yet. Hence it is (no longer) necessary 116 | * to use OptionPage::hasBeenShown() to check whether the page has been initialized 117 | * yet. 118 | */ 119 | 120 | /*! 121 | * \fn OptionPage::reset() 122 | * \brief Discards altered settings and resets relevant widgets. 123 | * \remarks 124 | * The SettingsDialog and any other classes/functions of this library will not call 125 | * this method if the option page has not been shown yet. Hence it is (no longer) necessary 126 | * to use OptionPage::hasBeenShown() to check whether the page has been initialized 127 | * yet. 128 | */ 129 | 130 | /*! 131 | * \fn OptionPage::setupWidget() 132 | * \brief Creates the widget for the page. Called on the first invocation of 133 | * widget(). 134 | */ 135 | } // namespace QtUtilities 136 | -------------------------------------------------------------------------------- /resources/qtconfigarguments.h: -------------------------------------------------------------------------------- 1 | #ifndef APPLICATION_UTILITIES_QTCONFIGARGUMENTS_H 2 | #define APPLICATION_UTILITIES_QTCONFIGARGUMENTS_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | 8 | #ifdef QT_UTILITIES_GUI_QTQUICK 9 | #include 10 | #include 11 | 12 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 13 | #if defined(PLATFORM_ANDROID) 14 | #define QT_UTILITIES_DEFAULT_QQC2_STYLE "Material" 15 | #elif defined(PLATFORM_WINDOWS) 16 | #include 17 | #include 18 | #define QT_UTILITIES_DEFAULT_QQC2_STYLE "Universal" 19 | #define QT_UTILITIES_DEFAULT_QQC2_STYLE_QSTRING \ 20 | (QLibraryInfo::version() >= QVersionNumber(6, 8, 0) \ 21 | && QOperatingSystemVersion::current() >= QOperatingSystemVersion(QOperatingSystemVersion::Windows11) \ 22 | ? QStringLiteral("FluentWinUI3") \ 23 | : QStringLiteral("Universal")) 24 | #endif 25 | #else 26 | #if defined(PLATFORM_ANDROID) 27 | #define QT_UTILITIES_DEFAULT_QQC2_STYLE "material" 28 | #elif defined(PLATFORM_WINDOWS) 29 | #define QT_UTILITIES_DEFAULT_QQC2_STYLE "universal" 30 | #endif 31 | #endif 32 | #if defined(QT_UTILITIES_DEFAULT_QQC2_STYLE) && !defined(QT_UTILITIES_DEFAULT_QQC2_STYLE_QSTRING) 33 | #define QT_UTILITIES_DEFAULT_QQC2_STYLE_QSTRING QStringLiteral(QT_UTILITIES_DEFAULT_QQC2_STYLE) 34 | #endif 35 | 36 | #endif 37 | 38 | namespace CppUtilities { 39 | 40 | class QT_UTILITIES_EXPORT QtConfigArguments { 41 | public: 42 | QtConfigArguments(); 43 | 44 | Argument &qtWidgetsGuiArg(); 45 | Argument &qtQuickGuiArg(); 46 | Argument &languageArg(); 47 | 48 | bool areQtGuiArgsPresent() const; 49 | void applySettings(bool preventApplyingDefaultFont = false) const; 50 | #ifdef QT_UTILITIES_GUI_QTQUICK 51 | void applySettingsForQuickGui() const; 52 | #endif 53 | 54 | private: 55 | Argument m_qtWidgetsGuiArg; 56 | Argument m_qtQuickGuiArg; 57 | Argument m_lngArg; 58 | Argument m_qmlDebuggerArg; 59 | Argument m_widgetsStyleArg; 60 | Argument m_quickControls2StyleArg; 61 | Argument m_iconThemeArg; 62 | Argument m_fontArg; 63 | Argument m_libraryPathsArg; 64 | Argument m_platformThemeArg; 65 | Argument m_sceneGraphRenderLoopArg; 66 | }; 67 | 68 | /*! 69 | * \brief Returns the argument for the Qt Widgets GUI. 70 | */ 71 | inline Argument &QtConfigArguments::qtWidgetsGuiArg() 72 | { 73 | return m_qtWidgetsGuiArg; 74 | } 75 | 76 | /*! 77 | * \brief Returns the argument for the Qt Quick GUI. 78 | */ 79 | inline Argument &QtConfigArguments::qtQuickGuiArg() 80 | { 81 | return m_qtQuickGuiArg; 82 | } 83 | 84 | /*! 85 | * \brief Returns the language argument. 86 | */ 87 | inline Argument &QtConfigArguments::languageArg() 88 | { 89 | return m_lngArg; 90 | } 91 | 92 | /*! 93 | * \brief Returns whether at least one of the GUI arguments is present. 94 | */ 95 | inline bool QtConfigArguments::areQtGuiArgsPresent() const 96 | { 97 | return m_qtWidgetsGuiArg.isPresent() || m_qtQuickGuiArg.isPresent(); 98 | } 99 | 100 | #ifdef QT_UTILITIES_GUI_QTQUICK 101 | /*! 102 | * \brief Applies settings the for Qt Quick GUI. 103 | */ 104 | inline void QtConfigArguments::applySettingsForQuickGui() const 105 | { 106 | if (m_quickControls2StyleArg.isPresent()) { 107 | QQuickStyle::setStyle(QString::fromLocal8Bit(m_quickControls2StyleArg.values().front())); 108 | } 109 | #ifdef QT_UTILITIES_DEFAULT_QQC2_STYLE 110 | else if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) { 111 | if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_MATERIAL_THEME")) { 112 | qputenv("QT_QUICK_CONTROLS_MATERIAL_THEME", "System"); 113 | } 114 | if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_UNIVERSAL_THEME")) { 115 | qputenv("QT_QUICK_CONTROLS_UNIVERSAL_THEME", "System"); 116 | } 117 | QQuickStyle::setStyle(QT_UTILITIES_DEFAULT_QQC2_STYLE_QSTRING); 118 | } 119 | #endif // QT_UTILITIES_DEFAULT_QQC2_STYLE 120 | } 121 | #endif // QT_UTILITIES_GUI_QTQUICK 122 | 123 | } // namespace CppUtilities 124 | 125 | #endif // APPLICATION_UTILITIES_QTCONFIGARGUMENTS_H 126 | 127 | #ifdef QT_CONFIG_ARGUMENTS 128 | #undef QT_CONFIG_ARGUMENTS 129 | #endif 130 | #define QT_CONFIG_ARGUMENTS CppUtilities::QtConfigArguments 131 | -------------------------------------------------------------------------------- /resources/resources.h: -------------------------------------------------------------------------------- 1 | #ifndef APPLICATION_UTILITIES_RESOURCES_H 2 | #define APPLICATION_UTILITIES_RESOURCES_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | QT_FORWARD_DECLARE_CLASS(QString) 14 | QT_FORWARD_DECLARE_CLASS(QSettings) 15 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 16 | QT_FORWARD_DECLARE_CLASS(QStringList) 17 | #endif 18 | 19 | /*! 20 | * \brief Sets the base name of the desktop entry for this application from buildsystem-provided meta-data. 21 | * \remarks 22 | * - This is done as part of SET_QT_APPLICATION_INFO and thus normally doesn't need to be invoked individually. 23 | * - This macro is still experimental. 24 | */ 25 | #define SET_QT_DESKTOP_FILE_NAME 26 | #if defined(Q_OS_LINUX) && defined(qGuiApp) && defined(APP_ID) 27 | #undef SET_QT_DESKTOP_FILE_NAME 28 | #define SET_QT_DESKTOP_FILE_NAME QGuiApplication::setDesktopFileName(QStringLiteral(APP_ID)); 29 | #endif 30 | 31 | /*! 32 | * \brief Sets the application meta data in the QCoreApplication singleton and attributes commonly used 33 | * within my applications. 34 | * \sa ::QtUtilities::setupCommonQtApplicationAttributes() 35 | */ 36 | #define SET_QT_APPLICATION_INFO \ 37 | QCoreApplication::setOrganizationName(QStringLiteral(APP_AUTHOR)); \ 38 | QCoreApplication::setOrganizationDomain(QStringLiteral(APP_DOMAIN)); \ 39 | QCoreApplication::setApplicationName(QStringLiteral(APP_NAME)); \ 40 | QCoreApplication::setApplicationVersion(QStringLiteral(APP_VERSION)); \ 41 | SET_QT_DESKTOP_FILE_NAME \ 42 | ::QtUtilities::setupCommonQtApplicationAttributes() 43 | 44 | /*! 45 | * \brief Loads translations for Qt, other dependencies and the application. 46 | */ 47 | #define LOAD_QT_TRANSLATIONS \ 48 | QtUtilities::TranslationFiles::loadQtTranslationFile(QT_TRANSLATION_FILES); \ 49 | QtUtilities::TranslationFiles::loadApplicationTranslationFile(QStringLiteral(PROJECT_CONFIG_NAME), APP_SPECIFIC_QT_TRANSLATION_FILES) 50 | 51 | namespace QtUtilities { 52 | 53 | namespace QtUtilitiesResources { 54 | 55 | QT_UTILITIES_EXPORT void init(); 56 | QT_UTILITIES_EXPORT void cleanup(); 57 | } // namespace QtUtilitiesResources 58 | 59 | namespace TranslationFiles { 60 | 61 | QT_UTILITIES_EXPORT QString &additionalTranslationFilePath(); 62 | QT_UTILITIES_EXPORT void loadQtTranslationFile(std::initializer_list repositoryNames); 63 | QT_UTILITIES_EXPORT void loadQtTranslationFile(std::initializer_list repositoryNames, const QString &localeName); 64 | QT_UTILITIES_EXPORT void loadApplicationTranslationFile(const QString &configName, const QString &applicationName); 65 | QT_UTILITIES_EXPORT void loadApplicationTranslationFile(const QString &configName, const QString &applicationName, const QString &localeName); 66 | QT_UTILITIES_EXPORT void loadApplicationTranslationFile(const QString &configName, const std::initializer_list &applicationNames); 67 | QT_UTILITIES_EXPORT void loadApplicationTranslationFile( 68 | const QString &configName, const std::initializer_list &applicationNames, const QString &localeName); 69 | QT_UTILITIES_EXPORT void clearTranslationFiles(); 70 | } // namespace TranslationFiles 71 | 72 | namespace ApplicationInstances { 73 | 74 | #if defined(QT_UTILITIES_GUI_QTWIDGETS) 75 | QT_UTILITIES_EXPORT bool hasWidgetsApp(); 76 | #endif 77 | #if defined(QT_UTILITIES_GUI_QTWIDGETS) || defined(QT_UTILITIES_GUI_QTQUICK) 78 | QT_UTILITIES_EXPORT bool hasGuiApp(); 79 | #endif 80 | QT_UTILITIES_EXPORT bool hasCoreApp(); 81 | } // namespace ApplicationInstances 82 | 83 | QT_UTILITIES_EXPORT void setupCommonQtApplicationAttributes(); 84 | QT_UTILITIES_EXPORT std::unique_ptr getSettings(const QString &organization, const QString &application = QString()); 85 | QT_UTILITIES_EXPORT QString errorMessageForSettings(const QSettings &settings); 86 | QT_UTILITIES_EXPORT void loadSettingsWithLogging(QSettings &settings); 87 | QT_UTILITIES_EXPORT void saveSettingsWithLogging(QSettings &settings); 88 | QT_UTILITIES_EXPORT void deletePipelineCacheIfNeeded(); 89 | 90 | } // namespace QtUtilities 91 | 92 | #endif // APPLICATION_UTILITIES_RESOURCES_H 93 | -------------------------------------------------------------------------------- /tests/dbusnotification.cpp: -------------------------------------------------------------------------------- 1 | #include "../misc/dbusnotification.h" 2 | 3 | #include "../misc/disablewarningsmoc.h" 4 | 5 | #include "resources/config.h" 6 | 7 | #include 8 | #include 9 | 10 | using namespace QtUtilities; 11 | 12 | /*! 13 | * \brief The DBusNotificationTests class tests the DBusNotification class. 14 | */ 15 | class DBusNotificationTests : public QObject { 16 | Q_OBJECT 17 | 18 | private Q_SLOTS: 19 | void smokeTest(); 20 | void semiAutomaticTest(); 21 | }; 22 | 23 | static void dummy(DBusNotification::Capabilities &&) 24 | { 25 | } 26 | const static auto callback = std::function(&dummy); 27 | 28 | /*! 29 | * \brief Runs some basic functions of DBusNotification (c'tor, d'tor, some accessors, ...) but doesn't really check 30 | * whether it works. 31 | * \remarks This test should pass regardless whether DBus-notifications are actually available. Hence it avoids any checks 32 | * which depend on that and just checks whether certain function don't lead to crashes. 33 | */ 34 | void DBusNotificationTests::smokeTest() 35 | { 36 | DBusNotification::isAvailable(); 37 | DBusNotification::queryCapabilities(callback); 38 | DBusNotification n(QStringLiteral("Smoke test"), NotificationIcon::Warning, 100); 39 | QVERIFY2(!n.isVisible(), "not immediately visible"); 40 | QCOMPARE(n.title(), QStringLiteral("Smoke test")); 41 | n.setApplicationName(QStringLiteral(APP_NAME " tests; " APP_VERSION)); 42 | QCOMPARE(n.applicationName(), QStringLiteral(APP_NAME " tests; " APP_VERSION)); 43 | n.show(QStringLiteral("Some message")); // will emit an error if not available 44 | n.isVisible(); 45 | n.hide(); 46 | QCOMPARE(n.message(), QStringLiteral("Some message")); 47 | n.update(QStringLiteral("Another message")); 48 | n.hide(); 49 | if (n.isVisible()) { 50 | QSignalSpy closedSpy(&n, &DBusNotification::closed); 51 | closedSpy.wait(); 52 | } 53 | } 54 | 55 | /*! 56 | * \brief Runs a semi-automatic test to verify whether DBusNotification works for real. 57 | * \remarks This test needs a daemon for D-Bus notifications running and requires manual user interaction. It is therefore 58 | * skipped unless an environment variable is set. 59 | */ 60 | void DBusNotificationTests::semiAutomaticTest() 61 | { 62 | const auto envValue = qgetenv(PROJECT_VARNAME_UPPER "_ENABLE_SEMI_AUTOMATIC_NOTIFICATION_TESTS"); 63 | auto envValueIsInt = false; 64 | if (envValue.isEmpty() || (envValue.toInt(&envValueIsInt) == 0 && envValueIsInt)) { 65 | QSKIP("Set the environment variable " PROJECT_VARNAME_UPPER "_ENABLE_SEMI_AUTOMATIC_NOTIFICATION_TESTS to run " 66 | "the semi-automatic D-Bus notification test."); 67 | } 68 | 69 | QVERIFY2(DBusNotification::isAvailable(), "D-Bus notifications are available"); 70 | 71 | DBusNotification n(QStringLiteral("Semi-automatic test"), NotificationIcon::Information, 10000); 72 | QString clickedAction, error; 73 | const auto actionConnection 74 | = connect(&n, &DBusNotification::actionInvoked, [&clickedAction](const QString &actionName) { clickedAction = actionName; }); 75 | const auto errorConnection 76 | = connect(&n, &DBusNotification::error, [&error]() { error = QStringLiteral("error occurred (TODO: pass an error message here)"); }); 77 | n.setApplicationName(QStringLiteral(APP_NAME " tests; " APP_VERSION)); 78 | n.show(QStringLiteral("Some message; will append more lines later")); 79 | for (auto i = 1; i <= 10; ++i) { 80 | n.update(QStringLiteral("Yet another line, should be displayed in the same notification as previous message (%1)").arg(i)); 81 | QTest::qWait(100); 82 | } 83 | QCOMPARE(error, QString()); 84 | n.setImage(QIcon::fromTheme(QStringLiteral("document-open")).pixmap(64).toImage()); 85 | n.setTitle(n.title() + QStringLiteral(" - click action to continue")); 86 | n.setActions(QStringList({ QStringLiteral("fail"), QStringLiteral("Let test fail"), QStringLiteral("pass"), QStringLiteral("Let test pass") })); 87 | QSignalSpy actionInvokedSpy(&n, &DBusNotification::actionInvoked); 88 | n.update(QStringLiteral("Click on \"Let test pass\" to continue within 10 seconds")); 89 | actionInvokedSpy.wait(10000); 90 | QCOMPARE(clickedAction, QStringLiteral("pass")); 91 | QSignalSpy closedSpy(&n, &DBusNotification::closed); 92 | n.setTimeout(5000); 93 | n.setActions(QStringList()); 94 | n.update(QStringLiteral("Waiting for message to close (will close automatically in 5 seconds)")); 95 | closedSpy.wait(8000); 96 | 97 | QCOMPARE(error, QString()); 98 | disconnect(actionConnection); 99 | disconnect(errorConnection); 100 | } 101 | 102 | QT_UTILITIES_DISABLE_WARNINGS_FOR_MOC_INCLUDE 103 | QTEST_MAIN(DBusNotificationTests) 104 | #include "dbusnotification.moc" 105 | -------------------------------------------------------------------------------- /misc/desktoputils.cpp: -------------------------------------------------------------------------------- 1 | #include "./desktoputils.h" 2 | 3 | #include 4 | #include 5 | 6 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) 7 | #include 8 | #include 9 | #endif 10 | 11 | #ifdef Q_OS_WIN32 12 | #include 13 | #endif 14 | 15 | #if defined(Q_OS_ANDROID) && (QT_VERSION >= QT_VERSION_CHECK(6, 4, 0)) 16 | #define QT_UTILITIES_OPEN_VIA_ACTIVITY 17 | #include 18 | #include 19 | #include 20 | #endif 21 | 22 | namespace QtUtilities { 23 | 24 | /*! 25 | * \brief Shows the specified file or directory using the default file browser. 26 | * \remarks 27 | * - The specified \a path must *not* be specified as URL. (The conversion to a URL suitable for 28 | * QDesktopServices::openUrl() is the main purpose of this function). 29 | * - The Qt documentation suggests to use 30 | * `QDesktopServices::openUrl(QUrl("file:///C:/Documents and Settings/All Users/Desktop", QUrl::TolerantMode));` 31 | * under Windows. However, that does not work if the path contains a '#'. It is also better to use 32 | * QUrl::DecodedMode to prevent QUrl from interpreting any of the paths characters in a special way. 33 | * - Tries to open the path via the activity method `openPath()` under Android so that method can be implemented 34 | * to override the behavior if needed. The method must take a string and return a boolean. If it does not exist 35 | * the usual QDesktopServices::openUrl() is done. 36 | */ 37 | bool openLocalFileOrDir(const QString &path) 38 | { 39 | #ifdef QT_UTILITIES_OPEN_VIA_ACTIVITY 40 | if (const auto context = QNativeInterface::QAndroidApplication::context(); context.isValid()) { 41 | auto env = QJniEnvironment(); 42 | if (auto openPathMethod = env.findMethod(context.objectClass(), "openPath", "(Ljava/lang/String;)Z")) { 43 | return env->CallBooleanMethod(context.object(), openPathMethod, QJniObject::fromString(path).object()) == JNI_TRUE; 44 | } 45 | } 46 | #endif 47 | 48 | QUrl url(QStringLiteral("file://")); 49 | 50 | #ifdef Q_OS_WIN32 51 | // replace backslashes with regular slashes 52 | QString tmp(path); 53 | tmp.replace(QChar('\\'), QChar('/')); 54 | 55 | // add a slash before the drive letter of an absolute path 56 | if (QFileInfo(path).isAbsolute()) { 57 | tmp = QStringLiteral("/") + tmp; 58 | } 59 | url.setPath(tmp, QUrl::DecodedMode); 60 | 61 | #else 62 | url.setPath(path, QUrl::DecodedMode); 63 | #endif 64 | return QDesktopServices::openUrl(url); 65 | } 66 | 67 | /*! 68 | * \brief Returns whether \a palette is dark. 69 | */ 70 | bool isPaletteDark(const QPalette &palette) 71 | { 72 | return palette.color(QPalette::WindowText).lightness() > palette.color(QPalette::Window).lightness(); 73 | } 74 | 75 | /*! 76 | * \brief Returns whether dark mode is enabled. 77 | * \remarks Whether dark mode is enabled can only be determined on Qt 6.5 or newer and only on certain 78 | * platforms. If it cannot be determined, std::nullopt is returned. 79 | */ 80 | std::optional isDarkModeEnabled() 81 | { 82 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) 83 | if (const auto *const styleHints = QGuiApplication::styleHints()) { 84 | const auto colorScheme = styleHints->colorScheme(); 85 | if (colorScheme != Qt::ColorScheme::Unknown) { 86 | return colorScheme == Qt::ColorScheme::Dark; 87 | } 88 | } 89 | #endif 90 | return std::nullopt; 91 | } 92 | 93 | /*! 94 | * \brief Invokes the specified callback when the color scheme changed. 95 | * 96 | * The first argument of the callback will be whether the color scheme is dark now (Qt::ColorScheme::Dark). 97 | * The callback is invoked immediately by this function unless \a invokeImmediately is set to false. 98 | * 99 | * \remarks Whether dark mode is enabled can only be determined on Qt 6.5 or newer and only on certain 100 | * platforms. If it cannot be determined the \a darkModeChangedCallback is never invoked. 101 | */ 102 | QMetaObject::Connection onDarkModeChanged(std::function &&darkModeChangedCallback, QObject *context, bool invokeImmediately) 103 | { 104 | #if (QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)) 105 | if (const auto *const styleHints = QGuiApplication::styleHints()) { 106 | if (invokeImmediately) { 107 | darkModeChangedCallback(styleHints->colorScheme() == Qt::ColorScheme::Dark); 108 | } 109 | return QObject::connect(styleHints, &QStyleHints::colorSchemeChanged, context, 110 | [handler = std::move(darkModeChangedCallback)](Qt::ColorScheme colorScheme) { return handler(colorScheme == Qt::ColorScheme::Dark); }); 111 | } 112 | #else 113 | Q_UNUSED(darkModeChangedCallback) 114 | Q_UNUSED(context) 115 | Q_UNUSED(invokeImmediately) 116 | #endif 117 | return QMetaObject::Connection(); 118 | } 119 | 120 | } // namespace QtUtilities 121 | -------------------------------------------------------------------------------- /widgets/pathselection.cpp: -------------------------------------------------------------------------------- 1 | #include "./pathselection.h" 2 | #include "./clearlineedit.h" 3 | 4 | #include "../misc/desktoputils.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #ifndef QT_NO_CONTEXTMENU 16 | #include 17 | #endif 18 | 19 | #include 20 | #include 21 | 22 | using namespace std; 23 | 24 | namespace QtUtilities { 25 | 26 | /*! 27 | * \class PathSelection 28 | * \brief A QLineEdit with a QPushButton next to it which allows to select 29 | * file/directory via QFileDialog. 30 | */ 31 | 32 | QCompleter *PathSelection::s_completer = nullptr; 33 | 34 | /*! 35 | * \brief Constructs a path selection widget. 36 | */ 37 | PathSelection::PathSelection(QWidget *parent) 38 | : QWidget(parent) 39 | , m_lineEdit(new ClearLineEdit(this)) 40 | , m_button(new QPushButton(this)) 41 | , m_customDialog(nullptr) 42 | , m_customMode(QFileDialog::Directory) 43 | { 44 | if (!s_completer) { 45 | s_completer = new QCompleter; 46 | s_completer->setCompletionMode(QCompleter::UnfilteredPopupCompletion); 47 | auto *const fileSystemModel = new QFileSystemModel(s_completer); 48 | fileSystemModel->setRootPath(QString()); 49 | s_completer->setModel(fileSystemModel); 50 | } 51 | 52 | m_lineEdit->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); 53 | m_lineEdit->installEventFilter(this); 54 | m_lineEdit->setCompleter(s_completer); 55 | m_button->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); 56 | setTexts(); 57 | 58 | auto *const layout = new QHBoxLayout(this); 59 | layout->setSpacing(3); 60 | layout->setContentsMargins(0, 0, 0, 0); 61 | layout->addWidget(m_lineEdit); 62 | layout->addWidget(m_button); 63 | setLayout(layout); 64 | 65 | connect(m_button, &QPushButton::clicked, this, &PathSelection::showFileDialog); 66 | } 67 | 68 | bool PathSelection::event(QEvent *event) 69 | { 70 | switch (event->type()) { 71 | case QEvent::LanguageChange: 72 | setTexts(); 73 | break; 74 | default:; 75 | } 76 | return QWidget::event(event); 77 | } 78 | 79 | bool PathSelection::eventFilter(QObject *obj, QEvent *event) 80 | { 81 | #ifndef QT_NO_CONTEXTMENU 82 | if (obj == m_lineEdit) { 83 | switch (event->type()) { 84 | case QEvent::ContextMenu: { 85 | unique_ptr menu(m_lineEdit->createStandardContextMenu()); 86 | menu->addSeparator(); 87 | connect(menu->addAction(QIcon::fromTheme(QStringLiteral("document-open")), tr("Select ...")), &QAction::triggered, this, 88 | &PathSelection::showFileDialog); 89 | QFileInfo fileInfo(m_lineEdit->text()); 90 | if (fileInfo.exists()) { 91 | if (fileInfo.isFile()) { 92 | connect(menu->addAction(QIcon::fromTheme(QStringLiteral("system-run")), tr("Open")), &QAction::triggered, 93 | bind(&openLocalFileOrDir, m_lineEdit->text())); 94 | } else if (fileInfo.isDir()) { 95 | connect(menu->addAction(QIcon::fromTheme(QStringLiteral("system-file-manager")), tr("Explore")), &QAction::triggered, 96 | bind(&openLocalFileOrDir, m_lineEdit->text())); 97 | } 98 | } 99 | menu->exec(static_cast(event)->globalPos()); 100 | } 101 | return true; 102 | default:; 103 | } 104 | } 105 | #endif 106 | return QWidget::eventFilter(obj, event); 107 | } 108 | 109 | void PathSelection::showFileDialog() 110 | { 111 | QString directory; 112 | QFileInfo fileInfo(m_lineEdit->text()); 113 | if (fileInfo.exists()) { 114 | if (fileInfo.isFile()) { 115 | directory = fileInfo.absoluteDir().absolutePath(); 116 | } else { 117 | directory = fileInfo.absolutePath(); 118 | } 119 | } 120 | if (m_customDialog) { 121 | m_customDialog->setDirectory(directory); 122 | if (m_customDialog->exec() == QFileDialog::Accepted) { 123 | m_lineEdit->selectAll(); 124 | m_lineEdit->insert(m_customDialog->selectedFiles().join(SEARCH_PATH_SEP_CHAR)); 125 | } 126 | } else { 127 | QFileDialog dialog(this); 128 | dialog.setDirectory(directory); 129 | dialog.setFileMode(m_customMode); 130 | if (window()) { 131 | dialog.setWindowTitle(tr("Select path") % QStringLiteral(" - ") % window()->windowTitle()); 132 | } else { 133 | dialog.setWindowTitle(tr("Select path")); 134 | } 135 | if (dialog.exec() == QFileDialog::Accepted) { 136 | m_lineEdit->selectAll(); 137 | m_lineEdit->insert(dialog.selectedFiles().join(SEARCH_PATH_SEP_CHAR)); 138 | } 139 | } 140 | } 141 | 142 | void PathSelection::setTexts() 143 | { 144 | m_button->setText(tr("Select ...")); 145 | } 146 | 147 | } // namespace QtUtilities 148 | -------------------------------------------------------------------------------- /paletteeditor/paletteeditor.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | QtUtilities::PaletteEditor 4 | 5 | 6 | 7 | 0 8 | 0 9 | 365 10 | 409 11 | 12 | 13 | 14 | 15 | 0 16 | 0 17 | 18 | 19 | 20 | Edit Palette 21 | 22 | 23 | 24 | 6 25 | 26 | 27 | 9 28 | 29 | 30 | 9 31 | 32 | 33 | 9 34 | 35 | 36 | 9 37 | 38 | 39 | 40 | 41 | 42 | 0 43 | 0 44 | 45 | 46 | 47 | 48 | 16777215 49 | 16777215 50 | 51 | 52 | 53 | Tune Palette 54 | 55 | 56 | 57 | 9 58 | 59 | 60 | 9 61 | 62 | 63 | 9 64 | 65 | 66 | 9 67 | 68 | 69 | 6 70 | 71 | 72 | 73 | 74 | 75 | 0 76 | 200 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Show Detai&ls 85 | 86 | 87 | 88 | 89 | 90 | 91 | &Compute Details 92 | 93 | 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | Quick 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 0 110 | 0 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | Qt::Horizontal 125 | 126 | 127 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | QtUtilities::ColorButton 136 | QToolButton 137 |
paletteeditor/colorbutton.h
138 |
139 |
140 | 141 | 142 | 143 | buttonBox 144 | accepted() 145 | QtUtilities::PaletteEditor 146 | accept() 147 | 148 | 149 | 180 150 | 331 151 | 152 | 153 | 134 154 | 341 155 | 156 | 157 | 158 | 159 | buttonBox 160 | rejected() 161 | QtUtilities::PaletteEditor 162 | reject() 163 | 164 | 165 | 287 166 | 329 167 | 168 | 169 | 302 170 | 342 171 | 172 | 173 | 174 | 175 |
176 | -------------------------------------------------------------------------------- /paletteeditor/paletteeditor.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_PALETTEEDITOR_H 2 | #define WIDGETS_PALETTEEDITOR_H 3 | 4 | #include "../global.h" 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | QT_FORWARD_DECLARE_CLASS(QListView) 12 | QT_FORWARD_DECLARE_CLASS(QLabel) 13 | 14 | namespace QtUtilities { 15 | 16 | class ColorButton; 17 | 18 | namespace Ui { 19 | class PaletteEditor; 20 | } 21 | 22 | /*! 23 | * \brief The PaletteEditor class provides a dialog to customize a QPalette. 24 | * 25 | * This is taken from 26 | * qttools/src/designer/src/components/propertyeditor/paletteeditor.cpp. 27 | * In contrast to the original version this version doesn't provide a preview. 28 | */ 29 | class QT_UTILITIES_EXPORT PaletteEditor : public QDialog { 30 | Q_OBJECT 31 | Q_PROPERTY(QPalette palette READ palette WRITE setPalette) 32 | public: 33 | PaletteEditor(QWidget *parent); 34 | ~PaletteEditor() override; 35 | 36 | static QPalette getPalette(QWidget *parent, const QPalette &init = QPalette(), const QPalette &parentPal = QPalette(), int *result = nullptr); 37 | 38 | QPalette palette() const; 39 | void setPalette(const QPalette &palette); 40 | void setPalette(const QPalette &palette, const QPalette &parentPalette); 41 | 42 | protected: 43 | bool event(QEvent *event) override; 44 | 45 | private Q_SLOTS: 46 | void buildPalette(); 47 | void paletteChanged(const QPalette &palette); 48 | void handleComputeRadioClicked(); 49 | void handleDetailsRadioClicked(); 50 | void load(); 51 | void save(); 52 | 53 | private: 54 | void updateStyledButton(); 55 | 56 | QPalette::ColorGroup currentColorGroup() const 57 | { 58 | return m_currentColorGroup; 59 | } 60 | 61 | std::unique_ptr m_ui; 62 | QPalette m_editPalette; 63 | QPalette m_parentPalette; 64 | QPalette::ColorGroup m_currentColorGroup; 65 | class PaletteModel *m_paletteModel; 66 | bool m_modelUpdated; 67 | bool m_paletteUpdated; 68 | bool m_compute; 69 | }; 70 | 71 | /*! 72 | * \brief The PaletteModel class is used by PaletteEditor. 73 | */ 74 | class QT_UTILITIES_EXPORT PaletteModel : public QAbstractTableModel { 75 | Q_OBJECT 76 | Q_PROPERTY(QPalette::ColorRole colorRole READ colorRole) 77 | public: 78 | explicit PaletteModel(QObject *parent = nullptr); 79 | 80 | int rowCount(const QModelIndex &parent = QModelIndex()) const override; 81 | int columnCount(const QModelIndex &parent = QModelIndex()) const override; 82 | QVariant data(const QModelIndex &index, int role) const override; 83 | bool setData(const QModelIndex &index, const QVariant &value, int role) override; 84 | Qt::ItemFlags flags(const QModelIndex &index) const override; 85 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 86 | 87 | QPalette getPalette() const; 88 | void setPalette(const QPalette &palette, const QPalette &parentPalette); 89 | 90 | QPalette::ColorRole colorRole() const 91 | { 92 | return QPalette::NoRole; 93 | } 94 | void setCompute(bool on) 95 | { 96 | m_compute = on; 97 | } 98 | 99 | Q_SIGNALS: 100 | void paletteChanged(const QPalette &palette); 101 | 102 | private: 103 | QPalette::ColorGroup columnToGroup(int index) const; 104 | int groupToColumn(QPalette::ColorGroup group) const; 105 | 106 | QPalette m_palette; 107 | QPalette m_parentPalette; 108 | QMap m_roleNames; 109 | bool m_compute; 110 | }; 111 | 112 | /*! 113 | * \brief The BrushEditor class is used by PaletteEditor. 114 | */ 115 | class QT_UTILITIES_EXPORT BrushEditor : public QWidget { 116 | Q_OBJECT 117 | 118 | public: 119 | explicit BrushEditor(QWidget *parent = nullptr); 120 | 121 | void setBrush(const QBrush &brush); 122 | QBrush brush() const; 123 | bool changed() const; 124 | 125 | Q_SIGNALS: 126 | void changed(QWidget *widget); 127 | 128 | private Q_SLOTS: 129 | void brushChanged(); 130 | 131 | private: 132 | ColorButton *m_button; 133 | bool m_changed; 134 | }; 135 | 136 | /*! 137 | * \brief The RoleEditor class is used by PaletteEditor. 138 | */ 139 | class QT_UTILITIES_EXPORT RoleEditor : public QWidget { 140 | Q_OBJECT 141 | public: 142 | explicit RoleEditor(QWidget *parent = nullptr); 143 | 144 | void setLabel(const QString &label); 145 | void setEdited(bool on); 146 | bool edited() const; 147 | 148 | Q_SIGNALS: 149 | void changed(QWidget *widget); 150 | 151 | private Q_SLOTS: 152 | void emitResetProperty(); 153 | 154 | private: 155 | QLabel *m_label; 156 | bool m_edited; 157 | }; 158 | 159 | /*! 160 | * \brief The ColorDelegate class is used by PaletteEditor. 161 | */ 162 | class QT_UTILITIES_EXPORT ColorDelegate : public QItemDelegate { 163 | Q_OBJECT 164 | 165 | public: 166 | explicit ColorDelegate(QObject *parent = nullptr); 167 | 168 | QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const override; 169 | 170 | void setEditorData(QWidget *ed, const QModelIndex &index) const override; 171 | void setModelData(QWidget *ed, QAbstractItemModel *model, const QModelIndex &index) const override; 172 | 173 | void updateEditorGeometry(QWidget *ed, const QStyleOptionViewItem &option, const QModelIndex &index) const override; 174 | 175 | void paint(QPainter *painter, const QStyleOptionViewItem &opt, const QModelIndex &index) const override; 176 | QSize sizeHint(const QStyleOptionViewItem &opt, const QModelIndex &index) const override; 177 | }; 178 | } // namespace QtUtilities 179 | 180 | #endif // WIDGETS_PALETTEEDITOR_H 181 | -------------------------------------------------------------------------------- /settingsdialog/settingsdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | QtUtilities::SettingsDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 515 10 | 311 11 | 12 | 13 | 14 | Settings 15 | 16 | 17 | 18 | 0 19 | 20 | 21 | 0 22 | 23 | 24 | 0 25 | 26 | 27 | 0 28 | 29 | 30 | 0 31 | 32 | 33 | 34 | 35 | 36 | 3 37 | 38 | 39 | 40 | 41 | -1 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 140 50 | 16777215 51 | 52 | 53 | 54 | QAbstractItemView::SelectRows 55 | 56 | 57 | 58 | 32 59 | 32 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 0 71 | 0 72 | 73 | 74 | 75 | No category selected 76 | 77 | 78 | true 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 140 89 | 16777215 90 | 91 | 92 | 93 | Filter 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 0 105 | 0 106 | 107 | 108 | 109 | 110 | 3 111 | 112 | 113 | 6 114 | 115 | 116 | 117 | 118 | Qt::Horizontal 119 | 120 | 121 | 122 | 168 123 | 20 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | background: none; 132 | 133 | 134 | Abort 135 | 136 | 137 | 138 | .. 139 | 140 | 141 | 142 | 143 | 144 | 145 | background: none; 146 | 147 | 148 | Apply 149 | 150 | 151 | 152 | .. 153 | 154 | 155 | 156 | 157 | 158 | 159 | OK 160 | 161 | 162 | 163 | .. 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | QtUtilities::ClearLineEdit 175 | QLineEdit 176 |
widgets/clearlineedit.h
177 |
178 |
179 | 180 | 181 |
182 | -------------------------------------------------------------------------------- /tests/setup.cpp: -------------------------------------------------------------------------------- 1 | #include "../setup/updater.h" 2 | 3 | #include "../misc/disablewarningsmoc.h" 4 | 5 | #include "resources/config.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | #include 21 | 22 | using namespace QtUtilities; 23 | 24 | class SetupTests : public QObject { 25 | Q_OBJECT 26 | 27 | private Q_SLOTS: 28 | void initTestCase(); 29 | void testUpdateNotifierReleaseChecking(); 30 | void testUpdateNotifierReleaseDistinction(); 31 | 32 | private: 33 | CppUtilities::TestApplication m_app; 34 | double m_timeoutFactor = 1.0; 35 | }; 36 | 37 | void SetupTests::initTestCase() 38 | { 39 | CppUtilities::applicationInfo.url = "https://github.com/Martchus/syncthingtray"; 40 | CppUtilities::applicationInfo.version = "1.4.0"; 41 | 42 | if (!qEnvironmentVariableIsSet(PROJECT_VARNAME_UPPER "_UPDATER_VERBOSE")) { 43 | qputenv(PROJECT_VARNAME_UPPER "_UPDATER_VERBOSE", "1"); 44 | } 45 | 46 | if (const auto timeoutFactorEnv = qgetenv(PROJECT_VARNAME_UPPER "_TEST_TIMEOUT_FACTOR"); !timeoutFactorEnv.isEmpty()) { 47 | try { 48 | m_timeoutFactor = CppUtilities::stringToNumber(timeoutFactorEnv.data()); 49 | qDebug() << "using timeout factor: " << m_timeoutFactor; 50 | } catch (const CppUtilities::ConversionException &) { 51 | qDebug() << "ignoring invalid " PROJECT_VARNAME_UPPER "_TEST_TIMEOUT_FACTOR"; 52 | } 53 | } 54 | } 55 | 56 | void SetupTests::testUpdateNotifierReleaseChecking() 57 | { 58 | auto settings = QSettings(); 59 | auto networkAccessManager = QNetworkAccessManager(); 60 | auto *const diskCache = new QNetworkDiskCache(&networkAccessManager); 61 | auto cacheDirectory = CppUtilities::testDirPath("setup"); 62 | diskCache->setCacheDirectory(QString::fromLocal8Bit(cacheDirectory.data(), static_cast(cacheDirectory.size()))); 63 | networkAccessManager.setCache(diskCache); 64 | auto updateHandler = UpdateHandler(&settings, &networkAccessManager); 65 | auto &updateNotifier = *updateHandler.notifier(); 66 | if (!updateNotifier.isSupported()) { 67 | qDebug() << "skipping: UpdateNotifier() is not supported"; 68 | return; 69 | } 70 | if (qEnvironmentVariableIntValue(PROJECT_VARNAME_UPPER "_TEST_ONLINE")) { 71 | updateHandler.setCacheLoadControl(QNetworkRequest::AlwaysNetwork); 72 | } else { 73 | updateHandler.setCacheLoadControl(QNetworkRequest::AlwaysCache); 74 | } 75 | 76 | auto eventLoop = QEventLoop(); 77 | auto timeout = QTimer(); 78 | timeout.setInterval(static_cast(5000 * m_timeoutFactor)); 79 | timeout.setSingleShot(true); 80 | 81 | auto newVersionFromSignal = QString(); 82 | updateNotifier.checkForUpdate(); 83 | 84 | QObject::connect(&updateNotifier, &UpdateNotifier::inProgressChanged, &eventLoop, &QEventLoop::quit); 85 | QObject::connect(&updateNotifier, &UpdateNotifier::updateAvailable, &updateNotifier, 86 | [&newVersionFromSignal](const QString &version, const QString &additionalInfo) { 87 | Q_UNUSED(additionalInfo) 88 | newVersionFromSignal = version; 89 | }); 90 | QObject::connect(&timeout, &QTimer::timeout, &eventLoop, &QEventLoop::quit); 91 | 92 | timeout.start(); 93 | eventLoop.exec(); 94 | timeout.stop(); 95 | 96 | QCOMPARE(updateNotifier.error(), QString()); 97 | qDebug() << "download URL: " << updateNotifier.downloadUrl(); 98 | QCOMPARE(updateNotifier.downloadUrl().host(), QStringLiteral("github.com")); 99 | QVERIFY2(updateNotifier.downloadUrl().path().startsWith(QLatin1String("/Martchus/syncthingtray/releases/download/")), 100 | "download URL contains expected path"); 101 | QCOMPARE(updateNotifier.signatureUrl().path(), updateNotifier.downloadUrl().path() + QStringLiteral(".sig")); 102 | QVERIFY2(updateNotifier.isUpdateAvailable(), "update considered available"); 103 | QVERIFY2(!updateNotifier.isInProgress(), "not timed out"); 104 | QVERIFY2(!updateNotifier.newVersion().isEmpty(), "new version assigned"); 105 | QVERIFY2(!newVersionFromSignal.isEmpty(), "updateAvailable() was emitted with non-empty version"); 106 | QCOMPARE(updateNotifier.newVersion(), newVersionFromSignal); 107 | QVERIFY2(!QVersionNumber::fromString(newVersionFromSignal).isNull(), "version is parsable"); 108 | } 109 | 110 | void SetupTests::testUpdateNotifierReleaseDistinction() 111 | { 112 | auto settings = QSettings(); 113 | auto updateNotifier = UpdateNotifier(); 114 | if (!updateNotifier.isSupported()) { 115 | qDebug() << "skipping: UpdateNotifier() is not supported"; 116 | return; 117 | } 118 | using namespace CppUtilities; 119 | const auto jsonData = QByteArray::fromStdString(readFile(testFilePath("setup/releases.json"))); 120 | 121 | // only regular release considered by default 122 | updateNotifier.supplyNewReleaseData(jsonData); 123 | QCOMPARE(updateNotifier.error(), QString()); 124 | QCOMPARE(updateNotifier.newVersion(), QStringLiteral("1.7.6")); 125 | 126 | // pre-release considered if flags set acordingly 127 | // rc2 wins over rc1 and beta1 128 | updateNotifier.setFlags(UpdateCheckFlags::IncludePreReleases); 129 | updateNotifier.supplyNewReleaseData(jsonData); 130 | QCOMPARE(updateNotifier.error(), QString()); 131 | QCOMPARE(updateNotifier.newVersion(), QStringLiteral("1.7.7-rc2")); 132 | 133 | // draft release considered if flags set accordingly 134 | // regular release tag wins over alpha1 135 | updateNotifier.setFlags(UpdateCheckFlags::IncludeDrafts | UpdateCheckFlags::IncludePreReleases); 136 | updateNotifier.supplyNewReleaseData(jsonData); 137 | QCOMPARE(updateNotifier.error(), QString()); 138 | QCOMPARE(updateNotifier.newVersion(), QStringLiteral("1.8.0")); 139 | } 140 | 141 | QT_UTILITIES_DISABLE_WARNINGS_FOR_MOC_INCLUDE 142 | QTEST_MAIN(SetupTests) 143 | #include "setup.moc" 144 | -------------------------------------------------------------------------------- /paletteeditor/colorbutton.cpp: -------------------------------------------------------------------------------- 1 | #include "./colorbutton.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace QtUtilities { 11 | 12 | /*! 13 | * \cond 14 | */ 15 | 16 | class ColorButtonPrivate { 17 | ColorButton *q_ptr; 18 | Q_DECLARE_PUBLIC(ColorButton) 19 | public: 20 | QColor m_color; 21 | #ifndef QT_NO_DRAGANDDROP 22 | QColor m_dragColor; 23 | QPoint m_dragStart; 24 | bool m_dragging; 25 | #endif 26 | bool m_backgroundCheckered; 27 | 28 | void slotEditColor(); 29 | QColor shownColor() const; 30 | QPixmap generatePixmap() const; 31 | }; 32 | 33 | void ColorButtonPrivate::slotEditColor() 34 | { 35 | const QColor newColor = QColorDialog::getColor(m_color, q_ptr, QString(), QColorDialog::ShowAlphaChannel); 36 | if (!newColor.isValid() || newColor == q_ptr->color()) 37 | return; 38 | q_ptr->setColor(newColor); 39 | } 40 | 41 | QColor ColorButtonPrivate::shownColor() const 42 | { 43 | #ifndef QT_NO_DRAGANDDROP 44 | if (m_dragging) 45 | return m_dragColor; 46 | #endif 47 | return m_color; 48 | } 49 | 50 | QPixmap ColorButtonPrivate::generatePixmap() const 51 | { 52 | QPixmap pix(24, 24); 53 | 54 | int pixSize = 20; 55 | QBrush br(shownColor()); 56 | 57 | QPixmap pm(2 * pixSize, 2 * pixSize); 58 | QPainter pmp(&pm); 59 | pmp.fillRect(0, 0, pixSize, pixSize, Qt::lightGray); 60 | pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::lightGray); 61 | pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::darkGray); 62 | pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::darkGray); 63 | pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, shownColor()); 64 | br = QBrush(pm); 65 | 66 | QPainter p(&pix); 67 | int corr = 1; 68 | QRect r = pix.rect().adjusted(corr, corr, -corr, -corr); 69 | p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); 70 | p.fillRect(r, br); 71 | 72 | p.fillRect(r.width() / 4 + corr, r.height() / 4 + corr, r.width() / 2, r.height() / 2, QColor(shownColor().rgb())); 73 | p.drawRect(pix.rect().adjusted(0, 0, -1, -1)); 74 | 75 | return pix; 76 | } 77 | 78 | /*! 79 | * \endcond 80 | */ 81 | 82 | ColorButton::ColorButton(QWidget *parent) 83 | : QToolButton(parent) 84 | , d_ptr(new ColorButtonPrivate) 85 | { 86 | d_ptr->q_ptr = this; 87 | d_ptr->m_dragging = false; 88 | d_ptr->m_backgroundCheckered = true; 89 | 90 | setAcceptDrops(true); 91 | 92 | connect(this, SIGNAL(clicked()), this, SLOT(slotEditColor())); 93 | setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred)); 94 | } 95 | 96 | ColorButton::~ColorButton() 97 | { 98 | } 99 | 100 | void ColorButton::setColor(const QColor &color) 101 | { 102 | if (d_ptr->m_color == color) 103 | return; 104 | update(); 105 | emit colorChanged(d_ptr->m_color = color); 106 | } 107 | 108 | QColor ColorButton::color() const 109 | { 110 | return d_ptr->m_color; 111 | } 112 | 113 | void ColorButton::setBackgroundCheckered(bool checkered) 114 | { 115 | if (d_ptr->m_backgroundCheckered == checkered) 116 | return; 117 | d_ptr->m_backgroundCheckered = checkered; 118 | update(); 119 | } 120 | 121 | bool ColorButton::isBackgroundCheckered() const 122 | { 123 | return d_ptr->m_backgroundCheckered; 124 | } 125 | 126 | void ColorButton::paintEvent(QPaintEvent *event) 127 | { 128 | QToolButton::paintEvent(event); 129 | if (!isEnabled()) 130 | return; 131 | 132 | const int pixSize = 10; 133 | QBrush br(d_ptr->shownColor()); 134 | if (d_ptr->m_backgroundCheckered) { 135 | QPixmap pm(2 * pixSize, 2 * pixSize); 136 | QPainter pmp(&pm); 137 | pmp.fillRect(0, 0, pixSize, pixSize, Qt::white); 138 | pmp.fillRect(pixSize, pixSize, pixSize, pixSize, Qt::white); 139 | pmp.fillRect(0, pixSize, pixSize, pixSize, Qt::black); 140 | pmp.fillRect(pixSize, 0, pixSize, pixSize, Qt::black); 141 | pmp.fillRect(0, 0, 2 * pixSize, 2 * pixSize, d_ptr->shownColor()); 142 | br = QBrush(pm); 143 | } 144 | 145 | QPainter p(this); 146 | const int corr = 4; 147 | QRect r = rect().adjusted(corr, corr, -corr, -corr); 148 | p.setBrushOrigin((r.width() % pixSize + pixSize) / 2 + corr, (r.height() % pixSize + pixSize) / 2 + corr); 149 | p.fillRect(r, br); 150 | 151 | const QColor frameColor1(0, 0, 0, 26); 152 | p.setPen(frameColor1); 153 | p.drawRect(r.adjusted(1, 1, -2, -2)); 154 | const QColor frameColor2(0, 0, 0, 51); 155 | p.setPen(frameColor2); 156 | p.drawRect(r.adjusted(0, 0, -1, -1)); 157 | } 158 | 159 | void ColorButton::mousePressEvent(QMouseEvent *event) 160 | { 161 | #ifndef QT_NO_DRAGANDDROP 162 | if (event->button() == Qt::LeftButton) 163 | d_ptr->m_dragStart = event->pos(); 164 | #endif 165 | QToolButton::mousePressEvent(event); 166 | } 167 | 168 | void ColorButton::mouseMoveEvent(QMouseEvent *event) 169 | { 170 | #ifndef QT_NO_DRAGANDDROP 171 | if (event->buttons() & Qt::LeftButton && (d_ptr->m_dragStart - event->pos()).manhattanLength() > QApplication::startDragDistance()) { 172 | auto *const mime = new QMimeData; 173 | mime->setColorData(color()); 174 | auto *const drg = new QDrag(this); 175 | drg->setMimeData(mime); 176 | drg->setPixmap(d_ptr->generatePixmap()); 177 | setDown(false); 178 | event->accept(); 179 | drg->exec(Qt::CopyAction); 180 | return; 181 | } 182 | #endif 183 | QToolButton::mouseMoveEvent(event); 184 | } 185 | 186 | #ifndef QT_NO_DRAGANDDROP 187 | void ColorButton::dragEnterEvent(QDragEnterEvent *event) 188 | { 189 | const QMimeData *mime = event->mimeData(); 190 | if (!mime->hasColor()) 191 | return; 192 | 193 | event->accept(); 194 | d_ptr->m_dragColor = qvariant_cast(mime->colorData()); 195 | d_ptr->m_dragging = true; 196 | update(); 197 | } 198 | 199 | void ColorButton::dragLeaveEvent(QDragLeaveEvent *event) 200 | { 201 | event->accept(); 202 | d_ptr->m_dragging = false; 203 | update(); 204 | } 205 | 206 | void ColorButton::dropEvent(QDropEvent *event) 207 | { 208 | event->accept(); 209 | d_ptr->m_dragging = false; 210 | if (d_ptr->m_dragColor == color()) 211 | return; 212 | setColor(d_ptr->m_dragColor); 213 | } 214 | #endif 215 | } // namespace QtUtilities 216 | 217 | #include "moc_colorbutton.cpp" 218 | -------------------------------------------------------------------------------- /misc/recentmenumanager.cpp: -------------------------------------------------------------------------------- 1 | #include "./recentmenumanager.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | namespace QtUtilities { 12 | 13 | /*! 14 | * \class RecentMenuManager 15 | * \brief The RecentMenuManager class manages the entries for a "recently opened 16 | * files" menu. 17 | */ 18 | 19 | /*! 20 | * \brief Constructs a new recent menu manager. 21 | * \param menu Specifies the QMenu instance to operate with. 22 | * \param parent Specifies the parent QObject; might be nullptr. 23 | * \remarks 24 | * - Menu title and icon are set within the constructor. 25 | * - The current menu entries are cleared. 26 | * - The menu entries shouldn't be manipulated manually by the caller till the 27 | * manager is destructed. 28 | * - The manager does not take ownership over \a menu. 29 | */ 30 | RecentMenuManager::RecentMenuManager(QMenu *menu, QObject *parent) 31 | : QObject(parent) 32 | , m_menu(menu) 33 | { 34 | m_menu->clear(); 35 | m_menu->setTitle(tr("&Recent")); 36 | m_menu->setIcon(QIcon::fromTheme(QStringLiteral("document-open-recent"))); 37 | m_sep = m_menu->addSeparator(); 38 | m_clearAction = m_menu->addAction(QIcon::fromTheme(QStringLiteral("edit-clear")), tr("&Clear list"), this, &RecentMenuManager::clearEntries); 39 | } 40 | 41 | /*! 42 | * \brief Restores the specified entries. 43 | */ 44 | void RecentMenuManager::restore(const QStringList &savedEntries) 45 | { 46 | QAction *action = nullptr; 47 | for (const QString &path : savedEntries) { 48 | if (!path.isEmpty()) { 49 | action = new QAction(path, m_menu); 50 | action->setProperty("file_path", path); 51 | m_menu->insertAction(m_sep, action); 52 | connect(action, &QAction::triggered, this, &RecentMenuManager::handleActionTriggered); 53 | } 54 | } 55 | if (action) { 56 | m_menu->actions().front()->setShortcut(QKeySequence(Qt::Key_F6)); 57 | m_menu->setEnabled(true); 58 | } 59 | } 60 | 61 | /*! 62 | * \brief Saves the current entries. 63 | */ 64 | QStringList RecentMenuManager::save() 65 | { 66 | QStringList existingEntires; 67 | QList entryActions = m_menu->actions(); 68 | existingEntires.reserve(entryActions.size()); 69 | for (const QAction *action : entryActions) { 70 | QVariant path = action->property("file_path"); 71 | if (!path.isNull()) { 72 | existingEntires << path.toString(); 73 | } 74 | } 75 | return existingEntires; 76 | } 77 | 78 | /*! 79 | * \brief Ensures an entry for the specified \a path is present and the first 80 | * entry in the list. 81 | */ 82 | void RecentMenuManager::addEntry(const QString &path) 83 | { 84 | QList existingEntries = m_menu->actions(); 85 | QAction *entry = nullptr; 86 | // remove shortcut from existing entries 87 | for (QAction *existingEntry : existingEntries) { 88 | existingEntry->setShortcut(QKeySequence()); 89 | // check whether existing entry matches entry to add 90 | if (existingEntry->property("file_path").toString() == path) { 91 | entry = existingEntry; 92 | break; 93 | } 94 | } 95 | if (!entry) { 96 | // remove old entries to have never more than 10 entries 97 | for (auto i = existingEntries.size() - 1; i > 8; --i) { 98 | delete existingEntries[i]; 99 | } 100 | existingEntries = m_menu->actions(); 101 | // create new action 102 | entry = new QAction(path, this); 103 | entry->setProperty("file_path", path); 104 | connect(entry, &QAction::triggered, this, &RecentMenuManager::handleActionTriggered); 105 | } else { 106 | // remove existing action (will be inserted again as first action) 107 | m_menu->removeAction(entry); 108 | } 109 | // add shortcut for new entry 110 | entry->setShortcut(QKeySequence(Qt::Key_F6)); 111 | // ensure menu is enabled 112 | m_menu->setEnabled(true); 113 | // add action as first action in the recent menu 114 | m_menu->insertAction(m_menu->isEmpty() ? nullptr : m_menu->actions().front(), entry); 115 | } 116 | 117 | /*! 118 | * \brief Clears all entries. 119 | */ 120 | void RecentMenuManager::clearEntries() 121 | { 122 | QList entries = m_menu->actions(); 123 | for (auto i = entries.begin(), end = entries.end() - 2; i != end; ++i) { 124 | if (*i != m_clearAction) { 125 | delete *i; 126 | } 127 | } 128 | m_menu->setEnabled(false); 129 | } 130 | 131 | /*! 132 | * \brief Internally called to emit fileSelected() after an action has been 133 | * triggered. 134 | */ 135 | void RecentMenuManager::handleActionTriggered() 136 | { 137 | if (QAction *action = qobject_cast(sender())) { 138 | const QString path = action->property("file_path").toString(); 139 | if (!path.isEmpty()) { 140 | if (QFile::exists(path)) { 141 | emit fileSelected(path); 142 | } else { 143 | QMessageBox msg; 144 | msg.setWindowTitle(tr("Recently opened files - ") + QCoreApplication::applicationName()); 145 | msg.setText(tr("The selected file can't be found anymore. Do you want " 146 | "to delete the obsolete entry from the list?")); 147 | msg.setIcon(QMessageBox::Warning); 148 | QPushButton *keepEntryButton = msg.addButton(tr("keep entry"), QMessageBox::NoRole); 149 | QPushButton *deleteEntryButton = msg.addButton(tr("delete entry"), QMessageBox::YesRole); 150 | msg.setEscapeButton(keepEntryButton); 151 | msg.exec(); 152 | if (msg.clickedButton() == deleteEntryButton) { 153 | delete action; 154 | QList remainingActions = m_menu->actions(); 155 | if (!remainingActions.isEmpty() && remainingActions.front() != m_sep && remainingActions.front() != m_clearAction) { 156 | remainingActions.front()->setShortcut(QKeySequence(Qt::Key_F6)); 157 | m_menu->setEnabled(true); 158 | } else { 159 | m_menu->setEnabled(false); 160 | } 161 | } 162 | } 163 | } 164 | } 165 | } 166 | 167 | /*! 168 | * \fn RecentMenuManager::fileSelected() 169 | * \brief Emitted after the user selected a file. 170 | * \remarks Only emitted when the selected file still existed; otherwise the 171 | * user is ask whether to keep or delete the entry. 172 | */ 173 | } // namespace QtUtilities 174 | -------------------------------------------------------------------------------- /tests/buttonoverlay.cpp: -------------------------------------------------------------------------------- 1 | #include "../widgets/clearcombobox.h" 2 | #include "../widgets/clearlineedit.h" 3 | #include "../widgets/clearplaintextedit.h" 4 | #include "../widgets/clearspinbox.h" 5 | #include "../widgets/iconbutton.h" 6 | 7 | #include "../misc/disablewarningsmoc.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace QtUtilities; 14 | 15 | class ButtonOverlayTests : public QObject { 16 | Q_OBJECT 17 | 18 | private: 19 | void assertGeneralDefaults(ButtonOverlay &buttonOverlay); 20 | void changeBasicConfiguration(ButtonOverlay &buttonOverlay); 21 | 22 | private Q_SLOTS: 23 | void testClearLineEdit(); 24 | void testClearComboBox(); 25 | void testClearSpinBox(); 26 | void testClearPlainTextEdit(); 27 | }; 28 | 29 | void ButtonOverlayTests::assertGeneralDefaults(ButtonOverlay &buttonOverlay) 30 | { 31 | // assert defaults 32 | QVERIFY2(buttonOverlay.isClearButtonEnabled(), "clear button enabled by default"); 33 | QVERIFY2(!buttonOverlay.isInfoButtonEnabled(), "info button disabled by default"); 34 | QVERIFY2(buttonOverlay.isCleared(), "widget considered cleared by default"); 35 | QVERIFY2(!buttonOverlay.isUsingCustomLayout(), "not using custom layout by default"); 36 | } 37 | 38 | void ButtonOverlayTests::changeBasicConfiguration(ButtonOverlay &buttonOverlay) 39 | { 40 | buttonOverlay.setClearButtonEnabled(false); 41 | QVERIFY2(!buttonOverlay.isClearButtonEnabled(), "clear button disabled"); 42 | buttonOverlay.enableInfoButton( 43 | QIcon::fromTheme(QStringLiteral("data-information")).pixmap(IconButton::defaultPixmapSize), QStringLiteral("Some info")); 44 | QVERIFY2(buttonOverlay.isInfoButtonEnabled(), "info button enabled"); 45 | } 46 | 47 | void ButtonOverlayTests::testClearLineEdit() 48 | { 49 | auto clearWidget = ClearLineEdit(); 50 | 51 | // assert defaults 52 | assertGeneralDefaults(clearWidget); 53 | QVERIFY2(static_cast(clearWidget).isClearButtonEnabled(), "clear button enabled via QLineEdit"); 54 | 55 | // change configuration 56 | changeBasicConfiguration(clearWidget); 57 | clearWidget.setText(QStringLiteral("not cleared anymore")); 58 | QVERIFY2(!clearWidget.isCleared(), "widget not considered cleared anymore"); 59 | auto customAction = QAction(QIcon::fromTheme(QStringLiteral("edit-copy")).pixmap(IconButton::defaultPixmapSize), QStringLiteral("Copy")); 60 | clearWidget.addCustomAction(&customAction); 61 | QVERIFY2(!clearWidget.isUsingCustomLayout(), "not resorted to using custom layout so far"); 62 | QCOMPARE(clearWidget.actions().size(), 2); // more an implementation detail but the QLineEdit should have 2 actions right now 63 | 64 | // test fallback to custom layout 65 | static_cast(clearWidget).setClearButtonEnabled(true); 66 | auto pushButton = QPushButton(QStringLiteral("Custom widget")); 67 | clearWidget.addCustomButton(&pushButton); 68 | QVERIFY2(clearWidget.isUsingCustomLayout(), "resorted to using custom layout due to addCustomButton()"); 69 | QVERIFY2(static_cast(clearWidget).isClearButtonEnabled(), "clear button still enabled"); 70 | QVERIFY2(!static_cast(clearWidget).isClearButtonEnabled(), "clear button not enabled via QLineEdit"); 71 | QCOMPARE(clearWidget.actions().size(), 0); // more an implementation detail but the QLineEdit should have been converted to icon buttons 72 | QCOMPARE(clearWidget.buttonLayout()->count(), 4); 73 | 74 | // change custom action; its fallback icon button should reflect the change 75 | const auto *const iconButton = qobject_cast(clearWidget.buttonLayout()->itemAt(2)->widget()); 76 | QVERIFY2(iconButton, "icon button present"); 77 | QCOMPARE(iconButton->toolTip(), QStringLiteral("Copy")); 78 | customAction.setText(QStringLiteral("Paste")); 79 | QCOMPARE(iconButton->toolTip(), QStringLiteral("Paste")); 80 | 81 | // remove buttons again 82 | static_cast(clearWidget).setClearButtonEnabled(false); 83 | clearWidget.disableInfoButton(); 84 | clearWidget.removeCustomAction(&customAction); 85 | clearWidget.removeCustomButton(&pushButton); 86 | QCOMPARE(clearWidget.buttonLayout()->count(), 0); 87 | } 88 | 89 | void ButtonOverlayTests::testClearComboBox() 90 | { 91 | auto clearWidget = ClearComboBox(); 92 | 93 | // assert defaults 94 | assertGeneralDefaults(clearWidget); 95 | 96 | // change configuration 97 | changeBasicConfiguration(clearWidget); 98 | clearWidget.setClearButtonEnabled(true); 99 | clearWidget.setCurrentText(QStringLiteral("not cleared anymore")); 100 | QVERIFY2(!clearWidget.isCleared(), "widget not considered cleared anymore"); 101 | auto customAction = QAction(QIcon::fromTheme(QStringLiteral("edit-copy")).pixmap(IconButton::defaultPixmapSize), QStringLiteral("Copy")); 102 | clearWidget.addCustomAction(&customAction); 103 | QVERIFY2(!clearWidget.isUsingCustomLayout(), "not resorted to using custom layout so far"); 104 | 105 | // trigger fallback 106 | QCOMPARE(clearWidget.buttonLayout()->count(), 3); 107 | } 108 | 109 | void ButtonOverlayTests::testClearSpinBox() 110 | { 111 | auto clearWidget = ClearSpinBox(); 112 | 113 | // assert defaults 114 | assertGeneralDefaults(clearWidget); 115 | 116 | // change configuration 117 | changeBasicConfiguration(clearWidget); 118 | clearWidget.setClearButtonEnabled(true); 119 | clearWidget.setValue(1); 120 | QVERIFY2(!clearWidget.isCleared(), "widget not considered cleared anymore"); 121 | auto customAction = QAction(QIcon::fromTheme(QStringLiteral("edit-copy")).pixmap(IconButton::defaultPixmapSize), QStringLiteral("Copy")); 122 | clearWidget.addCustomAction(&customAction); 123 | QVERIFY2(!clearWidget.isUsingCustomLayout(), "not resorted to using custom layout so far"); 124 | 125 | // trigger fallback 126 | QCOMPARE(clearWidget.buttonLayout()->count(), 3); 127 | } 128 | 129 | void ButtonOverlayTests::testClearPlainTextEdit() 130 | { 131 | auto clearWidget = ClearPlainTextEdit(); 132 | 133 | // assert defaults 134 | QVERIFY2(clearWidget.isClearButtonEnabled(), "clear button enabled by default"); 135 | QVERIFY2(!clearWidget.isInfoButtonEnabled(), "info button disabled by default"); 136 | QVERIFY2(clearWidget.isCleared(), "widget considered cleared by default"); 137 | QVERIFY2(clearWidget.isUsingCustomLayout(), "using custom layout by default"); 138 | QCOMPARE(clearWidget.buttonLayout()->count(), 1); 139 | 140 | // change configuration 141 | clearWidget.document()->setPlainText(QStringLiteral("not cleared anymore")); 142 | QVERIFY2(!clearWidget.isCleared(), "widget not considered cleared anymore"); 143 | } 144 | 145 | QT_UTILITIES_DISABLE_WARNINGS_FOR_MOC_INCLUDE 146 | QTEST_MAIN(ButtonOverlayTests) 147 | #include "buttonoverlay.moc" 148 | -------------------------------------------------------------------------------- /aboutdialog/aboutdialog.cpp: -------------------------------------------------------------------------------- 1 | #include "./aboutdialog.h" 2 | #include "../misc/dialogutils.h" 3 | 4 | #include "ui_aboutdialog.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | /*! 15 | * \brief The QtUtilities namespace contains all utilities provided by the qtutilities library. 16 | */ 17 | namespace QtUtilities { 18 | 19 | /*! 20 | * \class AboutDialog 21 | * \brief The AboutDialog class provides a simple about dialog. 22 | */ 23 | 24 | /*! 25 | * \brief Constructs an about dialog with the provided information. 26 | * \param parent Specifies the parent widget. 27 | * \param applicationName Specifies the name of the application. If empty, 28 | * QApplication::applicationName() will be used. 29 | * \param creator Specifies the creator of the application. If empty, 30 | * QApplication::organizationName() will be used. 31 | * \param version Specifies the version of the application. If empty, 32 | * QApplication::applicationVersion() will be used. 33 | * \param dependencyVersions Specifies the dependency versions which were present at link-time. If empty, 34 | * the application info from c++utilities is used. 35 | * \param description Specifies a short description about the application. 36 | * \param website Specifies the URL to the website of the application. 37 | * \param image Specifies the application icon. If the image is null, the 38 | * standard information icon will be used. 39 | */ 40 | AboutDialog::AboutDialog(QWidget *parent, const QString &applicationName, const QString &creator, const QString &version, 41 | const std::vector &dependencyVersions, const QString &website, const QString &description, const QImage &image) 42 | : QDialog(parent) 43 | , m_ui(new Ui::AboutDialog) 44 | { 45 | m_ui->setupUi(this); 46 | makeHeading(m_ui->productNameLabel); 47 | setStyleSheet(dialogStyleForPalette(palette())); 48 | setWindowFlags((windowFlags()) & ~(Qt::WindowMinMaxButtonsHint | Qt::WindowContextHelpButtonHint | Qt::WindowFullscreenButtonHint)); 49 | if (!applicationName.isEmpty()) { 50 | m_ui->productNameLabel->setText(applicationName); 51 | } else if (!QApplication::applicationDisplayName().isEmpty()) { 52 | m_ui->productNameLabel->setText(QApplication::applicationDisplayName()); 53 | } else { 54 | m_ui->productNameLabel->setText(QApplication::applicationName()); 55 | } 56 | if (creator.startsWith(QLatin1Char('<'))) { 57 | // assign rich text as-is 58 | m_ui->creatorLabel->setText(creator); 59 | } else { 60 | // add "developed by " before creator name 61 | auto setCreator = [this, creator = std::move(creator)] { 62 | m_ui->creatorLabel->setText(tr("developed by %1").arg(creator.isEmpty() ? QApplication::organizationName() : creator)); 63 | }; 64 | setCreator(); 65 | connect(this, &AboutDialog::retranslationRequired, this, std::move(setCreator)); 66 | } 67 | m_ui->versionLabel->setText(version.isEmpty() ? QApplication::applicationVersion() : version); 68 | const auto &deps(dependencyVersions.size() ? dependencyVersions : CppUtilities::applicationInfo.dependencyVersions); 69 | if (!deps.empty()) { 70 | QStringList linkedAgainst; 71 | linkedAgainst.reserve(static_cast(deps.size())); 72 | for (const auto &dependencyVersion : deps) { 73 | linkedAgainst << QString::fromUtf8(dependencyVersion); 74 | } 75 | m_ui->versionLabel->setToolTip(QStringLiteral("

") % tr("Linked against:") % QStringLiteral("

  • ") 76 | % linkedAgainst.join(QStringLiteral("
  • ")) % QStringLiteral("
")); 77 | } 78 | if (!website.isEmpty() || CppUtilities::applicationInfo.url) { 79 | auto setWebsite = [this, website = std::move(website)] { 80 | m_ui->websiteLabel->setText(tr("For updates and bug reports visit the project " 82 | "website.") 83 | .arg(!website.isEmpty() ? website : QString::fromUtf8(CppUtilities::applicationInfo.url))); 84 | }; 85 | setWebsite(); 86 | connect(this, &AboutDialog::retranslationRequired, this, std::move(setWebsite)); 87 | } else { 88 | m_ui->websiteLabel->hide(); 89 | } 90 | m_ui->descLabel->setText(description.isEmpty() && CppUtilities::applicationInfo.description 91 | ? QString::fromUtf8(CppUtilities::applicationInfo.description) 92 | : description); 93 | m_iconScene = new QGraphicsScene(this); 94 | auto *item = image.isNull() 95 | ? new QGraphicsPixmapItem(QApplication::style()->standardIcon(QStyle::SP_MessageBoxInformation, nullptr, this).pixmap(128)) 96 | : new QGraphicsPixmapItem(QPixmap::fromImage(image)); 97 | m_iconScene->addItem(item); 98 | m_ui->graphicsView->setScene(m_iconScene); 99 | auto setQtVersion = [this] { m_ui->qtVersionLabel->setText(tr("Using Qt %1").arg(QString::fromUtf8(qVersion()))); }; 100 | setQtVersion(); 101 | connect(this, &AboutDialog::retranslationRequired, this, std::move(setQtVersion)); 102 | connect(m_ui->qtVersionLabel, &QLabel::linkActivated, this, &AboutDialog::linkActivated); 103 | centerWidget(this, parentWidget()); 104 | } 105 | 106 | /*! 107 | * \brief Constructs an about dialog with the specified information. 108 | */ 109 | AboutDialog::AboutDialog(QWidget *parent, const QString &applicationName, const QString &creator, const QString &version, const QString &website, 110 | const QString &description, const QImage &image) 111 | : AboutDialog(parent, applicationName, creator, version, {}, website, description, image) 112 | { 113 | } 114 | 115 | /*! 116 | * \brief Constructs an about dialog with the specified \a parent, \a 117 | * description and \a image. 118 | */ 119 | AboutDialog::AboutDialog(QWidget *parent, const QString &website, const QString &description, const QImage &image) 120 | : AboutDialog(parent, QString(), QString(), QString(), website, description, image) 121 | { 122 | } 123 | 124 | /*! 125 | * \brief Destroys the about dialog. 126 | */ 127 | AboutDialog::~AboutDialog() 128 | { 129 | } 130 | 131 | bool AboutDialog::event(QEvent *event) 132 | { 133 | const auto res = QDialog::event(event); 134 | switch (event->type()) { 135 | case QEvent::PaletteChange: 136 | setStyleSheet(dialogStyleForPalette(palette())); 137 | break; 138 | case QEvent::LanguageChange: 139 | setWindowTitle(QCoreApplication::translate("QtUtilities::AboutDialog", "About", nullptr)); 140 | emit retranslationRequired(); 141 | break; 142 | default:; 143 | } 144 | return res; 145 | } 146 | 147 | void AboutDialog::linkActivated(const QString &link) 148 | { 149 | if (link == QLatin1String("qtversion")) { 150 | QMessageBox::aboutQt(nullptr); 151 | } 152 | } 153 | 154 | } // namespace QtUtilities 155 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.17.0 FATAL_ERROR) 2 | 3 | # meta data 4 | set(META_PROJECT_NAME qtutilities) 5 | set(META_PROJECT_VARNAME QT_UTILITIES) 6 | set(META_APP_NAME "Qt Utilities") 7 | set(META_APP_AUTHOR "Martchus") 8 | set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") 9 | set(META_APP_DESCRIPTION 10 | "Common Qt related C++ classes and routines used by my applications such as dialogs, widgets and models") 11 | set(META_VERSION_MAJOR 6) 12 | set(META_VERSION_MINOR 19) 13 | set(META_VERSION_PATCH 0) 14 | set(META_APP_VERSION ${META_VERSION_MAJOR}.${META_VERSION_MINOR}.${META_VERSION_PATCH}) 15 | set(META_GUI_OPTIONAL ON) 16 | 17 | # link explicitly against Qt Gui 18 | list(APPEND ADDITIONAL_QT_MODULES Gui) 19 | 20 | project(${META_PROJECT_NAME}) 21 | 22 | # add project files 23 | set(HEADER_FILES 24 | misc/xmlparsermacros.h 25 | misc/undefxmlparsermacros.h 26 | misc/trylocker.h 27 | misc/adoptlocker.h 28 | misc/dialogutils.h 29 | misc/disablewarningsmoc.h 30 | misc/desktoputils.h 31 | misc/conversion.h 32 | misc/compat.h 33 | settingsdialog/qtsettings.h 34 | setup/updater.h 35 | models/checklistmodel.h 36 | resources/qtconfigarguments.h 37 | resources/resources.h 38 | resources/importplugin.h) 39 | set(SRC_FILES 40 | misc/dialogutils.cpp 41 | misc/desktoputils.cpp 42 | settingsdialog/qtsettings.cpp 43 | setup/updater.cpp 44 | models/checklistmodel.cpp 45 | resources/qtconfigarguments.cpp 46 | resources/resources.cpp) 47 | set(RES_FILES resources/qtutilsicons.qrc) 48 | 49 | set(WIDGETS_HEADER_FILES 50 | aboutdialog/aboutdialog.h 51 | enterpassworddialog/enterpassworddialog.h 52 | settingsdialog/optioncategory.h 53 | settingsdialog/optioncategoryfiltermodel.h 54 | settingsdialog/optioncategorymodel.h 55 | settingsdialog/optionpage.h 56 | settingsdialog/settingsdialog.h 57 | widgets/buttonoverlay.h 58 | widgets/clearcombobox.h 59 | widgets/clearlineedit.h 60 | widgets/clearplaintextedit.h 61 | widgets/clearspinbox.h 62 | widgets/iconbutton.h 63 | widgets/pathselection.h 64 | paletteeditor/paletteeditor.h 65 | paletteeditor/colorbutton.h 66 | misc/recentmenumanager.h) 67 | set(WIDGETS_SRC_FILES 68 | aboutdialog/aboutdialog.cpp 69 | enterpassworddialog/enterpassworddialog.cpp 70 | settingsdialog/optioncategory.cpp 71 | settingsdialog/optioncategoryfiltermodel.cpp 72 | settingsdialog/optioncategorymodel.cpp 73 | settingsdialog/optionpage.cpp 74 | settingsdialog/settingsdialog.cpp 75 | widgets/buttonoverlay.cpp 76 | widgets/clearcombobox.cpp 77 | widgets/clearlineedit.cpp 78 | widgets/clearplaintextedit.cpp 79 | widgets/clearspinbox.cpp 80 | widgets/iconbutton.cpp 81 | widgets/pathselection.cpp 82 | paletteeditor/paletteeditor.cpp 83 | paletteeditor/colorbutton.cpp 84 | misc/recentmenumanager.cpp) 85 | set(WIDGETS_UI_FILES 86 | aboutdialog/aboutdialog.ui 87 | enterpassworddialog/enterpassworddialog.ui 88 | settingsdialog/settingsdialog.ui 89 | settingsdialog/qtappearanceoptionpage.ui 90 | settingsdialog/qtlanguageoptionpage.ui 91 | settingsdialog/qtenvoptionpage.ui 92 | paletteeditor/paletteeditor.ui) 93 | set(QT_TESTS) 94 | set(CMAKE_MODULE_FILES cmake/modules/QtConfig.cmake cmake/modules/QtGuiConfig.cmake cmake/modules/QtLinkage.cmake 95 | cmake/modules/QtWebViewProviderConfig.cmake cmake/modules/QtJsProviderConfig.cmake) 96 | set(CMAKE_TEMPLATE_FILES cmake/templates/qtconfig.h.in cmake/templates/webviewdefs.h.in cmake/templates/webviewincludes.h.in 97 | cmake/templates/jsdefs.h.in cmake/templates/jsincludes.h.in) 98 | 99 | set(TS_FILES 100 | translations/${META_PROJECT_NAME}_zh_CN.ts translations/${META_PROJECT_NAME}_de_DE.ts 101 | translations/${META_PROJECT_NAME}_ru_RU.ts translations/${META_PROJECT_NAME}_ja_JP.ts 102 | translations/${META_PROJECT_NAME}_en_US.ts) 103 | 104 | set(DOC_FILES README.md) 105 | 106 | set(REQUIRED_ICONS 107 | dialog-cancel 108 | dialog-ok 109 | dialog-ok-apply 110 | document-open 111 | document-open-recent 112 | edit-clear 113 | go-next 114 | preferences-desktop-icons 115 | preferences-desktop-locale 116 | qtcreator 117 | system-file-manager 118 | system-run 119 | system-search 120 | window-close) 121 | 122 | set(SCRIPT_FILES scripts/required_icons.sh) 123 | 124 | # required to include CMake modules from own project directory 125 | set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules" "${CMAKE_MODULE_PATH}") 126 | 127 | # configure whether setup tools are enabled 128 | option( 129 | SETUP_TOOLS 130 | "enables setup tools; needs c++utilities built with USE_LIBARCHIVE option; makes likely sense to disable when distributing via a package" 131 | OFF) 132 | 133 | # find c++utilities 134 | set(CPP_UTILITIES_REQUIRED_VERSION 5.5.0) 135 | if (SETUP_TOOLS) 136 | set(CPP_UTILITIES_REQUIRED_VERSION 5.26.0) 137 | endif () 138 | set(CONFIGURATION_PACKAGE_SUFFIX 139 | "" 140 | CACHE STRING "sets the suffix for find_package() calls to packages configured via c++utilities") 141 | set(PACKAGE_NAMESPACE 142 | "" 143 | CACHE STRING "sets the namespace (prefix) for find_package() calls to packages configured via c++utilities") 144 | if (PACKAGE_NAMESPACE) 145 | set(PACKAGE_NAMESPACE_PREFIX "${PACKAGE_NAMESPACE}-") 146 | endif () 147 | find_package(${PACKAGE_NAMESPACE_PREFIX}c++utilities${CONFIGURATION_PACKAGE_SUFFIX} ${CPP_UTILITIES_REQUIRED_VERSION} 148 | REQUIRED) 149 | use_cpp_utilities() 150 | 151 | # configure platform specific capslock detection for enterpassworddialog.cpp 152 | if (WIN32) 153 | set(HAVE_PLATFORM_SPECIFIC_CAPSLOCK_DETECTION ON) 154 | else () 155 | include(3rdParty) 156 | use_package(TARGET_NAME X11::X11 PACKAGE_NAME X11 OPTIONAL) 157 | if (TARGET X11::X11) 158 | set_property( 159 | SOURCE enterpassworddialog/enterpassworddialog.cpp 160 | APPEND 161 | PROPERTY COMPILE_DEFINITIONS X_AVAILABLE) 162 | set(HAVE_PLATFORM_SPECIFIC_CAPSLOCK_DETECTION ON) 163 | endif () 164 | endif () 165 | option(CAPSLOCK_DETECTION "enables capslock detection" ${HAVE_PLATFORM_SPECIFIC_CAPSLOCK_DETECTION}) 166 | if (CAPSLOCK_DETECTION) 167 | if (NOT HAVE_PLATFORM_SPECIFIC_CAPSLOCK_DETECTION) 168 | message(FATAL_ERROR "No backend for capslock detection found (WinAPI or X11 must be provided)") 169 | endif () 170 | set_property( 171 | SOURCE enterpassworddialog/enterpassworddialog.cpp 172 | APPEND 173 | PROPERTY COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_PLATFORM_SPECIFIC_CAPSLOCK_DETECTION) 174 | endif () 175 | 176 | # configure support for D-Bus notifications 177 | if (UNIX 178 | AND NOT APPLE 179 | AND NOT ANDROID) 180 | set(ENABLE_DBUS_NOTIFICATIONS_BY_DEFAULT ON) 181 | else () 182 | set(ENABLE_DBUS_NOTIFICATIONS_BY_DEFAULT OFF) 183 | endif () 184 | option(DBUS_NOTIFICATIONS "enables support for D-Bus notifications" ${ENABLE_DBUS_NOTIFICATIONS_BY_DEFAULT}) 185 | set(DBUS_NOTIFICATIONS_FILE_NAME misc/dbusnotification) 186 | if (DBUS_NOTIFICATIONS) 187 | list(APPEND HEADER_FILES ${DBUS_NOTIFICATIONS_FILE_NAME}.h) 188 | list(APPEND SRC_FILES ${DBUS_NOTIFICATIONS_FILE_NAME}.cpp) 189 | list(APPEND DBUS_FILES dbus/org.freedesktop.Notifications.xml) 190 | list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_SUPPORT_DBUS_NOTIFICATIONS) 191 | list(APPEND QT_TESTS dbusnotification) 192 | message(STATUS "D-Bus notifications enabled") 193 | else () 194 | list(APPEND EXCLUDED_FILES ${DBUS_NOTIFICATIONS_FILE_NAME}.h ${DBUS_NOTIFICATIONS_FILE_NAME}.cpp) 195 | message(STATUS "D-Bus notifications disabled") 196 | endif () 197 | 198 | # configure setup tools; if not enabled functions/classes under setup become no-ops 199 | if (SETUP_TOOLS) 200 | set_property( 201 | SOURCE setup/updater.cpp setup/signature.cpp tests/setup.cpp 202 | APPEND 203 | PROPERTY COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_SETUP_TOOLS_ENABLED) 204 | list(APPEND REQUIRED_ICONS install info) 205 | list(APPEND WIDGETS_UI_FILES setup/updateoptionpage.ui) 206 | list(APPEND QT_TESTS setup) 207 | list(APPEND ADDITIONAL_QT_MODULES Network Concurrent) 208 | endif () 209 | 210 | # include modules to apply configuration 211 | include(BasicConfig) 212 | include(QtGuiConfig) 213 | 214 | # add further Qt modules (which are not automatically detected) 215 | set(META_PUBLIC_QT_MODULES Core ${ADDITIONAL_QT_MODULES}) 216 | 217 | # include further modules to apply configuration 218 | include(QtConfig) 219 | include(WindowsResources) 220 | include(LibraryTarget) 221 | include(Doxygen) 222 | include(ConfigHeader) 223 | 224 | # configure test target 225 | if (WIDGETS_GUI) 226 | list(APPEND QT_TESTS buttonoverlay dialogs) 227 | endif () 228 | include(TestUtilities) 229 | list(APPEND QT_TEST_LIBRARIES ${CPP_UTILITIES_LIB} ${META_TARGET_NAME}) 230 | use_qt_module(LIBRARIES_VARIABLE "QT_TEST_LIBRARIES" PREFIX "${QT_PACKAGE_PREFIX}" MODULE "Test") 231 | foreach (TEST ${QT_TESTS}) 232 | configure_test_target(TEST_NAME "${TEST}_tests" SRC_FILES "tests/${TEST}.cpp" LIBRARIES "${QT_TEST_LIBRARIES}") 233 | endforeach () 234 | -------------------------------------------------------------------------------- /resources/qtconfigarguments.cpp: -------------------------------------------------------------------------------- 1 | #include "./qtconfigarguments.h" 2 | 3 | #include "../misc/compat.h" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #ifdef QT_UTILITIES_GUI_QTWIDGETS 13 | #include 14 | #include 15 | #include 16 | #else 17 | #include 18 | #endif 19 | 20 | #if defined(Q_OS_WINDOWS) && (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) 21 | #define QT_UTILITIES_CHECK_WINDOWS_VERSION 22 | #include 23 | #include 24 | #endif 25 | 26 | #include 27 | #include 28 | 29 | using namespace std; 30 | using namespace CppUtilities::EscapeCodes; 31 | 32 | /*! 33 | * \brief The CppUtilities namespace contains addons to the c++utilities library provided by the qtutilities library. 34 | */ 35 | namespace CppUtilities { 36 | 37 | /*! 38 | * \brief Constructs new Qt config arguments. 39 | */ 40 | QtConfigArguments::QtConfigArguments() 41 | : m_qtWidgetsGuiArg("qt-widgets-gui", 'g', "shows a Qt widgets based graphical user interface") 42 | , m_qtQuickGuiArg("qt-quick-gui", 'q', "shows a Qt quick based graphical user interface") 43 | , m_lngArg("lang", 'l', "sets the language for the Qt GUI") 44 | , m_qmlDebuggerArg("qmljsdebugger", 'q', 45 | "enables QML debugging (see " 46 | "http://doc.qt.io/qt-5/" 47 | "qtquick-debugging.html)") 48 | , m_widgetsStyleArg("widgets-style", '\0', "sets the Qt Widgets style") 49 | , m_quickControls2StyleArg("qqc2-style", '\0', "sets the Qt Quick Controls 2 style") 50 | , m_iconThemeArg("icon-theme", '\0', 51 | "sets the icon theme and additional " 52 | "theme search paths for the Qt GUI") 53 | , m_fontArg("font", '\0', "sets the font family and size (point) for the Qt GUI") 54 | , m_libraryPathsArg("library-paths", '\0', 55 | "sets the list of directories to search when loading " 56 | "libraries (all existing paths will be deleted)") 57 | , m_platformThemeArg("platformtheme", '\0', "specifies the Qt platform theme to be used") 58 | , m_sceneGraphRenderLoopArg("scene-graph-render-loop", '\0', "sets the loop for the Qt Quick Scene Graph OpenGL Renderer") 59 | { 60 | // language 61 | m_lngArg.setValueNames({ "language" }); 62 | m_lngArg.setRequiredValueCount(1); 63 | m_lngArg.setRequired(false); 64 | m_lngArg.setCombinable(true); 65 | // qml debugger (handled by Qt, just to let the parser know of it) 66 | m_qmlDebuggerArg.setValueNames({ "port:[,port_to][,host:][,block]" }); 67 | m_qmlDebuggerArg.setRequiredValueCount(1); 68 | m_qmlDebuggerArg.setCombinable(true); 69 | // appearance 70 | m_widgetsStyleArg.setValueNames({ "breeze/cleanlooks/fusion/kvantum/oxygen/adwaita/windows/..." }); 71 | m_widgetsStyleArg.setRequiredValueCount(1); 72 | m_widgetsStyleArg.setCombinable(true); 73 | m_widgetsStyleArg.setEnvironmentVariable("QT_STYLE_OVERRIDE"); 74 | m_quickControls2StyleArg.setValueNames({ "default/material/universal/org.kde.desktop/..." }); 75 | m_quickControls2StyleArg.setRequiredValueCount(1); 76 | m_quickControls2StyleArg.setCombinable(true); 77 | m_quickControls2StyleArg.setEnvironmentVariable("QT_QUICK_CONTROLS_STYLE"); 78 | m_iconThemeArg.setValueNames({ "theme name", "search path 1", "search path 2" }); 79 | m_iconThemeArg.setRequiredValueCount(Argument::varValueCount); 80 | m_iconThemeArg.setCombinable(true); 81 | m_iconThemeArg.setEnvironmentVariable("ICON_THEME_SEARCH_PATH and ICON_THEME"); 82 | m_fontArg.setValueNames({ "name", "size" }); 83 | m_fontArg.setRequiredValueCount(2); 84 | m_fontArg.setCombinable(true); 85 | m_libraryPathsArg.setValueNames({ "path 1", "path 2" }); 86 | m_libraryPathsArg.setRequiredValueCount(Argument::varValueCount); 87 | m_libraryPathsArg.setCombinable(true); 88 | m_platformThemeArg.setRequiredValueCount(1); 89 | m_platformThemeArg.setCombinable(true); 90 | m_platformThemeArg.setValueNames({ "qt5ct/kde/..." }); 91 | m_platformThemeArg.setPreDefinedCompletionValues("kde gnome " 92 | #if QT_VERSION_MAJOR == 5 93 | "qt5ct" 94 | #elif QT_VERSION_MAJOR == 6 95 | "qt6ct" 96 | #endif 97 | ); 98 | m_platformThemeArg.setEnvironmentVariable("QT_QPA_PLATFORMTHEME"); 99 | m_sceneGraphRenderLoopArg.setRequiredValueCount(1); 100 | m_sceneGraphRenderLoopArg.setCombinable(true); 101 | m_sceneGraphRenderLoopArg.setValueNames({ "basic/windows/threaded" }); 102 | m_sceneGraphRenderLoopArg.setPreDefinedCompletionValues("basic windows threaded"); 103 | m_sceneGraphRenderLoopArg.setEnvironmentVariable("QSG_RENDER_LOOP"); 104 | m_qtWidgetsGuiArg.setSubArguments( 105 | { &m_lngArg, &m_qmlDebuggerArg, &m_widgetsStyleArg, &m_iconThemeArg, &m_fontArg, &m_libraryPathsArg, &m_platformThemeArg }); 106 | m_qtQuickGuiArg.setSubArguments({ &m_lngArg, &m_qmlDebuggerArg, &m_quickControls2StyleArg, &m_iconThemeArg, &m_fontArg, &m_libraryPathsArg, 107 | &m_platformThemeArg, &m_sceneGraphRenderLoopArg }); 108 | m_qtWidgetsGuiArg.setDenotesOperation(true); 109 | m_qtQuickGuiArg.setDenotesOperation(true); 110 | #if defined(QT_UTILITIES_GUI_QTWIDGETS) 111 | m_qtWidgetsGuiArg.setImplicit(true); 112 | #elif defined(QT_UTILITIES_GUI_QTQUICK) 113 | m_qtQuickGuiArg.setImplicit(true); 114 | #endif 115 | } 116 | 117 | /*! 118 | * \brief Applies the settings from the arguments. 119 | * \remarks Also checks environment variables for the icon theme. 120 | * \param preventApplyingDefaultFont If true, the font will not be updated to 121 | * some default value if no font has been specified explicitly. 122 | */ 123 | void QtConfigArguments::applySettings(bool preventApplyingDefaultFont) const 124 | { 125 | if (m_lngArg.isPresent()) { 126 | QLocale::setDefault(QLocale(QString::fromLocal8Bit(m_lngArg.values().front()))); 127 | } 128 | #ifdef QT_UTILITIES_GUI_QTWIDGETS 129 | if (m_widgetsStyleArg.isPresent()) { 130 | if (QStyle *const style = QStyleFactory::create(QString::fromLocal8Bit(m_widgetsStyleArg.values().front()))) { 131 | QApplication::setStyle(style); 132 | } else { 133 | cerr << Phrases::Warning << "Can not find the specified Qt Widgets style." << Phrases::EndFlush; 134 | } 135 | } 136 | #endif 137 | if (m_iconThemeArg.isPresent()) { 138 | // set first value of m_iconThemeArg as icon theme and add further values as search paths 139 | if (auto i = m_iconThemeArg.values().cbegin(), end = m_iconThemeArg.values().cend(); i != end) { 140 | QIcon::setThemeName(QString::fromUtf8(*i)); 141 | if (++i != end) { 142 | auto searchPaths = QStringList(); 143 | searchPaths.reserve(static_cast(m_iconThemeArg.values().size())); 144 | for (; i != end; ++i) { 145 | searchPaths << QString::fromUtf8(*i); 146 | } 147 | searchPaths << QStringLiteral(":/icons"); 148 | QIcon::setThemeSearchPaths(searchPaths); 149 | } 150 | } 151 | } else { 152 | if (qEnvironmentVariableIsSet("ICON_THEME_SEARCH_PATH")) { 153 | QIcon::setThemeSearchPaths(QStringList({ qEnvironmentVariable("ICON_THEME_SEARCH_PATH"), QStringLiteral(":/icons") })); 154 | } else { 155 | QIcon::setThemeSearchPaths(QIcon::themeSearchPaths() << QStringLiteral("../share/icons") << QStringLiteral(":/icons")); 156 | } 157 | if (qEnvironmentVariableIsSet("ICON_THEME")) { 158 | QIcon::setThemeName(qEnvironmentVariable("ICON_THEME")); 159 | } 160 | } 161 | if (m_fontArg.isPresent()) { 162 | QFont font; 163 | font.setFamily(QString::fromLocal8Bit(m_fontArg.values().front())); 164 | try { 165 | font.setPointSize(stringToNumber(m_fontArg.values().back())); 166 | } catch (const ConversionException &) { 167 | cerr << Phrases::Warning << "The specified font size is no number and will be ignored." << Phrases::EndFlush; 168 | } 169 | QGuiApplication::setFont(font); 170 | } 171 | #ifdef Q_OS_WIN32 172 | else if (!preventApplyingDefaultFont) { 173 | QGuiApplication::setFont(QFont(QStringLiteral("Segoe UI"), 9)); 174 | } 175 | #else 176 | CPP_UTILITIES_UNUSED(preventApplyingDefaultFont) 177 | #endif 178 | if (m_libraryPathsArg.isPresent()) { 179 | QStringList libraryPaths; 180 | libraryPaths.reserve(static_cast(m_libraryPathsArg.values().size())); 181 | for (const auto &path : m_libraryPathsArg.values()) { 182 | libraryPaths << QString::fromLocal8Bit(path); 183 | } 184 | QCoreApplication::setLibraryPaths(libraryPaths); 185 | } 186 | if (m_sceneGraphRenderLoopArg.isPresent()) { 187 | qputenv(m_sceneGraphRenderLoopArg.environmentVariable(), QByteArray(m_sceneGraphRenderLoopArg.firstValue())); 188 | } 189 | 190 | #ifdef QT_UTILITIES_CHECK_WINDOWS_VERSION 191 | if (QOperatingSystemVersion::current() < QOperatingSystemVersion::Windows10_1809) { 192 | QMessageBox::warning(nullptr, QCoreApplication::applicationName(), 193 | QCoreApplication::translate("QtConfigArguments", 194 | "This application requires Windows 10, version 1809 or newer. The current Windows version is older so the application might not work " 195 | "correctly.")); 196 | } 197 | #endif 198 | } 199 | } // namespace CppUtilities 200 | -------------------------------------------------------------------------------- /models/checklistmodel.cpp: -------------------------------------------------------------------------------- 1 | #include "./checklistmodel.h" 2 | 3 | #include 4 | 5 | /*! 6 | \namespace Models 7 | \brief Provides common models. 8 | */ 9 | 10 | namespace QtUtilities { 11 | 12 | /*! 13 | * \class Models::ChecklistItem 14 | * \brief The ChecklistItem class provides an item for use with the 15 | * ChecklistModel class. 16 | */ 17 | 18 | /*! 19 | * \class Models::ChecklistModel 20 | * \brief The ChecklistModel class provides a generic model for storing 21 | * checkable items. 22 | */ 23 | 24 | /*! 25 | * \brief Constructs a new checklist model. 26 | */ 27 | ChecklistModel::ChecklistModel(QObject *parent) 28 | : QAbstractListModel(parent) 29 | { 30 | } 31 | 32 | int ChecklistModel::rowCount(const QModelIndex &parent) const 33 | { 34 | if (!parent.isValid()) { 35 | return static_cast(m_items.size()); 36 | } 37 | return 0; 38 | } 39 | 40 | Qt::ItemFlags ChecklistModel::flags(const QModelIndex &index) const 41 | { 42 | if (!index.isValid() || index.row() >= m_items.count() || index.model() != this) { 43 | return Qt::ItemIsDropEnabled; // allows drops outside the items 44 | } 45 | return QAbstractListModel::flags(index) | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; 46 | } 47 | 48 | QVariant ChecklistModel::data(const QModelIndex &index, int role) const 49 | { 50 | if (index.isValid() && index.row() < m_items.size()) { 51 | switch (role) { 52 | case Qt::DisplayRole: 53 | return m_items.at(index.row()).label(); 54 | case Qt::CheckStateRole: 55 | return m_items.at(index.row()).checkState(); 56 | case idRole(): 57 | return m_items.at(index.row()).id(); 58 | default:; 59 | } 60 | } 61 | return QVariant(); 62 | } 63 | 64 | QMap ChecklistModel::itemData(const QModelIndex &index) const 65 | { 66 | QMap roles; 67 | roles.insert(Qt::DisplayRole, data(index, Qt::DisplayRole)); 68 | roles.insert(Qt::CheckStateRole, data(index, Qt::CheckStateRole)); 69 | roles.insert(idRole(), data(index, idRole())); 70 | return roles; 71 | } 72 | 73 | bool ChecklistModel::setData(const QModelIndex &index, const QVariant &value, int role) 74 | { 75 | bool success = false; 76 | QVector roles{ role }; 77 | if (index.isValid() && index.row() < m_items.size()) { 78 | switch (role) { 79 | case Qt::DisplayRole: 80 | m_items[index.row()].m_label = value.toString(); 81 | success = true; 82 | break; 83 | case Qt::CheckStateRole: 84 | if (value.canConvert( 85 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 86 | QMetaType::Int 87 | #else 88 | QMetaType::fromType() 89 | #endif 90 | )) { 91 | m_items[index.row()].m_checkState = static_cast(value.toInt()); 92 | success = true; 93 | } 94 | break; 95 | case idRole(): { 96 | m_items[index.row()].m_id = value; 97 | success = true; 98 | auto label = labelForId(value); 99 | if (!label.isEmpty()) { 100 | m_items[index.row()].m_label = std::move(label); 101 | roles << Qt::DisplayRole; 102 | } 103 | break; 104 | } 105 | default:; 106 | } 107 | } 108 | if (success) { 109 | emit dataChanged(index, index, roles); 110 | } 111 | return success; 112 | } 113 | 114 | bool ChecklistModel::setItemData(const QModelIndex &index, const QMap &roles) 115 | { 116 | for (QMap::ConstIterator it = roles.constBegin(); it != roles.constEnd(); ++it) { 117 | setData(index, it.value(), it.key()); 118 | } 119 | return true; 120 | } 121 | 122 | /*! 123 | * \brief Sets the checked state of the specified item. 124 | */ 125 | bool ChecklistModel::setChecked(int row, Qt::CheckState checked) 126 | { 127 | if (row < 0 || row >= m_items.size()) { 128 | return false; 129 | } 130 | m_items[row].m_checkState = checked ? Qt::Checked : Qt::Unchecked; 131 | const auto index(this->index(row)); 132 | emit dataChanged(index, index, QVector{ Qt::CheckStateRole }); 133 | return true; 134 | } 135 | 136 | /*! 137 | * \brief Returns the label for the specified \a id. 138 | * 139 | * This method might be reimplemented when subclassing to provide labels 140 | * for the item IDs. 141 | * 142 | * If an item's ID is set (using setData() with idRole() or setItems()) this method 143 | * is called to update or initialize the item's label as well. If this method returns 144 | * an empty string (default behaviour) the item's label will not be updated. 145 | * 146 | * This is useful when items are moved by the view (eg. for Drag & Drop) and to 147 | * initialize the ChecklistItem labels more conveniently. 148 | */ 149 | QString ChecklistModel::labelForId(const QVariant &) const 150 | { 151 | return QString(); 152 | } 153 | 154 | Qt::DropActions ChecklistModel::supportedDropActions() const 155 | { 156 | return Qt::MoveAction; 157 | } 158 | 159 | bool ChecklistModel::insertRows(int row, int count, const QModelIndex &parent) 160 | { 161 | if (count < 1 || row < 0 || row > rowCount() || parent.isValid()) { 162 | return false; 163 | } 164 | beginInsertRows(QModelIndex(), row, row + count - 1); 165 | for (int index = row, end = row + count; index < end; ++index) { 166 | m_items.insert(index, ChecklistItem()); 167 | } 168 | endInsertRows(); 169 | return true; 170 | } 171 | 172 | bool ChecklistModel::removeRows(int row, int count, const QModelIndex &parent) 173 | { 174 | if (count < 1 || row < 0 || (row + count) > rowCount() || parent.isValid()) { 175 | return false; 176 | } 177 | beginRemoveRows(QModelIndex(), row, row + count - 1); 178 | for (int index = row, end = row + count; index < end; ++index) { 179 | m_items.removeAt(index); 180 | } 181 | endRemoveRows(); 182 | return true; 183 | } 184 | 185 | /*! 186 | * \brief Sets the items. Resets the model. 187 | */ 188 | void ChecklistModel::setItems(const QList &items) 189 | { 190 | beginResetModel(); 191 | m_items = items; 192 | for (auto &item : m_items) { 193 | if (item.m_label.isEmpty()) { 194 | item.m_label = labelForId(item.id()); 195 | } 196 | } 197 | endResetModel(); 198 | } 199 | 200 | /*! 201 | * \brief Restores the IDs and checkstates read from the specified \a settings 202 | * object. 203 | * 204 | * The items will be read from the array with the specified \a name. 205 | * 206 | * Resets the model (current items are cleared). 207 | * 208 | * Does not restore any labels. Labels are meant to be restored from the ID. 209 | */ 210 | void ChecklistModel::restore(QSettings &settings, const QString &name) 211 | { 212 | beginResetModel(); 213 | auto currentItems = m_items; 214 | QList restoredIds; 215 | m_items.clear(); 216 | int rows = settings.beginReadArray(name); 217 | m_items.reserve(rows); 218 | for (int i = 0; i < rows; ++i) { 219 | settings.setArrayIndex(i); 220 | const auto id = settings.value(QStringLiteral("id")); 221 | const auto isIdValid = [&] { 222 | for (const auto &item : currentItems) { 223 | if (item.id() == id) { 224 | return true; 225 | } 226 | } 227 | return false; 228 | }(); 229 | if (!isIdValid) { 230 | continue; 231 | } 232 | const auto selected = settings.value(QStringLiteral("selected")); 233 | if (!id.isNull() && !selected.isNull() 234 | && selected.canConvert( 235 | #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) 236 | QMetaType::Bool 237 | #else 238 | QMetaType::fromType() 239 | #endif 240 | ) 241 | && !restoredIds.contains(id)) { 242 | m_items << ChecklistItem(id, labelForId(id), selected.toBool() ? Qt::Checked : Qt::Unchecked); 243 | restoredIds << id; 244 | } 245 | } 246 | settings.endArray(); 247 | for (const ChecklistItem &item : currentItems) { 248 | if (!restoredIds.contains(item.id())) { 249 | m_items << item; 250 | } 251 | } 252 | endResetModel(); 253 | } 254 | 255 | /*! 256 | * \brief Saves the IDs and checkstates to the specified \a settings object. 257 | * 258 | * The items will be stored using an array with the specified \a name. 259 | * 260 | * Does not save any labels. 261 | */ 262 | void ChecklistModel::save(QSettings &settings, const QString &name) const 263 | { 264 | settings.beginWriteArray(name, static_cast(m_items.size())); 265 | auto index = int(); 266 | for (const ChecklistItem &item : m_items) { 267 | settings.setArrayIndex(index); 268 | settings.setValue(QStringLiteral("id"), item.id()); 269 | settings.setValue(QStringLiteral("selected"), item.isChecked()); 270 | ++index; 271 | } 272 | settings.endArray(); 273 | } 274 | 275 | /*! 276 | * \brief Returns the checked IDs. 277 | */ 278 | QVariantList ChecklistModel::toVariantList() const 279 | { 280 | QVariantList checkedIds; 281 | checkedIds.reserve(m_items.size()); 282 | for (const auto &item : m_items) { 283 | if (item.isChecked()) { 284 | checkedIds << item.id(); 285 | } 286 | } 287 | return checkedIds; 288 | } 289 | 290 | /*! 291 | * \brief Checks all items contained by \a checkedIds and unchecks other items. 292 | */ 293 | void ChecklistModel::applyVariantList(const QVariantList &checkedIds) 294 | { 295 | for (auto &item : m_items) { 296 | item.m_checkState = checkedIds.contains(item.id()) ? Qt::Checked : Qt::Unchecked; 297 | } 298 | emit dataChanged(index(0), index(static_cast(m_items.size())), { Qt::CheckStateRole }); 299 | } 300 | 301 | } // namespace QtUtilities 302 | -------------------------------------------------------------------------------- /aboutdialog/aboutdialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | QtUtilities::AboutDialog 4 | 5 | 6 | 7 | 43 8 | 0 9 | 10 | 11 | 12 | 13 | 480 14 | 380 15 | 16 | 17 | 18 | 19 | 480 20 | 380 21 | 22 | 23 | 24 | About 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | false 34 | 35 | 36 | 37 | 0 38 | 39 | 40 | 0 41 | 42 | 43 | 0 44 | 45 | 46 | 0 47 | 48 | 49 | 0 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | QFrame::NoFrame 58 | 59 | 60 | QFrame::Raised 61 | 62 | 63 | 64 | 65 | 66 | 67 | 32 68 | 128 69 | 70 | 71 | 72 | 73 | 16777215 74 | 128 75 | 76 | 77 | 78 | background: transparent; 79 | 80 | 81 | QFrame::NoFrame 82 | 83 | 84 | 0 85 | 86 | 87 | Qt::ScrollBarAlwaysOff 88 | 89 | 90 | Qt::ScrollBarAlwaysOff 91 | 92 | 93 | 94 | 95 | 0 96 | 0 97 | 0 98 | 99 | 100 | 101 | 102 | false 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 0 111 | 0 112 | 113 | 114 | 115 | font-size: 16pt; 116 | 117 | 118 | QFrame::NoFrame 119 | 120 | 121 | QFrame::Plain 122 | 123 | 124 | 1 125 | 126 | 127 | application name 128 | 129 | 130 | Qt::AlignCenter 131 | 132 | 133 | 134 | 135 | 136 | 137 | version 138 | 139 | 140 | Qt::AlignCenter 141 | 142 | 143 | 144 | 145 | 146 | 147 | Qt::Vertical 148 | 149 | 150 | 151 | 20 152 | 40 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | font-style: italic; 161 | 162 | 163 | description 164 | 165 | 166 | Qt::AlignCenter 167 | 168 | 169 | true 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 0 178 | 0 179 | 180 | 181 | 182 | true 183 | 184 | 185 | website link 186 | 187 | 188 | Qt::AlignCenter 189 | 190 | 191 | true 192 | 193 | 194 | true 195 | 196 | 197 | Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse 198 | 199 | 200 | 201 | 202 | 203 | 204 | Qt::Vertical 205 | 206 | 207 | 208 | 20 209 | 40 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 0 219 | 0 220 | 221 | 222 | 223 | font-size: 8pt; 224 | 225 | 226 | creators 227 | 228 | 229 | Qt::AlignCenter 230 | 231 | 232 | true 233 | 234 | 235 | true 236 | 237 | 238 | Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 8 247 | 248 | 249 | 250 | font-size: 8pt; 251 | 252 | 253 | Using Qt 254 | 255 | 256 | Qt::AlignCenter 257 | 258 | 259 | true 260 | 261 | 262 | Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | --------------------------------------------------------------------------------