├── .gitignore ├── .gitlab-ci.yml ├── CMakeLists.txt ├── CMakeLists_qt5.txt ├── LICENSE ├── LQtUtilsQuick ├── .gitignore ├── CMakeLists.txt ├── lquicksettings.cpp ├── lquicksettings.h ├── ltestsignals.cpp ├── ltestsignals.h ├── main.cpp ├── main.qml └── qml.qrc ├── README.md ├── docs ├── cutouts.webp └── cutouts_horizontal.webp ├── fontawesome ├── Font Awesome 6 Brands-Regular-400.otf ├── Font Awesome 6 Free-Regular-400.otf ├── Font Awesome 6 Free-Solid-900.otf ├── LQTFontAwesomeBrandsRegular.qml ├── LQTFontAwesomeFreeRegular.qml └── LQTFontAwesomeFreeSolid.qml ├── lqtutils.pri ├── lqtutils ├── CMakeLists.txt └── tst_lqtutils.cpp ├── lqtutils_autoexec.h ├── lqtutils_bqueue.h ├── lqtutils_data.h ├── lqtutils_enum.h ├── lqtutils_fa.cpp ├── lqtutils_fa.h ├── lqtutils_freq.cpp ├── lqtutils_freq.h ├── lqtutils_fsm.cpp ├── lqtutils_fsm.h ├── lqtutils_logging.h ├── lqtutils_math.h ├── lqtutils_misc.h ├── lqtutils_models.h ├── lqtutils_net.cpp ├── lqtutils_net.h ├── lqtutils_perf.h ├── lqtutils_prop.h ├── lqtutils_qsl.h ├── lqtutils_settings.h ├── lqtutils_sslcheck.h ├── lqtutils_string.h ├── lqtutils_system.h ├── lqtutils_threading.h ├── lqtutils_time.h ├── lqtutils_ui.cpp ├── lqtutils_ui.h ├── lqtutils_ui.mm ├── lqtutils_ui_apple.mm ├── qt5 └── CMakeLists.txt └── qt6 └── CMakeLists.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | build* 75 | CMakeLists.txt.* 76 | -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | variables: 2 | GIT_SUBMODULE_STRATEGY: recursive 3 | 4 | stages: 5 | - test_qt5 6 | - test_qt62_jammy 7 | - test_qt64 8 | - test_qt65_android 9 | - test_qt66 10 | - test_qt66_android 11 | - test_qt67 12 | - test_qt67_android 13 | - test_qt68 14 | - test_qt68_android 15 | - test_qt69 16 | - test_qt69_android 17 | 18 | Test_qt5: 19 | stage: test_qt5 20 | image: 21 | name: "carlonluca/qt-dev:5.15.2" 22 | entrypoint: [""] 23 | script: 24 | - mkdir build 25 | - cd build 26 | - cmake ../qt5 27 | - make 28 | - export QTEST_FUNCTION_TIMEOUT=10000000 29 | - ./LQtUtilsTest 30 | 31 | Test_qt62_jammy: 32 | stage: test_qt62_jammy 33 | image: 34 | name: "carlonluca/qt-dev:6.2.4_jammy" 35 | entrypoint: [""] 36 | script: 37 | - mkdir build 38 | - cd build 39 | - cmake ../qt6 40 | - make 41 | - export QTEST_FUNCTION_TIMEOUT=10000000 42 | - ./LQtUtilsTest 43 | 44 | Test_qt64: 45 | stage: test_qt64 46 | image: 47 | name: "carlonluca/qt-dev:6.4.3" 48 | entrypoint: [""] 49 | script: 50 | - mkdir build 51 | - cd build 52 | - cmake ../qt6 53 | - make 54 | - export QTEST_FUNCTION_TIMEOUT=10000000 55 | - LD_LIBRARY_PATH=/usr/local/lib ./LQtUtilsTest 56 | 57 | Test_qt65_android: 58 | stage: test_qt65_android 59 | image: 60 | name: "carlonluca/qt-dev:6.5.2" 61 | entrypoint: [""] 62 | script: 63 | - mkdir build 64 | - cd build 65 | - /opt/Qt-android-6.5.2/android_arm64_v8a/bin/qt-cmake \ 66 | -DCMAKE_BUILD_TYPE=Release \ 67 | -DQT_ANDROID_BUILD_ALL_ABIS:BOOL=ON \ 68 | -S ../qt6; 69 | - cmake --build . 70 | 71 | Test_qt66: 72 | stage: test_qt66 73 | image: 74 | name: "carlonluca/qt-dev:6.6.1" 75 | entrypoint: [""] 76 | script: 77 | - mkdir build 78 | - cd build 79 | - cmake ../qt6 80 | - make 81 | - export QTEST_FUNCTION_TIMEOUT=10000000 82 | - LD_LIBRARY_PATH=/usr/local/lib ./LQtUtilsTest 83 | 84 | Test_qt66_android: 85 | stage: test_qt66_android 86 | image: 87 | name: "carlonluca/qt-dev:6.6.1" 88 | entrypoint: [""] 89 | script: 90 | - mkdir build 91 | - cd build 92 | - /opt/Qt-android-6.6.1/android_arm64_v8a/bin/qt-cmake \ 93 | -DCMAKE_BUILD_TYPE=Release \ 94 | -DQT_ANDROID_BUILD_ALL_ABIS:BOOL=ON \ 95 | -S ../qt6; 96 | - cmake --build . 97 | 98 | Test_qt67: 99 | stage: test_qt67 100 | image: 101 | name: "carlonluca/qt-dev:6.7.3" 102 | entrypoint: [""] 103 | script: 104 | - mkdir build 105 | - cd build 106 | - cmake ../qt6 107 | - make 108 | - export QTEST_FUNCTION_TIMEOUT=10000000 109 | - LD_LIBRARY_PATH=/usr/local/lib ./LQtUtilsTest 110 | 111 | Test_qt67_android: 112 | stage: test_qt67_android 113 | image: 114 | name: "carlonluca/qt-dev:6.7.3" 115 | entrypoint: [""] 116 | script: 117 | - mkdir build 118 | - cd build 119 | - /opt/Qt-android-6.7.3/android_arm64_v8a/bin/qt-cmake \ 120 | -DCMAKE_BUILD_TYPE=Release \ 121 | -DQT_ANDROID_BUILD_ALL_ABIS:BOOL=ON \ 122 | -S ../qt6; 123 | - cmake --build . 124 | 125 | Test_qt68: 126 | stage: test_qt68 127 | image: 128 | name: "carlonluca/qt-dev:6.8.3" 129 | entrypoint: [""] 130 | script: 131 | - mkdir build 132 | - cd build 133 | - cmake ../qt6 134 | - make 135 | - export QTEST_FUNCTION_TIMEOUT=10000000 136 | - LD_LIBRARY_PATH=/usr/local/lib ./LQtUtilsTest 137 | 138 | Test_qt68_android: 139 | stage: test_qt68_android 140 | image: 141 | name: "carlonluca/qt-dev:6.8.3" 142 | entrypoint: [""] 143 | script: 144 | - mkdir build 145 | - cd build 146 | - /opt/qt/6.8.3/android_arm64_v8a/bin/qt-cmake \ 147 | -DCMAKE_BUILD_TYPE=Release \ 148 | -DQT_ANDROID_BUILD_ALL_ABIS:BOOL=ON \ 149 | -S ../qt6; 150 | - cmake --build . 151 | 152 | Test_qt69: 153 | stage: test_qt69 154 | image: 155 | name: "carlonluca/qt-dev:6.9.0" 156 | entrypoint: [""] 157 | script: 158 | - mkdir build 159 | - cd build 160 | - cmake ../qt6 161 | - make 162 | - export QTEST_FUNCTION_TIMEOUT=10000000 163 | - LD_LIBRARY_PATH=/usr/local/lib ./LQtUtilsTest 164 | 165 | Test_qt69_android: 166 | stage: test_qt69_android 167 | image: 168 | name: "carlonluca/qt-dev:6.9.0" 169 | entrypoint: [""] 170 | script: 171 | - mkdir build 172 | - cd build 173 | - /opt/qt/6.9.0/android_arm64_v8a/bin/qt-cmake \ 174 | -DCMAKE_BUILD_TYPE=Release \ 175 | -DQT_ANDROID_BUILD_ALL_ABIS:BOOL=ON \ 176 | -S ../qt6; 177 | - cmake --build . -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | set(CMAKE_AUTOMOC ON) 3 | 4 | find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Gui Qml Quick) 5 | find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Qml Quick) 6 | if (Qt6_FOUND) 7 | find_package(Qt NAMES Qt6 REQUIRED COMPONENTS StateMachine) 8 | find_package(Qt6 REQUIRED COMPONENTS StateMachine) 9 | endif() 10 | find_package(QT NAMES Qt6 Qt5 OPTIONAL_COMPONENTS DBus) 11 | find_package(Qt${QT_VERSION_MAJOR} OPTIONAL_COMPONENTS DBus) 12 | if (APPLE) 13 | find_library(USER_NOTIFICATIONS_FRAMEWORK UserNotifications) 14 | endif() 15 | 16 | qt_add_library(lqtutils STATIC 17 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_ui.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_ui.h 18 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_net.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_net.h 19 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_freq.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_freq.h 20 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_fsm.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_fsm.h 21 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_autoexec.h 22 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_bqueue.h 23 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_data.h 24 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_enum.h 25 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_logging.h 26 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_math.h 27 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_misc.h 28 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_perf.h 29 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_prop.h 30 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_settings.h 31 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_string.h 32 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_qsl.h 33 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_system.h 34 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_threading.h 35 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_time.h 36 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_models.h 37 | ) 38 | if (IOS) 39 | target_sources(lqtutils PRIVATE ${CMAKE_CURRENT_LIST_DIR}/lqtutils_ui.mm) 40 | endif() 41 | if (APPLE) 42 | target_sources(lqtutils PRIVATE ${CMAKE_CURRENT_LIST_DIR}/lqtutils_ui_apple.mm) 43 | endif() 44 | 45 | if (ENABLE_FONT_AWESOME) 46 | qt_add_qml_module(lqtutils 47 | URI lqtutils 48 | VERSION 1.0 49 | QML_FILES 50 | fontawesome/LQTFontAwesomeFreeRegular.qml 51 | fontawesome/LQTFontAwesomeFreeSolid.qml 52 | fontawesome/LQTFontAwesomeBrandsRegular.qml 53 | RESOURCES 54 | "fontawesome/Font Awesome 6 Brands-Regular-400.otf" 55 | "fontawesome/Font Awesome 6 Free-Regular-400.otf" 56 | "fontawesome/Font Awesome 6 Free-Solid-900.otf" 57 | SOURCES 58 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_fa.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_fa.h 59 | IMPORT_PATH 60 | ${CMAKE_CURRENT_LIST_DIR}/fontawesome 61 | RESOURCE_PREFIX / 62 | IMPORT_PATH "/fontawesome" 63 | ) 64 | add_compile_definitions(LQT_FONT_AWESOME_ENABLED) 65 | endif() 66 | 67 | target_include_directories(lqtutils 68 | PUBLIC ${CMAKE_CURRENT_LIST_DIR}) 69 | 70 | target_link_libraries(lqtutils PRIVATE 71 | Qt${QT_VERSION_MAJOR}::Core 72 | Qt${QT_VERSION_MAJOR}::CorePrivate 73 | Qt${QT_VERSION_MAJOR}::Gui 74 | Qt${QT_VERSION_MAJOR}::Qml 75 | Qt${QT_VERSION_MAJOR}::Quick 76 | ) 77 | if (Qt6DBus_FOUND) 78 | target_link_libraries(lqtutils PRIVATE Qt${QT_VERSION_MAJOR}::DBus) 79 | endif() 80 | if (Qt6_FOUND) 81 | target_link_libraries(lqtutils PRIVATE Qt6::StateMachine) 82 | endif() 83 | if (APPLE) 84 | target_link_libraries(lqtutils PRIVATE ${USER_NOTIFICATIONS_FRAMEWORK}) 85 | endif() 86 | -------------------------------------------------------------------------------- /CMakeLists_qt5.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | set(CMAKE_AUTOMOC ON) 3 | 4 | find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Gui Qml Quick) 5 | find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui Qml Quick) 6 | find_package(QT NAMES Qt6 Qt5 OPTIONAL_COMPONENTS DBus) 7 | find_package(Qt${QT_VERSION_MAJOR} OPTIONAL_COMPONENTS DBus) 8 | if (Qt6_FOUND) 9 | find_package(Qt6 REQUIRED COMPONENTS StateMachine) 10 | endif() 11 | 12 | add_library(lqtutils STATIC 13 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_ui.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_ui.h 14 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_net.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_net.h 15 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_freq.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_freq.h 16 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_fsm.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_fsm.h 17 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_autoexec.h 18 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_bqueue.h 19 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_data.h 20 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_enum.h 21 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_logging.h 22 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_math.h 23 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_misc.h 24 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_perf.h 25 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_prop.h 26 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_settings.h 27 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_string.h 28 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_qsl.h 29 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_system.h 30 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_threading.h 31 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_time.h 32 | ) 33 | if (IOS) 34 | target_sources(lqtutils PRIVATE ${CMAKE_CURRENT_LIST_DIR}/lqtutils_ui.mm) 35 | endif() 36 | 37 | if (ENABLE_FONT_AWESOME) 38 | add_compile_definitions(LQT_FONT_AWESOME_ENABLED) 39 | target_sources(lqtutils PRIVATE 40 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_fa.cpp ${CMAKE_CURRENT_LIST_DIR}/lqtutils_fa.h 41 | ${CMAKE_CURRENT_LIST_DIR}/lqtutils_fa.qrc 42 | ) 43 | endif() 44 | 45 | target_include_directories(lqtutils 46 | PUBLIC ${CMAKE_CURRENT_LIST_DIR}) 47 | 48 | target_link_libraries(lqtutils PRIVATE 49 | Qt${QT_VERSION_MAJOR}::Core 50 | Qt${QT_VERSION_MAJOR}::Gui 51 | Qt${QT_VERSION_MAJOR}::Qml 52 | Qt${QT_VERSION_MAJOR}::Quick 53 | ) 54 | if (Qt5DBus_FOUND) 55 | target_link_libraries(lqtutils PRIVATE Qt5::DBus) 56 | endif() 57 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Luca Carlon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LQtUtilsQuick/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | -------------------------------------------------------------------------------- /LQtUtilsQuick/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | project(LQtUtilsQuick LANGUAGES CXX) 4 | 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | 7 | set(CMAKE_AUTOUIC ON) 8 | set(CMAKE_AUTOMOC ON) 9 | set(CMAKE_AUTORCC ON) 10 | 11 | set(CMAKE_CXX_STANDARD 11) 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | 14 | # QtCreator supports the following variables for Android, which are identical to qmake Android variables. 15 | # Check http://doc.qt.io/qt-5/deployment-android.html for more information. 16 | # They need to be set before the find_package(Qt5 ...) call. 17 | 18 | #if(ANDROID) 19 | # set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android") 20 | # if (ANDROID_ABI STREQUAL "armeabi-v7a") 21 | # set(ANDROID_EXTRA_LIBS 22 | # ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libcrypto.so 23 | # ${CMAKE_CURRENT_SOURCE_DIR}/path/to/libssl.so) 24 | # endif() 25 | #endif() 26 | 27 | find_package(Qt5 COMPONENTS Core Quick REQUIRED) 28 | 29 | if(ANDROID) 30 | add_library(LQtUtilsQuick SHARED 31 | main.cpp 32 | qml.qrc 33 | ) 34 | else() 35 | add_executable(LQtUtilsQuick 36 | main.cpp 37 | ltestsignals.cpp 38 | lquicksettings.cpp 39 | qml.qrc 40 | ) 41 | endif() 42 | 43 | target_compile_definitions(LQtUtilsQuick 44 | PRIVATE $<$,$>:QT_QML_DEBUG>) 45 | target_link_libraries(LQtUtilsQuick 46 | PRIVATE Qt5::Core Qt5::Quick) 47 | -------------------------------------------------------------------------------- /LQtUtilsQuick/lquicksettings.cpp: -------------------------------------------------------------------------------- 1 | #include "lquicksettings.h" 2 | -------------------------------------------------------------------------------- /LQtUtilsQuick/lquicksettings.h: -------------------------------------------------------------------------------- 1 | #ifndef LQUICKSETTINGS_H 2 | #define LQUICKSETTINGS_H 3 | 4 | #include 5 | 6 | #include "../lqtutils_settings.h" 7 | #include "../lqtutils_prop.h" 8 | #include "../lqtutils_string.h" 9 | 10 | Q_NAMESPACE 11 | 12 | L_DECLARE_SETTINGS(LQuickSettings, new QSettings(QSL("settings.ini"), QSettings::IniFormat)) 13 | L_DEFINE_VALUE(int, appWidth, 200) 14 | L_DEFINE_VALUE(int, appHeight, 200) 15 | L_DEFINE_VALUE(int, appX, 100) 16 | L_DEFINE_VALUE(int, appY, 100) 17 | L_END_CLASS 18 | 19 | #endif // LQUICKSETTINGS_H 20 | -------------------------------------------------------------------------------- /LQtUtilsQuick/ltestsignals.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "lquicksettings.h" 4 | #include "ltestsignals.h" 5 | 6 | LTestSignals::LTestSignals(QObject *parent) : QObject(parent) 7 | { 8 | connect(&LQuickSettings::notifier(), &LQuickSettings::appWidthChanged, this, [] (int width) { 9 | qDebug() << "C++ app width changed:" << width; 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /LQtUtilsQuick/ltestsignals.h: -------------------------------------------------------------------------------- 1 | #ifndef LTESTSIGNALS_H 2 | #define LTESTSIGNALS_H 3 | 4 | #include 5 | 6 | #include <../lqtutils_enum.h> 7 | 8 | L_DECLARE_ENUM(MySharedEnum, 9 | Value1, 10 | Value2, 11 | Value6 = 6) 12 | 13 | Q_NAMESPACE 14 | 15 | class LTestSignals : public QObject 16 | { 17 | Q_OBJECT 18 | public: 19 | explicit LTestSignals(QObject* parent = nullptr); 20 | }; 21 | 22 | #endif // LTESTSIGNALS_H 23 | -------------------------------------------------------------------------------- /LQtUtilsQuick/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../lqtutils_settings.h" 6 | #include "../lqtutils_prop.h" 7 | 8 | #include "ltestsignals.h" 9 | #include "lquicksettings.h" 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | QGuiApplication app(argc, argv); 14 | MySharedEnum::qmlRegisterMySharedEnum("com.luke", 1, 0); 15 | QQmlApplicationEngine engine; 16 | engine.rootContext()->setContextProperty("settings", &LQuickSettings::notifier()); 17 | const QUrl url(QStringLiteral("qrc:/main.qml")); 18 | QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, 19 | &app, [url](QObject *obj, const QUrl &objUrl) { 20 | if (!obj && url == objUrl) 21 | QCoreApplication::exit(-1); 22 | }, Qt::QueuedConnection); 23 | engine.load(url); 24 | return app.exec(); 25 | } 26 | -------------------------------------------------------------------------------- /LQtUtilsQuick/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.12 2 | import QtQuick.Window 2.12 3 | import com.luke 1.0 4 | 5 | Window { 6 | property int enumValue: MySharedEnum.Value6 7 | property bool doBind: false 8 | 9 | visible: true 10 | 11 | Component.onCompleted: { 12 | console.log("Enum value:", enumValue) 13 | x = settings.appX 14 | y = settings.appY 15 | width = settings.appWidth 16 | height = settings.appHeight 17 | doBind = true 18 | } 19 | 20 | Connections { 21 | target: settings 22 | onAppWidthChanged: (w) => console.log("App width saved:", w) 23 | onAppHeightChanged: (h) => console.log("App height saved:", h) 24 | } 25 | 26 | Binding { when: doBind; target: settings; property: "appWidth"; value: width } 27 | Binding { when: doBind; target: settings; property: "appHeight"; value: height } 28 | Binding { when: doBind; target: settings; property: "appX"; value: x } 29 | Binding { when: doBind; target: settings; property: "appY"; value: y } 30 | } 31 | -------------------------------------------------------------------------------- /LQtUtilsQuick/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LQtUtils 2 | This is a module containing a few tools that I typically use in Qt apps. Using this module in your app is simple: add a submodule to your git repo and include the headers. Most of the code is in independent headers, so you don't need to build anything separately. 3 | A couple of headers need the related source file to be compiled explicitly; in that case I typically add this project as a submodule and link to my main project file. 4 | 5 | More articles related to these topics: https://bugfreeblog.duckdns.org/tag/lqtutils. 6 | 7 | ## Index 8 | 9 | - [How to Include](#how-to-include) 10 | - [Synthesize Qt properties in a short way (lqtutils_prop.h)](#synthesize-qt-props) 11 | - [Synthesize Qt enums and quickly expose to QML (lqtutils_enum.h)](#synthesize-qt-enums) 12 | - [Synthesize Qt settings with support for signals (lqtutils_settings.h)](#synthesize-qt-settings) 13 | - [Cache values and init automatically](#cache-values) 14 | - [Threading tools](#threading) 15 | - [Auto execute actions when exiting a scope (lqtutils_autoexec.h)](#autoexec) 16 | - [Measuring rate](#measure-rate) 17 | - [Measuring framerate](#measure-framerate) 18 | - [Showing Local Notifications](#local-notifications) 19 | - [Single shot timer for QML (lqtutils_ui.h)](#singleshot-qml) 20 | - [Getting safe areas on mobile](#safe-areas) 21 | - [Measure Performance (lqtutils_perf.h)](#measure-performance) 22 | - [Blocking Queue for qt (lqtutils_bqueue.h)](#blocking-queue) 23 | - [Download a File with Progress Notifications (lqtutils_net.h)](#download-file) 24 | - [FontAwesome in QML](#fontawesome) 25 | - [Compute total and available RAM (lqtutils_system.h) [Linux only]](#available-ram) 26 | 27 | 28 | ## How to Include 29 | 30 | To include in a qmake app: 31 | 32 | ``` 33 | include(lqtutils/lqtutils.pri) 34 | ``` 35 | 36 | To include in a cmake app in Qt6: 37 | 38 | ``` 39 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/deps/lqtutils) 40 | target_link_libraries(yourproj lqtutilsplugin) 41 | ``` 42 | 43 | NOTE: the name of the library to link is `lqtutilsplugin`, not just `lqtutils`. 44 | 45 | To include in a cmake app in Qt5: 46 | 47 | ``` 48 | include(${CMAKE_CURRENT_SOURCE_DIR}/deps/lqtutils/CMakeLists_qt5.txt) 49 | target_link_libraries(LQtUtilsTest PRIVATE lqtutils) 50 | ``` 51 | 52 | 53 | ## Synthesize Qt properties in a short way (lqtutils_prop.h) 54 | **For more info: https://bugfreeblog.duckdns.org/2020/05/synthesize-qt-properties.html.** 55 | 56 | Contains a few useful macros to synthesize Qt props. For instance: 57 | ```c++ 58 | class Fraction : public QObject 59 | { 60 | Q_OBJECT 61 | L_RW_PROP(double, numerator, setNumerator, 5) 62 | L_RW_PROP(double, denominator, setDenominator, 9) 63 | public: 64 | Fraction(QObject* parent = nullptr) : QObject(parent) {} 65 | }; 66 | ``` 67 | instead of: 68 | ```c++ 69 | class Fraction : public QObject 70 | { 71 | Q_OBJECT 72 | Q_PROPERTY(double numerator READ numerator WRITE setNumerator NOTIFY numeratorChanged) 73 | Q_PROPERTY(double denominator READ denominator WRITE setDenominator NOTIFY denominatorChanged) 74 | public: 75 | Fraction(QObject* parent = nullptr) : 76 | QObject(parent), 77 | m_numerator(5), 78 | m_denominator(9) 79 | {} 80 | 81 | double numerator() const { 82 | return m_numerator; 83 | } 84 | 85 | double denominator() const { 86 | return m_denominator; 87 | } 88 | 89 | public slots: 90 | void setNumerator(double numerator) { 91 | if (m_numerator == numerator) 92 | return; 93 | m_numerator = numerator; 94 | emit numeratorChanged(numerator); 95 | } 96 | 97 | void setDenominator(double denominator) { 98 | if (m_denominator == denominator) 99 | return; 100 | m_denominator = denominator; 101 | emit denominatorChanged(denominator); 102 | } 103 | 104 | signals: 105 | void numeratorChanged(double numerator); 106 | void denominatorChanged(double denominator); 107 | 108 | private: 109 | double m_numerator; 110 | double m_denominator; 111 | }; 112 | ``` 113 | When the QObject subclass is not supposed to have a particular behavior, the two macros L_BEGIN_CLASS and L_END_CLASS can speed up the declaration even more: 114 | ```c++ 115 | L_BEGIN_CLASS(Fraction) 116 | L_RW_PROP(double, numerator, setNumerator, 5) 117 | L_RW_PROP(double, denominator, setDenominator, 9) 118 | L_END_CLASS 119 | ``` 120 | The L_RW_PROP and L_RO_PROP macros are overloaded, and can therefore be used with three or four params. The last param is used if you want to init the prop to some specific value automatically. 121 | 122 | This makes the usage of Qt properties more synthetic and speeds up the development. It is very useful when many properties are needed to expose data to QML. 123 | 124 | ### References 125 | 126 | If you need to be able to modify the property itself from C++ instead of resetting it, you can use the *_REF alternatives of L_RW_PROP and L_RO_PROP. In that case, getter methods return a reference to the type in C++. 127 | 128 | ### Signals With or Without Parameters 129 | 130 | By default, signals are generated with the value passed in the argument. If you prefer signals without params, you can define LQTUTILS_OMIT_ARG_FROM_SIGNAL **before** including the header. 131 | 132 | ### Autosetter 133 | 134 | It is also possible to omit the name of the setter in the declaration. This results in the auto-generation of a setter with the name "set_". To obtain this behavior, simply use the variants of the above macros with the suffix "_AS". This should work for all property types: ro, rw, ref and all gadget props. 135 | 136 | ### Custom Setter 137 | 138 | It is possible to generate a property without its setter implementation. The setter con be implemented with a slot explicitly by writing a method named "set_" returning void. An example can be found [here](lqtutils/tst_lqtutils.cpp#L52). 139 | 140 | ### Complete List of Available Macros 141 | 142 | For QObjects: 143 | 144 | L_RW_PROP(type, name, setter) 145 | L_RW_PROP(type, name, setter, default) 146 | L_RW_PROP_AS(type, name) 147 | L_RW_PROP_AS(type, name, default) 148 | L_RW_PROP_CS(type, name) 149 | L_RW_PROP_CS(type, name, default) 150 | L_RW_PROP_REF(type, name, setter) 151 | L_RW_PROP_REF(type, name, setter, default) 152 | L_RW_PROP_REF_AS(type, name) 153 | L_RW_PROP_REF_AS(type, name, default) 154 | L_RW_PROP_REF_CS(type, name) 155 | L_RW_PROP_REF_CS(type, name, default) 156 | L_RO_PROP(type, name, setter) 157 | L_RO_PROP(type, name, setter, default) 158 | L_RO_PROP_AS(type, name) 159 | L_RO_PROP_AS(type, name, default) 160 | L_RO_PROP_CS(type, name) 161 | L_RO_PROP_CS(type, name, default) 162 | L_RO_PROP_REF(type, name, setter) 163 | L_RO_PROP_REF(type, name, setter, default) 164 | L_RO_PROP_REF_AS(type, name) 165 | L_RO_PROP_REF_AS(type, name, default) 166 | L_RO_PROP_REF_CS(type, name) 167 | L_RO_PROP_REF_CS(type, name, default) 168 | L_BEGIN_CLASS(name) 169 | L_END_CLASS 170 | 171 | For gadgets: 172 | 173 | L_RW_GPROP(type, name, setter) 174 | L_RW_GPROP(type, name, setter, default) 175 | L_RW_GPROP_AS(type, name) 176 | L_RW_GPROP_AS(type, name, default) 177 | L_RW_GPROP_CS(type, name) 178 | L_RW_GPROP_CS(type, name, default) 179 | L_RO_GPROP(type, name, setter) 180 | L_RO_GPROP(type, name, setter, default) 181 | L_RO_GPROP_AS(type, name) 182 | L_RO_GPROP_AS(type, name, default) 183 | L_RO_GPROP_CS(type, name) 184 | L_RO_GPROP_CS(type, name, default) 185 | L_BEGIN_GADGET(name) 186 | L_END_GADGET 187 | 188 | 189 | ## Synthesize Qt settings with support for signals (lqtutils_settings.h) 190 | **For more info: https://bugfreeblog.duckdns.org/2023/01/lqtutils-settings.html.** 191 | 192 | Contains a few tools that can be used to speed up writing simple settings to a file. Settings will still use QSettings and are therefore fully compatible. The macros are simply shortcuts to synthetise code. I only used this for creating ini files, but should work for other formats. An example: 193 | ```c++ 194 | L_DECLARE_SETTINGS(LSettingsTest, new QSettings("settings.ini", QSettings::IniFormat)) 195 | L_DEFINE_VALUE(QString, string1, QString("string1")) 196 | L_DEFINE_VALUE(QSize, size, QSize(100, 100)) 197 | L_DEFINE_VALUE(double, temperature, -1) 198 | L_DEFINE_VALUE(QByteArray, image, QByteArray()) 199 | L_END_CLASS 200 | 201 | L_DECLARE_SETTINGS(LSettingsTestSec1, new QSettings("settings.ini", QSettings::IniFormat), "SECTION_1") 202 | L_DEFINE_VALUE(QString, string2, QString("string2")) 203 | L_END_CLASS 204 | ``` 205 | This will provide an interface to a "strong type" settings file containing a string, a QSize value, a double, a jpg image and another string, in a specific section of the ini file. Each class is reentrant like QSettings and can be instantiated in multiple threads. 206 | Each class also provides a unique notifier: the notifier can be used to receive notifications when any thread in the code changes the settings, and can also be used in bindings in QML code. For an example, refer to LQtUtilsQuick: 207 | ```c++ 208 | Window { 209 | visible: true 210 | x: settings.appX 211 | y: settings.appY 212 | width: settings.appWidth 213 | height: settings.appHeight 214 | title: qsTr("Hello World") 215 | 216 | Connections { 217 | target: settings 218 | onAppWidthChanged: 219 | console.log("App width saved:", settings.appWidth) 220 | onAppHeightChanged: 221 | console.log("App width saved:", settings.appHeight) 222 | } 223 | 224 | Binding { target: settings; property: "appWidth"; value: width } 225 | Binding { target: settings; property: "appHeight"; value: height } 226 | Binding { target: settings; property: "appX"; value: x } 227 | Binding { target: settings; property: "appY"; value: y } 228 | } 229 | ``` 230 | 231 | 232 | ## synthesize Qt enums and quickly expose to QML (lqtutils_enum.h) 233 | **For more info: https://bugfreeblog.duckdns.org/2020/06/synthesizing-qt-settings.html.** 234 | 235 | Contains a macro to define a enum and register it with the meta-object system. This enum can then be exposed to the QML. To create the enum simply do: 236 | ```c++ 237 | L_DECLARE_ENUM(MyEnum, 238 | Value1 = 1, 239 | Value2, 240 | Value3) 241 | ``` 242 | This enum is exposed using a namespace without subclassing QObject. Register with the QML engine with: 243 | ```c++ 244 | MyEnum::registerEnum("com.luke", 1, 0); 245 | ``` 246 | 247 | 248 | ## Cache values and init automatically 249 | ```lqt::CacheValue``` caches values of any type in a hash and calls the provided lambda if the value was never initialized. This is useful when writing settings classes and you want to read only once. 250 | 251 | 252 | ## Threading tools (lqtutils_threading.h) 253 | ```lqt::RecursiveMutex``` is a simple QMutex subclass defaulting to recursive mode. 254 | 255 | ```INVOKE_AWAIT_ASYNC``` is a wrapper around QMetaObject::invokeMethod that allows to execute a slot or lambda in the thread of an obj, synchronously awaiting for the result. E.g.: 256 | ```c++ 257 | QThread* t = new QThread; 258 | t->start(); 259 | QObject* obj = new QObject; 260 | obj->moveToThread(t); 261 | INVOKE_AWAIT_ASYNC(obj, [&i, currentThread, t] { 262 | i++; 263 | QCOMPARE(t, QThread::currentThread()); 264 | QVERIFY(QThread::currentThread() != currentThread); 265 | QCOMPARE(i, 11); 266 | }); 267 | QCOMPARE(i, 11); 268 | ``` 269 | 270 | ```lqt_run_in_thread``` runs a lambda in a specific QThread asynchronously. 271 | 272 | 273 | ## Auto execute actions when exiting a scope (lqtutils_autoexec.h) 274 | A class that can be used to execute a lambda whenever the current scope ends, e.g.: 275 | ```c++ 276 | int i = 9; 277 | { 278 | lqt::AutoExec autoexec([&] { 279 | i++; 280 | }); 281 | QCOMPARE(i, 9); 282 | } 283 | QCOMPARE(i, 10); 284 | ``` 285 | 286 | ```lqt::SharedAutoExec```: a class that can create copiable autoexec objects. Useful to implement locks with a function being executed when the lock is released. 287 | 288 | 289 | ## Measuring rate (lqtutils_freq.h) 290 | 291 | ```lqt::FreqMeter``` is a class that allows to measure the rate of any sampling activity. For example the refresh rate (see lqtutils_ui.h) or the rate of some kind of processing. 292 | 293 | 294 | ## Measuring frame rate in QML (lqtutils_ui.h) 295 | 296 | **For more info: https://bugfreeblog.duckdns.org/2021/09/measure-framerate-qt.html.** 297 | 298 | ```lqt::FrameRateMonitor``` is a class that can be used to measure the current frame rate of a QQuickWindow. The class monitors the frequency of frame swaps and provides a property with a value reporting the frame rate during the last second. 299 | 300 | 301 | ## Showing Local Notifications 302 | 303 | The `lqt::SystemNotification` and the `lqt::AndroidSystemNotification` objects can be used to show a local notification on Linux, Android, Windows, iOS and MacOS. 304 | 305 | Example: 306 | 307 | ```c++ 308 | #ifdef Q_OS_ANDROID 309 | lqt::AndroidSystemNotification notification; 310 | notification.set_icon(QImage(":/qt/qml/FlashbackPrism/assets/icon_96.png")); 311 | notification.set_activityClass(QSL("org.qtproject.qt.android.bindings.QtActivity")); 312 | #else 313 | lqt::SystemNotification notification; 314 | #endif 315 | notification.set_appName(qApp->applicationName()); 316 | notification.set_title(tr("Flashbacks available")); 317 | notification.set_message(tr("You have %1 memories taken in %2 years for today. Have a look!").arg(photos).arg(years)); 318 | notification.set_openApp(true); 319 | notification.send(); 320 | ``` 321 | 322 | 323 | ## Single shot timer for QML (lqtutils_ui.h) 324 | 325 | ```c++ 326 | lqt::QmlUtils::singleShot(int msec, QJSValue callback) 327 | ``` 328 | 329 | Useful to defer an action of a specified amount of milleseconds in QML, without having to create a timer. Example: 330 | 331 | ```QML 332 | lqtUtils.singleShot(5000, () => console.log("Hello!")) 333 | ``` 334 | 335 | remember to expose lqt::QmlUtils to QML with: 336 | 337 | ```c++ 338 | engine.rootContext()->setContextProperty("lqtUtils", new lqt::QmlUtils(qApp)); 339 | ``` 340 | 341 | 342 | ## Getting safe areas on mobile (lqtutils_ui.h) 343 | 344 | **For more info: https://bugfreeblog.duckdns.org/2023/01/qt-qml-cutouts.html.** 345 | 346 | Both iOS and Android smartphones may include cutouts and you may want to know where it is safe to draw your QML UI. These four methods are useful to get the safe area both on iOS and Android (on Desktop the methods are still defined and simply return 0). 347 | 348 | ```c++ 349 | Q_INVOKABLE static double safeAreaBottomInset(); 350 | Q_INVOKABLE static double safeAreaTopInset(); 351 | Q_INVOKABLE static double safeAreaRightInset(); 352 | Q_INVOKABLE static double safeAreaLeftInset(); 353 | ``` 354 | 355 | Note that the methods return a value in logical pixels, so device pixel ratio is already taken into account. 356 | 357 | This is an example of a demo app where some elements extends under the cutouts, while the interactive area does not. 358 | 359 | 360 | 361 | ### How to Use 362 | 363 | To use it, simply instantiate it when you need it like this: 364 | 365 | ```c++ 366 | QQuickView view; 367 | LQTFrameRateMonitor* monitor = new lqt::FrameRateMonitor(&view); 368 | ``` 369 | 370 | expose it to QML if you need it: 371 | 372 | ```c++ 373 | view.engine()->rootContext()->setContextProperty("fpsmonitor", monitor); 374 | ``` 375 | 376 | and use it in QML (or in C++ if you prefer): 377 | 378 | ```js 379 | Text { text: qsTr("fps: ") + fpsmonitor.freq } 380 | ``` 381 | 382 | If there have been no updates to the scene, Qt will not redraw it and you will end up with a measurement of ~1fps. If you want to measure how many frames *can* be drawn per second, you can enable automatic frame update triggers via the helper 383 | 384 | ```c++ 385 | lqt::enableAutoFrameUpdates(view); 386 | ``` 387 | 388 | which causes the window to request an update on every frame swap (that is on every vsync in most cases). 389 | 390 | ### Details 391 | 392 | The frame rate is provided in a notifiable property of the ```lqt::FrameRateMonitor``` class: 393 | 394 | ```c++ 395 | L_RO_PROP_AS(int, freq, 0) 396 | ``` 397 | 398 | Note that this property is defined using the prop macros provided by this library. The property is recomputed when each frame is swapped or after a second. The overhead of the component should be minimal. 399 | 400 | 401 | ## Measure Performance (lqtutils_perf.h) 402 | 403 | ```measure_time``` is a shortcut to quickly measure a procedure provided in a lambda for benchmarking it. The macro ```L_MEASURE_TIME``` can be used to be able to enable/disable the measurement through ```L_ENABLE_BENCHMARKS``` at project level reducing overhead to zero. 404 | 405 | ### How to Use 406 | 407 | This is an exmaple: 408 | 409 | ```c++ 410 | measure_time([&] { 411 | const uchar* rgba = ... 412 | for (int x = 0; x < width; x++) { 413 | for (int y = 0; y < height; y++) { 414 | [process pixel] 415 | } 416 | } 417 | }, [] (qint64 time) { 418 | qWarning() << "Image processed in:" << time; 419 | }); 420 | ``` 421 | 422 | 423 | ## Blocking Queue for qt (lqtutils_bqueue.h) 424 | 425 | This header contains a simple blocking queue. It allows blocking/nonblocking insertions with timeout, blocking/nonblocking removal and a safe processing of the queue. This is an example of the producer/consumer pattern: 426 | 427 | ```c++ 428 | struct LQTTestProducer : public QThread 429 | { 430 | LQTTestProducer(LQTBlockingQueue* queue) : QThread(), m_queue(queue) {} 431 | void run() override { 432 | static int i = 0; 433 | while (!isInterruptionRequested()) { 434 | QThread::msleep(10); 435 | m_queue->enqueue(i++); 436 | QVERIFY(m_queue->size() <= 10); 437 | } 438 | } 439 | void requestDispose() { 440 | requestInterruption(); 441 | m_queue->requestDispose(); 442 | } 443 | 444 | private: 445 | LQTBlockingQueue* m_queue; 446 | }; 447 | 448 | struct LQTTestConsumer : public QThread 449 | { 450 | LQTTestConsumer(LQTBlockingQueue* queue) : QThread(), m_queue(queue) {} 451 | void run() override { 452 | static int i = 0; 453 | while (!isInterruptionRequested()) { 454 | QThread::msleep(15); 455 | std::optional ret = m_queue->dequeue(); 456 | if (!ret) 457 | return; 458 | QVERIFY(*ret == i++); 459 | QVERIFY(lqt_in_range(m_queue->size(), 0, 11)); 460 | } 461 | } 462 | void requestDispose() { 463 | requestInterruption(); 464 | m_queue->requestDispose(); 465 | } 466 | 467 | private: 468 | LQTBlockingQueue* m_queue; 469 | }; 470 | 471 | [...] 472 | 473 | lqt::BlockingQueue queue(10, QSL("display_name")); 474 | LQTTestConsumer consumer(&queue); 475 | LQTTestProducer producer(&queue); 476 | consumer.start(); 477 | producer.start(); 478 | 479 | QEventLoop loop; 480 | QTimer::singleShot(30*1000, this, [&] { loop.quit(); }); 481 | loop.exec(); 482 | 483 | producer.requestDispose(); 484 | producer.wait(); 485 | consumer.requestDispose(); 486 | consumer.wait(); 487 | ``` 488 | 489 | 490 | ## Download a File with Progress Notifications (lqtutils_net.h) 491 | 492 | The class ```lqt::Downloader``` can be used to download a URL to a file in a background thread. Example: 493 | 494 | ```c++ 495 | QScopedPointer downloader(new lqt::Downloader(url, filePath)); 496 | downloader->download(); 497 | connect(downloader.data(), &lqt::Downloader::downloadProgress, this, [&downloader] (qint64 progress, qint64 total) { 498 | qDebug() << "Downloading:" << progress << "/" << total << downloader->state(); 499 | }); 500 | connect(downloader.data(), &lqt::Downloader::stateChanged, this, [&loop, &downloader] { 501 | if (downloader->state() == lqt::DownloaderState::S_DONE) 502 | // Done 503 | }); 504 | ``` 505 | 506 | ## lqtutils_data.h 507 | 508 | Hash of a file: 509 | 510 | ```c++ 511 | QByteArray lqt::hash(const QString &fileName, 512 | QCryptographicHash::Algorithm algo = QCryptographicHash::Md5) 513 | ``` 514 | 515 | Writes a random file of the specified size: 516 | 517 | ```c++ 518 | bool lqt::random_file(const QString& fileName, qint64 size) 519 | ``` 520 | 521 | 522 | ## FontAwesome in QML (lqtutils_fa.h) 523 | 524 | **For more info: https://bugfreeblog.duckdns.org/2023/05/fontawesome-qml-lqtutils.html.** 525 | 526 | This header includes a function to load FontAwesome fonts and QML items to render the fonts in QML. To include FontAwesome in Qt6 add the support to your cmake file when including the project: 527 | 528 | ```cmake 529 | set(ENABLE_FONT_AWESOME true) 530 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/lqtutils) 531 | ``` 532 | 533 | then init FontAwesome in your main function: 534 | 535 | ```c++ 536 | lqt::embed_font_awesome(view.engine()->rootContext()); 537 | ``` 538 | 539 | Now types and fonts are loaded. To use it in QML: 540 | 541 | ```qml 542 | import "qrc:/lqtutils/fontawesome" 543 | 544 | [...] 545 | 546 | LQTFontAwesomeFreeSolid { 547 | width: 16 548 | height: 16 549 | iconUtf8: "\uf013" 550 | } 551 | ``` 552 | 553 | to ensure the plugin is linked, add these two lines to your main.cpp: 554 | 555 | ```c++ 556 | #include 557 | Q_IMPORT_QML_PLUGIN(lqtutilsPlugin) 558 | ``` 559 | 560 | Note that the size is mandatory. The available types are: `LQTFontAwesomeFreeSolid`, `LQTFontAwesomeFreeRegular` and `LQTFontAwesomeBrandsRegular`. The init function also set the context properties: `fontAwesomeBrandsRegular`, `fontAwesomeFreeRegular` and `fontAwesomeFreeSolid`. These are QFont instances available in QML. 561 | 562 | 563 | ## Compute total and available RAM (lqtutils_system.h) [Linux only] 564 | 565 | **For more info: https://bugfreeblog.duckdns.org/2024/01/computing-total-and-free-ram-in-linux-with-qt.html.** 566 | 567 | This header includes a function to compute the total available RAM and the RAM potentially allocatable. 568 | 569 | ```c++ 570 | std::optional mem = lqt::read_mem_data(); 571 | if (!mem) 572 | return; 573 | qInfo() << "Total:" << mem->totalMemBytes; 574 | qInfo() << "Free :" << mem->freeMemBytes; 575 | ``` -------------------------------------------------------------------------------- /docs/cutouts.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlonluca/lqtutils/33069cfc888a3f8a9b3f93249e0d145124181fdd/docs/cutouts.webp -------------------------------------------------------------------------------- /docs/cutouts_horizontal.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlonluca/lqtutils/33069cfc888a3f8a9b3f93249e0d145124181fdd/docs/cutouts_horizontal.webp -------------------------------------------------------------------------------- /fontawesome/Font Awesome 6 Brands-Regular-400.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlonluca/lqtutils/33069cfc888a3f8a9b3f93249e0d145124181fdd/fontawesome/Font Awesome 6 Brands-Regular-400.otf -------------------------------------------------------------------------------- /fontawesome/Font Awesome 6 Free-Regular-400.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlonluca/lqtutils/33069cfc888a3f8a9b3f93249e0d145124181fdd/fontawesome/Font Awesome 6 Free-Regular-400.otf -------------------------------------------------------------------------------- /fontawesome/Font Awesome 6 Free-Solid-900.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlonluca/lqtutils/33069cfc888a3f8a9b3f93249e0d145124181fdd/fontawesome/Font Awesome 6 Free-Solid-900.otf -------------------------------------------------------------------------------- /fontawesome/LQTFontAwesomeBrandsRegular.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | 3 | Item { 4 | property alias iconColor: innerText.color 5 | property alias iconUtf8: innerText.text 6 | property alias innerTextElement: innerText 7 | 8 | Text { 9 | id: innerText 10 | font.family: fontAwesomeBrandsRegular.family 11 | font.styleName: fontAwesomeBrandsRegular.styleName 12 | font.weight: fontAwesomeBrandsRegular.weight 13 | font.pixelSize: height 14 | anchors.fill: parent 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /fontawesome/LQTFontAwesomeFreeRegular.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | 3 | Item { 4 | property alias iconColor: innerText.color 5 | property alias iconUtf8: innerText.text 6 | property alias innerTextElement: innerText 7 | 8 | Text { 9 | id: innerText 10 | font.family: fontAwesomeFreeRegular.family 11 | font.styleName: fontAwesomeFreeRegular.styleName 12 | font.weight: fontAwesomeFreeRegular.weight 13 | font.pixelSize: height 14 | anchors.fill: parent 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /fontawesome/LQTFontAwesomeFreeSolid.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.15 2 | 3 | Item { 4 | property alias iconColor: innerText.color 5 | property alias iconUtf8: innerText.text 6 | property alias innerTextElement: innerText 7 | 8 | Text { 9 | id: innerText 10 | font.family: fontAwesomeFreeSolid.family 11 | font.styleName: fontAwesomeFreeSolid.styleName 12 | font.weight: fontAwesomeFreeSolid.weight 13 | font.pixelSize: height 14 | anchors.fill: parent 15 | horizontalAlignment: Text.AlignHCenter 16 | verticalAlignment: Text.AlignHCenter 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /lqtutils.pri: -------------------------------------------------------------------------------- 1 | # 2 | # MIT License 3 | # 4 | # Copyright (c) 2020-2021 Luca Carlon 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | 25 | SOURCES += \ 26 | $$PWD/lqtutils_ui.cpp \ 27 | $$PWD/lqtutils_freq.cpp \ 28 | $$PWD/lqtutils_fa.cpp 29 | HEADERS += \ 30 | $$PWD/lqtutils_ui.h \ 31 | $$PWD/lqtutils_freq.h \ 32 | $$PWD/lqtutils_fa.h 33 | ios { 34 | SOURCES += $$PWD/lqtutils_ui.mm 35 | } 36 | contains(QT, statemachine) { 37 | SOURCES += $$PWD/lqtutils_fsm.cpp 38 | HEADERS += $$PWD/lqtutils_fsm.h 39 | } 40 | OTHER_FILES += $$PWD/lqtutils_*.h 41 | -------------------------------------------------------------------------------- /lqtutils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # MIT License 3 | # 4 | # Copyright (c) 2020 Luca Carlon 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | 25 | cmake_minimum_required(VERSION 3.14) 26 | 27 | project(LQtUtilsTest LANGUAGES CXX) 28 | 29 | find_package(QT NAMES Qt6 Qt5 COMPONENTS Test Gui Qml Quick REQUIRED) 30 | find_package(Qt${QT_VERSION_MAJOR} COMPONENTS Test Gui Qml Quick REQUIRED) 31 | 32 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 33 | 34 | set(CMAKE_AUTOUIC ON) 35 | set(CMAKE_AUTOMOC ON) 36 | set(CMAKE_AUTORCC ON) 37 | 38 | set(CMAKE_CXX_STANDARD 17) 39 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 40 | enable_testing() 41 | 42 | include_directories(../) 43 | include(${CMAKE_CURRENT_SOURCE_DIR}/../CMakeLists.txt) 44 | 45 | 46 | add_executable(LQtUtilsTest ${lqtutils_src} tst_lqtutils.cpp) 47 | add_test(NAME LQtUtilsTest COMMAND LQtUtilsTest) 48 | target_link_libraries(LQtUtilsTest PRIVATE Qt${QT_VERSION_MAJOR}::Test Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Qml Qt${QT_VERSION_MAJOR}::Quick) 49 | -------------------------------------------------------------------------------- /lqtutils/tst_lqtutils.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "../lqtutils_prop.h" 36 | #include "../lqtutils_string.h" 37 | #include "../lqtutils_settings.h" 38 | #include "../lqtutils_enum.h" 39 | #include "../lqtutils_autoexec.h" 40 | #include "../lqtutils_threading.h" 41 | #include "../lqtutils_math.h" 42 | #include "../lqtutils_time.h" 43 | #include "../lqtutils_ui.h" 44 | #include "../lqtutils_bqueue.h" 45 | #include "../lqtutils_net.h" 46 | #include "../lqtutils_data.h" 47 | #include "../lqtutils_logging.h" 48 | #include "../lqtutils_system.h" 49 | #include "../lqtutils_misc.h" 50 | #include "../lqtutils_qsl.h" 51 | #include "../lqtutils_models.h" 52 | #ifdef LQT_FONT_AWESOME_ENABLED 53 | #include "../lqtutils_fa.h" 54 | #endif 55 | 56 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 57 | #include 58 | Q_IMPORT_QML_PLUGIN(lqtutilsPlugin) 59 | #endif 60 | 61 | struct LQTSerializeTest 62 | { 63 | QString s; 64 | QImage img; 65 | int i; 66 | }; 67 | Q_DECLARE_METATYPE(LQTSerializeTest) 68 | Q_DECLARE_METATYPE(LQTSerializeTest*) 69 | 70 | bool operator==(const LQTSerializeTest& t1, const LQTSerializeTest& t2) 71 | { return t1.s == t2.s && t1.i == t2.i && t1.img == t2.img; } 72 | 73 | QDebug& operator<<(QDebug& debug, const LQTSerializeTest& v) 74 | { return debug.nospace() << "{" << v.s << ", " << v.i << "}"; } 75 | 76 | QDataStream& operator<<(QDataStream& out, const LQTSerializeTest& v) 77 | { return out << v.s << v.i << v.img; } 78 | 79 | QDataStream& operator>>(QDataStream& in, LQTSerializeTest& v) 80 | { 81 | in >> v.s; 82 | in >> v.i; 83 | in >> v.img; 84 | return in; 85 | } 86 | 87 | inline uint qHash(const LQTSerializeTest& key, uint seed) 88 | { return qHash(key.s, seed) ^ key.i; } 89 | 90 | class LQtUtilsObject : public QObject 91 | { 92 | Q_OBJECT 93 | L_RW_PROP(QString, rwTest, setRwTest, QString()) 94 | L_RW_PROP_AS(QString, rwTestAuto, QString()) 95 | L_RW_PROP_AS(QString, rwTestAuto2, QString("rwTestAuto2")) 96 | L_RW_PROP_AS(QString, rwTestAuto3) 97 | L_RO_PROP_AS(QString, roTestAuto, QString()) 98 | L_RO_PROP_AS(QString, roTestAuto2, QString("roTestAuto2")) 99 | L_RO_PROP_AS(QString, roTestAuto3) 100 | L_RO_PROP(QString, test, setTest) 101 | L_RO_PROP_REF_AS(QString, refRoTestAuto) 102 | L_RO_PROP_REF_AS(QString, refRoTestAuto2, QString("refRoTestAuto2")) 103 | L_RW_PROP_CS(QString, csProp, QSL("HELLO")) 104 | L_RW_PROP_REF_CS(QString, csPropRef, QSL("HELLO")) 105 | L_RO_PROP_REF_CS(QString, csRoPropRef, QSL("csRoPropRef")) 106 | public: 107 | LQtUtilsObject(QObject* parent = nullptr) : QObject(parent) {} 108 | 109 | public slots: 110 | // Custom setter. 111 | void set_csProp(const QString& prop) { 112 | m_csProp = prop; 113 | m_test = QStringLiteral("csProp"); 114 | } 115 | 116 | void set_csPropRef(const QString& prop) { 117 | m_csPropRef = prop; 118 | m_test = QSL("csPropRef"); 119 | } 120 | 121 | void set_csRoProp(const QString& prop) { 122 | m_csRoPropRef = prop; 123 | m_test = QSL("csRoPropRef"); 124 | } 125 | }; 126 | 127 | L_BEGIN_GADGET(LQtUtilsGadget) 128 | L_RO_GPROP_AS(int, someInteger, 5) 129 | L_RO_GPROP_AS(int, someInteger2, 0) 130 | L_RW_GPROP_AS(int, someRwInteger, 6) 131 | L_RW_GPROP_AS(int, someRwInteger2, 0) 132 | L_RO_GPROP_CS(QString, csRoProp, QSL("csRoProp")) 133 | L_RW_GPROP_CS(QString, csRwProp, QSL("csRwProp")) 134 | public: 135 | void set_csRoProp(const QString&) { set_someInteger(5); } 136 | void set_csRwProp(const QString&) { set_someInteger(6); } 137 | L_END_GADGET 138 | 139 | L_BEGIN_CLASS(LPropTest) 140 | L_RW_PROP_REF(QStringList, myListRef, setMyListRef, QStringList() << "hello") 141 | L_RO_PROP(QStringList, myList, setMyList, QStringList() << "hello") 142 | L_END_CLASS 143 | 144 | L_DECLARE_SETTINGS(LSettingsTest, new QSettings("settings.ini", QSettings::IniFormat)) 145 | L_DEFINE_VALUE(QString, string1, QString("string1")) 146 | L_DEFINE_VALUE(QSize, size, QSize(100, 100)) 147 | L_DEFINE_VALUE(double, temperature, -1) 148 | L_DEFINE_VALUE(QByteArray, image, QByteArray()) 149 | L_DEFINE_VALUE(LQTSerializeTest, customVariant, QVariant::fromValue(LQTSerializeTest())) 150 | L_END_CLASS 151 | 152 | L_DECLARE_SETTINGS(LSettingsTestSec1, new QSettings("settings.ini", QSettings::IniFormat), "SECTION_1") 153 | L_DEFINE_VALUE(QString, string2, QString("string2")) 154 | L_DEFINE_VALUE(QString, string3, QString("string3")) 155 | L_DEFINE_VALUE(QString, string4, QString("string4")) 156 | L_END_CLASS 157 | 158 | L_DECLARE_ENUM(MyEnum, 159 | Value1 = 1, 160 | Value2, 161 | Value3) 162 | 163 | class LQtUtilsTest : public QObject 164 | { 165 | Q_OBJECT 166 | public: 167 | LQtUtilsTest(); 168 | ~LQtUtilsTest(); 169 | 170 | private slots: 171 | void test_case1(); 172 | void test_case2(); 173 | void test_case3(); 174 | void test_case4(); 175 | void test_case5(); 176 | void test_case6(); 177 | void test_case7(); 178 | void test_case8(); 179 | void test_case9(); 180 | void test_case10(); 181 | void test_case11(); 182 | void test_case12(); 183 | void test_case13(); 184 | void test_case14(); 185 | void test_case15(); 186 | void test_case16(); 187 | void test_case17(); 188 | void test_case18(); 189 | void test_case19(); 190 | void test_case20(); 191 | void test_case21(); 192 | void test_case22(); 193 | void test_case23(); 194 | void test_case24(); 195 | void test_case25(); 196 | void test_case25_2(); 197 | void test_case26(); 198 | void test_case27(); 199 | void test_case28(); 200 | void test_case29(); 201 | void test_case30(); 202 | void test_case31(); 203 | void test_case32(); 204 | void test_case33(); 205 | void test_case34(); 206 | void test_case35(); 207 | void test_case36(); 208 | void test_case37(); 209 | }; 210 | 211 | LQtUtilsTest::LQtUtilsTest() 212 | { 213 | qRegisterMetaType(); 214 | qRegisterMetaType(); 215 | } 216 | 217 | LQtUtilsTest::~LQtUtilsTest() {} 218 | 219 | void LQtUtilsTest::test_case1() 220 | { 221 | const QString s = QSL("HELLOOOOO!"); 222 | LQtUtilsObject test; 223 | test.setTest(s); 224 | connect(&test, &LQtUtilsObject::testChanged, this, [] (QString) { 225 | qDebug() << "Valued changed"; 226 | }); 227 | QVERIFY(test.test() == s); 228 | test.setTest("HELLO"); 229 | QVERIFY(test.test() != s); 230 | test.set_rwTestAuto("HELLO AUTOSET"); 231 | QVERIFY(test.rwTestAuto() == "HELLO AUTOSET"); 232 | QVERIFY(test.rwTestAuto2() == "rwTestAuto2"); 233 | QVERIFY(test.rwTestAuto3().isNull()); 234 | test.set_roTestAuto("roTestAuto"); 235 | QVERIFY(test.roTestAuto2() == "roTestAuto2"); 236 | QVERIFY(test.roTestAuto3().isNull()); 237 | QVERIFY(test.refRoTestAuto2() == "refRoTestAuto2"); 238 | 239 | test.set_csProp(QSL("csProp")); 240 | QVERIFY(test.test() == QSL("csProp")); 241 | QVERIFY(test.csProp() == QSL("csProp")); 242 | 243 | test.set_csPropRef(QSL("csPropRef")); 244 | QVERIFY(test.test() == QSL("csPropRef")); 245 | QVERIFY(test.csPropRef() == QSL("csPropRef")); 246 | test.csPropRef().replace(QSL("cs"), QSL("helloCs")); 247 | QVERIFY(test.csPropRef() == QSL("helloCsPropRef")); 248 | 249 | LQtUtilsGadget gadget; 250 | QVERIFY(gadget.someInteger() == 5); 251 | QVERIFY(gadget.someRwInteger() == 6); 252 | gadget.set_someInteger2(10); 253 | gadget.set_someRwInteger2(11); 254 | QVERIFY(gadget.someInteger2() == 10); 255 | QVERIFY(gadget.someRwInteger2() == 11); 256 | gadget.set_csRoProp(""); 257 | QVERIFY(gadget.someInteger() == 5); 258 | gadget.set_csRwProp(""); 259 | QVERIFY(gadget.someInteger() == 6); 260 | } 261 | 262 | void LQtUtilsTest::test_case2() 263 | { 264 | QImage image(100, 100, QImage::Format_ARGB32); 265 | QByteArray data; 266 | QBuffer buffer(&data); 267 | buffer.open(QIODevice::WriteOnly); 268 | QPainter p(&image); 269 | p.setBrush(Qt::red); 270 | p.drawRect(image.rect()); 271 | image.save(&buffer, "jpg"); 272 | QVERIFY(data.size() != 0); 273 | 274 | { 275 | QSettings settings("settings.ini", QSettings::IniFormat); 276 | settings.clear(); 277 | } 278 | 279 | { 280 | LSettingsTest& settings = LSettingsTest::notifier(); 281 | settings.set_string1("some string"); 282 | settings.set_size(QSize(250, 200)); 283 | settings.set_temperature(36.7); 284 | settings.set_image(data); 285 | 286 | LSettingsTestSec1 sec1; 287 | sec1.set_string2(QSL("value_in_section")); 288 | QCOMPARE(sec1.string3(true), QSL("string3")); 289 | sec1.set_string3(QSL("value_in_section3")); 290 | QCOMPARE(sec1.string3(true), QSL("value_in_section3")); 291 | QCOMPARE(sec1.string4(true), QSL("string4")); 292 | } 293 | 294 | { 295 | QSettings settings("settings.ini", QSettings::IniFormat); 296 | QCOMPARE(settings.value(QSL("string1")), QSL("some string")); 297 | QCOMPARE(settings.value(QSL("size")).toSize(), QSize(250, 200)); 298 | QCOMPARE(settings.value(QSL("temperature")).toDouble(), 36.7); 299 | QCOMPARE(settings.value(QSL("image")).toByteArray(), data); 300 | QCOMPARE(settings.value(QSL("SECTION_1/string2")), "value_in_section"); 301 | QCOMPARE(settings.value(QSL("SECTION_1/string3")), "value_in_section3"); 302 | QCOMPARE(settings.value(QSL("SECTION_1/string4")), "string4"); 303 | } 304 | 305 | bool signalEmitted = false; 306 | QEventLoop loop; 307 | QTimer::singleShot(0, this, [] { 308 | LSettingsTest::notifier().set_size(QSize(1280, 720)); 309 | }); 310 | connect(&LSettingsTest::notifier(), &LSettingsTest::sizeChanged, this, [&signalEmitted] { 311 | QCOMPARE(LSettingsTest::notifier().size(), QSize(1280, 720)); 312 | signalEmitted = true; 313 | }); 314 | connect(&LSettingsTest::notifier(), &LSettingsTest::sizeChanged, 315 | &loop, &QEventLoop::quit); 316 | loop.exec(); 317 | 318 | QCOMPARE(LSettingsTest::notifier().size(), QSize(1280, 720)); 319 | QVERIFY(signalEmitted); 320 | } 321 | 322 | void LQtUtilsTest::test_case3() 323 | { 324 | QMutex mutex; 325 | int count = 0; 326 | 327 | QEventLoop loop; 328 | 329 | QThreadPool::globalInstance()->setMaxThreadCount(500); 330 | for (int i = 0; i < 1000; i++) { 331 | QThreadPool::globalInstance()->start(QRunnable::create([&mutex, &count, &loop] { 332 | LSettingsTest settings; 333 | settings.set_string1(QSL("runnable1")); 334 | settings.set_temperature(35.5); 335 | 336 | QMutexLocker locker(&mutex); 337 | count++; 338 | if (count >= 2000) 339 | loop.quit(); 340 | })); 341 | QThreadPool::globalInstance()->start(QRunnable::create([&mutex, &count, &loop] { 342 | LSettingsTest settings; 343 | settings.set_string1(QSL("runnable2")); 344 | settings.set_temperature(37.5); 345 | 346 | QMutexLocker locker(&mutex); 347 | count++; 348 | if (count >= 2000) 349 | loop.quit(); 350 | })); 351 | } 352 | 353 | loop.exec(); 354 | 355 | { 356 | QSettings settings("settings.ini", QSettings::IniFormat); 357 | QVERIFY(settings.value(QSL("string1")) == QSL("runnable1") || 358 | settings.value(QSL("string1")) == QSL("runnable2")); 359 | QVERIFY(settings.value(QSL("temperature")).toDouble() == 35.5 || 360 | settings.value(QSL("temperature")).toDouble() == 37.5); 361 | } 362 | } 363 | 364 | void LQtUtilsTest::test_case4() 365 | { 366 | QBENCHMARK { 367 | for (int i = 0; i < 1000; i++) { 368 | LSettingsTestSec1 sec1; 369 | sec1.set_string2(QSL("value")); 370 | } 371 | } 372 | } 373 | 374 | void LQtUtilsTest::test_case5() 375 | { 376 | QBENCHMARK { 377 | for (int i = 0; i < 1000; i++) { 378 | QSettings settings("settings.ini", QSettings::IniFormat); 379 | settings.setValue(QSL("string2"), QSL("value")); 380 | } 381 | } 382 | } 383 | 384 | void LQtUtilsTest::test_case6() 385 | { 386 | QBENCHMARK { 387 | for (int i = 0; i < 1000; i++) { 388 | LSettingsTestSec1 sec1; 389 | QCOMPARE(sec1.string2(), QSL("value")); 390 | } 391 | } 392 | } 393 | 394 | void LQtUtilsTest::test_case7() 395 | { 396 | QBENCHMARK { 397 | for (int i = 0; i < 1000; i++) { 398 | QSettings settings("settings.ini", QSettings::IniFormat); 399 | QCOMPARE(settings.value(QSL("string2")), QSL("value")); 400 | } 401 | } 402 | } 403 | 404 | void LQtUtilsTest::test_case8() 405 | { 406 | MyEnum::qmlRegisterMyEnum("com.luke", 1, 0); 407 | QCOMPARE(MyEnum::Value1, 1); 408 | QCOMPARE(MyEnum::Value2, 2); 409 | QCOMPARE(QMetaEnum::fromType().valueToKey(MyEnum::Value3), QSL("Value3")); 410 | } 411 | 412 | void LQtUtilsTest::test_case9() 413 | { 414 | LPropTest test; 415 | test.myList().append("Luca"); 416 | 417 | LPropTest testRef; 418 | testRef.myListRef().append("Luca"); 419 | 420 | QCOMPARE(test.myList().size(), 1); 421 | QCOMPARE(test.myList().at(0), "hello"); 422 | QCOMPARE(testRef.myListRef().size(), 2); 423 | QCOMPARE(testRef.myListRef().at(0), "hello"); 424 | QCOMPARE(testRef.myListRef().at(1), "Luca"); 425 | } 426 | 427 | void LQtUtilsTest::test_case10() 428 | { 429 | int i = 9; 430 | { 431 | lqt::AutoExec autoexec([&] { 432 | i++; 433 | }); 434 | QCOMPARE(i, 9); 435 | } 436 | QCOMPARE(i, 10); 437 | } 438 | 439 | void LQtUtilsTest::test_case11() 440 | { 441 | int i = 9; 442 | QThread* currentThread = QThread::currentThread(); 443 | INVOKE_AWAIT_ASYNC(this, [&i, currentThread] { 444 | i++; 445 | QCOMPARE(QThread::currentThread(), currentThread); 446 | QCOMPARE(i, 10); 447 | }); 448 | QCOMPARE(i, 10); 449 | 450 | QThread* t = new QThread; 451 | t->start(); 452 | QObject* obj = new QObject; 453 | obj->moveToThread(t); 454 | INVOKE_AWAIT_ASYNC(obj, [&i, currentThread, t] { 455 | i++; 456 | QCOMPARE(t, QThread::currentThread()); 457 | QVERIFY(QThread::currentThread() != currentThread); 458 | QCOMPARE(i, 11); 459 | }); 460 | QCOMPARE(i, 11); 461 | } 462 | 463 | void LQtUtilsTest::test_case12() 464 | { 465 | int i = 9; 466 | 467 | { 468 | lqt::SharedAutoExec lock2; 469 | { 470 | lqt::SharedAutoExec lock; 471 | { 472 | lock = lqt::SharedAutoExec([&i] { 473 | i++; 474 | }); 475 | QCOMPARE(i, 9); 476 | } 477 | QCOMPARE(i, 9); 478 | lock2 = lock; 479 | } 480 | QCOMPARE(i, 9); 481 | } 482 | QCOMPARE(i, 10); 483 | } 484 | 485 | void LQtUtilsTest::test_case13() 486 | { 487 | QVERIFY(lqt::is_in(0, -1, 1)); 488 | QVERIFY(lqt::is_in(19.0, -10.0, 19.1)); 489 | QVERIFY(lqt::is_in(QSL("f"), QSL("a"), QSL("s"))); 490 | QVERIFY(lqt::is_in(QDateTime::currentDateTime(), 491 | QDateTime::currentDateTime().addDays(-4), 492 | QDateTime::currentDateTime().addSecs(1))); 493 | QVERIFY(!lqt::is_in(QDateTime::currentDateTime(), 494 | QDateTime::currentDateTime().addDays(4), 495 | QDateTime::currentDateTime().addSecs(1))); 496 | QVERIFY(lqt::nearest_in_range(1.1, 2., 4.) == 2); 497 | QVERIFY(lqt::nearest_in_range(5, 1, 2) == 2); 498 | QVERIFY(lqt::nearest_in_range(3, 1, 3) == 3); 499 | QVERIFY(lqt::nearest_in_range(2, 1, 3) == 2); 500 | } 501 | 502 | void LQtUtilsTest::test_case14() 503 | { 504 | QDateTime now = QDateTime::currentDateTime(); 505 | QVERIFY(lqt::today().date() == now.date()); 506 | QVERIFY(lqt::today().time() == QTime(0, 0, 0, 0)); 507 | QVERIFY(lqt::tomorrow().date() != now.date()); 508 | QVERIFY(lqt::tomorrow().time() == QTime(0, 0, 0, 0)); 509 | QVERIFY(lqt::today().date().day() == lqt::tomorrow().date().day() - 1); 510 | // May fail a couple of times a year. 511 | QVERIFY(lqt::today().msecsTo(lqt::tomorrow()) == 1000*60*60*24); 512 | } 513 | 514 | void LQtUtilsTest::test_case15() 515 | { 516 | QScopedPointer mon(new lqt::FrameRateMonitor); 517 | } 518 | 519 | void LQtUtilsTest::test_case16() 520 | { 521 | QRectF r(10, 20, 123.45, 678.90); 522 | QString s = lqt::string_from_rect(r); 523 | QVERIFY(s == QSL("10,20,123.45,678.9")); 524 | QRectF r2 = lqt::string_to_rect(s); 525 | QVERIFY(qFuzzyCompare(r.x(), r2.x())); 526 | QVERIFY(qFuzzyCompare(r.y(), r2.y())); 527 | QVERIFY(qFuzzyCompare(static_cast(r.width()), static_cast(r2.width()))); 528 | QVERIFY(qFuzzyCompare(static_cast(r.height()), static_cast(r2.height()))); 529 | } 530 | 531 | struct LQTTestProducer : public QThread 532 | { 533 | LQTTestProducer(lqt::BlockingQueue* queue) : QThread(), m_queue(queue) {} 534 | void run() override { 535 | static int i = 0; 536 | while (!isInterruptionRequested()) { 537 | QThread::msleep(10); 538 | m_queue->enqueue(i++); 539 | QVERIFY(m_queue->size() <= 10); 540 | } 541 | } 542 | void requestDispose() { 543 | requestInterruption(); 544 | m_queue->requestDispose(); 545 | } 546 | 547 | private: 548 | lqt::BlockingQueue* m_queue; 549 | }; 550 | 551 | struct LQTTestConsumer : public QThread 552 | { 553 | LQTTestConsumer(lqt::BlockingQueue* queue) : QThread(), m_queue(queue) {} 554 | void run() override { 555 | static int i = 0; 556 | while (!isInterruptionRequested()) { 557 | QThread::msleep(15); 558 | std::optional ret = m_queue->dequeue(); 559 | if (!ret) 560 | return; 561 | QVERIFY(*ret == i++); 562 | QVERIFY(lqt::is_in(m_queue->size(), 0, 10)); 563 | } 564 | } 565 | void requestDispose() { 566 | requestInterruption(); 567 | m_queue->requestDispose(); 568 | } 569 | 570 | private: 571 | lqt::BlockingQueue* m_queue; 572 | }; 573 | 574 | void LQtUtilsTest::test_case17() 575 | { 576 | lqt::BlockingQueue queue(10, QSL("display_name")); 577 | LQTTestConsumer consumer(&queue); 578 | LQTTestProducer producer(&queue); 579 | consumer.start(); 580 | producer.start(); 581 | 582 | QEventLoop loop; 583 | QTimer::singleShot(30*1000, this, [&] { loop.quit(); }); 584 | loop.exec(); 585 | 586 | producer.requestDispose(); 587 | producer.wait(); 588 | consumer.requestDispose(); 589 | consumer.wait(); 590 | } 591 | 592 | void LQtUtilsTest::test_case18() 593 | { 594 | lqt::BlockingQueue queue(2); 595 | queue.enqueue(0); 596 | queue.enqueue(1); 597 | QVERIFY(queue.size() == 2); 598 | QVERIFY(!queue.enqueue(2, 5)); 599 | QVERIFY(queue.size() == 2); 600 | 601 | queue.lockQueue([] (QList* queue) { 602 | QVERIFY((*queue)[0] == 0); 603 | QVERIFY((*queue)[1] == 1); 604 | }); 605 | 606 | QVERIFY(queue.enqueueDropFirst(2, 0)); 607 | QVERIFY(queue.size() == 2); 608 | 609 | queue.lockQueue([] (QList* queue) { 610 | QVERIFY((*queue)[0] == 1); 611 | QVERIFY((*queue)[1] == 2); 612 | }); 613 | 614 | queue.dequeue(); 615 | queue.dequeue(); 616 | QVERIFY(queue.dequeue(0) == std::nullopt); 617 | 618 | for (int i = 0; i < 1E5; i++) 619 | queue.dequeue(0); 620 | QVERIFY(queue.isEmpty()); 621 | } 622 | 623 | void LQtUtilsTest::test_case19() 624 | { 625 | QVERIFY(qFuzzyCompare(lqt::string_to_float(QSL("1.6"), 1.6), 1.6f)); 626 | QVERIFY(qFuzzyCompare(lqt::string_to_float(QSL("abc"), 1.6), 1.6f)); 627 | QVERIFY(lqt::string_to_int(QSL("5"), 5) == 5); 628 | #pragma GCC diagnostic push 629 | #pragma GCC diagnostic ignored "-Woverflow" 630 | QVERIFY(lqt::string_to_int(QString("%1").arg(std::numeric_limits::max() + 100), 1) == 1); 631 | QVERIFY(lqt::string_to_int64(QString("%1").arg(std::numeric_limits::max() + 100), 632 | 1) == std::numeric_limits::max() + 100); 633 | #pragma GCC diagnostic pop 634 | } 635 | 636 | void LQtUtilsTest::test_case20() 637 | { 638 | QThread t; 639 | t.start(); 640 | QEventLoop loop; 641 | QVERIFY(&t != QThread::currentThread()); 642 | lqt::run_in_thread(&t, [&loop, &t] { 643 | QVERIFY(&t == QThread::currentThread()); 644 | loop.quit(); 645 | }); 646 | loop.exec(); 647 | t.quit(); 648 | t.wait(); 649 | } 650 | 651 | void LQtUtilsTest::test_case21() 652 | { 653 | lqt::CacheValue boolCache; 654 | QVERIFY(boolCache.value("value1", [] () -> bool { return true; }) == true); 655 | QVERIFY(boolCache.value("value1", [] () -> bool { return false; }) == true); 656 | QVERIFY(boolCache.value("value2", [] () -> bool { return false; }) == false); 657 | QVERIFY(boolCache.value("value2", [] () -> bool { return true; }) == false); 658 | QVERIFY(boolCache.isSet("value1")); 659 | QVERIFY(boolCache.isSet("value2")); 660 | QVERIFY(!boolCache.isSet("value3")); 661 | boolCache.reset("value1"); 662 | boolCache.reset("value2"); 663 | QVERIFY(!boolCache.isSet("value1")); 664 | QVERIFY(!boolCache.isSet("value2")); 665 | boolCache.setValue("value1", false); 666 | QVERIFY(!boolCache.value("value1", [] { return false; }) == true); 667 | 668 | lqt::CacheValue enumCache; 669 | QVERIFY(enumCache.value("value1", [] () -> MyEnum::Value { return MyEnum::Value1; }) == MyEnum::Value1); 670 | QVERIFY(enumCache.value("value1", [] () -> MyEnum::Value { return MyEnum::Value2; }) == MyEnum::Value1); 671 | QVERIFY(enumCache.value("value2", [] () -> MyEnum::Value { return MyEnum::Value1; }) == MyEnum::Value1); 672 | QVERIFY(enumCache.value("value2", [] () -> MyEnum::Value { return MyEnum::Value2; }) == MyEnum::Value1); 673 | QVERIFY(enumCache.value("value3", [] () -> MyEnum::Value { return MyEnum::Value3; }) == MyEnum::Value3); 674 | } 675 | 676 | void LQtUtilsTest::test_case22() 677 | { 678 | QThread* t = new QThread; 679 | t->start(); 680 | 681 | QVERIFY(t != QThread::currentThread()); 682 | 683 | lqt::run_in_thread_sync(t, [t] { 684 | QVERIFY(t == QThread::currentThread()); 685 | }); 686 | 687 | QObject* o = new QObject; 688 | o->moveToThread(t); 689 | lqt::run_in_thread_sync(o, [t] { 690 | QVERIFY(t == QThread::currentThread()); 691 | return; 692 | }); 693 | 694 | int testValue = 1; 695 | QVERIFY(lqt::run_in_thread_sync(t, [&testValue] () -> bool { 696 | QThread::sleep(3); 697 | testValue = 5; 698 | return true; 699 | })); 700 | QVERIFY(testValue == 5); 701 | 702 | QVERIFY(lqt::run_in_thread_sync(o, [] () -> bool { 703 | return true; 704 | })); 705 | 706 | o->deleteLater(); 707 | t->quit(); 708 | t->wait(); 709 | t->deleteLater(); 710 | } 711 | 712 | void LQtUtilsTest::test_case23() 713 | { 714 | QString tmpFilePath = QSL("/tmp/lqtutils_test_tmp.png"); 715 | lqt::AutoExec exec([&tmpFilePath] { 716 | QFile::remove(tmpFilePath); 717 | }); 718 | 719 | QScopedPointer downloader(new lqt::Downloader( 720 | QSL("https://raw.githubusercontent.com/carlonluca/" 721 | "isogeometric-analysis/master/2.3/lagrange.svg.png"), 722 | tmpFilePath)); 723 | QVERIFY(downloader->state() == LQTDownloaderState::S_IDLE); 724 | 725 | downloader->download(); 726 | QVERIFY(downloader->state() == LQTDownloaderState::S_DOWNLOADING); 727 | 728 | QEventLoop loop; 729 | connect(downloader.data(), &lqt::Downloader::stateChanged, this, [&loop, &downloader] { 730 | if (downloader->state() == LQTDownloaderState::S_DONE) 731 | loop.quit(); 732 | }); 733 | loop.exec(); 734 | 735 | QVERIFY(downloader->state() == LQTDownloaderState::S_DONE); 736 | } 737 | 738 | void LQtUtilsTest::test_case24() 739 | { 740 | QString tmpFilePath = QSL("/tmp/lqtutils_test_tmp.png"); 741 | lqt::AutoExec exec([&tmpFilePath] { 742 | QFile::remove(tmpFilePath); 743 | }); 744 | 745 | QScopedPointer downloader(new lqt::Downloader( 746 | QSL("https://raw.githubusercontent.com/carlonluca/" 747 | "isogeometric-analysis/master/4.5/iga_knot_insertion_plate_hole.svg.png"), 748 | tmpFilePath)); 749 | QVERIFY(downloader->state() == LQTDownloaderState::S_IDLE); 750 | 751 | downloader->download(); 752 | 753 | QEventLoop loop; 754 | connect(downloader.data(), &lqt::Downloader::downloadProgress, this, [&loop, &downloader] { 755 | QVERIFY(downloader->state() == LQTDownloaderState::S_DOWNLOADING); 756 | downloader->abort(); 757 | loop.quit(); 758 | }); 759 | loop.exec(); 760 | QVERIFY(downloader->state() == LQTDownloaderState::S_ABORTED); 761 | } 762 | 763 | void LQtUtilsTest::test_case25() 764 | { 765 | QScopedPointer downloader(new lqt::Downloader( 766 | QSL("https://raw.githubusercontent.com/carlonluca/" 767 | "isogeometric-analysis/master/4.5/iga_knot_insertion_plate_hole.svg.png"), 768 | QSL("/tmp/dirthatdoesnotexist/tmp.png"))); 769 | QVERIFY(downloader->state() == LQTDownloaderState::S_IDLE); 770 | 771 | downloader->download(); 772 | 773 | QEventLoop loop; 774 | connect(downloader.data(), &lqt::Downloader::stateChanged, this, [&loop, &downloader] { 775 | if (downloader->state() == LQTDownloaderState::S_IO_FAILURE) { 776 | downloader->abort(); 777 | loop.quit(); 778 | } 779 | }); 780 | loop.exec(); 781 | QVERIFY(downloader->state() == LQTDownloaderState::S_IO_FAILURE); 782 | } 783 | 784 | void LQtUtilsTest::test_case25_2() 785 | { 786 | QByteArray expected; 787 | 788 | { 789 | QString tmpFilePath = QSL("/tmp/lqtutils_test_tmp.png"); 790 | lqt::AutoExec exec([&tmpFilePath] { 791 | QFile::remove(tmpFilePath); 792 | }); 793 | 794 | QScopedPointer downloader(new lqt::Downloader( 795 | QSL("https://raw.githubusercontent.com/carlonluca/" 796 | "isogeometric-analysis/master/2.3/lagrange.svg.png"), 797 | tmpFilePath)); 798 | QVERIFY(downloader->state() == LQTDownloaderState::S_IDLE); 799 | 800 | downloader->download(); 801 | QVERIFY(downloader->state() == LQTDownloaderState::S_DOWNLOADING); 802 | 803 | QEventLoop loop; 804 | connect(downloader.data(), &lqt::Downloader::stateChanged, this, [&loop, &downloader] { 805 | if (downloader->state() == LQTDownloaderState::S_DONE) 806 | loop.quit(); 807 | }); 808 | loop.exec(); 809 | 810 | QVERIFY(downloader->state() == LQTDownloaderState::S_DONE); 811 | 812 | QFile f(tmpFilePath); 813 | QVERIFY(f.open(QIODevice::ReadOnly)); 814 | expected = f.readAll(); 815 | } 816 | 817 | QByteArray received; 818 | QScopedPointer downloader(new lqt::Downloader( 819 | QSL("https://raw.githubusercontent.com/carlonluca/" 820 | "isogeometric-analysis/master/2.3/lagrange.svg.png"), 821 | &received)); 822 | QVERIFY(downloader->state() == LQTDownloaderState::S_IDLE); 823 | 824 | downloader->download(); 825 | 826 | QEventLoop loop; 827 | connect(downloader.data(), &lqt::Downloader::stateChanged, this, [&loop, &downloader] { 828 | if (downloader->state() == LQTDownloaderState::S_DONE) { 829 | downloader->abort(); 830 | loop.quit(); 831 | } 832 | }); 833 | loop.exec(); 834 | QVERIFY(downloader->state() == LQTDownloaderState::S_DONE); 835 | QVERIFY(received == expected); 836 | } 837 | 838 | void LQtUtilsTest::test_case26() 839 | { 840 | QString tmpFilePath = QSL("/tmp/lqtutils_test_tmp.xz"); 841 | lqt::AutoExec exec([&tmpFilePath] { 842 | QFile::remove(tmpFilePath); 843 | }); 844 | 845 | QScopedPointer downloader(new lqt::Downloader( 846 | QSL("https://raw.githubusercontent.com/carlonluca/" 847 | "isogeometric-analysis/master/4.5/iga_knot_insertion_plate_hole.svg.png"), 848 | tmpFilePath)); 849 | QVERIFY(downloader->state() == LQTDownloaderState::S_IDLE); 850 | 851 | downloader->download(); 852 | connect(downloader.data(), &lqt::Downloader::downloadProgress, this, [&downloader] (qint64 progress, qint64 total) { 853 | qDebug() << "Downloading:" << progress << "/" << total << downloader->state(); 854 | }); 855 | 856 | QEventLoop loop; 857 | connect(downloader.data(), &lqt::Downloader::stateChanged, this, [&loop, &downloader] { 858 | QVERIFY(downloader->state() == LQTDownloaderState::S_DOWNLOADING || 859 | downloader->state() == LQTDownloaderState::S_DONE); 860 | if (downloader->state() == LQTDownloaderState::S_DONE) 861 | loop.quit(); 862 | }); 863 | loop.exec(); 864 | QVERIFY(downloader->state() == LQTDownloaderState::S_DONE); 865 | QVERIFY(lqt::hash(tmpFilePath) == QByteArray::fromHex(QSL("b471254ec7c69949125834e107b45dd5").toUtf8())); 866 | } 867 | 868 | void LQtUtilsTest::test_case27() 869 | { 870 | QString tmpFilePath("/tmp/lqtutils_test_tmp"); 871 | lqt::AutoExec([&tmpFilePath] { 872 | QFile::remove(tmpFilePath); 873 | }); 874 | 875 | lqt::random_file(tmpFilePath, 1024); 876 | QVERIFY(QFile(tmpFilePath).size() == 1024); 877 | QByteArray md51 = lqt::hash(tmpFilePath); 878 | 879 | lqt::random_file(tmpFilePath, 2048); 880 | QVERIFY(QFile(tmpFilePath).size() == 2048); 881 | QByteArray md52 = lqt::hash(tmpFilePath); 882 | 883 | lqt::random_file(tmpFilePath, 4000); 884 | QVERIFY(QFile(tmpFilePath).size() == 4000); 885 | QByteArray md53 = lqt::hash(tmpFilePath); 886 | 887 | QVERIFY(md51 != md52); 888 | QVERIFY(md51 != md53); 889 | QVERIFY(md52 != md53); 890 | 891 | qDebug() << "MD5:" << md51.toHex() << md52.toHex() << md53.toHex(); 892 | QVERIFY(QFile(tmpFilePath).remove()); 893 | } 894 | 895 | void LQtUtilsTest::test_case28() 896 | { 897 | QVERIFY(lqt::approx_equal(.01f, .01f, .0f)); 898 | QVERIFY(!lqt::approx_equal(.01f, .02f, .0f)); 899 | QVERIFY(lqt::approx_equal(1.f/2, 2.f/4, .0001f)); 900 | QVERIFY(lqt::approx_equal(0.3, 0.1 + 0.2, 0.0005)); 901 | } 902 | 903 | void LQtUtilsTest::test_case29() 904 | { 905 | QImage img(100, 100, QImage::Format_ARGB32); 906 | img.fill(Qt::red); 907 | LQTSerializeTest t { 908 | QSL("test"), img, 10 909 | }; 910 | 911 | LSettingsTest().set_customVariant(t); 912 | 913 | LQTSerializeTest t2 = LSettingsTest().customVariant(); 914 | QVERIFY(t.i == t2.i); 915 | QVERIFY(t.img == t2.img); 916 | QVERIFY(t.s == t2.s); 917 | 918 | LSettingsTest settings; 919 | QBENCHMARK { 920 | for (int i = 0; i < 1E5; i++) 921 | QVERIFY(settings.size() == QSize(1280, 720)); 922 | } 923 | } 924 | 925 | void LQtUtilsTest::test_case30() 926 | { 927 | LQTSerializeTest t1; 928 | t1.i = 1; 929 | t1.s = "Hello"; 930 | 931 | LQTSerializeTest t2; 932 | t2.i = 2; 933 | t2.s = "Luca"; 934 | 935 | QList list2 = QList() << t1 << t2; 936 | #if QT_VERSION_MAJOR > 5 937 | QString res = QDebug::toString(lqt::ListOutput { list2 }); 938 | QVERIFY(res == "( {Hello, 1}, {Luca, 2} )"); 939 | #endif 940 | 941 | // Cannot compare this to a string as the output is not constant. 942 | QSet set = QSet { 943 | t1, t2 944 | }; 945 | qDebug() << "Set:" << lqt::SetOutput { set }; 946 | 947 | // Cannot compare this to a string as the output is not constant. 948 | QHash hash = QHash { 949 | { "1", t1 }, 950 | { "2", t2 } 951 | }; 952 | qDebug() << "Hash:" << lqt::HashOutput { hash }; 953 | } 954 | 955 | void LQtUtilsTest::test_case31() 956 | { 957 | QSet set = { 1, 2, 3, 4, 5 }; 958 | QMutableSetIterator it(set); 959 | while (it.hasNext()) { 960 | const int* any = lqt::get_any(set); 961 | if (!any) { 962 | QVERIFY(set.isEmpty()); 963 | break; 964 | } 965 | QVERIFY(set.contains(*any)); 966 | set.remove(*any); 967 | } 968 | } 969 | 970 | void LQtUtilsTest::test_case32() 971 | { 972 | std::optional mem = lqt::read_mem_data(); 973 | QVERIFY(mem.has_value()); 974 | QVERIFY(mem->totalMemBytes > 0); 975 | QVERIFY(mem->freeMemBytes > 0); 976 | QVERIFY(mem->totalMemBytes > mem->freeMemBytes); 977 | 978 | qInfo() << "Total:" << QLocale::system().formattedDataSize(mem->totalMemBytes); 979 | qInfo() << "Free :" << QLocale::system().formattedDataSize(mem->freeMemBytes); 980 | } 981 | 982 | void LQtUtilsTest::test_case33() 983 | { 984 | { 985 | const bool a = true; 986 | const bool b = false; 987 | QVERIFY(lqt::coalesce(a, b) == a); 988 | } 989 | 990 | { 991 | const bool a = false; 992 | const bool b = true; 993 | QVERIFY(lqt::coalesce(a, b) == b); 994 | } 995 | 996 | { 997 | const QString a; 998 | const QString b = QSL("HELLO!"); 999 | QVERIFY(lqt::coalesce(a, b) == b); 1000 | } 1001 | 1002 | { 1003 | const int a = 0; 1004 | const int b = 2; 1005 | QVERIFY(lqt::coalesce(a, b) == b); 1006 | } 1007 | } 1008 | 1009 | void LQtUtilsTest::test_case34() 1010 | { 1011 | const QList> list { 1012 | QSharedPointer(new LQTSerializeTest), 1013 | QSharedPointer(new LQTSerializeTest), 1014 | QSharedPointer(new LQTSerializeTest) 1015 | }; 1016 | list[1]->s = QSL("Luca Carlon"); 1017 | 1018 | QScopedPointer> model(new lqt::QmlSharedPointerList(list)); 1019 | QCOMPARE(model->rowCount(), list.size()); 1020 | QCOMPARE(model->data(model->index(1, 0)).value()->s, list[1]->s); 1021 | } 1022 | 1023 | void LQtUtilsTest::test_case35() 1024 | { 1025 | QList randomBuffers; 1026 | for (int i = 0; i < 100; i++) { 1027 | const QByteArray randomBuffer = lqt::random_data(1024*1024); 1028 | for (const QByteArray& buffer : randomBuffers) { 1029 | QVERIFY(randomBuffer != buffer); 1030 | QVERIFY(randomBuffer.size() == 1024*1024); 1031 | } 1032 | } 1033 | } 1034 | 1035 | void LQtUtilsTest::test_case36() 1036 | { 1037 | for (int i = 0; i < 100; i++) { 1038 | QTemporaryFile f; 1039 | QVERIFY(f.open()); 1040 | const QByteArray randomData = lqt::random_data(1024*1024); 1041 | QVERIFY(randomData.size() == 1024*1024); 1042 | QVERIFY(f.write(randomData) == 1024*1024); 1043 | f.close(); 1044 | QVERIFY(lqt::read_all(&f) == randomData); 1045 | QFile g("somefilewhichdoesnotexist"); 1046 | QVERIFY(lqt::read_all(&g).isEmpty()); 1047 | } 1048 | } 1049 | 1050 | void LQtUtilsTest::test_case37() 1051 | { 1052 | #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) 1053 | QTemporaryDir dir; 1054 | QString abc = dir.filePath("abc"); 1055 | QString def = dir.filePath("def"); 1056 | QDir subdir(lqt::path_combine({ dir.path(), "subdir" })); 1057 | QString ghi = subdir.filePath("ghi"); 1058 | 1059 | QVERIFY(subdir.mkpath(".")); 1060 | QVERIFY(lqt::write_all(abc, "abc")); 1061 | QVERIFY(lqt::write_all(def, "def")); 1062 | QVERIFY(lqt::write_all(ghi, "ghi")); 1063 | 1064 | const QString src = dir.path(); 1065 | const QString dst = lqt::path_combine({ QDir::tempPath(), "dst" }); 1066 | 1067 | QVERIFY(lqt::copy_path(src, dst)); 1068 | QVERIFY(lqt::read_all(lqt::path_combine({ dst, "abc" })) == "abc"); 1069 | QVERIFY(lqt::read_all(lqt::path_combine({ dst, "def" })) == "def"); 1070 | QVERIFY(lqt::read_all(lqt::path_combine({ dst, "subdir", "ghi" })) == "ghi"); 1071 | QVERIFY(QDir(dst).removeRecursively()); 1072 | #endif 1073 | } 1074 | 1075 | QTEST_GUILESS_MAIN(LQtUtilsTest) 1076 | 1077 | #include "tst_lqtutils.moc" 1078 | -------------------------------------------------------------------------------- /lqtutils_autoexec.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_AUTOEXEC_H 26 | #define LQTUTILS_AUTOEXEC_H 27 | 28 | #include 29 | 30 | #include 31 | 32 | namespace lqt { 33 | 34 | class AutoExec 35 | { 36 | public: 37 | AutoExec(std::function func) { reset(func); } 38 | AutoExec() { reset(nullptr); } 39 | ~AutoExec() { if (m_func) m_func(); } 40 | void reset(std::function func) { m_func = func; } 41 | void reset() { reset(nullptr); } 42 | private: 43 | std::function m_func; 44 | }; 45 | 46 | class SharedAutoExec 47 | { 48 | public: 49 | SharedAutoExec(std::function func) : 50 | m_exec(new AutoExec(func)) {} 51 | SharedAutoExec() {} 52 | 53 | protected: 54 | QSharedPointer m_exec; 55 | }; 56 | 57 | } // namespace 58 | 59 | #endif // LQTUTILS_AUTOEXEC_H 60 | -------------------------------------------------------------------------------- /lqtutils_bqueue.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_BQUEUE 26 | #define LQTUTILS_BQUEUE 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | 35 | namespace lqt { 36 | 37 | template 38 | class BlockingQueue 39 | { 40 | public: 41 | BlockingQueue(int capacity, const QString& name = QString()) : 42 | m_capacity(capacity), m_disposed(false), m_name(name) {} 43 | bool enqueue(const T& e, qint64 timeout = -1); 44 | bool enqueueDropFirst(const T& e, qint64 timeout = -1); 45 | std::optional waitFirst(bool remove, qint64 timeout = -1); 46 | std::optional dequeue(qint64 timeout = -1); 47 | std::optional peek(qint64 timeout = -1); 48 | int size() const; 49 | int capacity() const { return m_capacity; } 50 | bool isEmpty() const; 51 | bool isDisposed() const; 52 | void requestDispose(); 53 | void lockQueue(std::function* queue)> callback); 54 | QString name() { return m_name; } 55 | 56 | private: 57 | int m_capacity; 58 | bool m_disposed; 59 | QString m_name; 60 | mutable QMutex m_mutex; 61 | QWaitCondition m_condFull; 62 | QWaitCondition m_condEmpty; 63 | QList m_queue; 64 | }; 65 | 66 | template 67 | bool BlockingQueue::enqueue(const T& e, qint64 timeout) 68 | { 69 | QMutexLocker locker(&m_mutex); 70 | if (m_disposed) 71 | return false; 72 | if (m_queue.size() >= m_capacity) { 73 | if (!m_condFull.wait(&m_mutex, timeout)) 74 | return false; 75 | if (m_disposed) 76 | return false; 77 | } 78 | 79 | m_queue.append(e); 80 | m_condEmpty.wakeOne(); 81 | 82 | return true; 83 | } 84 | 85 | template 86 | bool BlockingQueue::enqueueDropFirst(const T& e, qint64 timeout) 87 | { 88 | QMutexLocker locker(&m_mutex); 89 | if (m_disposed) 90 | return false; 91 | if (m_queue.size() >= m_capacity) { 92 | if (!m_condFull.wait(&m_mutex, timeout)) 93 | if (!m_queue.isEmpty()) 94 | m_queue.takeFirst(); 95 | if (m_disposed) 96 | return false; 97 | } 98 | 99 | m_queue.append(e); 100 | m_condEmpty.wakeOne(); 101 | 102 | return true; 103 | } 104 | 105 | template 106 | std::optional BlockingQueue::waitFirst(bool remove, qint64 timeout) 107 | { 108 | QMutexLocker locker(&m_mutex); 109 | if (m_disposed) 110 | return std::nullopt; 111 | if (m_queue.isEmpty()) { 112 | if (!timeout) 113 | return std::nullopt; 114 | if (!m_condEmpty.wait(&m_mutex, timeout)) 115 | return std::nullopt; 116 | if (m_disposed) 117 | return std::nullopt; 118 | } 119 | 120 | std::optional ret = remove ? m_queue.takeFirst() : m_queue.first(); 121 | if (remove) 122 | m_condFull.wakeOne(); 123 | return ret; 124 | } 125 | 126 | template 127 | std::optional BlockingQueue::dequeue(qint64 timeout) 128 | { 129 | return waitFirst(true, timeout); 130 | } 131 | 132 | template 133 | std::optional BlockingQueue::peek(qint64 timeout) 134 | { 135 | return waitFirst(false, timeout); 136 | } 137 | 138 | template 139 | int BlockingQueue::size() const 140 | { 141 | QMutexLocker locker(&m_mutex); 142 | return m_queue.size(); 143 | } 144 | 145 | template 146 | bool BlockingQueue::isEmpty() const 147 | { 148 | QMutexLocker locker(&m_mutex); 149 | return m_queue.isEmpty(); 150 | } 151 | 152 | template 153 | bool BlockingQueue::isDisposed() const 154 | { 155 | QMutexLocker locker(&m_mutex); 156 | return m_disposed; 157 | } 158 | 159 | template 160 | void BlockingQueue::requestDispose() 161 | { 162 | QMutexLocker locker(&m_mutex); 163 | m_disposed = true; 164 | m_condFull.wakeAll(); 165 | m_condEmpty.wakeAll(); 166 | } 167 | 168 | template 169 | void BlockingQueue::lockQueue(std::function*)> callback) 170 | { 171 | QMutexLocker locker(&m_mutex); 172 | callback(&m_queue); 173 | } 174 | 175 | } 176 | 177 | #endif // LQTUTILS_BQUEUE 178 | -------------------------------------------------------------------------------- /lqtutils_data.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "lqtutils_string.h" 35 | 36 | namespace lqt { 37 | 38 | /** 39 | * Computes the hash of a file. 40 | * 41 | * @brief lqt_hash 42 | * @param fileName 43 | * @param algo 44 | * @return 45 | */ 46 | QByteArray hash(const QString &fileName, 47 | QCryptographicHash::Algorithm algo = QCryptographicHash::Md5) 48 | { 49 | QFile f(fileName); 50 | if (!f.open(QFile::ReadOnly)) 51 | return QByteArray(); 52 | 53 | QCryptographicHash hash(algo); 54 | if (hash.addData(&f)) 55 | return hash.result(); 56 | 57 | return QByteArray(); 58 | } 59 | 60 | /** 61 | * Writes random data to a device. 62 | * 63 | * @brief lqt_random_file 64 | * @param fileName 65 | * @param size 66 | * @return 67 | */ 68 | bool random_data(QIODevice* device, qint64 size) 69 | { 70 | const int chunks = size/1024; 71 | for (int i = 0; i < chunks; i++) { 72 | QVector list; 73 | list.resize(1024/4); 74 | QRandomGenerator::global()->fillRange(list.data(), list.size()); 75 | const int size = list.size()*sizeof(quint32); 76 | if (device->write(reinterpret_cast(list.data()), size) != size) 77 | return false; 78 | } 79 | 80 | const int tailSize = size - chunks*1024; 81 | for (int i = 0; i < tailSize; i++) { 82 | const quint32 v = QRandomGenerator::global()->generate(); 83 | device->write(reinterpret_cast(&v), 1); 84 | } 85 | 86 | return true; 87 | } 88 | 89 | /** 90 | * Creates a random file of a specified size. 91 | * 92 | * @brief lqt_random_file 93 | * @param fileName 94 | * @param size 95 | * @return 96 | */ 97 | bool random_file(const QString& fileName, qint64 size) 98 | { 99 | QFile f(fileName); 100 | if (!f.open(QIODevice::WriteOnly)) 101 | return false; 102 | 103 | return random_data(&f, size); 104 | } 105 | 106 | /** 107 | * Returns random data in a buffer. 108 | * 109 | * @brief random_data 110 | * @param size 111 | * @return 112 | */ 113 | QByteArray random_data(qint64 size) 114 | { 115 | QBuffer buffer; 116 | buffer.open(QIODevice::WriteOnly); 117 | return random_data(&buffer, size) ? buffer.buffer() : QByteArray(); 118 | } 119 | 120 | template 121 | inline const T* get_any(const QSet& set) 122 | { 123 | if (set.isEmpty()) 124 | return nullptr; 125 | typename QSet::const_iterator it = set.constBegin(); 126 | return &(*it); 127 | } 128 | 129 | /** 130 | * Reads an entire file into memory. 131 | * 132 | * @brief readAll 133 | * @param io 134 | * @return 135 | */ 136 | inline QByteArray read_all(QIODevice* io) 137 | { 138 | if (!io) 139 | return QByteArray(); 140 | 141 | if (!io->open(QIODevice::ReadOnly)) { 142 | qWarning() << "Failed to open I/O device"; 143 | return QByteArray(); 144 | } 145 | 146 | return io->readAll(); 147 | } 148 | 149 | /** 150 | * Reads all data in a file. 151 | * 152 | * @brief read_all 153 | * @param filePath 154 | * @return 155 | */ 156 | inline QByteArray read_all(const QString& filePath) 157 | { 158 | QFile f(filePath); 159 | return read_all(&f); 160 | } 161 | 162 | /** 163 | * Writes data to the device. 164 | * 165 | * @brief write_all 166 | * @param io 167 | * @param data 168 | * @return 169 | */ 170 | inline bool write_all(QIODevice* io, const QByteArray& data) 171 | { 172 | if (!io) 173 | return false; 174 | 175 | if (!io->open(QIODevice::WriteOnly)) { 176 | qWarning() << "Failed to open I/O device"; 177 | return false; 178 | } 179 | 180 | return io->write(data) == data.size(); 181 | } 182 | 183 | /** 184 | * Writes data to the file. 185 | * 186 | * @brief write_all 187 | * @param filePath 188 | * @param data 189 | * @return 190 | */ 191 | inline bool write_all(const QString& filePath, const QByteArray& data) 192 | { 193 | QFile f(filePath); 194 | return write_all(&f, data); 195 | } 196 | 197 | #if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) 198 | /** 199 | * Copies an entire directory to another location. 200 | * 201 | * @brief copy_path 202 | * @param src 203 | * @param dst 204 | * @return 205 | */ 206 | bool copy_path(const QString& src, const QString& dst) 207 | { 208 | QDir sourceDir(src); 209 | if (!sourceDir.exists()) 210 | return false; 211 | 212 | QDir dstDir(dst); 213 | if (dstDir.exists()) 214 | dstDir = QDir(lqt::path_combine({ dst, sourceDir.dirName() })); 215 | if (dstDir.exists()) { 216 | qWarning() << "Destination directory exists"; 217 | return false; 218 | } 219 | 220 | if (!dstDir.mkpath(".")) { 221 | qWarning() << "Failed to create dir" << dstDir.absolutePath(); 222 | return false; 223 | } 224 | 225 | for (const auto& entry : QDirListing(src, QDirListing::IteratorFlag::Recursive)) { 226 | const QString filePath = entry.absoluteFilePath(); 227 | const QString relativePath = sourceDir.relativeFilePath(filePath); 228 | const QString destPath = lqt::path_combine({dstDir.absolutePath(), relativePath}); 229 | 230 | if (QFile(destPath).exists()) { 231 | qWarning() << "Destination exists:" << destPath; 232 | continue; 233 | } 234 | 235 | if (entry.isDir()) { 236 | if (!QDir(destPath).mkpath(".")) { 237 | qWarning() << "Failed to create dir:" << destPath; 238 | continue; 239 | } 240 | } 241 | else { 242 | if (!QFile(filePath).copy(destPath)) { 243 | qWarning() << "Failed to copy" << filePath << "->" << destPath; 244 | continue; 245 | } 246 | } 247 | } 248 | 249 | return true; 250 | } 251 | #endif 252 | 253 | } // namespace 254 | -------------------------------------------------------------------------------- /lqtutils_enum.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020-2021 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_ENUM_H 26 | #define LQTUTILS_ENUM_H 27 | 28 | #include 29 | #include 30 | 31 | #define L_DECLARE_ENUM(enumName, ...) \ 32 | namespace enumName \ 33 | { \ 34 | Q_NAMESPACE \ 35 | enum Value { __VA_ARGS__ }; \ 36 | Q_ENUM_NS(Value) \ 37 | inline int qmlRegister##enumName(const char* uri, int major, int minor) { \ 38 | return qmlRegisterUncreatableMetaObject(enumName::staticMetaObject, \ 39 | uri, major, minor, #enumName, "Access to enums & flags only"); \ 40 | } \ 41 | inline int qRegisterMetaType() { \ 42 | return ::qRegisterMetaType(#enumName); \ 43 | } \ 44 | inline void registerEnum(const char* uri, int major, int minor) { \ 45 | enumName::qmlRegister##enumName(uri, major, minor); \ 46 | enumName::qRegisterMetaType(); \ 47 | } \ 48 | } 49 | 50 | #endif // LQTUTILS_ENUM_H 51 | -------------------------------------------------------------------------------- /lqtutils_fa.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "lqtutils_fa.h" 31 | #include "lqtutils_qsl.h" 32 | 33 | namespace lqt 34 | { 35 | 36 | bool embed_font_awesome(QQmlContext* ctx) 37 | { 38 | if (QFontDatabase::addApplicationFont(QSL(":/lqtutils/fontawesome/Font Awesome 6 Brands-Regular-400.otf")) == -1) { 39 | qWarning() << "Could not load font awesome brands regular"; 40 | return false; 41 | } 42 | 43 | if (QFontDatabase::addApplicationFont(QSL(":/lqtutils/fontawesome/Font Awesome 6 Free-Regular-400.otf")) == -1) { 44 | qWarning() << "Could not load font awesome free regular"; 45 | return false; 46 | } 47 | 48 | if (QFontDatabase::addApplicationFont(QSL(":/lqtutils/fontawesome/Font Awesome 6 Free-Solid-900.otf")) == -1) { 49 | qWarning() << "Could not load font awesome free solid"; 50 | return false; 51 | } 52 | 53 | if (ctx) { 54 | QQmlEngine* engine = ctx->engine(); 55 | if (!engine) { 56 | qWarning() << "Cannot find qml engine"; 57 | return false; 58 | } 59 | 60 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 61 | ctx->setContextProperty("fontAwesomeBrandsRegular", 62 | QFontDatabase::font("Font Awesome 6 Brands", "Regular", 9)); 63 | ctx->setContextProperty("fontAwesomeFreeRegular", 64 | QFontDatabase::font("Font Awesome 6 Free", "Regular", 9)); 65 | ctx->setContextProperty("fontAwesomeFreeSolid", 66 | QFontDatabase::font("Font Awesome 6 Free", "Solid", 9)); 67 | #else 68 | QFontDatabase db; 69 | ctx->setContextProperty("fontAwesomeBrandsRegular", 70 | db.font("Font Awesome 6 Brands", "Regular", 9)); 71 | ctx->setContextProperty("fontAwesomeFreeRegular", 72 | db.font("Font Awesome 6 Free", "Regular", 9)); 73 | ctx->setContextProperty("fontAwesomeFreeSolid", 74 | db.font("Font Awesome 6 Free", "Solid", 9)); 75 | #endif 76 | } 77 | 78 | return true; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /lqtutils_fa.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_FA_H 26 | #define LQTUTILS_FA_H 27 | 28 | #include 29 | 30 | QT_FORWARD_DECLARE_CLASS(QQmlContext); 31 | 32 | namespace lqt 33 | { 34 | 35 | bool embed_font_awesome(QQmlContext* ctx); 36 | 37 | } 38 | 39 | 40 | #endif // LQTUTILS_FA_H 41 | -------------------------------------------------------------------------------- /lqtutils_freq.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | 26 | #include 27 | #include 28 | 29 | #include "lqtutils_freq.h" 30 | 31 | #define AUTO_REFRESH_INTERVAL 1000 32 | 33 | namespace lqt { 34 | 35 | FreqMeter::FreqMeter(QObject* parent) : 36 | QObject(parent) 37 | { 38 | m_refreshTimer = new QTimer(this); 39 | connect(m_refreshTimer, &QTimer::timeout, 40 | this, &FreqMeter::refresh); 41 | m_refreshTimer->setInterval(AUTO_REFRESH_INTERVAL); 42 | m_refreshTimer->setSingleShot(true); 43 | m_refreshTimer->start(); 44 | } 45 | 46 | void FreqMeter::registerSample() 47 | { 48 | QMutexLocker locker(&m_mutex); 49 | m_timestamps.append(QDateTime::currentDateTime()); 50 | QTimer::singleShot(0, this, &FreqMeter::refresh); 51 | } 52 | 53 | void FreqMeter::refresh() 54 | { 55 | QMutexLocker locker(&m_mutex); 56 | QDateTime now = QDateTime::currentDateTime(); 57 | QMutableListIterator it(m_timestamps); 58 | while (it.hasNext()) { 59 | if (it.next().msecsTo(now) > 1000) 60 | it.remove(); 61 | else 62 | break; 63 | } 64 | set_freq(m_timestamps.size()); 65 | m_refreshTimer->stop(); 66 | m_refreshTimer->start(); 67 | } 68 | 69 | } // namesapce 70 | -------------------------------------------------------------------------------- /lqtutils_freq.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_FREQ_H 26 | #define LQTUTILS_FREQ_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include "lqtutils_prop.h" 35 | 36 | QT_FORWARD_DECLARE_CLASS(QTimer); 37 | 38 | namespace lqt { 39 | 40 | class FreqMeter : public QObject 41 | { 42 | Q_OBJECT 43 | L_RO_PROP_AS(int, freq, 0) 44 | public: 45 | explicit FreqMeter(QObject* parent = nullptr); 46 | 47 | public slots: 48 | void registerSample(); 49 | void refresh(); 50 | 51 | private: 52 | QMutex m_mutex; 53 | QList m_timestamps; 54 | QTimer* m_refreshTimer; 55 | }; 56 | 57 | } // namespace 58 | 59 | #endif // LQTUTILS_FREQ_H 60 | -------------------------------------------------------------------------------- /lqtutils_fsm.cpp: -------------------------------------------------------------------------------- 1 | #include "lqtutils_fsm.h" -------------------------------------------------------------------------------- /lqtutils_fsm.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_FSM_H 26 | #define LQTUTILS_FSM_H 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | #include "lqtutils_prop.h" 33 | 34 | namespace lqt { 35 | 36 | class LoggedState : public QState 37 | { 38 | Q_OBJECT 39 | L_RW_PROP(QString, stateName, setStateName) 40 | public: 41 | LoggedState(QState::ChildMode childMode, QString name = QString(), QState* parent = nullptr) : 42 | QState(childMode, parent) { m_stateName = name; init(); } 43 | LoggedState(QString name = QString(), QState* parent = nullptr) : 44 | QState(parent) { m_stateName = name; init(); } 45 | 46 | protected: 47 | virtual void init() { 48 | connect(this, &QState::entered, this, [this] { 49 | qDebug() << "Entered state:" << stateName(); 50 | }); 51 | } 52 | }; 53 | 54 | } // namespace 55 | 56 | #endif // LQTUTILS_FSM_H 57 | -------------------------------------------------------------------------------- /lqtutils_logging.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_LOGGING_H 26 | #define LQTUTILS_LOGGING_H 27 | 28 | #include 29 | 30 | namespace lqt { 31 | 32 | template 33 | struct ListOutput 34 | { 35 | QList& out; 36 | }; 37 | 38 | template 39 | struct SetOutput 40 | { 41 | QSet& out; 42 | }; 43 | 44 | template 45 | struct HashOutput 46 | { 47 | QHash& out; 48 | }; 49 | 50 | 51 | template 52 | inline QDebug operator<<(QDebug debug, const ListOutput& c) 53 | { 54 | QDebugStateSaver saver(debug); 55 | if (c.out.isEmpty()) 56 | return debug << "( )"; 57 | QListIterator it(c.out); 58 | debug.noquote().nospace() << "( "; 59 | while (it.hasNext()) { 60 | debug.noquote() << it.next(); 61 | if (it.hasNext()) 62 | debug.noquote() << ", "; 63 | } 64 | debug.noquote().nospace() << " )"; 65 | return debug; 66 | } 67 | 68 | template 69 | inline QDebug operator<<(QDebug debug, const SetOutput& c) 70 | { 71 | QDebugStateSaver saver(debug); 72 | if (c.out.isEmpty()) 73 | return debug << "{ }"; 74 | QSetIterator it(c.out); 75 | debug.noquote().nospace() << "{ "; 76 | while (it.hasNext()) { 77 | debug.noquote() << it.next(); 78 | if (it.hasNext()) 79 | debug.noquote() << ", "; 80 | } 81 | debug.noquote().nospace() << " }"; 82 | return debug; 83 | } 84 | 85 | template 86 | inline QDebug operator<<(QDebug debug, const HashOutput& c) 87 | { 88 | QDebugStateSaver saver(debug); 89 | if (c.out.isEmpty()) 90 | return debug << "{ }"; 91 | QHashIterator it(c.out); 92 | debug.noquote().nospace() << "{ "; 93 | while (it.hasNext()) { 94 | const auto& n = it.next(); 95 | debug.noquote().nospace() << "{ " << n.key() << ", " << n.value() << " }"; 96 | if (it.hasNext()) 97 | debug.noquote() << ", "; 98 | } 99 | debug.noquote().nospace() << " }"; 100 | return debug; 101 | } 102 | 103 | } 104 | 105 | #endif 106 | -------------------------------------------------------------------------------- /lqtutils_math.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_MATH 26 | #define LQTUTILS_MATH 27 | 28 | #include 29 | #include 30 | 31 | namespace lqt { 32 | 33 | /** 34 | * @brief in_range Returns true iif val is in [a, b). 35 | * @param val 36 | * @param a 37 | * @param b 38 | * @return 39 | */ 40 | template [[deprecated]] inline bool in_range(const T& val, const T& a, const T& b) 41 | { return val >= a && val < b; } 42 | 43 | /** 44 | * @brief in_range Returns true iif val is in [a, b]. 45 | * @param val 46 | * @param a 47 | * @param b 48 | * @return 49 | */ 50 | template inline bool is_in(const T& val, const T& a, const T& b) 51 | { return val >= a && val <= b; } 52 | 53 | /** 54 | * @brief nearest_in_range Returns the nearest value in the range. 55 | * @param val 56 | * @param a 57 | * @param b 58 | * @return 59 | */ 60 | template inline T nearest_in_range(const T& val, const T& a, const T& b) 61 | { return val < a ? a : (val > b) ? b : val; } 62 | 63 | /** 64 | * Compares two floating point numbers. 65 | * 66 | * @brief lqt_approx_equal 67 | * @param a 68 | * @param b 69 | * @param epsilon 70 | * @return 71 | */ 72 | template inline bool approx_equal(const T& a, const T& b, const T& epsilon) 73 | { return std::fabs(a - b) <= epsilon; } 74 | 75 | } // namespace 76 | 77 | #endif // LQTUTILS_MATH 78 | -------------------------------------------------------------------------------- /lqtutils_misc.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_MISC_H 26 | #define LQTUTILS_MISC_H 27 | 28 | #include 29 | 30 | #if defined Q_OS_BLACKBERRY || defined Q_OS_ANDROID || defined Q_OS_IOS || defined Q_OS_WP 31 | #define L_OS_MOBILE 32 | #else 33 | #define L_OS_DESKTOP 34 | #endif 35 | 36 | inline bool operator!(const QString& str) 37 | { 38 | return str.isEmpty(); 39 | } 40 | 41 | namespace lqt { 42 | 43 | /// 44 | /// \brief coalesce is a C++ implementation of the coalesce operator. 45 | /// \param lhs value to be evaluated: if evaluates to true, then it is 46 | /// returned, otherwise rhs is returned. 47 | /// \param rhs is the value to be returned if lhs does not evaluate 48 | /// to true. 49 | /// \return lhs if true, rhs otherwise. 50 | /// 51 | template 52 | T coalesce(T lhs, T rhs) 53 | { 54 | return !lhs ? rhs : lhs; 55 | } 56 | 57 | } 58 | 59 | #endif // LQTUTILS_MISC_H 60 | -------------------------------------------------------------------------------- /lqtutils_models.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_MODELS_H 26 | #define LQTUTILS_MODELS_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | namespace lqt { 37 | 38 | template 39 | class QmlSharedPointerList : public QAbstractListModel 40 | { 41 | public: 42 | QmlSharedPointerList(const QList>& list = QList>(), QObject* parent = nullptr) : 43 | QAbstractListModel(parent), m_list(list) {} 44 | 45 | int rowCount(const QModelIndex& parent = QModelIndex()) const override { 46 | Q_UNUSED(parent) 47 | return static_cast(m_list.count()); 48 | } 49 | 50 | QVariant data(const QModelIndex& index, int role = Qt::UserRole) const override { 51 | if (role != Qt::UserRole) 52 | return QVariant(); 53 | if (index.row() < 0 || index.row() >= m_list.size()) 54 | return QVariant(); 55 | 56 | return QVariant::fromValue(m_list[index.row()].data()); 57 | } 58 | 59 | QHash roleNames() const override { 60 | QHash roles; 61 | roles.insert(Qt::UserRole, "data"); 62 | return roles; 63 | } 64 | 65 | void refreshModel(const QList>& list) { 66 | beginResetModel(); 67 | m_list = list; 68 | endResetModel(); 69 | } 70 | 71 | private: 72 | QList> m_list; 73 | }; 74 | 75 | } // namespace 76 | 77 | #endif // LQTUTILS_MODELS_H 78 | -------------------------------------------------------------------------------- /lqtutils_net.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "lqtutils_net.h" 5 | 6 | //#define DEBUG_LOGS 7 | 8 | namespace lqt { 9 | 10 | DownloaderPriv::DownloaderPriv(const QUrl& url, QIODevice* io, QObject* parent) : 11 | QObject(parent) 12 | , m_manager(new QNetworkAccessManager(this)) 13 | , m_reply(nullptr) 14 | , m_url(url) 15 | , m_io(io) {} 16 | 17 | DownloaderPriv::~DownloaderPriv() 18 | { 19 | #ifdef DEBUG_LOGS 20 | qDebug() << Q_FUNC_INFO; 21 | #endif 22 | } 23 | 24 | void DownloaderPriv::download() 25 | { 26 | set_state(LQTDownloaderState::S_DOWNLOADING); 27 | 28 | m_reply = m_manager->get(QNetworkRequest(m_url)); 29 | connect(m_reply, &QNetworkReply::downloadProgress, 30 | this, &DownloaderPriv::downloadProgress); 31 | connect(m_reply, &QNetworkReply::readyRead, this, [this] { 32 | if (m_state != LQTDownloaderState::S_DOWNLOADING) 33 | return; 34 | write(m_reply->readAll()); 35 | }); 36 | connect(m_reply, &QNetworkReply::finished, this, [this] { 37 | if (m_state != LQTDownloaderState::S_DOWNLOADING) 38 | return; 39 | if (m_reply->error() != QNetworkReply::NoError) { 40 | #ifdef DEBUG_LOGS 41 | qWarning() << "Download error:" << m_reply->error() << m_reply->errorString(); 42 | #endif 43 | set_state(LQTDownloaderState::S_NETWORK_FAILURE); 44 | return; 45 | } 46 | 47 | m_io->close(); 48 | set_state(LQTDownloaderState::S_DONE); 49 | }); 50 | } 51 | 52 | void DownloaderPriv::abort() 53 | { 54 | if (m_reply) { 55 | m_reply->abort(); 56 | m_reply->deleteLater(); 57 | m_reply = nullptr; 58 | } 59 | 60 | set_state(LQTDownloaderState::S_ABORTED); 61 | } 62 | 63 | void DownloaderPriv::write(const QByteArray& data) 64 | { 65 | if (m_state != LQTDownloaderState::S_DOWNLOADING) 66 | return; 67 | 68 | if (!m_io->isOpen()) { 69 | if (!m_io->open(QIODevice::WriteOnly)) { 70 | set_state(LQTDownloaderState::S_IO_FAILURE); 71 | return; 72 | } 73 | } 74 | 75 | if (m_io->write(data) != data.size()) 76 | set_state(LQTDownloaderState::S_IO_FAILURE); 77 | } 78 | 79 | class LQTThread : public QThread 80 | { 81 | #ifdef DEBUG_LOGS 82 | ~LQTThread() { 83 | qDebug() << Q_FUNC_INFO; 84 | } 85 | #endif 86 | }; 87 | 88 | Downloader::Downloader(const QUrl& url, const QString& filePath, QObject* parent) : 89 | Downloader(url, new QFile(filePath), parent) 90 | { 91 | connect(m_thread, &QThread::finished, 92 | m_threadContext->ioDevice(), &QIODevice::deleteLater); 93 | } 94 | 95 | Downloader::Downloader(const QUrl &url, QByteArray* bucket, QObject *parent) : 96 | Downloader(url, new QBuffer(bucket), parent) 97 | { 98 | connect(m_thread, &QThread::finished, 99 | m_threadContext->ioDevice(), &QIODevice::deleteLater); 100 | } 101 | 102 | Downloader::Downloader(const QUrl& url, QIODevice* destIo, QObject* parent) : 103 | QObject(parent) 104 | , m_url(url) 105 | , m_io(destIo) 106 | , m_thread(new LQTThread) 107 | , m_threadContext(new DownloaderPriv(url, destIo)) 108 | { 109 | LQTDownloaderState::registerEnum("com.luke", 1, 0); 110 | 111 | m_threadContext->moveToThread(m_thread); 112 | m_thread->start(); 113 | 114 | connect(m_threadContext, &DownloaderPriv::stateChanged, 115 | this, &Downloader::stateChanged); 116 | connect(m_threadContext, &DownloaderPriv::downloadProgress, 117 | this, &Downloader::downloadProgress); 118 | connect(this, &Downloader::destroyed, 119 | m_threadContext, &QObject::deleteLater); 120 | connect(m_threadContext, &DownloaderPriv::destroyed, 121 | m_thread, &QThread::quit); 122 | connect(m_thread, &QThread::finished, 123 | m_thread, &QThread::deleteLater); 124 | } 125 | 126 | Downloader::~Downloader() 127 | { 128 | #ifdef DEBUG_LOGS 129 | qDebug() << Q_FUNC_INFO; 130 | #endif 131 | if (state() == LQTDownloaderState::S_DOWNLOADING) 132 | abort(); 133 | } 134 | 135 | void Downloader::download() 136 | { 137 | if (m_threadContext->state() != LQTDownloaderState::S_IDLE) { 138 | qFatal("Do not re-use the downloader"); 139 | return; 140 | } 141 | 142 | QMetaObject::invokeMethod(m_threadContext, "download", Qt::BlockingQueuedConnection); 143 | } 144 | 145 | void Downloader::abort() 146 | { 147 | if (m_threadContext->state() == LQTDownloaderState::S_DOWNLOADING) 148 | QMetaObject::invokeMethod(m_threadContext, "abort", Qt::BlockingQueuedConnection); 149 | } 150 | 151 | } // namespace 152 | -------------------------------------------------------------------------------- /lqtutils_net.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2022 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_NET 26 | #define LQTUTILS_NET 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #include "lqtutils_prop.h" 36 | #include "lqtutils_enum.h" 37 | 38 | L_DECLARE_ENUM(LQTDownloaderState, 39 | S_IDLE, 40 | S_DOWNLOADING, 41 | S_DONE, 42 | S_NETWORK_FAILURE, 43 | S_IO_FAILURE, 44 | S_ABORTED) 45 | 46 | namespace lqt { 47 | 48 | class DownloaderPriv : public QObject 49 | { 50 | Q_OBJECT 51 | L_RO_PROP_AS(LQTDownloaderState::Value, state, LQTDownloaderState::S_IDLE) 52 | public: 53 | DownloaderPriv(const QUrl& url, QIODevice* io, QObject* parent = nullptr); 54 | ~DownloaderPriv(); 55 | 56 | QIODevice* ioDevice() { return m_io; } 57 | 58 | public slots: 59 | void download(); 60 | void abort(); 61 | void write(const QByteArray& data); 62 | 63 | signals: 64 | void downloadProgress(qint64 progress, qint64 total); 65 | 66 | private: 67 | QNetworkAccessManager* m_manager; 68 | QNetworkReply* m_reply; 69 | QUrl m_url; 70 | QIODevice* m_io; 71 | }; 72 | 73 | class Downloader : public QObject 74 | { 75 | Q_OBJECT 76 | Q_PROPERTY(bool state READ state NOTIFY stateChanged) 77 | public: 78 | Downloader(const QUrl& url, const QString& filePath, QObject* parent = nullptr); 79 | Downloader(const QUrl& url, QByteArray* bucket, QObject* parent = nullptr); 80 | Downloader(const QUrl& url, QIODevice* destIo, QObject *parent); 81 | ~Downloader(); 82 | 83 | void download(); 84 | void abort(); 85 | 86 | LQTDownloaderState::Value state() const { return m_threadContext->state(); } 87 | 88 | signals: 89 | void stateChanged(LQTDownloaderState::Value state); 90 | void downloadProgress(qint64 done, qint64 total); 91 | 92 | private: 93 | QThread* m_thread; 94 | DownloaderPriv* m_threadContext; 95 | QUrl m_url; 96 | QIODevice* m_io; 97 | }; 98 | 99 | } // namespace 100 | 101 | #endif // LQTUTILS_NET 102 | -------------------------------------------------------------------------------- /lqtutils_perf.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_PERF_H 26 | #define LQTUTILS_PERF_H 27 | 28 | #include 29 | #include 30 | 31 | #include 32 | #include 33 | 34 | #ifdef __GNUC__ 35 | #define LC_LIKELY(x) \ 36 | __builtin_expect((x), 1) 37 | #define LC_UNLIKELY(x) \ 38 | __builtin_expect((x), 0) 39 | #else 40 | #define LC_LIKELY(x) (x) 41 | #define LC_UNLIKELY(x) (x) 42 | #endif // __GNUC__ 43 | 44 | namespace lqt { 45 | 46 | /** 47 | * @brief measure_time Measures time spent in lambda f. 48 | * @param f The procedure to time. 49 | * @param callback The callback returning the result. 50 | * @return Time taken to compute f. 51 | */ 52 | inline void measure_time(std::function f, std::function callback = nullptr, bool disable = false) 53 | { 54 | if (disable) 55 | f(); 56 | else { 57 | QElapsedTimer timer; 58 | timer.start(); 59 | f(); 60 | 61 | qint64 time = timer.elapsed(); 62 | if (callback) 63 | callback(time); 64 | } 65 | } 66 | 67 | /** 68 | * @brief measure_time Measures time spent in lambda f. 69 | * @param f The procedure to time. 70 | * @param disable Whether you want to disable the measurement. 71 | * @param callback The callback returning the result. 72 | * @return Time taken to compute f and result of f. 73 | */ 74 | template 75 | inline T measure_time(std::function f, std::function callback = nullptr, bool disable = false) 76 | { 77 | if (disable) 78 | return f(); 79 | else { 80 | QElapsedTimer timer; 81 | timer.start(); 82 | T res = f(); 83 | 84 | qint64 time = timer.elapsed(); 85 | if (callback) 86 | callback(time); 87 | return res; 88 | } 89 | } 90 | 91 | } // namespace 92 | 93 | #endif // LQTUTILS_PERF_H 94 | -------------------------------------------------------------------------------- /lqtutils_prop.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_PROP_H 26 | #define LQTUTILS_PROP_H 27 | 28 | #include 29 | 30 | // Define LQTUTILS_OMIT_ARG_FROM_SIGNAL to omit the argument from the change notification 31 | // signals. 32 | #ifdef LQTUTILS_OMIT_ARG_FROM_SIGNAL 33 | #define LQTUTILS_EMIT_SIGNAL(name) 34 | #define LQTUTILS_DECLARE_SIGNAL(type, name) 35 | #else 36 | #define LQTUTILS_EMIT_SIGNAL(name) name 37 | #define LQTUTILS_DECLARE_SIGNAL(type, name) type name 38 | #endif 39 | 40 | // The EXPAND macro here is only needed for MSVC: 41 | // https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly 42 | #define EXPAND( x ) x 43 | #define L_PROP_GET_MACRO(_1, _2, _3, _4, NAME,...) NAME 44 | 45 | // QObject 46 | // ======= 47 | #define L_RW_PROP(...) \ 48 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RW_PROP4, L_RW_PROP3, L_RW_PROP2)(__VA_ARGS__) ) 49 | #define L_RW_PROP_AS(...) \ 50 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RW_PROP4_AS, L_RW_PROP3_AS, L_RW_PROP2_AS, L_RW_PROP1_AS)(__VA_ARGS__) ) 51 | #define L_RW_PROP_CS(...) \ 52 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RW_PROP4_CS, L_RW_PROP3_CS, L_RW_PROP2_CS)(__VA_ARGS__) ) 53 | #define L_RO_PROP(...) \ 54 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RO_PROP4, L_RO_PROP3, L_RO_PROP2)(__VA_ARGS__) ) 55 | #define L_RO_PROP_AS(...) \ 56 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RO_PROP4_AS, L_RO_PROP3_AS, L_RO_PROP2_AS, L_RO_PROP1_AS)(__VA_ARGS__) ) 57 | #define L_RO_PROP_CS(...) \ 58 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RO_PROP4_CS, L_RO_PROP3_CS, L_RO_PROP2_CS)(__VA_ARGS__) ) 59 | // References 60 | #define L_RW_PROP_REF(...) \ 61 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RW_PROP_REF4, L_RW_PROP_REF3, L_RW_PROP_REF2)(__VA_ARGS__) ) 62 | #define L_RW_PROP_REF_AS(...) \ 63 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RW_PROP_REF4_AS, L_RW_PROP_REF3_AS, L_RW_PROP_REF2_AS, L_RW_PROP_REF1_AS)(__VA_ARGS__) ) 64 | #define L_RW_PROP_REF_CS(...) \ 65 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RW_PROP_REF4_CS, L_RW_PROP_REF3_CS, L_RW_PROP_REF2_CS)(__VA_ARGS__) ) 66 | #define L_RO_PROP_REF(...) \ 67 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RO_PROP_REF4, L_RO_PROP_REF3, L_RO_PROP_REF2)(__VA_ARGS__) ) 68 | #define L_RO_PROP_REF_AS(...) \ 69 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RO_PROP_REF4_AS, L_RO_PROP_REF3_AS, L_RO_PROP_REF2_AS, L_RO_PROP_REF1_AS)(__VA_ARGS__) ) 70 | #define L_RO_PROP_REF_CS(...) \ 71 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RO_PROP_REF4_CS, L_RO_PROP_REF3_CS, L_RO_PROP_REF2_CS)(__VA_ARGS__) ) 72 | 73 | // A read-write prop both in C++ and in QML 74 | // ======================================== 75 | #define _INT_DECL_L_RW_PROP(type, name, setter) \ 76 | public: \ 77 | type name() const { return m_##name ; } \ 78 | Q_SIGNALS: \ 79 | void name##Changed(LQTUTILS_DECLARE_SIGNAL(type, name)); \ 80 | private: \ 81 | Q_PROPERTY(type name READ name WRITE setter NOTIFY name##Changed) 82 | 83 | #define L_RW_PROP_(type, name, setter) \ 84 | _INT_DECL_L_RW_PROP(type, name, setter) \ 85 | public Q_SLOTS: \ 86 | void setter(type name) { \ 87 | if (m_##name == name) return; \ 88 | m_##name = name; \ 89 | emit name##Changed(LQTUTILS_EMIT_SIGNAL(name)); \ 90 | } \ 91 | private: 92 | 93 | #define L_RW_PROP4(type, name, setter, def) \ 94 | L_RW_PROP_(type, name, setter) \ 95 | type m_##name = def; 96 | 97 | #define L_RW_PROP3(type, name, setter) \ 98 | L_RW_PROP_(type, name, setter) \ 99 | type m_##name; 100 | 101 | // Autosetter 102 | #define L_RW_PROP_AS_(type, name) L_RW_PROP_(type, name, set_##name) 103 | 104 | #define L_RW_PROP3_AS(type, name, def) \ 105 | L_RW_PROP_AS_(type, name) \ 106 | type m_##name = def; 107 | 108 | #define L_RW_PROP2_AS(type, name) \ 109 | L_RW_PROP_AS_(type, name) \ 110 | type m_##name; 111 | 112 | // Custom setter 113 | #define L_RW_PROP_CS_(type, name) \ 114 | _INT_DECL_L_RW_PROP(type, name, set_##name) 115 | 116 | #define L_RW_PROP3_CS(type, name, def) \ 117 | L_RW_PROP_CS_(type, name) \ 118 | type m_##name = def; 119 | 120 | #define L_RW_PROP2_CS(type, name) \ 121 | L_RW_PROP_CS_(type, name) \ 122 | type m_##name; 123 | 124 | // A read-write prop from C++, but read-only from QML 125 | // ================================================== 126 | #define _INT_DECL_L_RO_PROP(type, name, setter) \ 127 | public: \ 128 | type name() const { return m_##name ; } \ 129 | Q_SIGNALS: \ 130 | void name##Changed(LQTUTILS_DECLARE_SIGNAL(type, name)); \ 131 | private: \ 132 | Q_PROPERTY(type name READ name NOTIFY name##Changed) \ 133 | 134 | #define L_RO_PROP_(type, name, setter) \ 135 | _INT_DECL_L_RO_PROP(type, name, set_##name) \ 136 | public: \ 137 | void setter(type name) { \ 138 | if (m_##name == name) return; \ 139 | m_##name = name; \ 140 | emit name##Changed(LQTUTILS_EMIT_SIGNAL(name)); \ 141 | } \ 142 | private: 143 | 144 | #define L_RO_PROP4(type, name, setter, def) \ 145 | L_RO_PROP_(type, name, setter) \ 146 | type m_##name = def; 147 | 148 | #define L_RO_PROP3(type, name, setter) \ 149 | L_RO_PROP_(type, name, setter) \ 150 | type m_##name; 151 | 152 | // Autosetter 153 | #define L_RO_PROP_AS_(type, name) L_RO_PROP_(type, name, set_##name) 154 | 155 | #define L_RO_PROP3_AS(type, name, def) \ 156 | L_RO_PROP_AS_(type, name) \ 157 | type m_##name = def; 158 | 159 | #define L_RO_PROP2_AS(type, name) \ 160 | L_RO_PROP_AS_(type, name) \ 161 | type m_##name; 162 | 163 | // Custom setter 164 | #define L_RO_PROP_CS_(type, name) \ 165 | _INT_DECL_L_RO_PROP(type, name, set_##name) 166 | 167 | #define L_RO_PROP3_CS(type, name, def) \ 168 | L_RO_PROP_CS_(type, name) \ 169 | type m_##name = def; 170 | 171 | #define L_RO_PROP2_CS(type, name) \ 172 | L_RO_PROP_CS_(type, name) \ 173 | type m_##name; 174 | 175 | // For references 176 | // ============== 177 | 178 | // A read-write prop both in C++ and in QML 179 | // ======================================== 180 | #define _INT_DECL_L_RW_PROP_REF(type, name, setter) \ 181 | public: \ 182 | type& name() { return m_##name ; } \ 183 | Q_SIGNALS: \ 184 | void name##Changed(LQTUTILS_DECLARE_SIGNAL(type, name)); \ 185 | private: \ 186 | Q_PROPERTY(type name READ name WRITE setter NOTIFY name##Changed) 187 | 188 | #define L_RW_PROP_REF_(type, name, setter) \ 189 | _INT_DECL_L_RW_PROP_REF(type, name, setter) \ 190 | public Q_SLOTS: \ 191 | void setter(type name) { \ 192 | if (m_##name == name) return; \ 193 | m_##name = name; \ 194 | emit name##Changed(LQTUTILS_EMIT_SIGNAL(name)); \ 195 | } \ 196 | private: 197 | 198 | #define L_RW_PROP_REF4(type, name, setter, def) \ 199 | L_RW_PROP_REF_(type, name, setter) \ 200 | type m_##name = def; 201 | 202 | #define L_RW_PROP_REF3(type, name, setter) \ 203 | L_RW_PROP_REF_(type, name, setter) \ 204 | type m_##name; 205 | 206 | // Autosetter 207 | #define L_RW_PROP_REF_AS_(type, name) L_RW_PROP_REF_(type, name, set_##name) 208 | 209 | #define L_RW_PROP_REF3_AS(type, name, def) \ 210 | L_RW_PROP_REF_AS_(type, name) \ 211 | type m_##name = def; 212 | 213 | #define L_RW_PROP_REF2_AS(type, name) \ 214 | L_RW_PROP_REF_AS_(type, name) \ 215 | type m_##name; 216 | 217 | // Custom setter 218 | #define L_RW_PROP_REF_CS_(type, name) \ 219 | _INT_DECL_L_RW_PROP_REF(type, name, set_##name) 220 | 221 | #define L_RW_PROP_REF3_CS(type, name, def) \ 222 | L_RW_PROP_REF_CS_(type, name) \ 223 | type m_##name = def; 224 | 225 | #define L_RW_PROP_REF2_CS(type, name) \ 226 | L_RW_PROP_REF_CS_(type, name) \ 227 | type m_##name; 228 | 229 | // A read-write prop from C++, but read-only from QML 230 | // ================================================== 231 | #define _INT_DECL_L_RO_PROP_REF(type, name, setter) \ 232 | public: \ 233 | type& name() { return m_##name ; } \ 234 | Q_SIGNALS: \ 235 | void name##Changed(LQTUTILS_DECLARE_SIGNAL(type, name)); \ 236 | private: \ 237 | Q_PROPERTY(type name READ name NOTIFY name##Changed) 238 | 239 | #define L_RO_PROP_REF_(type, name, setter) \ 240 | _INT_DECL_L_RO_PROP_REF(type, name, setter) \ 241 | public Q_SLOTS: \ 242 | void setter(type name) { \ 243 | if (m_##name == name) return; \ 244 | m_##name = name; \ 245 | emit name##Changed(LQTUTILS_EMIT_SIGNAL(name)); \ 246 | } \ 247 | private: 248 | 249 | #define L_RO_PROP_REF4(type, name, setter, def) \ 250 | L_RO_PROP_REF_(type, name, setter) \ 251 | type m_##name = def; 252 | 253 | #define L_RO_PROP_REF3(type, name, setter) \ 254 | L_RO_PROP_REF_(type, name, setter) \ 255 | type m_##name; 256 | 257 | // Autosetter 258 | #define L_RO_PROP_REF_AS_(type, name) L_RO_PROP_REF_(type, name, set_##name) 259 | 260 | #define L_RO_PROP_REF3_AS(type, name, def) \ 261 | L_RO_PROP_REF_AS_(type, name) \ 262 | type m_##name = def; 263 | 264 | #define L_RO_PROP_REF2_AS(type, name) \ 265 | L_RO_PROP_REF_AS_(type, name) \ 266 | type m_##name; 267 | 268 | // Custom setter 269 | #define L_RO_PROP_REF_CS_(type, name) \ 270 | _INT_DECL_L_RO_PROP_REF(type, name, set_##name) \ 271 | 272 | #define L_RO_PROP_REF3_CS(type, name, def) \ 273 | L_RO_PROP_REF_CS_(type, name) \ 274 | type m_##name = def; 275 | 276 | #define L_RO_PROP_REF2_CS(type, name) \ 277 | L_RO_PROP_REF_CS_(type, name) \ 278 | type m_##name; 279 | 280 | // Convenience macros to speed up a QObject subclass. 281 | #define L_BEGIN_CLASS(name) \ 282 | class name : public QObject \ 283 | { \ 284 | Q_OBJECT \ 285 | public: \ 286 | Q_INVOKABLE name(QObject* parent = nullptr) : \ 287 | QObject(parent) {} \ 288 | private: 289 | 290 | #define L_END_CLASS }; 291 | 292 | // Gadget 293 | // ====== 294 | 295 | #define L_RW_GPROP(...) \ 296 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RW_GPROP4, L_RW_GPROP3, L_RW_GPROP2)(__VA_ARGS__) ) 297 | #define L_RW_GPROP_AS(...) \ 298 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RW_GPROP4_AS, L_RW_GPROP3_AS, L_RW_GPROP2_AS, L_RW_GPROP1_AS)(__VA_ARGS__) ) 299 | #define L_RW_GPROP_CS(...) \ 300 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RW_GPROP4_CS, L_RW_GPROP3_CS, L_RW_GPROP2_CS)(__VA_ARGS__) ) 301 | #define L_RO_GPROP(...) \ 302 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RO_GPROP4, L_RO_GPROP3, L_RO_GPROP2)(__VA_ARGS__) ) 303 | #define L_RO_GPROP_AS(...) \ 304 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RO_GPROP4_AS, L_RO_GPROP3_AS, L_RO_GPROP2_AS, L_RO_GPROP1_AS)(__VA_ARGS__) ) 305 | #define L_RO_GPROP_CS(...) \ 306 | EXPAND( L_PROP_GET_MACRO(__VA_ARGS__, L_RO_GPROP4_CS, L_RO_GPROP3_CS, L_RO_GPROP2_CS)(__VA_ARGS__) ) 307 | 308 | #define _INT_DECL_GADGET_PROP_GETTER(type, name) \ 309 | public: \ 310 | type name() const { return m_##name ; } 311 | 312 | // A read-write prop both in C++ and in QML 313 | // ======================================== 314 | #define L_RW_GPROP_(type, name, setter) \ 315 | _INT_DECL_GADGET_PROP_GETTER(type, name) \ 316 | private: \ 317 | Q_PROPERTY(type name READ name WRITE setter) \ 318 | public: \ 319 | void setter(type name) { \ 320 | m_##name = name; \ 321 | } \ 322 | private: 323 | 324 | #define L_RW_GPROP4(type, name, setter, def) \ 325 | L_RW_GPROP_(type, name, setter) \ 326 | type m_##name = def; 327 | 328 | #define L_RW_GPROP3(type, name, setter) \ 329 | L_RW_GPROP_(type, name, setter) \ 330 | type m_##name; 331 | 332 | // Autosetter 333 | #define L_RW_GPROP_AS_(type, name) L_RW_GPROP_(type, name, set_##name) 334 | 335 | #define L_RW_GPROP3_AS(type, name, def) \ 336 | L_RW_GPROP_AS_(type, name) \ 337 | type m_##name = def; 338 | 339 | #define L_RW_GPROP2_AS(type, name) \ 340 | L_RW_GPROP_AS_(type, name) \ 341 | type m_##name; 342 | 343 | // Custom setter 344 | #define L_RW_GPROP_CS_(type, name) \ 345 | _INT_DECL_GADGET_PROP_GETTER(type, name) \ 346 | private: \ 347 | Q_PROPERTY(type name READ name) 348 | 349 | #define L_RW_GPROP3_CS(type, name, def) \ 350 | L_RW_GPROP_CS_(type, name) \ 351 | type m_##name = def; 352 | 353 | #define L_RW_GPROP2_CS(type, name) \ 354 | L_RW_GPROP_CS_(type, name) \ 355 | type m_##name; 356 | 357 | // A read-write prop from C++, but read-only from QML 358 | // ================================================== 359 | #define L_RO_GPROP_(type, name, setter) \ 360 | _INT_DECL_GADGET_PROP_GETTER(type, name) \ 361 | private: \ 362 | Q_PROPERTY(type name READ name) \ 363 | public: \ 364 | void setter(type name) { \ 365 | m_##name = name; \ 366 | } \ 367 | private: 368 | 369 | #define L_RO_GPROP4(type, name, setter, def) \ 370 | L_RO_GPROP_(type, name, setter) \ 371 | type m_##name = def; 372 | 373 | #define L_RO_GPROP3(type, name, setter) \ 374 | L_RO_GPROP_(type, name, setter) \ 375 | type m_##name; 376 | 377 | // Autosetter 378 | #define L_RO_GPROP_AS_(type, name) L_RO_GPROP_(type, name, set_##name) 379 | 380 | #define L_RO_GPROP3_AS(type, name, def) \ 381 | L_RO_GPROP_AS_(type, name) \ 382 | type m_##name = def; 383 | 384 | #define L_RO_GPROP2_AS(type, name) \ 385 | L_RO_GPROP_AS_(type, name) \ 386 | type m_##name; 387 | 388 | // Custom setter 389 | #define L_RO_GPROP_CS_(type, name) \ 390 | _INT_DECL_GADGET_PROP_GETTER(type, name) \ 391 | private: \ 392 | Q_PROPERTY(type name READ name) 393 | 394 | #define L_RO_GPROP3_CS(type, name, def) \ 395 | L_RO_GPROP_CS_(type, name) \ 396 | type m_##name = def; 397 | 398 | #define L_RO_GPROP2_CS(type, name) \ 399 | L_RO_GPROP_CS_(type, name) \ 400 | type m_##name; 401 | 402 | // Convenience macros to speed up a gadget subclass. 403 | #define L_BEGIN_GADGET(name) \ 404 | class name \ 405 | { \ 406 | Q_GADGET \ 407 | public: \ 408 | Q_INVOKABLE name() {} \ 409 | private: 410 | 411 | 412 | #define L_END_GADGET }; 413 | 414 | #endif // LQTUTILS_PROP_H 415 | -------------------------------------------------------------------------------- /lqtutils_qsl.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_QSL_H 26 | #define LQTUTILS_QSL_H 27 | 28 | #ifndef QSL 29 | #define QSL QStringLiteral 30 | #endif // QSL 31 | 32 | #endif // LQTUTILS_QSL_H 33 | -------------------------------------------------------------------------------- /lqtutils_settings.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LSETTINGS_H 26 | #define LSETTINGS_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | // The EXPAND macro here is only needed for MSVC: 35 | // https://stackoverflow.com/questions/5134523/msvc-doesnt-expand-va-args-correctly 36 | #define EXPAND( x ) x 37 | #define L_SETTINGS_GET_MACRO(_1, _2, _3, NAME,...) NAME 38 | #define L_DECLARE_SETTINGS(...) \ 39 | EXPAND(L_SETTINGS_GET_MACRO(__VA_ARGS__, L_DECLARE_SETTINGS3, L_DECLARE_SETTINGS2, L_DECLARE_SETTINGS1)(__VA_ARGS__)) 40 | 41 | // Defines a single value inside the settings class. 42 | #define L_DEFINE_VALUE(type, name, def) \ 43 | public: \ 44 | type name(bool create = false) const { \ 45 | const QString key = m_section + QStringLiteral(#name); \ 46 | if (create && !m_settings->contains(key)) \ 47 | m_settings->setValue(key, def); \ 48 | return m_settings->value(key, def).value(); \ 49 | } \ 50 | public Q_SLOTS: \ 51 | void set_##name(type value) { \ 52 | if (name() == value) return; \ 53 | m_settings->setValue(m_section + QStringLiteral(#name), QVariant::fromValue(value)); \ 54 | if (this != ¬ifier()) emit name##Changed(value); \ 55 | emit notifier().name##Changed(value); \ 56 | } \ 57 | Q_SIGNALS: \ 58 | void name##Changed(type name); \ 59 | private: \ 60 | Q_PROPERTY(type name READ name WRITE set_##name NOTIFY name##Changed) 61 | 62 | // Declares the settings class. 63 | #define L_DECLARE_SETTINGS2(classname, qsettings) \ 64 | L_DECLARE_SETTINGS3(classname, qsettings, "") 65 | 66 | #define L_DECLARE_SETTINGS3(classname, qsettings, section) \ 67 | class classname : public QObject \ 68 | { \ 69 | private: \ 70 | Q_OBJECT \ 71 | public: \ 72 | static classname& notifier() { \ 73 | static classname _notifier; \ 74 | return _notifier; \ 75 | } \ 76 | public: \ 77 | classname(QObject* parent = nullptr) : QObject(parent) { \ 78 | m_settings = qsettings; \ 79 | m_section = QStringLiteral(section).isEmpty() ? "" : QString("%1/").arg(section); \ 80 | } \ 81 | ~classname() { delete m_settings; } \ 82 | protected: \ 83 | QSettings* m_settings; \ 84 | QString m_section; 85 | 86 | namespace lqt { 87 | 88 | template 89 | class CacheValue 90 | { 91 | public: 92 | T value(const QString& key, std::function init); 93 | void reset(const QString& key); 94 | void setValue(const QString& key, const T& v); 95 | bool isSet(const QString& key); 96 | 97 | private: 98 | QHash m_cache; 99 | QMutex m_mutex; 100 | }; 101 | 102 | template 103 | T CacheValue::value(const QString& key, std::function init) 104 | { 105 | QMutexLocker locker(&m_mutex); 106 | if (m_cache.contains(key)) 107 | return m_cache.value(key); 108 | T v = init(); 109 | m_cache.insert(key, v); 110 | return v; 111 | } 112 | 113 | template 114 | void CacheValue::reset(const QString& key) 115 | { 116 | QMutexLocker locker(&m_mutex); 117 | m_cache.remove(key); 118 | } 119 | 120 | template 121 | void CacheValue::setValue(const QString& key, const T& v) 122 | { 123 | QMutexLocker locker(&m_mutex); 124 | m_cache.insert(key, v); 125 | } 126 | 127 | template 128 | bool CacheValue::isSet(const QString& key) 129 | { 130 | QMutexLocker locker(&m_mutex); 131 | return m_cache.contains(key); 132 | } 133 | 134 | } // namespace 135 | 136 | #endif 137 | -------------------------------------------------------------------------------- /lqtutils_sslcheck.h: -------------------------------------------------------------------------------- 1 | #ifndef LQTUTILS_SSL_H 2 | #define LQTUTILS_SSL_H 3 | 4 | #ifdef QT_NO_SSL 5 | #error SSL support missing in this Qt build 6 | #endif 7 | 8 | #endif // LQTUTILS_SSL_H 9 | -------------------------------------------------------------------------------- /lqtutils_string.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_STRING_H 26 | #define LQTUTILS_STRING_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #ifndef QStringLiteral 34 | #define QStringLiteral(s) s 35 | #endif 36 | 37 | namespace lqt { 38 | 39 | inline QString path_combine(std::initializer_list l) 40 | { 41 | QString ret; 42 | for (size_t i = 0; i < l.size(); i++) { 43 | ret += *(l.begin() + i); 44 | if (Q_LIKELY(i != l.size() - 1)) 45 | ret += QDir::separator(); 46 | } 47 | return ret; 48 | } 49 | 50 | inline int string_to_int(const QString& s, int def, bool* ok = nullptr) 51 | { 52 | bool _ok; 53 | int ret = QLocale::system().toInt(s, &_ok); 54 | if (ok) 55 | *ok = _ok; 56 | return _ok ? ret : def; 57 | } 58 | 59 | inline qint64 string_to_int64(const QString& s, qint64 def, bool* ok = nullptr) 60 | { 61 | bool _ok; 62 | qint64 ret = QLocale::system().toLongLong(s, &_ok); 63 | if (ok) 64 | *ok = _ok; 65 | return _ok ? ret : def; 66 | } 67 | 68 | inline qint64 string_to_uint64(const QString& s, quint64 def, bool* ok = nullptr) 69 | { 70 | bool _ok; 71 | qint64 ret = QLocale::system().toULongLong(s, &_ok); 72 | if (ok) 73 | *ok = _ok; 74 | return _ok ? ret : def; 75 | } 76 | 77 | inline float string_to_float(const QString& s, float def, bool* ok = nullptr) 78 | { 79 | bool _ok; 80 | float ret = QLocale::system().toFloat(s, &_ok); 81 | if (ok) 82 | *ok = _ok; 83 | return _ok ? ret : def; 84 | } 85 | 86 | inline double string_to_double(const QString& s, double def, bool* ok = nullptr) 87 | { 88 | bool _ok; 89 | double ret = QLocale::system().toDouble(s, &_ok); 90 | if (ok) 91 | *ok = _ok; 92 | return _ok ? ret : def; 93 | } 94 | 95 | inline float string_to_float_en(const QString& s, float def, bool* ok = nullptr) 96 | { 97 | bool _ok; 98 | QLocale locale(QLocale::English, QLocale::UnitedStates); 99 | float ret = locale.toFloat(s, &_ok); 100 | if (ok) 101 | *ok = _ok; 102 | return _ok ? ret : def; 103 | } 104 | 105 | inline QRectF string_to_rect(const QString& s) 106 | { 107 | if (s.isEmpty()) 108 | return QRectF(); 109 | 110 | QStringList tokens = s.split(','); 111 | if (tokens.size() != 4) 112 | return QRectF(); 113 | 114 | bool convOk; 115 | QRectF ret; 116 | QLocale locale(QLocale::English, QLocale::UnitedStates); 117 | ret.setX(locale.toDouble(tokens[0].trimmed(), &convOk)); 118 | if (!convOk) 119 | return QRectF(); 120 | ret.setY(locale.toDouble(tokens[1].trimmed(), &convOk)); 121 | if (!convOk) 122 | return QRectF(); 123 | ret.setWidth(locale.toDouble(tokens[2].trimmed(), &convOk)); 124 | if (!convOk) 125 | return QRectF(); 126 | ret.setHeight(locale.toDouble(tokens[3].trimmed(), &convOk)); 127 | if (!convOk) 128 | return QRectF(); 129 | 130 | return ret; 131 | } 132 | 133 | inline QString string_from_rect(const QRectF& rect) 134 | { 135 | if (!rect.isValid() || rect.isNull()) 136 | return QString(); 137 | 138 | QLocale locale(QLocale::English, QLocale::UnitedStates); 139 | return QString(QStringLiteral("%1,%2,%3,%4")).arg( 140 | locale.toString(rect.x()) 141 | , locale.toString(rect.y()) 142 | , locale.toString(rect.width()) 143 | , locale.toString(rect.height())); 144 | } 145 | 146 | inline QPointF string_to_point(const QString& s) 147 | { 148 | if (s.isEmpty()) 149 | return QPoint(); 150 | 151 | QStringList tokens = s.split(','); 152 | if (tokens.size() != 2) 153 | return QPoint(); 154 | 155 | bool convOk; 156 | QPoint ret; 157 | QLocale locale(QLocale::English, QLocale::UnitedStates); 158 | ret.setX(locale.toDouble(tokens[0].trimmed(), &convOk)); 159 | if (!convOk) 160 | return QPointF(); 161 | ret.setY(locale.toDouble(tokens[1].trimmed(), &convOk)); 162 | if (!convOk) 163 | return QPointF(); 164 | 165 | return ret; 166 | } 167 | 168 | inline QString string_from_point(const QPointF& point) 169 | { 170 | if (point.isNull()) 171 | return QString(); 172 | 173 | QLocale locale(QLocale::English, QLocale::UnitedStates); 174 | return QString(QStringLiteral("%1,%2")) 175 | .arg(locale.toString(point.x()), locale.toString(point.y())); 176 | } 177 | 178 | inline QSize string_to_size(const QString& s) 179 | { 180 | if (s.isEmpty()) 181 | return QSize(); 182 | 183 | QStringList tokens = s.split('x'); 184 | if (tokens.size() != 2) 185 | return QSize(); 186 | 187 | bool convOk; 188 | int width = string_to_int(tokens[0], 0, &convOk); 189 | if (!convOk) 190 | return QSize(); 191 | int height = string_to_int(tokens[1], 0, &convOk); 192 | if (!convOk) 193 | return QSize(); 194 | 195 | return QSize(width, height); 196 | } 197 | 198 | inline QString size_to_string(const QSize& size) 199 | { 200 | if (!size.isValid() || size.isNull()) 201 | return QString(); 202 | 203 | return QString(QStringLiteral("%1x%2")) 204 | .arg(size.width()) 205 | .arg(size.height()); 206 | } 207 | 208 | } // namespace 209 | 210 | #endif // LQTUTILS_STRING_H 211 | -------------------------------------------------------------------------------- /lqtutils_system.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include 31 | 32 | namespace lqt { 33 | 34 | struct MemData 35 | { 36 | // Total usable memory. 37 | qint64 totalMemBytes; 38 | // Total memory still available. This is not necessarily unused, but can 39 | // be allocated. 40 | qint64 freeMemBytes; 41 | }; 42 | 43 | /// @brief Returns RAM info. This does not take swap into account. 44 | /// @return Structure with total and free mem. 45 | inline std::optional read_mem_data() 46 | { 47 | QFile memFile(QStringLiteral("/proc/meminfo")); 48 | if (!memFile.exists()) { 49 | qWarning() << "Meminfo file missing"; 50 | return std::nullopt; 51 | } 52 | 53 | if (!memFile.open(QIODevice::ReadOnly)) { 54 | qWarning() << "Cannot read meminfo file"; 55 | return std::nullopt; 56 | } 57 | 58 | const QString meminfoContent = memFile.readAll(); 59 | 60 | static const QRegularExpression regexMemTotal(QStringLiteral("MemTotal:\\s*(\\d+)")); 61 | static const QRegularExpression regexMemFree(QStringLiteral("MemFree:\\s*(\\d+)")); 62 | static const QRegularExpression regexMemBuff(QStringLiteral("Buffers:\\s*(\\d+)")); 63 | static const QRegularExpression regexMemCache(QStringLiteral("Cached:\\s*(\\d+)")); 64 | static const QRegularExpression regexMemRecl(QStringLiteral("SReclaimable:\\s*(\\d+)")); 65 | 66 | auto readValue = [&meminfoContent] (const QRegularExpression& regex) -> qint64 { 67 | QRegularExpressionMatch match = regex.match(meminfoContent); 68 | if (!match.hasMatch() || match.lastCapturedIndex() != 1) { 69 | qWarning() << "Failed to parse meminfo file"; 70 | return -1; 71 | } 72 | 73 | return match.captured(1).toLongLong(); 74 | }; 75 | 76 | const qint64 memTotal = readValue(regexMemTotal)*1024; 77 | const qint64 memFree = readValue(regexMemFree)*1024; 78 | const qint64 memBuff = readValue(regexMemBuff)*1024; 79 | const qint64 memCache = readValue(regexMemCache)*1024; 80 | const qint64 memRecl = readValue(regexMemRecl)*1024; 81 | if (memTotal < 0 || memFree < 0 || memBuff < 0 || memCache < 0 || memRecl < 0) 82 | return std::nullopt; 83 | 84 | return MemData { 85 | memTotal, 86 | memFree + memBuff + memCache + memRecl 87 | }; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /lqtutils_threading.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2020 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_THREADING_H 26 | #define LQTUTILS_THREADING_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 35 | #include 36 | #endif 37 | #include 38 | 39 | #define INVOKE_AWAIT_ASYNC(obj, ...) { \ 40 | auto type = QThread::currentThread() == obj->thread() ? Qt::DirectConnection : Qt::BlockingQueuedConnection; \ 41 | QMetaObject::invokeMethod(obj, __VA_ARGS__, type); \ 42 | } 43 | 44 | namespace lqt { 45 | 46 | template T run_in_thread_sync(QObject* o, std::function f) 47 | { 48 | QSemaphore sem; 49 | T ret; 50 | QTimer::singleShot(0, o, [&f, &ret, &sem] { 51 | ret = f(); 52 | sem.release(); 53 | }); 54 | sem.acquire(); 55 | return ret; 56 | } 57 | 58 | template T run_in_thread_sync(QThread* t, std::function f) 59 | { 60 | QSemaphore sem; 61 | T ret; 62 | QObject* o = new QObject; 63 | o->moveToThread(t); 64 | QTimer::singleShot(0, o, [&f, o, &ret, &sem] { 65 | ret = f(); 66 | o->deleteLater(); 67 | sem.release(); 68 | }); 69 | sem.acquire(); 70 | return ret; 71 | } 72 | 73 | inline void run_in_thread_sync(QThread* t, std::function f) 74 | { 75 | QSemaphore sem; 76 | QObject* o = new QObject; 77 | o->moveToThread(t); 78 | QTimer::singleShot(0, o, [&f, o, &sem] { 79 | f(); 80 | o->deleteLater(); 81 | sem.release(); 82 | }); 83 | sem.acquire(); 84 | } 85 | 86 | inline void run_in_thread_sync(QObject* o, std::function f, QSemaphore* sem) 87 | { 88 | QTimer::singleShot(0, o, [&f, &sem] { 89 | f(); 90 | sem->release(); 91 | }); 92 | sem->acquire(); 93 | } 94 | 95 | inline void run_in_thread_sync(QObject* o, std::function f) 96 | { 97 | QSemaphore sem; 98 | run_in_thread_sync(o, f, &sem); 99 | } 100 | 101 | inline void run_in_thread(QThread* t, std::function f) 102 | { 103 | QObject* o = new QObject; 104 | o->moveToThread(t); 105 | QTimer::singleShot(0, o, [f, o] { 106 | f(); 107 | o->deleteLater(); 108 | }); 109 | } 110 | 111 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) 112 | class RecursiveMutex : public QMutex 113 | { 114 | public: 115 | RecursiveMutex() : 116 | QMutex(QMutex::Recursive) {} 117 | }; 118 | #else 119 | typedef QRecursiveMutex RecursiveMutex; 120 | #endif 121 | 122 | } // namespace 123 | 124 | #endif // LQTUTILS_THREADING_H -------------------------------------------------------------------------------- /lqtutils_time.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_TIME_H 26 | #define LQTUTILS_TIME_H 27 | 28 | #include 29 | 30 | namespace lqt { 31 | 32 | inline QDateTime today() { 33 | QDateTime now = QDateTime::currentDateTime(); 34 | now.setTime(QTime(0, 0)); 35 | return now; 36 | } 37 | 38 | inline QDateTime tomorrow() { 39 | return today().addDays(1); 40 | } 41 | 42 | } 43 | 44 | #endif // LQTUTILS_STRING_H 45 | -------------------------------------------------------------------------------- /lqtutils_ui.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #ifdef Q_OS_ANDROID 33 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 34 | #include 35 | #else 36 | #include 37 | #include 38 | typedef QAndroidJniObject QJniObject; 39 | #endif 40 | #include 41 | #include 42 | #endif 43 | 44 | #if !defined(QT_NO_DBUS) && defined(Q_OS_LINUX) 45 | #include 46 | #include 47 | #endif 48 | 49 | #if defined(Q_OS_WINDOWS) 50 | #include 51 | #include 52 | #endif 53 | 54 | #include 55 | 56 | #include "lqtutils_ui.h" 57 | #include "lqtutils_qsl.h" 58 | #include "lqtutils_misc.h" 59 | 60 | namespace lqt { 61 | 62 | #ifdef Q_OS_ANDROID 63 | inline QJniObject get_activity() 64 | { 65 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 66 | return QNativeInterface::QAndroidApplication::context(); 67 | #else 68 | return QtAndroid::androidActivity(); 69 | #endif 70 | } 71 | 72 | inline QJniObject get_window() 73 | { 74 | const QJniObject activity = get_activity(); 75 | if (!activity.isValid()) 76 | return QJniObject(); 77 | 78 | QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); 79 | if (!window.isValid()) { 80 | qWarning() << "Cannot get window"; 81 | return QJniObject(); 82 | } 83 | 84 | return window; 85 | } 86 | 87 | inline void run_activity_thread(std::function f) 88 | { 89 | #ifdef Q_OS_ANDROID 90 | #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 91 | QNativeInterface::QAndroidApplication::runOnAndroidMainThread(f).waitForFinished(); 92 | #else 93 | QtAndroid::runOnAndroidThreadSync(f); 94 | #endif 95 | #endif 96 | } 97 | #endif 98 | 99 | #ifndef Q_OS_IOS 100 | ScreenLock::ScreenLock() : 101 | m_isValid(false) 102 | { 103 | #ifdef Q_OS_ANDROID 104 | run_activity_thread([&] { 105 | lockScreen(true); 106 | }); 107 | #endif 108 | } 109 | 110 | ScreenLock::~ScreenLock() 111 | { 112 | #ifdef Q_OS_ANDROID 113 | run_activity_thread([&] { 114 | lockScreen(false); 115 | }); 116 | #endif 117 | } 118 | 119 | void ScreenLock::lockScreen(bool lock) 120 | { 121 | #ifdef Q_OS_ANDROID 122 | QJniObject window = get_window(); 123 | if (!window.isValid()) 124 | return; 125 | 126 | const int FLAG_KEEP_SCREEN_ON = 128; 127 | window.callMethod(lock ? "addFlags" : "clearFlags", "(I)V", FLAG_KEEP_SCREEN_ON); 128 | #else 129 | Q_UNUSED(lock); 130 | #endif 131 | } 132 | #endif // Q_IOS_OS 133 | 134 | FrameRateMonitor::FrameRateMonitor(QQuickWindow* w, QObject* parent) : 135 | FreqMeter(parent) 136 | { 137 | setWindow(w); 138 | } 139 | 140 | void FrameRateMonitor::setWindow(QQuickWindow* w) 141 | { 142 | if (!w) 143 | return; 144 | connect(w, &QQuickWindow::frameSwapped, 145 | this, &FrameRateMonitor::registerSample); 146 | } 147 | 148 | QMetaObject::Connection enableAutoFrameUpdates(QQuickWindow& w) 149 | { 150 | return QObject::connect(&w, &QQuickWindow::frameSwapped, 151 | &w, &QQuickWindow::requestUpdate); 152 | } 153 | 154 | 155 | void QmlUtils::singleShot(int msec, QJSValue callback) 156 | { 157 | QTimer::singleShot(msec, this, [callback] () mutable 158 | { 159 | if (callback.isCallable()) 160 | callback.call(); 161 | }); 162 | } 163 | 164 | #ifndef Q_OS_IOS 165 | #ifdef Q_OS_ANDROID 166 | inline QJniObject get_decor_view() 167 | { 168 | QJniObject activity = get_activity(); 169 | if (!activity.isValid()) { 170 | qWarning() << "Could not get android context"; 171 | return QJniObject(); 172 | } 173 | 174 | QJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;"); 175 | if (!window.isValid()) { 176 | qWarning() << "Cannot get window"; 177 | return QJniObject(); 178 | } 179 | 180 | return window.callObjectMethod("getDecorView", "()Landroid/view/View;"); 181 | } 182 | 183 | inline QJniObject get_cutout() 184 | { 185 | QJniObject decoView = get_decor_view(); 186 | if (!decoView.isValid()) { 187 | qWarning() << "Cannot get decorator view"; 188 | return QJniObject(); 189 | } 190 | 191 | QJniObject insets = decoView.callObjectMethod("getRootWindowInsets", "()Landroid/view/WindowInsets;"); 192 | if (!insets.isValid()) { 193 | qWarning() << "Cannot get root window insets"; 194 | return QJniObject(); 195 | } 196 | 197 | return insets.callObjectMethod("getDisplayCutout", "()Landroid/view/DisplayCutout;"); 198 | } 199 | #endif 200 | 201 | double QmlUtils::safeAreaTopInset() 202 | { 203 | #ifdef Q_OS_ANDROID 204 | QJniObject cutout = get_cutout(); 205 | if (!cutout.isValid()) 206 | return 0; 207 | 208 | return cutout.callMethod("getSafeInsetTop", "()I")/qApp->devicePixelRatio(); 209 | #else 210 | return 0; 211 | #endif 212 | } 213 | 214 | double QmlUtils::safeAreaRightInset() 215 | { 216 | #ifdef Q_OS_ANDROID 217 | QJniObject cutout = get_cutout(); 218 | if (!cutout.isValid()) 219 | return 0; 220 | 221 | return cutout.callMethod("getSafeInsetRight", "()I")/qApp->devicePixelRatio(); 222 | #else 223 | return 0; 224 | #endif 225 | } 226 | 227 | double QmlUtils::safeAreaLeftInset() 228 | { 229 | #ifdef Q_OS_ANDROID 230 | QJniObject cutout = get_cutout(); 231 | if (!cutout.isValid()) 232 | return 0; 233 | 234 | return cutout.callMethod("getSafeInsetLeft", "()I")/qApp->devicePixelRatio(); 235 | #else 236 | return 0; 237 | #endif 238 | } 239 | 240 | QRectF QmlUtils::visibleDisplayFrame() 241 | { 242 | #ifdef Q_OS_ANDROID 243 | QJniObject decoView = get_decor_view(); 244 | if (!decoView.isValid()) { 245 | qWarning() << "Could not get decor view"; 246 | return QRectF(); 247 | } 248 | 249 | QJniObject rectangle("android/graphics/Rect"); 250 | decoView.callMethod("getWindowVisibleDisplayFrame", "(Landroid/graphics/Rect;)V", rectangle.object()); 251 | 252 | const jint top = rectangle.getField("top")/qApp->devicePixelRatio(); 253 | const jint bottom = rectangle.getField("bottom")/qApp->devicePixelRatio(); 254 | const jint left = rectangle.getField("left")/qApp->devicePixelRatio(); 255 | const jint right = rectangle.getField("right")/qApp->devicePixelRatio(); 256 | 257 | return QRectF(left, top, right - left, bottom - top); 258 | #else 259 | return QRectF(); 260 | #endif 261 | } 262 | 263 | double QmlUtils::safeAreaBottomInset() 264 | { 265 | #ifdef Q_OS_ANDROID 266 | QJniObject cutout = get_cutout(); 267 | if (!cutout.isValid()) 268 | return 0; 269 | 270 | return cutout.callMethod("getSafeInsetBottom", "()I")/qApp->devicePixelRatio(); 271 | #else 272 | return 0; 273 | #endif 274 | } 275 | 276 | bool QmlUtils::shareResource(const QUrl& resUrl, const QString& mimeType, const QString& authority) 277 | { 278 | #ifdef Q_OS_ANDROID 279 | const QJniObject jniPath = QJniObject::fromString(resUrl.toLocalFile()); 280 | if (!jniPath.isValid()) { 281 | qWarning() << "Could not create jni url"; 282 | return false; 283 | } 284 | 285 | const QJniObject jniFile("java/io/File", "(Ljava/lang/String;)V", jniPath.object()); 286 | if (!jniFile.isValid()) { 287 | qWarning() << "Could not instantiate file"; 288 | return false; 289 | } 290 | 291 | const QJniObject jniUri = QJniObject::callStaticObjectMethod("androidx/core/content/FileProvider", 292 | "getUriForFile", 293 | "(Landroid/content/Context;Ljava/lang/String;Ljava/io/File;)Landroid/net/Uri;", 294 | #if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0) 295 | QNativeInterface::QAndroidApplication::context().object(), 296 | #else 297 | QNativeInterface::QAndroidApplication::context(), 298 | #endif 299 | QJniObject::fromString(authority).object(), 300 | jniFile.object()); 301 | if (!jniUri.isValid()) { 302 | qWarning() << "Failed to create FilePath uri"; 303 | return false; 304 | } 305 | 306 | const QJniObject jniParam = QJniObject::getStaticObjectField("android/content/Intent", "ACTION_SEND"); 307 | if(!jniParam.isValid()) { 308 | qWarning() << "Could not get jni intent"; 309 | return false; 310 | } 311 | 312 | QJniObject jniIntent("android/content/Intent", "(Ljava/lang/String;)V", jniParam.object()); 313 | if (!jniIntent.isValid()) { 314 | qWarning() << "Could not instantiate intent"; 315 | return false; 316 | } 317 | 318 | QJniObject jniType = QJniObject::fromString(mimeType); 319 | if (!jniType.isValid()) { 320 | qWarning() << "Could not create jni type"; 321 | return false; 322 | } 323 | 324 | QJniObject jniResult = jniIntent.callObjectMethod("setType", 325 | "(Ljava/lang/String;)Landroid/content/Intent;", 326 | jniType.object()); 327 | if (!jniResult.isValid()) { 328 | qWarning() << "Cannot set intent type"; 329 | return false; 330 | } 331 | 332 | const QJniObject jniExtraStream = QJniObject::getStaticObjectField("android/content/Intent", 333 | "EXTRA_STREAM"); 334 | if (!jniExtraStream.isValid()) { 335 | qWarning() << "Cannot read extra stream constant"; 336 | return false; 337 | } 338 | 339 | jniResult = jniIntent.callObjectMethod("putExtra", 340 | "(Ljava/lang/String;Landroid/os/Parcelable;)Landroid/content/Intent;", 341 | jniExtraStream.object(), 342 | jniUri.object()); 343 | if (!jniResult.isValid()) { 344 | qWarning() << "Cannot set extra"; 345 | return false; 346 | } 347 | 348 | jint jniGrantReadUri = QJniObject::getStaticField("android/content/Intent", "FLAG_GRANT_READ_URI_PERMISSION"); 349 | Q_ASSERT(jniGrantReadUri == 1); 350 | 351 | jint jniGrantWriteUri = QJniObject::getStaticField("android/content/Intent", "FLAG_GRANT_WRITE_URI_PERMISSION"); 352 | Q_ASSERT(jniGrantWriteUri == 2); 353 | 354 | jniResult = jniIntent.callObjectMethod("addFlags", "(I)Landroid/content/Intent;", jniGrantReadUri); 355 | if (!jniResult.isValid()) { 356 | qWarning() << "Could not add flags to intent"; 357 | return false; 358 | } 359 | 360 | jniResult = jniIntent.callObjectMethod("addFlags", "(I)Landroid/content/Intent;", jniGrantWriteUri); 361 | if (!jniResult.isValid()) { 362 | qWarning() << "Could not add flags to intent"; 363 | return false; 364 | } 365 | 366 | QJniObject activity = QNativeInterface::QAndroidApplication::context(); 367 | if (!activity.isValid()) { 368 | qWarning() << "Could not get android context"; 369 | return false; 370 | } 371 | 372 | QJniObject packageManager = activity.callObjectMethod("getPackageManager", 373 | "()Landroid/content/pm/PackageManager;"); 374 | if (!packageManager.isValid()) { 375 | qWarning() << "Could not get packageManager object"; 376 | return false; 377 | } 378 | 379 | QJniObject componentName = jniIntent.callObjectMethod("resolveActivity", 380 | "(Landroid/content/pm/PackageManager;)Landroid/content/ComponentName;", 381 | packageManager.object()); 382 | if (!componentName.isValid()) 383 | return false; 384 | 385 | QJniObject jniShareIntent = 386 | QJniObject::callStaticObjectMethod("android/content/Intent", 387 | "createChooser", 388 | "(Landroid/content/Intent;Ljava/lang/CharSequence;)Landroid/content/Intent;", 389 | jniIntent.object(), 390 | QJniObject::fromString("Share file").object()); 391 | if (!jniShareIntent.isValid()) { 392 | qWarning() << "Could not create share intent"; 393 | return false; 394 | } 395 | 396 | QtAndroidPrivate::startActivity(jniShareIntent, 1, nullptr); 397 | return true; 398 | #else 399 | return false; 400 | #endif 401 | } 402 | 403 | #endif 404 | 405 | bool QmlUtils::isMobile() 406 | { 407 | #ifdef L_OS_MOBILE 408 | return true; 409 | #else 410 | return false; 411 | #endif 412 | } 413 | 414 | bool QmlUtils::setBarColorLight(bool light, bool fullscreen) 415 | { 416 | #if defined(Q_OS_ANDROID) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 417 | run_activity_thread([&] { 418 | QJniObject decoView = get_decor_view(); 419 | if (!decoView.isValid()) { 420 | qWarning() << "Could not get decor view"; 421 | return; 422 | } 423 | 424 | if (fullscreen) { 425 | const jint layoutStable = QJniObject::getStaticField("android/view/View", "SYSTEM_UI_FLAG_LAYOUT_STABLE"); 426 | const jint hideNavigation = QJniObject::getStaticField("android/view/View", "SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION"); 427 | const jint fullscreen = QJniObject::getStaticField("android/view/View", "SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN"); 428 | const jint uiVisibility = decoView.callMethod("getSystemUiVisibility", "()I") 429 | | layoutStable 430 | | hideNavigation 431 | | fullscreen; 432 | decoView.callMethod("setSystemUiVisibility", "(I)V", uiVisibility); 433 | } 434 | 435 | if (QtAndroidPrivate::androidSdkVersion() < 26) { 436 | qWarning() << "Not supported for SDKs < 30"; 437 | return; 438 | } 439 | else if (QtAndroidPrivate::androidSdkVersion() < 30) { 440 | const jint lightStatus = QJniObject::getStaticField("android/view/View", "SYSTEM_UI_FLAG_LIGHT_STATUS_BAR"); 441 | const jint lightNav = QJniObject::getStaticField("android/view/View", "SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR"); 442 | jint uiVisibility = decoView.callMethod("getSystemUiVisibility", "()I"); 443 | if (light) { 444 | uiVisibility |= lightStatus; 445 | uiVisibility |= lightNav; 446 | } 447 | else { 448 | uiVisibility &= ~lightStatus; 449 | uiVisibility &= ~lightNav; 450 | } 451 | decoView.callMethod("setSystemUiVisibility", "(I)V", uiVisibility); 452 | } 453 | else { 454 | QJniObject insetsController = decoView.callObjectMethod("getWindowInsetsController", "()Landroid/view/WindowInsetsController;"); 455 | if (!insetsController.isValid()) { 456 | qWarning() << "Could not get WindowInsetsController"; 457 | return; 458 | } 459 | 460 | const jint lightValue = QJniObject::getStaticField("android/view/WindowInsetsController", "APPEARANCE_LIGHT_STATUS_BARS"); 461 | insetsController.callMethod("setSystemBarsAppearance", "(II)V", light ? lightValue : 0, lightValue); 462 | } 463 | }); 464 | return true; 465 | #else 466 | // Not supported. 467 | return false; 468 | #endif 469 | } 470 | 471 | bool QmlUtils::setNavBarColor(const QColor &color) 472 | { 473 | #if defined(Q_OS_ANDROID) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 474 | run_activity_thread([color] { 475 | if (QtAndroidPrivate::androidSdkVersion() < 21) { 476 | qWarning() << "Not supported for SDKs < 21"; 477 | return; 478 | } 479 | 480 | QJniObject window = get_window(); 481 | if (!window.isValid()) 482 | return; 483 | 484 | window.callMethod("setNavigationBarColor", "(I)V", color.rgba()); 485 | }); 486 | return true; 487 | #else 488 | // Not supported. 489 | return false; 490 | #endif 491 | } 492 | 493 | bool QmlUtils::setStatusBarColor(const QColor &color) 494 | { 495 | #if defined(Q_OS_ANDROID) && QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) 496 | run_activity_thread([color] { 497 | if (QtAndroidPrivate::androidSdkVersion() < 21) { 498 | qWarning() << "Not supported for SDKs < 21"; 499 | return; 500 | } 501 | 502 | QJniObject window = get_window(); 503 | if (!window.isValid()) 504 | return; 505 | 506 | window.callMethod("setStatusBarColor", "(I)V", color.rgba()); 507 | }); 508 | return true; 509 | #else 510 | // Not supported. 511 | return false; 512 | #endif 513 | } 514 | 515 | void SystemNotification::send() 516 | { 517 | #if !defined(QT_NO_DBUS) && defined(Q_OS_LINUX) 518 | QDBusInterface interface(QSL("org.freedesktop.Notifications"), 519 | QSL("/org/freedesktop/Notifications"), 520 | QSL("org.freedesktop.Notifications")); 521 | 522 | const QVariantList args { 523 | m_appName, 524 | m_replacesId, 525 | m_icon, 526 | m_title, 527 | m_message, 528 | m_actions, 529 | m_hints, 530 | m_timeout 531 | }; 532 | QDBusReply reply = interface.callWithArgumentList(QDBus::AutoDetect, QSL("Notify"), args); 533 | 534 | if (reply.isValid()) 535 | qWarning() << "Failed to send notification:" << reply.error().message(); 536 | #elif defined(Q_OS_ANDROID) 537 | qWarning() << "Please use AndroidSystemNotification"; 538 | #elif defined(Q_OS_WINDOWS) 539 | 540 | const std::wstring wstrTitle = m_title.toStdWString(); 541 | const std::wstring wstrMsg = m_message.toStdWString(); 542 | 543 | NOTIFYICONDATA notifyIconData; 544 | ZeroMemory(¬ifyIconData, sizeof(NOTIFYICONDATA)); 545 | 546 | notifyIconData.cbSize = sizeof(NOTIFYICONDATA); 547 | notifyIconData.hWnd = nullptr; 548 | notifyIconData.uID = m_replacesId; 549 | notifyIconData.uFlags = NIF_INFO; 550 | notifyIconData.dwInfoFlags = m_icon.isNull() ? NIIF_INFO : (NIIF_USER | NIIF_LARGE_ICON); 551 | notifyIconData.hBalloonIcon = m_icon.isNull() ? LoadIcon(nullptr, IDI_INFORMATION) : m_icon.toHICON(); 552 | notifyIconData.uTimeout = m_timeout; 553 | 554 | lstrcpyn(notifyIconData.szInfoTitle, wstrTitle.c_str(), sizeof(notifyIconData.szInfoTitle)/sizeof(wchar_t)); 555 | lstrcpyn(notifyIconData.szInfo, wstrMsg.c_str(), sizeof(notifyIconData.szInfo)/sizeof(wchar_t)); 556 | 557 | Shell_NotifyIcon(NIM_ADD, ¬ifyIconData); 558 | 559 | DestroyIcon(notifyIconData.hBalloonIcon); 560 | 561 | #else 562 | qWarning() << "Not implemented"; 563 | #endif 564 | } 565 | 566 | #ifdef Q_OS_ANDROID 567 | void AndroidSystemNotification::send() 568 | { 569 | const QJniObject activity = get_activity(); 570 | 571 | #define J_CLASS_NOT "Landroid/app/Notification;" 572 | #define J_CLASS_NOT_BUILDER "Landroid/app/Notification$Builder;" 573 | #define J_CLASS_BITMAP "Landroid/graphics/Bitmap;" 574 | #define J_CLASS_STRING "Ljava/lang/String;" 575 | 576 | const QJniObject channelId = QJniObject::fromString(qApp->applicationName()); 577 | const QJniObject channelName = QJniObject::fromString(QString("%1 notifier").arg(qApp->applicationName())); 578 | 579 | QJniObject notificationManager = activity.callObjectMethod("getSystemService", 580 | "(" J_CLASS_STRING ")Ljava/lang/Object;", 581 | QJniObject::fromString("notification").object()); 582 | 583 | QJniObject builder; 584 | if (QJniObject::getStaticField("android/os/Build$VERSION", "SDK_INT") >= 26) { 585 | const jint importance = QJniObject::getStaticField("android/app/NotificationManager", "IMPORTANCE_DEFAULT"); 586 | QJniObject notificationChannel("android/app/NotificationChannel", 587 | "(" J_CLASS_STRING "Ljava/lang/CharSequence;I)V", 588 | channelId.object(), channelName.object(), importance); 589 | notificationManager.callMethod("createNotificationChannel", 590 | "(Landroid/app/NotificationChannel;)V", notificationChannel.object()); 591 | builder = QJniObject("android/app/Notification$Builder", 592 | "(Landroid/content/Context;" J_CLASS_STRING ")V", activity.object(), channelId.object()); 593 | } else { 594 | builder = QJniObject("android/app/Notification$Builder", 595 | "(Landroid/content/Context;)V", activity.object()); 596 | } 597 | 598 | QJniEnvironment env; 599 | QJniObject icon; 600 | if (!m_icon.isNull()) { 601 | QBuffer iconBuffer; 602 | iconBuffer.open(QIODevice::WriteOnly); 603 | if (!m_icon.save(&iconBuffer, "png")) { 604 | qWarning() << "Failed to encode icon to png"; 605 | return; 606 | } 607 | 608 | const QByteArray iconData = iconBuffer.buffer(); 609 | 610 | QJniObject byteArrayObj = QJniObject::fromLocalRef(env->NewByteArray(iconData.size())); 611 | env->SetByteArrayRegion(byteArrayObj.object(), 612 | 0, 613 | iconData.size(), 614 | reinterpret_cast(iconData.constData())); 615 | 616 | QJniObject bitmap = QJniObject::callStaticObjectMethod("android/graphics/BitmapFactory", "decodeByteArray", 617 | "([BII)" J_CLASS_BITMAP, 618 | byteArrayObj.object(), 0, iconData.size()); 619 | icon = QJniObject::callStaticObjectMethod("android/graphics/drawable/Icon", "createWithBitmap", 620 | "(" J_CLASS_BITMAP ")Landroid/graphics/drawable/Icon;", 621 | bitmap.object()); 622 | } 623 | 624 | const jint flags = QJniObject::getStaticField("android/content/Intent", "FLAG_ACTIVITY_NEW_TASK") | 625 | QJniObject::getStaticField("android/content/Intent", "FLAG_ACTIVITY_CLEAR_TASK"); 626 | const QJniObject openIntent("android/content/Intent", 627 | "(Landroid/content/Context;Ljava/lang/Class;)V", 628 | activity.object(), 629 | env.findClass(m_activityClass.replace('.', '/').toLocal8Bit())); 630 | 631 | QJniObject pendingIntent; 632 | if (openIntent.isValid()) { 633 | openIntent.callObjectMethod("setFlags", "(I)Landroid/content/Intent;", flags); 634 | pendingIntent = QJniObject::callStaticObjectMethod("android/app/PendingIntent", 635 | "getActivity", 636 | "(Landroid/content/Context;ILandroid/content/Intent;I)Landroid/app/PendingIntent;", 637 | activity.object(), 638 | 0, 639 | openIntent.object(), 640 | QJniObject::getStaticField("android/app/PendingIntent", "FLAG_IMMUTABLE")); 641 | } 642 | 643 | if (icon.isValid()) 644 | builder.callObjectMethod("setSmallIcon", 645 | "(Landroid/graphics/drawable/Icon;)" J_CLASS_NOT_BUILDER, 646 | icon.object()); 647 | if (pendingIntent.isValid() && m_openApp) 648 | builder.callObjectMethod("setContentIntent", 649 | "(Landroid/app/PendingIntent;)" J_CLASS_NOT_BUILDER, 650 | pendingIntent.object()); 651 | builder.callObjectMethod("setContentTitle", 652 | "(Ljava/lang/CharSequence;)" J_CLASS_NOT_BUILDER, 653 | QJniObject::fromString(m_title).object()); 654 | builder.callObjectMethod("setContentText", 655 | "(Ljava/lang/CharSequence;)" J_CLASS_NOT_BUILDER, 656 | QJniObject::fromString(m_message).object()); 657 | builder.callObjectMethod("setAutoCancel", "(Z)" J_CLASS_NOT_BUILDER, false); 658 | 659 | QJniObject notification = builder.callObjectMethod("build", "()" J_CLASS_NOT); 660 | 661 | const jint notificationId = 0; 662 | notificationManager.callMethod("notify", "(I" J_CLASS_NOT ")V", notificationId, notification.object()); 663 | } 664 | #endif // Q_OS_ANDROID 665 | 666 | } // namespace 667 | -------------------------------------------------------------------------------- /lqtutils_ui.h: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2021 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #ifndef LQTUTILS_UI_H 26 | #define LQTUTILS_UI_H 27 | 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | 36 | #include "lqtutils_freq.h" 37 | #include "lqtutils_prop.h" 38 | 39 | QT_FORWARD_DECLARE_CLASS(QQuickWindow); 40 | 41 | namespace lqt { 42 | 43 | class FrameRateMonitor : public FreqMeter 44 | { 45 | Q_OBJECT 46 | public: 47 | FrameRateMonitor(QQuickWindow* w = nullptr, QObject* parent = nullptr); 48 | Q_INVOKABLE void setWindow(QQuickWindow* w); 49 | }; 50 | 51 | QMetaObject::Connection enableAutoFrameUpdates(QQuickWindow& w); 52 | 53 | class ScreenLock 54 | { 55 | public: 56 | ScreenLock(); 57 | ~ScreenLock(); 58 | bool isValid() { return m_isValid; } 59 | 60 | private: 61 | void lockScreen(bool lock); 62 | 63 | private: 64 | bool m_isValid; 65 | }; 66 | 67 | class QmlUtils : public QObject 68 | { 69 | Q_OBJECT 70 | public: 71 | QmlUtils(QObject* parent = nullptr) : QObject(parent) {} 72 | 73 | Q_INVOKABLE void singleShot(int msec, QJSValue callback); 74 | Q_INVOKABLE static double safeAreaBottomInset(); 75 | Q_INVOKABLE static double safeAreaTopInset(); 76 | Q_INVOKABLE static double safeAreaRightInset(); 77 | Q_INVOKABLE static double safeAreaLeftInset(); 78 | Q_INVOKABLE static QRectF visibleDisplayFrame(); 79 | Q_INVOKABLE static bool shareResource(const QUrl& resUrl, 80 | const QString& mimeType, 81 | const QString& authority); 82 | Q_INVOKABLE static bool isMobile(); 83 | Q_INVOKABLE static bool setBarColorLight(bool light, bool fullscreen); 84 | Q_INVOKABLE static bool setNavBarColor(const QColor& color); 85 | Q_INVOKABLE static bool setStatusBarColor(const QColor& color); 86 | }; 87 | 88 | /** 89 | * @brief The SystemNotification class 90 | * 91 | * NOTE: icon must be 16x16 or 32x32 RGB32 on Windows. 92 | * NOTE: timeout is valid on Windows only for 2000 and XP. 93 | */ 94 | class SystemNotification : public QObject 95 | { 96 | Q_OBJECT 97 | L_RW_PROP_AS(QString, appName) 98 | L_RW_PROP_AS(quint32, replacesId, 0) 99 | L_RW_PROP_AS(QImage, icon) 100 | L_RW_PROP_AS(QString, title) 101 | L_RW_PROP_AS(QString, message) 102 | L_RW_PROP_AS(QStringList, actions) 103 | L_RW_PROP_AS(QVariantMap, hints) 104 | L_RW_PROP_AS(qint32, timeout, -1) 105 | L_RW_PROP_AS(bool, openApp, false) 106 | public: 107 | SystemNotification(QObject* parent = nullptr) : QObject(parent) {} 108 | 109 | virtual void send(); 110 | 111 | private: 112 | friend class AndroidSystemNotification; 113 | friend class AppleSystemNotification; 114 | }; 115 | 116 | #ifdef Q_OS_ANDROID 117 | class AndroidSystemNotification : public SystemNotification 118 | { 119 | Q_OBJECT 120 | L_RW_PROP_AS(QString, activityClass) 121 | public: 122 | AndroidSystemNotification(QObject* parent = nullptr) : SystemNotification(parent) {} 123 | 124 | virtual void send() override; 125 | }; 126 | #endif 127 | 128 | #ifdef Q_OS_DARWIN 129 | class AppleSystemNotification : public SystemNotification 130 | { 131 | Q_OBJECT 132 | public: 133 | AppleSystemNotification(QObject* parent = nullptr) : SystemNotification(parent) {} 134 | 135 | virtual void send() override; 136 | 137 | static void requestPermission(std::function callback); 138 | }; 139 | #endif 140 | 141 | } // namespace 142 | 143 | #endif // LQTUTILS_UI_H 144 | -------------------------------------------------------------------------------- /lqtutils_ui.mm: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include "lqtutils_ui.h" 6 | 7 | namespace lqt { 8 | 9 | ScreenLock::ScreenLock() : 10 | m_isValid(false) 11 | { 12 | [[UIApplication sharedApplication] setIdleTimerDisabled: YES]; 13 | } 14 | 15 | ScreenLock::~ScreenLock() 16 | { 17 | [[UIApplication sharedApplication] setIdleTimerDisabled: NO]; 18 | } 19 | 20 | double QmlUtils::safeAreaBottomInset() 21 | { 22 | if (@available(iOS 11.0, *)) { 23 | UIWindow *window = UIApplication.sharedApplication.windows.firstObject; 24 | return window.safeAreaInsets.bottom; 25 | } 26 | 27 | return 0; 28 | } 29 | 30 | double QmlUtils::safeAreaTopInset() 31 | { 32 | if (@available(iOS 11.0, *)) { 33 | UIWindow *window = UIApplication.sharedApplication.windows.firstObject; 34 | return window.safeAreaInsets.top; 35 | } 36 | 37 | return 0; 38 | } 39 | 40 | double QmlUtils::safeAreaLeftInset() 41 | { 42 | if (@available(iOS 11.0, *)) { 43 | UIWindow *window = UIApplication.sharedApplication.windows.firstObject; 44 | return window.safeAreaInsets.left; 45 | } 46 | 47 | return 0; 48 | } 49 | 50 | double QmlUtils::safeAreaRightInset() 51 | { 52 | if (@available(iOS 11.0, *)) { 53 | UIWindow *window = UIApplication.sharedApplication.windows.firstObject; 54 | return window.safeAreaInsets.right; 55 | } 56 | 57 | return 0; 58 | } 59 | 60 | bool QmlUtils::shareResource(const QUrl& resUrl, const QString& /* mimeType */, const QString& /* authority */) 61 | { 62 | NSString* nsFilePath = resUrl.toLocalFile().toNSString(); 63 | NSURL* url = [NSURL fileURLWithPath:nsFilePath]; 64 | if (!url) { 65 | qWarning() << "Invalid file name:" << resUrl.toLocalFile(); 66 | return false; 67 | } 68 | 69 | NSArray* dataToShare = @[url]; 70 | 71 | UIActivityViewController* activityViewController = [[UIActivityViewController alloc] initWithActivityItems:dataToShare applicationActivities:nil]; 72 | [activityViewController autorelease]; 73 | 74 | UIView* view = [[[[UIApplication sharedApplication] keyWindow] rootViewController] view]; 75 | activityViewController.popoverPresentationController.sourceView = view; 76 | 77 | [[[[UIApplication sharedApplication] keyWindow] rootViewController] presentViewController:activityViewController animated:YES completion:nil]; 78 | 79 | return true; 80 | } 81 | 82 | QRectF QmlUtils::visibleDisplayFrame() 83 | { 84 | qWarning() << "Not implemented"; 85 | return QRectF(); 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /lqtutils_ui_apple.mm: -------------------------------------------------------------------------------- 1 | /** 2 | * MIT License 3 | * 4 | * Copyright (c) 2023 Luca Carlon 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in all 14 | * copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | * SOFTWARE. 23 | **/ 24 | 25 | #include "lqtutils_ui.h" 26 | 27 | #import 28 | #import 29 | 30 | @interface NotificationDelegate : NSObject 31 | @end 32 | 33 | @implementation NotificationDelegate 34 | 35 | -(void)userNotificationCenter:(UNUserNotificationCenter*)center 36 | willPresentNotification:(UNNotification*)notification 37 | withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler 38 | { 39 | Q_UNUSED(center) 40 | completionHandler(UNNotificationPresentationOptionAlert); 41 | } 42 | 43 | -(void)userNotificationCenter:(UNUserNotificationCenter*)center 44 | didReceiveNotificationResponse:(UNNotificationResponse*)response 45 | withCompletionHandler:(void(^)())completionHandler 46 | { 47 | Q_UNUSED(center) 48 | Q_UNUSED(response) 49 | completionHandler(); 50 | } 51 | 52 | @end 53 | 54 | namespace lqt { 55 | 56 | void AppleSystemNotification::send() 57 | { 58 | UNMutableNotificationContent* content = [[[UNMutableNotificationContent alloc] init] autorelease]; 59 | content.title = m_title.toNSString(); 60 | content.body = m_message.toNSString(); 61 | 62 | UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1 repeats:NO]; 63 | UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"LocalNotification" content:content trigger:trigger]; 64 | 65 | UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; 66 | if (!center.delegate) 67 | center.delegate = [[NotificationDelegate alloc] init]; 68 | [center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) { 69 | if (error) { 70 | qDebug() << "Error adding notification request:" << QString::fromNSString(error.localizedDescription); 71 | } 72 | }]; 73 | } 74 | 75 | void AppleSystemNotification::requestPermission(std::function callback) 76 | { 77 | UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; 78 | [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionAlert | UNAuthorizationOptionSound) 79 | completionHandler:^(BOOL granted, NSError * _Nullable error) { 80 | if (error) { 81 | qWarning() << "Error occurred requesting authorization:" 82 | << QString::fromNSString([error localizedDescription]) 83 | << QString::fromNSString([error localizedFailureReason]); 84 | callback(false); 85 | return; 86 | } 87 | 88 | callback(true); 89 | }]; 90 | } 91 | 92 | } // namespace 93 | -------------------------------------------------------------------------------- /qt5/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # MIT License 3 | # 4 | # Copyright (c) 2020 Luca Carlon 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | 25 | cmake_minimum_required(VERSION 3.5) 26 | 27 | project(LQtUtilsTest LANGUAGES CXX) 28 | 29 | find_package(Qt5 COMPONENTS Core Test Gui Qml Quick DBus REQUIRED) 30 | 31 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 32 | 33 | set(CMAKE_AUTOUIC ON) 34 | set(CMAKE_AUTOMOC ON) 35 | set(CMAKE_AUTORCC ON) 36 | 37 | set(CMAKE_CXX_STANDARD 17) 38 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 39 | enable_testing() 40 | 41 | include_directories(../) 42 | include(${CMAKE_CURRENT_SOURCE_DIR}/../CMakeLists_qt5.txt) 43 | 44 | add_executable(LQtUtilsTest ../lqtutils/tst_lqtutils.cpp) 45 | add_test(NAME LQtUtilsTest COMMAND LQtUtilsTest) 46 | target_link_libraries(LQtUtilsTest PRIVATE Qt5::Test Qt5::Gui Qt5::Qml Qt5::Quick Qt5::DBus lqtutils) 47 | -------------------------------------------------------------------------------- /qt6/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # MIT License 3 | # 4 | # Copyright (c) 2021 Luca Carlon 5 | # 6 | # Permission is hereby granted, free of charge, to any person obtaining a copy 7 | # of this software and associated documentation files (the "Software"), to deal 8 | # in the Software without restriction, including without limitation the rights 9 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | # copies of the Software, and to permit persons to whom the Software is 11 | # furnished to do so, subject to the following conditions: 12 | # 13 | # The above copyright notice and this permission notice shall be included in all 14 | # copies or substantial portions of the Software. 15 | # 16 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | # SOFTWARE. 23 | # 24 | 25 | cmake_minimum_required(VERSION 3.5) 26 | 27 | project(LQtUtilsTest LANGUAGES CXX) 28 | 29 | find_package(Qt6 COMPONENTS Core Test Gui Qml Quick REQUIRED) 30 | find_package(Qt6 OPTIONAL_COMPONENTS DBus) 31 | 32 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 33 | 34 | set(CMAKE_AUTOUIC ON) 35 | set(CMAKE_AUTOMOC ON) 36 | set(CMAKE_AUTORCC ON) 37 | 38 | set(CMAKE_CXX_STANDARD 17) 39 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 40 | enable_testing() 41 | 42 | set(ENABLE_FONT_AWESOME true) 43 | include_directories(../) 44 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/.. subproject/lqtutils) 45 | 46 | add_executable(LQtUtilsTest ../lqtutils/tst_lqtutils.cpp) 47 | add_test(NAME LQtUtilsTest COMMAND LQtUtilsTest) 48 | target_link_libraries(LQtUtilsTest PRIVATE Qt6::Test Qt6::Gui Qt6::Qml Qt6::Quick lqtutilsplugin) 49 | if (Qt6DBus_FOUND) 50 | target_link_libraries(LQtUtilsTest PRIVATE Qt6::DBus) 51 | endif() 52 | 53 | if(MSVC) 54 | target_compile_options(LQtUtilsTest PRIVATE /W4 /WX) 55 | else() 56 | target_compile_options(LQtUtilsTest PRIVATE -Wall -Wextra -Wpedantic) 57 | endif() 58 | --------------------------------------------------------------------------------