diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..23a669a --- /dev/null +++ b/.gitignore @@ -0,0 +1,107 @@ +.build/ +_other/ +_distrib*/ + +# Tmp files +*~ +Thumbs.db* + +# C++ objects and libs +*.slo +*.lo +*.o +*.a +*.la +*.lai +*.so +*.so.* +*.dll +*.dylib +*.ko +*.obj +*.elf +*.lib + +# Qt-es +object_script.*.Release +object_script.*.Debug +*_plugin_import.cpp +/.qmake.cache +/.qmake.stash +*.pro.user +*.pro.user.* +*.qbs.user +*.qbs.user.* +*.moc +moc_*.cpp +moc_*.h +qrc_*.cpp +ui_*.h +*.qmlc +*.jsc +Makefile* +*build-* +*.qm +*.prl + +# Qt unit tests +target_wrapper.* + +# QtCreator +*.autosave + +# QtCreator Qml +*.qmlproject.user +*.qmlproject.user.* + +# QtCreator CMake +CMakeLists.txt.user* + +# QtCreator 4.8< compilation database +compile_commands.json + +# QtCreator local machine specific files for imported projects +*creator.user* + +*_qmlcache.qrc + +# ---> C +# Prerequisites +*.d + +# Linker output +*.ilk +*.map +*.exp + +# Precompiled Headers +*.gch +*.pch + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +*.su +*.idb +*.pdb + +# Kernel Module Compile Results +*.mod* +*.cmd +.tmp_versions/ +modules.order +Module.symvers +Mkfile.old +dkms.conf + +# Fortran module files +*.mod +*.smod + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..3dcecc4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 4.0) + +project (QtCustomStyleExample) + +# application: +add_subdirectory(sources) + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9be8c26 --- /dev/null +++ b/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) 2025 Evgeny Teterin (nayk) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/LICENSE.ru b/LICENSE.ru new file mode 100644 index 0000000..6aff4e4 --- /dev/null +++ b/LICENSE.ru @@ -0,0 +1,25 @@ +MIT лицензия + +Copyright (c) 2025 Evgeny Teterin (nayk) + +Данная лицензия разрешает лицам, получившим копию +данного программного обеспечения и сопутствующей документации +(в дальнейшем именуемыми «Программное Обеспечение»), безвозмездно +использовать Программное Обеспечение без ограничений, +включая неограниченное право на использование, копирование, изменение, +слияние, публикацию, распространение, сублицензирование и/или продажу +копий Программного Обеспечения, а также лицам, которым предоставляется +данное Программное Обеспечение, при соблюдении следующих условий: + +Указанное выше уведомление об авторском праве и данные условия +должны быть включены во все копии или значимые части данного Программного Обеспечения. + +ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», +БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, +ВКЛЮЧАЯ ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ +НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ. +НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ +ПО КАКИМ-ЛИБО ИСКАМ, ЗА УЩЕРБ ИЛИ ПО ИНЫМ ТРЕБОВАНИЯМ, В ТОМ ЧИСЛЕ, +ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТЕ ИЛИ ИНОЙ СИТУАЦИИ, +ВОЗНИКШИМ ИЗ-ЗА ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ +ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ. diff --git a/README.md b/README.md index 82e0866..1e11fd1 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,21 @@ # Qt_Custom_Style_Example -Пример кастомизации приложения на Qt с использованием стилевых таблиц QSS. \ No newline at end of file +Пример кастомизации приложения на Qt с использованием стилевых таблиц QSS. + +Класс `Application` содержит статичный метод настройки стиля для приложения: + +``` +updateApplicationStyle(AppTheme appTheme); +``` + +## Пример внешнего вида приложения (Qt6, ОС Windows 11) + +Тёмный вариант: + +![picture](/_resources/images/screenshot1.png) + +Светлый вариант: + +![picture](/_resources/images/screenshot2.png) + + diff --git a/_cmake/app_settings.cmake b/_cmake/app_settings.cmake new file mode 100644 index 0000000..3dd65b2 --- /dev/null +++ b/_cmake/app_settings.cmake @@ -0,0 +1,28 @@ + +# Файл для подключения в основной проект через include +# Настройки для приложений + +include(${CMAKE_CURRENT_LIST_DIR}/common.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/developer.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/version.cmake) + +# Имя выходного файла совпадает с названием проекта: +set(RUNTIME_OUTPUT_NAME ${PROJECT_NAME}) + +configure_file( + ${CMAKE_CURRENT_LIST_DIR}/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h + @ONLY +) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${DISTRIB_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${DISTRIB_DIR}) + +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + configure_file( + ${CMAKE_CURRENT_LIST_DIR}/versioninfo.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc + @ONLY + ) +endif() + diff --git a/_cmake/common.cmake b/_cmake/common.cmake new file mode 100644 index 0000000..7f4a474 --- /dev/null +++ b/_cmake/common.cmake @@ -0,0 +1,54 @@ + +# Общие настройки для всех типов проектов + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +if (CMAKE_COMPILER_IS_GNUCXX) + if (CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9.0.0") + set(CMAKE_CXX_STANDARD 20) + else() + set(CMAKE_CXX_STANDARD 23) + endif() +else() + set(CMAKE_CXX_STANDARD 23) +endif() + +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +# Поиск библиотек Qt: +find_package(QT NAMES Qt6 Qt5 REQUIRED) + +# Настройки каталогов: +include(${CMAKE_CURRENT_LIST_DIR}/setup_directories.cmake) + +# Определение разрядности: +if (${CMAKE_SIZEOF_VOID_P} STREQUAL 4) + set(DIR_PREFIX "32") +elseif (${CMAKE_SIZEOF_VOID_P} STREQUAL 8) + set(DIR_PREFIX "64") +endif () + +# Каталог для готовых приложений и библиотек после компиляции: +set(DISTRIB_DIR + ${ROOT_PROJECT_DIR}_distrib/${CMAKE_SYSTEM_NAME}_Qt${QT_VERSION}_${DIR_PREFIX}-bit +) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(IS_DEBUG TRUE) + set(IS_RELEASE FALSE) +else() + set(IS_DEBUG FALSE) + set(IS_RELEASE TRUE) +endif() + +message(STATUS "Project '${PROJECT_NAME}' compiler ${CMAKE_CXX_COMPILER} version: ${CMAKE_CXX_COMPILER_VERSION}") +message(STATUS "Project '${PROJECT_NAME}' compiler dir: '${CXX_COMPILER_DIR}'") +message(STATUS "Project '${PROJECT_NAME}' cmake dir: '${CMAKE_BIN_DIR}'") +message(STATUS "Project '${PROJECT_NAME}' distrib dir: '${DISTRIB_DIR}'") +message(STATUS "Project '${PROJECT_NAME}' IS_RELEASE: ${IS_RELEASE}, IS_DEBUG: ${IS_DEBUG}") +message(STATUS "Project '${PROJECT_NAME}' CMAKE_PREFIX_PATH: '${CMAKE_PREFIX_PATH}'") +message(STATUS "Project '${PROJECT_NAME}' CMAKE_SYSTEM_LIBRARY_PATH: '${CMAKE_SYSTEM_LIBRARY_PATH}'") + diff --git a/_cmake/config.h.in b/_cmake/config.h.in new file mode 100644 index 0000000..41f8ac6 --- /dev/null +++ b/_cmake/config.h.in @@ -0,0 +1,9 @@ +#define PROG_NAME "@PROJECT_NAME@" +#define PROG_VERSION "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@BUILD_NUM@@PROJECT_VERSION_TWEAK@" +#define PROG_CAPTION "@PROJECT_NAME@ v" +#define PROG_DESCRIPTION "@PROJECT_DESCRIPTION@" +#define SOFT_DEVELOPER "@SOFT_DEVELOPER@" +#define DEVELOPER_DOMAIN "@DEVELOPER_DOMAIN@" +#define BUILD_DATE "@BUILD_DATE@" +#define BUILD_NUM "@BUILD_NUM@" +#define ORIGINAL_FILE_NAME "@ORIGINAL_FILE_NAME@" diff --git a/_cmake/copy_depends.cmake b/_cmake/copy_depends.cmake new file mode 100644 index 0000000..211a6a7 --- /dev/null +++ b/_cmake/copy_depends.cmake @@ -0,0 +1,289 @@ + +# Определение зависимостей после сборки приложения + +cmake_minimum_required(VERSION 4.0) + +set(LIBS_FIND_DIRS "${PREFIX}") +set(QT_SHARE_DIR "${PREFIX}") + +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + + if (EXISTS "${PREFIX}/bin") + set(LIBS_FIND_DIRS "${PREFIX}/bin") + endif() + +else() + + if (EXISTS "${PREFIX}/lib") + set(LIBS_FIND_DIRS "${PREFIX}/lib") + endif() + +endif() + +if (EXISTS "${PREFIX}/share/qt${QT_VERSION_MAJOR}") + set(QT_SHARE_DIR "${PREFIX}/share/qt${QT_VERSION_MAJOR}") +endif() + +if(TARGET_TYPE STREQUAL "EXECUTABLE") + + if (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}") + set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}") + elseif (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}.exe") + set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}.exe") + endif() + +else() + + if (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}") + set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}") + elseif (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}.dll") + set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}.dll") + elseif (EXISTS "${OUTPUT_DIR}/${TARGET_NAME}.so") + set(TARGET_FILE "${OUTPUT_DIR}/${TARGET_NAME}.so") + endif() + +endif() + +message(STATUS "TARGET_FILE: '${TARGET_FILE}'") +message(STATUS "LIBS_FIND_DIRS: '${LIBS_FIND_DIRS}'") +message(STATUS "OUTPUT_DIR: '${OUTPUT_DIR}'") +message(STATUS "QT_VERSION_MAJOR: '${QT_VERSION_MAJOR}'") +message(STATUS "Find and copy dependencies. Please, wait...") + +# functions -------------------------------------------------------------------- + +# Копирование файла + +function(safe_copy src dest) + + # Пытаемся скопировать через execute_process (кросс-платформенно) + execute_process( + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${src}" "${dest}" + RESULT_VARIABLE copy_result + ERROR_VARIABLE copy_error + OUTPUT_QUIET # Подавляем stdout + ) + + if(NOT copy_result EQUAL 0) + message(WARNING "Не удалось скопировать ${src} -> ${dest}: ${copy_error}") + return() + endif() + +endfunction() + +# Отслеживание цепочки ссылок + +function(get_symlink_chain dep symlink_chain) + unset(chain) + set(current_dep "${dep}") + + # 1. Разрешаем основную цепочку симлинков (исходный файл → конечный файл) + while(IS_SYMLINK "${current_dep}") + execute_process( + COMMAND readlink "${current_dep}" + OUTPUT_VARIABLE link_target + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + list(INSERT chain 0 "${current_dep}") # Добавляем текущий симлинк в начало + get_filename_component(parent_dir "${current_dep}" DIRECTORY) + set(current_dep "${parent_dir}/${link_target}") # Переходим по ссылке + endwhile() + + # 2. Добавляем конечный файл в начало цепочки + list(INSERT chain 0 "${current_dep}") + + # 3. Теперь ищем ВСЕ симлинки в этой директории, которые ссылаются на конечный файл + if(EXISTS "${current_dep}") + get_filename_component(final_file "${current_dep}" REALPATH) # Абсолютный путь конечного файла + get_filename_component(parent_dir "${current_dep}" DIRECTORY) # Директория конечного файла + + # Получаем список всех файлов в этой директории + file(GLOB all_files LIST_DIRECTORIES false "${parent_dir}/*") + foreach(file IN LISTS all_files) + # Если это симлинк, проверяем, ведёт ли он на конечный файл + if(IS_SYMLINK "${file}") + execute_process( + COMMAND readlink -f "${file}" # Абсолютный путь цели симлинка + OUTPUT_VARIABLE symlink_target + OUTPUT_STRIP_TRAILING_WHITESPACE + ) + # Если цель симлинка совпадает с конечным файлом, добавляем в цепочку + if(symlink_target STREQUAL final_file AND NOT file IN_LIST chain) + list(APPEND chain "${file}") + endif() + endif() + endforeach() + endif() + + set(${symlink_chain} "${chain}" PARENT_SCOPE) +endfunction() + +# ------------------------------------------------------------------------------ + +if (EXISTS "${TARGET_FILE}") + + # Получаем runtime-зависимости + file(GET_RUNTIME_DEPENDENCIES + EXECUTABLES ${TARGET_FILE} + RESOLVED_DEPENDENCIES_VAR RESOLVED_DEPS + UNRESOLVED_DEPENDENCIES_VAR UNRESOLVED_DEPS + DIRECTORIES ${LIBS_FIND_DIRS} + ) + + set(USE_SQL OFF) + set(USE_PRINT OFF) + set(USE_NETWORK OFF) + set(USE_SERIALPORT OFF) + set(USE_MULTIMEDIA OFF) + + message(STATUS "\nResolved dependencies copy:\n") + + foreach(DEP ${RESOLVED_DEPS}) + + string(FIND "${DEP}" "${LIBS_FIND_DIRS}" POS) + + if(POS EQUAL 0) + + if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + + message(STATUS "${DEP}") + file(COPY "${DEP}" DESTINATION "${OUTPUT_DIR}") + + else () + + #message(STATUS "${DEP}") + #file(COPY "${DEP}" DESTINATION "${OUTPUT_DIR}/system_lib" FOLLOW_SYMLINK_CHAIN) + + get_symlink_chain("${DEP}" dep_chain) + + foreach(file IN LISTS dep_chain) + + message(STATUS "${file}") + safe_copy("${file}" "${OUTPUT_DIR}") + + endforeach() + + endif() + + endif() + + string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Sql" POS) + + if(POS GREATER 0) + set(USE_SQL ON) + endif() + + string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Print" POS) + + if(POS GREATER 0) + set(USE_PRINT ON) + endif() + + string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Network" POS) + + if(POS GREATER 0) + set(USE_NETWORK ON) + endif() + + string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Serial" POS) + + if(POS GREATER 0) + set(USE_SERIALPORT ON) + endif() + + string(FIND "${DEP}" "Qt${QT_VERSION_MAJOR}Multimedia" POS) + + if(POS GREATER 0) + set(USE_MULTIMEDIA ON) + endif() + + endforeach() + + if (EXISTS "${LIBS_FIND_DIRS}/libQt${QT_VERSION_MAJOR}XcbQpa.so") + + get_symlink_chain("${LIBS_FIND_DIRS}/libQt${QT_VERSION_MAJOR}XcbQpa.so" xcb_chain) + + foreach(file IN LISTS xcb_chain) + + message(STATUS "${file}") + safe_copy("${file}" "${OUTPUT_DIR}") + + endforeach() + + endif() + + message(STATUS "\nResolved dependencies not copy:\n") + + foreach(DEP ${RESOLVED_DEPS}) + string(FIND "${DEP}" "${LIBS_FIND_DIRS}" POS) + if(NOT POS EQUAL 0) + message(STATUS "${DEP}") + endif() + endforeach() + + message(STATUS "\nUnresolved dependencies:\n") + + foreach(DEP ${UNRESOLVED_DEPS}) + message(STATUS "${DEP}") + endforeach() + + if (EXISTS "${QT_SHARE_DIR}/plugins/platforms") + file(COPY "${QT_SHARE_DIR}/plugins/platforms" DESTINATION "${OUTPUT_DIR}") + endif() + + if (EXISTS "${QT_SHARE_DIR}/plugins/platformthemes") + file(COPY "${QT_SHARE_DIR}/plugins/platformthemes" DESTINATION "${OUTPUT_DIR}") + endif() + + if (EXISTS "${QT_SHARE_DIR}/plugins/styles") + file(COPY "${QT_SHARE_DIR}/plugins/styles" DESTINATION "${OUTPUT_DIR}") + endif() + + if (EXISTS "${QT_SHARE_DIR}/plugins/imageformats") + file(COPY "${QT_SHARE_DIR}/plugins/imageformats" DESTINATION "${OUTPUT_DIR}") + endif() + + if (USE_SQL AND (EXISTS "${QT_SHARE_DIR}/plugins/sqldrivers")) + file(COPY "${QT_SHARE_DIR}/plugins/sqldrivers" DESTINATION "${OUTPUT_DIR}") + endif() + + if (USE_PRINT AND (EXISTS "${QT_SHARE_DIR}/plugins/printsupport")) + file(COPY "${QT_SHARE_DIR}/plugins/printsupport" DESTINATION "${OUTPUT_DIR}") + endif() + + if (USE_NETWORK AND (EXISTS "${QT_SHARE_DIR}/plugins/networkinformation")) + file(COPY "${QT_SHARE_DIR}/plugins/networkinformation" DESTINATION "${OUTPUT_DIR}") + endif() + + if (USE_MULTIMEDIA AND (EXISTS "${QT_SHARE_DIR}/plugins/multimedia")) + file(COPY "${QT_SHARE_DIR}/plugins/multimedia" DESTINATION "${OUTPUT_DIR}") + endif() + + if (EXISTS "${QT_SHARE_DIR}/translations") + + file(MAKE_DIRECTORY "${OUTPUT_DIR}/translations") + + if (EXISTS "${QT_SHARE_DIR}/translations/qtbase_ru.qm") + file(COPY "${QT_SHARE_DIR}/translations/qtbase_ru.qm" DESTINATION "${OUTPUT_DIR}/translations") + endif() + + if (EXISTS "${QT_SHARE_DIR}/translations/qtdeclarative_ru.qm") + file(COPY "${QT_SHARE_DIR}/translations/qtdeclarative_ru.qm" DESTINATION "${OUTPUT_DIR}/translations") + endif() + + if (USE_SERIALPORT AND (EXISTS "${QT_SHARE_DIR}/translations/qtserialport_ru.qm")) + file(COPY "${QT_SHARE_DIR}/translations/qtserialport_ru.qm" DESTINATION "${OUTPUT_DIR}/translations") + endif() + + if (USE_MULTIMEDIA AND (EXISTS "${QT_SHARE_DIR}/translations/qtmultimedia_ru.qm")) + file(COPY "${QT_SHARE_DIR}/translations/qtmultimedia_ru.qm" DESTINATION "${OUTPUT_DIR}/translations") + endif() + + endif() + +else() + + message(STATUS "'${TARGET_FILE}' not found. Exit.") + +endif() + diff --git a/_cmake/developer.cmake b/_cmake/developer.cmake new file mode 100644 index 0000000..530fa4a --- /dev/null +++ b/_cmake/developer.cmake @@ -0,0 +1,8 @@ + +if(NOT DEFINED SOFT_DEVELOPER OR "${SOFT_DEVELOPER}" STREQUAL "") + set(SOFT_DEVELOPER "Evgeny Teterin") +endif() + +if(NOT DEFINED DEVELOPER_DOMAIN OR "${DEVELOPER_DOMAIN}" STREQUAL "") + set(DEVELOPER_DOMAIN "poseon.ru") +endif() diff --git a/_cmake/lib_settings.cmake b/_cmake/lib_settings.cmake new file mode 100644 index 0000000..324e71d --- /dev/null +++ b/_cmake/lib_settings.cmake @@ -0,0 +1,29 @@ + +# Файл для подключения в основной проект через include +# Настройки для библиотек + +include(${CMAKE_CURRENT_LIST_DIR}/common.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/developer.cmake) +include(${CMAKE_CURRENT_LIST_DIR}/version.cmake) + +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${DISTRIB_DIR}) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${DISTRIB_DIR}) + +if (CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(RUNTIME_OUTPUT_NAME lib${PROJECT_NAME}.dll) + configure_file( + ${CMAKE_CURRENT_LIST_DIR}/versioninfo.rc.in + ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc + @ONLY + ) +else() + set(RUNTIME_OUTPUT_NAME lib${PROJECT_NAME}.so) +endif() + +configure_file( + ${CMAKE_CURRENT_LIST_DIR}/config.h.in + ${CMAKE_CURRENT_BINARY_DIR}/config.h + @ONLY +) + + diff --git a/_cmake/post_build.cmake b/_cmake/post_build.cmake new file mode 100644 index 0000000..39c5ee3 --- /dev/null +++ b/_cmake/post_build.cmake @@ -0,0 +1,40 @@ + +# Файл для подключения в основной проект через include + +# Вспомогательные действия после сборки проекта + +if(IS_DEBUG) + message(STATUS "Project '${PROJECT_NAME}' [post-build] DEBUG building: skip post-build actions") + return() +endif() + +# Включение/выключение поиска зависимостей: +set(COPY_DEPENDS ON) + +get_target_property(TARGET_TYPE ${PROJECT_NAME} TYPE) + +if(TARGET_TYPE STREQUAL "EXECUTABLE") + message(STATUS "${PROJECT_NAME} is an executable.") +elseif(TARGET_TYPE STREQUAL "STATIC_LIBRARY" OR TARGET_TYPE STREQUAL "SHARED_LIBRARY") + message(STATUS "${PROJECT_NAME} is a library.") +else() + message(STATUS "${PROJECT_NAME} is of another type: ${TARGET_TYPE}") +endif() + +if (COPY_DEPENDS) + + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo "Copying runtime dependencies..." + COMMAND ${CMAKE_COMMAND} + -DTARGET_NAME="${RUNTIME_OUTPUT_NAME}" + -DTARGET_TYPE="${TARGET_TYPE}" + -DOUTPUT_DIR="${DISTRIB_DIR}" + -DPREFIX="${CMAKE_PREFIX_PATH}" + -DQT_VERSION_MAJOR=${QT_VERSION_MAJOR} + -DCMAKE_SYSTEM_NAME="${CMAKE_SYSTEM_NAME}" + -P "${CMAKE_INC_DIR}/copy_depends.cmake" + COMMENT "Copying runtime dependencies for ${PROJECT_NAME}" + ) + +endif() + diff --git a/_cmake/setup_directories.cmake b/_cmake/setup_directories.cmake new file mode 100644 index 0000000..65235b8 --- /dev/null +++ b/_cmake/setup_directories.cmake @@ -0,0 +1,39 @@ + +set(ROOT_PROJECT_DIR + ${CMAKE_CURRENT_LIST_DIR}/.. +) + +cmake_path(NORMAL_PATH ROOT_PROJECT_DIR OUTPUT_VARIABLE ROOT_PROJECT_DIR) + +set(CMAKE_INC_DIR + ${ROOT_PROJECT_DIR}_cmake +) + +set(RESOURCES_DIR + ${ROOT_PROJECT_DIR}_resources +) + +set(COMMON_SOURCES_DIR + ${ROOT_PROJECT_DIR}_include +) + +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(SYSTEM_INCLUDE_DIR "${CMAKE_PREFIX_PATH}/include") +else () + set(SYSTEM_INCLUDE_DIR "/usr/include") +endif () + +get_filename_component(CXX_COMPILER_DIR "${CMAKE_CXX_COMPILER}" DIRECTORY) + +get_filename_component(CMAKE_BIN_DIR "${CMAKE_COMMAND}" DIRECTORY) + +set(ROOT_COMPILER_DIR + ${CXX_COMPILER_DIR}/.. +) + +cmake_path(NORMAL_PATH ROOT_COMPILER_DIR OUTPUT_VARIABLE ROOT_COMPILER_DIR) + +set(COMPILER_INCLUDE_DIR + ${ROOT_COMPILER_DIR}include +) + diff --git a/_cmake/target_options.cmake b/_cmake/target_options.cmake new file mode 100644 index 0000000..a6e7fbe --- /dev/null +++ b/_cmake/target_options.cmake @@ -0,0 +1,61 @@ + +# Файл для подключения в основной проект через include + +# Настройки компиляции для всего +target_compile_options(${PROJECT_NAME} PRIVATE + -Wall + -Wextra + -Wpedantic +) + + +if (DISABLE_HARD_WARNING_ERROR) + target_compile_options(${PROJECT_NAME} PRIVATE + -Wall + -Wextra + -Wpedantic + ) +else() + target_compile_options(${PROJECT_NAME} PRIVATE + -Wall # Все стандартные предупреждения + -Wextra # Дополнительные предупреждения + -Wpedantic # Соответствие стандарту C++ + -Werror # Превратить предупреждения в ошибки + -Wconversion # Предупреждения о неявных преобразованиях + -Wsign-conversion # Предупреждения о знаковых/беззнаковых преобразованиях + -Wshadow # Предупреждения о "тенях" переменных + -Wunused # Предупреждения о неиспользуемом коде + -Wold-style-cast # Запрет C-style кастов (только static_cast/dynamic_cast/...) + -Wnull-dereference # Предупреждения о возможных разыменованиях nullptr + -Wdouble-promotion # Предупреждения о неявном преобразовании float → double + -Wformat=2 # Строгая проверка printf/scanf + ) +endif() + +target_compile_definitions(${PROJECT_NAME} PRIVATE + QT_NO_FOREACH + QT_NO_URL_CAST_FROM_STRING + QT_NO_NARROWING_CONVERSIONS_IN_CONNECT + QT_STRICT_ITERATORS +) + +find_program(CLANG_TIDY_EXE NAMES "clang-tidy") + +if (CLANG_TIDY_EXE) + set(CMAKE_CXX_CLANG_TIDY "${CLANG_TIDY_EXE};-checks=*") +endif() + +if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo") + target_compile_options(${PROJECT_NAME} PRIVATE + -fsanitize=address # AddressSanitizer (поиск утечек, выходов за границы) + -fsanitize=undefined # UndefinedBehaviorSanitizer (UB-проверки) + -fsanitize=leak # LeakSanitizer (поиск утечек памяти) + -fno-omit-frame-pointer # Для лучшего стека вызовов в санитайзерах + ) + target_link_options(${PROJECT_NAME} PRIVATE + -fsanitize=address + -fsanitize=undefined + -fsanitize=leak + ) +endif() + diff --git a/_cmake/update_translations.cmake b/_cmake/update_translations.cmake new file mode 100644 index 0000000..f21186f --- /dev/null +++ b/_cmake/update_translations.cmake @@ -0,0 +1,70 @@ + +# Файл для подключения в основной проект через include + +# Обновление и генерация файлов переводов +# Языки переводов перечислить в переменной LNG + +if(IS_DEBUG) + message(STATUS "Project '${PROJECT_NAME}' [translations] DEBUG building: skip .qm generation") + return() +endif() + +# Если список языков не задан в основном CMakeLists.txt — применяем по умолчанию +if(NOT DEFINED LNG) + set(LNG en ru) + message(STATUS "Project '${PROJECT_NAME}' [translations] LNG not set, use default: ${LNG}") +else() + message(STATUS "Project '${PROJECT_NAME}' [translations] Use LNG: ${LNG}") +endif() + +# Подключить LinguistTools +find_package(Qt${QT_VERSION_MAJOR}LinguistTools REQUIRED) + +# Полные пути утилит +get_target_property(LUPDATE_EXECUTABLE Qt${QT_VERSION_MAJOR}::lupdate IMPORTED_LOCATION) +get_target_property(LRELEASE_EXECUTABLE Qt${QT_VERSION_MAJOR}::lrelease IMPORTED_LOCATION) + +# Каталог генерации файлов ts и qm файлов +set(TRANSLATIONS_DIR "${CMAKE_CURRENT_SOURCE_DIR}/translations") +set(QM_OUTPUT_DIR "${DISTRIB_DIR}/translations") +file(MAKE_DIRECTORY "${TRANSLATIONS_DIR}") +file(MAKE_DIRECTORY "${QM_OUTPUT_DIR}") + +# Список TS файлов в зависимости от списка языков +set(TS_FILES "") + +# Обновление каждого файла с добавлением в список +foreach(LANG ${LNG}) + + set(TS_FILE "${TRANSLATIONS_DIR}/${PROJECT_NAME}_${LANG}.ts") + list(APPEND TS_FILES "${TS_FILE}") + message(STATUS "Project '${PROJECT_NAME}' Update translation file: ${TS_FILE}") + + execute_process(COMMAND ${LUPDATE_EXECUTABLE} + ${PROJECT_SOURCES} + -ts ${TS_FILE} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + ) + +endforeach() + +# Генерация файлов qm +set(QM_FILES "") + +foreach(TS ${TS_FILES}) + + get_filename_component(TS_NAME_WE ${TS} NAME_WE) + set(QM ${QM_OUTPUT_DIR}/${TS_NAME_WE}.qm) + list(APPEND QM_FILES ${QM}) + + add_custom_command( + OUTPUT ${QM} + COMMAND ${LRELEASE_EXECUTABLE} ${TS} -qm ${QM} + DEPENDS ${TS} + COMMENT "Generating ${QM}" + ) +endforeach() + +add_custom_target(translations_qm ALL DEPENDS ${QM_FILES}) + +add_dependencies(translations_qm ${PROJECT_NAME}) diff --git a/_cmake/version.cmake b/_cmake/version.cmake new file mode 100644 index 0000000..c320df2 --- /dev/null +++ b/_cmake/version.cmake @@ -0,0 +1,52 @@ + +# Variables for generating the version. +# Used in the application (config.h file and versioninfo.rc) +# + +# Получаем полный временной штамп в UTC (пример: "2025-04-16 12:33:58 UTC") +string(TIMESTAMP BUILD_DATE "%Y-%m-%d %H:%M:%S UTC" UTC) +string(TIMESTAMP VERSION_DOY "%j" UTC) # день в году (001..366) + +# Извлекаем компоненты даты и времени +string(SUBSTRING "${BUILD_DATE}" 0 4 YEAR_STR) +string(SUBSTRING "${BUILD_DATE}" 2 2 VERSION_YY) +string(SUBSTRING "${BUILD_DATE}" 5 2 VERSION_MM) +string(SUBSTRING "${BUILD_DATE}" 8 2 VERSION_DD) +string(SUBSTRING "${BUILD_DATE}" 11 2 VERSION_HH) +string(SUBSTRING "${BUILD_DATE}" 14 2 VERSION_MIN) +string(SUBSTRING "${BUILD_DATE}" 17 2 VERSION_SS) + +# Убираем ведущие нули путём преобразования в числа +math(EXPR VERSION_YY_NOZERO "${VERSION_YY}") +math(EXPR VERSION_MM_NOZERO "${VERSION_MM}") +math(EXPR VERSION_DD_NOZERO "${VERSION_DD}") +math(EXPR VERSION_HH_NOZERO "${VERSION_HH}") +math(EXPR VERSION_MIN_NOZERO "${VERSION_MIN}") +math(EXPR VERSION_SS_NOZERO "${VERSION_SS}") +math(EXPR VERSION_DOY_NOZERO "${VERSION_DOY}") + +if (VERSION_FULLDATE) + # Полная дата: YY.MM.DD.NNN + + # Считаем количество минут с начала суток + math(EXPR VERSION_NNN + "${VERSION_HH_NOZERO} * 60 + ${VERSION_MIN_NOZERO}" + ) + + set(PROJECT_VERSION_MAJOR ${VERSION_YY_NOZERO}) + set(PROJECT_VERSION_MINOR ${VERSION_MM_NOZERO}) + set(PROJECT_VERSION_PATCH ${VERSION_DD_NOZERO}) + set(BUILD_NUM ${VERSION_NNN}) + +else() + # Версия от MAJOR, MINOR + YY + день года + # MAJOR и MINOR должны быть заданы ранее через `project(... VERSION ...)` + + set(PROJECT_VERSION_PATCH ${VERSION_YY_NOZERO}) + set(BUILD_NUM ${VERSION_DOY_NOZERO}) +endif() + +set(ORIGINAL_FILE_NAME ${RUNTIME_OUTPUT_NAME}) + +message(STATUS "Project '${PROJECT_NAME}' version: ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}.${BUILD_NUM}") + diff --git a/_cmake/versioninfo.rc.in b/_cmake/versioninfo.rc.in new file mode 100644 index 0000000..14c4204 --- /dev/null +++ b/_cmake/versioninfo.rc.in @@ -0,0 +1,35 @@ +1 TYPELIB "versioninfo.rc" + +1 VERSIONINFO + FILEVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, @BUILD_NUM@ + PRODUCTVERSION @PROJECT_VERSION_MAJOR@, @PROJECT_VERSION_MINOR@, @PROJECT_VERSION_PATCH@, @BUILD_NUM@ + FILEFLAGSMASK 0x3fL +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "@SOFT_DEVELOPER@" + VALUE "FileDescription", "@PROJECT_DESCRIPTION@" + VALUE "FileVersion","@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@BUILD_NUM@" + VALUE "InternalName", "@PROJECT_NAME@" + VALUE "LegalCopyright", "Copyright (c) @YEAR_STR@ @SOFT_DEVELOPER@" + VALUE "OriginalFilename", "@ORIGINAL_FILE_NAME@" + VALUE "ProductName", "@PROJECT_NAME@" + VALUE "ProductVersion","@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@.@BUILD_NUM@" + VALUE "BuildDate", "@BUILD_DATE@" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END diff --git a/_include/application_config.cpp b/_include/application_config.cpp new file mode 100644 index 0000000..cbb0c97 --- /dev/null +++ b/_include/application_config.cpp @@ -0,0 +1,335 @@ +/**************************************************************************** +** Copyright (c) 2025 Evgeny Teterin (nayk) +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#include "application_config.h" +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) +# include +#endif + +//============================================================================== +bool systemUseDarkTheme() +{ + QApplication *application = qobject_cast( QApplication::instance() ); + if (!application) return false; + + QPalette appPalette = application->palette(); + bool darkTheme {false}; + + int n = -1; + +#if QT_VERSION > QT_VERSION_CHECK(6, 5, 0) + + if (application->styleHints()->colorScheme() == Qt::ColorScheme::Light) n = 1; + else if (application->styleHints()->colorScheme() == Qt::ColorScheme::Dark) n = 0; +#endif + +#ifdef Q_OS_WIN + if (n < 0) { + + QSettings registry(R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize)", + QSettings::NativeFormat); + //n = registry.value("SystemUsesLightTheme",-1).toInt(); + n = registry.value("AppsUseLightTheme", -1).toInt(); + } +#endif + if (n < 0) { + + QRgb c1 = appPalette.color(QPalette::Window).rgb(); + QRgb c2 = appPalette.color(QPalette::WindowText).rgb(); + + darkTheme = ((qRed(c1) * 11 + qGreen(c1) * 16 + qBlue(c1) * 5) / 32) + < ((qRed(c2) * 11 + qGreen(c2) * 16 + qBlue(c2) * 5) / 32); + } + else { + + darkTheme = (n == 0); + } + + return darkTheme; +} + +//============================================================================== +void Application::initialize() +{ +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + QTextCodec::setCodecForLocale(QTextCodec::codecForName("UTF-8")); +#endif + +#if defined (PROG_NAME) + QApplication::setApplicationName( QString(PROG_NAME) ); +#endif + +#if defined (SOFT_DEVELOPER) + QApplication::setOrganizationName( QString(SOFT_DEVELOPER) ); +#endif + +#if defined (DEVELOPER_DOMAIN) + QApplication::setOrganizationDomain( QString(DEVELOPER_DOMAIN) ); +#endif + +#if defined (PROG_VERSION) + QApplication::setApplicationVersion( QString(PROG_VERSION) ); +#endif + +#if defined (BUILD_DATE) + qApp->setProperty( "buildDate", QString(BUILD_DATE) ); +#endif +} +//============================================================================== +QString Application::applicationRootPath() +{ + QString path { QApplication::applicationDirPath() }; + + if ( path.right(1) != "/" ) { + + path += "/"; + } + + if (path.right(5) == "/bin/") { + + path.remove( path.length() - 4, 4 ); + } + + return path; +} +//============================================================================== +void Application::installTranslations(const QString &lng) +{ + const QString dirName { applicationRootPath() + "translations/" }; + QDir translationsDir { dirName }; + + QStringList filesList = translationsDir.entryList( + QStringList() << QString("*_%1.qm").arg(lng), + QDir::Files + ); + + for (const QString &fileName: std::as_const(filesList)) { + + QTranslator *translator = new QTranslator(QApplication::instance()); + + if (translator->load( dirName + fileName )) { + + QApplication::instance()->installTranslator( translator ); + } + else { + + delete translator; + } + } +} +//============================================================================== +bool Application::updateApplicationStyle(AppTheme appTheme) +{ + QApplication *application = qobject_cast( QApplication::instance() ); + if (!application) return false; + + application->setStyleSheet(""); + + if (QStyleFactory::keys().contains("Fusion")) { + + application->setStyle( QStyleFactory::create("Fusion") ); + } + + bool darkTheme = (appTheme == SystemTheme) ? systemUseDarkTheme() : appTheme == DarkTheme; + QPalette appPalette = application->palette(); + + appPalette.setColor( QPalette::Highlight, QColor(55, 174, 254)); + appPalette.setColor( QPalette::HighlightedText, QColor(245, 250, 255)); + appPalette.setColor( QPalette::Link, QColor(55, 174, 254)); + appPalette.setColor( QPalette::LinkVisited, QColor(52, 103, 146)); + appPalette.setColor( QPalette::Disabled, QPalette::Link, QColor(52, 103, 146)); + appPalette.setColor( QPalette::Disabled, QPalette::LinkVisited, QColor(40, 90, 130)); + + if (darkTheme) { + + appPalette.setColor( QPalette::Window, QColor(30, 35, 40)); + appPalette.setColor( QPalette::WindowText, QColor(245, 250, 255)); + appPalette.setColor( QPalette::Base, QColor(45, 50, 55)); + appPalette.setColor( QPalette::AlternateBase, QColor(48, 53, 58)); + appPalette.setColor( QPalette::ToolTipBase, QColor(60, 65, 70)); + appPalette.setColor( QPalette::ToolTipText, QColor(200, 205, 210)); + appPalette.setColor( QPalette::Text, QColor(245, 250, 255)); + appPalette.setColor( QPalette::Button, QColor(55, 60, 65)); + appPalette.setColor( QPalette::ButtonText, QColor(245, 250, 255)); + appPalette.setColor( QPalette::BrightText, QColor(194, 232, 255)); + appPalette.setColor( QPalette::PlaceholderText, QColor(200, 205, 210)); + appPalette.setColor( QPalette::Disabled, QPalette::WindowText, QColor(157, 157, 157)); + appPalette.setColor( QPalette::Disabled, QPalette::Base, QColor(30, 35, 40)); + appPalette.setColor( QPalette::Disabled, QPalette::AlternateBase, QColor(33, 38, 43)); + appPalette.setColor( QPalette::Disabled, QPalette::ToolTipBase, QColor(200, 205, 210)); + appPalette.setColor( QPalette::Disabled, QPalette::ToolTipText, QColor(5, 10, 15)); + appPalette.setColor( QPalette::Disabled, QPalette::Text, QColor(150, 155, 160)); + appPalette.setColor( QPalette::Disabled, QPalette::ButtonText, QColor(150, 155, 160)); + appPalette.setColor( QPalette::Disabled, QPalette::BrightText, QColor(150, 193, 218)); + + } + else { // light theme + + appPalette.setColor( QPalette::Window, QColor(230, 235, 240)); + appPalette.setColor( QPalette::WindowText, QColor(0, 3, 5)); + appPalette.setColor( QPalette::Base, QColor(245, 250, 255)); + appPalette.setColor( QPalette::AlternateBase, QColor(242, 247, 252)); + appPalette.setColor( QPalette::ToolTipBase, QColor(255, 255, 220)); + appPalette.setColor( QPalette::ToolTipText, QColor(0, 0, 0)); + appPalette.setColor( QPalette::Text, QColor(0, 3, 5)); + appPalette.setColor( QPalette::Button, QColor(225, 230, 235)); + appPalette.setColor( QPalette::ButtonText, QColor(0, 3, 5)); + appPalette.setColor( QPalette::BrightText, QColor(255, 255, 255)); + appPalette.setColor( QPalette::PlaceholderText, QColor(5, 10, 15)); + appPalette.setColor( QPalette::Disabled, QPalette::WindowText, QColor(120, 120, 120)); + appPalette.setColor( QPalette::Disabled, QPalette::Base, QColor(240, 240, 240)); + appPalette.setColor( QPalette::Disabled, QPalette::AlternateBase, QColor(247, 247, 247)); + appPalette.setColor( QPalette::Disabled, QPalette::ToolTipBase, QColor(255, 255, 220)); + appPalette.setColor( QPalette::Disabled, QPalette::ToolTipText, QColor(0, 0, 0)); + appPalette.setColor( QPalette::Disabled, QPalette::Text, QColor(120, 120, 120)); + appPalette.setColor( QPalette::Disabled, QPalette::ButtonText, QColor(120, 120, 120)); + appPalette.setColor( QPalette::Disabled, QPalette::BrightText, QColor(255, 255, 255)); + } + + application->setPalette(appPalette); + + QString qssFile {":/qss/style.qss"}; + QString qss; + + if (QFile::exists(qssFile)) { + + QFile file(qssFile); + + if (!file.open(QFile::ReadOnly)) return false; + + QByteArray buf; + + while (!file.atEnd()) { + + buf.append( file.read(1024) ); + } + + file.close(); + + qss = QString(buf); + + QRgb rgb = appPalette.color(QPalette::Highlight).rgb(); + QString clStr = QString("rgb(%1, %2, %3)").arg(qRed(rgb)).arg(qGreen(rgb)).arg(qBlue(rgb)); + + qss.replace("$MENU_ITEM_SELECT_COLOR", clStr); + + rgb = appPalette.color(QPalette::LinkVisited).rgb(); + clStr = QString("rgb(%1, %2, %3)").arg(qRed(rgb)).arg(qGreen(rgb)).arg(qBlue(rgb)); + + qss.replace("$MENU_ITEM_SELECT_BORDER", clStr); + + double checkBoxMetric = QApplication::primaryScreen()->logicalDotsPerInch() / 4.3; + double toggleMetric = checkBoxMetric * 1.2; + + clStr = QString("%1px").arg(qRound( checkBoxMetric )); + + qss.replace("$CHECKBOX_SIZE", clStr); + + clStr = QString("%1px").arg(qRound( toggleMetric )); + + qss.replace("$TOGGLE_HEIGHT", clStr); + + clStr = QString("%1px").arg(qRound( toggleMetric * 1.5 )); + + qss.replace("$TOGGLE_WIDTH", clStr); + + clStr = darkTheme ? "dark" : "light"; + + qss.replace("$THEME", clStr); + + if (toggleMetric > 32) { + + qss.replace("toggle_checked.png", "toggle_checked@2x.png"); + qss.replace("toggle_unchecked.png", "toggle_unchecked@2x.png"); + qss.replace("toggle_checked_disabled.png", "toggle_checked_disabled@2x.png"); + qss.replace("toggle_unchecked_disabled.png", "toggle_unchecked_disabled@2x.png"); + qss.replace("toggle_checked_focus.png", "toggle_checked_focus@2x.png"); + qss.replace("toggle_unchecked_focus.png", "toggle_unchecked_focus@2x.png"); + qss.replace("toggle_pressed.png", "toggle_pressed@2x.png"); + } + + if (checkBoxMetric > 32) { + + qss.replace("_checked.png", "_checked@2x.png"); + qss.replace("_unchecked.png", "_unchecked@2x.png"); + qss.replace("_indeterminate.png", "_indeterminate@2x.png"); + qss.replace("_disabled.png", "_disabled@2x.png"); + qss.replace("_focus.png", "_focus@2x.png"); + qss.replace("_pressed.png", "_pressed@2x.png"); + } + } + + qssFile = applicationRootPath() + "style.qss"; + + if (QFile::exists(qssFile)) { + + QFile file(qssFile); + + if (!file.open(QFile::ReadOnly)) return false; + + QByteArray buf; + + while (!file.atEnd()) { + + buf.append( file.read(1024) ); + } + + file.close(); + + qss += "\n" + QString(buf); + } + + application->setStyleSheet(qss); + application->setProperty("AppsUseDarkTheme", darkTheme); + return true; +} +//============================================================================== +bool Application::applicationUseDarkTheme() +{ + QApplication *application = qobject_cast( QApplication::instance() ); + if (!application) return false; + + if (!application->property("AppsUseDarkTheme").isNull()) { + + return application->property("AppsUseDarkTheme").toBool(); + } + + bool darkTheme = systemUseDarkTheme(); + application->setProperty("AppsUseDarkTheme", darkTheme); + return darkTheme; +} +//============================================================================== diff --git a/_include/application_config.h b/_include/application_config.h new file mode 100644 index 0000000..7356dd9 --- /dev/null +++ b/_include/application_config.h @@ -0,0 +1,54 @@ +/**************************************************************************** +** Copyright (c) 2025 Evgeny Teterin (nayk) +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#pragma once +#ifndef APP_CONFIG_H +#define APP_CONFIG_H + +#include + +//============================================================================== +class Application +{ + Q_DISABLE_COPY(Application) + +public: + enum AppTheme { + SystemTheme = 0, + LightTheme = 1, + DarkTheme = 2 + }; + + static void initialize(); + static QString applicationRootPath(); + static void installTranslations(const QString &lng = "ru"); + static bool updateApplicationStyle(AppTheme appTheme = AppTheme::SystemTheme); + static bool applicationUseDarkTheme(); + +private: + Application() = delete; +}; + +#endif // APP_CONFIG_H +//============================================================================== diff --git a/_resources/icons.qrc b/_resources/icons.qrc new file mode 100644 index 0000000..8bbd249 --- /dev/null +++ b/_resources/icons.qrc @@ -0,0 +1,7 @@ + + + icons/cancel-40.png + icons/ok-40.png + icons/shutdown-40.png + + diff --git a/_resources/icons/cancel-40.png b/_resources/icons/cancel-40.png new file mode 100644 index 0000000..13885e5 Binary files /dev/null and b/_resources/icons/cancel-40.png differ diff --git a/_resources/icons/ok-40.png b/_resources/icons/ok-40.png new file mode 100644 index 0000000..81a445c Binary files /dev/null and b/_resources/icons/ok-40.png differ diff --git a/_resources/icons/shutdown-40.png b/_resources/icons/shutdown-40.png new file mode 100644 index 0000000..3013655 Binary files /dev/null and b/_resources/icons/shutdown-40.png differ diff --git a/_resources/images/main_icon.png b/_resources/images/main_icon.png new file mode 100644 index 0000000..5770cc4 Binary files /dev/null and b/_resources/images/main_icon.png differ diff --git a/_resources/images/main_title.png b/_resources/images/main_title.png new file mode 100644 index 0000000..b0edb75 Binary files /dev/null and b/_resources/images/main_title.png differ diff --git a/_resources/images/screenshot1.png b/_resources/images/screenshot1.png new file mode 100644 index 0000000..16881ea Binary files /dev/null and b/_resources/images/screenshot1.png differ diff --git a/_resources/images/screenshot2.png b/_resources/images/screenshot2.png new file mode 100644 index 0000000..5d4f67e Binary files /dev/null and b/_resources/images/screenshot2.png differ diff --git a/_resources/main.ico b/_resources/main.ico new file mode 100644 index 0000000..e271339 Binary files /dev/null and b/_resources/main.ico differ diff --git a/_resources/main.qrc b/_resources/main.qrc new file mode 100644 index 0000000..f13e3d1 --- /dev/null +++ b/_resources/main.qrc @@ -0,0 +1,6 @@ + + + images/main_icon.png + images/main_title.png + + diff --git a/_resources/main_icon.rc b/_resources/main_icon.rc new file mode 100644 index 0000000..59f679d --- /dev/null +++ b/_resources/main_icon.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON "main.ico" diff --git a/_resources/qss/dark/checkbox_checked.png b/_resources/qss/dark/checkbox_checked.png new file mode 100644 index 0000000..3687e56 Binary files /dev/null and b/_resources/qss/dark/checkbox_checked.png differ diff --git a/_resources/qss/dark/checkbox_checked@2x.png b/_resources/qss/dark/checkbox_checked@2x.png new file mode 100644 index 0000000..e7a6b1a Binary files /dev/null and b/_resources/qss/dark/checkbox_checked@2x.png differ diff --git a/_resources/qss/dark/checkbox_checked_disabled.png b/_resources/qss/dark/checkbox_checked_disabled.png new file mode 100644 index 0000000..e3cb2f1 Binary files /dev/null and b/_resources/qss/dark/checkbox_checked_disabled.png differ diff --git a/_resources/qss/dark/checkbox_checked_disabled@2x.png b/_resources/qss/dark/checkbox_checked_disabled@2x.png new file mode 100644 index 0000000..0c8c28a Binary files /dev/null and b/_resources/qss/dark/checkbox_checked_disabled@2x.png differ diff --git a/_resources/qss/dark/checkbox_checked_focus.png b/_resources/qss/dark/checkbox_checked_focus.png new file mode 100644 index 0000000..58982ce Binary files /dev/null and b/_resources/qss/dark/checkbox_checked_focus.png differ diff --git a/_resources/qss/dark/checkbox_checked_focus@2x.png b/_resources/qss/dark/checkbox_checked_focus@2x.png new file mode 100644 index 0000000..ba33ba4 Binary files /dev/null and b/_resources/qss/dark/checkbox_checked_focus@2x.png differ diff --git a/_resources/qss/dark/checkbox_checked_pressed.png b/_resources/qss/dark/checkbox_checked_pressed.png new file mode 100644 index 0000000..f104bb2 Binary files /dev/null and b/_resources/qss/dark/checkbox_checked_pressed.png differ diff --git a/_resources/qss/dark/checkbox_checked_pressed@2x.png b/_resources/qss/dark/checkbox_checked_pressed@2x.png new file mode 100644 index 0000000..bb972d6 Binary files /dev/null and b/_resources/qss/dark/checkbox_checked_pressed@2x.png differ diff --git a/_resources/qss/dark/checkbox_indeterminate.png b/_resources/qss/dark/checkbox_indeterminate.png new file mode 100644 index 0000000..8159551 Binary files /dev/null and b/_resources/qss/dark/checkbox_indeterminate.png differ diff --git a/_resources/qss/dark/checkbox_indeterminate@2x.png b/_resources/qss/dark/checkbox_indeterminate@2x.png new file mode 100644 index 0000000..75864b4 Binary files /dev/null and b/_resources/qss/dark/checkbox_indeterminate@2x.png differ diff --git a/_resources/qss/dark/checkbox_indeterminate_disabled.png b/_resources/qss/dark/checkbox_indeterminate_disabled.png new file mode 100644 index 0000000..181625a Binary files /dev/null and b/_resources/qss/dark/checkbox_indeterminate_disabled.png differ diff --git a/_resources/qss/dark/checkbox_indeterminate_disabled@2x.png b/_resources/qss/dark/checkbox_indeterminate_disabled@2x.png new file mode 100644 index 0000000..0d32c78 Binary files /dev/null and b/_resources/qss/dark/checkbox_indeterminate_disabled@2x.png differ diff --git a/_resources/qss/dark/checkbox_indeterminate_focus.png b/_resources/qss/dark/checkbox_indeterminate_focus.png new file mode 100644 index 0000000..d7b19f6 Binary files /dev/null and b/_resources/qss/dark/checkbox_indeterminate_focus.png differ diff --git a/_resources/qss/dark/checkbox_indeterminate_focus@2x.png b/_resources/qss/dark/checkbox_indeterminate_focus@2x.png new file mode 100644 index 0000000..d6403ca Binary files /dev/null and b/_resources/qss/dark/checkbox_indeterminate_focus@2x.png differ diff --git a/_resources/qss/dark/checkbox_indeterminate_pressed.png b/_resources/qss/dark/checkbox_indeterminate_pressed.png new file mode 100644 index 0000000..37f46ca Binary files /dev/null and b/_resources/qss/dark/checkbox_indeterminate_pressed.png differ diff --git a/_resources/qss/dark/checkbox_indeterminate_pressed@2x.png b/_resources/qss/dark/checkbox_indeterminate_pressed@2x.png new file mode 100644 index 0000000..aa7493e Binary files /dev/null and b/_resources/qss/dark/checkbox_indeterminate_pressed@2x.png differ diff --git a/_resources/qss/dark/checkbox_unchecked.png b/_resources/qss/dark/checkbox_unchecked.png new file mode 100644 index 0000000..e363ed6 Binary files /dev/null and b/_resources/qss/dark/checkbox_unchecked.png differ diff --git a/_resources/qss/dark/checkbox_unchecked@2x.png b/_resources/qss/dark/checkbox_unchecked@2x.png new file mode 100644 index 0000000..520f5e2 Binary files /dev/null and b/_resources/qss/dark/checkbox_unchecked@2x.png differ diff --git a/_resources/qss/dark/checkbox_unchecked_disabled.png b/_resources/qss/dark/checkbox_unchecked_disabled.png new file mode 100644 index 0000000..066185e Binary files /dev/null and b/_resources/qss/dark/checkbox_unchecked_disabled.png differ diff --git a/_resources/qss/dark/checkbox_unchecked_disabled@2x.png b/_resources/qss/dark/checkbox_unchecked_disabled@2x.png new file mode 100644 index 0000000..9c80ad7 Binary files /dev/null and b/_resources/qss/dark/checkbox_unchecked_disabled@2x.png differ diff --git a/_resources/qss/dark/checkbox_unchecked_focus.png b/_resources/qss/dark/checkbox_unchecked_focus.png new file mode 100644 index 0000000..366b868 Binary files /dev/null and b/_resources/qss/dark/checkbox_unchecked_focus.png differ diff --git a/_resources/qss/dark/checkbox_unchecked_focus@2x.png b/_resources/qss/dark/checkbox_unchecked_focus@2x.png new file mode 100644 index 0000000..4ab2173 Binary files /dev/null and b/_resources/qss/dark/checkbox_unchecked_focus@2x.png differ diff --git a/_resources/qss/dark/checkbox_unchecked_pressed.png b/_resources/qss/dark/checkbox_unchecked_pressed.png new file mode 100644 index 0000000..d9a0bf7 Binary files /dev/null and b/_resources/qss/dark/checkbox_unchecked_pressed.png differ diff --git a/_resources/qss/dark/checkbox_unchecked_pressed@2x.png b/_resources/qss/dark/checkbox_unchecked_pressed@2x.png new file mode 100644 index 0000000..9e2b051 Binary files /dev/null and b/_resources/qss/dark/checkbox_unchecked_pressed@2x.png differ diff --git a/_resources/qss/dark/radio_checked.png b/_resources/qss/dark/radio_checked.png new file mode 100644 index 0000000..acb8901 Binary files /dev/null and b/_resources/qss/dark/radio_checked.png differ diff --git a/_resources/qss/dark/radio_checked@2x.png b/_resources/qss/dark/radio_checked@2x.png new file mode 100644 index 0000000..e190476 Binary files /dev/null and b/_resources/qss/dark/radio_checked@2x.png differ diff --git a/_resources/qss/dark/radio_checked_disabled.png b/_resources/qss/dark/radio_checked_disabled.png new file mode 100644 index 0000000..49df439 Binary files /dev/null and b/_resources/qss/dark/radio_checked_disabled.png differ diff --git a/_resources/qss/dark/radio_checked_disabled@2x.png b/_resources/qss/dark/radio_checked_disabled@2x.png new file mode 100644 index 0000000..a9ffd40 Binary files /dev/null and b/_resources/qss/dark/radio_checked_disabled@2x.png differ diff --git a/_resources/qss/dark/radio_checked_focus.png b/_resources/qss/dark/radio_checked_focus.png new file mode 100644 index 0000000..4bd472e Binary files /dev/null and b/_resources/qss/dark/radio_checked_focus.png differ diff --git a/_resources/qss/dark/radio_checked_focus@2x.png b/_resources/qss/dark/radio_checked_focus@2x.png new file mode 100644 index 0000000..aed5e0c Binary files /dev/null and b/_resources/qss/dark/radio_checked_focus@2x.png differ diff --git a/_resources/qss/dark/radio_checked_pressed.png b/_resources/qss/dark/radio_checked_pressed.png new file mode 100644 index 0000000..ebb323b Binary files /dev/null and b/_resources/qss/dark/radio_checked_pressed.png differ diff --git a/_resources/qss/dark/radio_checked_pressed@2x.png b/_resources/qss/dark/radio_checked_pressed@2x.png new file mode 100644 index 0000000..ffe0fd8 Binary files /dev/null and b/_resources/qss/dark/radio_checked_pressed@2x.png differ diff --git a/_resources/qss/dark/radio_unchecked.png b/_resources/qss/dark/radio_unchecked.png new file mode 100644 index 0000000..9ffddc4 Binary files /dev/null and b/_resources/qss/dark/radio_unchecked.png differ diff --git a/_resources/qss/dark/radio_unchecked@2x.png b/_resources/qss/dark/radio_unchecked@2x.png new file mode 100644 index 0000000..2160a32 Binary files /dev/null and b/_resources/qss/dark/radio_unchecked@2x.png differ diff --git a/_resources/qss/dark/radio_unchecked_disabled.png b/_resources/qss/dark/radio_unchecked_disabled.png new file mode 100644 index 0000000..7ddff64 Binary files /dev/null and b/_resources/qss/dark/radio_unchecked_disabled.png differ diff --git a/_resources/qss/dark/radio_unchecked_disabled@2x.png b/_resources/qss/dark/radio_unchecked_disabled@2x.png new file mode 100644 index 0000000..4de5d0d Binary files /dev/null and b/_resources/qss/dark/radio_unchecked_disabled@2x.png differ diff --git a/_resources/qss/dark/radio_unchecked_focus.png b/_resources/qss/dark/radio_unchecked_focus.png new file mode 100644 index 0000000..e62b996 Binary files /dev/null and b/_resources/qss/dark/radio_unchecked_focus.png differ diff --git a/_resources/qss/dark/radio_unchecked_focus@2x.png b/_resources/qss/dark/radio_unchecked_focus@2x.png new file mode 100644 index 0000000..eaf7bc2 Binary files /dev/null and b/_resources/qss/dark/radio_unchecked_focus@2x.png differ diff --git a/_resources/qss/dark/radio_unchecked_pressed.png b/_resources/qss/dark/radio_unchecked_pressed.png new file mode 100644 index 0000000..8aaa343 Binary files /dev/null and b/_resources/qss/dark/radio_unchecked_pressed.png differ diff --git a/_resources/qss/dark/radio_unchecked_pressed@2x.png b/_resources/qss/dark/radio_unchecked_pressed@2x.png new file mode 100644 index 0000000..ba4f83b Binary files /dev/null and b/_resources/qss/dark/radio_unchecked_pressed@2x.png differ diff --git a/_resources/qss/dark/toggle_checked.png b/_resources/qss/dark/toggle_checked.png new file mode 100644 index 0000000..8c0d593 Binary files /dev/null and b/_resources/qss/dark/toggle_checked.png differ diff --git a/_resources/qss/dark/toggle_checked@2x.png b/_resources/qss/dark/toggle_checked@2x.png new file mode 100644 index 0000000..dd2d5c2 Binary files /dev/null and b/_resources/qss/dark/toggle_checked@2x.png differ diff --git a/_resources/qss/dark/toggle_checked_disabled.png b/_resources/qss/dark/toggle_checked_disabled.png new file mode 100644 index 0000000..aa00d89 Binary files /dev/null and b/_resources/qss/dark/toggle_checked_disabled.png differ diff --git a/_resources/qss/dark/toggle_checked_disabled@2x.png b/_resources/qss/dark/toggle_checked_disabled@2x.png new file mode 100644 index 0000000..e9513a4 Binary files /dev/null and b/_resources/qss/dark/toggle_checked_disabled@2x.png differ diff --git a/_resources/qss/dark/toggle_checked_focus.png b/_resources/qss/dark/toggle_checked_focus.png new file mode 100644 index 0000000..5bedaf0 Binary files /dev/null and b/_resources/qss/dark/toggle_checked_focus.png differ diff --git a/_resources/qss/dark/toggle_checked_focus@2x.png b/_resources/qss/dark/toggle_checked_focus@2x.png new file mode 100644 index 0000000..25fb51a Binary files /dev/null and b/_resources/qss/dark/toggle_checked_focus@2x.png differ diff --git a/_resources/qss/dark/toggle_pressed.png b/_resources/qss/dark/toggle_pressed.png new file mode 100644 index 0000000..0c47f24 Binary files /dev/null and b/_resources/qss/dark/toggle_pressed.png differ diff --git a/_resources/qss/dark/toggle_pressed@2x.png b/_resources/qss/dark/toggle_pressed@2x.png new file mode 100644 index 0000000..ba04282 Binary files /dev/null and b/_resources/qss/dark/toggle_pressed@2x.png differ diff --git a/_resources/qss/dark/toggle_unchecked.png b/_resources/qss/dark/toggle_unchecked.png new file mode 100644 index 0000000..ef7d0e7 Binary files /dev/null and b/_resources/qss/dark/toggle_unchecked.png differ diff --git a/_resources/qss/dark/toggle_unchecked@2x.png b/_resources/qss/dark/toggle_unchecked@2x.png new file mode 100644 index 0000000..6501a15 Binary files /dev/null and b/_resources/qss/dark/toggle_unchecked@2x.png differ diff --git a/_resources/qss/dark/toggle_unchecked_disabled.png b/_resources/qss/dark/toggle_unchecked_disabled.png new file mode 100644 index 0000000..d0ca53e Binary files /dev/null and b/_resources/qss/dark/toggle_unchecked_disabled.png differ diff --git a/_resources/qss/dark/toggle_unchecked_disabled@2x.png b/_resources/qss/dark/toggle_unchecked_disabled@2x.png new file mode 100644 index 0000000..aa7f1b8 Binary files /dev/null and b/_resources/qss/dark/toggle_unchecked_disabled@2x.png differ diff --git a/_resources/qss/dark/toggle_unchecked_focus.png b/_resources/qss/dark/toggle_unchecked_focus.png new file mode 100644 index 0000000..bd2166e Binary files /dev/null and b/_resources/qss/dark/toggle_unchecked_focus.png differ diff --git a/_resources/qss/dark/toggle_unchecked_focus@2x.png b/_resources/qss/dark/toggle_unchecked_focus@2x.png new file mode 100644 index 0000000..4c16a36 Binary files /dev/null and b/_resources/qss/dark/toggle_unchecked_focus@2x.png differ diff --git a/_resources/qss/light/checkbox_checked.png b/_resources/qss/light/checkbox_checked.png new file mode 100644 index 0000000..fd1d4a4 Binary files /dev/null and b/_resources/qss/light/checkbox_checked.png differ diff --git a/_resources/qss/light/checkbox_checked@2x.png b/_resources/qss/light/checkbox_checked@2x.png new file mode 100644 index 0000000..bbcc435 Binary files /dev/null and b/_resources/qss/light/checkbox_checked@2x.png differ diff --git a/_resources/qss/light/checkbox_checked_disabled.png b/_resources/qss/light/checkbox_checked_disabled.png new file mode 100644 index 0000000..34492ed Binary files /dev/null and b/_resources/qss/light/checkbox_checked_disabled.png differ diff --git a/_resources/qss/light/checkbox_checked_disabled@2x.png b/_resources/qss/light/checkbox_checked_disabled@2x.png new file mode 100644 index 0000000..e4b77e4 Binary files /dev/null and b/_resources/qss/light/checkbox_checked_disabled@2x.png differ diff --git a/_resources/qss/light/checkbox_checked_focus.png b/_resources/qss/light/checkbox_checked_focus.png new file mode 100644 index 0000000..31e2111 Binary files /dev/null and b/_resources/qss/light/checkbox_checked_focus.png differ diff --git a/_resources/qss/light/checkbox_checked_focus@2x.png b/_resources/qss/light/checkbox_checked_focus@2x.png new file mode 100644 index 0000000..a651ca5 Binary files /dev/null and b/_resources/qss/light/checkbox_checked_focus@2x.png differ diff --git a/_resources/qss/light/checkbox_checked_pressed.png b/_resources/qss/light/checkbox_checked_pressed.png new file mode 100644 index 0000000..0863079 Binary files /dev/null and b/_resources/qss/light/checkbox_checked_pressed.png differ diff --git a/_resources/qss/light/checkbox_checked_pressed@2x.png b/_resources/qss/light/checkbox_checked_pressed@2x.png new file mode 100644 index 0000000..38874b2 Binary files /dev/null and b/_resources/qss/light/checkbox_checked_pressed@2x.png differ diff --git a/_resources/qss/light/checkbox_indeterminate.png b/_resources/qss/light/checkbox_indeterminate.png new file mode 100644 index 0000000..37d03be Binary files /dev/null and b/_resources/qss/light/checkbox_indeterminate.png differ diff --git a/_resources/qss/light/checkbox_indeterminate@2x.png b/_resources/qss/light/checkbox_indeterminate@2x.png new file mode 100644 index 0000000..df49db5 Binary files /dev/null and b/_resources/qss/light/checkbox_indeterminate@2x.png differ diff --git a/_resources/qss/light/checkbox_indeterminate_disabled.png b/_resources/qss/light/checkbox_indeterminate_disabled.png new file mode 100644 index 0000000..dd53a1e Binary files /dev/null and b/_resources/qss/light/checkbox_indeterminate_disabled.png differ diff --git a/_resources/qss/light/checkbox_indeterminate_disabled@2x.png b/_resources/qss/light/checkbox_indeterminate_disabled@2x.png new file mode 100644 index 0000000..8cd127c Binary files /dev/null and b/_resources/qss/light/checkbox_indeterminate_disabled@2x.png differ diff --git a/_resources/qss/light/checkbox_indeterminate_focus.png b/_resources/qss/light/checkbox_indeterminate_focus.png new file mode 100644 index 0000000..25afd03 Binary files /dev/null and b/_resources/qss/light/checkbox_indeterminate_focus.png differ diff --git a/_resources/qss/light/checkbox_indeterminate_focus@2x.png b/_resources/qss/light/checkbox_indeterminate_focus@2x.png new file mode 100644 index 0000000..fe36858 Binary files /dev/null and b/_resources/qss/light/checkbox_indeterminate_focus@2x.png differ diff --git a/_resources/qss/light/checkbox_indeterminate_pressed.png b/_resources/qss/light/checkbox_indeterminate_pressed.png new file mode 100644 index 0000000..e290fb8 Binary files /dev/null and b/_resources/qss/light/checkbox_indeterminate_pressed.png differ diff --git a/_resources/qss/light/checkbox_indeterminate_pressed@2x.png b/_resources/qss/light/checkbox_indeterminate_pressed@2x.png new file mode 100644 index 0000000..97efff2 Binary files /dev/null and b/_resources/qss/light/checkbox_indeterminate_pressed@2x.png differ diff --git a/_resources/qss/light/checkbox_unchecked.png b/_resources/qss/light/checkbox_unchecked.png new file mode 100644 index 0000000..8e845d3 Binary files /dev/null and b/_resources/qss/light/checkbox_unchecked.png differ diff --git a/_resources/qss/light/checkbox_unchecked@2x.png b/_resources/qss/light/checkbox_unchecked@2x.png new file mode 100644 index 0000000..d4b9b6f Binary files /dev/null and b/_resources/qss/light/checkbox_unchecked@2x.png differ diff --git a/_resources/qss/light/checkbox_unchecked_disabled.png b/_resources/qss/light/checkbox_unchecked_disabled.png new file mode 100644 index 0000000..07b6030 Binary files /dev/null and b/_resources/qss/light/checkbox_unchecked_disabled.png differ diff --git a/_resources/qss/light/checkbox_unchecked_disabled@2x.png b/_resources/qss/light/checkbox_unchecked_disabled@2x.png new file mode 100644 index 0000000..84ac5e7 Binary files /dev/null and b/_resources/qss/light/checkbox_unchecked_disabled@2x.png differ diff --git a/_resources/qss/light/checkbox_unchecked_focus.png b/_resources/qss/light/checkbox_unchecked_focus.png new file mode 100644 index 0000000..257f6dd Binary files /dev/null and b/_resources/qss/light/checkbox_unchecked_focus.png differ diff --git a/_resources/qss/light/checkbox_unchecked_focus@2x.png b/_resources/qss/light/checkbox_unchecked_focus@2x.png new file mode 100644 index 0000000..275d3b9 Binary files /dev/null and b/_resources/qss/light/checkbox_unchecked_focus@2x.png differ diff --git a/_resources/qss/light/checkbox_unchecked_pressed.png b/_resources/qss/light/checkbox_unchecked_pressed.png new file mode 100644 index 0000000..2d2a134 Binary files /dev/null and b/_resources/qss/light/checkbox_unchecked_pressed.png differ diff --git a/_resources/qss/light/checkbox_unchecked_pressed@2x.png b/_resources/qss/light/checkbox_unchecked_pressed@2x.png new file mode 100644 index 0000000..46750f4 Binary files /dev/null and b/_resources/qss/light/checkbox_unchecked_pressed@2x.png differ diff --git a/_resources/qss/light/radio_checked.png b/_resources/qss/light/radio_checked.png new file mode 100644 index 0000000..f72a6e8 Binary files /dev/null and b/_resources/qss/light/radio_checked.png differ diff --git a/_resources/qss/light/radio_checked@2x.png b/_resources/qss/light/radio_checked@2x.png new file mode 100644 index 0000000..dcb4e44 Binary files /dev/null and b/_resources/qss/light/radio_checked@2x.png differ diff --git a/_resources/qss/light/radio_checked_disabled.png b/_resources/qss/light/radio_checked_disabled.png new file mode 100644 index 0000000..4009f74 Binary files /dev/null and b/_resources/qss/light/radio_checked_disabled.png differ diff --git a/_resources/qss/light/radio_checked_disabled@2x.png b/_resources/qss/light/radio_checked_disabled@2x.png new file mode 100644 index 0000000..060164b Binary files /dev/null and b/_resources/qss/light/radio_checked_disabled@2x.png differ diff --git a/_resources/qss/light/radio_checked_focus.png b/_resources/qss/light/radio_checked_focus.png new file mode 100644 index 0000000..4e0da9b Binary files /dev/null and b/_resources/qss/light/radio_checked_focus.png differ diff --git a/_resources/qss/light/radio_checked_focus@2x.png b/_resources/qss/light/radio_checked_focus@2x.png new file mode 100644 index 0000000..2d659f1 Binary files /dev/null and b/_resources/qss/light/radio_checked_focus@2x.png differ diff --git a/_resources/qss/light/radio_checked_pressed.png b/_resources/qss/light/radio_checked_pressed.png new file mode 100644 index 0000000..91f45ba Binary files /dev/null and b/_resources/qss/light/radio_checked_pressed.png differ diff --git a/_resources/qss/light/radio_checked_pressed@2x.png b/_resources/qss/light/radio_checked_pressed@2x.png new file mode 100644 index 0000000..cb24b03 Binary files /dev/null and b/_resources/qss/light/radio_checked_pressed@2x.png differ diff --git a/_resources/qss/light/radio_unchecked.png b/_resources/qss/light/radio_unchecked.png new file mode 100644 index 0000000..8fac5d4 Binary files /dev/null and b/_resources/qss/light/radio_unchecked.png differ diff --git a/_resources/qss/light/radio_unchecked@2x.png b/_resources/qss/light/radio_unchecked@2x.png new file mode 100644 index 0000000..8783175 Binary files /dev/null and b/_resources/qss/light/radio_unchecked@2x.png differ diff --git a/_resources/qss/light/radio_unchecked_disabled.png b/_resources/qss/light/radio_unchecked_disabled.png new file mode 100644 index 0000000..d6de6b0 Binary files /dev/null and b/_resources/qss/light/radio_unchecked_disabled.png differ diff --git a/_resources/qss/light/radio_unchecked_disabled@2x.png b/_resources/qss/light/radio_unchecked_disabled@2x.png new file mode 100644 index 0000000..93c5589 Binary files /dev/null and b/_resources/qss/light/radio_unchecked_disabled@2x.png differ diff --git a/_resources/qss/light/radio_unchecked_focus.png b/_resources/qss/light/radio_unchecked_focus.png new file mode 100644 index 0000000..6ae76c4 Binary files /dev/null and b/_resources/qss/light/radio_unchecked_focus.png differ diff --git a/_resources/qss/light/radio_unchecked_focus@2x.png b/_resources/qss/light/radio_unchecked_focus@2x.png new file mode 100644 index 0000000..8888b52 Binary files /dev/null and b/_resources/qss/light/radio_unchecked_focus@2x.png differ diff --git a/_resources/qss/light/radio_unchecked_pressed.png b/_resources/qss/light/radio_unchecked_pressed.png new file mode 100644 index 0000000..e660846 Binary files /dev/null and b/_resources/qss/light/radio_unchecked_pressed.png differ diff --git a/_resources/qss/light/radio_unchecked_pressed@2x.png b/_resources/qss/light/radio_unchecked_pressed@2x.png new file mode 100644 index 0000000..c133f6b Binary files /dev/null and b/_resources/qss/light/radio_unchecked_pressed@2x.png differ diff --git a/_resources/qss/light/toggle_checked.png b/_resources/qss/light/toggle_checked.png new file mode 100644 index 0000000..e2821c4 Binary files /dev/null and b/_resources/qss/light/toggle_checked.png differ diff --git a/_resources/qss/light/toggle_checked@2x.png b/_resources/qss/light/toggle_checked@2x.png new file mode 100644 index 0000000..91ac737 Binary files /dev/null and b/_resources/qss/light/toggle_checked@2x.png differ diff --git a/_resources/qss/light/toggle_checked_disabled.png b/_resources/qss/light/toggle_checked_disabled.png new file mode 100644 index 0000000..21b2248 Binary files /dev/null and b/_resources/qss/light/toggle_checked_disabled.png differ diff --git a/_resources/qss/light/toggle_checked_disabled@2x.png b/_resources/qss/light/toggle_checked_disabled@2x.png new file mode 100644 index 0000000..931ec3f Binary files /dev/null and b/_resources/qss/light/toggle_checked_disabled@2x.png differ diff --git a/_resources/qss/light/toggle_checked_focus.png b/_resources/qss/light/toggle_checked_focus.png new file mode 100644 index 0000000..8cf0af8 Binary files /dev/null and b/_resources/qss/light/toggle_checked_focus.png differ diff --git a/_resources/qss/light/toggle_checked_focus@2x.png b/_resources/qss/light/toggle_checked_focus@2x.png new file mode 100644 index 0000000..cc34e91 Binary files /dev/null and b/_resources/qss/light/toggle_checked_focus@2x.png differ diff --git a/_resources/qss/light/toggle_pressed.png b/_resources/qss/light/toggle_pressed.png new file mode 100644 index 0000000..a28655f Binary files /dev/null and b/_resources/qss/light/toggle_pressed.png differ diff --git a/_resources/qss/light/toggle_pressed@2x.png b/_resources/qss/light/toggle_pressed@2x.png new file mode 100644 index 0000000..9c25ece Binary files /dev/null and b/_resources/qss/light/toggle_pressed@2x.png differ diff --git a/_resources/qss/light/toggle_unchecked.png b/_resources/qss/light/toggle_unchecked.png new file mode 100644 index 0000000..33c045a Binary files /dev/null and b/_resources/qss/light/toggle_unchecked.png differ diff --git a/_resources/qss/light/toggle_unchecked@2x.png b/_resources/qss/light/toggle_unchecked@2x.png new file mode 100644 index 0000000..80df86f Binary files /dev/null and b/_resources/qss/light/toggle_unchecked@2x.png differ diff --git a/_resources/qss/light/toggle_unchecked_disabled.png b/_resources/qss/light/toggle_unchecked_disabled.png new file mode 100644 index 0000000..5b7fc13 Binary files /dev/null and b/_resources/qss/light/toggle_unchecked_disabled.png differ diff --git a/_resources/qss/light/toggle_unchecked_disabled@2x.png b/_resources/qss/light/toggle_unchecked_disabled@2x.png new file mode 100644 index 0000000..56925be Binary files /dev/null and b/_resources/qss/light/toggle_unchecked_disabled@2x.png differ diff --git a/_resources/qss/light/toggle_unchecked_focus.png b/_resources/qss/light/toggle_unchecked_focus.png new file mode 100644 index 0000000..c7fc8cc Binary files /dev/null and b/_resources/qss/light/toggle_unchecked_focus.png differ diff --git a/_resources/qss/light/toggle_unchecked_focus@2x.png b/_resources/qss/light/toggle_unchecked_focus@2x.png new file mode 100644 index 0000000..687005b Binary files /dev/null and b/_resources/qss/light/toggle_unchecked_focus@2x.png differ diff --git a/_resources/qss/style.qss b/_resources/qss/style.qss new file mode 100644 index 0000000..7b1efc1 --- /dev/null +++ b/_resources/qss/style.qss @@ -0,0 +1,486 @@ + +QMenu { + margin: 0.1em; + icon-size: 1em; +} + +QMenuBar { + spacing: 0.2em; + padding: 0.1em; +} + +QMenu::item { + min-height: 1.2em; + padding: 0.05em 1em 0.05em 0.5em; + border: 0.05em solid transparent; +} + +QMenu::item:selected { + border-color: $MENU_ITEM_SELECT_BORDER; + background: $MENU_ITEM_SELECT_COLOR; +} + +QMenu::icon { + border: none; + margin-left: 0.5em; + width: 1em; + height: 1em; +} + +QMenu::indicator { + width: $CHECKBOX_SIZE; + height: $CHECKBOX_SIZE; + margin-left: 0.5em; +} + +/* non-exclusive indicator = check box style indicator (see QActionGroup::setExclusive) */ + +QMenu::indicator:non-exclusive:unchecked { + image: url(":/qss/$THEME/checkbox_unchecked.png"); +} + +QMenu::indicator:non-exclusive:unchecked:selected, QMenu::indicator:non-exclusive:unchecked:hover, QMenu::indicator:non-exclusive:unchecked:focus, QMenu::indicator:non-exclusive:unchecked:pressed { + image: url(":/qss/$THEME/checkbox_unchecked.png"); + border: none; +} + +QMenu::indicator:non-exclusive:unchecked:disabled { + image: url(":/qss/$THEME/checkbox_unchecked_disabled.png"); +} + +QMenu::indicator:non-exclusive:checked { + image: url(":/qss/$THEME/checkbox_checked.png"); +} + +QMenu::indicator:non-exclusive:checked:selected, QMenu::indicator:non-exclusive:checked:hover, QMenu::indicator:non-exclusive:checked:focus, QMenu::indicator:non-exclusive:checked:pressed { + image: url(":/qss/$THEME/checkbox_checked.png"); + border: none; +} + +QMenu::indicator:non-exclusive:checked:disabled { + image: url(":/qss/$THEME/checkbox_checked_disabled.png"); +} + +QMenu::indicator:non-exclusive:indeterminate { + image: url(":/qss/$THEME/checkbox_indeterminate.png"); +} + +QMenu::indicator:non-exclusive:indeterminate:disabled { + image: url(":/qss/$THEME/checkbox_indeterminate_disabled.png"); +} + +QMenu::indicator:non-exclusive:indeterminate:selected, QMenu::indicator:non-exclusive:indeterminate:focus, QMenu::indicator:non-exclusive:indeterminate:hover, QMenu::indicator:non-exclusive:indeterminate:pressed { + image: url(":/qss/$THEME/checkbox_indeterminate.png"); +} + +/* exclusive indicator = radio button style indicator (see QActionGroup::setExclusive) */ + +QMenu::indicator:exclusive:unchecked { + image: url(":/qss/$THEME/radio_unchecked.png"); +} + +QMenu::indicator:exclusive:unchecked:selected, QMenu::indicator:exclusive:unchecked:hover, QMenu::indicator:exclusive:unchecked:focus, QMenu::indicator:exclusive:unchecked:pressed { + image: url(":/qss/$THEME/radio_unchecked.png"); + border: none; + outline: none; +} + +QMenu::indicator:exclusive:unchecked:disabled { + image: url(":/qss/$THEME/radio_unchecked_disabled.png"); +} + +QMenu::indicator:exclusive:checked { + image: url(":/qss/$THEME/radio_checked.png"); + border: none; + outline: none; +} + +QMenu::indicator:exclusive:checked:selected, QMenu::indicator:exclusive:checked:hover, QMenu::indicator:exclusive:checked:focus, QMenu::indicator:exclusive:checked:pressed { + image: url(":/qss/$THEME/radio_checked.png"); + border: none; + outline: none; +} + +QMenu::indicator:exclusive:checked:disabled { + image: url(":/qss/$THEME/radio_checked_disabled.png"); + outline: none; +} + +/* CheckBox ***********************************************************/ + +QCheckBox { + spacing: 4px; + outline: none; + padding-top: 0; + min-height: $CHECKBOX_SIZE; +} + +QCheckBox:focus { + border: none; +} + +QCheckBox::indicator { + margin-left: 2px; + margin-top: 2px; + height: $CHECKBOX_SIZE; + width: $CHECKBOX_SIZE; +} + +QCheckBox::indicator:unchecked { + image: url(":/qss/$THEME/checkbox_unchecked.png"); +} + +QCheckBox::indicator:unchecked:hover, QCheckBox::indicator:unchecked:focus { + image: url(":/qss/$THEME/checkbox_unchecked_focus.png"); + border: none; +} + +QCheckBox::indicator:unchecked:pressed { + image: url(":/qss/$THEME/checkbox_unchecked_pressed.png"); + border: none; +} + +QCheckBox::indicator:unchecked:disabled { + image: url(":/qss/$THEME/checkbox_unchecked_disabled.png"); +} + +QCheckBox::indicator:checked { + image: url(":/qss/$THEME/checkbox_checked.png"); +} + +QCheckBox::indicator:checked:hover, QCheckBox::indicator:checked:focus { + image: url(":/qss/$THEME/checkbox_checked_focus.png"); + border: none; +} + +QCheckBox::indicator:checked:pressed { + image: url(":/qss/$THEME/checkbox_checked_pressed.png"); + border: none; +} + +QCheckBox::indicator:checked:disabled { + image: url(":/qss/$THEME/checkbox_checked_disabled.png"); +} + +QCheckBox::indicator:indeterminate { + image: url(":/qss/$THEME/checkbox_indeterminate.png"); +} + +QCheckBox::indicator:indeterminate:disabled { + image: url(":/qss/$THEME/checkbox_indeterminate_disabled.png"); +} + +QCheckBox::indicator:indeterminate:focus, QCheckBox::indicator:indeterminate:hover { + image: url(":/qss/$THEME/checkbox_indeterminate_focus.png"); +} + +QCheckBox::indicator:indeterminate:pressed { + image: url(":/qss/$THEME/checkbox_indeterminate_pressed.png"); + border: none; +} + +/* CheckBox on Frame *****************************************************/ + +QFrame > QCheckBox { + spacing: 4px; + outline: none; + padding-top: 0; + min-height: $TOGGLE_HEIGHT; +} + +QFrame > QCheckBox:focus { + border: none; +} + +QFrame > QCheckBox::indicator { + margin-left: 2px; + margin-top: 2px; + height: $TOGGLE_HEIGHT; + width: $TOGGLE_WIDTH; +} + +QFrame > QCheckBox::indicator:unchecked, +QFrame > QCheckBox::indicator:unchecked:focus { + image: url(":/qss/$THEME/toggle_unchecked.png"); +} + +QFrame > QCheckBox::indicator:unchecked:hover { + image: url(":/qss/$THEME/toggle_unchecked_focus.png"); + border: none; +} + +QFrame > QCheckBox::indicator:unchecked:pressed, +QFrame > QCheckBox::indicator:checked:pressed, +QFrame > QCheckBox::indicator:indeterminate:pressed { + image: url(":/qss/$THEME/toggle_pressed.png"); + border: none; +} + +QFrame > QCheckBox::indicator:unchecked:disabled { + image: url(":/qss/$THEME/toggle_unchecked_disabled.png"); +} + +QFrame > QCheckBox::indicator:checked, +QFrame > QCheckBox::indicator:checked:focus { + image: url(":/qss/$THEME/toggle_checked.png"); +} + +QFrame > QCheckBox::indicator:checked:hover { + image: url(":/qss/$THEME/toggle_checked_focus.png"); + border: none; +} + +QFrame > QCheckBox::indicator:checked:disabled { + image: url(":/qss/$THEME/toggle_checked_disabled.png"); +} + +QFrame > QCheckBox::indicator:indeterminate, +QFrame > QCheckBox::indicator:indeterminate:focus { + image: url(":/qss/$THEME/toggle_pressed.png"); +} + +QFrame > QCheckBox::indicator:indeterminate:disabled { + image: url(":/qss/$THEME/checkbox_unchecked_disabled.png"); +} + +QFrame > QCheckBox::indicator:indeterminate:hover { + image: url(":/qss/$THEME/checkbox_unchecked_disabled.png"); +} + +/* GroupBox ********************************************************************* */ + +QGroupBox { + padding: 2px; + padding-top: 20px; + margin-top: 4px; + margin-bottom: 2px; +} + +QGroupBox::title { + subcontrol-origin: margin; + subcontrol-position: top left; + left: 2px; + padding-left: 4px; + padding-right: 4px; + padding-top: 1px; +} + +QGroupBox[checkable="false"]::title { + padding-top: 4px; +} + +QGroupBox::indicator { + margin-left: 2px; + margin-top: 3px; + padding: 0; + height: $CHECKBOX_SIZE; + width: $CHECKBOX_SIZE; +} + +QGroupBox::indicator:unchecked { + image: url(":/qss/$THEME/checkbox_unchecked.png"); + border: none; +} + +QGroupBox::indicator:unchecked:hover, QGroupBox::indicator:unchecked:focus, QGroupBox::indicator:unchecked:pressed { + image: url(":/qss/$THEME/checkbox_unchecked_focus.png"); + border: none; +} + +QGroupBox::indicator:unchecked:disabled { + image: url(":/qss/$THEME/checkbox_unchecked_disabled.png"); +} + +QGroupBox::indicator:checked { + image: url(":/qss/$THEME/checkbox_checked.png"); + border: none; +} + +QGroupBox::indicator:checked:hover, QGroupBox::indicator:checked:focus, QGroupBox::indicator:checked:pressed { + image: url(":/qss/$THEME/checkbox_checked_focus.png"); + border: none; +} + +QGroupBox::indicator:checked:disabled { + image: url(":/qss/$THEME/checkbox_checked_disabled.png"); +} + +/* Customize radio buttons. *******************************************/ + +QRadioButton { + spacing: 4px; + outline: none; + border: none; + padding-top: 0; + min-height: $CHECKBOX_SIZE; +} + +QRadioButton:focus { + border: none; +} + +QRadioButton::indicator { + border: none; + outline: none; + margin-left: 2px; + margin-top: 2px; + height: $CHECKBOX_SIZE; + width: $CHECKBOX_SIZE; +} + +QRadioButton::indicator:unchecked { + image: url(":/qss/$THEME/radio_unchecked.png"); +} + +QRadioButton::indicator:unchecked:hover, QRadioButton::indicator:unchecked:focus { + border: none; + outline: none; + image: url(":/qss/$THEME/radio_unchecked_focus.png"); +} + +QRadioButton::indicator:unchecked:pressed { + border: none; + outline: none; + image: url(":/qss/$THEME/radio_unchecked_pressed.png"); +} + +QRadioButton::indicator:unchecked:disabled { + image: url(":/qss/$THEME/radio_unchecked_disabled.png"); +} + +QRadioButton::indicator:checked { + border: none; + outline: none; + image: url(":/qss/$THEME/radio_checked.png"); +} + +QRadioButton::indicator:checked:hover, QRadioButton::indicator:checked:focus { + border: none; + outline: none; + image: url(":/qss/$THEME/radio_checked_focus.png"); +} + +QRadioButton::indicator:checked:pressed { + border: none; + outline: none; + image: url(":/qss/$THEME/radio_checked_pressed.png"); +} + +QRadioButton::indicator:checked:disabled { + outline: none; + image: url(":/qss/$THEME/radio_checked_disabled.png"); +} + +/* **************************************************************************** */ + +QListWidget::indicator:checked, +QTreeWidget::indicator:checked, +QTableWidget::indicator:checked, +QTreeView::indicator:checked, +QListView::indicator:checked, +QTableView::indicator:checked, +QColumnView::indicator:checked { + image: url(":/qss/$THEME/checkbox_checked.png"); +} + +QListWidget::indicator:checked:hover, +QTreeWidget::indicator:checked:hover, +QTableWidget::indicator:checked:hover, +QListWidget::indicator:checked:focus, +QTreeWidget::indicator:checked:focus, +QTableWidget::indicator:checked:focus, +QTreeView::indicator:checked:hover, +QTreeView::indicator:checked:focus, +QListView::indicator:checked:hover, +QListView::indicator:checked:focus, +QTableView::indicator:checked:hover, +QTableView::indicator:checked:focus, +QColumnView::indicator:checked:hover, +QColumnView::indicator:checked:focus { + image: url(":/qss/$THEME/checkbox_checked_focus.png"); +} + +QListWidget::indicator:checked:pressed, +QTreeWidget::indicator:checked:pressed, +QTableWidget::indicator:checked:pressed, +QTreeView::indicator:checked:pressed, +QListView::indicator:checked:pressed, +QTableView::indicator:checked:pressed, +QColumnView::indicator:checked:pressed { + image: url(":/qss/$THEME/checkbox_checked_pressed.png"); +} + +QListWidget::indicator:unchecked, +QTreeWidget::indicator:unchecked, +QTableWidget::indicator:unchecked, +QTreeView::indicator:unchecked, +QListView::indicator:unchecked, +QTableView::indicator:unchecked, +QColumnView::indicator:unchecked { + image: url(":/qss/$THEME/checkbox_unchecked.png"); +} + +QListWidget::indicator:unchecked:hover, +QTreeWidget::indicator:unchecked:hover, +QTableWidget::indicator:unchecked:hover, +QListWidget::indicator:unchecked:focus, +QTreeWidget::indicator:unchecked:focus, +QTableWidget::indicator:unchecked:focus, +QTreeView::indicator:unchecked:hover, +QTreeView::indicator:unchecked:focus +QListView::indicator:unchecked:hover, +QListView::indicator:unchecked:focus, +QTableView::indicator:unchecked:hover, +QTableView::indicator:unchecked:focus, +QColumnView::indicator:unchecked:hover, +QColumnView::indicator:unchecked:focus { + image: url(":/qss/$THEME/checkbox_unchecked_focus.png"); +} + +QListWidget::indicator:unchecked:pressed, +QTreeWidget::indicator:unchecked:pressed, +QTableWidget::indicator:unchecked:pressed, +QTreeView::indicator:unchecked:pressed, +QListView::indicator:unchecked:pressed, +QTableView::indicator:unchecked:pressed, +QColumnView::indicator:unchecked:pressed { + image: url(":/qss/$THEME/checkbox_unchecked_pressed.png"); +} + + +QListWidget::indicator:indeterminate, +QTreeWidget::indicator:indeterminate, +QTableWidget::indicator:indeterminate, +QTreeView::indicator:indeterminate, +QListView::indicator:indeterminate, +QTableView::indicator:indeterminate, +QColumnView::indicator:indeterminate { + image: url(":/qss/$THEME/checkbox_indeterminate.png"); +} + +QListWidget::indicator:indeterminate:hover, +QTreeWidget::indicator:indeterminate:hover, +QTableWidget::indicator:indeterminate:hover, +QListWidget::indicator:indeterminate:focus, +QTreeWidget::indicator:indeterminate:focus, +QTableWidget::indicator:indeterminate:focus, +QTreeView::indicator:indeterminate:hover, +QTreeView::indicator:indeterminate:focus, +QListView::indicator:indeterminate:hover, +QListView::indicator:indeterminate:focus, +QTableView::indicator:indeterminate:hover, +QTableView::indicator:indeterminate:focus, +QColumnView::indicator:indeterminate:hover, +QColumnView::indicator:indeterminate:focus { + image: url(":/qss/$THEME/checkbox_indeterminate_focus.png"); +} + +QListWidget::indicator:indeterminate:pressed, +QTreeWidget::indicator:indeterminate:pressed, +QTableWidget::indicator:indeterminate:pressed, +QTreeView::indicator:indeterminate:pressed, +QListView::indicator:indeterminate:pressed, +QTableView::indicator:indeterminate:pressed, +QColumnView::indicator:indeterminate:pressed { + image: url(":/qss/$THEME/checkbox_indeterminate_pressed.png"); +} diff --git a/_resources/style.qrc b/_resources/style.qrc new file mode 100644 index 0000000..e1b0ee4 --- /dev/null +++ b/_resources/style.qrc @@ -0,0 +1,113 @@ + + + qss/style.qss + qss/dark/checkbox_checked.png + qss/dark/checkbox_checked@2x.png + qss/dark/checkbox_checked_disabled.png + qss/dark/checkbox_checked_disabled@2x.png + qss/dark/checkbox_checked_focus.png + qss/dark/checkbox_checked_focus@2x.png + qss/dark/checkbox_checked_pressed.png + qss/dark/checkbox_checked_pressed@2x.png + qss/dark/checkbox_indeterminate.png + qss/dark/checkbox_indeterminate@2x.png + qss/dark/checkbox_indeterminate_disabled.png + qss/dark/checkbox_indeterminate_disabled@2x.png + qss/dark/checkbox_indeterminate_focus.png + qss/dark/checkbox_indeterminate_focus@2x.png + qss/dark/checkbox_indeterminate_pressed.png + qss/dark/checkbox_indeterminate_pressed@2x.png + qss/dark/checkbox_unchecked.png + qss/dark/checkbox_unchecked@2x.png + qss/dark/checkbox_unchecked_disabled.png + qss/dark/checkbox_unchecked_disabled@2x.png + qss/dark/checkbox_unchecked_focus.png + qss/dark/checkbox_unchecked_focus@2x.png + qss/dark/checkbox_unchecked_pressed.png + qss/dark/checkbox_unchecked_pressed@2x.png + qss/dark/radio_checked.png + qss/dark/radio_checked@2x.png + qss/dark/radio_checked_disabled.png + qss/dark/radio_checked_disabled@2x.png + qss/dark/radio_checked_focus.png + qss/dark/radio_checked_focus@2x.png + qss/dark/radio_checked_pressed.png + qss/dark/radio_checked_pressed@2x.png + qss/dark/radio_unchecked.png + qss/dark/radio_unchecked@2x.png + qss/dark/radio_unchecked_disabled.png + qss/dark/radio_unchecked_disabled@2x.png + qss/dark/radio_unchecked_focus.png + qss/dark/radio_unchecked_focus@2x.png + qss/dark/radio_unchecked_pressed.png + qss/dark/radio_unchecked_pressed@2x.png + qss/dark/toggle_checked.png + qss/dark/toggle_checked@2x.png + qss/dark/toggle_checked_disabled.png + qss/dark/toggle_checked_disabled@2x.png + qss/dark/toggle_checked_focus.png + qss/dark/toggle_checked_focus@2x.png + qss/dark/toggle_pressed.png + qss/dark/toggle_pressed@2x.png + qss/dark/toggle_unchecked.png + qss/dark/toggle_unchecked@2x.png + qss/dark/toggle_unchecked_disabled.png + qss/dark/toggle_unchecked_disabled@2x.png + qss/dark/toggle_unchecked_focus.png + qss/dark/toggle_unchecked_focus@2x.png + qss/light/checkbox_checked.png + qss/light/checkbox_checked@2x.png + qss/light/checkbox_checked_disabled.png + qss/light/checkbox_checked_disabled@2x.png + qss/light/checkbox_checked_focus.png + qss/light/checkbox_checked_focus@2x.png + qss/light/checkbox_checked_pressed.png + qss/light/checkbox_checked_pressed@2x.png + qss/light/checkbox_indeterminate.png + qss/light/checkbox_indeterminate@2x.png + qss/light/checkbox_indeterminate_disabled.png + qss/light/checkbox_indeterminate_disabled@2x.png + qss/light/checkbox_indeterminate_focus.png + qss/light/checkbox_indeterminate_focus@2x.png + qss/light/checkbox_indeterminate_pressed.png + qss/light/checkbox_indeterminate_pressed@2x.png + qss/light/checkbox_unchecked.png + qss/light/checkbox_unchecked@2x.png + qss/light/checkbox_unchecked_disabled.png + qss/light/checkbox_unchecked_disabled@2x.png + qss/light/checkbox_unchecked_focus.png + qss/light/checkbox_unchecked_focus@2x.png + qss/light/checkbox_unchecked_pressed.png + qss/light/checkbox_unchecked_pressed@2x.png + qss/light/radio_checked.png + qss/light/radio_checked@2x.png + qss/light/radio_checked_disabled.png + qss/light/radio_checked_disabled@2x.png + qss/light/radio_checked_focus.png + qss/light/radio_checked_focus@2x.png + qss/light/radio_checked_pressed.png + qss/light/radio_checked_pressed@2x.png + qss/light/radio_unchecked.png + qss/light/radio_unchecked@2x.png + qss/light/radio_unchecked_disabled.png + qss/light/radio_unchecked_disabled@2x.png + qss/light/radio_unchecked_focus.png + qss/light/radio_unchecked_focus@2x.png + qss/light/radio_unchecked_pressed.png + qss/light/radio_unchecked_pressed@2x.png + qss/light/toggle_checked.png + qss/light/toggle_checked@2x.png + qss/light/toggle_checked_disabled.png + qss/light/toggle_checked_disabled@2x.png + qss/light/toggle_checked_focus.png + qss/light/toggle_checked_focus@2x.png + qss/light/toggle_pressed.png + qss/light/toggle_pressed@2x.png + qss/light/toggle_unchecked.png + qss/light/toggle_unchecked@2x.png + qss/light/toggle_unchecked_disabled.png + qss/light/toggle_unchecked_disabled@2x.png + qss/light/toggle_unchecked_focus.png + qss/light/toggle_unchecked_focus@2x.png + + diff --git a/sources/CMakeLists.txt b/sources/CMakeLists.txt new file mode 100644 index 0000000..bfbbda6 --- /dev/null +++ b/sources/CMakeLists.txt @@ -0,0 +1,78 @@ + +cmake_minimum_required(VERSION 4.0) + +project(custom_example + VERSION 1.0 + DESCRIPTION "Example Custom style application" + LANGUAGES CXX +) + +include(${CMAKE_CURRENT_SOURCE_DIR}/../_cmake/app_settings.cmake) + +find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS + Core Widgets +) + +set(PROJECT_SOURCES + main.cpp + main_window.cpp + main_window.h + main_window.ui + ${COMMON_SOURCES_DIR}/application_config.h + ${COMMON_SOURCES_DIR}/application_config.cpp +) + +set(PROJECT_RESOURCES + ${RESOURCES_DIR}/main.qrc + ${RESOURCES_DIR}/icons.qrc + ${RESOURCES_DIR}/style.qrc +) + +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set(PROJECT_RC_FILES + ${RESOURCES_DIR}/main_icon.rc + ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc + ) +endif() + +if(${QT_VERSION_MAJOR} GREATER_EQUAL 6) + qt_add_executable(${PROJECT_NAME} + MANUAL_FINALIZATION + ${PROJECT_SOURCES} + ${PROJECT_RESOURCES} + ${PROJECT_RC_FILES} + ) +else() + add_executable(${PROJECT_NAME} + ${PROJECT_SOURCES} + ${PROJECT_RESOURCES} + ${PROJECT_RC_FILES} + ) +endif() + +include(${CMAKE_INC_DIR}/target_options.cmake) + +target_link_libraries(${PROJECT_NAME} PRIVATE + Qt${QT_VERSION_MAJOR}::Core + Qt${QT_VERSION_MAJOR}::Widgets +) + +target_include_directories(${PROJECT_NAME} PRIVATE + ${SYSTEM_INCLUDE_DIR} + ${CMAKE_CURRENT_BINARY_DIR} + ${COMMON_SOURCES_DIR} +) + +if(CMAKE_SYSTEM_NAME STREQUAL "Windows") + set_target_properties(${PROJECT_NAME} PROPERTIES + WIN32_EXECUTABLE TRUE + ) +endif() + +if(QT_VERSION_MAJOR EQUAL 6) + qt_finalize_executable(${PROJECT_NAME}) +endif() + +include(${CMAKE_INC_DIR}/post_build.cmake) + + diff --git a/sources/main.cpp b/sources/main.cpp new file mode 100644 index 0000000..b6dd8c6 --- /dev/null +++ b/sources/main.cpp @@ -0,0 +1,22 @@ +#include "main_window.h" +#include +#include "application_config.h" + +//============================================================================== +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + + Application::initialize(); + Application::installTranslations(); + Application::updateApplicationStyle(); + + MainWindow w; + + w.show(); + a.setQuitOnLastWindowClosed(true); + + return a.exec(); +} +//============================================================================== + diff --git a/sources/main_window.cpp b/sources/main_window.cpp new file mode 100644 index 0000000..e611537 --- /dev/null +++ b/sources/main_window.cpp @@ -0,0 +1,70 @@ +/**************************************************************************** +** Copyright (c) 2025 Evgeny Teterin (nayk) +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#include "main_window.h" +#include "./ui_main_window.h" +#include + +#include "application_config.h" + +//============================================================================== +MainWindow::MainWindow(QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) +{ + ui->setupUi(this); + this->setWindowTitle( qApp->applicationName() ); + connect(ui->radioButtonSystem, &QRadioButton::toggled, + this, &MainWindow::radioButtonToggled); + connect(ui->radioButtonLight, &QRadioButton::toggled, + this, &MainWindow::radioButtonToggled); + connect(ui->radioButtonDark, &QRadioButton::toggled, + this, &MainWindow::radioButtonToggled); + connect(ui->actionExit, &QAction::triggered, + this, &QMainWindow::close); +} +//============================================================================== +MainWindow::~MainWindow() +{ + delete ui; +} +//============================================================================== +void MainWindow::radioButtonToggled(bool checked) +{ + Q_UNUSED(checked) + + Application::AppTheme theme {Application::SystemTheme}; + + if (ui->radioButtonLight->isChecked()) + theme = Application::LightTheme; + else if (ui->radioButtonDark->isChecked()) + theme = Application::DarkTheme; + + Application::updateApplicationStyle(theme); + QApplication::processEvents(QEventLoop::ExcludeUserInputEvents); +} +//============================================================================== + +//============================================================================== + diff --git a/sources/main_window.h b/sources/main_window.h new file mode 100644 index 0000000..920f0f0 --- /dev/null +++ b/sources/main_window.h @@ -0,0 +1,55 @@ +/**************************************************************************** +** Copyright (c) 2025 Evgeny Teterin (nayk) +** All right reserved. +** +** Permission is hereby granted, free of charge, to any person obtaining +** a copy of this software and associated documentation files (the +** "Software"), to deal in the Software without restriction, including +** without limitation the rights to use, copy, modify, merge, publish, +** distribute, sublicense, and/or sell copies of the Software, and to +** permit persons to whom the Software is furnished to do so, subject to +** the following conditions: +** +** The above copyright notice and this permission notice shall be +** included in all copies or substantial portions of the Software. +** +** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +** NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +** LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +** OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +** WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +** +****************************************************************************/ +#pragma once +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include + +//============================================================================== +QT_BEGIN_NAMESPACE +namespace Ui { +class MainWindow; +} +QT_END_NAMESPACE + +//============================================================================== +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(QWidget *parent = nullptr); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + +private slots: + void radioButtonToggled(bool checked); +}; + +#endif // MAINWINDOW_H +//============================================================================== diff --git a/sources/main_window.ui b/sources/main_window.ui new file mode 100644 index 0000000..806ae86 --- /dev/null +++ b/sources/main_window.ui @@ -0,0 +1,298 @@ + + + MainWindow + + + + 0 + 0 + 579 + 443 + + + + MainWindow + + + + :/images/main_icon.png:/images/main_icon.png + + + + + + + + 0 + 0 + + + + Текущий стиль оформления + + + + + + + 110 + 0 + + + + Системный + + + true + + + buttonGroup + + + + + + + + 110 + 0 + + + + Светлый + + + buttonGroup + + + + + + + + 110 + 0 + + + + Тёмный + + + buttonGroup + + + + + + + Qt::Orientation::Horizontal + + + + 186 + 20 + + + + + + + + + + + + 0 + 0 + + + + Пример отображения контролов + + + + + + + + + + CheckBox 1 + + + true + + + + + + + CheckBox 2 + + + + + + + 99999 + + + 12345 + + + + + + + + Item 1 + + + + + Item 2 + + + + + Item 3 + + + + + + + + + + + + + + TextLabel + + + + + + + Text string + + + + + + + ... + + + + + + + + + + + PushButton 1 + + + + + + + PushButton 2 + + + + + + + PushButton 3 + + + + + + + PushButton 4 + + + + + + + PushButton 5 + + + + + + + PushButton 6 + + + + + + + + + + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css"> +p, li { white-space: pre-wrap; } +hr { height: 1px; border-width: 0; } +li.unchecked::marker { content: "\2610"; } +li.checked::marker { content: "\2612"; } +</style></head><body style=" font-family:'Segoe UI'; font-size:9pt; font-weight:400; font-style:normal;"> +<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Text example</p></body></html> + + + + + + + + + + + + + 0 + 0 + 579 + 21 + + + + + Файл + + + + + + + + + + :/icons/shutdown-40.png:/icons/shutdown-40.png + + + Выход + + + + + + + + + + + +