├── doc ├── .gitignore ├── logo_64.png ├── logo_96.png ├── Doxyfile.in ├── customdoxygen.css ├── CMakeLists.txt ├── qttagsfix.py ├── qt-tagfiles.py ├── DoxygenLayout.xml └── Doxyfile ├── src ├── widgets │ ├── TimerEdit │ ├── TreeComboBox │ ├── ExportableTableView │ ├── OverlayStackLayout │ ├── ScrollableMessageBox │ ├── widgets.pro │ ├── maxLibQtWidgets.pri │ ├── CMakeLists.txt │ ├── ActionPushButton.h │ ├── ActionPushButton.cpp │ ├── TimerEdit.h │ ├── ScrollableMessageBox.h │ ├── ScrollableMessageBox.cpp │ ├── BuddyLabel.h │ ├── OverlayStackLayout.cpp │ ├── ExportableTableView.h │ ├── RoundedMessageBox.h │ ├── TimerEdit.cpp │ ├── OverlayStackLayout.h │ ├── ExportableTableView.cpp │ └── CollapsingToolBar.h ├── core │ ├── AppDebugMessageHandler │ ├── maxLibQtCore.pri │ ├── core.pro │ ├── CMakeLists.txt │ ├── AppDebugMessageHandler.cpp │ └── AppDebugMessageHandler.h ├── itemmodels │ ├── GroupedItemsProxyModel │ ├── maxLibQtItemModels.pri │ ├── itemmodels.pro │ ├── CMakeLists.txt │ └── GroupedItemsProxyModel.h ├── quick │ ├── quick.pro │ └── maxLibQt │ │ ├── maxLibQt.dox │ │ └── controls │ │ ├── qmldir │ │ ├── controls.pri │ │ ├── controls.pro │ │ ├── MLHexSpinBox.qml │ │ └── tests.qml ├── src.pro └── CMakeLists.txt ├── maxLibQt.pro ├── examples └── imageviewer │ ├── doc │ ├── imageviewer-grid_alt.jpg │ ├── imageviewer-image_alt.jpg │ ├── imageviewer-grid_loaded.jpg │ ├── imageviewer-image_info.jpg │ ├── imageviewer-grid_infobox.jpg │ └── imageviewer-image_tbleft.jpg │ ├── imageviewer.qrc │ ├── CMakeLists.txt │ ├── README.md │ ├── main.cpp │ ├── GraphicsImageView.h │ ├── GraphicsImageView.cpp │ ├── ImageGrid.h │ ├── imageviewer.css │ ├── ImageViewer.h │ └── ImageGrid.cpp ├── .gitignore ├── maxLibQt.pri ├── CMakeLists.txt ├── LICENSE.txt └── README.md /doc/.gitignore: -------------------------------------------------------------------------------- 1 | html*/ 2 | -------------------------------------------------------------------------------- /src/widgets/TimerEdit: -------------------------------------------------------------------------------- 1 | #include "TimerEdit.h" 2 | -------------------------------------------------------------------------------- /src/widgets/TreeComboBox: -------------------------------------------------------------------------------- 1 | #include "TreeComboBox.h" 2 | -------------------------------------------------------------------------------- /src/widgets/ExportableTableView: -------------------------------------------------------------------------------- 1 | #include "ExportableTableView.h" 2 | -------------------------------------------------------------------------------- /src/widgets/OverlayStackLayout: -------------------------------------------------------------------------------- 1 | #include "OverlayStackLayout.h" 2 | -------------------------------------------------------------------------------- /src/core/AppDebugMessageHandler: -------------------------------------------------------------------------------- 1 | #include "AppDebugMessageHandler.h" 2 | -------------------------------------------------------------------------------- /src/widgets/ScrollableMessageBox: -------------------------------------------------------------------------------- 1 | #include "ScrollableMessageBox.h" 2 | -------------------------------------------------------------------------------- /src/itemmodels/GroupedItemsProxyModel: -------------------------------------------------------------------------------- 1 | #include "GroupedItemsProxyModel.h" 2 | -------------------------------------------------------------------------------- /doc/logo_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpaperno/maxLibQt/HEAD/doc/logo_64.png -------------------------------------------------------------------------------- /doc/logo_96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpaperno/maxLibQt/HEAD/doc/logo_96.png -------------------------------------------------------------------------------- /src/quick/quick.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += maxLibQt/controls 4 | -------------------------------------------------------------------------------- /maxLibQt.pro: -------------------------------------------------------------------------------- 1 | TARGET = maxLibQt 2 | TEMPLATE = subdirs 3 | 4 | SUBDIRS += src 5 | 6 | include(maxLibQt.pri) 7 | -------------------------------------------------------------------------------- /src/quick/maxLibQt/maxLibQt.dox: -------------------------------------------------------------------------------- 1 | /*! 2 | \namespace maxLibQt 3 | \brief QML controls and components. 4 | */ 5 | -------------------------------------------------------------------------------- /src/src.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += core 4 | SUBDIRS += itemmodels 5 | SUBDIRS += quick 6 | SUBDIRS += widgets 7 | -------------------------------------------------------------------------------- /examples/imageviewer/doc/imageviewer-grid_alt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpaperno/maxLibQt/HEAD/examples/imageviewer/doc/imageviewer-grid_alt.jpg -------------------------------------------------------------------------------- /examples/imageviewer/doc/imageviewer-image_alt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpaperno/maxLibQt/HEAD/examples/imageviewer/doc/imageviewer-image_alt.jpg -------------------------------------------------------------------------------- /examples/imageviewer/imageviewer.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | imageviewer.css 4 | 5 | 6 | -------------------------------------------------------------------------------- /examples/imageviewer/doc/imageviewer-grid_loaded.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpaperno/maxLibQt/HEAD/examples/imageviewer/doc/imageviewer-grid_loaded.jpg -------------------------------------------------------------------------------- /examples/imageviewer/doc/imageviewer-image_info.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpaperno/maxLibQt/HEAD/examples/imageviewer/doc/imageviewer-image_info.jpg -------------------------------------------------------------------------------- /examples/imageviewer/doc/imageviewer-grid_infobox.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpaperno/maxLibQt/HEAD/examples/imageviewer/doc/imageviewer-grid_infobox.jpg -------------------------------------------------------------------------------- /examples/imageviewer/doc/imageviewer-image_tbleft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mpaperno/maxLibQt/HEAD/examples/imageviewer/doc/imageviewer-image_tbleft.jpg -------------------------------------------------------------------------------- /src/quick/maxLibQt/controls/qmldir: -------------------------------------------------------------------------------- 1 | module maxLibQt.controls 2 | 3 | MLDoubleSpinBox 1.0 MLDoubleSpinBox.qml 4 | MLHexSpinBox 1.0 MLHexSpinBox.qml 5 | -------------------------------------------------------------------------------- /src/quick/maxLibQt/controls/controls.pri: -------------------------------------------------------------------------------- 1 | 2 | QML_SRC += $$PWD/MLDoubleSpinBox.qml 3 | QML_SRC += $$PWD/MLHexSpinBox.qml 4 | 5 | !qtquickcompiler: QML_FILES += $$QML_SRC 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.user 2 | *.vcproj 3 | *.vcxproj* 4 | *.sln 5 | .*project 6 | .settings 7 | .idea 8 | build*/ 9 | cmake-build-*/ 10 | *.qmlc 11 | docs/ 12 | *.tmp 13 | *.bak 14 | -------------------------------------------------------------------------------- /src/itemmodels/maxLibQtItemModels.pri: -------------------------------------------------------------------------------- 1 | 2 | INCLUDEPATH += $$PWD 3 | 4 | HEADERS += \ 5 | $$PWD/GroupedItemsProxyModel.h 6 | 7 | SOURCES += \ 8 | $$PWD/GroupedItemsProxyModel.cpp 9 | -------------------------------------------------------------------------------- /maxLibQt.pri: -------------------------------------------------------------------------------- 1 | 2 | DISTFILES += \ 3 | $$PWD/LICENSE.txt \ 4 | $$PWD/LICENSE.GPL.txt \ 5 | $$PWD/README.md 6 | 7 | OTHER_FILES += \ 8 | $$DISTFILES \ 9 | $$PWD/CMakeLists.txt 10 | -------------------------------------------------------------------------------- /src/core/maxLibQtCore.pri: -------------------------------------------------------------------------------- 1 | ## maxLibQtCore 2 | 3 | INCLUDEPATH += $$PWD 4 | 5 | HEADERS += \ 6 | $$PWD/AppDebugMessageHandler.h 7 | 8 | SOURCES += \ 9 | $$PWD/AppDebugMessageHandler.cpp 10 | -------------------------------------------------------------------------------- /src/core/core.pro: -------------------------------------------------------------------------------- 1 | TARGET = maxLibQtCore 2 | TEMPLATE = lib 3 | 4 | QT += core 5 | 6 | DESTDIR = $${OUT_PWD}/bin 7 | DEFINES += QT_USE_QSTRINGBUILDER 8 | 9 | include(maxLibQtCore.pri) 10 | 11 | OTHER_FILES += $$PWD/CMakeLists.txt 12 | -------------------------------------------------------------------------------- /src/quick/maxLibQt/controls/controls.pro: -------------------------------------------------------------------------------- 1 | TARGET = maxLibQtQuickControls 2 | TEMPLATE = aux 3 | 4 | include(controls.pri) 5 | 6 | QT += quick qml 7 | 8 | OTHER_FILES += qmldir 9 | OTHER_FILES += $$QML_SRC 10 | OTHER_FILES += tests.qml 11 | -------------------------------------------------------------------------------- /src/widgets/widgets.pro: -------------------------------------------------------------------------------- 1 | TARGET = maxLibQtWidgets 2 | TEMPLATE = lib 3 | 4 | QT += core widgets 5 | 6 | DESTDIR = $${OUT_PWD}/bin 7 | DEFINES += QT_USE_QSTRINGBUILDER 8 | 9 | include(maxLibQtWidgets.pri) 10 | 11 | OTHER_FILES += $$PWD/CMakeLists.txt 12 | -------------------------------------------------------------------------------- /src/itemmodels/itemmodels.pro: -------------------------------------------------------------------------------- 1 | TARGET = maxLibQtItemModels 2 | TEMPLATE = lib 3 | 4 | QT += core 5 | 6 | DESTDIR = $${OUT_PWD}/bin 7 | DEFINES += QT_USE_QSTRINGBUILDER 8 | 9 | include(maxLibQtItemModels.pri) 10 | 11 | OTHER_FILES += $$PWD/CMakeLists.txt 12 | -------------------------------------------------------------------------------- /doc/Doxyfile.in: -------------------------------------------------------------------------------- 1 | # Doxyfile 1.8.13 2 | 3 | @INCLUDE = @DOC_DIR@/Doxyfile 4 | 5 | OUTPUT_DIRECTORY = @DOC_OUTPUT_DIR@ 6 | 7 | QUIET = YES 8 | WARNINGS = NO 9 | WARN_IF_UNDOCUMENTED = NO 10 | WARN_IF_DOC_ERROR = NO 11 | HTML_TIMESTAMP = NO 12 | -------------------------------------------------------------------------------- /src/widgets/maxLibQtWidgets.pri: -------------------------------------------------------------------------------- 1 | 2 | QT += widgets 3 | 4 | INCLUDEPATH += $$PWD 5 | 6 | HEADERS += \ 7 | $$PWD/ActionPushButton.h \ 8 | $$PWD/BuddyLabel.h \ 9 | $$PWD/CollapsingToolBar.h \ 10 | $$PWD/ExportableTableView.h \ 11 | $$PWD/OverlayStackLayout.h \ 12 | $$PWD/RoundedMessageBox.h \ 13 | $$PWD/ScrollableMessageBox.h \ 14 | $$PWD/TimerEdit.h \ 15 | $$PWD/TreeComboBox.h 16 | 17 | SOURCES += \ 18 | $$PWD/ActionPushButton.cpp \ 19 | $$PWD/ExportableTableView.cpp \ 20 | $$PWD/OverlayStackLayout.cpp \ 21 | $$PWD/ScrollableMessageBox.cpp \ 22 | $$PWD/TimerEdit.cpp \ 23 | $$PWD/TreeComboBox.cpp 24 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.12) 2 | project(maxLibQt) 3 | 4 | set(SRC_SUBDIRS 5 | core 6 | itemmodels 7 | widgets 8 | ) 9 | 10 | set(BUILT_LIBRARIES) # populated by subprojects 11 | 12 | foreach(sub_dir ${SRC_SUBDIRS}) 13 | add_subdirectory(${sub_dir}) 14 | endforeach(sub_dir) 15 | 16 | # maxLibQt is a "dummy" lib so that other binaries could link 17 | # to this single one instead of the individual libs. 18 | # The maxLibQt target cannot be built specifically. 19 | if (BUILT_LIBRARIES) 20 | add_library(${PROJECT_NAME} INTERFACE) 21 | target_link_libraries(${PROJECT_NAME} INTERFACE ${BUILT_LIBRARIES}) 22 | endif() 23 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.12) 2 | project(maxLibQt) 3 | 4 | set(CMAKE_COLOR_MAKEFILE ON) 5 | 6 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 7 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/lib) 8 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/bin) 9 | 10 | set(SRC_DIR "${PROJECT_SOURCE_DIR}/src") 11 | set(DOC_DIR "${PROJECT_SOURCE_DIR}/doc") 12 | 13 | find_package(Qt5Core) 14 | if(Qt5Core_FOUND) 15 | message(STATUS "Qt Version: ${Qt5Core_VERSION}") 16 | else() 17 | message(FATAL_ERROR "Qt not found!") 18 | endif() 19 | 20 | ## Subprojects 21 | add_subdirectory("${SRC_DIR}") 22 | 23 | ## Doxymentation 24 | add_subdirectory("${DOC_DIR}") 25 | -------------------------------------------------------------------------------- /doc/customdoxygen.css: -------------------------------------------------------------------------------- 1 | 2 | #projectlogo img 3 | { 4 | max-height: 56px 5 | } 6 | 7 | 8 | #projectname 9 | { 10 | /*font: bold 280% Tahoma, Arial,sans-serif;*/ 11 | /*margin: 0px;*/ 12 | padding: 2px 2px 0px 10px; 13 | font-variant: small-caps; 14 | } 15 | 16 | 17 | a.el { 18 | color: #009b00; 19 | font-weight: normal; 20 | } 21 | 22 | a.el:visited { 23 | color: #007800; 24 | font-weight: normal; 25 | } 26 | 27 | .navpath li.navelem a { 28 | font-weight: bold 29 | } 30 | 31 | .memtitle { 32 | font-size: 1.3em; 33 | } 34 | 35 | div.fragment { 36 | padding: 5px; 37 | } 38 | 39 | div.line { 40 | padding: 2px 0; 41 | text-indent: 0; 42 | } 43 | 44 | 45 | h2.memtitle { display: none; } 46 | div.memproto { border-top-left-radius: 4px; } 47 | table.memname tr { display: inline-table; } 48 | table.memname td { padding: 1px 0; } 49 | td.memname { font-size: larger; } 50 | -------------------------------------------------------------------------------- /src/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.12) 2 | cmake_policy(SET CMP0020 NEW) 3 | set(CMAKE_CXX_STANDARD 11) 4 | if (CMAKE_VERSION VERSION_LESS "3.1" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 5 | set (CMAKE_CXX_FLAGS "--std=gnu++11 ${CMAKE_CXX_FLAGS}") 6 | endif () 7 | 8 | find_package(Qt5Core) 9 | 10 | project(maxLibQtCore) 11 | 12 | set(SRCS 13 | "${CMAKE_CURRENT_LIST_DIR}/AppDebugMessageHandler.cpp" 14 | ) 15 | 16 | set(HDRS 17 | "${CMAKE_CURRENT_LIST_DIR}/AppDebugMessageHandler.h" 18 | ) 19 | 20 | qt5_wrap_cpp(SRCS ${HDRS}) 21 | 22 | add_library(${PROJECT_NAME} ${SRCS} ${HDRS}) 23 | target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core) 24 | target_compile_definitions(${PROJECT_NAME} PRIVATE QT_USE_QSTRINGBUILDER) 25 | target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_LIST_DIR}") 26 | 27 | # need to push this upstream 28 | set(BUILT_LIBRARIES ${BUILT_LIBRARIES} ${PROJECT_NAME} PARENT_SCOPE) 29 | -------------------------------------------------------------------------------- /src/itemmodels/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.12) 2 | cmake_policy(SET CMP0020 NEW) 3 | set(CMAKE_CXX_STANDARD 11) 4 | if (CMAKE_VERSION VERSION_LESS "3.1" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 5 | set (CMAKE_CXX_FLAGS "--std=gnu++11 ${CMAKE_CXX_FLAGS}") 6 | endif () 7 | 8 | find_package(Qt5Core) 9 | 10 | project(maxLibQtItemModels) 11 | 12 | set(SRCS 13 | "${CMAKE_CURRENT_LIST_DIR}/GroupedItemsProxyModel.cpp" 14 | ) 15 | 16 | set(HDRS 17 | "${CMAKE_CURRENT_LIST_DIR}/GroupedItemsProxyModel.h" 18 | ) 19 | 20 | qt5_wrap_cpp(SRCS ${HDRS}) 21 | 22 | add_library(${PROJECT_NAME} ${SRCS} ${HDRS}) 23 | target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core) 24 | target_compile_definitions(${PROJECT_NAME} PRIVATE QT_USE_QSTRINGBUILDER) 25 | target_include_directories(${PROJECT_NAME} PUBLIC "${CMAKE_CURRENT_LIST_DIR}") 26 | 27 | # need to push this upstream 28 | set(BUILT_LIBRARIES ${BUILT_LIBRARIES} ${PROJECT_NAME} PARENT_SCOPE) 29 | -------------------------------------------------------------------------------- /doc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ## Generate Doxygen docs from source 2 | 3 | set(DOC_OUTPUT_DIR "${DOC_DIR}" CACHE STRING "Documentation output path.") 4 | 5 | find_package(Doxygen) 6 | 7 | if(DOXYGEN_FOUND AND DOC_DIR AND DOC_OUTPUT_DIR) 8 | set(DOXYGEN_IN ${DOC_DIR}/Doxyfile.in) 9 | set(DOXYGEN_OUT "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile") 10 | 11 | configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) 12 | 13 | add_custom_target(maxLibQt-doxygen 14 | COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} 15 | WORKING_DIRECTORY ${DOC_DIR} 16 | COMMENT "Generating API documentation with Doxygen." 17 | VERBATIM ) 18 | 19 | ## clean 20 | file(TO_NATIVE_PATH "html/*.html html/*.js html/*.css html/*.png" DOCFILES) 21 | if(WIN32) 22 | set(RM del /Q) 23 | separate_arguments(DOCFILES WINDOWS_COMMAND "${DOCFILES}") 24 | else() 25 | set(RM rm) 26 | separate_arguments(DOCFILES UNIX_COMMAND "${DOCFILES}") 27 | endif() 28 | add_custom_target(maxLibQt-doxygen-clean 29 | COMMAND ${RM} ${DOCFILES} 30 | COMMAND ${CMAKE_COMMAND} -E remove_directory html/search 31 | WORKING_DIRECTORY ${DOC_OUTPUT_DIR} 32 | COMMENT "Deleting stale documentation files." 33 | VERBATIM ) 34 | endif() 35 | -------------------------------------------------------------------------------- /examples/imageviewer/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.0) 2 | 3 | project(ImageViewer) 4 | 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | set(CMAKE_AUTOMOC ON) 7 | set(CMAKE_AUTOUIC ON) 8 | set(CMAKE_AUTORCC ON) 9 | 10 | find_package(Qt5Core) 11 | find_package(Qt5Gui) 12 | find_package(Qt5Widgets) 13 | find_package(Qt5Svg) 14 | 15 | set(PWD "${CMAKE_CURRENT_LIST_DIR}") 16 | set(SRC_PATH "../../src") 17 | 18 | include_directories(${SRC_PATH}/widgets) 19 | #add_subdirectory(${MLQT_PATH}/widgets) 20 | 21 | set(HDRS 22 | "${PWD}/GraphicsImageView.h" 23 | "${PWD}/ImageGrid.h" 24 | "${PWD}/ImageViewer.h" 25 | "${SRC_PATH}/widgets/OverlayStackLayout.h" 26 | ) 27 | 28 | set(SRCS 29 | "${PWD}/main.cpp" 30 | "${PWD}/GraphicsImageView.cpp" 31 | "${PWD}/ImageGrid.cpp" 32 | "${PWD}/ImageViewer.cpp" 33 | "${SRC_PATH}/widgets/OverlayStackLayout.cpp" 34 | ) 35 | 36 | set(RSRCS "${PWD}/imageviewer.qrc") 37 | 38 | add_executable(${PROJECT_NAME} ${HDRS} ${SRCS} ${RSRCS}) 39 | 40 | target_compile_definitions(${PROJECT_NAME} PRIVATE QT_USE_QSTRINGBUILDER) 41 | target_compile_definitions(${PROJECT_NAME} PRIVATE SOURCE_DIR="${PWD}") 42 | target_link_libraries(${PROJECT_NAME} Qt5::Core Qt5::Gui Qt5::Widgets Qt5::Svg) 43 | -------------------------------------------------------------------------------- /examples/imageviewer/README.md: -------------------------------------------------------------------------------- 1 | ## Image Viewer Example Application ## 2 | 3 | This is a simple but fully functional image viewer application with a "gallery view" page 4 | and another for viewing a full size image with adjustable scaling, rotation, and zoom factors. 5 | This example is primarily meant to demonstrate usage of the [StackOverlayLayout][1]. 6 | 7 | ![Gallery view folder loaded](doc/imageviewer-grid_loaded.jpg) 8 | ![Full image view with info overlay](doc/imageviewer-image_info.jpg) 9 | ![Gallery with alternate theme](doc/imageviewer-grid_alt.jpg) 10 | ![Full image view with alternate theme](doc/imageviewer-image_alt.jpg) 11 | ![Gallery with info overlay](doc/imageviewer-grid_infobox.jpg) 12 | ![Full image with left toolbar](doc/imageviewer-image_tbleft.jpg) 13 | 14 | [1]: https://mpaperno.github.io/maxLibQt/class_overlay_stack_layout.html#details 15 | 16 | ------------- 17 | Copyright (c)2019 Maxim Paperno. All rights reserved. 18 | This example is part of the maxLibQt project and is governed by 19 | the same licensing. 20 | 21 | This program is distributed in the hope that it will be useful, 22 | but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 24 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | /*! \page LICENSE.txt LICENSE.txt 2 | \verbatim 3 | 4 | maxLibQt C++ code library for the Qt framework. 5 | 6 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 7 | Contact: http://www.WorldDesign.com/contact 8 | 9 | LICENSE 10 | 11 | Unless specified otherwise in the individual component files, 12 | the components of this library are licensed under a dual-use system. 13 | 14 | Commercial License Usage 15 | Licensees holding valid commercial licenses may use the components in 16 | accordance with the terms contained in a written agreement between 17 | you and the copyright holder. 18 | 19 | GNU General Public License Usage 20 | Alternatively, the components may be used under the terms of the GNU 21 | General Public License as published by the Free Software Foundation, 22 | either version 3 of the License, or (at your option) any later version. 23 | 24 | This program is distributed in the hope that it will be useful, 25 | but WITHOUT ANY WARRANTY; without even the implied warranty of 26 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 27 | GNU General Public License for more details. 28 | 29 | A copy of the GNU General Public License is available in the file LICENSE.GPL.txt 30 | which should be included in this distribution. It is also available at . 31 | 32 | \endverbatim */ 33 | -------------------------------------------------------------------------------- /src/widgets/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.0) 2 | 3 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 4 | set(CMAKE_AUTOMOC ON) 5 | 6 | find_package(Qt5Core) 7 | find_package(Qt5Widgets) 8 | 9 | project(maxLibQtWidgets) 10 | 11 | set(SRCS 12 | "${CMAKE_CURRENT_LIST_DIR}/ActionPushButton.cpp" 13 | "${CMAKE_CURRENT_LIST_DIR}/ExportableTableView.cpp" 14 | "${CMAKE_CURRENT_LIST_DIR}/OverlayStackLayout.cpp" 15 | "${CMAKE_CURRENT_LIST_DIR}/ScrollableMessageBox.cpp" 16 | "${CMAKE_CURRENT_LIST_DIR}/TimerEdit.cpp" 17 | "${CMAKE_CURRENT_LIST_DIR}/TreeComboBox.cpp" 18 | ) 19 | 20 | set(HDRS 21 | "${CMAKE_CURRENT_LIST_DIR}/ActionPushButton.h" 22 | "${CMAKE_CURRENT_LIST_DIR}/BuddyLabel.h" 23 | "${CMAKE_CURRENT_LIST_DIR}/CollapsingToolBar.h" 24 | "${CMAKE_CURRENT_LIST_DIR}/ExportableTableView.h" 25 | "${CMAKE_CURRENT_LIST_DIR}/OverlayStackLayout.h" 26 | "${CMAKE_CURRENT_LIST_DIR}/RoundedMessageBox.h" 27 | "${CMAKE_CURRENT_LIST_DIR}/ScrollableMessageBox.h" 28 | "${CMAKE_CURRENT_LIST_DIR}/TimerEdit.h" 29 | "${CMAKE_CURRENT_LIST_DIR}/TreeComboBox.h" 30 | ) 31 | 32 | add_library(${PROJECT_NAME} ${SRCS} ${HDRS}) 33 | target_link_libraries(${PROJECT_NAME} PRIVATE Qt5::Core Qt5::Widgets) 34 | target_compile_definitions(${PROJECT_NAME} PRIVATE QT_USE_QSTRINGBUILDER) 35 | 36 | # need to push this upstream 37 | set(BUILT_LIBRARIES ${BUILT_LIBRARIES} ${PROJECT_NAME} PARENT_SCOPE) 38 | -------------------------------------------------------------------------------- /src/quick/maxLibQt/controls/MLHexSpinBox.qml: -------------------------------------------------------------------------------- 1 | /* 2 | MLHexSpinBox 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2018 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | import QtQuick 2.10 29 | import QtQuick.Controls 2.3 30 | 31 | /*! 32 | \brief MLHexSpinBox allows editing integers using hexadecimal notation. 33 | 34 | It uses MLDoubleSpinBox as the base class because this allows a wider range of values, including signed integers. 35 | The basic Controls 2 SpinBox is limited to signed int range only. 36 | 37 | Individual property documentation can be found inline. 38 | 39 | \sa MLDoubleSpinBox 40 | */ 41 | 42 | MLDoubleSpinBox { 43 | id: control 44 | objectName: "MLHexSpinBox" 45 | 46 | property bool upperCase: true //!< Whether to force upper-case formatting for letters. 47 | property bool zeroPad: true //!< Whether to pad numbers with leading zeros up to \p digits length. 48 | property bool showPrefix: true //!< Whether to show the "0x" prefix. 49 | property int digits: Math.abs(topValue).toString(16).length //!< Number of digits expected, used in validator, input mask, and for zero-padding. Default is based on maximum value. 50 | 51 | from: 0 52 | to: 0xFFFFFFFF 53 | decimals: 0 54 | inputMethodHints: Qt.ImhNoPredictiveText | (upperCase ? Qt.ImhPreferUppercase : 0) 55 | inputMask: (value < 0 ? "-" : "") + (showPrefix ? "\\0\\x" : "") + "H".repeat(digits) 56 | 57 | validator: RegExpValidator { 58 | regExp: new RegExp("-?(0x)?[0-9A-Fa-f]{1," + control.digits + "}") 59 | } 60 | 61 | function textFromValue(value, locale) { 62 | var ret = Math.abs(Number(value)).toString(16); 63 | if (zeroPad && digits > ret.length) 64 | ret = "0".repeat(digits - ret.length) + ret; 65 | if (upperCase) 66 | ret = ret.toUpperCase(); 67 | if (showPrefix) 68 | ret = "0x" + ret; 69 | 70 | return ret; 71 | } 72 | 73 | function valueFromText(text, locale) { 74 | return parseInt(text, 16); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /examples/imageviewer/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | Image Viewer example application 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Rights Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #include "ImageViewer.h" 29 | #include 30 | #include 31 | #include 32 | 33 | #define RESOURCES_PATH ":" // use for embedded CSS resource 34 | //#define RESOURCES_PATH SOURCE_DIR // use for local CSS file, SOURCE_DIR should be set in build script 35 | 36 | /*! 37 | \ingroup examples 38 | \defgroup examples_imageviewer Image Viewer example application 39 | Main application file for the Image Viewer example. 40 | 41 | ``` 42 | Usage: ImageViewer [options] [path] 43 | 44 | Options: 45 | -m, --maxcache Maximum size of image previews cache (default: 1000 MB). 46 | -?, -h, --help Displays this help. 47 | 48 | Arguments: 49 | path Start with this image folder or file. 50 | ``` 51 | 52 | \note The global \c QPixmapCache is used to store image previews in the \c ImageGrid class. It should be suitably "large." 53 | 54 | */ 55 | int main(int argc, char *argv[]) 56 | { 57 | QApplication::setStyle("Fusion"); // things style better with Fusion 58 | QApplication a(argc, argv); 59 | QCoreApplication::setOrganizationName("maxLibQt"); 60 | QCoreApplication::setApplicationName("maxLibQt Image Viewer Example"); 61 | 62 | const int maxCache = QPixmapCache::cacheLimit() * 100 / 1024; 63 | 64 | QCommandLineParser clp; 65 | clp.setApplicationDescription(QCoreApplication::applicationName()); 66 | clp.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); 67 | clp.addPositionalArgument(QStringLiteral("path"), QStringLiteral("Start with this image folder or file."), QStringLiteral("[path]")); 68 | clp.addOption({ {QStringLiteral("m"), QStringLiteral("maxcache")}, 69 | QStringLiteral("Maximum size of image previews cache (default: %1 MB).").arg(maxCache), 70 | QStringLiteral("MBytes"), QString::number(maxCache) }); 71 | clp.addHelpOption(); 72 | clp.process(a); 73 | 74 | QPixmapCache::setCacheLimit(clp.value(QStringLiteral("m")).toInt() * 1024); 75 | ImageViewer w(RESOURCES_PATH "/imageviewer.css", clp.positionalArguments().value(0)); 76 | return a.exec(); 77 | } 78 | -------------------------------------------------------------------------------- /examples/imageviewer/GraphicsImageView.h: -------------------------------------------------------------------------------- 1 | /* 2 | GraphicsImageView 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Rights Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef GRAPHICSIMAGEVIEW_H 29 | #define GRAPHICSIMAGEVIEW_H 30 | 31 | #include 32 | #include 33 | 34 | /*! 35 | \ingroup examples_imageviewer 36 | A custom \c QGraphicsView for displaying an image scaled to fit into the full available viewport/scene geometry. 37 | It will automatically resize the \c QGraphicsScene and rescale the contained image whenever the size of this widget 38 | changes. It also has a few extra features for "zooming" and rotating the image using \c QGraphicsItem transformations, 39 | and a setting for how the image ratio is preserved while scaling. 40 | */ 41 | class GraphicsImageView : public QGraphicsView 42 | { 43 | Q_OBJECT 44 | Q_PROPERTY(QString imageFile READ imageFile WRITE setImageFile NOTIFY imageChanged USER true) 45 | Q_PROPERTY(qreal imageScale READ imageScale WRITE setImageScale NOTIFY imageScaleChanged) 46 | Q_PROPERTY(qreal imageRotation READ imageRotation WRITE setImageRotation NOTIFY imageRotationChanged) 47 | Q_PROPERTY(Qt::AspectRatioMode imageScalingMode READ imageScalingMode WRITE setImageScalingMode NOTIFY imageScalingModeChanged) 48 | 49 | public: 50 | explicit GraphicsImageView(QWidget *p = nullptr); 51 | 52 | QString imageFile() const { return m_imageFile; } 53 | Qt::AspectRatioMode imageScalingMode() const { return m_scaleMode; } 54 | qreal imageScale() const { return m_item ? m_item->scale() : 0.0; } 55 | qreal imageRotation() const { return m_item ? m_item->rotation() : 0.0; } 56 | 57 | public slots: 58 | void setImageFile(const QString &imageFile); 59 | void setImageScalingMode(int mode); 60 | 61 | void setImageScale(qreal scale) const; 62 | void zoomImage(int steps) const { setImageScale(imageScale() + steps * 0.1); } 63 | void zoomIn() const { zoomImage(1); } 64 | void zoomOut() const { zoomImage(-1); } 65 | void zoomReset() const { setImageScale(1.0); } 66 | 67 | void setImageRotation(qreal degrees) const; 68 | void rotateCw() const { setImageRotation(imageRotation() + 90.0); } 69 | void rotateCCw() const { setImageRotation(imageRotation() - 90.0); } 70 | void rotationReset() const { setImageRotation(0); } 71 | 72 | signals: 73 | void imageChanged() const; 74 | void imageScaleChanged(qreal) const; 75 | void imageRotationChanged(qreal) const; 76 | void imageScalingModeChanged(Qt::AspectRatioMode) const; 77 | 78 | protected slots: 79 | void loadImage(const QSize &size); 80 | 81 | protected: 82 | void resizeEvent(QResizeEvent *e) override; 83 | 84 | private: 85 | bool createItem(); 86 | void removeItem(); 87 | 88 | Qt::AspectRatioMode m_scaleMode = Qt::KeepAspectRatio; 89 | QString m_imageFile; 90 | QGraphicsPixmapItem *m_item = nullptr; 91 | 92 | Q_DISABLE_COPY(GraphicsImageView) 93 | }; 94 | 95 | #endif // GRAPHICSIMAGEVIEW_H 96 | -------------------------------------------------------------------------------- /src/widgets/ActionPushButton.h: -------------------------------------------------------------------------------- 1 | /* 2 | ActionPushButton 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef ACTIONPUSHBUTTON_H 29 | #define ACTIONPUSHBUTTON_H 30 | 31 | #include 32 | 33 | class QAction; 34 | class QEvent; 35 | 36 | /*! 37 | \brief The ActionPushButton class is a \c QPushButton which takes a default \c QAction, just like a \c QToolButton can. 38 | Like \c QToolButton, it will inherit all properties from the default \c QAction, such as text, icon, checkable status & state, 39 | tool tip, and so on. The default action can be set with \c setDefaultAction() and retrieved with \c defaultAction(). 40 | The default action can also be set using the dedicated \ref ActionPushButton(QAction *, QWidget *) constructor. 41 | 42 | It also adds a \c triggered(QAction *) signal for all \c QActions added to the button (not just the default one). 43 | */ 44 | class ActionPushButton : public QPushButton 45 | { 46 | Q_OBJECT 47 | //! Current default action, if any. Value is `nullptr` if no default action has been set. 48 | Q_PROPERTY(QAction *defaultAction READ defaultAction WRITE setDefaultAction) 49 | 50 | public: 51 | //! Inherits base class constructors. 52 | using QPushButton::QPushButton; 53 | //! Construct using \a defaultAction as the default action. 54 | explicit ActionPushButton(QAction *defaultAction, QWidget *parent = nullptr); 55 | 56 | //! Current default action, if any. Returns `nullptr` if no default action has been set. 57 | inline QAction *defaultAction() const { return m_defaultAction; } 58 | 59 | public slots: 60 | //! Sets the default action to \a action. The action is added to the widget if it hasn't been already. 61 | //! To clear the default action, pass a `nullptr` as the \a action. Clearing the default action in this way 62 | //! does *not* remove it from this widget itself. Use \c QWidget::removeAction() for that instead, which will 63 | //! also clear the default action on this button (if the action being removed is the current default, of course). 64 | void setDefaultAction(QAction *action); 65 | 66 | signals: 67 | //! Signal emitted whenever any \c QAction added to this button (with \c QWidget::addAction() or \c setDefaultAction()) is triggered. 68 | void triggered(QAction *); 69 | 70 | protected: 71 | bool event(QEvent *e) override; 72 | void nextCheckState() override; 73 | 74 | private slots: 75 | void updateFromAction(QAction *action); 76 | void onActionTriggered(); 77 | 78 | private: 79 | QAction *m_defaultAction = nullptr; 80 | 81 | Q_DISABLE_COPY(ActionPushButton) 82 | 83 | #ifdef DOXYGEN_SHOULD_INCLUDE_THIS 84 | public: 85 | //! Inherited from \c QPushButton. \{ 86 | ActionPushButton(QWidget *parent = nullptr); 87 | ActionPushButton(const QString &text, QWidget *parent = nullptr); 88 | ActionPushButton(const QIcon &icon, const QString &text, QWidget *parent = nullptr); 89 | //! \} 90 | #endif 91 | }; 92 | 93 | #endif // ACTIONPUSHBUTTON_H 94 | -------------------------------------------------------------------------------- /src/widgets/ActionPushButton.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ActionPushButton 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #include "ActionPushButton.h" 29 | 30 | #include 31 | #include 32 | 33 | ActionPushButton::ActionPushButton(QAction *defaultAction, QWidget *parent) : 34 | QPushButton(parent) 35 | { 36 | setDefaultAction(defaultAction); 37 | } 38 | 39 | bool ActionPushButton::event(QEvent *e) 40 | { 41 | switch (e->type()) { 42 | case QEvent::ActionAdded: 43 | if (QActionEvent *ae = static_cast(e)) 44 | connect(ae->action(), &QAction::triggered, this, &ActionPushButton::onActionTriggered); 45 | break; 46 | 47 | case QEvent::ActionRemoved: 48 | if (QActionEvent *ae = static_cast(e)) { 49 | ae->action()->disconnect(this); 50 | if (ae->action() == m_defaultAction) 51 | setDefaultAction(nullptr); 52 | } 53 | break; 54 | 55 | case QEvent::ActionChanged: 56 | if (QActionEvent *ae = static_cast(e)) 57 | if (ae->action() == m_defaultAction) 58 | updateFromAction(m_defaultAction); 59 | break; 60 | 61 | default: 62 | break; 63 | } 64 | return QPushButton::event(e); 65 | } 66 | 67 | void ActionPushButton::nextCheckState() 68 | { 69 | if (!!m_defaultAction) 70 | m_defaultAction->trigger(); 71 | else 72 | QPushButton::nextCheckState(); 73 | } 74 | 75 | void ActionPushButton::updateFromAction(QAction *action) 76 | { 77 | if (!action) 78 | return; 79 | QString buttonText = action->iconText(); 80 | // If iconText() is generated from text(), we need to remove any '&'s so they don't turn into shortcuts 81 | if (buttonText == action->text()) 82 | buttonText.replace(QLatin1String("&"), QLatin1String("")); 83 | setText(buttonText); 84 | setIcon(action->icon()); 85 | setToolTip(action->toolTip()); 86 | setStatusTip(action->statusTip()); 87 | setWhatsThis(action->whatsThis()); 88 | setCheckable(action->isCheckable()); 89 | setChecked(action->isChecked()); 90 | setEnabled(action->isEnabled()); 91 | setVisible(action->isVisible()); 92 | setAutoRepeat(action->autoRepeat()); 93 | if (!testAttribute(Qt::WA_SetFont)) { 94 | setFont(action->font()); 95 | setAttribute(Qt::WA_SetFont, false); 96 | } 97 | } 98 | 99 | void ActionPushButton::setDefaultAction(QAction *action) 100 | { 101 | if (m_defaultAction == action) 102 | return; 103 | 104 | if (!!m_defaultAction && !!m_defaultAction->menu() && m_defaultAction->menu() == menu()) 105 | setMenu(nullptr); 106 | 107 | m_defaultAction = action; 108 | if (!action) 109 | return; 110 | 111 | if (!actions().contains(action)) 112 | addAction(action); 113 | updateFromAction(action); 114 | if (!!action->menu() && !menu()) 115 | setMenu(action->menu()); 116 | } 117 | 118 | void ActionPushButton::onActionTriggered() 119 | { 120 | if (QAction *act = qobject_cast(sender())) 121 | emit triggered(act); 122 | } 123 | -------------------------------------------------------------------------------- /src/widgets/TimerEdit.h: -------------------------------------------------------------------------------- 1 | /* 2 | TimerEdit 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef TIMEREDIT_H 29 | #define TIMEREDIT_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | /** 38 | \class TimerEdit 39 | \version 1.0.0 40 | 41 | \brief A time value line editor which accepts negative and large times (> 23:59:59), suitable for a timer, etc. 42 | 43 | Qt's \c QTimeEdit has a limitation in that it can only accept "valid" times, in the range of 0:00:00 to 23:59:59. 44 | The TimerEdit control aims to address that. It allows any amount of time to be entered, as well as negative times. 45 | It provides a mask for data entry, a validator with settable min/max times, and increment/decrement via keyboard 46 | or mouse wheel in configurable steps. 47 | 48 | Data I/O is in seconds only, that is you set the time value in total seconds and also read it back in seconds 49 | (there is no conversion from/to a \e QTime object). 50 | 51 | */ 52 | class TimerEdit : public QLineEdit 53 | { 54 | Q_OBJECT 55 | Q_PROPERTY(int minimumTime READ minimumTime WRITE setMinimumTime) 56 | Q_PROPERTY(int maximumTime READ maximumTime WRITE setMaximumTime) 57 | Q_PROPERTY(bool showSeconds READ showSeconds WRITE setShowSeconds) 58 | Q_PROPERTY(unsigned int singleStep READ singleStep WRITE setSingleStep) 59 | Q_PROPERTY(unsigned int pageStep READ pageStep WRITE setPageStep) 60 | 61 | 62 | public: 63 | enum TimeParts {Whole, Polarity, Hours, Minutes, Seconds}; 64 | 65 | TimerEdit(QWidget *parent = Q_NULLPTR); 66 | 67 | int timeInSeconds() const; 68 | int timepart(const QString &input, TimeParts part) const; 69 | 70 | bool showSeconds() const { return m_showSeconds; } 71 | int minimumTime() const { return m_minTime; } 72 | int maximumTime() const { return m_maxTime; } 73 | unsigned int singleStep() const { return m_singleStep; } 74 | unsigned int pageStep() const { return m_pageStep; } 75 | 76 | public slots: 77 | void setTime(int seconds); 78 | void incrDecr(int seconds); 79 | void setTimeRange(int minSeconds, int maxSeconds); 80 | void setMinimumTime(int minSeconds); 81 | void setMaximumTime(int maxSeconds); 82 | void setShowSeconds(bool showSeconds); 83 | 84 | void setSingleStep(unsigned int step) { m_singleStep = step; } 85 | void setPageStep(unsigned int pageStep) { m_pageStep = pageStep; } 86 | 87 | protected: 88 | void textEditedHandler(); 89 | virtual void setupFormat(); 90 | void emitValueChanged(); 91 | void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; 92 | void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; 93 | 94 | bool m_showSeconds; 95 | int m_minTime; // seconds 96 | int m_maxTime; // seconds 97 | unsigned m_singleStep; // seconds 98 | unsigned m_pageStep; // seconds 99 | short m_hourDigits; 100 | QRegularExpressionValidator * m_validator; 101 | }; 102 | 103 | #endif // TIMEREDIT_H 104 | -------------------------------------------------------------------------------- /src/widgets/ScrollableMessageBox.h: -------------------------------------------------------------------------------- 1 | /* 2 | ScrollableMessageBox 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef SCROLLABLEESSAGEBOX_H 29 | #define SCROLLABLEESSAGEBOX_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | /** 39 | \class ScrollableMessageBox 40 | \version 1.0.0 41 | 42 | \brief A simple message box with a large scrollable area (a \c QTextEdit ) for detailed text (including HTML formatting). 43 | 44 | This is a basic message box dialog for presenting an amount of text which would not comfortably fit in a regular popup message 45 | dialog. It addresses the deficiencies found in \c QMessageBox with "informative text" option, namely its small and fixed 46 | size and lack of text formatting options (no rich text or even font control). 47 | 48 | Basic usage is like \c QMessageBox. You can set the dialog title, an optional short text message to show at the top, and 49 | the larger text block to show as the "details." There is only one "OK" button to dismiss the dialog. 50 | 51 | When first shown, the dialog will try to esablish a reasonable size based on the contents of the QTextEdit. The dialog is also 52 | fully resizable. 53 | 54 | Developers have direct access to the \c QTextEdit used for displaying the text, the \c QLineEdit used for the short text message, 55 | and the \c QDialogButtonBox with the default "Ok" button. There are a couple convenience functions for setting the font style 56 | and word wrap on the \c QTextEdit. By default a variable-width font is used with word-wrap on. 57 | 58 | */ 59 | class ScrollableMessageBox : public QDialog 60 | { 61 | Q_OBJECT 62 | 63 | class TextEdit : public QTextEdit 64 | { 65 | public: 66 | TextEdit(QWidget *parent=0) : QTextEdit(parent) {} 67 | QSize sizeHint() const Q_DECL_OVERRIDE; 68 | }; 69 | 70 | public: 71 | explicit ScrollableMessageBox(QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::Dialog); 72 | ScrollableMessageBox(const QString &title, const QString &text = QString(), const QString &details = QString(), QWidget *parent = Q_NULLPTR, Qt::WindowFlags f = Qt::Dialog); 73 | 74 | //! The QTextEdit which holds the detailed text 75 | QTextEdit * textEdit() const { return m_textEdit; } 76 | //! The QLabel which holds the text 77 | QLabel * textLabel() const { return m_textLabel; } 78 | //! The dialog's button box 79 | QDialogButtonBox * buttonBox() const { return m_btnBox; } 80 | 81 | public slots: 82 | void setText(const QString &text = QString()); 83 | void setDetailedText(const QString &details = QString(), Qt::TextFormat format = Qt::AutoText); 84 | void setFontFixedWidth(bool fixed = true); 85 | void setWordWrap(bool wrap = true); 86 | 87 | protected: 88 | void init(const QString &title = QString(), const QString &text = QString(), const QString &details = QString()); 89 | 90 | QLabel * m_textLabel; 91 | TextEdit * m_textEdit; 92 | QDialogButtonBox * m_btnBox; 93 | }; 94 | 95 | #endif // SCROLLABLEESSAGEBOX_H 96 | -------------------------------------------------------------------------------- /src/widgets/ScrollableMessageBox.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ScrollableMessageBox 3 | 4 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 5 | Contact: http://www.WorldDesign.com/contact 6 | 7 | LICENSE: 8 | 9 | Commercial License Usage 10 | Licensees holding valid commercial licenses may use this file in 11 | accordance with the terms contained in a written agreement between 12 | you and the copyright holder. 13 | 14 | GNU General Public License Usage 15 | Alternatively, this file may be used under the terms of the GNU 16 | General Public License as published by the Free Software Foundation, 17 | either version 3 of the License, or (at your option) any later version. 18 | 19 | This program is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | A copy of the GNU General Public License is available at . 25 | */ 26 | 27 | #include "ScrollableMessageBox.h" 28 | 29 | 30 | ScrollableMessageBox::ScrollableMessageBox(QWidget *parent, Qt::WindowFlags f) : 31 | QDialog(parent, f | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) 32 | { 33 | init(); 34 | } 35 | 36 | ScrollableMessageBox::ScrollableMessageBox(const QString &title, const QString &text, const QString &details, QWidget *parent, Qt::WindowFlags f) : 37 | QDialog(parent, f | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint) 38 | { 39 | init(title, text, details); 40 | } 41 | 42 | void ScrollableMessageBox::setText(const QString &text) 43 | { 44 | m_textLabel->setText(text); 45 | } 46 | 47 | void ScrollableMessageBox::setDetailedText(const QString &details, Qt::TextFormat format) 48 | { 49 | if (format == Qt::AutoText) 50 | m_textEdit->setText(details); 51 | else if (format == Qt::RichText) 52 | m_textEdit->setHtml(details); 53 | else 54 | m_textEdit->setPlainText(details); 55 | } 56 | 57 | void ScrollableMessageBox::setFontFixedWidth(bool fixed) 58 | { 59 | QFont newFont(m_textEdit->font()); 60 | if (fixed) { 61 | newFont.setFamily("Courier"); 62 | newFont.setStyleHint(QFont::TypeWriter); 63 | } 64 | else { 65 | newFont.setFamily("Helvetica"); 66 | newFont.setStyleHint(QFont::SansSerif); 67 | } 68 | #ifdef Q_OS_MACOS 69 | newFont.setPointSize(13); 70 | m_textEdit->setAttribute(Qt::WA_MacNormalSize); 71 | #elif defined Q_OS_WIN 72 | newFont.setPointSize(10); 73 | #endif 74 | m_textEdit->setFont(newFont); 75 | } 76 | 77 | void ScrollableMessageBox::setWordWrap(bool wrap) 78 | { 79 | m_textEdit->setWordWrapMode(wrap ? QTextOption::WrapAtWordBoundaryOrAnywhere : QTextOption::NoWrap); 80 | } 81 | 82 | void ScrollableMessageBox::init(const QString & title, const QString & text, const QString & details) 83 | { 84 | m_textLabel = new QLabel(this); 85 | m_textLabel->setAlignment(Qt::AlignVCenter | Qt::AlignLeft); 86 | m_textLabel->setOpenExternalLinks(true); 87 | 88 | m_textEdit = new TextEdit(this); 89 | m_textEdit->setReadOnly(true); 90 | m_textEdit->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); 91 | 92 | m_btnBox = new QDialogButtonBox(QDialogButtonBox::Ok, this); 93 | 94 | connect(m_btnBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 95 | connect(m_btnBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 96 | 97 | QVBoxLayout * lo = new QVBoxLayout(this); 98 | lo->setSpacing(8); 99 | lo->addWidget(m_textLabel); 100 | lo->addWidget(m_textEdit); 101 | lo->addWidget(m_btnBox); 102 | 103 | setSizeGripEnabled(true); 104 | setFontFixedWidth(false); 105 | setWordWrap(true); 106 | 107 | if (!title.isEmpty()) 108 | setWindowTitle(title); 109 | if (!text.isEmpty()) 110 | setText(text); 111 | if (!details.isEmpty()) 112 | setDetailedText(details); 113 | } 114 | 115 | 116 | /* 117 | ScrollableMessageBox::TextEdit 118 | */ 119 | 120 | QSize ScrollableMessageBox::TextEdit::sizeHint() const 121 | { 122 | // stupid trick to get an idea of necessary size to contain all the text, works for html and plain 123 | QLabel tmp; 124 | tmp.setFont(font()); 125 | tmp.setText(toHtml()); 126 | return tmp.sizeHint() + QSize(30, 20); 127 | } 128 | -------------------------------------------------------------------------------- /examples/imageviewer/GraphicsImageView.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | GraphicsImageView 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Rights Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #include "GraphicsImageView.h" 29 | #include 30 | 31 | GraphicsImageView::GraphicsImageView(QWidget *p) : 32 | QGraphicsView(new QGraphicsScene, p) 33 | { 34 | setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 35 | setBackgroundRole(QPalette::Shadow); 36 | setViewportUpdateMode(FullViewportUpdate); 37 | setOptimizationFlags(DontSavePainterState | DontClipPainter | DontAdjustForAntialiasing); 38 | setAlignment(Qt::AlignCenter); 39 | setFrameStyle(QFrame::NoFrame); 40 | setLineWidth(0); 41 | } 42 | 43 | void GraphicsImageView::setImageFile(const QString &imageFile) 44 | { 45 | if (m_imageFile != imageFile) { 46 | m_imageFile = imageFile; 47 | loadImage(viewport()->contentsRect().size()); 48 | } 49 | } 50 | 51 | void GraphicsImageView::setImageScalingMode(int mode) 52 | { 53 | if (m_scaleMode != Qt::AspectRatioMode(mode)) { 54 | m_scaleMode = Qt::AspectRatioMode(mode); 55 | if (m_item) { 56 | rotationReset(); 57 | zoomReset(); 58 | scene()->setSceneRect(viewport()->contentsRect()); 59 | loadImage(viewport()->contentsRect().size()); 60 | } 61 | emit imageScalingModeChanged(m_scaleMode); 62 | } 63 | } 64 | 65 | void GraphicsImageView::setImageScale(qreal scale) const 66 | { 67 | if (m_item && scale > 0.0) { 68 | m_item->setScale(scale); 69 | if (qFuzzyCompare(scale, 1.0)) 70 | m_item->setPos(0,0); 71 | emit imageScaleChanged(scale); 72 | } 73 | } 74 | 75 | void GraphicsImageView::setImageRotation(qreal degrees) const 76 | { 77 | if (!m_item) 78 | return; 79 | m_item->setRotation(degrees); 80 | emit imageRotationChanged(degrees); 81 | } 82 | 83 | void GraphicsImageView::loadImage(const QSize &size) 84 | { 85 | if (!scene()) 86 | return; 87 | if (m_imageFile.isEmpty()) { 88 | // remove existing image, if any 89 | removeItem(); 90 | return; 91 | } 92 | 93 | // Load image at original size 94 | QPixmap pm(m_imageFile); 95 | if (pm.isNull()) { 96 | // file not found/other error 97 | removeItem(); 98 | return; 99 | } 100 | // Resize the image here. 101 | pm = pm.scaled(size, m_scaleMode, Qt::SmoothTransformation); 102 | if (createItem()) { 103 | m_item->setPixmap(pm); 104 | m_item->setTransformOriginPoint(m_item->boundingRect().center()); 105 | } 106 | if (pm.size().width() < size.width() || pm.size().height() < size.height()) 107 | scene()->setSceneRect(QRectF(viewport()->contentsRect().topLeft(), QSizeF(pm.size()))); 108 | emit imageChanged(); 109 | } 110 | 111 | void GraphicsImageView::resizeEvent(QResizeEvent *e) 112 | { 113 | QGraphicsView::resizeEvent(e); 114 | if (!scene()) 115 | return; 116 | // Set scene size to fill the available viewport size; 117 | const QRect sceneRect(viewport()->contentsRect()); 118 | scene()->setSceneRect(sceneRect); 119 | // Keep the root item sized to fill the viewport and scene; 120 | if (m_item) 121 | loadImage(sceneRect.size()); 122 | } 123 | 124 | bool GraphicsImageView::createItem() { 125 | if (m_item) 126 | return true; 127 | if (!m_item && scene()) { 128 | m_item = new QGraphicsPixmapItem(); 129 | m_item->setFlag(QGraphicsItem::ItemIsMovable); 130 | m_item->setTransformationMode(Qt::SmoothTransformation); 131 | m_item->setShapeMode(QGraphicsPixmapItem::BoundingRectShape); 132 | m_item->setCursor(Qt::ClosedHandCursor); 133 | scene()->addItem(m_item); 134 | return true; 135 | } 136 | return false; 137 | } 138 | 139 | void GraphicsImageView::removeItem() 140 | { 141 | if (m_item) { 142 | if (scene()) 143 | scene()->removeItem(m_item); 144 | delete m_item; 145 | m_item = nullptr; 146 | emit imageChanged(); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/widgets/BuddyLabel.h: -------------------------------------------------------------------------------- 1 | /* 2 | BuddyLabel 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef BUDDYLABEL_H 29 | #define BUDDYLABEL_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | /*! 39 | \brief The BuddyLabel class is a QLabel with enhanced "buddy" capabilities. 40 | 41 | It overrides the \c QLabel::setBuddy() method and, besides the usual shortcut handling provided by \c QLabel, 42 | it adds mouse click handling and mirroring of the buddy's tool tip text. 43 | 44 | Mouse clicks are connected to the \c QWidget::setFocus slot. For \c QCheckBox it also connects to the \c click() slot so the box can be (un)checked by clicking on the label. 45 | Mouse double-clicks are connected to \c QLineEdit::selectAll() on widgets which either are or have a \c QLineEdit (like \c QAbstractSpinBox and editable \c QComboBox). 46 | Custom connections could be added by connecting to the \c clicked() and/or \c doubleClicked() signals, or inheriting and overriding the \c connectBuddy() virtual method. 47 | */ 48 | class BuddyLabel : public QLabel 49 | { 50 | Q_OBJECT 51 | public: 52 | using QLabel::QLabel; 53 | 54 | public slots: 55 | //! Overrides the \c QLabel::setBuddy() method, which isn't virtual. Calls the base class implementation as well, so the shortcut mechanism still works. 56 | void setBuddy(QWidget *buddy) 57 | { 58 | if (this->buddy()) { 59 | this->buddy()->removeEventFilter(this); 60 | disconnect(this->buddy()); 61 | disconnectBuddy(this->buddy()); 62 | } 63 | 64 | QLabel::setBuddy(buddy); 65 | 66 | if (!buddy) 67 | return; 68 | 69 | setToolTip(buddy->toolTip()); 70 | buddy->installEventFilter(this); 71 | connectBuddy(buddy); 72 | } 73 | 74 | signals: 75 | //! Emitted when label is clicked with left mouse button (or something emulating one). 76 | void clicked(); 77 | //! Emitted when label is double-clicked with left mouse button (or something emulating one). 78 | void doubleClicked(); 79 | 80 | protected: 81 | //! Override this method for custom connections. 82 | virtual void connectBuddy(QWidget *buddy) 83 | { 84 | // Single clicks 85 | connect(this, &BuddyLabel::clicked, buddy, QOverload<>::of(&QWidget::setFocus)); 86 | if (QCheckBox *cb = qobject_cast(buddy)) 87 | connect(this, &BuddyLabel::clicked, cb, &QCheckBox::click); 88 | 89 | // Double clicks 90 | if (QLineEdit *le = qobject_cast(buddy)) 91 | connect(this, &BuddyLabel::doubleClicked, le, &QLineEdit::selectAll); 92 | else if (QAbstractSpinBox *sb = qobject_cast(buddy)) 93 | connect(this, &BuddyLabel::doubleClicked, sb, &QAbstractSpinBox::selectAll); 94 | else if (QComboBox *cb = qobject_cast(buddy)) 95 | if (cb->isEditable() && cb->lineEdit()) 96 | connect(this, &BuddyLabel::doubleClicked, cb->lineEdit(), &QLineEdit::selectAll); 97 | } 98 | 99 | //! Hook for custom disconnections. We already disconnect ourselves from all slots in \a buddy in the main handler. 100 | virtual void disconnectBuddy(QWidget *buddy) { Q_UNUSED(buddy) } 101 | 102 | //! The filter monitors for tool tip changes on the buddy 103 | bool eventFilter(QObject *obj, QEvent *ev) 104 | { 105 | if (ev->type() == QEvent::ToolTipChange && buddy() && obj == buddy()) 106 | setToolTip(buddy()->toolTip()); 107 | return false; 108 | } 109 | 110 | void mousePressEvent(QMouseEvent *ev) 111 | { 112 | if (ev->button() == Qt::LeftButton) { 113 | m_pressed = true; 114 | ev->accept(); 115 | } 116 | QLabel::mousePressEvent(ev); 117 | } 118 | 119 | void mouseReleaseEvent(QMouseEvent *ev) 120 | { 121 | if (m_pressed && rect().contains(ev->pos())) 122 | emit clicked(); 123 | m_pressed = false; 124 | QLabel::mouseReleaseEvent(ev); 125 | } 126 | 127 | void mouseDoubleClickEvent(QMouseEvent *ev) 128 | { 129 | if (ev->button() == Qt::LeftButton && rect().contains(ev->pos())) 130 | emit doubleClicked(); 131 | QLabel::mouseDoubleClickEvent(ev); 132 | } 133 | 134 | private: 135 | bool m_pressed = false; 136 | Q_DISABLE_COPY(BuddyLabel) 137 | }; 138 | 139 | #endif // BUDDYLABEL_H 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # maxLibQt - C++ and QML code library for the *Qt*™ framework. 2 | 3 | This is a (growing) collection of somewhat random C++ classes and QtQuick QML modules for use with the [Qt](http://qt.io)™ framework. 4 | 5 | They are free to use in other open source projects under the terms of the GNU Public License (GPL). For use in 6 | commercial or other closed-source software, you need to contact me for a license agreement. See the LICENSE.txt file. 7 | 8 | The original target Qt library version was 5.2+. Newer components, and some that have been updated more recently, 9 | may require a later version, at least 5.9 (if you run into something that could be made more backwards-compatible, 10 | let me know). Most (if not all) of the C++ code requires C++11 at minimum. 11 | 12 | Project home: https://github.com/mpaperno/maxLibQt 13 | 14 | ## C++ Components: 15 | 16 | * Core 17 | * `AppDebugMessageHandler` - Custom debug/message handler class to work in conjunction with *qDebug()* family of functions. 18 | * Item Models 19 | * `GroupedItemsProxyModel` - A proxy model (`QIdentityProxyModel` subclass) which allows a grouped tree-based item presentation of a flat table data model. Typically used for visually grouping items by some shared criteria, like a category or subject. Useful in a `QTreeView` or the `TreeComboBox` from this collection. 20 | * Layouts 21 | * `OverlayStackLayout` - A QStackedLayout with additional features to allow stacks with "floating" overlay widgets, such as toolbars, buttons, 22 | messages, etc., while still allowing interaction with exposed areas of the widget(s) underneath. Includes a functional image viewer example application. 23 | * Widgets 24 | * `ActionPushButton` - A QPushButton which takes a default QAction, just like a QToolButton can. 25 | * `BuddyLabel` - A QLabel with enhanced "buddy" capabilities like click redirect and tooltip inheritance. 26 | * `CollapsingToolBar` - A QToolBar which toggles between _ToolButtonIconOnly_ and _ToolButtonTextBesideIcon_ styles based on available width. 27 | * `ExportableTableView` - A QTableView with features to export data as plain text or HTML. 28 | * `RoundedMessageBox` - A frameless QMessageBox implementation, fully stylable via CSS or QPalette. 29 | * `ScrollableMessageBox` - A simple message box with a large scrollable area (a QTextEdit) for detailed text (including HTML formatting). 30 | * `TimerEdit` - A time value line editor which accepts negative and large times (> 23:59:59), suitable for a timer, etc. 31 | * `TreeComboBox` - A QComboBox control which works with a tree-based data model & view, allowing drill-down selection of items. 32 | 33 | ## QML Components: 34 | 35 | * Controls 36 | * [`MLDoubleSpinBox`](maxLibQt::controls::MLDoubleSpinBox) - A SpinBox control which handles float/double number types to the desired precision, as well as large integers (within ECMAScript limits). Avoids the `int` size type limitation of the current QtQuick Controls (v2.0 - 2.4) SpinBox but still using the current theme styling (Fusion/Material/etc). 37 | * [`MLHexSpinBox`](maxLibQt::controls::MLHexSpinBox) - A SpinBox control which allows editing integers in hexadeciaml format. Allows a wide range of numbers, including unsigned integers. Based on `MLDoubleSpinBox`. 38 | 39 | ### Documentation: 40 | 41 | Some Doxygen-style documentation is embedded in the code and can be generated as either part of the CMake build or manually with the 42 | included Doxyfile (run `doxygen doc/Doxyfile` in root of this project). To generate docs for *QML code* you'd need to use install [doxyqml](https://github.com/agateau/doxyqml) (the Doxyfile is already configured to use it if you have it). Some of the source code is further documented inline (but never enough). 43 | 44 | This documentation is also published at https://mpaperno.github.io/maxLibQt/ 45 | 46 | ### Building (C++): 47 | 48 | Both *CMake* and *qmake* project files are provided. They're set up to be built as individual libraries split by base folder (core, item models, etc). With CMake you can also build one library file which contains all the others (haven't figured out how to do that with qmake yet). 49 | 50 | You can also simply include whichever C++ module(s) you need into your own project. They're standalone unless specifically mentioned otherwise in their docs. There are also `.pri` *qmake* files for each source folder which could be used to quickly include all modules in that folder (and add to the `INCLUDEPATH`). 51 | 52 | ### Using (QML) 53 | 54 | Simplest way to use the QtQuick modules would be to copy them into your code tree. 55 | 56 | To use them as external ("library") imports in your code, put the `maxLibQt` folder (from inside the `/src/quick/` folder of this project) into the QML search path for your app. Then, for example to use `MLDoubleSpinBox`, you'd specify `import maxLibQt.controls 1.0` in your QML. 57 | 58 | ------------- 59 | ### Author 60 | 61 | Maxim Paperno 62 | https://github.com/mpaperno/ 63 | http://www.WorldDesign.com/contact 64 | 65 | Please inquire about custom C++/Qt development work. 66 | 67 | ### Copyright, License, and Disclaimer 68 | 69 | Copyright (c) Maxim Paperno. All rights reserved. 70 | 71 | See LICENSE.txt file for license details. 72 | 73 | This program is distributed in the hope that it will be useful, 74 | but WITHOUT ANY WARRANTY; without even the implied warranty of 75 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 76 | 77 | *Qt* is a trademark of *The Qt Company* with which neither I nor this project have any affiliation. 78 | -------------------------------------------------------------------------------- /src/widgets/OverlayStackLayout.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | OverlayStackLayout 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Rights Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #include "OverlayStackLayout.h" 29 | #include 30 | #include 31 | 32 | namespace { 33 | static const char offsetProperty[15] {"positionOffset"}; 34 | } 35 | 36 | OverlayStackLayout::OverlayStackLayout(QWidget * parent) : 37 | QStackedLayout(parent) 38 | { 39 | setStackingMode(StackAll); 40 | } 41 | 42 | OverlayStackLayout::OverlayStackLayout(QLayout * parentLayout) : 43 | QStackedLayout(parentLayout) 44 | { 45 | setStackingMode(StackAll); 46 | } 47 | 48 | int OverlayStackLayout::insertWidget(int index, QWidget *widget, Qt::Alignment alignment) 49 | { 50 | const int ret = insertWidget(index, widget); 51 | if (ret > -1) 52 | setAlignment(widget, alignment); 53 | return ret; 54 | } 55 | 56 | int OverlayStackLayout::insertWidget(int index, QWidget *widget, Qt::Alignment alignment, const QPoint &offset) 57 | { 58 | const int ret = insertWidget(index, widget, alignment); 59 | if (ret > -1) 60 | setOffset(widget, offset); 61 | return ret; 62 | } 63 | 64 | void OverlayStackLayout::setOffset(QWidget *widget, const QPoint &offset) const 65 | { 66 | if (widget->property(offsetProperty).isValid() && widget->property(offsetProperty).toPoint() == offset) 67 | return; 68 | widget->setProperty(offsetProperty, offset); 69 | doLayout(); 70 | } 71 | 72 | void OverlayStackLayout::setSenderOffset(const QPoint &offset) const 73 | { 74 | if (QWidget *w = qobject_cast(sender())) 75 | setOffset(w, offset); 76 | } 77 | 78 | void OverlayStackLayout::setSenderAlignment(Qt::Alignment align) 79 | { 80 | if (QWidget *w = qobject_cast(sender())) 81 | setAlignment(w, align); 82 | } 83 | 84 | void OverlayStackLayout::setStackingMode(QStackedLayout::StackingMode mode) 85 | { 86 | if (mode == stackingMode()) 87 | return; 88 | QStackedLayout::setStackingMode(mode); 89 | // resetting the mode to StackAll messes with our layout 90 | if (mode == StackAll) 91 | doLayout(); 92 | } 93 | 94 | void OverlayStackLayout::setGeometry(const QRect & geo) 95 | { 96 | if (geo == geometry()) 97 | return; 98 | QLayout::setGeometry(geo); 99 | doLayout(); 100 | } 101 | 102 | void OverlayStackLayout::doLayout() const 103 | { 104 | const int n = count(); 105 | if (!n) 106 | return; 107 | for (int i=0; i < n; ++i) { 108 | QWidget *w = nullptr; 109 | if (QLayoutItem *item = itemAt(i)) 110 | w = item->widget(); 111 | if (!w) 112 | continue; 113 | 114 | // available geometry for widgets, use size based on layout attribute 115 | const QRect rect = w->testAttribute(Qt::WA_LayoutOnEntireRect) ? geometry() : contentsRect(); 116 | // widget's desired size 117 | const QSize wSize = w->sizeHint().expandedTo(w->minimumSize()).boundedTo(w->maximumSize()); 118 | QRect wRect(rect.topLeft(), wSize); // default widget position and size 119 | QPoint offset(0, 0); // position offset 120 | if (w->property(offsetProperty).isValid()) 121 | offset = w->property(offsetProperty).toPoint(); 122 | 123 | // expand or constrain to full width? 124 | if ((w->sizePolicy().expandingDirections() & Qt::Horizontal) || wRect.width() > rect.width() - offset.x()) 125 | wRect.setWidth(rect.width() - offset.x()); 126 | 127 | // expand or constrain to full height? 128 | if ((w->sizePolicy().expandingDirections() & Qt::Vertical) || wRect.height() > rect.height() - offset.y()) 129 | wRect.setHeight(rect.height() - offset.y()); 130 | 131 | // Adjust position for alignment 132 | const Qt::Alignment align = itemAt(i)->alignment(); 133 | if (rect.width() > wRect.width() - offset.x()) { 134 | switch (align & Qt::AlignHorizontal_Mask) { 135 | case Qt::AlignHCenter: 136 | wRect.moveLeft(rect.x() + (rect.width() - wRect.width()) / 2); 137 | break; 138 | case Qt::AlignRight: 139 | wRect.moveRight(rect.right()); 140 | break; 141 | case Qt::AlignLeft: 142 | default: 143 | wRect.moveLeft(rect.left()); 144 | break; 145 | } 146 | } 147 | if (rect.height() > wRect.height() - offset.y()) { 148 | switch (align & Qt::AlignVertical_Mask) { 149 | case Qt::AlignVCenter: 150 | wRect.moveTop(rect.y() + (rect.height() - wRect.height()) / 2); 151 | break; 152 | case Qt::AlignBottom: 153 | wRect.moveBottom(rect.bottom()); 154 | break; 155 | case Qt::AlignTop: 156 | default: 157 | wRect.moveTop(rect.top()); 158 | break; 159 | } 160 | } 161 | // adjust for user-defined offset 162 | if (!offset.isNull()) 163 | wRect.moveTopLeft(wRect.topLeft() + offset); 164 | 165 | // Set position and size of the widget. 166 | w->setGeometry(wRect); 167 | // Honor the stacking attribute 168 | if (w->testAttribute(Qt::WA_AlwaysStackOnTop)) 169 | w->raise(); 170 | } 171 | } 172 | 173 | #include "moc_OverlayStackLayout.cpp" 174 | -------------------------------------------------------------------------------- /examples/imageviewer/ImageGrid.h: -------------------------------------------------------------------------------- 1 | /* 2 | ImageGrid 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Rights Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef IMAGEGRID_H 29 | #define IMAGEGRID_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | 40 | /*! 41 | \ingroup examples_imageviewer 42 | */ 43 | class ImageGridDelegate : public QStyledItemDelegate 44 | { 45 | Q_OBJECT 46 | public: 47 | using QStyledItemDelegate::QStyledItemDelegate; 48 | 49 | bool showTooltips = true; // respond to tooltip help event 50 | qreal frameWidth = 2.0; // selection frame size 51 | 52 | void paint(QPainter *p, const QStyleOptionViewItem &opt, const QModelIndex &idx) const override; 53 | QPixmap pixmap(const QModelIndex &idx, const QSize &size) const; 54 | bool helpEvent(QHelpEvent *e, QAbstractItemView *v, const QStyleOptionViewItem &, const QModelIndex &idx) override; 55 | QSize sizeHint(const QStyleOptionViewItem &opt, const QModelIndex &) const override { return opt.decorationSize; } 56 | 57 | protected: 58 | // we don't use this, but provide an optimized version in case this gets called internally in superclass 59 | void initStyleOption(QStyleOptionViewItem *o, const QModelIndex &idx) const override { o->index = idx; } 60 | private: 61 | Q_DISABLE_COPY(ImageGridDelegate) 62 | }; 63 | 64 | 65 | /*! 66 | \ingroup examples_imageviewer 67 | 68 | \c ImageGrid is a \c QListView using a custom \c ImageGridDelegate to present a grid of "previews" of images in a given folder. 69 | The number of columns used to show the images, or the image preview sizes can set explicitly, or both can auto-adjust based 70 | on the available space (this is the default setting). 71 | 72 | Note that the global \c QPixmapCache is used to store image previews by the \c ImageGridDelegate class. It should be suitably "large." 73 | */ 74 | class ImageGrid : public QListView 75 | { 76 | Q_OBJECT 77 | Q_PROPERTY(int count READ count NOTIFY countChanged) 78 | Q_PROPERTY(QString currentPath READ currentPath WRITE setCurrentPath NOTIFY currentPathChanged) 79 | Q_PROPERTY(QString currentImage READ currentImage WRITE setCurrentImage NOTIFY currentImageChanged) 80 | Q_PROPERTY(int currentFileIndex READ currentFileIndex WRITE setCurrentFile NOTIFY currentFileIndexChanged) 81 | Q_PROPERTY(QDir::SortFlags sortFlags READ sortFlags WRITE sortBy NOTIFY sortChanged) 82 | Q_PROPERTY(QSize imageSize READ imageSize WRITE setImageSize NOTIFY imageSizeChanged) 83 | Q_PROPERTY(int columns READ columns WRITE setColumns) 84 | 85 | public: 86 | explicit ImageGrid(QWidget *p = nullptr); 87 | 88 | int count() const; 89 | QString currentPath() const; 90 | QString currentImage() const; 91 | int currentFileIndex() const; 92 | QDir::SortFlags sortFlags() const; 93 | QSize imageSize() const; 94 | int columns() const; 95 | 96 | public slots: 97 | void setCurrentPath(const QString &path); 98 | void setCurrentImage(const QString &imageFile); 99 | void setCurrentFile(int index); 100 | void nextImage() { selectImage(findNextImage(false)); } 101 | void prevImage() { selectImage(findNextImage(true)); } 102 | void sortBy(QDir::SortFlags flags); 103 | void setColumns(uint columns = 0); 104 | void setImageSize(const QSize &size = QSize()); 105 | 106 | signals: 107 | void countChanged(int count); 108 | void currentPathChanged(const QString &path) const; 109 | void imageSelected(const QString &filename) const; 110 | void currentImageChanged(const QString &filename) const; 111 | void currentFileIndexChanged(int index) const; 112 | void sortChanged(QDir::SortFlags flags) const; 113 | void imageSizeChanged(const QSize &size) const; 114 | 115 | protected: 116 | void resizeEvent(QResizeEvent *e) override; 117 | 118 | private slots: 119 | void selectImage(const QModelIndex &idx); 120 | void updateLayout(bool force = false); 121 | void resetModel(); 122 | 123 | private: 124 | uint columnsForWidth(int w) const; 125 | QModelIndex findNextImage(bool prevoius = false); 126 | 127 | QFileSystemModel *m_model = nullptr; 128 | uint m_columns = 0; 129 | QDir::SortFlags m_sortBy = QDir::Name; 130 | QSize m_icnSize; 131 | 132 | Q_DISABLE_COPY(ImageGrid) 133 | }; 134 | 135 | inline 136 | int ImageGrid::count() const { return m_model ? m_model->rowCount(rootIndex()) : 0; } 137 | 138 | inline 139 | QString ImageGrid::currentPath() const { return m_model ? m_model->rootPath() : QString(); } 140 | 141 | inline 142 | QString ImageGrid::currentImage() const { 143 | return currentIndex().isValid() ? 144 | currentIndex().data(QFileSystemModel::FilePathRole).toString() : 145 | QString(); 146 | } 147 | 148 | inline 149 | int ImageGrid::currentFileIndex() const { return currentIndex().row(); } 150 | 151 | inline 152 | QDir::SortFlags ImageGrid::sortFlags() const { return m_sortBy; } 153 | 154 | inline 155 | QSize ImageGrid::imageSize() const { return m_icnSize; } 156 | 157 | inline 158 | int ImageGrid::columns() const { return m_columns; } 159 | 160 | inline 161 | void ImageGrid::resizeEvent(QResizeEvent *e) 162 | { 163 | QListView::resizeEvent(e); 164 | updateLayout(); 165 | } 166 | 167 | #endif // IMAGEGRID_H 168 | -------------------------------------------------------------------------------- /src/widgets/ExportableTableView.h: -------------------------------------------------------------------------------- 1 | /* 2 | ExportableTableView 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef EXPORTABLETABLEVIEW_H 29 | #define EXPORTABLETABLEVIEW_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | 42 | /** 43 | \class ExportableTableView 44 | \version 1.0.1 45 | 46 | \brief The ExportableTableView class provides a regular QTableView but with features to export the data 47 | as plain text or HTML. 48 | 49 | Any selection of data can be exported. The horizontal headings, if any, are included in the export. 50 | The export functions are available to the user via a custom context menu or keyboard shortcuts. 51 | 3 field delimiter choices are available for plain-text export (tab, comma, or pipe). 52 | Data can be saved to the clipboard or to a file. 53 | 54 | The export functions can also be accessed programmatically via \c toPlainText(), \c toHtml(), 55 | and \c saveToFile(). 56 | 57 | The style sheet for the generated page can be set with \c setHtmlStyle(). The overall HTML 58 | template can be customized with \c setHtmlTemplate(). The data itself is always 59 | formatted as a basic HTML table and then inserted into the template at the \c %2 placeholder. 60 | 61 | HTML version tries to preserve many data role attributes of the model items: 62 | 63 | \li \c Qt::FontRole 64 | \li \c Qt::ForegroundRole 65 | \li \c Qt::BackgroundRole 66 | \li \c Qt::TextAlignmentRole 67 | \li \c Qt::ToolTipRole 68 | 69 | Note that \c Qt::EditRole data is not specifically preserved (unless it already matches \c Qt::DisplayRole ). 70 | 71 | */ 72 | class ExportableTableView : public QTableView 73 | { 74 | Q_OBJECT 75 | 76 | public: 77 | /*! 78 | \brief ExportableTableView 79 | \param parent 80 | */ 81 | ExportableTableView(QWidget *parent = Q_NULLPTR); 82 | 83 | /*! \brief Get the default style sheet used for HTML export. */ 84 | static QString getDefaultHtmlStyle(); 85 | /*! \brief Get the default overall template used for HTML export. */ 86 | static QString getDefaultHtmlTemplate(); 87 | /*! 88 | \brief Set the style sheet for HTML export. This goes between the \c "" tags of the page. 89 | \param value The CSS code. 90 | */ 91 | void setHtmlStyle(const QString &value); 92 | /*! 93 | \brief Set the overall template for HTML export. The template must have two placeholders: \c %1 for the style and \c %2 for the table. 94 | \param value The HTML code. 95 | */ 96 | void setHtmlTemplate(const QString &value); 97 | 98 | /*! 99 | \brief Saves data from the passed model indices to a text string. 100 | \param indexList A list of indices to export. 101 | \param delim The delimiter to use. Can be multiple characters (eg. \c ", ") Default is a single TAB. 102 | \return \e QString with the formatted text. If the data has column headings, these are on the first line. 103 | */ 104 | QString toPlainText(const QModelIndexList &indexList, const QString &delim = "\t") const; 105 | 106 | /*! 107 | \brief Saves data from the passed model indices to an HTML-formatted string. 108 | \param indexList A list of indices to export. 109 | \return \e QString with the formatted HTML. If the data has column headings, these are included as table headings. 110 | */ 111 | QString toHtml(const QModelIndexList &indexList) const; 112 | 113 | /*! 114 | Saves data from the passed model indices to a file in text or HTML format. The file extension determines 115 | the format/text delimiter: \e \.html for HTML, \e \.tab for TAB, \e \.csv for CSV, and anything else 116 | is pipe-delimited. 117 | 118 | \param indexList A list of indices to export. 119 | \param fileName An optional file name (with path). If none is passed then a file chooser dialog is presented. 120 | \return true on success, false on failure (no file selected or write error) 121 | */ 122 | bool saveToFile(const QModelIndexList &indexList, const QString &fileName = QString()); 123 | 124 | /*! \brief Return a list of selected cells. If none are selected then it first selects the whole table. */ 125 | QModelIndexList getSelectedOrAll(); 126 | 127 | QSize sizeHint() const Q_DECL_OVERRIDE; 128 | 129 | public slots: 130 | /*! 131 | \brief Copies currently selected cell(s) to the clipboard as plain text. 132 | \param delim The delimiter to use. Can be multiple characters (eg. ", ") Default is a single TAB. 133 | */ 134 | void copyText(const QString &delim = QString("\t")); 135 | /*! \brief Copies currently selected cell(s) to the clipboard as HTML (with MIME content-type \e "text/html"). */ 136 | void copyHtml(); 137 | /*! \brief Designed for \e QAction connections, it calls \c copyText() or \c copyHtml() based on delimiter specified in \c sender()->property\("delim"\) (use "html" as \p delim for HTML format). */ 138 | void copy(); 139 | /*! \brief Saves currently selected cell(s) to a file of the user's choice and format. */ 140 | void save(); 141 | 142 | protected: 143 | void onCustomContextMenuRequested(const QPoint &pos); 144 | 145 | QString m_htmlTemplate; 146 | QString m_htmlStyle; 147 | }; 148 | 149 | #endif // EXPORTABLETABLEVIEW_H 150 | -------------------------------------------------------------------------------- /doc/qttagsfix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __copyright__ = """ 3 | COPYRIGHT: (c)2020 Maxim Paperno; All Rights Reserved. 4 | 5 | Released under MIT License: 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | """ 24 | 25 | DESCRIPTION = """ 26 | A utility to fix Qt's Doxygen-compatible tag files which have invalid data 27 | for enum types and values, as per QTBUG-61790. It can be used as a standalone 28 | utility, or imported as a package to call the fixEnums() method directly. 29 | 30 | It reads a given input file, applies the fixes, and writes the fixed XML 31 | back to a file (either the same one as the input, or a new one). 32 | 33 | It looks for tags improperly formatted like this: 34 | 35 | 36 | Orientation 37 | Orientation-enum 38 | 39 | 40 | 41 | Orientation-enum 42 | 43 | 44 | 45 | And converts them to: 46 | 47 | 48 | Orientation 49 | qt.html 50 | Orientation-enum 51 | 52 | 53 | Horizontal 54 | qt.html 55 | Orientation-enum 56 | 57 | 58 | One limitation is that there is no way to detect deprecated enums, so those 59 | will still be linked to the parent's main docs page, instead of the "-obsolete" 60 | page where they actually are listed. 61 | """ 62 | 63 | import sys 64 | import argparse 65 | import xml.etree.ElementTree as ET 66 | 67 | # used as parser target for preserving comments in XML 68 | class CommentedTreeBuilder(ET.TreeBuilder): 69 | def comment(self, data): 70 | self.start(ET.Comment, {}) 71 | self.data(data) 72 | self.end(ET.Comment) 73 | 74 | 75 | # to strip useless arglist tag 76 | def stripEmptyArglist(parent): 77 | alist = parent.find('arglist') 78 | if alist is not None and not alist.text: 79 | parent.remove(alist) 80 | lastEl = parent.find('anchor') 81 | if lastEl is not None: 82 | parent.find('anchor').tail = parent.tail 83 | 84 | 85 | # Fix enum members in Qt tagfiles (QTBUG-61790). 86 | # `infile` is the full path to the XML tag file to process. 87 | # If `outfile` is `None` (default) then the `infile` file is overwritten. 88 | def fixEnums(infile, outfile=None): 89 | if not outfile: 90 | outfile = infile 91 | tree = ET.parse(infile, ET.XMLParser(target=CommentedTreeBuilder())) 92 | root = tree.getroot() 93 | 94 | # iterate over all compounds with named enum members 95 | for comp in root.iterfind(".//member[@kind='enum']/name/../.."): 96 | if comp is None: 97 | continue 98 | # the file name of the parent compound becomes the for enum links 99 | fn = comp.find('filename') 100 | if fn is None: 101 | continue 102 | # Skip the whole compound if it doesn't have the broken enum syntax anywhere 103 | # (we could probably just bail out entirely here, but who knows...) 104 | if comp.find(".//member[@name]") is None: 105 | continue 106 | 107 | # get line break and indent level for inserting elements 108 | tail = comp.find("./member/name").tail 109 | # element needed for enumeration and enumvalue tags 110 | afileEl = ET.Element('anchorfile') 111 | afileEl.text = fn.text 112 | afileEl.tail = tail 113 | 114 | # fix tags (change to kind="enumeration" and add ) 115 | for enum in comp.iterfind(".//member[@kind='enum']"): 116 | if enum.find('anchorfile') is not None: 117 | continue # has anchorfile element, must have been fixed already 118 | enum.set('kind', "enumeration") 119 | enum.insert(1, afileEl) 120 | stripEmptyArglist(enum) 121 | 122 | # fix enum value members which appear as but should 123 | # be add elements 124 | for enumval in comp.iterfind(".//member[@name]"): 125 | ename = enumval.get('name') 126 | if ename is None or enumval.find('name') is not None: 127 | continue # must have been fixed already 128 | enumval.attrib.clear() # remove 'name' attrib 129 | enumval.set('kind', "enumvalue") 130 | nameEl = ET.Element('name') 131 | nameEl.text = ename 132 | nameEl.tail = tail 133 | enumval.insert(0, nameEl) 134 | enumval.insert(1, afileEl) 135 | stripEmptyArglist(enumval) 136 | 137 | # write out the new XML 138 | tree.write(outfile, encoding="UTF-8", xml_declaration=True) 139 | 140 | 141 | def main(): 142 | parser = argparse.ArgumentParser( 143 | formatter_class=argparse.RawDescriptionHelpFormatter, 144 | description=DESCRIPTION) 145 | 146 | parser.add_argument("infile", 147 | help="Input XML tagfile to fix.") 148 | parser.add_argument("outfile", 149 | help="Output file for fixed XML. Use 'overwrite' to modify the input file itself.") 150 | 151 | opts = parser.parse_args(); 152 | 153 | if opts.outfile == "overwrite": 154 | opts.outfile = opts.infile 155 | 156 | print("Fixing up tagfile: " + opts.infile) 157 | print("Output to: " + opts.outfile) 158 | 159 | fixEnums(opts.infile, opts.outfile) 160 | 161 | return 0 162 | 163 | 164 | if __name__ == "__main__": 165 | sys.exit(main()) 166 | -------------------------------------------------------------------------------- /src/widgets/RoundedMessageBox.h: -------------------------------------------------------------------------------- 1 | /* 2 | RoundedMessageBox 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Rights Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef ROUNDEDMESSAGEBOX_H 29 | #define ROUNDEDMESSAGEBOX_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | /*! 38 | \brief The RoundedMessageBox class is a frameless \c QMessageBox implementation. 39 | It can be styled using using either QSS (CSS) or by using a custom \c QPalette 40 | in combination with the regular \c QWidget::setForegroundRole() and \c QWidget::setBackgroundRole(). 41 | 42 | This was originally created as an answer to a [StackOverflow question][1]. It is very simple, only 43 | re-implementing the \c paintEvent(). There is no way for the user to move the message box around 44 | on the screen (allowing to drag it by the contents area would be a nice TODO). 45 | 46 | Here is an example of using the message box with both ways of styling it, also from the SO answer (see link for screenshot). 47 | \code 48 | int main(int argc, char *argv[]) 49 | { 50 | //QApplication::setStyle("Fusion"); 51 | QApplication app(argc, argv); 52 | 53 | // Dialog setup 54 | RoundedMessageBox *msgBox = new RoundedMessageBox(); 55 | msgBox->setAttribute(Qt::WA_DeleteOnClose); 56 | msgBox->setMinimumSize(300, 300); 57 | msgBox->setWindowTitle("Frameless window test"); 58 | msgBox->setText("

Frameless rounded message box.

"); 59 | msgBox->setInformativeText("Lorem ipsum dolor sit amet, consectetur ...."); 60 | 61 | // Styling: two options with the same (garish) result. 62 | if (1) { 63 | // Use QSS style 64 | app.setStyleSheet(QStringLiteral( 65 | "QMessageBox { " 66 | "border-radius: 12px; " 67 | "border: 3.5px solid; " 68 | "border-color: qlineargradient(x1: 1, y1: 1, x2: 0, y2: 0, stop: 0 #ffeb7f, stop: 1 #d09d1e); " 69 | "background: qlineargradient(x1: 0, y1: 0, x2: 1, y2: 1, stop: 0 #ffeb7f, stop: 1 #d09d1e); " 70 | "color: #003200; " 71 | "}" 72 | )); 73 | } 74 | else { 75 | // Use "native" styling 76 | msgBox->radius = 12.0; 77 | msgBox->borderWidth = 3.5; 78 | 79 | QLinearGradient bgGrad(0, 0, 1, 1); 80 | bgGrad.setCoordinateMode(QGradient::ObjectMode); 81 | bgGrad.setColorAt(0.0, QColor("gold").lighter()); 82 | bgGrad.setColorAt(1.0, QColor("goldenrod").darker(105)); 83 | QLinearGradient fgGrad(bgGrad); 84 | fgGrad.setStart(bgGrad.finalStop()); 85 | fgGrad.setFinalStop(bgGrad.start()); 86 | 87 | QPalette pal; 88 | pal.setBrush(QPalette::Window, QBrush(bgGrad)); 89 | pal.setBrush(QPalette::Mid, QBrush(fgGrad)); 90 | pal.setBrush(QPalette::WindowText, QColor("darkgreen").darker()); 91 | msgBox->setPalette(pal); 92 | 93 | msgBox->setForegroundRole(QPalette::Mid); // default is WindowText 94 | msgBox->setBackgroundRole(QPalette::Window); // this is actually the default already 95 | } 96 | 97 | msgBox->show(); 98 | return app.exec(); 99 | } 100 | \endcode 101 | 102 | [1]: https://stackoverflow.com/questions/58145272/qdialog-with-rounded-corners-have-black-corners-instead-of-being-translucent/58151965#58151965 103 | */ 104 | class RoundedMessageBox : public QMessageBox 105 | { 106 | Q_OBJECT 107 | public: 108 | explicit RoundedMessageBox(QWidget *parent = nullptr) : 109 | QMessageBox(parent) 110 | { 111 | // The FramelessWindowHint flag and WA_TranslucentBackground attribute are vital. 112 | setWindowFlags(windowFlags() | Qt::FramelessWindowHint | Qt::WindowSystemMenuHint); 113 | setAttribute(Qt::WA_TranslucentBackground); 114 | } 115 | 116 | qreal radius = 0.0; //!< desired radius in absolute pixels 117 | qreal borderWidth = -1.0; //!< -1 = use style hint frame width; 0 = no border; > 0 = use this width. 118 | 119 | protected: 120 | void paintEvent(QPaintEvent *) override 121 | { 122 | if (!(windowFlags() & Qt::FramelessWindowHint) && !testAttribute(Qt::WA_TranslucentBackground)) 123 | return; // nothing to do 124 | 125 | QPainter p(this); 126 | p.setRenderHint(QPainter::Antialiasing); 127 | 128 | // Have style sheet? 129 | if (testAttribute(Qt::WA_StyleSheetTarget)) { 130 | // Let QStylesheetStyle have its way with us. 131 | QStyleOption opt; 132 | opt.initFrom(this); 133 | style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this); 134 | p.end(); 135 | return; 136 | } 137 | 138 | // Paint thyself. 139 | QRectF rect(QPointF(0, 0), size()); 140 | // Check for a border size. 141 | qreal penWidth = borderWidth; 142 | if (penWidth < 0.0) { 143 | QStyleOption opt; 144 | opt.initFrom(this); 145 | penWidth = style()->pixelMetric(QStyle::PM_DefaultFrameWidth, &opt, this); 146 | } 147 | // Got pen? 148 | if (penWidth > 0.0) { 149 | p.setPen(QPen(palette().brush(foregroundRole()), penWidth)); 150 | // Ensure border fits inside the available space. 151 | const qreal dlta = penWidth * 0.5; 152 | rect.adjust(dlta, dlta, -dlta, -dlta); 153 | } 154 | else { 155 | // QPainter comes with a default 1px pen when initialized on a QWidget. 156 | p.setPen(Qt::NoPen); 157 | } 158 | // Set the brush from palette role. 159 | p.setBrush(palette().brush(backgroundRole())); 160 | // Got radius? Otherwise draw a quicker rect. 161 | if (radius > 0.0) 162 | p.drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize); 163 | else 164 | p.drawRect(rect); 165 | 166 | // C'est finí 167 | p.end(); 168 | } 169 | }; 170 | 171 | #endif // ROUNDEDMESSAGEBOX_H 172 | -------------------------------------------------------------------------------- /examples/imageviewer/imageviewer.css: -------------------------------------------------------------------------------- 1 | 2 | /* Toobar buttons */ 3 | Toolbar QToolButton { 4 | font: bold normal 14px sans-serif; 5 | color: #62777F; 6 | background: transparent; 7 | border-radius: 12px; 8 | padding: 3px 6px 4px; 9 | max-height: 1.2em; 10 | } 11 | Toolbar QToolButton:checked, 12 | Toolbar QToolButton:hover { 13 | color: #D5F2E5; 14 | background-color: #62777F; 15 | } 16 | Toolbar QToolButton:pressed { background-color: #72AF95; } 17 | Toolbar QToolButton:disabled { color: gray; } 18 | Toolbar[orientation='2'] QToolButton { width: 100%; max-width: 4em; } 19 | 20 | /* split buttons */ 21 | Toolbar QToolButton[align='left'], Toolbar QToolButton[align='middle'] { 22 | border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: .5px solid gray; } 23 | Toolbar QToolButton[align='right'], Toolbar QToolButton[align='middle'] { 24 | border-top-left-radius: 0; border-bottom-left-radius: 0; border-left: .5px solid gray; } 25 | Toolbar QToolButton[align='middle'] { padding-left: 0; padding-right: 0; } 26 | 27 | /* menu buttons */ 28 | Toolbar QToolButton[popupMode='2'] { padding-right: 15px; } 29 | Toolbar QToolButton[popupMode='2'][align='left'] { padding-left: 15px; padding-right: 9px; } 30 | Toolbar QToolButton[popupMode='2'][align='right'] { padding-left: 9px; } 31 | Toolbar[orientation='2'] QToolButton[popupMode='2'] { padding-right: 6px; padding-left: 0; } 32 | Toolbar[orientation='2'] QToolButton[popupMode='2'][align='left'] { padding-left: 9px; padding-right: 0; } 33 | Toolbar[orientation='2'] QToolButton[popupMode='2'][align='right'] { padding-right: 9px; padding-left: 0; } 34 | Toolbar QToolButton::menu-indicator { subcontrol-origin: border; subcontrol-position: bottom right; right: 3px; } 35 | Toolbar QToolButton[align='left']::menu-indicator { subcontrol-position: bottom left; left: 3px; right: 0; } 36 | 37 | /* Toobar separator */ 38 | Toolbar #tb_separator { background-color: palette(dark); } 39 | Toolbar[orientation='1'] #tb_separator { min-width: 1px; min-height: 2ex; margin: 1ex 0; } 40 | Toolbar[orientation='2'] #tb_separator { min-height: 1px; min-width: 2ex; margin: 0 1ex; } 41 | 42 | /* Toobar style */ 43 | Toolbar { 44 | margin: 0; 45 | qproperty-spacing: 14; 46 | qproperty-hSizePolicy: 1; /* QSizePolicy::Minimum */ 47 | qproperty-vSizePolicy: 1; 48 | } 49 | Toolbar[orientation='2'] { qproperty-spacing: 20; } 50 | Toolbar[edge='1'] { qproperty-alignment: 36; } /* top: AlignHCenter|AlignTop */ 51 | Toolbar[edge='2'] { qproperty-alignment: 33; } /* left: AlignLeft|AlignTop; */ 52 | Toolbar[edge='4'] { qproperty-alignment: 34; } /* right: AlignRogjt|AlignTop; */ 53 | Toolbar[edge='8'] { qproperty-alignment: 68; } /* bottom: AlignHCenter|AlignBottom; */ 54 | 55 | /* Toobar Gradient background theme */ 56 | Toolbar[theme='0'][orientation='1'] { qproperty-hSizePolicy: 7; } /* QSizePolicy::Expanding */ 57 | Toolbar[theme='0'][orientation='2'] { qproperty-vSizePolicy: 7; } /* QSizePolicy::Expanding */ 58 | Toolbar[theme='0'][edge='1'] { 59 | padding: 14px 3px 28px 3px; 60 | background: qlineargradient(x1:0,y1:0,x2:0,y2:1, stop: 0 black, stop: 1 transparent); 61 | } 62 | Toolbar[theme='0'][edge='2'] { 63 | padding: 14px 28px 14px 14px; 64 | background: qlineargradient(x1:0,y1:0,x2:1,y2:0, stop: 0 black, stop: 1 transparent); 65 | } 66 | Toolbar[theme='0'][edge='4'] { 67 | padding: 14px 14px 14px 28px; 68 | background: qlineargradient(x1:1,y1:0,x2:0,y2:0, stop: 0 black, stop: 1 transparent); 69 | } 70 | Toolbar[theme='0'][edge='8'] { 71 | padding: 28px 3px 14px 3px; 72 | background: qlineargradient(x1:0,y1:1,x2:0,y2:0, stop: 0 black, stop: 1 transparent); 73 | } 74 | Toolbar[theme='0'] QToolButton { color: #D5F2E5; background: #62777F; } 75 | Toolbar[theme='0'] QToolButton:checked, 76 | Toolbar[theme='0'] QToolButton:hover:!pressed { color: #62777F; background: #D5F2E5; } 77 | Toolbar[theme='0'] QToolButton:disabled { background: #8862777F; } 78 | 79 | /* Toobar Solid background theme */ 80 | Toolbar[theme='1'] { 81 | background: qlineargradient(x1:0,y1:0,x2:0,y2:1, stop: 0 #ACBFB6, stop: .4 #E5F9F1, stop: .5 #E3F7F7, stop: .6 #E5F9F1, stop: 1 #ACBFB6); 82 | border-radius: 14px; 83 | padding: 4px; 84 | } 85 | Toolbar[theme='1'][orientation='2'] { 86 | background: qlineargradient(x1:0,y1:0,x2:1,y2:0, stop: 0 #ACBFB6, stop: .3 #E5F9F1, stop: .5 #E3F7F7, stop: .7 #E5F9F1, stop: 1 #ACBFB6); 87 | } 88 | Toolbar[theme='1'][edge='1'] { margin-top: 10px; } 89 | Toolbar[theme='1'][edge='2'] { margin-top: 10px; margin-left: 10px; } 90 | Toolbar[theme='1'][edge='4'] { margin-top: 10px; margin-right: 10px; } 91 | Toolbar[theme='1'][edge='8'] { margin-bottom: 10px; } 92 | 93 | /* Toobar for main menu, just used as transparent container. */ 94 | Toolbar[theme='2'] { 95 | background: transparent; 96 | border-radius: 24px; 97 | padding: 1px; 98 | border: 0; margin: 0; 99 | min-width: 48px; 100 | min-height: 48px; 101 | qproperty-spacing: 0; 102 | } 103 | /* Main menu floating button */ 104 | Toolbar[theme='2'] QToolButton { 105 | font: 900 normal 24px monospace; 106 | padding: 0; 107 | border-radius: 24px; 108 | min-width: 48px; 109 | min-height: 48px; 110 | color: #F3EFF7; 111 | background: #991CB3EA; 112 | } 113 | Toolbar[theme='2'] QToolButton:hover { background: #BB731EEA; } 114 | Toolbar[theme='2'] QToolButton:pressed { background: #CCAE1EE8;} 115 | Toolbar[theme='2'] QToolButton::menu-indicator { image: null; } 116 | 117 | /* Floating "toast" message box */ 118 | Infobox { 119 | border: 0; 120 | border-radius: 12px; 121 | background: #88000000; 122 | color: palette(highlighted-text); 123 | padding: 10px 20px; 124 | font-size: 16px; 125 | } 126 | 127 | /* Image grid style */ 128 | ImageGrid { padding: 24px; padding-right: 0; } 129 | ImageGrid QScrollBar:vertical { 130 | width: 14px; 131 | padding: 2px; 132 | border-radius: 6px; 133 | background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0.0 #121212, stop: 0.5 #282828, stop: 1 #121212); 134 | } 135 | ImageGrid QScrollBar::handle:vertical { 136 | min-height: 20px; 137 | border-radius: 5px; 138 | background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #383838, stop: 0.3 #0A516D, stop: 0.7 #0A516D, stop: 1 #383838); 139 | } 140 | ImageGrid QScrollBar::add-page:vertical, 141 | ImageGrid QScrollBar::add-line:vertical, 142 | ImageGrid QScrollBar::sub-page:vertical, 143 | ImageGrid QScrollBar::sub-line:vertical { 144 | background: none; 145 | } 146 | 147 | #ImageGridView_toolbar[edge='4'] { 148 | margin-right: 16px; /* for scrollbar */ 149 | } 150 | -------------------------------------------------------------------------------- /doc/qt-tagfiles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __copyright__ = """ 3 | COPYRIGHT: (c)2019 Maxim Paperno; All Rights Reserved. 4 | 5 | Released under MIT License: 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | the Software, and to permit persons to whom the Software is furnished to do so, 12 | subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 19 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 20 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 21 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 22 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | """ 24 | 25 | DESCRIPTION = """ 26 | A utility to find Doxygen-compatible .tags files in a Qt Docs installation 27 | folder and either concatenate them all into one large .tags file (for online 28 | linking), or copy them all to a single folder (for QHP generation with locally 29 | linked references). This simplifies use with Doxygen's TAGFILES feature since 30 | the tagfiles can be referenced from a constant location relative to the other 31 | Doxygen-related assets. 32 | 33 | This utility also fixes enumeration tags which are borked as per QTBUG-61790. 34 | The fixes are applied to the file copy(ies), the originals are not modified. 35 | """ 36 | 37 | HELPTEXT = """ 38 | argument should point to a Qt documentation installation folder, 39 | for example "/usr/qt/Docs/Qt-5.12.6" or "c:\Qt\Docs\Qt-5.12.6". 40 | 41 | If the docs root argument is not provided, then the value of environment 42 | variables QT_DIR and QTHOME are checked (if both are found, the former takes 43 | precedence). If a valid path was found, it is searched for a "Docs" subfolder 44 | which contains subfolders in the format of "Qt-5.#.#" (where the numbers 45 | represent a Qt version). The newest, or only, version subfolder found is then 46 | used as the Qt docs root. 47 | 48 | The list of modules to import documentation from is essentially a list of 49 | subfolder(s) of this documentation root. Each module folder should contain an 50 | identically named .tags file (eg. /Docs/Qt-5.12.6/qtcore/qtcore.tags). 51 | """ 52 | 53 | # Default module set, only import tags for ones we're very likely to need. 54 | QTMODULES = [ 55 | "qtcore", 56 | # "qtbluetooth", 57 | # "qtconcurrent", 58 | # "qtdbus", # no tagfile 59 | "qtgui", 60 | # "qtnetwork", 61 | "qtprintsupport", 62 | # "qtqml", 63 | # "qtquick", 64 | # "qtquickcontrols", 65 | # "qtsvg", 66 | "qtwidgets", 67 | ] 68 | 69 | # Default output path/file (only the path part is used when copying with -i opt) 70 | OUTPUT_DEST = "tagfiles/qt.tags" 71 | 72 | import argparse 73 | import glob 74 | import os 75 | import re 76 | import sys 77 | from shutil import copy2 78 | from qttagsfix import fixEnums 79 | 80 | def copyFiles(modules, docsdir, destpath): 81 | for module in modules: 82 | fn = module + ".tags" 83 | tagfile = os.path.join(docsdir, module, fn) 84 | if os.path.isfile(tagfile): 85 | print("Copying tag file: " + tagfile) 86 | copy2(tagfile, destpath) 87 | else: 88 | print("Could not find "+tagfile+", skipping.") 89 | doEnumsFix(os.path.join(destpath, fn)) 90 | return 0 91 | 92 | 93 | def concatFiles(modules, docsdir, destfile): 94 | # get Qt version, only for a comment in the created tags file, not vital 95 | qtver = os.getenv("QT_VERSION", os.path.basename(os.path.normpath(docsdir))) 96 | qtver = re.sub("[^\d\.]", "", qtver) 97 | if not qtver: 98 | qtver = "(unknown)" 99 | with open(destfile, "w", encoding="utf-8") as f: 100 | # write an opening header with inserted comment 101 | f.write('\n') 102 | f.write('\n') 103 | f.write("" % (modules, qtver)) 105 | for module in modules: 106 | tagfile = os.path.join(docsdir, module, module + ".tags") 107 | if not os.path.isfile(tagfile): 108 | print("Could not find "+tagfile+", skipping.") 109 | continue 110 | with (open(tagfile, "r", encoding="utf-8")) as tf: 111 | print("Adding tag file: " + tagfile) 112 | f.write("\n\n\n\n" % os.path.basename(tagfile)) 113 | tagtext = tf.read() 114 | # strip opening header tags from input files 115 | tagtext = tagtext[re.search('\n', tagtext).end():] 116 | # strip closing tag also, but not from the last file 117 | tagtext = tagtext[:tagtext.rfind('\n')] 118 | f.write(tagtext) 119 | f.write('\n') 120 | doEnumsFix(destfile) 121 | return 0 122 | 123 | def doEnumsFix(infile): 124 | print("Fixing up enums in tagfile: " + infile) 125 | fixEnums(infile) 126 | 127 | def main(): 128 | parser = argparse.ArgumentParser( 129 | formatter_class=argparse.RawDescriptionHelpFormatter, 130 | description=DESCRIPTION, epilog=HELPTEXT) 131 | 132 | parser.add_argument("docsroot", nargs='?', metavar="", 133 | help="Qt documentation installation folder.") 134 | parser.add_argument("-m", nargs="*", metavar="", default=QTMODULES, 135 | help="Qt module(s) to import (default: %(default)s).") 136 | parser.add_argument("-i", default=False, action="store_true", 137 | help="Copy individual files instead of concatenating.") 138 | parser.add_argument("-o", metavar="", default=OUTPUT_DEST, 139 | help="Output file (if concatenating) or folder (if copying with -i) (default: %(default)s).") 140 | 141 | opts = parser.parse_args(); 142 | 143 | if not len(opts.m): 144 | sys.exit("Empty modules list, nothing to import.") 145 | 146 | destfile = os.path.normpath(opts.o) 147 | destpath = destfile 148 | if not opts.i: 149 | destpath = os.path.dirname(destfile) 150 | if not destfile or not os.access(destpath, os.W_OK): 151 | sys.exit("Unable access output location %s (%s)" % (destfile, destpath)) 152 | print("Output to: " + destfile) 153 | 154 | docsdir = "" 155 | if opts.docsroot: 156 | docsdir = opts.docsroot 157 | else: 158 | # try to find docs folder based on QTHOME or QT_DIR env. vars 159 | qtdir = os.getenv("QT_DIR", os.getenv('QTHOME', "")) 160 | if qtdir: 161 | qtdir = os.path.join(qtdir, "Docs", "Qt-5*") 162 | dirs = list(filter(os.path.isdir, glob.glob(qtdir))) 163 | if len(dirs): 164 | dirs.sort(key = lambda x: os.path.getctime(x), reverse = True) 165 | docsdir = dirs[0] 166 | 167 | if not docsdir or not os.path.isdir(docsdir) or not os.access(docsdir, os.R_OK): 168 | sys.exit("Unable to determine or access Qt docs directory: " + docsdir) 169 | 170 | print("Using source directory: %s" % docsdir) 171 | 172 | if opts.i: 173 | # just copy individual files 174 | return copyFiles(opts.m, docsdir, destpath) 175 | 176 | # concatenate tagfiles into one 177 | return concatFiles(opts.m, docsdir, destfile) 178 | 179 | 180 | if __name__ == "__main__": 181 | sys.exit(main()) 182 | -------------------------------------------------------------------------------- /examples/imageviewer/ImageViewer.h: -------------------------------------------------------------------------------- 1 | /* 2 | ImageViewer 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Rights Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef IMAGEVIEWER_H 29 | #define IMAGEVIEWER_H 30 | 31 | #include 32 | #include 33 | #include 34 | 35 | class OverlayStackLayout; 36 | class GraphicsImageView; 37 | class ImageGrid; 38 | QT_BEGIN_NAMESPACE 39 | class QAction; 40 | class QActionGroup; 41 | class QMenu; 42 | class QSpacerItem; 43 | class QToolButton; 44 | QT_END_NAMESPACE 45 | 46 | //! \ingroup examples_imageviewer 47 | class Toolbar : public QFrame 48 | { 49 | Q_OBJECT 50 | Q_PROPERTY(Qt::Orientation orientation READ orientation WRITE setOrientation NOTIFY orientationChanged) 51 | Q_PROPERTY(Qt::Edge edge READ edge WRITE setEdge NOTIFY edgeChanged) 52 | Q_PROPERTY(int alignment READ alignment WRITE setAlignment NOTIFY alignmentChanged) 53 | Q_PROPERTY(int theme READ theme WRITE setTheme NOTIFY themeChanged) 54 | Q_PROPERTY(int spacing READ spacing WRITE setSpacing) 55 | Q_PROPERTY(int hSizePolicy READ hSizePolicy WRITE setHSizePolicy) 56 | Q_PROPERTY(int vSizePolicy READ vSizePolicy WRITE setVSizePolicy) 57 | 58 | public: 59 | explicit Toolbar(QWidget *p = nullptr); 60 | 61 | int theme() const { return m_theme; } 62 | int spacing() const { return m_lo->spacing(); } 63 | Qt::Edge edge() const { return m_edge; } 64 | Qt::Orientation orientation() const { return m_o; } 65 | int alignment() const { return m_align; } 66 | int hSizePolicy() const { return sizePolicy().horizontalPolicy(); } 67 | int vSizePolicy() const { return sizePolicy().verticalPolicy(); } 68 | 69 | QToolButton *addButton(QAction *act, QLayout *lo, Qt::Alignment a = Qt::AlignCenter, const QKeySequence &sc = QKeySequence()); 70 | QToolButton *addButton(const QString &ttl, const QString &tip, QLayout *lo, Qt::Alignment a, const QKeySequence &sc = QKeySequence()); 71 | QToolButton *addButton(const QString &ttl, const QString &tip, const QKeySequence &sc = QKeySequence()) { return addButton(ttl, tip, m_lo, Qt::AlignCenter, sc); } 72 | QMenu *addMenuButton(const QString &ttl, const QString &tip = QString(), QLayout *lo = nullptr, Qt::Alignment a = Qt::AlignCenter); 73 | QMenu *makeMenuButton(QToolButton *tb, const QString &name = QString()) const; 74 | 75 | QVector addButtonSet(const QStringList &txts, const QStringList &tts, const QVector &scuts = QVector()); 76 | QAction *addGroupAction(const QString &ttl, QActionGroup *grp, const QVariant &data = QVariant(), bool chkd = false); 77 | QWidget *addSeparator() const; 78 | QSpacerItem *addSpacer(int w = 0, int h = 0, QSizePolicy::Policy hPol = QSizePolicy::Expanding, QSizePolicy::Policy vPol = QSizePolicy::Expanding) const; 79 | QSpacerItem *addHSpacer() { return addSpacer(0, 0, QSizePolicy::Expanding, QSizePolicy::Ignored); } 80 | QSpacerItem *addVSpacer() { return addSpacer(0, 0, QSizePolicy::Ignored, QSizePolicy::Expanding); } 81 | 82 | public slots: 83 | void setOrientation(Qt::Orientation o); 84 | void setAlignment(int a); 85 | void setVertical(bool on) { setOrientation(on ? Qt::Vertical : Qt::Horizontal); } 86 | void setHorizontal(bool on) { setVertical(!on); } 87 | void setEdge(Qt::Edge edge); 88 | void setTheme(int idx); 89 | void toggleTheme(bool on) { setTheme(int(on)); } 90 | void setSpacing(int spacing) { m_lo->setSpacing(spacing); } 91 | void setHSizePolicy(int pol) { QSizePolicy p = sizePolicy(); p.setHorizontalPolicy(QSizePolicy::Policy(pol)); setSizePolicy(p); } 92 | void setVSizePolicy(int pol) { QSizePolicy p = sizePolicy(); p.setVerticalPolicy(QSizePolicy::Policy(pol)); setSizePolicy(p); } 93 | 94 | signals: 95 | void orientationChanged(Qt::Orientation o) const; 96 | void edgeChanged(Qt::Edge edge) const; 97 | void themeChanged() const; 98 | void alignmentChanged(Qt::Alignment align) const; 99 | 100 | private: 101 | Qt::Orientation m_o = Qt::Horizontal; 102 | Qt::Edge m_edge = Qt::TopEdge; 103 | Qt::Alignment m_align = Qt::AlignLeft | Qt::AlignTop; 104 | int m_theme = 0; 105 | QBoxLayout *m_lo; 106 | 107 | Q_DISABLE_COPY(Toolbar) 108 | }; 109 | 110 | 111 | #include 112 | #include 113 | 114 | QT_BEGIN_NAMESPACE 115 | class QGraphicsOpacityEffect; 116 | class QPropertyAnimation; 117 | class QTimerEvent; 118 | QT_END_NAMESPACE 119 | 120 | //! \ingroup examples_imageviewer 121 | class Infobox : public QLabel 122 | { 123 | Q_OBJECT 124 | Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) 125 | 126 | public: 127 | explicit Infobox(QWidget *p); 128 | qreal opacity() const; 129 | 130 | public slots: 131 | void showMessage(const QString &msg = QString(), int timeout = 5000); 132 | void setOpacity(qreal opacity); 133 | void fade(bool out = true, int duration = -1); 134 | void fadeIn() { fade(false, -1); } 135 | void fadeIn(int duration) { fade(false, duration); } 136 | void fadeOut() { fade(true, -1); } 137 | void fadeOut(int duration) { fade(true, duration); } 138 | 139 | signals: 140 | void opacityChanged(qreal) const; 141 | 142 | protected: 143 | void timerEvent(QTimerEvent *e) override; 144 | 145 | private: 146 | QGraphicsOpacityEffect *m_opacity; 147 | QPropertyAnimation *m_fade; 148 | QBasicTimer m_msgTim; 149 | }; 150 | 151 | 152 | //! \ingroup examples_imageviewer 153 | class ImageViewer : public QWidget 154 | { 155 | Q_OBJECT 156 | public: 157 | explicit ImageViewer(const QString &appCssFile, const QString &imagePath = QString(), QWidget *p = nullptr); 158 | 159 | public slots: 160 | void setImagesPath(const QString &path) const; 161 | void loadImage(const QString &img) const; 162 | void setAppCssFile(const QString &filename); 163 | 164 | signals: 165 | void themeChanged(int theme) const; 166 | void toolbarEdgeChanged(Qt::Edge edge) const; 167 | 168 | private slots: 169 | void showFolderInfo(int count) const; 170 | void showImageInfo(const QString &img) const; 171 | void selectFolder() const; 172 | void setTheme(bool trans); 173 | void updateTheme() const; 174 | void loadStylesheet() const; 175 | void setPaletteColors(bool light = false); 176 | void updateViewPalette() const; 177 | 178 | private: 179 | void setupOptionsMenu(); 180 | Toolbar *createPage(QWidget *main); 181 | void setupListViewPage(); 182 | void setupImageViewPage(); 183 | 184 | int imgInfoTimeout = 6000; 185 | QString appCssFile; 186 | Infobox *msgBox; 187 | ImageGrid *listView; 188 | GraphicsImageView *imageView; 189 | OverlayStackLayout *stackLo; 190 | 191 | Q_DISABLE_COPY(ImageViewer) 192 | }; 193 | 194 | #endif // IMAGEVIEWER_H 195 | -------------------------------------------------------------------------------- /src/widgets/TimerEdit.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | TimerEdit 3 | 4 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 5 | Contact: http://www.WorldDesign.com/contact 6 | 7 | LICENSE: 8 | 9 | Commercial License Usage 10 | Licensees holding valid commercial licenses may use this file in 11 | accordance with the terms contained in a written agreement between 12 | you and the copyright holder. 13 | 14 | GNU General Public License Usage 15 | Alternatively, this file may be used under the terms of the GNU 16 | General Public License as published by the Free Software Foundation, 17 | either version 3 of the License, or (at your option) any later version. 18 | 19 | This program is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | A copy of the GNU General Public License is available at . 25 | */ 26 | 27 | #include "TimerEdit.h" 28 | #include 29 | 30 | TimerEdit::TimerEdit(QWidget *parent) : 31 | QLineEdit(parent), 32 | m_showSeconds(true), 33 | m_minTime(0), 34 | m_maxTime(24 * 3600 - 1), 35 | m_singleStep(1), 36 | m_pageStep(60), 37 | m_hourDigits(2), 38 | m_validator(new QRegularExpressionValidator(this)) 39 | { 40 | setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed); 41 | setupFormat(); 42 | setToolTip(tr("Use mouse scroll or UP/DOWN arrow keys to change time in small steps.\nCTRL + scroll or PAGE UP/DOWN keys to change time in larger steps.")); 43 | connect(this, &TimerEdit::textEdited, this, &TimerEdit::textEditedHandler); 44 | } 45 | 46 | int TimerEdit::timeInSeconds() const 47 | { 48 | int ret = 0; 49 | 50 | ret += timepart(text(), TimeParts::Hours) * 3600; 51 | ret += timepart(text(), TimeParts::Minutes) * 60; 52 | ret += timepart(text(), TimeParts::Seconds); 53 | ret *= timepart(text(), TimeParts::Polarity); 54 | 55 | return ret; 56 | } 57 | 58 | int TimerEdit::timepart(const QString &input, TimerEdit::TimeParts part) const 59 | { 60 | int ret = 0; 61 | QRegularExpressionMatch match = m_validator->regularExpression().match(input, 0, QRegularExpression::NormalMatch); 62 | 63 | if (match.hasMatch()) { 64 | switch (part) { 65 | case Hours: 66 | if (!match.captured("hrs").isEmpty()) 67 | ret = match.captured("hrs").toInt(); 68 | break; 69 | 70 | case Minutes: 71 | if (!match.captured("mins").isEmpty()) 72 | ret = match.captured("mins").toInt(); 73 | break; 74 | 75 | case Seconds: 76 | if (!match.captured("secs").isEmpty()) 77 | ret = match.captured("secs").toInt(); 78 | break; 79 | 80 | case Polarity: 81 | if (!match.captured("pol").isEmpty() && !match.captured("pol").compare("-")) 82 | ret = -1; 83 | else 84 | ret = 1; 85 | break; 86 | 87 | default: 88 | break; 89 | } 90 | } 91 | 92 | return ret; 93 | } 94 | 95 | 96 | //// Slots //// 97 | 98 | void TimerEdit::setTime(int seconds) 99 | { 100 | if (seconds > m_maxTime) 101 | seconds = m_maxTime; 102 | else if (seconds < m_minTime) 103 | seconds = m_minTime; 104 | 105 | div_t qrM = div(abs(seconds), 60); 106 | div_t qrH = div(qrM.quot, 60); 107 | 108 | QString val = QString("%1").arg(qrH.rem, 2, 10, QChar('0')); 109 | 110 | if (m_hourDigits) 111 | val.prepend(QString("%1:").arg(qrH.quot, m_hourDigits, 10, QChar('0'))); 112 | 113 | if (m_showSeconds) 114 | val.append(QString(":%1").arg(qrM.rem, 2, 10, QChar('0'))); 115 | 116 | if (m_minTime < 0) { 117 | if (seconds < 0) 118 | val.prepend("-"); 119 | else 120 | val.prepend(" "); 121 | } 122 | 123 | if (text().compare(val)) { 124 | setText(val); 125 | } 126 | } 127 | 128 | void TimerEdit::incrDecr(int seconds) 129 | { 130 | if (!seconds) 131 | return; 132 | 133 | const int cs = timeInSeconds(); 134 | int s = seconds + cs; 135 | if (s < m_minTime) 136 | s = m_minTime; 137 | if (s > m_maxTime) 138 | s = m_maxTime; 139 | if (s != cs) { 140 | setTime(s); 141 | emitValueChanged(); 142 | } 143 | } 144 | 145 | void TimerEdit::setTimeRange(int minSeconds, int maxSeconds) 146 | { 147 | if (minSeconds <= maxSeconds && (m_minTime != minSeconds || m_maxTime != maxSeconds)) { 148 | int digits = 0; 149 | if (maxSeconds >= 3600) 150 | digits = floorf(logf(round(maxSeconds / 3600)) / logf(10.0f)) + 1; 151 | 152 | bool mod = (m_hourDigits != digits || std::signbit((float)m_minTime) != std::signbit((float)minSeconds)); 153 | 154 | m_minTime = minSeconds; 155 | m_maxTime = maxSeconds; 156 | m_hourDigits = digits; 157 | 158 | if (mod) 159 | setupFormat(); 160 | } 161 | } 162 | 163 | void TimerEdit::setMinimumTime(int minSeconds) 164 | { 165 | setTimeRange(minSeconds, (m_maxTime > minSeconds ? m_maxTime : minSeconds)); 166 | } 167 | 168 | void TimerEdit::setMaximumTime(int maxSeconds) 169 | { 170 | setTimeRange((m_minTime < maxSeconds ? m_minTime : maxSeconds), maxSeconds); 171 | } 172 | 173 | void TimerEdit::setShowSeconds(bool showSeconds) 174 | { 175 | if (m_showSeconds != showSeconds) { 176 | m_showSeconds = showSeconds; 177 | setupFormat(); 178 | } 179 | } 180 | 181 | 182 | //// Protected //// 183 | 184 | void TimerEdit::textEditedHandler() 185 | { 186 | int sec = timeInSeconds(); 187 | if (sec < m_minTime) 188 | setTime(m_minTime); 189 | else if (sec > m_maxTime) 190 | setTime(m_maxTime); 191 | else 192 | return; 193 | 194 | emitValueChanged(); 195 | } 196 | 197 | void TimerEdit::setupFormat() 198 | { 199 | QString inputRe = "(?[0-5][0-9])"; 200 | QString inputMsk = "99"; 201 | QString suffx = "\\" % tr("m") % "\\" % tr("m"); 202 | 203 | if (m_hourDigits) { 204 | inputRe.prepend("(?[0-9]*[0-9]):"); 205 | inputMsk.prepend(":"); 206 | suffx.prepend(":"); 207 | for (int i=0; i < m_hourDigits; ++i) { 208 | inputMsk.prepend("9"); 209 | suffx.prepend("\\" % tr("h")); 210 | } 211 | } 212 | if (m_showSeconds) { 213 | inputRe.append(":(?[0-5][0-9])"); 214 | inputMsk.append(":99"); 215 | suffx.append(":\\" % tr("s") % "\\" % tr("s")); 216 | } 217 | if (m_minTime < 0) { 218 | inputRe.prepend("(?-|\\+|\\s)?"); 219 | inputMsk.prepend("#"); 220 | } 221 | 222 | inputRe.prepend("^"); 223 | inputRe.append("\\s?\\[[^\\]]+\\]$"); 224 | inputMsk.append(" \\[" % suffx % "\\]"); 225 | 226 | m_validator->setRegularExpression(QRegularExpression(inputRe)); 227 | 228 | setInputMask(inputMsk); 229 | setValidator(m_validator); 230 | } 231 | 232 | void TimerEdit::emitValueChanged() 233 | { 234 | emit QLineEdit::textEdited(text()); 235 | } 236 | 237 | void TimerEdit::keyPressEvent(QKeyEvent *event) 238 | { 239 | switch (event->key()) { 240 | case Qt::Key_Up: 241 | incrDecr(m_singleStep); 242 | break; 243 | 244 | case Qt::Key_Down: 245 | incrDecr(-(int)m_singleStep); 246 | break; 247 | 248 | case Qt::Key_PageUp: 249 | incrDecr(m_pageStep); 250 | break; 251 | 252 | case Qt::Key_PageDown: 253 | incrDecr(-(int)m_pageStep); 254 | break; 255 | 256 | case Qt::Key_Plus: 257 | case Qt::Key_Equal: 258 | case Qt::Key_Minus: 259 | if (m_minTime < 0) { 260 | setTime(-timeInSeconds()); 261 | emitValueChanged(); 262 | } 263 | break; 264 | 265 | default: 266 | QLineEdit::keyPressEvent(event); 267 | break; 268 | } 269 | } 270 | 271 | void TimerEdit::wheelEvent(QWheelEvent *event) 272 | { 273 | if (event->angleDelta().isNull()) { 274 | event->ignore(); 275 | return; 276 | } 277 | int numSteps = -event->angleDelta().y() / 8 / 15 * -1; // one step per 15deg 278 | numSteps *= (event->modifiers() & Qt::ControlModifier) ? m_pageStep : m_singleStep; 279 | incrDecr(numSteps); 280 | event->accept(); 281 | } 282 | -------------------------------------------------------------------------------- /src/quick/maxLibQt/controls/tests.qml: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of maxLibQt 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2018 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | import QtQuick 2.10 29 | import QtQuick.Controls 2.3 30 | import QtQuick.Layouts 1.3 31 | 32 | /* 33 | Testing page for maxLibQt QtQuick Controls components. 34 | */ 35 | 36 | ScrollView { 37 | id: root 38 | width: 400 39 | height: 650 40 | padding: 9 41 | 42 | ColumnLayout { 43 | id: sbTests 44 | spacing: 6 45 | clip: true 46 | 47 | property bool editable: true 48 | property bool wrap: true 49 | property bool useLocaleFormat: true 50 | property bool showGroupSeparator: true 51 | 52 | function setupSb(sb) { 53 | sb.editable = Qt.binding(function() { return sbTests.editable; }); 54 | sb.wrap = Qt.binding(function() { return sbTests.wrap; }); 55 | sb.useLocaleFormat = Qt.binding(function() { return sbTests.useLocaleFormat; }); 56 | sb.showGroupSeparator = Qt.binding(function() { return sbTests.showGroupSeparator; }); 57 | //sb.Layout.fillWidth = true 58 | } 59 | 60 | RowLayout { 61 | CheckBox { 62 | text: "Editable" 63 | checked: sbTests.editable 64 | onToggled: sbTests.editable = !sbTests.editable 65 | } 66 | CheckBox { 67 | text: "Wrap" 68 | checked: sbTests.wrap 69 | onToggled: sbTests.wrap = !sbTests.wrap 70 | } 71 | CheckBox { 72 | text: "Group Sep." 73 | checked: sbTests.showGroupSeparator 74 | onToggled: sbTests.showGroupSeparator = !sbTests.showGroupSeparator 75 | } 76 | CheckBox { 77 | text: "Localize" 78 | checked: sbTests.useLocaleFormat 79 | onToggled: sbTests.useLocaleFormat = !sbTests.useLocaleFormat 80 | } 81 | } 82 | 83 | GroupBox { 84 | title: "MLDoubleSpinBox" 85 | Layout.fillWidth: true 86 | 87 | GridLayout { 88 | anchors.fill: parent 89 | columns: 2 90 | 91 | Label { text: "Default" } 92 | MLDoubleSpinBox { 93 | Component.onCompleted: sbTests.setupSb(this) 94 | } 95 | 96 | Label { text: "+/-9e6.4: s:.01; loc: en" } 97 | MLDoubleSpinBox { 98 | to: 9e6; from: -to 99 | decimals: 4 100 | value: 1234567.1234 101 | stepSize: 0.01 102 | locale: Qt.locale("en_US") 103 | Component.onCompleted: sbTests.setupSb(this) 104 | } 105 | 106 | Label { text: "+/-9e6.4; s:.01; loc: fr" } 107 | MLDoubleSpinBox { 108 | to: 9e6; from: -to 109 | decimals: 4 110 | value: 1234567.1234 111 | stepSize: 0.01 112 | locale: Qt.locale("fr") 113 | Component.onCompleted: sbTests.setupSb(this) 114 | } 115 | 116 | Label { text: "+/-9e6.4; s:.01; loc: de" } 117 | MLDoubleSpinBox { 118 | to: 9e6; from: -to 119 | decimals: 4 120 | value: 1234567.1234 121 | stepSize: 0.01 122 | locale: Qt.locale("de") 123 | Component.onCompleted: sbTests.setupSb(this) 124 | } 125 | 126 | Label { text: "+/-9e6.0" } 127 | MLDoubleSpinBox { 128 | to: 9e6; from: -to 129 | decimals: 0 130 | value: 10234567.123456 // invalid value, should auto-correct to 9e6 131 | Component.onCompleted: sbTests.setupSb(this) 132 | } 133 | 134 | Label { text: "+/-9e6.6; exp" } 135 | MLDoubleSpinBox { 136 | to: 9e6; from: -to 137 | decimals: 6 138 | value: 12.3456789 139 | notation: DoubleValidator.ScientificNotation 140 | stepSize: 0.1 141 | Component.onCompleted: sbTests.setupSb(this) 142 | } 143 | 144 | Label { text: "+/-1e4.2; s:.01; pfx/sfx basic" } 145 | MLDoubleSpinBox { 146 | to: 10000; from: -to 147 | value: 55.5 148 | stepSize: 0.01 149 | prefix: "foo: " 150 | suffix: " (bars)" 151 | Component.onCompleted: sbTests.setupSb(this) 152 | } 153 | 154 | Label { text: "+/-1e4.2; s:.01; pfx/sfx inpMask" } 155 | /* This is an example of using a custom input mask to create an editable spin box with prefix/suffix. 156 | It is somewhat generalized, but only supports standard notation, with no group separators. */ 157 | MLDoubleSpinBox { 158 | to: 10000; from: -to 159 | value: 55.5 160 | stepSize: 0.01 161 | notation: DoubleValidator.StandardNotation // inputMask and textFromValue do not work with exp. notation 162 | prefix: "foo: " 163 | suffix: "[bar" + (Math.abs(value) !== 1 ? "s" : "") + "]" 164 | inputMask: getInputMask() 165 | // We need to use the "experimental" RegExpValidator since DoubleValidator does not support masks. 166 | validator: regExpValidator 167 | Component.onCompleted: sbTests.setupSb(this) 168 | 169 | function textFromValue(value, locale) { 170 | locale = locale || effectiveLocale; 171 | var text = Math.abs(value).toLocaleString(locale, 'f', Math.max(decimals, 0)), 172 | dig = Math.ceil(Math.abs(topValue)).toString(10).length, 173 | len = Math.floor(Math.abs(value)).toString(10).length; 174 | 175 | text = Math.abs(value).toLocaleString(locale, 'f', Math.max(decimals, 0)); 176 | 177 | if (dig > len) 178 | text = "0".repeat(dig - len) + text; 179 | if (botValue < 0) 180 | text = (value < 0 ? "-" : "+") + text; 181 | 182 | if (prefix) 183 | text = prefix + text; 184 | if (suffix) 185 | text = text + suffix; 186 | 187 | return text; 188 | } 189 | 190 | function getInputMask() { 191 | var dig = Math.abs(Math.round(topValue)).toString(10).length - 1, 192 | mask = (botValue < 0 ? "#" : "") + "0".repeat(dig) + "9" + (decimals > 0 ? effectiveLocale.decimalPoint + "0".repeat(decimals) : ""); 193 | if (prefix) 194 | mask = escapeInputMaskChars(prefix) + mask; 195 | if (suffix) 196 | mask = mask + escapeInputMaskChars(suffix); 197 | return mask; 198 | } 199 | } 200 | 201 | RowLayout { 202 | Label { text: "+/-9e4.%1: regExp".arg(reBox.decimals) } 203 | CheckBox { id: exp; text: "exp"; checked: false } 204 | } 205 | // This version allows entry in both scientific and standard formats, and will convert to the specified notation after editing is finished. 206 | MLDoubleSpinBox { 207 | id: reBox 208 | to: 9e4; from: -to 209 | decimals: exp.checked ? 4 + 4 : 4 210 | value: 12345.1234 211 | notation: exp.checked ? DoubleValidator.ScientificNotation : DoubleValidator.StandardNotation 212 | validator: regExpValidator 213 | Component.onCompleted: sbTests.setupSb(this) 214 | } 215 | 216 | } // GridLayout 217 | } // GroupBox MLDoubleSpinBox 218 | 219 | GroupBox { 220 | title: "MLHexSpinBox" 221 | Layout.fillWidth: true 222 | 223 | GridLayout { 224 | anchors.fill: parent 225 | columns: 2 226 | 227 | Label { text: "Default" } 228 | MLHexSpinBox { 229 | Component.onCompleted: sbTests.setupSb(this) 230 | } 231 | 232 | Label { text: "to: 0xFFFF; pfx: false" } 233 | MLHexSpinBox { 234 | to: 0xFFFF 235 | showPrefix: false 236 | Component.onCompleted: sbTests.setupSb(this) 237 | } 238 | 239 | Label { text: "+/-0xFFFF; noPad, LCase" } 240 | MLHexSpinBox { 241 | to: 0xFFFF 242 | from: -to 243 | zeroPad: false 244 | upperCase: false 245 | Component.onCompleted: sbTests.setupSb(this) 246 | } 247 | 248 | } // GridLayout 249 | } // GroupBox MLHexSpinBox 250 | 251 | } // ColumnLayout 252 | } 253 | -------------------------------------------------------------------------------- /doc/DoxygenLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | -------------------------------------------------------------------------------- /doc/Doxyfile: -------------------------------------------------------------------------------- 1 | # Doxyfile 1.8.15 2 | 3 | PROJECT_NAME = maxLibQt 4 | PROJECT_BRIEF = 5 | PROJECT_LOGO = logo_64.png 6 | ABBREVIATE_BRIEF = "The $name class" \ 7 | "The $name widget" \ 8 | "The $name file" \ 9 | is \ 10 | provides \ 11 | specifies \ 12 | contains \ 13 | represents \ 14 | a \ 15 | an \ 16 | the 17 | 18 | ALWAYS_DETAILED_SEC = YES 19 | JAVADOC_AUTOBRIEF = YES 20 | QT_AUTOBRIEF = YES 21 | TAB_SIZE = 2 22 | ALIASES = "default{1}=\nDefault value is \c \1.\n" \ 23 | "reimp{1}=Reimplemented from \c \1." \ 24 | "reimp=Reimplemented from superclass." \ 25 | default= \ 26 | "pacc=\par Access functions:^^" \ 27 | "psig=\par Notifier signal:^^" \ 28 | "intern=\parInternal use only." 29 | EXTENSION_MAPPING = qml=C++ 30 | DISTRIBUTE_GROUP_DOC = YES 31 | INLINE_SIMPLE_STRUCTS = YES 32 | 33 | #--------------------------------------------------------------------------- 34 | # Build related configuration options 35 | #--------------------------------------------------------------------------- 36 | 37 | EXTRACT_ALL = YES 38 | EXTRACT_STATIC = YES 39 | EXTRACT_LOCAL_CLASSES = NO 40 | FORCE_LOCAL_INCLUDES = YES 41 | SORT_BRIEF_DOCS = YES 42 | SORT_MEMBERS_CTORS_1ST = YES 43 | SORT_GROUP_NAMES = YES 44 | SORT_BY_SCOPE_NAME = YES 45 | SHOW_USED_FILES = NO 46 | LAYOUT_FILE = DoxygenLayout.xml 47 | 48 | #--------------------------------------------------------------------------- 49 | # Configuration options related to warning and progress messages 50 | #--------------------------------------------------------------------------- 51 | 52 | QUIET = YES 53 | WARNINGS = YES 54 | WARN_IF_UNDOCUMENTED = NO 55 | WARN_IF_DOC_ERROR = YES 56 | WARN_NO_PARAMDOC = NO 57 | WARN_AS_ERROR = NO 58 | 59 | #--------------------------------------------------------------------------- 60 | # Configuration options related to the input files 61 | #--------------------------------------------------------------------------- 62 | 63 | INPUT = ../src 64 | RECURSIVE = YES 65 | 66 | FILE_PATTERNS = *.c \ 67 | *.cc \ 68 | *.cxx \ 69 | *.cpp \ 70 | *.c++ \ 71 | *.java \ 72 | *.ii \ 73 | *.ixx \ 74 | *.ipp \ 75 | *.i++ \ 76 | *.inl \ 77 | *.idl \ 78 | *.ddl \ 79 | *.odl \ 80 | *.h \ 81 | *.hh \ 82 | *.hxx \ 83 | *.hpp \ 84 | *.h++ \ 85 | *.cs \ 86 | *.d \ 87 | *.php \ 88 | *.php4 \ 89 | *.php5 \ 90 | *.phtml \ 91 | *.inc \ 92 | *.m \ 93 | *.markdown \ 94 | *.md \ 95 | *.mm \ 96 | *.dox \ 97 | *.py \ 98 | *.f90 \ 99 | *.f \ 100 | *.for \ 101 | *.tcl \ 102 | *.vhd \ 103 | *.vhdl \ 104 | *.ucf \ 105 | *.qsf \ 106 | *.as \ 107 | *.js \ 108 | *.txt \ 109 | *.qml 110 | 111 | EXCLUDE = 112 | EXCLUDE_PATTERNS = */.git/* \ 113 | */cmake/* \ 114 | */CMakeFiles/* \ 115 | CMakeLists.txt \ 116 | CMakeCache.txt \ 117 | */doc?/* \ 118 | moc_*.* \ 119 | _*.* \ 120 | *-?Copy*.* \ 121 | */*test* \ 122 | *_p.cpp \ 123 | *_p.h 124 | EXCLUDE_SYMBOLS = iterator const_iterator 125 | 126 | EXAMPLE_PATH = ../examples 127 | EXAMPLE_PATTERNS = *.h *.cpp 128 | EXAMPLE_RECURSIVE = YES 129 | 130 | FILTER_PATTERNS = *.qml=doxyqml 131 | 132 | USE_MDFILE_AS_MAINPAGE = README.md 133 | 134 | #--------------------------------------------------------------------------- 135 | # Configuration options related to source browsing 136 | #--------------------------------------------------------------------------- 137 | 138 | SOURCE_BROWSER = YES 139 | 140 | #--------------------------------------------------------------------------- 141 | # Configuration options related to the alphabetical class index 142 | #--------------------------------------------------------------------------- 143 | 144 | #--------------------------------------------------------------------------- 145 | # Configuration options related to the HTML output 146 | #--------------------------------------------------------------------------- 147 | 148 | GENERATE_HTML = YES 149 | HTML_OUTPUT = html 150 | HTML_HEADER = 151 | HTML_FOOTER = 152 | HTML_STYLESHEET = 153 | HTML_EXTRA_STYLESHEET = customdoxygen.css 154 | HTML_COLORSTYLE_HUE = 235 155 | HTML_COLORSTYLE_SAT = 19 156 | HTML_COLORSTYLE_GAMMA = 185 157 | HTML_TIMESTAMP = YES 158 | HTML_DYNAMIC_SECTIONS = YES 159 | 160 | GENERATE_TREEVIEW = YES 161 | ENUM_VALUES_PER_LINE = 1 162 | TREEVIEW_WIDTH = 300 163 | EXT_LINKS_IN_WINDOW = YES 164 | 165 | GENERATE_QHP = NO 166 | QCH_FILE = ../maxLibQt.qch 167 | QHP_NAMESPACE = us.paperno.maxLibQt 168 | QHP_VIRTUAL_FOLDER = doc 169 | QHP_CUST_FILTER_NAME = maxLibQt 170 | QHP_CUST_FILTER_ATTRS = maxLibQt 171 | QHP_SECT_FILTER_ATTRS = maxLibQt 172 | QHG_LOCATION = 173 | 174 | #--------------------------------------------------------------------------- 175 | # Configuration options related to the LaTeX output 176 | #--------------------------------------------------------------------------- 177 | 178 | GENERATE_LATEX = NO 179 | 180 | #--------------------------------------------------------------------------- 181 | # Configuration options related to the preprocessor 182 | #--------------------------------------------------------------------------- 183 | 184 | ENABLE_PREPROCESSING = YES 185 | MACRO_EXPANSION = YES 186 | PREDEFINED = DOXYGEN_SHOULD_INCLUDE_THIS \ 187 | \ 188 | Q_WS_X11= \ 189 | Q_WS_WIN= \ 190 | Q_WS_MAC= \ 191 | Q_WS_QWS= \ 192 | Q_WS_MAEMO_5= \ 193 | Q_OS_LINUX= \ 194 | Q_OS_UNIX= \ 195 | Q_OS_WIN= \ 196 | Q_OS_MAC= \ 197 | Q_OS_MACX= \ 198 | Q_OS_DARWIN= \ 199 | Q_OS_FREEBSD= \ 200 | Q_OS_NETBSD= \ 201 | Q_OS_OPENBSD= \ 202 | Q_OS_BSD4= \ 203 | Q_OS_SOLARIS= \ 204 | Q_OS_IRIX= \ 205 | \ 206 | Q_SLOTS="slots" \ 207 | Q_SIGNALS="signals" \ 208 | Q_DECL_CONSTEXPR= \ 209 | Q_DECL_RELAXED_CONSTEXPR= \ 210 | Q_DECL_OVERRIDE="override" \ 211 | Q_DECL_FINAL="final" \ 212 | Q_DECL_EQ_DEFAULT="= default" \ 213 | Q_DECL_EQ_DELETE="= delete" \ 214 | Q_DECL_NOEXCEPT= \ 215 | Q_DECL_DEPRECATED= \ 216 | Q_DECL_UNUSED_MEMBER= \ 217 | Q_DECL_VARIABLE_DEPRECATED= \ 218 | Q_DECL_EXPORT= \ 219 | Q_DECL_IMPORT= \ 220 | Q_DECL_HIDDEN= \ 221 | Q_DECL_NULLPTR="nullptr" \ 222 | Q_REQUIRED_RESULT= \ 223 | Q_SCRIPTABLE= \ 224 | Q_INVOKABLE= 225 | 226 | #--------------------------------------------------------------------------- 227 | # Configuration options related to external references 228 | #--------------------------------------------------------------------------- 229 | 230 | TAGFILES = tagfiles/qt.tags=https://doc.qt.io/qt-5/ 231 | PERL_PATH = perl 232 | 233 | #--------------------------------------------------------------------------- 234 | # Configuration options related to the dot tool 235 | #--------------------------------------------------------------------------- 236 | 237 | CLASS_DIAGRAMS = NO 238 | -------------------------------------------------------------------------------- /src/widgets/OverlayStackLayout.h: -------------------------------------------------------------------------------- 1 | /* 2 | OverlayStackLayout 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef OVERLAYSTACKLAYOUT_H 29 | #define OVERLAYSTACKLAYOUT_H 30 | 31 | #include 32 | 33 | /*! 34 | \brief The OverlayStackLayout re-implements a \c QStackedLayout with additional features 35 | to allow stacks with "floating" overlay widgets, such as toolbars, buttons, messages, etc., 36 | while still allowing interaction with exposed areas of the widget(s) underneath. 37 | 38 | The functionality is similar to \c QStackedLayout::StackAll mode, but instead of forcing all 39 | child widgets to be the same size (as \c QStackedLayout does), this version respects the size 40 | hints of each widget. It also respects the widget alignment settings (like most other `QLayout`s), 41 | and adds a way to fine-tune the position with a `positionOffset` property which can be set on any widget. 42 | These are set with the standard \c QLayout::setAlignment() and custom \c OverlayStackLayout::setOffset() 43 | methods respectively. A few \c addWidget() and \c insertWidget() overloads are also provided to set 44 | these properties at insertion time. 45 | 46 | So for example one could have a "main" widget with \c QSizePolicy::Expanding flags in both directions 47 | which will take up the full size of the layout. Then add a toolbar with fixed/minimum size policy 48 | and `Qt::AlignTop | Qt::AlignHCenter` alignment which will "float" on top of the main widget and 49 | keep itself centered in the available width. Only the actual toolbar area would capture mouse events, 50 | so interaction with the main area widget is still possible. The toolbar could be spaced away from the 51 | top of the window using the \c positionOffset property with a positive `y` value. 52 | 53 | \code 54 | QWidget *w = new QWidget(); // Container widget for stack layout 55 | // overlay layout for the container, toolbar will "float" on top of main widget 56 | OverlayStackLayout *lo = new OverlayStackLayout(w); 57 | lo->setContentsMargins(0,0,0,0); // Clear the default margins 58 | QWidget *main = new QWidget(w); // A content widget 59 | main->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); // stretch to fit 60 | Toolbar *toolbar = new Toolbar(w); // A floating toolbar 61 | // Add the main widget 62 | lo->addWidget(main); 63 | // Add toolbar widget with center/top alignment and 20px vertical offset 64 | lo->addWidget(toolbar, Qt::AlignTop | Qt::AlignHCenter, QPoint(0, 20)); 65 | lo->setCurrentIndex(1); // stack toolbar on top 66 | \endcode 67 | 68 | \c OverlayStackLayout also respects the \c Qt::WA_LayoutOnEntireRect widget attribute. Widgets with 69 | this attribute set will always be laid out ignoring any \c contentsMargins() set on this layout (that is, 70 | using the full available \c geometry() size, vs. \c contentsRect() size). This allows some widgets to 71 | be spaced away from the edges, while others cover the full available area. Using the example above, 72 | the toolbar could be spaced away from the top by setting a positive top content margin on the layout, 73 | and setting the \c Qt::WA_LayoutOnEntireRect attribute on the main expanding widget. 74 | 75 | By default the layout operates in \c QStackedLayout::StackAll mode, meaning no widgets are 76 | hidden when changing the \c currentIndex, only the stack order changes. However, the 77 | \c QStackedLayout::StackOne mode can also be used, for example to switch between multiple pages like a 78 | "typical" stack. In this mode the \c Qt::WA_LayoutOnEntireRect widget attribute could be set 79 | to ensure one or more widgets are always stacked on top of others. 80 | 81 | Here is another example showing the above concepts in action. 82 | 83 | ``` 84 | MyWidget() : QWidget() 85 | { 86 | OverlayStackLayout *stackLo = new OverlayStackLayout(this); 87 | ``` 88 | \snippet imageviewer/ImageViewer.cpp 1 89 | \snippet imageviewer/ImageViewer.cpp 2 90 | ``` 91 | // ... create more page widgets... 92 | ``` 93 | \snippet imageviewer/ImageViewer.cpp 3 94 | \snippet imageviewer/ImageViewer.cpp 4 95 | ``` 96 | } 97 | ``` 98 | 99 | Note that for non-interactive overlays (eg. messages/information), one can set the 100 | \c Qt::WA_TransparentForMouseEvents widget attribute to avoid interference with any underlying 101 | widgets altogether. 102 | 103 | A complete example application demonstrating the different uses of \c OverlayStackLayout is available 104 | in the \e /examples/imageviewer folder of this project. 105 | 106 | \sa QStackedLayout 107 | */ 108 | class OverlayStackLayout : public QStackedLayout 109 | { 110 | Q_OBJECT 111 | public: 112 | //! Constructs a new \c OverlayStackLayout with the optional \a parent widget. 113 | //! If \a parent is specified, this layout will install itself on the parent widget. 114 | explicit OverlayStackLayout(QWidget *parent = nullptr); 115 | //! Constructs a new \c OverlayStackLayout and inserts it into the given \a parentLayout. 116 | explicit OverlayStackLayout(QLayout *parentLayout); 117 | 118 | //! Insert \a widget into the stack at \a index position with specified \a alignment. 119 | //! \sa QStackedLayout::insertWidget() 120 | int insertWidget(int index, QWidget *widget, Qt::Alignment alignment); 121 | //! Insert \a widget into the stack at \a index position with specified \a alignment and position \a offset coordinates. 122 | //! \sa QStackedLayout::insertWidget() 123 | int insertWidget(int index, QWidget *widget, Qt::Alignment alignment, const QPoint &offset); 124 | //! Inherited from \c QStackedLayout::insertWidget(int, QWidget *) 125 | using QStackedLayout::insertWidget; 126 | //! Add \a widget to the stack with specified \a alignment. \sa QStackedLayout::addWidget() 127 | int addWidget(QWidget *widget, Qt::Alignment alignment); 128 | //! Add \a widget to the stack with specified \a alignment and position \a offset coordinates. 129 | //! \sa QStackedLayout::addWidget() 130 | int addWidget(QWidget *widget, Qt::Alignment alignment, const QPoint &offset); 131 | //! Inherited from \c QStackedLayout::addWidget(QWidget *) 132 | using QStackedLayout::addWidget; 133 | 134 | void setGeometry(const QRect &geo) override; 135 | 136 | public slots: 137 | //! Set the layout position offset coordinates for given \a widget. 138 | //! The offset is in absolute pixels and is applied after alignment positioning. 139 | //! \note Note that you can also simply assign a property named "positionOffset" 140 | //! with a \c QPoint type value to any widget and it will be respected by this layout. 141 | void setOffset(QWidget *widget, const QPoint &offset) const; 142 | //! Convenience slot to set the layout position offset on a signal from a \c QWidget::sender(). 143 | //! This has no effect if the sender widget is not currently in this layout. \sa setOffset() 144 | void setSenderOffset(const QPoint &offset) const; 145 | //! Convenience slot to set the layout alignment on a signal from a \c QWidget::sender(). 146 | //! This has no effect if the sender widget is not currently in this layout. \sa setAlignment() 147 | void setSenderAlignment(Qt::Alignment align); 148 | 149 | //! Re-implemented (shadowing) \c QStackedLayout::setStackingMode() to ensure proper child visibility. 150 | //! The default stacking mode for \c OverlayStackLayout is \c QStackedLayout::StackAll. 151 | //! \note Setting the stacking mode to \c QStackedLayout::StackOne when `currentIndex() > 0` will 152 | //! hide (`setVisible(false)`) all widgets except the current one. Conversely, setting the mode to \c StackAll 153 | //! will set \e all widgets to be visible. This is due to how \c QStackedLayout::setStackingMode() operates. 154 | //! In general it is recommended to set the desired stacking mode \e before adding widgets. 155 | void setStackingMode(StackingMode mode); 156 | 157 | private slots: 158 | void doLayout() const; 159 | 160 | private: 161 | Q_DISABLE_COPY(OverlayStackLayout) 162 | }; 163 | 164 | inline 165 | int OverlayStackLayout::addWidget(QWidget *widget, Qt::Alignment alignment) 166 | { 167 | return insertWidget(count(), widget, alignment); 168 | } 169 | 170 | inline 171 | int OverlayStackLayout::addWidget(QWidget *widget, Qt::Alignment alignment, const QPoint &offset) 172 | { 173 | return insertWidget(count(), widget, alignment, offset); 174 | } 175 | 176 | #endif // OVERLAYSTACKLAYOUT_H 177 | -------------------------------------------------------------------------------- /src/core/AppDebugMessageHandler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | AppDebugMessageHandler 3 | 4 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 5 | Contact: http://www.WorldDesign.com/contact 6 | 7 | LICENSE: 8 | 9 | Commercial License Usage 10 | Licensees holding valid commercial licenses may use this file in 11 | accordance with the terms contained in a written agreement between 12 | you and the copyright holder. 13 | 14 | GNU General Public License Usage 15 | Alternatively, this file may be used under the terms of the GNU 16 | General Public License as published by the Free Software Foundation, 17 | either version 3 of the License, or (at your option) any later version. 18 | 19 | This program is distributed in the hope that it will be useful, 20 | but WITHOUT ANY WARRANTY; without even the implied warranty of 21 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 | GNU General Public License for more details. 23 | 24 | A copy of the GNU General Public License is available at . 25 | */ 26 | 27 | #include "AppDebugMessageHandler.h" 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | AppDebugMessageHandler::AppDebugMessageHandler() : 34 | QObject(), 35 | m_defaultHandler(nullptr) 36 | { 37 | setAppDebugOutputLevel(APP_DBG_HANDLER_DEFAULT_LEVEL); 38 | setShowSourcePath(APP_DBG_HANDLER_SHOW_SRC_PATH); 39 | setSourceBasePath(QStringLiteral(APP_DBG_HANDLER_SRC_PATH)); 40 | setShowFunctionDeclarations(APP_DBG_HANDLER_SHOW_FUNCTION_DECL); 41 | setShowTimestamp(APP_DBG_HANDLER_SHOW_TIMESTAMP); 42 | setTimestampFormat(QStringLiteral(APP_DBG_HANDLER_TIMESTAMP_FORMAT)); 43 | defaultMessagePattern(); // preload 44 | #if (QT_VERSION < QT_VERSION_CHECK(5, 4, 0)) 45 | m_functionFilter = QRegularExpression("^.+?(\\w+\\().+$"); 46 | #endif 47 | } 48 | 49 | // static 50 | AppDebugMessageHandler *AppDebugMessageHandler::instance() 51 | { 52 | static AppDebugMessageHandler instance; 53 | return &instance; 54 | } 55 | 56 | void AppDebugMessageHandler::setAppDebugOutputLevel(quint8 appDebugOutputLevel) 57 | { 58 | QWriteLocker locker(&m_mutex); 59 | m_appDebugOutputLevel = qMin(appDebugOutputLevel, 4); 60 | } 61 | 62 | void AppDebugMessageHandler::setShowSourcePath(bool showSourcePath) 63 | { 64 | QWriteLocker locker(&m_mutex); 65 | m_showSourcePath = showSourcePath; 66 | m_defaultPattern.clear(); 67 | } 68 | 69 | void AppDebugMessageHandler::setSourceBasePath(const QString &path) 70 | { 71 | QWriteLocker locker(&m_mutex); 72 | m_srcPathFilter = path.isEmpty() ? QRegularExpression() : QRegularExpression(QStringLiteral("^%1[\\\\\\/](.*?)$").arg(path), QRegularExpression::InvertedGreedinessOption); 73 | } 74 | 75 | void AppDebugMessageHandler::setShowFunctionDeclarations(bool showFunctionDeclarations) 76 | { 77 | QWriteLocker locker(&m_mutex); 78 | m_showFunctionDeclarations = showFunctionDeclarations; 79 | m_defaultPattern.clear(); 80 | } 81 | 82 | void AppDebugMessageHandler::setShowTimestamp(bool showTimestamp) 83 | { 84 | QWriteLocker locker(&m_mutex); 85 | m_showTimestamp = showTimestamp; 86 | m_defaultPattern.clear(); 87 | } 88 | 89 | void AppDebugMessageHandler::setTimestampFormat(const QString &timeFormat) 90 | { 91 | QWriteLocker locker(&m_mutex); 92 | m_tsFormat = timeFormat; 93 | m_defaultPattern.clear(); 94 | } 95 | 96 | void AppDebugMessageHandler::setMessagePattern(const QString &pattern) 97 | { 98 | QWriteLocker locker(&m_mutex); 99 | m_msgPattern = pattern; 100 | } 101 | 102 | void AppDebugMessageHandler::addOutputDevice(QIODevice *device) 103 | { 104 | QWriteLocker locker(&m_mutex); 105 | if (device && !m_outputDevices.contains(device)) 106 | m_outputDevices.append(device); 107 | } 108 | 109 | void AppDebugMessageHandler::removeOutputDevice(QIODevice *device) 110 | { 111 | if (!device) 112 | return; 113 | QWriteLocker locker(&m_mutex); 114 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) 115 | m_outputDevices.removeAll(device); 116 | #else 117 | int i = 0; 118 | foreach (QIODevice * d, m_outputDevices) { 119 | if (d == device) 120 | m_outputDevices.remove(i); 121 | ++i; 122 | } 123 | #endif 124 | } 125 | 126 | QString AppDebugMessageHandler::defaultMessagePattern() const 127 | { 128 | if (!m_defaultPattern.isEmpty()) 129 | return m_defaultPattern; 130 | 131 | QString msgPattern; 132 | if (m_showTimestamp) 133 | msgPattern.append(QStringLiteral("[%{time %1}] ").arg(m_tsFormat)); 134 | 135 | msgPattern.append(QLatin1String("[%{short-type}] ")); 136 | 137 | if (m_showSourcePath) 138 | msgPattern.append(QLatin1String("%{file}::")); 139 | 140 | if (m_showFunctionDeclarations) 141 | msgPattern.append(QLatin1String("%{full-function}")); 142 | else 143 | msgPattern.append(QLatin1String("%{function}()")); 144 | 145 | msgPattern.append(QLatin1String(":%{line} -%{if-category} [%{category}]%{endif} %{message}")); 146 | 147 | #ifndef QT_NO_GLIB 148 | msgPattern.append("%{if-fatal}\nBACKTRACE:\n%{backtrace depth=12 separator=\"\n\"}%{endif}"); 149 | #endif 150 | 151 | m_defaultPattern = msgPattern; 152 | return msgPattern; 153 | } 154 | 155 | QString AppDebugMessageHandler::messagePattern() const 156 | { 157 | return m_msgPattern.isEmpty() ? defaultMessagePattern() : m_msgPattern; 158 | } 159 | 160 | void AppDebugMessageHandler::installAppMessageHandler() 161 | { 162 | #if APP_DBG_HANDLER_ENABLE 163 | m_defaultHandler = qInstallMessageHandler(g_appDebugMessageHandler); 164 | #else 165 | qInstallMessageHandler(nullptr); 166 | #endif 167 | } 168 | 169 | void AppDebugMessageHandler::messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) 170 | { 171 | // normalize types, QtDebugMsg stays 0, QtInfoMsg becomes 1, rest are QtMsgType + 1 172 | quint8 lvl = type; 173 | if (type == QtInfoMsg) 174 | lvl = 1; 175 | else if (type > QtDebugMsg) 176 | ++lvl; 177 | 178 | QReadLocker locker(&m_mutex); 179 | 180 | if (lvl < m_appDebugOutputLevel) 181 | return; 182 | 183 | #if defined(Q_OS_LINUX) && (QT_VERSION < QT_VERSION_CHECK(5, 3, 0)) 184 | // Filter out lots of QPainter warnings from undocked QDockWidgets... hackish but effective (only workaround found so far) 185 | if (lvl == 2 && QString(context.function).contains("QPainter::")) 186 | return; 187 | #endif 188 | 189 | QString msgPattern = messagePattern(); 190 | 191 | QString file = context.file; 192 | if (m_srcPathFilter.isValid()) 193 | file.replace(m_srcPathFilter, "\\1"); 194 | 195 | const bool hasDevices = !m_outputDevices.isEmpty(); 196 | 197 | locker.unlock(); 198 | 199 | msgPattern.replace(QLatin1String("%{short-type}"), shortTypeNames().value(type, QStringLiteral("?"))); 200 | msgPattern.replace(QLatin1String("%{full-function}"), context.function); 201 | 202 | QMessageLogContext newContext(qPrintable(file), context.line, context.function, context.category); 203 | 204 | qSetMessagePattern(msgPattern); 205 | 206 | if (!m_defaultHandler || hasDevices || receivers(SIGNAL(messageOutput(quint8, const QString &)))) { 207 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)) 208 | msgPattern = qFormatLogMessage(type, newContext, msg); 209 | #else 210 | msgPattern.replace("%{type}", fullTypeNames().value(type, "unknown")); 211 | msgPattern.replace("%{line}", QString::number(context.line)); 212 | msgPattern.replace("%{if-category} [%{category}]%{endif}", QString(context.category)); 213 | msgPattern.replace("%{message}", msg); 214 | msgPattern.replace("%{function}", QString(context.function).replace(m_functionFilter, "\\1)")); 215 | #endif 216 | 217 | emit messageOutput(lvl, msgPattern); 218 | 219 | if (hasDevices) { 220 | locker.relock(); 221 | const QVector devices(m_outputDevices); 222 | locker.unlock(); 223 | for (QIODevice * d : devices) { 224 | if (d && d->isWritable() && (!d->property("level").isValid() || d->property("level").toInt() <= lvl)) { 225 | d->write(qPrintable(msgPattern + "\n")); 226 | if (QFileDevice * fd = qobject_cast(d)) 227 | fd->flush(); 228 | } 229 | } 230 | } 231 | } 232 | 233 | // if (QThread::currentThread() == qApp->thread()) // gui thread 234 | 235 | if (m_defaultHandler) { 236 | m_defaultHandler(type, newContext, msg); 237 | } 238 | else { 239 | fprintf(stderr, "%s", qPrintable(msgPattern)); 240 | if (type == QtFatalMsg) 241 | abort(); 242 | } 243 | } 244 | 245 | // static 246 | QHash &AppDebugMessageHandler::shortTypeNames() 247 | { 248 | static QHash symbols({ 249 | {QtDebugMsg, tr("D")}, 250 | {QtWarningMsg, tr("W")}, 251 | {QtCriticalMsg, tr("C")}, 252 | {QtFatalMsg, tr("F")}, 253 | {QtInfoMsg, tr("I")} 254 | }); 255 | return symbols; 256 | } 257 | 258 | #if (QT_VERSION < QT_VERSION_CHECK(5, 4, 0)) 259 | // static 260 | QHash &AppDebugMessageHandler::fullTypeNames() 261 | { 262 | static QHash symbols({ 263 | {QtDebugMsg, tr("debug")}, 264 | {QtWarningMsg, tr("warning")}, 265 | {QtCriticalMsg, tr("critical")}, 266 | {QtFatalMsg, tr("fatal")}, 267 | {QtInfoMsg, tr("info")} 268 | }); 269 | return symbols; 270 | } 271 | #endif 272 | 273 | // Message handler which is installed using qInstallMessageHandler. This needs to be global. 274 | void g_appDebugMessageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg) 275 | { 276 | if (AppDebugMessageHandler::instance()) 277 | AppDebugMessageHandler::instance()->messageHandler(type, context, msg); 278 | } 279 | -------------------------------------------------------------------------------- /src/widgets/ExportableTableView.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ExportableTableView 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #include "ExportableTableView.h" 29 | 30 | ExportableTableView::ExportableTableView(QWidget *parent) : QTableView(parent) 31 | { 32 | setContextMenuPolicy(Qt::CustomContextMenu); 33 | setHtmlStyle(getDefaultHtmlStyle()); 34 | setHtmlTemplate(getDefaultHtmlTemplate()); 35 | 36 | QAction * selAll = new QAction(tr("Select All"), this); 37 | selAll->setShortcut(QKeySequence::SelectAll); 38 | addAction(selAll); 39 | 40 | QAction * cpyTab = new QAction(tr("Copy selection as TAB-delimited text"), this); 41 | cpyTab->setShortcut(QKeySequence::Copy); 42 | cpyTab->setProperty("delim", "\t"); 43 | addAction(cpyTab); 44 | 45 | QAction * cpyCsv = new QAction(tr("Copy selection as comma-delimited text (CSV)"), this); 46 | cpyCsv->setShortcut(Qt::CTRL + Qt::SHIFT + Qt::Key_C); 47 | cpyCsv->setProperty("delim", ", "); 48 | addAction(cpyCsv); 49 | 50 | QAction * cpyPipe = new QAction(tr("Copy selection as pipe-delimited text"), this); 51 | cpyPipe->setShortcut(Qt::CTRL + Qt::ALT + Qt::Key_C); 52 | cpyPipe->setProperty("delim", " | "); 53 | addAction(cpyPipe); 54 | 55 | QAction * cpyHtml = new QAction(tr("Copy selection as HTML"), this); 56 | cpyCsv->setShortcut(Qt::CTRL + Qt::ALT + Qt::SHIFT + Qt::Key_C); 57 | cpyHtml->setProperty("delim", "html"); 58 | addAction(cpyHtml); 59 | 60 | QAction * saveFile = new QAction(tr("Save selection to file"), this); 61 | saveFile->setShortcut(QKeySequence::Save); 62 | addAction(saveFile); 63 | 64 | connect(selAll, &QAction::triggered, this, &QTableView::selectAll); 65 | connect(cpyTab, &QAction::triggered, this, &ExportableTableView::copy); 66 | connect(cpyCsv, &QAction::triggered, this, &ExportableTableView::copy); 67 | connect(cpyPipe, &QAction::triggered, this, &ExportableTableView::copy); 68 | connect(cpyHtml, &QAction::triggered, this, &ExportableTableView::copy); 69 | connect(saveFile, &QAction::triggered, this, &ExportableTableView::save); 70 | connect(this, &QTableView::customContextMenuRequested, this, &ExportableTableView::onCustomContextMenuRequested); 71 | } 72 | 73 | QString ExportableTableView::getDefaultHtmlStyle() 74 | { 75 | return "th, td {font-family: sans-serif; padding: 3px 15px 3px 3px;} " \ 76 | "th {text-align: left;}"; 77 | } 78 | 79 | QString ExportableTableView::getDefaultHtmlTemplate() 80 | { 81 | return "\n\n\n\n" \ 84 | "\n" \ 85 | "%2" \ 86 | "
\n" \ 87 | ""; 88 | } 89 | 90 | void ExportableTableView::setHtmlStyle(const QString &value) 91 | { 92 | m_htmlStyle = value; 93 | } 94 | 95 | void ExportableTableView::setHtmlTemplate(const QString &value) 96 | { 97 | m_htmlTemplate = value; 98 | } 99 | 100 | QString ExportableTableView::toPlainText(const QModelIndexList &indexList, const QString &delim) const 101 | { 102 | QString ret, header; 103 | const QChar rowDelim = '\n'; 104 | bool firstRow = true; 105 | for (int i = 0; i < indexList.count(); ++i) { 106 | const QModelIndex & idx = indexList.at(i); 107 | 108 | if (firstRow) 109 | header.append(model()->headerData(idx.column(), Qt::Horizontal).toString()); 110 | 111 | ret.append(idx.data(Qt::DisplayRole).toString()); 112 | 113 | if (i + 1 == indexList.count() || indexList.at(i+1).row() != idx.row()) { 114 | ret.append(rowDelim); 115 | if (firstRow && !header.isEmpty()) 116 | header.append(rowDelim); 117 | firstRow = false; 118 | } 119 | else { 120 | ret.append(delim); 121 | if (firstRow && !header.isEmpty()) 122 | header.append(delim); 123 | } 124 | } 125 | if (!header.isEmpty()) 126 | ret.prepend(header); 127 | 128 | return ret; 129 | } 130 | 131 | QString ExportableTableView::toHtml(const QModelIndexList &indexList) const 132 | { 133 | QString ret, header, row; 134 | bool firstRow = true; 135 | 136 | for (int i = 0; i < indexList.count(); ++i) { 137 | const QModelIndex & idx = indexList.at(i); 138 | const int algn = (idx.data(Qt::TextAlignmentRole).isValid() ? idx.data(Qt::TextAlignmentRole).toInt() : (Qt::AlignLeft | Qt::AlignVCenter)); 139 | const QString fg = (idx.data(Qt::ForegroundRole).isValid() ? idx.data(Qt::ForegroundRole).value().color().name(QColor::HexRgb) : "initial"); 140 | const QString bg = (idx.data(Qt::BackgroundRole).isValid() ? idx.data(Qt::BackgroundRole).value().color().name(QColor::HexRgb) : "initial"); 141 | const QString ttl = (idx.data(Qt::ToolTipRole).isValid() ? idx.data(Qt::ToolTipRole).toString().replace("\"", "\"\"") : QString()); 142 | 143 | QString fnt; 144 | if (idx.data(Qt::FontRole).isValid()) { 145 | const QFont font = idx.data(Qt::FontRole).value(); 146 | fnt = "font: \"" % font.family() % "\" " % QString::number(font.pointSize()) % "pt;"; 147 | } 148 | 149 | if (firstRow) 150 | header.append(QString("%1\n").arg(model()->headerData(idx.column(), Qt::Horizontal).toString())); 151 | 152 | row.append("%1\n"); // cell template 153 | row = row.arg(idx.data(Qt::DisplayRole).toString()).arg(fg).arg(bg).arg(fnt); 154 | row = row.arg((algn & Qt::AlignRight) ? "right" : (algn & Qt::AlignHCenter) ? "center" : "left"); 155 | row = row.arg((algn & Qt::AlignTop) ? "top" : (algn & Qt::AlignBottom) ? "bottom" : "middle"); 156 | row = row.arg(ttl.isEmpty() ? ttl : QString("title=\"%1\"").arg(ttl)); 157 | 158 | if (i + 1 == indexList.count() || indexList.at(i+1).row() != idx.row()) { 159 | ret.append(QString("\n%1\n").arg(row)); 160 | row.clear(); 161 | firstRow = false; 162 | } 163 | } 164 | 165 | ret = QString("\n%1\n").arg(ret); 166 | if (!header.isEmpty()) 167 | ret.prepend(QString("\n\n%1\n\n").arg(header)); 168 | ret = m_htmlTemplate.arg(m_htmlStyle).arg(ret); 169 | 170 | return ret; 171 | } 172 | 173 | bool ExportableTableView::saveToFile(const QModelIndexList &indexList, const QString &fileName) 174 | { 175 | static QString lastDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); 176 | 177 | if (indexList.isEmpty()) 178 | return false; 179 | 180 | QString fname = fileName; 181 | if (fname.isEmpty()) { 182 | QString types = tr("Tab-delimited text") % " (*.tab);;" % tr("Comma-delimited text") % " (*.csv);;" % tr("Pipe-delimited text") % " (*.txt);;" % tr("HTML") % " (*.html)"; 183 | fname = QFileDialog::getSaveFileName(this, tr("Save to file"), lastDir, types); 184 | } 185 | 186 | if (fname.isEmpty()) 187 | return false; 188 | 189 | QFile file(fname); 190 | QFileInfo fi(file); 191 | lastDir = fi.absolutePath(); 192 | 193 | if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) 194 | return false; 195 | 196 | if (fi.suffix() == "html") 197 | file.write(toHtml(indexList).toUtf8()); 198 | else 199 | file.write(toPlainText(indexList, (fi.suffix() == "tab" ? "\t" : fi.suffix() == "csv" ? ", " : " | ")).toUtf8()); 200 | file.close(); 201 | 202 | return true; 203 | } 204 | 205 | QSize ExportableTableView::sizeHint() const 206 | { 207 | int w = 0; 208 | for (int i = 0; i < model()->columnCount(); ++i) 209 | w += sizeHintForColumn(i); 210 | return QSize(w + verticalScrollBar()->sizeHint().width() + 60, sizeHintForRow(0) * model()->rowCount()); 211 | } 212 | 213 | void ExportableTableView::copyText(const QString &delim) 214 | { 215 | QApplication::clipboard()->setText(toPlainText(getSelectedOrAll(), delim)); 216 | } 217 | 218 | void ExportableTableView::copyHtml() 219 | { 220 | QMimeData * mdata = new QMimeData; 221 | mdata->setHtml(toHtml(getSelectedOrAll())); 222 | QApplication::clipboard()->setMimeData(mdata); 223 | } 224 | 225 | void ExportableTableView::copy() 226 | { 227 | QString delim = "\t"; 228 | if (sender() && sender()->property("delim").isValid()) 229 | delim = sender()->property("delim").toString(); 230 | if (delim == "html") 231 | copyHtml(); 232 | else 233 | copyText(delim); 234 | } 235 | 236 | void ExportableTableView::save() 237 | { 238 | saveToFile(getSelectedOrAll()); 239 | } 240 | 241 | QModelIndexList ExportableTableView::getSelectedOrAll() 242 | { 243 | QModelIndexList sel = selectionModel()->selectedIndexes(); 244 | if (sel.isEmpty()) { 245 | selectAll(); 246 | sel = selectionModel()->selectedIndexes(); 247 | } 248 | return sel; 249 | } 250 | 251 | void ExportableTableView::onCustomContextMenuRequested(const QPoint & pos) 252 | { 253 | QMenu menu; 254 | menu.addActions(actions()); 255 | menu.exec(mapToGlobal(pos)); 256 | } 257 | -------------------------------------------------------------------------------- /src/widgets/CollapsingToolBar.h: -------------------------------------------------------------------------------- 1 | /* 2 | CollapsingToolBar 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef COLLAPSINGTOOLBAR_H_ 29 | #define COLLAPSINGTOOLBAR_H_ 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | 37 | /*! 38 | \brief The CollapsingToolBar will change the \c QToolButton::toolButtonStyle of all added \c QToolButtons (via \c QAction or directly) 39 | depending on the total available width. It was originally written as an answer to a [StackOverflow question][1], and is not very sophisticated. 40 | Here is the usage example/test given in the SO question (find a suitable image file to use for an icon). 41 | 42 | \code 43 | class MainWindow : public QMainWindow 44 | { 45 | Q_OBJECT 46 | public: 47 | MainWindow(QWidget *parent = nullptr) 48 | : QMainWindow(parent) 49 | { 50 | QToolBar* tb = new CollapsingToolBar(this); 51 | tb->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 52 | tb->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); 53 | 54 | QIcon icon = QIcon(QPixmap(info_xpm)); // need an image here 55 | for (int i=0; i < 6; ++i) 56 | tb->addAction(icon, QStringLiteral("Action %1").arg(i)); 57 | 58 | addToolBar(tb); 59 | show(); 60 | 61 | // Adding another action after show() may collapse all the actions if the new toolbar preferred width doesn't fit the window. 62 | // Only an issue if the toolbar size hint was what determined the window width to begin with. 63 | //tb->addAction(icon, QStringLiteral("Action After")); 64 | 65 | // Test setting button style after showing (comment out the one above) 66 | //tb->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 67 | 68 | // Test changing icon size after showing. 69 | //tb->setIconSize(QSize(48, 48)); 70 | 71 | // Try this too... 72 | //tb->setMovable(false); 73 | } 74 | }; 75 | 76 | int main(int argc, char *argv[]) 77 | { 78 | QApplication app(argc, argv); 79 | //QApplication::setStyle("Fusion"); 80 | //QApplication::setStyle("windows"); 81 | MainWindow w; 82 | return app.exec(); 83 | } 84 | \endcode 85 | 86 | [1]: https://stackoverflow.com/questions/57913277/changing-toolbuttonstyle-of-qtoolbuttons-dynamically-depending-upon-the-size-of/57918115#57918115 87 | */ 88 | class CollapsingToolBar : public QToolBar 89 | { 90 | Q_OBJECT 91 | public: 92 | explicit CollapsingToolBar(QWidget *parent = nullptr) : CollapsingToolBar(QString(), parent) {} 93 | 94 | explicit CollapsingToolBar(const QString &title, QWidget *parent = nullptr) : 95 | QToolBar(title, parent) 96 | { 97 | initSizes(); 98 | // If icon sizes change we need to recalculate all the size hints, but we need to wait until the buttons have adjusted themselves, so we queue the update. 99 | connect(this, &QToolBar::iconSizeChanged, [this](const QSize &) { 100 | QMetaObject::invokeMethod(this, "recalcExpandedSize", Qt::QueuedConnection); 101 | }); 102 | // The drag handle can mess up our sizing, update preferred size if it changes. 103 | connect(this, &QToolBar::movableChanged, [this](bool movable) { 104 | const int handleSz = style()->pixelMetric(QStyle::PM_ToolBarHandleExtent, nullptr, this);; 105 | m_expandedSize = (movable ? m_expandedSize + handleSz : m_expandedSize - handleSz); 106 | adjustForSize(); 107 | }); 108 | } 109 | 110 | protected: 111 | 112 | // Monitor action events to keep track of required size. 113 | void actionEvent(QActionEvent *e) override 114 | { 115 | QToolBar::actionEvent(e); 116 | 117 | int width = 0; 118 | switch (e->type()) 119 | { 120 | case QEvent::ActionAdded: 121 | // Personal pet-peeve... optionally set buttons with menus to have instant popups instead of splits with the main button doing nothing. 122 | //if (QToolButton *tb = qobject_cast(widgetForAction(e->action()))) 123 | // tb->setPopupMode(QToolButton::InstantPopup); 124 | //Q_FALLTHROUGH; 125 | case QEvent::ActionChanged: 126 | width = widthForAction(e->action()); 127 | if (width <= 0) 128 | return; 129 | 130 | if (e->type() == QEvent::ActionAdded || !m_actionWidths.contains(e->action())) 131 | m_expandedSize += width + m_spacing; 132 | else 133 | m_expandedSize = m_expandedSize - m_actionWidths.value(e->action()) + width; 134 | m_actionWidths.insert(e->action(), width); 135 | break; 136 | 137 | case QEvent::ActionRemoved: 138 | if (!m_actionWidths.contains(e->action())) 139 | return; 140 | width = m_actionWidths.value(e->action()); 141 | m_expandedSize -= width + m_spacing; 142 | m_actionWidths.remove(e->action()); 143 | break; 144 | 145 | default: 146 | return; 147 | } 148 | adjustForSize(); 149 | } 150 | 151 | bool event(QEvent *e) override 152 | { 153 | // Watch for style change 154 | if (e->type() == QEvent::StyleChange) 155 | recalcExpandedSize(); 156 | return QToolBar::event(e); 157 | } 158 | 159 | void resizeEvent(QResizeEvent *e) override 160 | { 161 | adjustForSize(); 162 | QToolBar::resizeEvent(e); 163 | } 164 | 165 | private slots: 166 | // Here we do the actual switching of tool button style based on available width. 167 | void adjustForSize() 168 | { 169 | int availableWidth = contentsRect().width(); 170 | if (!isVisible() || m_expandedSize <= 0 || availableWidth <= 0) 171 | return; 172 | 173 | switch (toolButtonStyle()) { 174 | case Qt::ToolButtonIconOnly: 175 | if (availableWidth > m_expandedSize) 176 | setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 177 | break; 178 | 179 | case Qt::ToolButtonTextBesideIcon: 180 | if (availableWidth <= m_expandedSize) 181 | setToolButtonStyle(Qt::ToolButtonIconOnly); 182 | break; 183 | 184 | default: 185 | break; 186 | } 187 | } 188 | 189 | // Loops over all previously-added actions and re-calculates new size (eg. after icon size change) 190 | void recalcExpandedSize() 191 | { 192 | if (m_actionWidths.isEmpty()) 193 | return; 194 | initSizes(); 195 | int width = 0; 196 | QHash::iterator it = m_actionWidths.begin(); 197 | for ( ; it != m_actionWidths.end(); ++it) { 198 | width = widthForAction(it.key()); 199 | if (width <= 0) 200 | continue; 201 | m_expandedSize += width + m_spacing; 202 | it.value() = width; 203 | } 204 | adjustForSize(); 205 | } 206 | 207 | private: 208 | void initSizes() 209 | { 210 | // Preload some sizes based on style settings. 211 | // This is the spacing between items 212 | m_spacing = style()->pixelMetric(QStyle::PM_ToolBarItemSpacing, nullptr, this); 213 | // Size of a separator 214 | m_separatorWidth = style()->pixelMetric(QStyle::PM_ToolBarSeparatorExtent, nullptr, this); 215 | // The layout margins (we can't even get the private QToolBarLayout via layout() so we figure it out like it does) 216 | m_expandedSize = (style()->pixelMetric(QStyle::PM_ToolBarItemMargin, nullptr, this) + style()->pixelMetric(QStyle::PM_ToolBarFrameWidth, nullptr, this)) * 2; 217 | // And the size of the drag handle if we have one 218 | if (isMovable()) 219 | m_expandedSize += style()->pixelMetric(QStyle::PM_ToolBarHandleExtent, nullptr, this); 220 | } 221 | 222 | int widthForAction(QAction *action) const 223 | { 224 | // Try to find how wide the action representation (widget/separator) is. 225 | if (action->isSeparator()) 226 | return m_separatorWidth; 227 | 228 | if (QToolButton *tb = qobject_cast(widgetForAction(action))) { 229 | const Qt::ToolButtonStyle oldStyle = tb->toolButtonStyle(); 230 | // force the widest size 231 | tb->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 232 | const int width = tb->sizeHint().width(); 233 | tb->setToolButtonStyle(oldStyle); 234 | return width; 235 | } 236 | 237 | if (const QWidget *w = widgetForAction(action)) 238 | return w->sizeHint().width(); 239 | 240 | return 0; 241 | } 242 | 243 | int m_expandedSize = -1; // The maximum size we need with all buttons expanded and allowing for margins/etc 244 | int m_spacing = 0; // Layout spacing between items 245 | int m_separatorWidth = 0; // Width of separators 246 | QHash m_actionWidths; // Use this to track action additions/removals/changes 247 | }; 248 | 249 | #endif // COLLAPSINGTOOLBAR_H_ 250 | -------------------------------------------------------------------------------- /examples/imageviewer/ImageGrid.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | ImageGrid 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2019 Maxim Paperno; All Rights Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #include "ImageGrid.h" 29 | 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | ImageGrid::ImageGrid(QWidget *p) : 40 | QListView(p) 41 | { 42 | setObjectName("ImageGridView"); 43 | setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); 44 | //setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 45 | setFrameStyle(QFrame::NoFrame); 46 | setBackgroundRole(QPalette::Shadow); 47 | viewport()->setBackgroundRole(QPalette::Shadow); 48 | viewport()->setMouseTracking(false); // avoid unnecessary repaints 49 | setMouseTracking(false); 50 | setItemDelegate(new ImageGridDelegate(this)); 51 | setEditTriggers(NoEditTriggers); 52 | setSelectionMode(SingleSelection); 53 | setSelectionBehavior(SelectRows); 54 | setViewMode(IconMode); 55 | setDragDropMode(NoDragDrop); 56 | setMovement(Static); 57 | setLayoutMode(Batched); 58 | setBatchSize(1); 59 | setSpacing(4); 60 | setIconSize(QSize(64, 64)); // reset in resizeEvent 61 | setUniformItemSizes(true); // delegate always returns decorationSize (icon size) for sizeHint() 62 | 63 | connect(this, &QListView::activated, this, &ImageGrid::selectImage); 64 | connect(this, &QListView::clicked, this, &ImageGrid::selectImage); 65 | } 66 | 67 | void ImageGrid::setCurrentPath(const QString &path) 68 | { 69 | if (currentPath() == path) 70 | return; 71 | 72 | resetModel(); 73 | Q_ASSERT(m_model != nullptr); 74 | m_model->setRootPath(path); 75 | setRootIndex(m_model->index(path)); 76 | // ensure a layout update on next event loop 77 | QMetaObject::invokeMethod(this, "updateLayout", Qt::QueuedConnection, Q_ARG(bool, true)); 78 | emit currentPathChanged(path); 79 | } 80 | 81 | void ImageGrid::selectImage(const QModelIndex &idx) 82 | { 83 | QString img; 84 | if (!idx.isValid() || (img = idx.data(QFileSystemModel::FilePathRole).toString()).isEmpty()) 85 | return; 86 | if (currentIndex() != idx) 87 | setCurrentIndex(idx); 88 | emit imageSelected(img); 89 | } 90 | 91 | void ImageGrid::setCurrentImage(const QString &imageFile) 92 | { 93 | qDebug("%s : %d", qPrintable(imageFile), count()); 94 | if (!count()) 95 | return; 96 | selectImage(m_model->match(m_model->index(0, 0, rootIndex()), QFileSystemModel::FilePathRole, imageFile, 1, Qt::MatchContains).value(0)); 97 | } 98 | 99 | void ImageGrid::setCurrentFile(int index) 100 | { 101 | if (m_model) 102 | selectImage(m_model->index(index, 0, rootIndex())); 103 | } 104 | 105 | void ImageGrid::sortBy(QDir::SortFlags flags) 106 | { 107 | if (m_sortBy == flags || !m_model) 108 | return; 109 | m_sortBy = flags; // | QDir::IgnoreCase; 110 | const int col = (flags & QDir::Name) ? 0 : (flags & QDir::Size) ? 1 : (flags & QDir::Type) ? 2 : (flags & QDir::Time) ? 3 : 0; 111 | m_model->sort(col, (flags & QDir::Reversed) ? Qt::DescendingOrder : Qt::AscendingOrder); 112 | emit sortChanged(flags); 113 | } 114 | 115 | void ImageGrid::setColumns(uint columns) 116 | { 117 | if (m_columns != columns) { 118 | m_columns = columns; 119 | updateLayout(); 120 | } 121 | } 122 | 123 | void ImageGrid::setImageSize(const QSize &size) 124 | { 125 | if (m_icnSize == size) 126 | return; 127 | m_icnSize = size; 128 | if (size.isEmpty()) 129 | updateLayout(); 130 | else 131 | setIconSize(size); 132 | emit imageSizeChanged(size); 133 | } 134 | 135 | uint ImageGrid::columnsForWidth(int w) const // never returns zero 136 | { 137 | if (m_columns) 138 | return m_columns; 139 | if (!m_model || !rootIndex().isValid()) 140 | return 1; 141 | return qMax(uint(qMin(qRound(w / 256.0), count())), 1U); 142 | //return qMax(qRound(w / 256.0), 1); 143 | //return (w >= 2560 ? 6 : w >= 1920 ? 5 : w >= 1200 ? 4 : w >= 800 ? 3 : w > 400 ? 2 : 1); 144 | } 145 | 146 | void ImageGrid::updateLayout(bool force) 147 | { 148 | if (!m_model || !m_icnSize.isEmpty() || !isVisibleTo(parentWidget())) 149 | return; 150 | int w = viewport()->contentsRect().width() - 151 | style()->pixelMetric(QStyle::PM_ScrollBarExtent, nullptr, verticalScrollBar()) - 152 | ((width() - contentsRect().width()) * 2); 153 | const uint cols = columnsForWidth(w); 154 | w -= (spacing() * (int(cols) - 1)); 155 | const int sz = qRound(w / float(cols)); 156 | // only resize at steps of 12 to avoid excessive redraws 157 | if (force || qAbs(iconSize().width() - sz) > 12) 158 | setIconSize(QSize(sz, qCeil(sz * 0.75))); 159 | } 160 | 161 | void ImageGrid::resetModel() 162 | { 163 | static const QStringList filter = { 164 | QLatin1String("*.jpg"), QLatin1String("*.jpeg"), QLatin1String("*.png"), 165 | QLatin1String("*.gif"), QLatin1String("*.bmp"), QLatin1String("*.pbm"), 166 | #ifdef QT_SVG_LIB 167 | QLatin1String("*.svg") 168 | #endif 169 | }; 170 | // Qt bug, QFileSystemModel "forgets" filter() settings when root is reset or tree is navigated, so we need a full reset. 171 | if (m_model) 172 | m_model->deleteLater(); 173 | m_model = new QFileSystemModel(this); 174 | m_model->setNameFilters(filter); 175 | m_model->setFilter(QDir::Files); 176 | m_model->setReadOnly(true); 177 | m_model->setNameFilterDisables(false); 178 | setModel(m_model); 179 | connect(m_model, &QFileSystemModel::directoryLoaded, this, [this](const QString &path) { 180 | if (path == currentPath()) 181 | emit countChanged(count()); 182 | }); 183 | connect(selectionModel(), &QItemSelectionModel::selectionChanged, this, [this](const QItemSelection &sel, const QItemSelection &) { 184 | const QModelIndex &idx = sel.indexes().value(0); 185 | if (!idx.isValid()) 186 | return; 187 | emit currentFileIndexChanged(idx.row()); 188 | emit currentImageChanged(currentImage()); 189 | }); 190 | } 191 | 192 | QModelIndex ImageGrid::findNextImage(bool prevoius) 193 | { 194 | const int n = count(); 195 | if (n < 2) 196 | return QModelIndex(); 197 | QModelIndex idx; 198 | bool wrapped = false; 199 | int i = currentIndex().row(); 200 | while (!idx.isValid() || m_model->isDir(idx)) { 201 | i = (prevoius ? i - 1 : i + 1); 202 | if (i < 0 || i >= n) { 203 | if (wrapped) 204 | break; 205 | i = (prevoius ? n - 1 : 0); 206 | wrapped = true; 207 | } 208 | idx = m_model->index(i, 0, rootIndex()); 209 | } 210 | if (idx.isValid() && m_model->isDir(idx)) 211 | idx = QModelIndex(); 212 | return idx; 213 | } 214 | 215 | 216 | // 217 | // ImageGridDelegate 218 | // 219 | 220 | void ImageGridDelegate::paint(QPainter *p, const QStyleOptionViewItem &opt, const QModelIndex &idx) const 221 | { 222 | if (!idx.isValid() || opt.rect.isEmpty() || opt.decorationSize.isEmpty()) 223 | return; 224 | const QPixmap pm(pixmap(idx, opt.decorationSize - QSizeF(frameWidth * 2, frameWidth * 2).toSize())); 225 | if (pm.isNull()) 226 | return; 227 | QRectF rect = QRectF(opt.rect).adjusted(frameWidth, frameWidth, -frameWidth, -frameWidth); 228 | QStyle *style = opt.widget ? opt.widget->style() : QApplication::style(); 229 | const QRect arect = style->alignedRect(opt.direction, opt.displayAlignment, pm.size(), QRect(QPoint(0,0), rect.size().toSize())); 230 | rect.moveTopLeft(rect.topLeft() + arect.topLeft()); 231 | //qDebug() << opt.rect << rect << opt.decorationSize << pm.size(); 232 | p->drawPixmap(rect.topLeft(), pm); // use point position to avoid any painter scaling check 233 | if (opt.state & QStyle::State_Selected) { 234 | // draw selection rectangle 235 | p->setBrush(Qt::NoBrush); 236 | p->setPen(QPen(opt.palette.brush(QPalette::Highlight), frameWidth, Qt::DashLine, Qt::RoundCap)); 237 | p->drawRect(rect.adjusted(0, 0, arect.x() * -2, arect.y() * -2)); 238 | } 239 | } 240 | 241 | QPixmap ImageGridDelegate::pixmap(const QModelIndex &idx, const QSize &size) const 242 | { 243 | QPixmap pm; 244 | if (!idx.isValid()) 245 | return pm; 246 | const QString imgFile = idx.data(QFileSystemModel::FilePathRole).toString(); 247 | if (imgFile.isEmpty() || !QFileInfo(imgFile).exists()) 248 | return pm; 249 | const QString hash(imgFile.simplified().append(QLatin1String("_")).append(QString::number(quint64(size.width()) << 32 | uint(size.height())))); 250 | QPixmapCache::find(hash, &pm); 251 | if (pm.isNull()) { 252 | //qDebug() << "Cache miss!" << imgFile << sz << hash; 253 | pm.load(imgFile); 254 | if (!pm.isNull()) { 255 | if (pm.size().width() > size.width() || pm.size().height() > size.height()) 256 | pm = pm.scaled(size, Qt::KeepAspectRatio, Qt::FastTransformation); 257 | QPixmapCache::insert(hash, pm); 258 | } 259 | } 260 | return pm; 261 | } 262 | 263 | bool ImageGridDelegate::helpEvent(QHelpEvent *e, QAbstractItemView *v, const QStyleOptionViewItem &, const QModelIndex &idx) 264 | {; 265 | if (!idx.isValid() || !e || !v || (e->type() == QEvent::ToolTip && !showTooltips)) 266 | return false; 267 | const QFileInfo fi(idx.data(QFileSystemModel::FilePathRole).toString()); 268 | if (!fi.isFile()) 269 | return false; 270 | const QString tt(fi.fileName() % "\nC: " % fi.created().toString(Qt::TextDate) % "\nM: " % 271 | fi.lastModified().toString(Qt::TextDate) % "\n" % QString::number(fi.size() / 1024) % " KB"); 272 | switch (e->type()) { 273 | case QEvent::ToolTip: 274 | QToolTip::showText(e->globalPos(), tt, v); 275 | return true; 276 | case QEvent::WhatsThis: 277 | QWhatsThis::showText(e->globalPos(), tt, v); 278 | return true; 279 | case QEvent::QueryWhatsThis: 280 | return true; 281 | default: 282 | return false; 283 | } 284 | } 285 | 286 | #include "moc_ImageGrid.cpp" 287 | -------------------------------------------------------------------------------- /src/itemmodels/GroupedItemsProxyModel.h: -------------------------------------------------------------------------------- 1 | /* 2 | GroupedItemsProxyModel 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef GROUPEDITEMSPROXYMODEL_H 29 | #define GROUPEDITEMSPROXYMODEL_H 30 | 31 | #include 32 | #include 33 | 34 | /** 35 | \class GroupedItemsProxyModel 36 | \version 1.0.0 37 | 38 | \brief GroupedItemsProxyModel allows a grouped item presentation of a flat table data model. 39 | 40 | GroupedItemsProxyModel allows a grouped item presentation of a flat table data model. Items can be visually grouped by data in one or more 41 | columns of the original data. Any number of groupings can be nested. GroupedItemsProxyModel inherits from QIdentityProxyModel and 42 | proxies all data from the original model unmodified. Only the visual presentation is affected, for example like \e QSortFilterProxyModel. 43 | 44 | Groupings can be managed with addGroup(), insertGroup() and removeGroup(). Each function takes a column number as a parameter. The source 45 | data is scanned, and a new grouping row is created for each unique value found in the given column. The title (and icon, if any) of the grouping 46 | row is taken from the source data (\c Qt::DisplayRole and \c Qt::DecorationRole respectively). By default, items are grouped based on 47 | their \c Qt::EditRole but you can change this with setGroupMatchRole(). 48 | 49 | When grouping is active, a new first column is (optionally) added which can be used to sort the groupings. By default the groups are shown 50 | in the order in which they were found in the source data. The extra column can be disabled with setGroupColumnVisible(). The title of the column 51 | can be set with setGroupHeaderTitle(). 52 | 53 | This model can be cascased with other models. For example set this model as the source for a \e QSortFilterProxyModel to enable sorting 54 | the view (including the grouping items as mentioned above). 55 | 56 | This model can only handle flat data models as input (table/list). It will not include any children of the top-level items. 57 | 58 | When no groupings have been added, this acts as a transparent proxy (see \e QIdentityProxyModel). However the limitation of only flat source 59 | models still applies. 60 | 61 | */ 62 | class GroupedItemsProxyModel : public QIdentityProxyModel 63 | { 64 | Q_OBJECT 65 | Q_PROPERTY(int groupMatchRole READ groupMatchRole WRITE setGroupMatchRole) 66 | Q_PROPERTY(bool groupColumnVisible READ groupColumnVisible WRITE setGroupColumnVisible) 67 | Q_PROPERTY(bool groupColumnIsProxy READ groupColumnIsProxy WRITE setGroupColumnIsProxy) 68 | Q_PROPERTY(int groupColumnProxySrc READ groupColumnProxySrc WRITE setGroupColumnProxySrc) 69 | Q_PROPERTY(bool groupRowSelectable READ groupRowSelectable WRITE setGroupRowSelectable) 70 | Q_PROPERTY(QString groupHeaderTitle READ groupHeaderTitle WRITE setGroupHeaderTitle) 71 | 72 | public: 73 | enum { SourceRowNumberRole = Qt::UserRole - 5 }; 74 | 75 | explicit GroupedItemsProxyModel(QObject *parent = Q_NULLPTR, QAbstractItemModel *sourceModel = Q_NULLPTR, const QVector &groupColumns = QVector()); 76 | ~GroupedItemsProxyModel(); 77 | 78 | // QAbstractItemModel interface 79 | void setSourceModel(QAbstractItemModel *newSourceModel) Q_DECL_OVERRIDE; 80 | QModelIndex index(int row, int column, const QModelIndex &parent) const Q_DECL_OVERRIDE; 81 | QModelIndex parent(const QModelIndex &child) const Q_DECL_OVERRIDE; 82 | int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 83 | int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 84 | bool hasChildren(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE; 85 | QVariant data(const QModelIndex &index, int role = Qt::EditRole) const Q_DECL_OVERRIDE; 86 | QMap itemData(const QModelIndex &index) const Q_DECL_OVERRIDE; 87 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE; 88 | Qt::ItemFlags flags(const QModelIndex &index) const Q_DECL_OVERRIDE; 89 | QSize span(const QModelIndex &index) const Q_DECL_OVERRIDE; 90 | 91 | // QAbstractProxyModel interface 92 | QModelIndex mapToSource(const QModelIndex &proxyIndex) const Q_DECL_OVERRIDE; 93 | QModelIndex mapFromSource(const QModelIndex &sourceIndex) const Q_DECL_OVERRIDE; 94 | 95 | // GroupedItemProxyModel interface 96 | 97 | /*! \property groupMatchRole Which data role to use for matching group data. Default is \c Qt::EditRole. \sa setGroupMatchRole() */ 98 | inline int groupMatchRole() const { return m_groupMatchRole; } 99 | /*! \property groupColumnVisible Show the extra grouping column (eg. to allow sorting on it). Default is \c{true}. \sa setGroupColumnVisible() */ 100 | inline int groupColumnVisible() const { return m_groupColumnVisible; } 101 | /*! \property groupColumnIsProxy Proxy data from source column to the extra grouping column (if shown). This includes display data. Default is \c{false}. \sa setGroupColumnIsProxy() */ 102 | inline int groupColumnIsProxy() const { return m_groupColumnIsProxy; } 103 | /*! \property groupColumnProxySrc Which column to use for proxy data. A value of \{-1} (default) means to use the grouping column. \sa setGroupColumnProxySrc(), groupColumnIsProxy() */ 104 | inline int groupColumnProxySrc() const { return m_groupColumnProxySrc; } 105 | /*! \property groupRowSelectable Allow selecting the grouping row. Default is \c{false} \sa setGroupRowSelectable() */ 106 | inline int groupRowSelectable() const { return m_groupRowSelectable; } 107 | /*! \property groupHeaderTitle Title of grouping column. Default is \c "Group". Set an empty string to hide title. \sa setGroupHeaderTitle() */ 108 | inline QString groupHeaderTitle() const { return m_root->data(Qt::DisplayRole).toString(); } 109 | 110 | public slots: 111 | /*! Add a new grouping based on \a column. \sa removeGroup() */ 112 | inline void addGroup(int column) { addGroups(QVector() << column); } 113 | /*! Add multiple groupings based on \a columns. The groups are appended to any existing group(s). \sa clearGroups() */ 114 | virtual void addGroups(const QVector & columns); 115 | /*! Add multiple groupings based on \a columns. Any existing group(s) are first cleared. \sa clearGroups() */ 116 | virtual void setGroups(const QVector & columns); 117 | /*! Insert a new grouping at \a index based on \a column. \sa addGroup(), removeGroup() */ 118 | virtual void insertGroup(int index, int column); 119 | /*! Remove the previously-added \a column grouping. \sa addGroup() */ 120 | virtual void removeGroup(int column); 121 | /*! Remove the previously-added groupings. \sa addGroup(), addGroups() */ 122 | virtual void clearGroups(); 123 | // properties 124 | virtual void setGroupMatchRole(int role); 125 | virtual void setGroupColumnVisible(bool visible) { m_groupColumnVisible = visible; } 126 | virtual void setGroupColumnIsProxy(bool enable) { m_groupColumnIsProxy = enable; } 127 | virtual void setGroupColumnProxySrc(int column) { m_groupColumnProxySrc = column; } 128 | virtual void setGroupRowSelectable(bool selectable) { m_groupRowSelectable = selectable; } 129 | virtual void setGroupHeaderTitle(const QString &title, const QString &tooltip = QString()); 130 | 131 | protected: 132 | class GroupedProxyItem; // forward 133 | 134 | virtual uint extraColumns() const { return (!m_groups.isEmpty() && groupColumnVisible() ? 1 : 0); } 135 | virtual QModelIndex indexForItem(GroupedProxyItem *item, const int col = 0) const; 136 | virtual GroupedProxyItem * itemForIndex(const QModelIndex &index, const bool rootDefault = false) const; 137 | virtual GroupedProxyItem * findGroupItem(const int group, const QVariant &value, GroupedProxyItem *parent = NULL) const; 138 | virtual QModelIndex sourceIndexForProxy(GroupedProxyItem *item) const; 139 | virtual bool isProxyColumn(const QModelIndex &index) const { return (index.isValid() && extraColumns() && index.column() == 0 && groupColumnIsProxy()); } 140 | int totalRowCount(GroupedProxyItem *parent = NULL) const; 141 | GroupedProxyItem * itemForRow(int row, GroupedProxyItem *parent = NULL) const; 142 | 143 | protected slots: 144 | virtual GroupedProxyItem * placeSourceRow(const int row); 145 | virtual void removeItem(GroupedProxyItem *item); 146 | virtual void removeUnusedGroups(GroupedProxyItem *parent = NULL); 147 | virtual void reloadSourceModel(); 148 | virtual void modelResetHandler(); 149 | virtual void dataChangedHandler(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector &roles); 150 | virtual void rowsInsertedHandler(const QModelIndex &parent, int first, int last); 151 | virtual void rowsRemovedHandler(const QModelIndex &parent, int first, int last); 152 | virtual void rowsMovedHandler(const QModelIndex &parent, int start, int end, const QModelIndex &destination, int row); 153 | 154 | protected: 155 | GroupedProxyItem * m_root; 156 | QHash m_sourceMap; 157 | QVector m_groups; 158 | int m_groupMatchRole; 159 | int m_groupColumnProxySrc; 160 | bool m_groupColumnVisible; 161 | bool m_groupColumnIsProxy; 162 | bool m_groupRowSelectable; 163 | 164 | private: 165 | bool setReloadSuspended(bool enable) { bool prev = m_reloadSuspended; m_reloadSuspended = enable; return prev; } 166 | bool reloadSuspended() const { return m_reloadSuspended; } 167 | 168 | bool m_reloadSuspended; 169 | 170 | 171 | protected: 172 | class GroupedProxyItem 173 | { 174 | public: 175 | explicit GroupedProxyItem(const QModelIndex &srcIndex, const bool isSrcItem = false, GroupedProxyItem *parent = NULL); 176 | explicit GroupedProxyItem(GroupedProxyItem *parent = NULL); 177 | virtual ~GroupedProxyItem(); 178 | 179 | int row() const; 180 | QVariant data(int role = Qt::EditRole) const { return m_itemData.value(role, QVariant()); } 181 | QMap itemData() const { return m_itemData; } 182 | int rowCount() const { return m_childItems.size(); } 183 | QVector children() const { return m_childItems; } 184 | GroupedProxyItem * child(const int row) const { return m_childItems.value(row, NULL); } 185 | int childRow(GroupedProxyItem * child) const { return m_childItems.indexOf(child); } 186 | GroupedProxyItem * parent() const { return m_parentItem; } 187 | 188 | QModelIndex sourceIndex() const { return m_sourceIndex; } 189 | bool isSourceItem() const { return m_isSourceItem; } 190 | bool isGroupItem() const { return !isSourceItem(); } 191 | 192 | void clear(); 193 | GroupedProxyItem * addChild(const QModelIndex &srcIndex, const bool isSrcItem = true); 194 | void removeChild(GroupedProxyItem * item); 195 | void removeChild(const int row) { removeChild(child(row)); } 196 | bool setData(const QVariant &data, int role = Qt::EditRole); 197 | bool setItemData(const QMap &roles); 198 | 199 | protected: 200 | GroupedProxyItem * m_parentItem; 201 | QVector m_childItems; 202 | QMap m_itemData; 203 | QPersistentModelIndex m_sourceIndex; 204 | bool m_isSourceItem; 205 | }; // GroupedProxyItem 206 | \ 207 | }; // GroupedItemsProxyModel 208 | 209 | #endif // GROUPEDITEMSPROXYMODEL_H 210 | -------------------------------------------------------------------------------- /src/core/AppDebugMessageHandler.h: -------------------------------------------------------------------------------- 1 | /* 2 | AppDebugMessageHandler 3 | https://github.com/mpaperno/maxLibQt 4 | 5 | COPYRIGHT: (c)2017 Maxim Paperno; All Right Reserved. 6 | Contact: http://www.WorldDesign.com/contact 7 | 8 | LICENSE: 9 | 10 | Commercial License Usage 11 | Licensees holding valid commercial licenses may use this file in 12 | accordance with the terms contained in a written agreement between 13 | you and the copyright holder. 14 | 15 | GNU General Public License Usage 16 | Alternatively, this file may be used under the terms of the GNU 17 | General Public License as published by the Free Software Foundation, 18 | either version 3 of the License, or (at your option) any later version. 19 | 20 | This program is distributed in the hope that it will be useful, 21 | but WITHOUT ANY WARRANTY; without even the implied warranty of 22 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 23 | GNU General Public License for more details. 24 | 25 | A copy of the GNU General Public License is available at . 26 | */ 27 | 28 | #ifndef APPDEBUGMESSAGEHANDLER_H 29 | #define APPDEBUGMESSAGEHANDLER_H 30 | 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | 38 | //! Enable/disable this custom handler handler entirely \relates AppDebugMessageHandler 39 | #ifndef APP_DBG_HANDLER_ENABLE 40 | #define APP_DBG_HANDLER_ENABLE 1 41 | #endif 42 | 43 | //! Default log level. \sa AppDebugMessageHandler::appDebugOutputLevel \relates AppDebugMessageHandler 44 | #ifndef APP_DBG_HANDLER_DEFAULT_LEVEL 45 | #define APP_DBG_HANDLER_DEFAULT_LEVEL 0 46 | #endif 47 | 48 | //! Include source file path/name in output (filtered with APP_DBG_HANDLER_SRC_PATH). 49 | //! \sa AppDebugMessageHandler::showSourcePath \relates AppDebugMessageHandler 50 | #ifndef APP_DBG_HANDLER_SHOW_SRC_PATH 51 | #define APP_DBG_HANDLER_SHOW_SRC_PATH 0 52 | #endif 53 | 54 | //! Show full function declaration with return and attribute types instead of just the Class::Name. 55 | //! \sa AppDebugMessageHandler::showFunctionDeclarations \relates AppDebugMessageHandler 56 | #ifndef APP_DBG_HANDLER_SHOW_FUNCTION_DECL 57 | #define APP_DBG_HANDLER_SHOW_FUNCTION_DECL 0 58 | #endif 59 | 60 | //! Include timestamp in default message pattern. \sa AppDebugMessageHandler::showTimestamp \relates AppDebugMessageHandler 61 | #ifndef APP_DBG_HANDLER_SHOW_TIMESTAMP 62 | #define APP_DBG_HANDLER_SHOW_TIMESTAMP 0 63 | #endif 64 | 65 | //! Default timestamp format (as per Qt message pattern docs for %{time ...} options). 66 | //! \sa AppDebugMessageHandler::timestampFormat \relates AppDebugMessageHandler 67 | #ifndef APP_DBG_HANDLER_TIMESTAMP_FORMAT 68 | #define APP_DBG_HANDLER_TIMESTAMP_FORMAT "process" 69 | #endif 70 | 71 | //! base path for source file name filter, everything after this is kept; RegEx, non-greedy 72 | //! \sa AppDebugMessageHandler::setSourceBasePath() \relates AppDebugMessageHandler 73 | #ifndef APP_DBG_HANDLER_SRC_PATH 74 | #define APP_DBG_HANDLER_SRC_PATH ".*src" 75 | #endif 76 | 77 | // Make sure to include this header if using qInfo() 78 | #if (QT_VERSION < QT_VERSION_CHECK(5, 5, 0)) 79 | #if defined(QT_NO_INFO_OUTPUT) 80 | #define qInfo QT_NO_QDEBUG_MACRO 81 | #else 82 | #define qInfo qDebug 83 | #endif 84 | #define QtInfoMsg QtMsgType(4) 85 | #endif 86 | 87 | /*! 88 | \class AppDebugMessageHandler 89 | \version 2.0.0 90 | 91 | \brief Custom debug/message handler class to work in conjunction with \c qDebug() family of functions. 92 | 93 | AppDebugMessageHandler helps with formatting all stderr output generated by the application in a consistent manner. 94 | For example the function and line from which the debug message is called is always printed. 95 | It can also be used by other event listeners to intercept messages they may be interested in (connect to \c messageOutput() signal 96 | or add a \c QIODevice to receive the output). 97 | 98 | You can set a minimum logging level for all messages by setting the \ref appDebugOutputLevel property or \c APP_DBG_HANDLER_DEFAULT_LEVEL macro. 99 | 100 | There are several properties which control the default output message formatting. Optionally, a custom pattern can be set with 101 | \c setMessagePattern(). This type of custom pattern allows for additional placeholder values which are not available with 102 | the default Qt handler. See \c setMessagePattern() for details. 103 | 104 | Note that the message format can still be overridden by setting \e QT_MESSAGE_PATTERN environment variable at _run-time_. 105 | see: https://doc.qt.io/qt-5/qtglobal.html#qSetMessagePattern 106 | 107 | Include this header to use qInfo() safely with Qt < v5.5 (qInfo becomes alias for qDebug). 108 | 109 | This is an app-wide "global" thread-safe singleton class, use it with \c AppDebugMessageHandler::instance(). 110 | For example, at start of application: 111 | 112 | \code 113 | QApplication app(argc, argv); 114 | AppDebugMessageHandler::instance()->installAppMessageHandler(); 115 | \endcode 116 | 117 | To connect to a signal: 118 | 119 | \code 120 | connect(AppDebugMessageHandler::instance(), &AppDebugMessageHandler::messageOutput, this, &DebugOutput::onAppDebugMessage);} 121 | \endcode 122 | 123 | To add a new I/O stream for receiving messages (eg. a log file): 124 | 125 | \code 126 | addOutputDevice(myQFile); 127 | \endcode 128 | 129 | When adding a QIODevice in this way, you may optionally set a minimum severity level below which you do not want messages 130 | written to this device. Use the Qt property system to set a properly named \a level on the QIODevice. For example: 131 | 132 | \code 133 | myQFile.setProperty("level", 2); // only warning and higher 134 | \endcode 135 | 136 | */ 137 | class AppDebugMessageHandler : public QObject 138 | { 139 | Q_OBJECT 140 | //! Minimum severity level of messages to print. 0 = debug+; 1 = info+; 2 = warning+; 3 = critical+; 4 = fatal only. \pacc appDebugOutputLevel(void), setAppDebugOutputLevel() 141 | Q_PROPERTY(int appDebugOutputLevel READ appDebugOutputLevel WRITE setAppDebugOutputLevel) 142 | //! Enables or disabled showing the file path in the default message pattern. \pacc showSourcePath(void), setShowSourcePath() \sa setSourceBasePath() 143 | Q_PROPERTY(bool showSourcePath READ showSourcePath WRITE setShowSourcePath) 144 | //! Enables or disables showing full function declarations (with return and attribute types) in the default message pattern. \pacc showFunctionDeclarations(void), setShowFunctionDeclarations() 145 | Q_PROPERTY(bool showFunctionDeclarations READ showFunctionDeclarations WRITE setShowFunctionDeclarations) 146 | //! Enables or disables showing timestamps in the default message pattern. \pacc showTimestamp(void), setShowTimestamp() \sa timestampFormat 147 | Q_PROPERTY(bool showTimestamp READ showTimestamp WRITE setShowTimestamp) 148 | //! Set the timestamp format, as per Qt message pattern docs for \c %{time ...} options. E.g. one of "process", "boot", or a \c QDateTime::toString() format. \pacc timestampFormat(void), setTimestampFormat() \sa showTimestamp 149 | Q_PROPERTY(QString timestampFormat READ timestampFormat WRITE setTimestampFormat) 150 | 151 | public: 152 | //! Get the singleton instance of this object. The instance is created automatically if necessary. 153 | static AppDebugMessageHandler *instance(); 154 | //! This function must be called to initialize this custom message handler. E.g. \c AppDebugMessageHandler::instance()->installAppMessageHandler() 155 | void installAppMessageHandler(); 156 | 157 | inline quint8 appDebugOutputLevel() const { return m_appDebugOutputLevel; } //!< \sa appDebugOutputLevel 158 | void setAppDebugOutputLevel(quint8 appDebugOutputLevel); //!< \sa appDebugOutputLevel 159 | 160 | inline bool showSourcePath() const { return m_showSourcePath; } //!< \sa showSourcePath 161 | void setShowSourcePath(bool showSourcePath); //!< \sa showSourcePath 162 | //! Set the base path for source file name filter, everything after this is kept. The string could be literal or a RegEx pattern (evaluated as non-greedy). 163 | void setSourceBasePath(const QString &path = QString()); 164 | 165 | inline bool showFunctionDeclarations() const { return m_showFunctionDeclarations; } //!< \sa showFunctionDeclarations 166 | void setShowFunctionDeclarations(bool showFunctionDeclarations); //!< \sa showFunctionDeclarations 167 | 168 | inline bool showTimestamp() const { return m_showTimestamp; } //!< \sa showTimestamp 169 | void setShowTimestamp(bool showTimestamp); //!< \sa showTimestamp 170 | 171 | inline QString timestampFormat() const { return m_tsFormat; } //!< \sa timestampFormat 172 | void setTimestampFormat(const QString &timeFormat); //!< \sa timestampFormat 173 | 174 | //! Add a new I/O stream for receiving messages. 175 | void addOutputDevice(QIODevice *device); 176 | //! Remove a previously-added I/O stream. 177 | void removeOutputDevice(QIODevice *device); 178 | 179 | //! Returns the current message pattern in use. This will be either the one set specifically with \c setMessagePattern() or the \c defaultMessagePattern() otherwise. 180 | QString messagePattern() const; 181 | //! Returns the default message pattern. This format will take into account any properties set previously, such as \c showSourcePath, \c showFunctionDeclarations, \c showTimestamp and \c timestampFormat. 182 | QString defaultMessagePattern() const; 183 | 184 | /*! Specifies a debug message pattern to use instead of the default. 185 | The \a pattern is the same as for \c qSetMessagePattern() but extended with the following attributes: 186 | 187 | \li \c %{short-type} : Short type name (eg. "W" for warning, "D" for debug, etc) (as returned by \c shortTypeNames()) 188 | \li \c %{full-function} : Full function declaration including return and attribute types (if any) 189 | 190 | Call this function with a blank \c QString to reset the message pattern back to default. 191 | */ 192 | void setMessagePattern(const QString &pattern = QString()); 193 | 194 | //! Handle a debug message. This is typically called by the installed global message handler callback ( \c g_appDebugMessageHandler() ). 195 | void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &msg); 196 | 197 | /*! 198 | Returns a reference to the mapping of \c QtMsgType enum values to abbreviated names. 199 | New names can be specified by setting this reference to a new value. For example: 200 | \code 201 | AppDebugMessageHandler::shortTypeNames() = { 202 | {QtDebugMsg, QStringLiteral("DBG")}, 203 | {QtWarningMsg, QStringLiteral("WRN")}, 204 | {QtCriticalMsg, QStringLiteral("CRT")}, 205 | {QtFatalMsg, QStringLiteral("FTL")}, 206 | {QtInfoMsg, QStringLiteral("INF")} 207 | }; 208 | \endcode 209 | */ 210 | static QHash &shortTypeNames(); 211 | 212 | #if (QT_VERSION < QT_VERSION_CHECK(5, 4, 0)) 213 | static QHash &fullTypeNames(); 214 | #endif 215 | 216 | signals: 217 | //! Notifies when a new log massage is available. 218 | void messageOutput(quint8 level, const QString &msg); 219 | 220 | private: 221 | explicit AppDebugMessageHandler(); 222 | Q_DISABLE_COPY(AppDebugMessageHandler) 223 | 224 | QtMessageHandler m_defaultHandler; 225 | QRegularExpression m_srcPathFilter; 226 | quint8 m_appDebugOutputLevel; 227 | QVector m_outputDevices; 228 | bool m_showSourcePath; 229 | bool m_showFunctionDeclarations; 230 | bool m_showTimestamp; 231 | QString m_tsFormat; 232 | QString m_msgPattern; 233 | mutable QString m_defaultPattern; 234 | QReadWriteLock m_mutex; 235 | #if (QT_VERSION < QT_VERSION_CHECK(5, 4, 0)) 236 | QRegularExpression m_functionFilter; 237 | #endif 238 | 239 | }; 240 | 241 | //! \relates AppDebugMessageHandler 242 | //! Message handler which is installed using qInstallMessageHandler. This needs to be global. 243 | //! Use AppDebugMessageHandler::instance()->installAppMessageHandler(); 244 | //! instead of installing this function directly (it will verify this handler is enabled, etc). 245 | void g_appDebugMessageHandler(QtMsgType, const QMessageLogContext &, const QString &); 246 | 247 | #endif // APPDEBUGMESSAGEHANDLER_H 248 | --------------------------------------------------------------------------------