├── .gitignore ├── .travis.yml ├── .travis ├── deploy_pages.sh └── run-all-tests.sh ├── CMakeLists.txt ├── LICENSE ├── QuickFluxConfig.cmake.in ├── README.md ├── appveyor.yml ├── docs ├── concept │ ├── after-using-flux.jpg │ ├── architecture.png │ ├── before-using-flux.jpg │ └── middleware-data-flow.png ├── index.qdoc └── quickflux.qdocconf ├── examples ├── README.md ├── middleware │ ├── .gitignore │ ├── CMakeLists.txt │ ├── README.md │ ├── actions │ │ ├── ActionTypes.qml │ │ ├── AppActions.qml │ │ └── qmldir │ ├── components │ │ └── ImagePreview.qml │ ├── deployment.pri │ ├── main.cpp │ ├── main.qml │ ├── middleware.pro │ ├── middlewares │ │ ├── ImagePickerMiddleware.qml │ │ └── NavigationMiddleware.qml │ ├── qml.qrc │ ├── stores │ │ ├── MainStore.qml │ │ ├── RootStore.qml │ │ └── qmldir │ └── views │ │ ├── ImageViewer.qml │ │ └── StackView.qml ├── photoalbum │ ├── .gitignore │ ├── README.md │ ├── actions │ │ ├── ActionTypes.qml │ │ ├── AppActions.qml │ │ └── qmldir │ ├── deployment.pri │ ├── main.cpp │ ├── main.qml │ ├── photoalbum.pro │ ├── qml.qrc │ ├── scripts │ │ └── ImagePickerScript.qml │ ├── stores │ │ ├── PhotoStore.qml │ │ └── qmldir │ └── views │ │ ├── ImagePreview.qml │ │ ├── ImageViewer.qml │ │ └── StackView.qml └── todo │ ├── .gitignore │ ├── CMakeLists.txt │ ├── MainWindow.qml │ ├── README.md │ ├── actions │ ├── ActionTypes.qml │ ├── AppActions.qml │ └── qmldir │ ├── main.cpp │ ├── main.qml │ ├── middlewares │ ├── DialogMiddleware.qml │ └── SystemMiddleware.qml │ ├── qml.qrc │ ├── stores │ ├── MainStore.qml │ ├── RootStore.qml │ ├── TodoStore.qml │ ├── UserPrefsStore.qml │ └── qmldir │ ├── todo.pro │ └── views │ ├── Footer.qml │ ├── Header.qml │ ├── TodoItem.qml │ ├── TodoList.qml │ └── TodoVisualModel.qml ├── qpm.json ├── quickflux.pri ├── quickflux.pro ├── src ├── QFAppDispatcher ├── QFKeyTable ├── QuickFlux ├── priv │ ├── qfappscriptdispatcherwrapper.h │ ├── qfappscriptrunnable.h │ ├── qfhook.cpp │ ├── qfhook.h │ ├── qflistener.h │ ├── qfmiddlewareshook.cpp │ ├── qfmiddlewareshook.h │ ├── qfsignalproxy.cpp │ ├── qfsignalproxy.h │ ├── quickfluxfunctions.cpp │ └── quickfluxfunctions.h ├── qfactioncreator.cpp ├── qfactioncreator.h ├── qfappdispatcher.cpp ├── qfappdispatcher.h ├── qfapplistener.cpp ├── qfapplistener.h ├── qfapplistenergroup.cpp ├── qfapplistenergroup.h ├── qfappscript.cpp ├── qfappscript.h ├── qfappscriptdispatcherwrapper.cpp ├── qfappscriptgroup.cpp ├── qfappscriptgroup.h ├── qfappscriptrunnable.cpp ├── qfdispatcher.cpp ├── qfdispatcher.h ├── qffilter.cpp ├── qffilter.h ├── qfhydrate.cpp ├── qfhydrate.h ├── qfkeytable.cpp ├── qfkeytable.h ├── qflistener.cpp ├── qfmiddleware.cpp ├── qfmiddleware.h ├── qfmiddlewarelist.cpp ├── qfmiddlewarelist.h ├── qfobject.cpp ├── qfobject.h ├── qfqmltypes.cpp ├── qfstore.cpp ├── qfstore.h └── src.pri ├── templates └── quickflux-project-template │ ├── .gitignore │ ├── App │ ├── actions │ │ ├── ActionTypes.qml │ │ ├── AppActions.qml │ │ └── qmldir │ ├── adapters │ │ ├── StoreAdapter.qml │ │ └── qmldir │ ├── constants │ │ ├── Constants.qml │ │ └── qmldir │ ├── stores │ │ ├── MainStore.qml │ │ └── qmldir │ └── storeworkers │ │ ├── MainStoreWorker.qml │ │ ├── StoreWorker.qml │ │ └── qmldir │ ├── README.md │ ├── app.pri │ ├── app.pro │ ├── app.qrc │ ├── appview.cpp │ ├── appview.h │ ├── deployment.pri │ ├── main.cpp │ ├── main.qml │ └── qpm.json └── tests └── quickfluxunittests ├── .gitignore ├── QuickFluxTests ├── ActionTypes.qml ├── AppActions.qml ├── AppActionsKeyTable.qml ├── DispatcherTests.qml ├── DummyAction.qml └── qmldir ├── actiontypes.cpp ├── actiontypes.h ├── dummy.qml ├── main.cpp ├── messagelogger.cpp ├── messagelogger.h ├── qml.qrc ├── qmltests ├── tst_actioncreator.qml ├── tst_appdispatcher.qml ├── tst_appdispatcher_dispatch_reentrant.qml ├── tst_appdispatcher_waitfor.qml ├── tst_applistener.qml ├── tst_applistener_alwayson.qml ├── tst_applistener_filter.qml ├── tst_applistener_waitfor.qml ├── tst_applistenergroup.qml ├── tst_appscript.qml ├── tst_appscriptgroup.qml ├── tst_filter.qml ├── tst_hydrate.qml ├── tst_keytable.qml ├── tst_middleware_exception.qml ├── tst_middleware_filterFunctionEnabled.qml ├── tst_middlewarelist.qml ├── tst_middlewarelist_applyTarget.qml ├── tst_qimage.qml ├── tst_store.qml ├── tst_store_bridge.qml └── tst_store_children.qml ├── qpm.json ├── quickfluxunittests.cpp ├── quickfluxunittests.h ├── quickfluxunittests.pro ├── testenv.cpp └── testenv.h /.gitignore: -------------------------------------------------------------------------------- 1 | # C++ objects and libs 2 | 3 | *.slo 4 | *.lo 5 | *.o 6 | *.a 7 | *.la 8 | *.lai 9 | *.so 10 | *.dll 11 | *.dylib 12 | 13 | # Qt-es 14 | 15 | /.qmake.cache 16 | /.qmake.stash 17 | *.pro.user 18 | *.pro.user.* 19 | *.qbs.user 20 | *.qbs.user.* 21 | *.moc 22 | moc_*.cpp 23 | qrc_*.cpp 24 | ui_*.h 25 | Makefile* 26 | *-build-* 27 | 28 | # QtCreator 29 | 30 | *.autosave 31 | 32 | #QtCtreator Qml 33 | *.qmlproject.user 34 | *.qmlproject.user.* 35 | 36 | build-* 37 | html 38 | build 39 | vendor 40 | *.qmlc 41 | *.swp 42 | CMakeLists.txt.user 43 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | language : cpp 3 | dist: bionic 4 | services: 5 | - docker 6 | - xvfb 7 | env: 8 | - DISPLAY=:99.0 9 | compiler: 10 | - gcc 11 | before_install: 12 | - export PATH=$PWD/.travis:$PATH 13 | - export DISPLAY=:99.0 14 | - docker pull benlau/qtsdk:5.9.9 15 | - export GOPATH=`pwd`/gosrc 16 | - export PATH=`pwd`/gosrc/bin:$PATH 17 | - go get qpm.io/qpm 18 | 19 | script: 20 | - sleep 3 21 | - pushd tests/quickfluxunittests 22 | - qpm install 23 | - popd 24 | - docker run --rm --network host -v "$PWD:/src" -t benlau/qtsdk:5.9.9 bash -c 'cd src && .travis/run-all-tests.sh' 25 | -------------------------------------------------------------------------------- /.travis/deploy_pages.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e # exit with nonzero exit code if anything fails 3 | 4 | # clear and re-create the out directory 5 | rm -rf build || exit 0; 6 | mkdir build; 7 | 8 | # run our compile script, discussed above 9 | (cd docs; qdoc quickflux.qdocconf) 10 | 11 | REPO=$(git config remote.origin.url) 12 | 13 | # go to the out directory and create a *new* Git repo 14 | cd build 15 | cp -a ../docs/html/* . 16 | git init 17 | 18 | # The first and only commit to this new Git repo contains all the 19 | # files present with the commit message "Deploy to GitHub Pages". 20 | git add . 21 | git commit --author="Ben Lau " -m "Deploy to GitHub Pages" 22 | 23 | # Force push from the current repo's master branch to the remote 24 | # repo's gh-pages branch. (All previous history on the gh-pages branch 25 | # will be lost, since we are overwriting it.) We redirect any output to 26 | # /dev/null to hide any sensitive credential data that might otherwise be exposed. 27 | git push --force $REPO master:gh-pages 28 | -------------------------------------------------------------------------------- /.travis/run-all-tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export DISPLAY=:99.0 4 | set -v 5 | set -e 6 | export QT_QPA_PLATFORM=minimal 7 | pushd tests/quickfluxunittests 8 | qmake quickfluxunittests.pro 9 | make 10 | ./quickfluxunittests 11 | popd 12 | 13 | pushd examples/todo 14 | qmake 15 | make 16 | make clean 17 | mkdir build 18 | cd build 19 | cmake ../ 20 | make 21 | popd 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # 2 | # To build it with cmake, you should register qml types explicitly by calling registerQuickFluxQmlTypes() in your main.cpp 3 | # See examples/middleware for example 4 | # 5 | 6 | cmake_minimum_required(VERSION 3.0.0) 7 | project(quickflux VERSION 1.1.3) 8 | 9 | option(quickflux_INSTALL "Enable the installation of targets." ON) 10 | 11 | if(MSVC) 12 | set_property (GLOBAL PROPERTY USE_FOLDERS ON) 13 | endif() 14 | 15 | set(SRC_DIR "${PROJECT_SOURCE_DIR}/src") 16 | 17 | include(GNUInstallDirs) 18 | find_package(Qt5 COMPONENTS Core Quick Qml Gui CONFIG REQUIRED) 19 | 20 | set(quickflux_PRIVATE_SOURCES 21 | ${SRC_DIR}/priv/qfhook.cpp 22 | ${SRC_DIR}/priv/qfmiddlewareshook.cpp 23 | ${SRC_DIR}/priv/qfsignalproxy.cpp 24 | ${SRC_DIR}/priv/quickfluxfunctions.cpp 25 | ) 26 | 27 | set(quickflux_PUBLIC_SOURCES 28 | ${SRC_DIR}/qfactioncreator.cpp 29 | ${SRC_DIR}/qfappdispatcher.cpp 30 | ${SRC_DIR}/qfapplistener.cpp 31 | ${SRC_DIR}/qfapplistenergroup.cpp 32 | ${SRC_DIR}/qfappscript.cpp 33 | ${SRC_DIR}/qfappscriptdispatcherwrapper.cpp 34 | ${SRC_DIR}/qfappscriptgroup.cpp 35 | ${SRC_DIR}/qfappscriptrunnable.cpp 36 | ${SRC_DIR}/qfdispatcher.cpp 37 | ${SRC_DIR}/qffilter.cpp 38 | ${SRC_DIR}/qfhydrate.cpp 39 | ${SRC_DIR}/qfkeytable.cpp 40 | ${SRC_DIR}/qflistener.cpp 41 | ${SRC_DIR}/qfmiddleware.cpp 42 | ${SRC_DIR}/qfmiddlewarelist.cpp 43 | ${SRC_DIR}/qfobject.cpp 44 | ${SRC_DIR}/qfqmltypes.cpp 45 | ${SRC_DIR}/qfstore.cpp 46 | ) 47 | 48 | set(quickflux_PRIVATE_HEADERS 49 | ${SRC_DIR}/priv/qfappscriptdispatcherwrapper.h 50 | ${SRC_DIR}/priv/qfappscriptrunnable.h 51 | ${SRC_DIR}/priv/qfhook.h 52 | ${SRC_DIR}/priv/qflistener.h 53 | ${SRC_DIR}/priv/qfmiddlewareshook.h 54 | ${SRC_DIR}/priv/qfsignalproxy.h 55 | ${SRC_DIR}/priv/quickfluxfunctions.h 56 | ) 57 | 58 | set(quickflux_PUBLIC_HEADERS 59 | ${SRC_DIR}/qfactioncreator.h 60 | ${SRC_DIR}/QFAppDispatcher 61 | ${SRC_DIR}/qfapplistener.h 62 | ${SRC_DIR}/qfapplistenergroup.h 63 | ${SRC_DIR}/qfappscript.h 64 | ${SRC_DIR}/qfappdispatcher.h 65 | ${SRC_DIR}/qfappscriptgroup.h 66 | ${SRC_DIR}/qfdispatcher.h 67 | ${SRC_DIR}/qffilter.h 68 | ${SRC_DIR}/qfhydrate.h 69 | ${SRC_DIR}/QFKeyTable 70 | ${SRC_DIR}/qfkeytable.h 71 | ${SRC_DIR}/qfmiddleware.h 72 | ${SRC_DIR}/qfmiddlewarelist.h 73 | ${SRC_DIR}/qfobject.h 74 | ${SRC_DIR}/qfstore.h 75 | ${SRC_DIR}/QuickFlux 76 | ) 77 | 78 | source_group(include FILES 79 | ${quickflux_PRIVATE_HEADERS} 80 | ${quickflux_PUBLIC_HEADERS} 81 | ) 82 | 83 | if(MSVC) 84 | source_group("Source Files" FILES ${quickflux_PUBLIC_SOURCES}) 85 | source_group("Source Files\\Private" FILES ${quickflux_PRIVATE_SOURCES}) 86 | source_group("Header Files" FILES ${quickflux_PUBLIC_HEADERS}) 87 | source_group("Header Files\\Private" FILES ${quickflux_PRIVATE_HEADERS}) 88 | source_group("Source Files\\MOC" REGULAR_EXPRESSION "moc*") 89 | endif() 90 | 91 | add_library(quickflux STATIC 92 | ${quickflux_PRIVATE_SOURCES} 93 | ${quickflux_PRIVATE_HEADERS} 94 | ${quickflux_PUBLIC_SOURCES} 95 | ${quickflux_PUBLIC_HEADERS} 96 | ${moc} 97 | ) 98 | add_library(QuickFlux::quickflux ALIAS quickflux) 99 | 100 | target_link_libraries(quickflux 101 | PUBLIC 102 | Qt5::Qml 103 | Qt5::Quick 104 | Qt5::Core 105 | ) 106 | 107 | target_include_directories(quickflux 108 | PUBLIC 109 | "$" 110 | "$" 111 | ) 112 | 113 | set_target_properties(quickflux PROPERTIES 114 | AUTOMOC TRUE 115 | DEBUG_POSTFIX d 116 | ) 117 | 118 | if(quickflux_INSTALL) 119 | 120 | install(TARGETS quickflux EXPORT QuickFluxTargets 121 | LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" 122 | ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" 123 | RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" 124 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/quickflux" 125 | ) 126 | 127 | install(FILES 128 | ${quickflux_PUBLIC_HEADERS} 129 | DESTINATION include/quickflux 130 | ) 131 | install(FILES 132 | ${quickflux_PRIVATE_HEADERS} 133 | DESTINATION include/quickflux/priv 134 | ) 135 | 136 | 137 | include(CMakePackageConfigHelpers) 138 | write_basic_package_version_file( 139 | ${CMAKE_BINARY_DIR}/cmake/QuickFluxVersion.cmake 140 | VERSION ${PROJECT_VERSION} 141 | COMPATIBILITY SameMajorVersion 142 | ) 143 | 144 | # installation - build tree specific package config files 145 | export(EXPORT QuickFluxTargets FILE ${CMAKE_BINARY_DIR}/QuickFluxTargets.cmake) 146 | configure_file(${PROJECT_SOURCE_DIR}/QuickFluxConfig.cmake.in 147 | ${CMAKE_BINARY_DIR}/QuickFluxConfig.cmake 148 | COPYONLY 149 | ) 150 | 151 | # installation - relocatable package config files 152 | configure_package_config_file(${PROJECT_SOURCE_DIR}/QuickFluxConfig.cmake.in 153 | ${CMAKE_CURRENT_BINARY_DIR}/cmake/QuickFluxConfig.cmake 154 | INSTALL_DESTINATION cmake 155 | ) 156 | 157 | set(CONFIG_PACKAGE_LOCATION ${CMAKE_INSTALL_LIBDIR}/cmake/QuickFlux) 158 | 159 | install(EXPORT QuickFluxTargets 160 | FILE QuickFluxTargets.cmake 161 | NAMESPACE QuickFlux:: 162 | DESTINATION ${CONFIG_PACKAGE_LOCATION} 163 | ) 164 | 165 | install(FILES 166 | ${CMAKE_BINARY_DIR}/cmake/QuickFluxConfig.cmake 167 | ${CMAKE_BINARY_DIR}/cmake/QuickFluxVersion.cmake 168 | DESTINATION ${CONFIG_PACKAGE_LOCATION} 169 | ) 170 | 171 | endif() 172 | -------------------------------------------------------------------------------- /QuickFluxConfig.cmake.in: -------------------------------------------------------------------------------- 1 | include("${CMAKE_CURRENT_LIST_DIR}/QuickFluxTargets.cmake") 2 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: build{build} 2 | 3 | branches: 4 | except: 5 | - project/travis 6 | 7 | environment: 8 | matrix: 9 | - name: win32 10 | platform: amd64_x86 11 | qt: msvc2017 12 | APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017 13 | 14 | build_script: 15 | - call "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\VC\Auxiliary\Build\vcvars64.bat" 16 | - set GOPATH=c:\gopath 17 | - set QTDIR=C:\Qt\5.13.2\msvc2017_64 18 | - set PATH=%PATH%;%QTDIR%\bin;C:\MinGW\bin;%GOPATH%\bin; 19 | - go get qpm.io/qpm 20 | - go install qpm.io/qpm 21 | - dir %GOPATH%\bin 22 | - dir C:\Qt 23 | - dir %QTDIR%\bin 24 | - cd tests\quickfluxunittests 25 | - qpm install 26 | - qmake quickfluxunittests.pro 27 | - nmake 28 | - dir /w 29 | - dir release /w 30 | - release\quickfluxunittests 31 | - cd ../.. 32 | - cd examples/todo && cmake -G "NMake Makefiles" . && dir /w && nmake 33 | -------------------------------------------------------------------------------- /docs/concept/after-using-flux.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlau/quickflux/2a37acff0416c56cb349e5bc1b841b25ff1bb6f8/docs/concept/after-using-flux.jpg -------------------------------------------------------------------------------- /docs/concept/architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlau/quickflux/2a37acff0416c56cb349e5bc1b841b25ff1bb6f8/docs/concept/architecture.png -------------------------------------------------------------------------------- /docs/concept/before-using-flux.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlau/quickflux/2a37acff0416c56cb349e5bc1b841b25ff1bb6f8/docs/concept/before-using-flux.jpg -------------------------------------------------------------------------------- /docs/concept/middleware-data-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/benlau/quickflux/2a37acff0416c56cb349e5bc1b841b25ff1bb6f8/docs/concept/middleware-data-flow.png -------------------------------------------------------------------------------- /docs/index.qdoc: -------------------------------------------------------------------------------- 1 | /*! 2 | \page index.html 3 | \title QuickFlux 4 | 5 | QML Components 6 | \list 7 | \li \l Dispatcher 8 | \li \l Store 9 | \li \l Filter 10 | \li \l KeyTable 11 | \li \l ActionCreator 12 | \li \l Middleware 13 | \li \l MiddlewareList 14 | \li \l Hydrate 15 | \li \l AppScript 16 | \li \l AppScriptGroup 17 | \li \l AppListener 18 | \endlist 19 | 20 | QML Singleton Object 21 | \list 22 | \li \l AppDispatcher 23 | \endlist 24 | 25 | C++ Classes 26 | \list 27 | \li \l QFAppDispatcher 28 | \endlist 29 | 30 | */ 31 | -------------------------------------------------------------------------------- /docs/quickflux.qdocconf: -------------------------------------------------------------------------------- 1 | project = QuickFlux 2 | url = https://github.com/benlau/quickflux 3 | description = A Flux implementation for QML 4 | sourcedirs = ./ 5 | sourcedirs += ../ 6 | headerdirs += ../ 7 | exampledirs += ../examples 8 | sources.fileextensions = "*.qml *.qdoc *.cpp" 9 | headers.fileextensions = "*.h" 10 | version = 1.1 11 | syntaxhighlighting = true 12 | sourceencoding = UTF-8 13 | outputencoding = UTF-8 14 | outputdir = html 15 | outputformats = HTML 16 | 17 | HTML.templatedir = . 18 | #HTML.stylesheets = $$QT_INSTALL_DOCS/global/html/style/offline.css 19 | HTML.stylesheets = $$QT_DOCS_STYLE 20 | 21 | HTML.headerstyles = \ 22 | " \n" 23 | 24 | images += concept/middleware-data-flow.png 25 | 26 | 27 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | Example Programs 2 | ================ 3 | 4 | todo/ - General usage of ActionCreator/AppDispatcher and Store (Since 1.1) 5 | 6 | 7 | Advanced Tutorials 8 | ------------------ 9 | 10 | middleware/ - Example of middleware (Since 1.1) 11 | 12 | -------------------------------------------------------------------------------- /examples/middleware/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | *.debug 25 | Makefile* 26 | *.prl 27 | *.app 28 | moc_*.cpp 29 | ui_*.h 30 | qrc_*.cpp 31 | Thumbs.db 32 | *.res 33 | *.rc 34 | /.qmake.cache 35 | /.qmake.stash 36 | 37 | # qtcreator generated files 38 | *.pro.user* 39 | 40 | # xemacs temporary files 41 | *.flc 42 | 43 | # Vim temporary files 44 | .*.swp 45 | 46 | # Visual Studio generated files 47 | *.ib_pdb_index 48 | *.idb 49 | *.ilk 50 | *.pdb 51 | *.sln 52 | *.suo 53 | *.vcproj 54 | *vcproj.*.*.user 55 | *.ncb 56 | *.sdf 57 | *.opensdf 58 | *.vcxproj 59 | *vcxproj.* 60 | 61 | # MinGW generated files 62 | *.Debug 63 | *.Release 64 | 65 | # Python byte code 66 | *.pyc 67 | 68 | # Binaries 69 | # -------- 70 | *.dll 71 | *.exe 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/middleware/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.8.2) 2 | 3 | project(middleware) 4 | 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | 7 | set(CMAKE_AUTOMOC ON) 8 | 9 | find_package(Qt5Core REQUIRED) 10 | 11 | include_directories(${Qt5Qml_INCLUDE_DIRS}) 12 | include_directories(${Qt5Gui_INCLUDE_DIRS}) 13 | 14 | #include("${CMAKE_CURRENT_SOURCE_DIR}/../../CMakeLists.txt") 15 | 16 | # Generate rules for building source files from the resources 17 | qt5_add_resources(QRCS qml.qrc) 18 | 19 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../ ${CMAKE_CURRENT_BINARY_DIR}/quickflux) 20 | 21 | message(STATUS "test" ${quickflux_SOURCES}) 22 | 23 | # Tell CMake to create the middleware executable 24 | add_executable(middleware main.cpp ${QRCS} quickflux) 25 | 26 | #target_link_libraries(middleware ${CMAKE_CURRENT_BINARY_DIR}/quickflux/libquickflux.a) 27 | link_directories(quickflux) 28 | target_link_libraries(middleware quickflux) 29 | 30 | qt5_use_modules(middleware Qml Gui) 31 | 32 | -------------------------------------------------------------------------------- /examples/middleware/README.md: -------------------------------------------------------------------------------- 1 | Photo Album Example (middleware) 2 | ================================ 3 | 4 | Purpose: Demonstrate the use of Middleware 5 | 6 | /actions - ActionTypes and ActionCreator 7 | /stores - Store 8 | /middlewares - Middlewares 9 | /components - Dump UI components (Have no app dependencies. Receive only props, providing data and signal) 10 | /views - Smart UI Components (Receive data from Store and call actions) 11 | -------------------------------------------------------------------------------- /examples/middleware/actions/ActionTypes.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | 5 | KeyTable { 6 | id: actionTypes; 7 | 8 | property string pickPhoto 9 | 10 | property string previewPhoto 11 | 12 | property string navigateTo 13 | 14 | property string navigateBack 15 | } 16 | -------------------------------------------------------------------------------- /examples/middleware/actions/AppActions.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | import "./" 5 | 6 | ActionCreator { 7 | 8 | /* Create action by signal */ 9 | 10 | signal pickPhoto() 11 | 12 | signal previewPhoto(string url); 13 | 14 | /* 15 | 16 | signal pickPhoto(url) 17 | 18 | signal previewPhoto(string url); 19 | 20 | They are equivalent to: 21 | 22 | function pickPhoto(url) { 23 | dispatch("pickPhoto", {url: url}); 24 | } 25 | 26 | function previewPhoto(url) { 27 | dispatch(ActionTypes.previewPhoto,{url: url}); 28 | } 29 | 30 | */ 31 | 32 | /* Create action by traditional method */ 33 | 34 | 35 | function navigateTo(item,properties) { 36 | dispatch(ActionTypes.navigateTo, 37 | {item: item, 38 | properties: properties}); 39 | } 40 | 41 | function navigateBack() { 42 | dispatch(ActionTypes.navigateBack); 43 | } 44 | } 45 | 46 | -------------------------------------------------------------------------------- /examples/middleware/actions/qmldir: -------------------------------------------------------------------------------- 1 | singleton AppActions 1.0 AppActions.qml 2 | singleton ActionTypes 1.0 ActionTypes.qml 3 | -------------------------------------------------------------------------------- /examples/middleware/components/ImagePreview.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 1.2 4 | import QuickFlux 1.0 5 | import "../actions" 6 | 7 | Rectangle { 8 | color: "#000000" 9 | 10 | property alias source: image.source 11 | 12 | signal cancelled 13 | signal confirmed 14 | 15 | ColumnLayout { 16 | anchors.fill: parent 17 | 18 | Image { 19 | id: image 20 | asynchronous: true 21 | Layout.fillWidth: true 22 | Layout.fillHeight: true 23 | 24 | fillMode: Image.PreserveAspectFit 25 | } 26 | 27 | RowLayout { 28 | Layout.fillWidth: true 29 | Layout.fillHeight: false 30 | Layout.maximumHeight: cancelButton.implicitHeight 31 | 32 | Button { 33 | id: cancelButton 34 | text: qsTr("Cancel") 35 | onClicked: { 36 | cancelled(); 37 | } 38 | } 39 | 40 | Button { 41 | id: confirmButton 42 | text: qsTr("Confirm"); 43 | onClicked: { 44 | confirmed(); 45 | } 46 | } 47 | 48 | } 49 | } 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /examples/middleware/deployment.pri: -------------------------------------------------------------------------------- 1 | unix:!android { 2 | isEmpty(target.path) { 3 | qnx { 4 | target.path = /tmp/$${TARGET}/bin 5 | } else { 6 | target.path = /opt/$${TARGET}/bin 7 | } 8 | export(target.path) 9 | } 10 | INSTALLS += target 11 | } 12 | 13 | export(INSTALLS) 14 | 15 | -------------------------------------------------------------------------------- /examples/middleware/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QGuiApplication app(argc, argv); 8 | 9 | // It is needed only if you link QuickFlux from cmake via libquickflux.a. Build with qpm/quickflux.pri is not needed. 10 | registerQuickFluxQmlTypes(); 11 | 12 | QQmlApplicationEngine engine; 13 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 14 | 15 | return app.exec(); 16 | } 17 | 18 | -------------------------------------------------------------------------------- /examples/middleware/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Window 2.2 3 | import QtQuick.Controls 1.3 4 | import QuickFlux 1.1 5 | import "./views" 6 | import "./actions" 7 | import "./middlewares" 8 | 9 | Window { 10 | visible: true 11 | title: "Photo Album" 12 | width: 640 13 | height: 480 14 | 15 | MiddlewareList { 16 | applyTarget: AppActions 17 | 18 | ImagePickerMiddleware { 19 | } 20 | 21 | NavigationMiddleware { 22 | stack: stack 23 | } 24 | } 25 | 26 | StackView { 27 | id: stack 28 | anchors.fill: parent 29 | } 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /examples/middleware/middleware.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += qml quick 4 | CONFIG += c++11 5 | 6 | SOURCES += main.cpp 7 | 8 | RESOURCES += qml.qrc 9 | 10 | # Additional import path used to resolve QML modules in Qt Creator's code model 11 | QML_IMPORT_PATH = 12 | 13 | DISTFILES += \ 14 | actions/qmldir \ 15 | stores/qmldir \ 16 | README.md 17 | 18 | include("../../quickflux.pri") 19 | -------------------------------------------------------------------------------- /examples/middleware/middlewares/ImagePickerMiddleware.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Dialogs 1.0 3 | import QuickFlux 1.1 4 | import "../actions" 5 | import "../components" 6 | 7 | Middleware { 8 | 9 | filterFunctionEnabled: true 10 | 11 | Component { 12 | id: imagePreview 13 | 14 | ImagePreview { 15 | onCancelled: { 16 | next(ActionTypes.navigateBack); 17 | } 18 | 19 | onConfirmed: { 20 | next(ActionTypes.navigateBack); 21 | next(ActionTypes.pickPhoto, {url: dialog.fileUrl.toString()}); 22 | } 23 | } 24 | } 25 | 26 | FileDialog { 27 | id: dialog 28 | title: qsTr("Pick Image") 29 | nameFilters: [ "Image files (*.jpg *.png)"] 30 | onAccepted: { 31 | var message = { 32 | item: imagePreview, 33 | properties: { 34 | source: dialog.fileUrl 35 | } 36 | } 37 | next(ActionTypes.navigateTo, message); 38 | } 39 | } 40 | 41 | function pickPhoto(message) { 42 | dialog.open(); 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /examples/middleware/middlewares/NavigationMiddleware.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.1 3 | 4 | Middleware { 5 | 6 | filterFunctionEnabled: true 7 | 8 | property var stack; 9 | 10 | function navigateTo(message) { 11 | stack.push(message.item,message.properties); 12 | } 13 | 14 | function navigateBack(message) { 15 | stack.pop(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/middleware/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | actions/AppActions.qml 5 | actions/qmldir 6 | stores/qmldir 7 | views/ImageViewer.qml 8 | actions/ActionTypes.qml 9 | views/StackView.qml 10 | middlewares/ImagePickerMiddleware.qml 11 | middlewares/NavigationMiddleware.qml 12 | components/ImagePreview.qml 13 | stores/RootStore.qml 14 | stores/MainStore.qml 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/middleware/stores/MainStore.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | 4 | RootStore { 5 | } 6 | -------------------------------------------------------------------------------- /examples/middleware/stores/RootStore.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.1 3 | import "../actions" 4 | 5 | Store { 6 | bindSource: AppDispatcher 7 | property ListModel photoModel : ListModel {} 8 | 9 | function add(url) { 10 | photoModel.append({ url: url }); 11 | } 12 | 13 | Filter { 14 | type: ActionTypes.pickPhoto 15 | onDispatched: { 16 | add(message.url); 17 | } 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /examples/middleware/stores/qmldir: -------------------------------------------------------------------------------- 1 | singleton MainStore 1.0 MainStore.qml 2 | -------------------------------------------------------------------------------- /examples/middleware/views/ImageViewer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 1.3 4 | import "../stores" 5 | import "../actions" 6 | 7 | Rectangle { 8 | id: viewer 9 | color: "#000000" 10 | 11 | ColumnLayout { 12 | anchors.fill: parent 13 | 14 | Grid { 15 | Layout.fillWidth: true 16 | Layout.fillHeight: true 17 | columns: 3 18 | spacing: 0 19 | 20 | Repeater { 21 | model: MainStore.photoModel 22 | delegate: Image { 23 | width: viewer.width / 3 24 | height: width / 4 * 3 25 | source: model.url 26 | asynchronous: true 27 | fillMode: Image.PreserveAspectCrop 28 | } 29 | } 30 | } 31 | 32 | Button { 33 | Layout.fillWidth: true 34 | Layout.fillHeight: false 35 | text: qsTr("Pick Image") 36 | onClicked: { 37 | AppActions.pickPhoto(); 38 | } 39 | } 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /examples/middleware/views/StackView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.3 3 | import QuickFlux 1.0 4 | import "../actions" 5 | 6 | StackView { 7 | id: stack 8 | anchors.fill: parent 9 | 10 | initialItem: ImageViewer { 11 | 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /examples/photoalbum/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | *.debug 25 | Makefile* 26 | *.prl 27 | *.app 28 | moc_*.cpp 29 | ui_*.h 30 | qrc_*.cpp 31 | Thumbs.db 32 | *.res 33 | *.rc 34 | /.qmake.cache 35 | /.qmake.stash 36 | 37 | # qtcreator generated files 38 | *.pro.user* 39 | 40 | # xemacs temporary files 41 | *.flc 42 | 43 | # Vim temporary files 44 | .*.swp 45 | 46 | # Visual Studio generated files 47 | *.ib_pdb_index 48 | *.idb 49 | *.ilk 50 | *.pdb 51 | *.sln 52 | *.suo 53 | *.vcproj 54 | *vcproj.*.*.user 55 | *.ncb 56 | *.sdf 57 | *.opensdf 58 | *.vcxproj 59 | *vcxproj.* 60 | 61 | # MinGW generated files 62 | *.Debug 63 | *.Release 64 | 65 | # Python byte code 66 | *.pyc 67 | 68 | # Binaries 69 | # -------- 70 | *.dll 71 | *.exe 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/photoalbum/README.md: -------------------------------------------------------------------------------- 1 | Photo Album Example 2 | =================== 3 | 4 | Purpose: Demonstrate the use of AppScript 5 | -------------------------------------------------------------------------------- /examples/photoalbum/actions/ActionTypes.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | 5 | KeyTable { 6 | id: actionTypes; 7 | 8 | property string askToPickPhoto 9 | 10 | property string previewPhoto 11 | 12 | property string pickPhoto 13 | 14 | property string navigateTo 15 | 16 | property string navigateBack 17 | } 18 | -------------------------------------------------------------------------------- /examples/photoalbum/actions/AppActions.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | import "./" 5 | 6 | ActionCreator { 7 | 8 | /* Create action by signal */ 9 | 10 | signal askToPickPhoto(); 11 | 12 | signal pickPhoto(string url) 13 | 14 | /* 15 | "signal pickPhoto(url) 16 | 17 | It is equivalent to: 18 | 19 | function pickPhoto(url) { 20 | AppDispatcher.dispatch("pickPhoto", {url: url}); 21 | } 22 | */ 23 | 24 | /* Create action by traditional method */ 25 | 26 | function previewPhoto(url) { 27 | AppDispatcher.dispatch(ActionTypes.previewPhoto,{url: url}); 28 | } 29 | 30 | function navigateTo(item,properties) { 31 | AppDispatcher.dispatch(ActionTypes.navigateTo, 32 | {item: item, 33 | properties: properties}); 34 | } 35 | 36 | function navigateBack() { 37 | AppDispatcher.dispatch(ActionTypes.navigateBack); 38 | } 39 | } 40 | 41 | -------------------------------------------------------------------------------- /examples/photoalbum/actions/qmldir: -------------------------------------------------------------------------------- 1 | singleton AppActions 1.0 AppActions.qml 2 | singleton ActionTypes 1.0 ActionTypes.qml 3 | -------------------------------------------------------------------------------- /examples/photoalbum/deployment.pri: -------------------------------------------------------------------------------- 1 | unix:!android { 2 | isEmpty(target.path) { 3 | qnx { 4 | target.path = /tmp/$${TARGET}/bin 5 | } else { 6 | target.path = /opt/$${TARGET}/bin 7 | } 8 | export(target.path) 9 | } 10 | INSTALLS += target 11 | } 12 | 13 | export(INSTALLS) 14 | 15 | -------------------------------------------------------------------------------- /examples/photoalbum/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QGuiApplication app(argc, argv); 7 | 8 | QQmlApplicationEngine engine; 9 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 10 | 11 | return app.exec(); 12 | } 13 | 14 | -------------------------------------------------------------------------------- /examples/photoalbum/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Window 2.2 3 | import QtQuick.Controls 1.3 4 | import QuickFlux 1.0 5 | import "./views" 6 | import "./scripts" 7 | import "./actions" 8 | 9 | Window { 10 | visible: true 11 | title: "Photo Album" 12 | width: 640 13 | height: 480 14 | 15 | StackView { 16 | anchors.fill: parent 17 | } 18 | 19 | ImagePickerScript { 20 | } 21 | 22 | } 23 | 24 | -------------------------------------------------------------------------------- /examples/photoalbum/photoalbum.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += qml quick 4 | CONFIG += c++11 5 | 6 | SOURCES += main.cpp 7 | 8 | RESOURCES += qml.qrc 9 | 10 | # Additional import path used to resolve QML modules in Qt Creator's code model 11 | QML_IMPORT_PATH = 12 | 13 | DISTFILES += \ 14 | actions/qmldir \ 15 | stores/qmldir \ 16 | README.md 17 | 18 | include("../../quickflux.pri") 19 | -------------------------------------------------------------------------------- /examples/photoalbum/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | actions/AppActions.qml 5 | actions/qmldir 6 | stores/PhotoStore.qml 7 | stores/qmldir 8 | views/ImageViewer.qml 9 | scripts/ImagePickerScript.qml 10 | actions/ActionTypes.qml 11 | views/ImagePreview.qml 12 | views/StackView.qml 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/photoalbum/scripts/ImagePickerScript.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.0 3 | import QtQuick.Dialogs 1.0 4 | import "../actions" 5 | import "../views" 6 | import "../stores" 7 | 8 | Item { 9 | 10 | FileDialog { 11 | id: dialog 12 | title: qsTr("Pick Image") 13 | nameFilters: [ "Image files (*.jpg *.png)"] 14 | } 15 | 16 | Component { 17 | id: imagePreview 18 | 19 | ImagePreview { 20 | 21 | } 22 | } 23 | 24 | // Use AppScript to pick image from local file system, preview, and ask for confirmation. 25 | // It is a component designed for handling asynchronous sequence task. 26 | 27 | // Benefit of using AppScript 28 | // 1. Centralize your workflow code in one place 29 | // 2. Highly integrated with AppDispatcher. The order of callback execution is guaranteed in sequence order. 30 | // 3. Only one script can be executed at a time. Registered callbacks by previous script will be removed before started. 31 | // 4. exit() will remove all the registered callbacks. No callback leave after termination. 32 | 33 | // Why not just use Promise? 34 | // 1. You need another library. (e.g QuickPromise) 35 | // 2. You need to set reject condtion per callback/promise 36 | // Explanation: Coding in a promise way requires you to handle every reject condition correctly. Otherwise, 37 | // promise will leave in memory and their behaviour will be unexpected. 38 | // AppScript.run() / exit() clear all the registered callback completely. You can write less code. 39 | 40 | AppScript { 41 | // Run this script if "Pick Image" button is clicked. 42 | runWhen: ActionTypes.askToPickPhoto 43 | 44 | script: { 45 | // Step 1. Open file dialog 46 | dialog.open(); 47 | 48 | // Register a set of callbacks as workflow 49 | // Registered callbacks will be executed once only per script execution 50 | once(dialog.onAccepted, function() { 51 | 52 | // Step 2. Once user picked an image, launch preview window and ask for confirmation. 53 | AppActions.navigateTo(imagePreview, 54 | {source: dialog.fileUrl}); 55 | 56 | }).then(ActionTypes.pickPhoto, function(message) { 57 | // The function of then() is same as once() but it won't 58 | // trigger the callback until once() is triggered. 59 | 60 | // Step 3. Add picked image to store and go back to previous page. 61 | 62 | PhotoStore.add(String(message.url)); 63 | 64 | AppActions.navigateBack(); 65 | 66 | }); // <-- You may chain then() function again. 67 | 68 | // Condition to terminate the workflow: 69 | // Force to terminate if dialog is rejected / or navigateBack is dispatched 70 | // That will remove all the registered callbacks 71 | 72 | once(dialog.onRejected,exit.bind(this,0)); 73 | 74 | once(ActionTypes.navigateBack,exit.bind(this,0)); 75 | } 76 | } 77 | 78 | } 79 | 80 | -------------------------------------------------------------------------------- /examples/photoalbum/stores/PhotoStore.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import "../actions" 4 | 5 | Item { 6 | property ListModel photoModel : ListModel {} 7 | 8 | function add(url) { 9 | photoModel.append({ url: url }); 10 | } 11 | } 12 | 13 | -------------------------------------------------------------------------------- /examples/photoalbum/stores/qmldir: -------------------------------------------------------------------------------- 1 | singleton PhotoStore 1.0 PhotoStore.qml 2 | -------------------------------------------------------------------------------- /examples/photoalbum/views/ImagePreview.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 1.2 4 | import QuickFlux 1.0 5 | import "../actions" 6 | 7 | Rectangle { 8 | color: "#000000" 9 | 10 | property alias source: image.source 11 | 12 | ColumnLayout { 13 | anchors.fill: parent 14 | 15 | Image { 16 | id: image 17 | asynchronous: true 18 | Layout.fillWidth: true 19 | Layout.fillHeight: true 20 | 21 | fillMode: Image.PreserveAspectFit 22 | } 23 | 24 | RowLayout { 25 | Layout.fillWidth: true 26 | Layout.fillHeight: false 27 | Layout.maximumHeight: cancelButton.implicitHeight 28 | 29 | Button { 30 | id: cancelButton 31 | text: qsTr("Cancel") 32 | onClicked: { 33 | AppActions.navigateBack(); 34 | } 35 | } 36 | 37 | Button { 38 | id: confirmButton 39 | text: qsTr("Confirm"); 40 | onClicked: { 41 | AppActions.pickPhoto(source); 42 | } 43 | } 44 | 45 | } 46 | } 47 | 48 | } 49 | 50 | -------------------------------------------------------------------------------- /examples/photoalbum/views/ImageViewer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Layouts 1.1 3 | import QtQuick.Controls 1.3 4 | import "../stores" 5 | import "../actions" 6 | 7 | Rectangle { 8 | id: viewer 9 | color: "#000000" 10 | 11 | ColumnLayout { 12 | anchors.fill: parent 13 | 14 | Grid { 15 | Layout.fillWidth: true 16 | Layout.fillHeight: true 17 | columns: 3 18 | spacing: 0 19 | 20 | Repeater { 21 | model: PhotoStore.photoModel 22 | delegate: Image { 23 | width: viewer.width / 3 24 | height: width / 4 * 3 25 | source: model.url 26 | asynchronous: true 27 | fillMode: Image.PreserveAspectCrop 28 | } 29 | } 30 | } 31 | 32 | Button { 33 | Layout.fillWidth: true 34 | Layout.fillHeight: false 35 | text: qsTr("Pick Image") 36 | onClicked: { 37 | AppActions.askToPickPhoto(); 38 | } 39 | } 40 | } 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /examples/photoalbum/views/StackView.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.3 3 | import QuickFlux 1.0 4 | import "../actions" 5 | 6 | Item { 7 | 8 | StackView { 9 | id: stack 10 | anchors.fill: parent 11 | 12 | initialItem: ImageViewer { 13 | 14 | } 15 | } 16 | 17 | AppListener { 18 | filter: ActionTypes.navigateTo 19 | onDispatched: { 20 | stack.push(message.item,message.properties); 21 | } 22 | } 23 | 24 | AppListener { 25 | filter: ActionTypes.navigateBack 26 | onDispatched: { 27 | stack.pop(); 28 | } 29 | } 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /examples/todo/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | *.debug 25 | Makefile* 26 | *.prl 27 | *.app 28 | moc_*.cpp 29 | ui_*.h 30 | qrc_*.cpp 31 | Thumbs.db 32 | *.res 33 | *.rc 34 | /.qmake.cache 35 | /.qmake.stash 36 | 37 | # qtcreator generated files 38 | *.pro.user* 39 | 40 | # xemacs temporary files 41 | *.flc 42 | 43 | # Vim temporary files 44 | .*.swp 45 | 46 | # Visual Studio generated files 47 | *.ib_pdb_index 48 | *.idb 49 | *.ilk 50 | *.pdb 51 | *.sln 52 | *.suo 53 | *.vcproj 54 | *vcproj.*.*.user 55 | *.ncb 56 | *.sdf 57 | *.opensdf 58 | *.vcxproj 59 | *vcxproj.* 60 | 61 | # MinGW generated files 62 | *.Debug 63 | *.Release 64 | 65 | # Python byte code 66 | *.pyc 67 | 68 | # Binaries 69 | # -------- 70 | *.dll 71 | *.exe 72 | 73 | 74 | -------------------------------------------------------------------------------- /examples/todo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1.0) 2 | 3 | project(todo) 4 | 5 | set(CMAKE_VERBOSE_MAKEFILE ON) 6 | 7 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 8 | 9 | set(CMAKE_AUTOMOC ON) 10 | 11 | set(CMAKE_AUTOUIC ON) 12 | 13 | set(CMAKE_AUTORCC ON) 14 | 15 | find_package(Qt5Qml CONFIG REQUIRED) 16 | find_package(Qt5Gui CONFIG REQUIRED) 17 | find_package(Qt5Core CONFIG REQUIRED) 18 | find_package(Qt5Quick CONFIG REQUIRED) 19 | 20 | set(todo_SRCS 21 | main.cpp 22 | qml.qrc 23 | ) 24 | 25 | include(ExternalProject) 26 | 27 | ExternalProject_Add(QuickFlux 28 | PREFIX "${PROJECT_BINARY_DIR}/QuickFlux-build" 29 | SOURCE_DIR "${PROJECT_SOURCE_DIR}/../.." 30 | CMAKE_ARGS "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}" 31 | "-DCMAKE_INSTALL_PREFIX=${PROJECT_BINARY_DIR}/QuickFlux" 32 | "-DCMAKE_INSTALL_LIBDIR=${PROJECT_BINARY_DIR}/QuickFlux/lib" 33 | "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}" 34 | ) 35 | 36 | link_directories("${PROJECT_BINARY_DIR}/QuickFlux/lib") 37 | include_directories("${PROJECT_BINARY_DIR}/QuickFlux/include/quickflux") 38 | 39 | add_executable(todo WIN32 ${todo_SRCS}) 40 | add_dependencies(todo QuickFlux) 41 | 42 | target_link_libraries(todo debug quickfluxd optimized quickflux) 43 | target_link_libraries(todo Qt5::Qml Qt5::Gui Qt5::Core Qt5::Quick) 44 | -------------------------------------------------------------------------------- /examples/todo/MainWindow.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Window 2.2 3 | import QtQuick.Layouts 1.0 4 | import QuickFlux 1.1 5 | import "./views" 6 | import "./middlewares" 7 | import "./actions" 8 | 9 | Window { 10 | width: 480 11 | height: 640 12 | visible: true 13 | 14 | ColumnLayout { 15 | anchors.fill: parent 16 | anchors.leftMargin: 16 17 | anchors.rightMargin: 16 18 | 19 | Header { 20 | Layout.fillWidth: true 21 | Layout.fillHeight: false 22 | } 23 | 24 | TodoList { 25 | Layout.fillWidth: true 26 | Layout.fillHeight: true 27 | } 28 | 29 | Footer { 30 | Layout.fillWidth: true 31 | Layout.fillHeight: false 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /examples/todo/README.md: -------------------------------------------------------------------------------- 1 | Todo List Example 2 | ================= 3 | 4 | Purpose: Demonstrate how to write a QML application in a Flux way. 5 | 6 | File Structure 7 | -------------- 8 | 9 | ``` 10 | . 11 | ├── actions 12 | │ ├── ActionTypes.qml 13 | │ ├── AppActions.qml 14 | │ └── qmldir 15 | ├── adapters 16 | ├── main.qml 17 | ├── mainWindow.qml 18 | ├── middlewares 19 | │ └── DialogMiddleware.qml 20 | ├── qml.qrc 21 | ├── stores 22 | │ ├── MainStore.qml 23 | │ ├── RootStore.qml 24 | │ ├── TodoStore.qml 25 | │ ├── UserPrefsStore.qml 26 | │ └── qmldir 27 | ├── todo.pro 28 | └── views 29 | ├── Footer.qml 30 | ├── Header.qml 31 | ├── TodoItem.qml 32 | ├── TodoList.qml 33 | └── TodoVisualModel.qml 34 | 35 | ``` 36 | 37 | 38 | **ActionTypes.qml** 39 | 40 | ActionTypes is a constant table (singleton component) to store all the available action types. 41 | 42 | Example: 43 | 44 | ``` 45 | pragma Singleton 46 | import QtQuick 2.0 47 | import QuickFlux 1.0 48 | 49 | KeyTable { 50 | // KeyTable is an object with properties equal to its key name 51 | 52 | property string addTask; 53 | 54 | property string setTaskDone; 55 | 56 | property string setShowCompletedTasks; 57 | 58 | } 59 | ``` 60 | 61 | It is not recommended to name an action by combing sender and event like removeItemButtonClicked. 62 | It is suggested to tell what users do (e.g. askToRemoveItem) or what it should actually do (e.g. removeItem). 63 | You may add a prefix of scope to its name if needed. (e.g. itemRemove) 64 | 65 | **AppActions.qml** 66 | 67 | AppActions is an action creator, a helper component to create and dispatch actions via the central dispatcher. It has no knowledge about the data model and who will use it. As it only depends on AppDispatcher, it could be used anywhere. 68 | 69 | AppDispatcher is the central dispatcher. It is also a singleton object. Actions sent by dispatch() function call will be placed on a queue. Then, the Dispatcher will emit a“dispatched” signal. 70 | 71 | Moreover, there has a side benefit in using ActionTypes and AppActions. Since they stores all the actions, when a new developer joins the project. He/she may open theses two files and know the entire API. 72 | 73 | **Stores** 74 | 75 | Stores contain application data, state and logic. 76 | Somehow it is similar to MVVM's View Model. 77 | However, Stores are read-only to Views. 78 | Views could only query data from Stores. 79 | "Update" should be done via Actions. 80 | Therefore, the "queries" and "updates" operations are in fact separated. 81 | 82 | UserPrefsStore.qml 83 | 84 | ``` 85 | import QtQuick 2.0 86 | import QuickFlux 1.1 87 | import "../actions" 88 | 89 | Store { 90 | 91 | property bool showCompletedTasks: false 92 | 93 | Filter { 94 | // Views do not write to showCompeletedTasks directly. 95 | // It asks AppActions.setShowCompletedTasks() to do so. 96 | type: ActionTypes.setShowCompletedTasks 97 | onDispatched: { 98 | showCompletedTasks = message.value; 99 | } 100 | } 101 | 102 | } 103 | 104 | Design Principles 105 | ----------------- 106 | 107 | **1. Avoid writing a big QML file. Break down into smaller pieces** 108 | 109 | **2. Avoid signal propagation between custom view components.** 110 | 111 | main.qml 112 | ``` 113 | Window { 114 | width: 480 115 | height: 640 116 | visible: true 117 | 118 | ColumnLayout { 119 | anchors.fill: parent 120 | anchors.leftMargin: 16 121 | anchors.rightMargin: 16 122 | 123 | Header { 124 | Layout.fillWidth: true 125 | Layout.fillHeight: false 126 | } 127 | 128 | TodoList { 129 | Layout.fillWidth: true 130 | Layout.fillHeight: true 131 | } 132 | 133 | Footer { 134 | Layout.fillWidth: true 135 | Layout.fillHeight: false 136 | } 137 | } 138 | } 139 | ``` 140 | 141 | This example program is divided into 3 parts : Header, TodoList and Footer. 142 | Each part contains at least one button that may change the status to other components. 143 | However, they are not connected by any signal/data binding between each others as shown in the above code. 144 | They have no knowledge of others. 145 | The result is a loose coupled design. 146 | 147 | **3. Tell, Don't ask** 148 | 149 | Inline code for handling user event can be hard to trace and test. They are not centralized in one source file. The dependence is not obvious and therefore it is easy to be broken during refactoring. 150 | 151 | To simplify your inline code, don’t ask for the state to make any decision, just tell what do you want to do to Action class: 152 | 153 | Example: 154 | 155 | header.qml 156 | ``` 157 | onCheckedChanged: { 158 | AppActions.setTaskDone(uid,checked); 159 | } 160 | ``` 161 | -------------------------------------------------------------------------------- /examples/todo/actions/ActionTypes.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | 5 | KeyTable { 6 | // KeyTable is an object with properties equal to its key name 7 | 8 | property string addTask 9 | 10 | property string setTaskDone 11 | 12 | property string setShowCompletedTasks 13 | 14 | property string startApp 15 | } 16 | 17 | -------------------------------------------------------------------------------- /examples/todo/actions/AppActions.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.1 4 | import "./" 5 | 6 | ActionCreator { 7 | 8 | // Add a new task 9 | signal addTask(string title); 10 | 11 | // Set/unset done on a task 12 | signal setTaskDone(var uid, bool done) 13 | 14 | // Show/hide completed task 15 | signal setShowCompletedTasks(bool value) 16 | 17 | } 18 | 19 | -------------------------------------------------------------------------------- /examples/todo/actions/qmldir: -------------------------------------------------------------------------------- 1 | singleton AppActions 0.1 AppActions.qml 2 | singleton ActionTypes 0.1 ActionTypes.qml 3 | -------------------------------------------------------------------------------- /examples/todo/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | qputenv("QML_DISABLE_DISK_CACHE", "true"); 9 | 10 | QGuiApplication app(argc, argv); 11 | 12 | registerQuickFluxQmlTypes(); // It is not necessary to call this function if the QuickFlux library is installed via qpm 13 | 14 | QQmlApplicationEngine engine; 15 | engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 16 | 17 | QFAppDispatcher* dispatcher = QFAppDispatcher::instance(&engine); 18 | dispatcher->dispatch("startApp"); 19 | 20 | return app.exec(); 21 | } 22 | 23 | -------------------------------------------------------------------------------- /examples/todo/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Window 2.2 3 | import QtQuick.Layouts 1.0 4 | import QuickFlux 1.1 5 | import "./views" 6 | import "./middlewares" 7 | import "./actions" 8 | 9 | Item { 10 | 11 | MiddlewareList { 12 | applyTarget: AppActions 13 | 14 | SystemMiddleware { 15 | mainWindow: mainWindow 16 | } 17 | 18 | DialogMiddleware { 19 | } 20 | } 21 | 22 | MainWindow { 23 | id: mainWindow 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /examples/todo/middlewares/DialogMiddleware.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.1 3 | import QtQuick.Dialogs 1.2 4 | import "../actions" 5 | import "../stores" 6 | 7 | Middleware { 8 | 9 | property RootStore store: MainStore 10 | 11 | MessageDialog { 12 | id: dialog 13 | title: "Confirmation" 14 | text: "Are you sure want to show completed tasks?" 15 | standardButtons: StandardButton.Ok | StandardButton.Cancel 16 | 17 | onAccepted: { 18 | next(ActionTypes.setShowCompletedTasks, {value: true}); 19 | } 20 | 21 | onRejected: { 22 | /// Trigger the changed signal even it is unchanged. It forces the checkbox to be turned off. 23 | store.userPrefs.showCompletedTasksChanged(); 24 | } 25 | } 26 | 27 | function dispatch(type, message) { 28 | 29 | if (type === ActionTypes.setShowCompletedTasks && 30 | message.value === true) { 31 | // If user want to show completed tasks, drop the action and show a dialog 32 | dialog.open(); 33 | return; 34 | } 35 | 36 | /// Pass the action to next middleware / store 37 | next(type, message); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /examples/todo/middlewares/SystemMiddleware.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.1 3 | import QtQuick.Dialogs 1.2 4 | import "../actions" 5 | import "../stores" 6 | 7 | Middleware { 8 | 9 | property RootStore store: MainStore 10 | 11 | property var mainWindow: null 12 | 13 | function dispatch(type, message) { 14 | if (type === ActionTypes.startApp) { 15 | mainWindow.visible = true; 16 | return; 17 | } 18 | next(type, message); 19 | } 20 | 21 | Connections { 22 | target: mainWindow 23 | onClosing: { 24 | // You may inject a hook to forbid closing or save data if necessary 25 | console.log("closing"); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /examples/todo/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | actions/AppActions.qml 5 | views/Footer.qml 6 | views/TodoItem.qml 7 | views/TodoList.qml 8 | actions/qmldir 9 | stores/TodoStore.qml 10 | stores/qmldir 11 | views/TodoVisualModel.qml 12 | views/Header.qml 13 | actions/ActionTypes.qml 14 | stores/UserPrefsStore.qml 15 | stores/RootStore.qml 16 | stores/MainStore.qml 17 | middlewares/DialogMiddleware.qml 18 | MainWindow.qml 19 | middlewares/SystemMiddleware.qml 20 | 21 | 22 | -------------------------------------------------------------------------------- /examples/todo/stores/MainStore.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.1 4 | 5 | RootStore { 6 | /// Set the source of actions from AppDispatcher 7 | bindSource: AppDispatcher 8 | } 9 | -------------------------------------------------------------------------------- /examples/todo/stores/RootStore.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.1 3 | 4 | Store { 5 | 6 | // It hold the todo list data model 7 | property alias todo: todo 8 | 9 | TodoStore { 10 | id: todo 11 | } 12 | 13 | // It hold user's perference. (e.g should it show completed tasks?) 14 | property alias userPrefs: userPrefs 15 | 16 | UserPrefsStore { 17 | id: userPrefs 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /examples/todo/stores/TodoStore.qml: -------------------------------------------------------------------------------- 1 | /** Todo Item Store 2 | 3 | A centralized data store of Todo list. 4 | 5 | */ 6 | import QtQuick 2.0 7 | import QuickFlux 1.1 8 | import "../actions" 9 | 10 | Store { 11 | property alias model: model 12 | 13 | property int nextUid: 4; 14 | 15 | ListModel { 16 | // Initial data 17 | id: model 18 | 19 | ListElement { 20 | uid: 0 21 | title: "Task Zero" 22 | done: true 23 | } 24 | 25 | ListElement { 26 | uid: 1 27 | title: "Task A" 28 | done: false 29 | } 30 | 31 | ListElement { 32 | uid: 2 33 | title: "Task B" 34 | done: false 35 | } 36 | 37 | ListElement { 38 | uid: 3 39 | title: "Task C" 40 | done: false 41 | } 42 | } 43 | 44 | Filter { 45 | // Filter - Add a filter rule to parent 46 | 47 | // Filter item listens on parent's dispatched signal, 48 | // if a dispatched signal match with its type, it will 49 | // emit its own "dispatched" signal. Otherwise, it will 50 | // simply ignore the signal. 51 | 52 | type: ActionTypes.addTask 53 | onDispatched: { 54 | var item = { 55 | uid: nextUid++, 56 | title: message.title, 57 | done: false 58 | } 59 | model.append(item); 60 | } 61 | } 62 | 63 | Filter { 64 | type: ActionTypes.setTaskDone 65 | onDispatched: { 66 | for (var i = 0 ; i < model.count ; i++) { 67 | var item = model.get(i); 68 | if (item.uid === message.uid) { 69 | model.setProperty(i,"done",message.done); 70 | break; 71 | } 72 | } 73 | } 74 | } 75 | 76 | } 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /examples/todo/stores/UserPrefsStore.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.1 3 | import "../actions" 4 | 5 | Store { 6 | 7 | property bool showCompletedTasks: false 8 | 9 | Filter { 10 | // Views do not write to showCompeletedTasks directly. 11 | // It asks AppActions.setShowCompletedTasks() to do so. 12 | type: ActionTypes.setShowCompletedTasks 13 | onDispatched: { 14 | showCompletedTasks = message.value; 15 | } 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /examples/todo/stores/qmldir: -------------------------------------------------------------------------------- 1 | singleton MainStore 0.1 MainStore.qml 2 | -------------------------------------------------------------------------------- /examples/todo/todo.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += qml quick 4 | CONFIG += c++11 5 | 6 | SOURCES += main.cpp 7 | 8 | RESOURCES += qml.qrc 9 | 10 | # Additional import path used to resolve QML modules in Qt Creator's code model 11 | QML_IMPORT_PATH = $$PWD 12 | 13 | # If you install QuickFlux by qpm.pri, change it to include(vendor/vendor.pri) 14 | include(../../quickflux.pri) 15 | 16 | DISTFILES += \ 17 | README.md 18 | -------------------------------------------------------------------------------- /examples/todo/views/Footer.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | import "../actions" 5 | 6 | Item { 7 | height: 56 8 | 9 | function add() { 10 | AppActions.addTask(textField.text); 11 | textField.text = ""; 12 | } 13 | 14 | RowLayout { 15 | anchors.fill: parent 16 | 17 | TextField { 18 | id: textField 19 | Layout.fillWidth: true 20 | focus: true 21 | onAccepted: add(); 22 | } 23 | 24 | Button { 25 | text: "ADD" 26 | onClicked: { 27 | add(); 28 | } 29 | } 30 | } 31 | } 32 | 33 | -------------------------------------------------------------------------------- /examples/todo/views/Header.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import "../actions" 4 | import "../stores" 5 | 6 | Item { 7 | height: 48 8 | 9 | CheckBox { 10 | id: checkBox 11 | checked: false 12 | text: "Show Completed"; 13 | anchors.right: parent.right 14 | anchors.verticalCenter: parent.verticalCenter 15 | 16 | onCheckedChanged: { 17 | AppActions.setShowCompletedTasks(checked); 18 | } 19 | 20 | Binding { 21 | target: checkBox 22 | property: "checked" 23 | value: MainStore.userPrefs.showCompletedTasks 24 | } 25 | } 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /examples/todo/views/TodoItem.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtQuick.Controls 1.0 3 | import QtQuick.Layouts 1.0 4 | import "../actions" 5 | 6 | Rectangle { 7 | id: item 8 | color: "white" 9 | height: 48 10 | 11 | property int uid; 12 | property string title 13 | property alias checked: checkBox.checked 14 | 15 | RowLayout { 16 | anchors.fill: parent 17 | 18 | CheckBox { 19 | id: checkBox 20 | anchors.verticalCenter: parent.verticalCenter 21 | } 22 | 23 | Text { 24 | text: title 25 | Layout.fillWidth: true 26 | } 27 | } 28 | 29 | onCheckedChanged: { 30 | AppActions.setTaskDone(uid,checked); 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /examples/todo/views/TodoList.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.0 3 | import QtQuick.Controls 1.0 4 | import QtQuick.Layouts 1.0 5 | import "../stores" 6 | 7 | ScrollView { 8 | ListView { 9 | anchors.fill: parent 10 | 11 | model: TodoVisualModel { 12 | id: visualModel 13 | } 14 | } 15 | } 16 | 17 | -------------------------------------------------------------------------------- /examples/todo/views/TodoVisualModel.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.0 3 | import "../stores" 4 | import "../actions" 5 | 6 | VisualDataModel { 7 | 8 | model: MainStore.todo.model 9 | 10 | filterOnGroup: MainStore.userPrefs.showCompletedTasks ? "" : "nonCompleted" 11 | 12 | groups: [ 13 | VisualDataGroup { 14 | name: "nonCompleted" 15 | includeByDefault: true 16 | } 17 | ] 18 | 19 | delegate: TodoItem { 20 | id:item 21 | uid: model.uid 22 | title: model.title 23 | checked: model.done 24 | 25 | Component.onCompleted: { 26 | item.VisualDataModel.inNonCompleted = Qt.binding(function() { return !model.done}) 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /qpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.github.benlau.quickflux", 3 | "description": "A Flux implementation for QML", 4 | "author": { 5 | "name": "Ben Lau", 6 | "email": "xbenlau@gmail.com" 7 | }, 8 | "repository": { 9 | "type": "GITHUB", 10 | "url": "https://github.com/benlau/quickflux.git" 11 | }, 12 | "version": { 13 | "label": "1.1.3", 14 | "revision": "1", 15 | "fingerprint": "" 16 | }, 17 | "dependencies": [ 18 | ], 19 | "license": "APACHE_2_0", 20 | "pri_filename": "quickflux.pri", 21 | "webpage": "https://github.com/benlau/quickflux" 22 | } 23 | -------------------------------------------------------------------------------- /quickflux.pri: -------------------------------------------------------------------------------- 1 | include(src/src.pri) 2 | 3 | DISTFILES += \ 4 | $$PWD/README.md 5 | 6 | 7 | -------------------------------------------------------------------------------- /quickflux.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | CONFIG += ordered 4 | 5 | SUBDIRS += tests/quickfluxunittests 6 | -------------------------------------------------------------------------------- /src/QFAppDispatcher: -------------------------------------------------------------------------------- 1 | #include "qfappdispatcher.h" 2 | -------------------------------------------------------------------------------- /src/QFKeyTable: -------------------------------------------------------------------------------- 1 | #include "qfkeytable.h" 2 | -------------------------------------------------------------------------------- /src/QuickFlux: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | void registerQuickFluxQmlTypes(); 6 | -------------------------------------------------------------------------------- /src/priv/qfappscriptdispatcherwrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef QFAPPSCRIPTDISPATCHERWRAPPER_H 2 | #define QFAPPSCRIPTDISPATCHERWRAPPER_H 3 | 4 | #include 5 | #include 6 | #include "qfdispatcher.h" 7 | 8 | class QFAppScriptDispatcherWrapper : public QObject 9 | { 10 | Q_OBJECT 11 | public: 12 | QFAppScriptDispatcherWrapper(); 13 | 14 | QString type() const; 15 | void setType(const QString &type); 16 | 17 | QFDispatcher *dispatcher() const; 18 | void setDispatcher(QFDispatcher *dispatcher); 19 | 20 | public slots: 21 | void dispatch(QJSValue arguments); 22 | 23 | private: 24 | QString m_type; 25 | QPointer m_dispatcher; 26 | 27 | }; 28 | 29 | #endif // QFAPPSCRIPTDISPATCHERWRAPPER_H 30 | -------------------------------------------------------------------------------- /src/priv/qfappscriptrunnable.h: -------------------------------------------------------------------------------- 1 | #ifndef QFAPPSCRIPTRUNNABLE_H 2 | #define QFAPPSCRIPTRUNNABLE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | /// QFAppScriptRunnable handles registered callback in QFAppScript (Private class) 10 | 11 | class QFAppScriptRunnable : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit QFAppScriptRunnable(QObject *parent = 0); 16 | ~QFAppScriptRunnable(); 17 | 18 | QJSValue script() const; 19 | void setScript(const QJSValue &script); 20 | 21 | QString type() const; 22 | 23 | void run(QJSValue message); 24 | 25 | QFAppScriptRunnable *next() const; 26 | void setNext(QFAppScriptRunnable *next); 27 | 28 | void setCondition(QJSValue condition); 29 | 30 | void setEngine(QQmlEngine* engine); 31 | 32 | void release(); 33 | 34 | bool isOnceOnly() const; 35 | void setIsOnceOnly(bool isOnceOnly); 36 | 37 | signals: 38 | 39 | public slots: 40 | QFAppScriptRunnable* then(QJSValue condition,QJSValue value); 41 | 42 | private: 43 | void setType(const QString &type); 44 | 45 | QJSValue m_script; 46 | QString m_type; 47 | QFAppScriptRunnable* m_next; 48 | QPointer m_engine; 49 | 50 | QJSValue m_condition; 51 | QJSValue m_callback; 52 | bool m_isSignalCondition; 53 | bool m_isOnceOnly; 54 | }; 55 | 56 | #endif // QFAPPSCRIPTRUNNABLE_H 57 | -------------------------------------------------------------------------------- /src/priv/qfhook.cpp: -------------------------------------------------------------------------------- 1 | #include "qfhook.h" 2 | 3 | QFHook::QFHook(QObject *parent) : QObject(parent) 4 | { 5 | 6 | } 7 | -------------------------------------------------------------------------------- /src/priv/qfhook.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | class QFHook : public QObject 6 | { 7 | Q_OBJECT 8 | public: 9 | explicit QFHook(QObject *parent = 0); 10 | 11 | virtual void dispatch(QString type, QJSValue message) = 0; 12 | 13 | signals: 14 | void dispatched(QString type, QJSValue message); 15 | 16 | }; 17 | -------------------------------------------------------------------------------- /src/priv/qflistener.h: -------------------------------------------------------------------------------- 1 | #ifndef QFLISTENER_H 2 | #define QFLISTENER_H 3 | 4 | #include 5 | #include 6 | 7 | class QFDispatcher; 8 | 9 | /// A listener class for AppDispatcher. 10 | 11 | class QFListener : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit QFListener(QObject *parent = 0); 16 | ~QFListener(); 17 | 18 | QJSValue callback() const; 19 | 20 | void setCallback(const QJSValue &callback); 21 | 22 | void dispatch(QFDispatcher* dispatcher, QString type, QJSValue message); 23 | 24 | int listenerId() const; 25 | 26 | void setListenerId(int listenerId); 27 | 28 | QList waitFor() const; 29 | 30 | void setWaitFor(const QList &waitFor); 31 | 32 | signals: 33 | void dispatched(QString type, QJSValue message); 34 | 35 | public slots: 36 | 37 | private: 38 | QJSValue m_callback; 39 | 40 | int m_listenerId; 41 | 42 | QList m_waitFor; 43 | }; 44 | 45 | #endif // QFLISTENER_H 46 | -------------------------------------------------------------------------------- /src/priv/qfmiddlewareshook.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "qfmiddlewareshook.h" 3 | #include "./priv/quickfluxfunctions.h" 4 | 5 | QFMiddlewaresHook::QFMiddlewaresHook(QObject *parent) : QFHook(parent) 6 | { 7 | 8 | } 9 | 10 | void QFMiddlewaresHook::dispatch(QString type, QJSValue message) 11 | { 12 | if (m_middlewares.isNull()) { 13 | emit dispatched(type , message); 14 | } else { 15 | next(-1, type , message); 16 | } 17 | } 18 | 19 | void QFMiddlewaresHook::setup(QQmlEngine *engine, QObject *middlewares) 20 | { 21 | 22 | m_middlewares = middlewares; 23 | QJSValue mobj = engine->newQObject(middlewares); 24 | QJSValue hobj = engine->newQObject(this); 25 | 26 | QString source = "(function (middlewares, hook) {" 27 | " function create(senderIndex) {" 28 | " return function (type, message) {" 29 | " hook.next(senderIndex, type , message);" 30 | " }" 31 | " }" 32 | " var data = middlewares.data;" 33 | " for (var i = 0 ; i < data.length; i++) {" 34 | " var m = data[i];" 35 | " m._nextCallback = create(i);" 36 | " }" 37 | "})"; 38 | 39 | QJSValue function = engine->evaluate(source); 40 | 41 | QJSValueList args; 42 | args << mobj; 43 | args << hobj; 44 | 45 | QJSValue ret = function.call(args); 46 | 47 | if (ret.isError()) { 48 | QuickFlux::printException(ret); 49 | } 50 | 51 | source = "(function (middlewares, hook) {" 52 | " return function invoke(receiverIndex, type , message) {" 53 | " if (receiverIndex >= middlewares.data.length) {" 54 | " hook.resolve(type, message);" 55 | " return;" 56 | " }" 57 | " var m = middlewares.data[receiverIndex];" 58 | " if (m.filterFunctionEnabled && m.hasOwnProperty(type) && typeof m[type] === \"function\") { " 59 | " m[type](message);" 60 | " } else if (m.hasOwnProperty(\"dispatch\") && typeof m.dispatch === \"function\") {" 61 | " m.dispatch(type, message);" 62 | " } else {" 63 | " invoke(receiverIndex + 1,type, message);" 64 | " }" 65 | " }" 66 | "})"; 67 | 68 | function = engine->evaluate(source); 69 | invoke = function.call(args); 70 | if (invoke.isError()) { 71 | QuickFlux::printException(invoke); 72 | } 73 | } 74 | 75 | void QFMiddlewaresHook::next(int senderIndex, QString type, QJSValue message) 76 | { 77 | QJSValueList args; 78 | 79 | args << QJSValue(senderIndex + 1); 80 | args << QJSValue(type); 81 | args << message; 82 | QJSValue result = invoke.call(args); 83 | if (result.isError()) { 84 | QuickFlux::printException(result); 85 | } 86 | } 87 | 88 | void QFMiddlewaresHook::resolve(QString type, QJSValue message) 89 | { 90 | emit dispatched(type, message); 91 | } 92 | -------------------------------------------------------------------------------- /src/priv/qfmiddlewareshook.h: -------------------------------------------------------------------------------- 1 | #ifndef QFMIDDLEWARESHOOK_H 2 | #define QFMIDDLEWARESHOOK_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "./priv/qfhook.h" 8 | 9 | class QFMiddlewaresHook : public QFHook 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit QFMiddlewaresHook(QObject *parent = nullptr); 14 | 15 | signals: 16 | 17 | public: 18 | void dispatch(QString type, QJSValue message); 19 | void setup(QQmlEngine* engine, QObject* middlewares); 20 | 21 | public slots: 22 | void next(int senderId, QString type, QJSValue message); 23 | void resolve(QString type, QJSValue message); 24 | 25 | 26 | private: 27 | QJSValue invoke; 28 | QPointer m_middlewares; 29 | }; 30 | 31 | #endif // QFMIDDLEWARESHOOK_H 32 | -------------------------------------------------------------------------------- /src/priv/qfsignalproxy.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "qfsignalproxy.h" 4 | 5 | QFSignalProxy::QFSignalProxy(QObject *parent) : QObject(parent) 6 | { 7 | 8 | } 9 | 10 | void QFSignalProxy::bind(QObject *source, int signalIdx, QQmlEngine* engine, QFDispatcher* dispatcher) 11 | { 12 | const int memberOffset = QObject::staticMetaObject.methodCount(); 13 | 14 | QMetaMethod method = source->metaObject()->method(signalIdx); 15 | 16 | parameterTypes = QVector(method.parameterCount()); 17 | parameterNames = QVector(method.parameterCount()); 18 | type = method.name(); 19 | m_engine = engine; 20 | m_dispatcher = dispatcher; 21 | 22 | for (int i = 0 ; i < method.parameterCount() ; i++) { 23 | parameterTypes[i] = method.parameterType(i); 24 | parameterNames[i] = QString(method.parameterNames().at(i)); 25 | } 26 | 27 | if (!QMetaObject::connect(source, signalIdx, this, memberOffset, Qt::AutoConnection, 0)) { 28 | qWarning() << "Failed to bind signal"; 29 | } 30 | } 31 | 32 | int QFSignalProxy::qt_metacall(QMetaObject::Call _c, int _id, void **_a) 33 | { 34 | int methodId = QObject::qt_metacall(_c, _id, _a); 35 | 36 | if (methodId < 0) { 37 | return methodId; 38 | } 39 | 40 | if (_c == QMetaObject::InvokeMetaMethod) { 41 | 42 | if (methodId == 0) { 43 | QVariantMap message; 44 | 45 | for (int i = 0 ; i < parameterTypes.count() ; i++) { 46 | const QMetaType::Type type = static_cast(parameterTypes.at(i)); 47 | QVariant v; 48 | 49 | if (type == QMetaType::QVariant) { 50 | v = *reinterpret_cast(_a[i + 1]); 51 | } else { 52 | v = QVariant(type, _a[i + 1]); 53 | } 54 | 55 | message[parameterNames.at(i)] = v; 56 | } 57 | 58 | dispatch(message); 59 | } 60 | methodId--; 61 | } 62 | 63 | return methodId; 64 | } 65 | 66 | void QFSignalProxy::dispatch(const QVariantMap &message) 67 | { 68 | if (m_engine.isNull() || m_dispatcher.isNull()) { 69 | return; 70 | } 71 | 72 | QJSValue value = m_engine->newObject(); 73 | 74 | QMapIterator iter(message); 75 | while (iter.hasNext()) { 76 | iter.next(); 77 | QJSValue v = m_engine->toScriptValue(iter.value()); 78 | value.setProperty(iter.key(), v); 79 | } 80 | 81 | m_dispatcher->dispatch(type, value); 82 | } 83 | 84 | QFDispatcher *QFSignalProxy::dispatcher() const 85 | { 86 | return m_dispatcher; 87 | } 88 | 89 | void QFSignalProxy::setDispatcher(QFDispatcher *dispatcher) 90 | { 91 | m_dispatcher = dispatcher; 92 | } 93 | -------------------------------------------------------------------------------- /src/priv/qfsignalproxy.h: -------------------------------------------------------------------------------- 1 | #ifndef QFSIGNALPROXY_H 2 | #define QFSIGNALPROXY_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "qfdispatcher.h" 10 | 11 | class QFSignalProxy : public QObject 12 | { 13 | public: 14 | explicit QFSignalProxy(QObject *parent = 0); 15 | 16 | void bind(QObject* source, int signalIdx, QQmlEngine* engine, QFDispatcher* dispatcher); 17 | 18 | int qt_metacall(QMetaObject::Call _c, int _id, void **_a); 19 | 20 | QFDispatcher *dispatcher() const; 21 | 22 | void setDispatcher(QFDispatcher *dispatcher); 23 | 24 | private: 25 | void dispatch(const QVariantMap &message); 26 | 27 | QString type; 28 | QVector parameterTypes; 29 | QVector parameterNames; 30 | QPointer m_engine; 31 | QPointer m_dispatcher; 32 | 33 | }; 34 | 35 | #endif // QFSIGNALPROXY_H 36 | -------------------------------------------------------------------------------- /src/priv/quickfluxfunctions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "quickfluxfunctions.h" 3 | 4 | void QuickFlux::printException(QJSValue value) 5 | { 6 | if (value.isError()) { 7 | QString message = QString("%1:%2: %3: %4") 8 | .arg(value.property("fileName").toString()) 9 | .arg(value.property("lineNumber").toString()) 10 | .arg(value.property("name").toString()) 11 | .arg(value.property("message").toString()); 12 | qWarning() << message; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/priv/quickfluxfunctions.h: -------------------------------------------------------------------------------- 1 | #ifndef QUICKFLUXFUNCTIONS_H 2 | #define QUICKFLUXFUNCTIONS_H 3 | 4 | #include 5 | 6 | namespace QuickFlux { 7 | 8 | void printException(QJSValue value); 9 | 10 | } 11 | 12 | #if ((QT_VERSION == QT_VERSION_CHECK(5,7,1)) || \ 13 | (QT_VERSION == QT_VERSION_CHECK(5,8,0)) || \ 14 | (QT_VERSION == QT_VERSION_CHECK(5,6,2)) || \ 15 | defined(SAILFISH_OS)) 16 | 17 | /* A dirty hack to fix QTBUG-58133 and #13 issue. 18 | 19 | Reference: 20 | 1. https://bugreports.qt.io/browse/QTBUG-58133 21 | 2. https://github.com/benlau/quickflux/issues/13 22 | */ 23 | 24 | #define QF_PRECHECK_DISPATCH(engine, type, message) {\ 25 | if (message.isUndefined() && engine) { \ 26 | message = engine->toScriptValue(QVariant()); \ 27 | }\ 28 | } 29 | #else 30 | #define QF_PRECHECK_DISPATCH(engine, type, message) Q_UNUSED(engine) 31 | #endif 32 | 33 | 34 | #endif // QUICKFLUXFUNCTIONS_H 35 | -------------------------------------------------------------------------------- /src/qfactioncreator.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "qfactioncreator.h" 5 | #include "priv/qfsignalproxy.h" 6 | #include "qfdispatcher.h" 7 | 8 | /*! 9 | \qmltype ActionCreator 10 | \brief Create message from signal then dispatch via AppDispatcher 11 | 12 | ActionCreator is a component that listens on its own signals, convert to message then dispatch via AppDispatcher. The message type will be same as the signal name. There has no limitation on the number of arguments and their data type. 13 | 14 | For example, you may declare an ActionCreator based component as: 15 | 16 | \code 17 | import QtQuick 2.0 18 | import QuickFlux 1.0 19 | pragma singleton 20 | 21 | ActionCreator { 22 | signal open(string url); 23 | } 24 | \endcode 25 | 26 | It is equivalent to: 27 | 28 | \code 29 | import QtQuick 2.0 30 | import QuickFlux 1.0 31 | pragma singleton 32 | 33 | Item { 34 | function open(url) { 35 | AppDispatcher.dispatch(“open”, {url: url}); 36 | } 37 | } 38 | \endcode 39 | 40 | */ 41 | 42 | QFActionCreator::QFActionCreator(QObject *parent) : QObject(parent) 43 | { 44 | 45 | } 46 | 47 | QString QFActionCreator::genKeyTable() 48 | { 49 | QStringList imports, header, footer, properties; 50 | 51 | imports << "pragma Singleton" 52 | << "import QtQuick 2.0" 53 | << "import QuickFlux 1.0\n"; 54 | 55 | header << "KeyTable {\n"; 56 | 57 | footer << "}"; 58 | 59 | const int memberOffset = QObject::staticMetaObject.methodCount(); 60 | 61 | const QMetaObject* meta = metaObject(); 62 | 63 | int count = meta->methodCount(); 64 | 65 | for (int i = memberOffset ; i < count ;i++) { 66 | QMetaMethod method = meta->method(i); 67 | if (method.name() == "dispatcherChanged") { 68 | continue; 69 | } 70 | if (method.methodType() == QMetaMethod::Signal) { 71 | properties << QString(" property string %1;\n").arg(QString(method.name())); 72 | } 73 | } 74 | 75 | QStringList content; 76 | content << imports << header << properties << footer; 77 | 78 | return content.join("\n"); 79 | } 80 | 81 | void QFActionCreator::dispatch(QString type, QJSValue message) 82 | { 83 | if (!m_dispatcher.isNull()) { 84 | m_dispatcher->dispatch(type, message); 85 | } 86 | } 87 | 88 | void QFActionCreator::classBegin() 89 | { 90 | 91 | } 92 | 93 | void QFActionCreator::componentComplete() 94 | { 95 | QQmlEngine* engine = qmlEngine(this); 96 | 97 | if (m_dispatcher.isNull()) { 98 | setDispatcher(qobject_cast(QFAppDispatcher::instance(engine))); 99 | } 100 | 101 | QFDispatcher* dispatcher = m_dispatcher.data(); 102 | 103 | const int memberOffset = QObject::staticMetaObject.methodCount(); 104 | 105 | const QMetaObject* meta = metaObject(); 106 | 107 | int count = meta->methodCount(); 108 | 109 | for (int i = memberOffset ; i < count ;i++) { 110 | QMetaMethod method = meta->method(i); 111 | if (method.name() == "dispatcherChanged") { 112 | continue; 113 | } 114 | 115 | if (method.methodType() == QMetaMethod::Signal) { 116 | QFSignalProxy* proxy = new QFSignalProxy(this); 117 | proxy->bind(this, i, engine, dispatcher); 118 | m_proxyList << proxy; 119 | } 120 | } 121 | } 122 | 123 | /*! \qmlproperty object ActionCreator::dispatcher 124 | 125 | This property holds the target Dispatcher instance. It will dispatch all the actions to that object. 126 | 127 | \code 128 | 129 | ActionCreator { 130 | dispatcher: Dispatcher { 131 | } 132 | } 133 | 134 | \endcode 135 | 136 | The default value is AppDispatcher 137 | */ 138 | 139 | 140 | QFDispatcher *QFActionCreator::dispatcher() const 141 | { 142 | return m_dispatcher; 143 | } 144 | 145 | void QFActionCreator::setDispatcher(QFDispatcher *value) 146 | { 147 | m_dispatcher = value; 148 | for (int i = 0 ; i < m_proxyList.size();i++) { 149 | m_proxyList[i]->setDispatcher(m_dispatcher); 150 | } 151 | 152 | emit dispatcherChanged(); 153 | } 154 | -------------------------------------------------------------------------------- /src/qfactioncreator.h: -------------------------------------------------------------------------------- 1 | #ifndef QFACTIONCREATOR_H 2 | #define QFACTIONCREATOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include "priv/qfsignalproxy.h" 9 | 10 | class QFActionCreator : public QObject, public QQmlParserStatus 11 | { 12 | Q_OBJECT 13 | Q_INTERFACES(QQmlParserStatus) 14 | Q_PROPERTY(QFDispatcher* dispatcher READ dispatcher WRITE setDispatcher NOTIFY dispatcherChanged) 15 | 16 | public: 17 | explicit QFActionCreator(QObject *parent = 0); 18 | 19 | QFDispatcher *dispatcher() const; 20 | void setDispatcher(QFDispatcher *value); 21 | 22 | public slots: 23 | QString genKeyTable(); 24 | void dispatch(QString type, QJSValue message = QJSValue()); 25 | 26 | signals: 27 | void dispatcherChanged(); 28 | 29 | protected: 30 | void classBegin(); 31 | void componentComplete(); 32 | 33 | private: 34 | QPointer m_dispatcher; 35 | QList m_proxyList; 36 | }; 37 | 38 | #endif // QFACTIONCREATOR_H 39 | -------------------------------------------------------------------------------- /src/qfappdispatcher.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "qfappdispatcher.h" 4 | 5 | /*! 6 | \qmltype AppDispatcher 7 | \inqmlmodule QuickFlux 8 | \brief Message Dispatcher 9 | \inherits Dispatcher 10 | 11 | AppDispatcher is a singleton object for delivering action message. 12 | */ 13 | 14 | 15 | QFAppDispatcher::QFAppDispatcher(QObject *parent) : QFDispatcher(parent) 16 | { 17 | 18 | } 19 | 20 | /*! \fn QFAppDispatcher *QFAppDispatcher::instance(QQmlEngine *engine) 21 | 22 | Obtain the singleton instance of AppDispatcher for specific \a engine 23 | 24 | */ 25 | 26 | QFAppDispatcher *QFAppDispatcher::instance(QQmlEngine *engine) 27 | { 28 | QFAppDispatcher *dispatcher = qobject_cast(singletonObject(engine,"QuickFlux",1,0,"AppDispatcher")); 29 | 30 | return dispatcher; 31 | } 32 | 33 | 34 | /*! \fn QObject *QFAppDispatcher::singletonObject(QQmlEngine *engine, QString package, int versionMajor, int versionMinor, QString typeName) 35 | 36 | \a engine QQmlEngine instance 37 | 38 | \a package The package name of the singleton object 39 | 40 | \a versionMajor The major version no. of the singleton object 41 | 42 | \a versionMinor The minor version no. of the singleton object 43 | 44 | \a typeName The name of the singleton object 45 | 46 | Obtain a singleton object from a package for specific QQmlEngine instance. 47 | It is useful when you need to get a singleton Actions object from C++. 48 | 49 | */ 50 | 51 | QObject *QFAppDispatcher::singletonObject(QQmlEngine *engine, QString package, int versionMajor, int versionMinor, QString typeName) 52 | { 53 | QString pattern = "import QtQuick 2.0\nimport %1 %2.%3;QtObject { property var object : %4 }"; 54 | 55 | QString qml = pattern.arg(package).arg(versionMajor).arg(versionMinor).arg(typeName); 56 | 57 | QObject* holder = 0; 58 | 59 | QQmlComponent comp (engine); 60 | comp.setData(qml.toUtf8(),QUrl()); 61 | holder = comp.create(); 62 | 63 | if (!holder) { 64 | qWarning() << QString("QuickFlux: Failed to gain singleton object: %1").arg(typeName); 65 | qWarning() << QString("Error: ") << comp.errorString(); 66 | return 0; 67 | } 68 | 69 | QObject*object = holder->property("object").value(); 70 | holder->deleteLater(); 71 | 72 | if (!object) { 73 | qWarning() << QString("QuickFlux: Failed to gain singleton object: %1").arg(typeName); 74 | qWarning() << QString("Error: Unknown"); 75 | } 76 | 77 | return object; 78 | } 79 | -------------------------------------------------------------------------------- /src/qfappdispatcher.h: -------------------------------------------------------------------------------- 1 | #ifndef QFAPPDISPATCHER_H 2 | #define QFAPPDISPATCHER_H 3 | 4 | #include 5 | #include "qfdispatcher.h" 6 | 7 | class QFAppDispatcher : public QFDispatcher 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit QFAppDispatcher(QObject *parent = 0); 12 | 13 | /// Obtain the singleton instance of AppDispatcher for specific QQmlEngine 14 | static QFAppDispatcher* instance(QQmlEngine* engine); 15 | 16 | /// Obtain a singleton object from package for specific QQmlEngine 17 | static QObject* singletonObject(QQmlEngine* engine,QString package, 18 | int versionMajor, 19 | int versionMinor, 20 | QString typeName); 21 | }; 22 | 23 | #endif // QFAPPDISPATCHER_H 24 | -------------------------------------------------------------------------------- /src/qfapplistener.h: -------------------------------------------------------------------------------- 1 | #ifndef QFAPPLISTENER_H 2 | #define QFAPPLISTENER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "priv/qflistener.h" 11 | #include "qfdispatcher.h" 12 | 13 | class QFAppListener : public QQuickItem 14 | { 15 | Q_OBJECT 16 | Q_PROPERTY(QString filter READ filter WRITE setFilter NOTIFY filterChanged) 17 | Q_PROPERTY(QStringList filters READ filters WRITE setFilters NOTIFY filtersChanged) 18 | Q_PROPERTY(bool alwaysOn READ alwaysOn WRITE setAlwaysOn NOTIFY alwaysOnChanged) 19 | Q_PROPERTY(int listenerId READ listenerId WRITE setListenerId NOTIFY listenerIdChanged) 20 | Q_PROPERTY(QList waitFor READ waitFor WRITE setWaitFor NOTIFY waitForChanged) 21 | 22 | public: 23 | explicit QFAppListener(QQuickItem *parent = nullptr); 24 | ~QFAppListener(); 25 | 26 | /// Get the listening target. 27 | QObject *target() const; 28 | 29 | /// Set the listening target. If the class is constructed by QQmlComponent. It will be set automatically. 30 | void setTarget(QFDispatcher *target); 31 | 32 | /// Add a listener to the end of the listeners array for the specified message. Multiple calls passing the same combination of event and listener will result in the listener being added multiple times. 33 | Q_INVOKABLE QFAppListener* on(QString type,QJSValue callback); 34 | 35 | /// Remove a listener from the listener array for the specified message. 36 | Q_INVOKABLE void removeListener(QString type,QJSValue callback); 37 | 38 | /// Remove all the listeners for a message with type. If type is empty, it will remove all the listeners. 39 | Q_INVOKABLE void removeAllListener(QString type = QString()); 40 | 41 | /// Get the filter for incoming message 42 | QString filter() const; 43 | 44 | /// Set a filter to incoming message. Only message with type matched with the filter will emit "dispatched" signal. 45 | void setFilter(const QString &filter); 46 | 47 | /// Get a list of filter for incoming message 48 | QStringList filters() const; 49 | 50 | /// Set a list of filter to incoming message. Only message with type matched with the filters will emit "dispatched" signal. 51 | void setFilters(const QStringList &filters); 52 | 53 | bool alwaysOn() const; 54 | void setAlwaysOn(bool alwaysOn); 55 | 56 | int listenerId() const; 57 | void setListenerId(int listenerId); 58 | 59 | QList waitFor() const; 60 | 61 | void setWaitFor(const QList &waitFor); 62 | 63 | signals: 64 | /// It is emitted whatever it has received a dispatched message from AppDispatcher. 65 | Q_SIGNAL void dispatched(QString type,QJSValue message); 66 | 67 | void filterChanged(); 68 | 69 | void filtersChanged(); 70 | 71 | void alwaysOnChanged(); 72 | 73 | void listenerIdChanged(); 74 | 75 | void waitForChanged(); 76 | 77 | public slots: 78 | 79 | private: 80 | virtual void componentComplete(); 81 | 82 | Q_INVOKABLE void onMessageReceived(QString name,QJSValue message); 83 | 84 | void setListenerWaitFor(); 85 | 86 | QPointer m_target; 87 | 88 | QMap > mapping; 89 | 90 | QString m_filter; 91 | QStringList m_filters; 92 | bool m_alwaysOn; 93 | 94 | int m_listenerId; 95 | QFListener* m_listener; 96 | 97 | QList m_waitFor; 98 | }; 99 | 100 | #endif // QFAPPLISTENER_H 101 | -------------------------------------------------------------------------------- /src/qfapplistenergroup.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "qfapplistenergroup.h" 3 | #include "qfapplistener.h" 4 | #include "priv/qflistener.h" 5 | 6 | QFAppListenerGroup::QFAppListenerGroup(QQuickItem* parent) : QQuickItem(parent) 7 | { 8 | m_listenerId = 0; 9 | m_listener = 0; 10 | } 11 | 12 | QList QFAppListenerGroup::listenerIds() const 13 | { 14 | return m_listenerIds; 15 | } 16 | 17 | void QFAppListenerGroup::setListenerIds(const QList &listenerIds) 18 | { 19 | m_listenerIds = listenerIds; 20 | emit listenerIdsChanged(); 21 | } 22 | 23 | void QFAppListenerGroup::componentComplete() 24 | { 25 | QQuickItem::componentComplete(); 26 | 27 | QQmlEngine *engine = qmlEngine(this); 28 | Q_ASSERT(engine); 29 | 30 | QFAppDispatcher* dispatcher = QFAppDispatcher::instance(engine); 31 | 32 | m_listener = new QFListener(this); 33 | m_listenerId = dispatcher->addListener(m_listener); 34 | setListenerWaitFor(); 35 | 36 | QList ids = search(this); 37 | setListenerIds(ids); 38 | } 39 | 40 | QList QFAppListenerGroup::search(QQuickItem *item) 41 | { 42 | QList res; 43 | 44 | QFAppListener* listener = qobject_cast(item); 45 | 46 | if (listener) { 47 | res.append(listener->listenerId()); 48 | listener->setWaitFor(QList() << m_listenerId); 49 | } 50 | 51 | QList childs = item->childItems(); 52 | 53 | for (int i = 0 ; i < childs.size() ; i++) { 54 | QList subRes = search(childs.at(i)); 55 | if (subRes.size() > 0) { 56 | res.append(subRes); 57 | } 58 | } 59 | return res; 60 | } 61 | 62 | void QFAppListenerGroup::setListenerWaitFor() 63 | { 64 | m_listener->setWaitFor(m_waitFor); 65 | } 66 | 67 | QList QFAppListenerGroup::waitFor() const 68 | { 69 | return m_waitFor; 70 | } 71 | 72 | void QFAppListenerGroup::setWaitFor(const QList &waitFor) 73 | { 74 | m_waitFor = waitFor; 75 | setListenerWaitFor(); 76 | emit waitForChanged(); 77 | } 78 | 79 | -------------------------------------------------------------------------------- /src/qfapplistenergroup.h: -------------------------------------------------------------------------------- 1 | #ifndef QFAPPLISTENERGROUP_H 2 | #define QFAPPLISTENERGROUP_H 3 | 4 | #include 5 | #include 6 | #include "priv/qflistener.h" 7 | 8 | class QFAppListenerGroup : public QQuickItem 9 | { 10 | Q_OBJECT 11 | Q_PROPERTY(QList listenerIds READ listenerIds WRITE setListenerIds NOTIFY listenerIdsChanged) 12 | Q_PROPERTY(QList waitFor READ waitFor WRITE setWaitFor NOTIFY waitForChanged) 13 | 14 | public: 15 | QFAppListenerGroup(QQuickItem* parent = 0); 16 | 17 | QList listenerIds() const; 18 | 19 | void setListenerIds(const QList &listenerIds); 20 | 21 | QList waitFor() const; 22 | 23 | void setWaitFor(const QList &waitFor); 24 | 25 | public slots: 26 | 27 | signals: 28 | 29 | void listenerIdsChanged(); 30 | void waitForChanged(); 31 | 32 | private: 33 | virtual void componentComplete(); 34 | 35 | QList search(QQuickItem* item); 36 | 37 | void setListenerWaitFor(); 38 | 39 | QList m_waitFor; 40 | 41 | QList m_listenerIds; 42 | int m_listenerId; 43 | QFListener* m_listener; 44 | }; 45 | 46 | #endif // QFAPPLISTENERGROUP_H 47 | -------------------------------------------------------------------------------- /src/qfappscript.h: -------------------------------------------------------------------------------- 1 | #ifndef QFAPPSCRIPT_H 2 | #define QFAPPSCRIPT_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include "priv/qfappscriptrunnable.h" 12 | 13 | class QFAppScript : public QQuickItem 14 | { 15 | Q_OBJECT 16 | Q_PROPERTY(QQmlScriptString script READ script WRITE setScript NOTIFY scriptChanged) 17 | Q_PROPERTY(bool running READ running WRITE setRunning NOTIFY runningChanged) 18 | Q_PROPERTY(QString runWhen READ runWhen WRITE setRunWhen NOTIFY runWhenChanged) 19 | Q_PROPERTY(QJSValue message READ message NOTIFY messageChanged) 20 | Q_PROPERTY(int listenerId READ listenerId WRITE setListenerId NOTIFY listenerIdChanged) 21 | Q_PROPERTY(QList waitFor READ waitFor WRITE setWaitFor NOTIFY waitForChanged) 22 | Q_PROPERTY(bool autoExit READ autoExit WRITE setAutoExit NOTIFY autoExitChanged) 23 | 24 | public: 25 | explicit QFAppScript(QQuickItem *parent = 0); 26 | 27 | QQmlScriptString script() const; 28 | void setScript(const QQmlScriptString &script); 29 | 30 | bool running() const; 31 | 32 | QString runWhen() const; 33 | void setRunWhen(const QString &runWhen); 34 | 35 | QJSValue message() const; 36 | void setMessage(const QJSValue &message); 37 | 38 | int listenerId() const; 39 | void setListenerId(int listenerId); 40 | 41 | QList waitFor() const; 42 | void setWaitFor(const QList &waitFor); 43 | 44 | bool autoExit() const; 45 | void setAutoExit(bool autoExit); 46 | 47 | signals: 48 | void started(); 49 | void finished(int returnCode); 50 | 51 | void scriptChanged(); 52 | void runningChanged(); 53 | void runWhenChanged(); 54 | void messageChanged(); 55 | void listenerIdChanged(); 56 | void waitForChanged(); 57 | void autoExitChanged(); 58 | 59 | public slots: 60 | void exit(int returnCode = 0); 61 | void run(QJSValue message = QJSValue()); 62 | 63 | QFAppScriptRunnable* once(QJSValue condition, QJSValue script); 64 | void on(QJSValue condition, QJSValue script); 65 | 66 | private slots: 67 | void onDispatched(QString type,QJSValue message); 68 | 69 | private: 70 | virtual void componentComplete(); 71 | void abort(); 72 | void clear(); 73 | void setRunning(bool running); 74 | 75 | void setListenerWaitFor(); 76 | 77 | QQmlScriptString m_script; 78 | QList m_runnables; 79 | QPointer m_dispatcher; 80 | QString m_runWhen; 81 | 82 | bool m_running; 83 | bool m_processing; 84 | 85 | int m_listenerId; 86 | 87 | bool m_autoExit; 88 | 89 | // The message object passed to run() 90 | QJSValue m_message; 91 | QFListener* m_listener; 92 | 93 | QList m_waitFor; 94 | }; 95 | 96 | #endif // QFAPPSCRIPT_H 97 | -------------------------------------------------------------------------------- /src/qfappscriptdispatcherwrapper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "qfdispatcher.h" 3 | #include "priv/qfappscriptdispatcherwrapper.h" 4 | 5 | QFAppScriptDispatcherWrapper::QFAppScriptDispatcherWrapper() 6 | { 7 | 8 | } 9 | 10 | QString QFAppScriptDispatcherWrapper::type() const 11 | { 12 | return m_type; 13 | } 14 | 15 | void QFAppScriptDispatcherWrapper::setType(const QString &type) 16 | { 17 | m_type = type; 18 | } 19 | 20 | void QFAppScriptDispatcherWrapper::dispatch(QJSValue arguments) 21 | { 22 | if (m_dispatcher.isNull()) { 23 | qWarning() << "AppScript::Unexcepted condition: AppDispatcher is not present."; 24 | return; 25 | } 26 | 27 | m_dispatcher->dispatch(m_type,arguments); 28 | } 29 | 30 | QFDispatcher *QFAppScriptDispatcherWrapper::dispatcher() const 31 | { 32 | return m_dispatcher; 33 | } 34 | 35 | void QFAppScriptDispatcherWrapper::setDispatcher(QFDispatcher *dispatcher) 36 | { 37 | m_dispatcher = dispatcher; 38 | } 39 | -------------------------------------------------------------------------------- /src/qfappscriptgroup.cpp: -------------------------------------------------------------------------------- 1 | #include "qfappscriptgroup.h" 2 | 3 | /*! \qmltype AppScriptGroup 4 | \inqmlmodule QuickFlux 5 | 6 | AppScriptGroup hold a group of AppScript objects which are mutually exclusive in execution. 7 | Whatever a AppScript is going to start, it will terminate all other AppScript objects. 8 | So that only one AppScript is running at a time. 9 | 10 | \code 11 | 12 | Item { 13 | 14 | AppScript { 15 | id: script1 16 | script: { 17 | // write script here 18 | } 19 | } 20 | 21 | AppScript { 22 | id: script2 23 | script: { 24 | // write script here 25 | } 26 | } 27 | 28 | AppScriptGroup { 29 | scripts: [script1, script2] 30 | } 31 | 32 | Component.onCompleted: { 33 | 34 | script1.run(); 35 | 36 | script2.run(); 37 | 38 | // At this point, AppScriptGroup will force script1 to terminate since script2 has been started. 39 | 40 | } 41 | 42 | } 43 | 44 | \endcode 45 | 46 | */ 47 | 48 | QFAppScriptGroup::QFAppScriptGroup(QQuickItem* parent) : QQuickItem(parent) 49 | { 50 | 51 | } 52 | 53 | /*! \qmlproperty array AppScriptGroup::scripts 54 | This property hold an array of AppScript object. 55 | They are mutually exclusive in execution. 56 | 57 | \code 58 | 59 | AppScript { 60 | id: script1 61 | } 62 | 63 | AppScript { 64 | id: script2 65 | } 66 | 67 | AppScriptGroup { 68 | scripts: [script1, script2] 69 | } 70 | 71 | \endcode 72 | */ 73 | 74 | QJSValue QFAppScriptGroup::scripts() const 75 | { 76 | return m_scripts; 77 | } 78 | 79 | void QFAppScriptGroup::setScripts(const QJSValue &scripts) 80 | { 81 | for (int i = 0 ; i < objects.count() ; i++) { 82 | if (objects.at(i).data()) { 83 | objects.at(i)->disconnect(this); 84 | } 85 | } 86 | 87 | objects.clear(); 88 | m_scripts = scripts; 89 | 90 | if (!scripts.isArray()) { 91 | qWarning() << "AppScriptGroup: Invalid scripts property"; 92 | return; 93 | } 94 | 95 | int count = scripts.property("length").toInt(); 96 | 97 | for (int i = 0 ; i < count ; i++) { 98 | QJSValue item = scripts.property(i); 99 | 100 | if (!item.isQObject()) { 101 | qWarning() << "AppScriptGroup: Invalid scripts property"; 102 | continue; 103 | } 104 | 105 | QFAppScript* object = qobject_cast(item.toQObject()); 106 | 107 | if (!object) { 108 | qWarning() << "AppScriptGroup: Invalid scripts property"; 109 | continue; 110 | } 111 | objects << object; 112 | connect(object,SIGNAL(started()), 113 | this,SLOT(onStarted())); 114 | } 115 | 116 | emit scriptsChanged(); 117 | } 118 | 119 | /*! \qmlmethod AppScriptGroup::exitAll() 120 | 121 | Terminate all AppScript objects 122 | 123 | */ 124 | 125 | void QFAppScriptGroup::exitAll() 126 | { 127 | for (int i = 0 ; i < objects.count() ; i++) { 128 | if (objects.at(i).data()) { 129 | objects.at(i)->exit(); 130 | } 131 | } 132 | } 133 | 134 | void QFAppScriptGroup::onStarted() 135 | { 136 | QFAppScript* source = qobject_cast(sender()); 137 | 138 | for (int i = 0 ; i < objects.count() ; i++) { 139 | QPointer object = objects.at(i); 140 | if (object.isNull()) 141 | continue; 142 | 143 | if (object.data() != source) { 144 | object->exit(); 145 | } 146 | } 147 | 148 | } 149 | 150 | -------------------------------------------------------------------------------- /src/qfappscriptgroup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "qfappscript.h" 6 | 7 | class QFAppScriptGroup : public QQuickItem 8 | { 9 | Q_OBJECT 10 | Q_PROPERTY(QJSValue scripts READ scripts WRITE setScripts NOTIFY scriptsChanged) 11 | 12 | public: 13 | QFAppScriptGroup(QQuickItem* parent = 0); 14 | 15 | QJSValue scripts() const; 16 | 17 | void setScripts(const QJSValue &scripts); 18 | 19 | signals: 20 | void scriptsChanged(); 21 | 22 | public slots: 23 | void exitAll(); 24 | 25 | private slots: 26 | void onStarted(); 27 | 28 | private: 29 | QJSValue m_scripts; 30 | QList > objects; 31 | }; 32 | -------------------------------------------------------------------------------- /src/qfappscriptrunnable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "priv/qfappscriptrunnable.h" 5 | #include "priv/qfappscriptdispatcherwrapper.h" 6 | 7 | QFAppScriptRunnable::QFAppScriptRunnable(QObject *parent) : QObject(parent) 8 | { 9 | m_engine = 0; 10 | m_next = 0; 11 | m_isSignalCondition = false; 12 | m_isOnceOnly = true; 13 | } 14 | 15 | QFAppScriptRunnable::~QFAppScriptRunnable() 16 | { 17 | release(); 18 | } 19 | 20 | QJSValue QFAppScriptRunnable::script() const 21 | { 22 | return m_script; 23 | } 24 | 25 | void QFAppScriptRunnable::setScript(const QJSValue &script) 26 | { 27 | m_script = script; 28 | } 29 | 30 | QString QFAppScriptRunnable::type() const 31 | { 32 | return m_type; 33 | } 34 | 35 | void QFAppScriptRunnable::setType(const QString &type) 36 | { 37 | m_type = type; 38 | } 39 | 40 | bool QFAppScriptRunnable::isOnceOnly() const 41 | { 42 | return m_isOnceOnly; 43 | } 44 | 45 | void QFAppScriptRunnable::setIsOnceOnly(bool isOnceOnly) 46 | { 47 | m_isOnceOnly = isOnceOnly; 48 | } 49 | 50 | void QFAppScriptRunnable::setEngine(QQmlEngine* engine) 51 | { 52 | m_engine = engine; 53 | } 54 | 55 | void QFAppScriptRunnable::release() 56 | { 57 | if (!m_condition.isNull() && 58 | m_condition.isObject() && 59 | m_condition.hasProperty("disconnect")) { 60 | 61 | QJSValue disconnect = m_condition.property("disconnect"); 62 | QJSValueList args; 63 | args << m_callback; 64 | 65 | disconnect.callWithInstance(m_condition,args); 66 | } 67 | 68 | m_condition = QJSValue(); 69 | m_callback = QJSValue(); 70 | } 71 | 72 | void QFAppScriptRunnable::run(QJSValue message) 73 | { 74 | QJSValueList args; 75 | if (m_isSignalCondition && 76 | message.hasProperty("length")) { 77 | int count = message.property("length").toInt(); 78 | for (int i = 0 ; i < count;i++) { 79 | args << message.property(i); 80 | } 81 | } else { 82 | args << message; 83 | } 84 | QJSValue ret = m_script.call(args); 85 | 86 | if (ret.isError()) { 87 | QString message = QString("%1:%2: %3: %4") 88 | .arg(ret.property("fileName").toString()) 89 | .arg(ret.property("lineNumber").toString()) 90 | .arg(ret.property("name").toString()) 91 | .arg(ret.property("message").toString()); 92 | qWarning() << message; 93 | } 94 | 95 | } 96 | 97 | QFAppScriptRunnable *QFAppScriptRunnable::then(QJSValue condition,QJSValue script) 98 | { 99 | QFAppScriptRunnable* runnable = new QFAppScriptRunnable(this); 100 | runnable->setEngine(m_engine.data()); 101 | runnable->setCondition(condition); 102 | runnable->setScript(script); 103 | setNext(runnable); 104 | return runnable; 105 | } 106 | 107 | QFAppScriptRunnable *QFAppScriptRunnable::next() const 108 | { 109 | return m_next; 110 | } 111 | 112 | void QFAppScriptRunnable::setNext(QFAppScriptRunnable *next) 113 | { 114 | m_next = next; 115 | } 116 | 117 | void QFAppScriptRunnable::setCondition(QJSValue condition) 118 | { 119 | m_condition = condition; 120 | 121 | if (condition.isString()) { 122 | setType(condition.toString()); 123 | m_isSignalCondition = false; 124 | } else if (condition.isObject() && condition.hasProperty("connect")) { 125 | Q_ASSERT(!m_engine.isNull()); 126 | 127 | QString type = QString("QuickFlux.AppScript.%1").arg(QUuid::createUuid().toString()); 128 | setType(type); 129 | 130 | QString generator = "(function(dispatcher) { return function() {dispatcher.dispatch(arguments)}})"; 131 | QFAppDispatcher* dispatcher = QFAppDispatcher::instance(m_engine); 132 | QFAppScriptDispatcherWrapper * wrapper = new QFAppScriptDispatcherWrapper(); 133 | wrapper->setType(type); 134 | wrapper->setDispatcher(dispatcher); 135 | 136 | QJSValue generatorFunc = m_engine->evaluate(generator); 137 | 138 | QJSValueList args; 139 | args << m_engine->newQObject(wrapper); 140 | QJSValue callback = generatorFunc.call(args); 141 | 142 | args.clear(); 143 | args << callback; 144 | 145 | QJSValue connect = condition.property("connect"); 146 | connect.callWithInstance(condition,args); 147 | 148 | m_callback = callback; 149 | m_isSignalCondition = true; 150 | } else { 151 | qWarning() << "AppScript: Invalid condition type"; 152 | } 153 | } 154 | 155 | -------------------------------------------------------------------------------- /src/qfdispatcher.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "priv/qflistener.h" 11 | #include "priv/qfhook.h" 12 | /// Message Dispatcher 13 | 14 | class QFDispatcher : public QObject 15 | { 16 | Q_OBJECT 17 | public: 18 | explicit QFDispatcher(QObject *parent = 0); 19 | ~QFDispatcher(); 20 | 21 | signals: 22 | /// Listeners should listen on this signal to get the latest dispatched message from AppDispatcher 23 | Q_SIGNAL void dispatched(QString type,QJSValue message); 24 | 25 | public slots: 26 | /// Dispatch a message via Dispatcher 27 | /** 28 | @param type The type of the message 29 | @param message The message content 30 | @reentrant 31 | 32 | Dispatch a message with type via the AppDispatcher. Listeners should listen on the "dispatched" 33 | signal to be notified. 34 | 35 | Usually, it will emit "dispatched" signal immediately after calling dispatch(). However, if 36 | AppDispatcher is still dispatching messages, the new messages will be placed on a queue, and 37 | wait until it is finished. It guarantees the order of messages are arrived in sequence to 38 | listeners 39 | */ 40 | Q_INVOKABLE void dispatch(QString type,QJSValue message = QJSValue()); 41 | 42 | Q_INVOKABLE void waitFor(QList ids); 43 | 44 | Q_INVOKABLE int addListener(QJSValue callback); 45 | 46 | Q_INVOKABLE void removeListener(int id); 47 | 48 | public: 49 | 50 | void dispatch(const QString& type, const QVariant& message); 51 | 52 | int addListener(QFListener* listener); 53 | 54 | QQmlEngine *engine() const; 55 | 56 | void setEngine(QQmlEngine *engine); 57 | 58 | QFHook *hook() const; 59 | 60 | void setHook(QFHook *hook); 61 | 62 | private slots: 63 | /// Invoke listener and emit the dispatched signal 64 | void send(QString type,QJSValue message); 65 | 66 | private: 67 | void invokeListeners(QList ids); 68 | 69 | bool m_dispatching; 70 | 71 | QPointer m_engine; 72 | 73 | // Queue for dispatching messages 74 | QQueue > m_queue; 75 | 76 | // Next id for listener. 77 | int nextListenerId; 78 | 79 | // Registered listener 80 | QMap > m_listeners; 81 | 82 | // Current dispatching listener id 83 | int dispatchingListenerId; 84 | 85 | // Current dispatching message 86 | QJSValue dispatchingMessage; 87 | 88 | // Current dispatching message type 89 | QString dispatchingMessageType; 90 | 91 | // List of listeners pending to be invoked. 92 | QMap pendingListeners; 93 | 94 | // List of listeners blocked in waitFor() 95 | QMap waitingListeners; 96 | 97 | QPointer m_hook; 98 | }; 99 | 100 | -------------------------------------------------------------------------------- /src/qffilter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "priv/quickfluxfunctions.h" 5 | #include "qffilter.h" 6 | 7 | /*! 8 | \qmltype Filter 9 | \brief Add filter rule to AppListener 10 | 11 | Filter component listens for the parent's dispatched signal, if a dispatched signal 12 | match with its type, it will emit its own "dispatched" signal. Otherwise, it will 13 | simply ignore the signal. 14 | 15 | This component provides an alternative way to filter incoming message which is 16 | suitable for making Store component. 17 | 18 | Example: 19 | 20 | \code 21 | 22 | pragma Singleton 23 | import QtQuick 2.0 24 | import QuickFlux 1.0 25 | import "../actions" 26 | 27 | AppListener { 28 | id: store 29 | 30 | property ListModel model: ListModel { } 31 | 32 | Filter { 33 | type: ActionTypes.addTask 34 | onDispatched: { 35 | model.append({task: message.task}); 36 | } 37 | } 38 | 39 | } 40 | 41 | \endcode 42 | 43 | It is not suggested to use nested AppListener in a Store component. Because nested AppListener 44 | do not share the same AppListener::listenerId, it will be difficult to control the order 45 | of message reception between store component. 46 | 47 | In contrast, Filter share the same listenerId with its parent, and therefore it is a solution 48 | for above problem. 49 | 50 | */ 51 | 52 | /*! 53 | \qmlsignal Filter::dispatched(string type, object message) 54 | 55 | It is a proxy of parent's dispatched signal. If the parent emits a signal matched with the Filter::type / Filter::types property, 56 | it will emit this signal 57 | */ 58 | 59 | 60 | QFFilter::QFFilter(QObject *parent) : QObject(parent) 61 | { 62 | 63 | } 64 | 65 | /*! \qmlproperty string Filter::type 66 | 67 | These types determine the filtering rule for incoming message. Only type matched will emit the "dispatched" signal. 68 | 69 | \code 70 | AppListener { 71 | Filter { 72 | type: "action1" 73 | onDispatched: { 74 | // handle the action 75 | } 76 | } 77 | } 78 | \endcode 79 | 80 | \sa Filter::types 81 | */ 82 | 83 | 84 | QString QFFilter::type() const 85 | { 86 | if (m_types.size() == 0) { 87 | return ""; 88 | } else { 89 | return m_types[0]; 90 | } 91 | } 92 | 93 | void QFFilter::setType(const QString &type) 94 | { 95 | m_types = QStringList() << type; 96 | emit typeChanged(); 97 | emit typesChanged(); 98 | } 99 | 100 | void QFFilter::classBegin() 101 | { 102 | 103 | } 104 | 105 | void QFFilter::componentComplete() 106 | { 107 | QObject* object = parent(); 108 | m_engine = qmlEngine(this); 109 | 110 | if (!object) { 111 | qDebug() << "Filter - Disabled due to missing parent."; 112 | return; 113 | } 114 | 115 | const QMetaObject* meta = object->metaObject(); 116 | 117 | if (meta->indexOfSignal("dispatched(QString,QJSValue)") >= 0) { 118 | connect(object,SIGNAL(dispatched(QString,QJSValue)), 119 | this,SLOT(filter(QString,QJSValue))); 120 | } else if (meta->indexOfSignal("dispatched(QString,QVariant)") >= 0) { 121 | connect(object,SIGNAL(dispatched(QString,QVariant)), 122 | this,SLOT(filter(QString,QVariant))); 123 | } else { 124 | qDebug() << "Filter - Disabled due to missing dispatched signal in parent object."; 125 | return; 126 | } 127 | 128 | } 129 | 130 | void QFFilter::filter(QString type, QJSValue message) 131 | { 132 | if (m_types.indexOf(type) >= 0) { 133 | QF_PRECHECK_DISPATCH(m_engine.data(), type, message); 134 | emit dispatched(type, message); 135 | } 136 | } 137 | 138 | void QFFilter::filter(QString type, QVariant message) 139 | { 140 | if (m_types.indexOf(type) >= 0) { 141 | QJSValue value = message.value(); 142 | QF_PRECHECK_DISPATCH(m_engine.data(), type, value); 143 | 144 | emit dispatched(type, value); 145 | } 146 | } 147 | 148 | /*! \qmlproperty array Filter::types 149 | 150 | These types determine the filtering rule for incoming message. Only type matched will emit the "dispatched" signal. 151 | 152 | \code 153 | AppListener { 154 | Filter { 155 | types: ["action1", "action2"] 156 | onDispatched: { 157 | // handle the action 158 | } 159 | } 160 | } 161 | \endcode 162 | 163 | \sa Filter::type 164 | 165 | */ 166 | 167 | 168 | QStringList QFFilter::types() const 169 | { 170 | return m_types; 171 | } 172 | 173 | void QFFilter::setTypes(const QStringList &types) 174 | { 175 | m_types = types; 176 | } 177 | 178 | QQmlListProperty QFFilter::children() 179 | { 180 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) 181 | return QQmlListProperty(qobject_cast(this), 182 | &m_children); 183 | #else 184 | return QQmlListProperty(qobject_cast(this), 185 | m_children); 186 | #endif 187 | } 188 | -------------------------------------------------------------------------------- /src/qffilter.h: -------------------------------------------------------------------------------- 1 | #ifndef QFFILTER_H 2 | #define QFFILTER_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | // Filter represents a filter rule in AppListener 13 | 14 | class QFFilter : public QObject, public QQmlParserStatus 15 | { 16 | Q_OBJECT 17 | Q_PROPERTY(QString type READ type WRITE setType NOTIFY typeChanged) 18 | Q_PROPERTY(QStringList types READ types WRITE setTypes NOTIFY typesChanged) 19 | Q_INTERFACES(QQmlParserStatus) 20 | Q_PROPERTY(QQmlListProperty __children READ children) 21 | Q_CLASSINFO("DefaultProperty", "__children") 22 | 23 | public: 24 | explicit QFFilter(QObject *parent = 0); 25 | 26 | QString type() const; 27 | 28 | void setType(const QString &type); 29 | 30 | QStringList types() const; 31 | 32 | void setTypes(const QStringList &types); 33 | 34 | QQmlListProperty children(); 35 | 36 | signals: 37 | void dispatched(QString type, QJSValue message); 38 | 39 | void typeChanged(); 40 | 41 | void typesChanged(); 42 | 43 | protected: 44 | void classBegin(); 45 | void componentComplete(); 46 | 47 | private slots: 48 | void filter(QString type, QJSValue message); 49 | void filter(QString type, QVariant message); 50 | 51 | private: 52 | QStringList m_types; 53 | QList m_children; 54 | QPointer m_engine; 55 | }; 56 | 57 | #endif // QFFILTER_H 58 | -------------------------------------------------------------------------------- /src/qfhydrate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "qfhydrate.h" 4 | #include "qfobject.h" 5 | #include "qfstore.h" 6 | #include 7 | 8 | static QVariantMap dehydrator(QObject* source); 9 | 10 | static auto dehydratorFunction = [](const QStringList& ignoreList) -> std::function { 11 | 12 | return [=](QObject* source) { 13 | QVariantMap dest; 14 | const QMetaObject* meta = source->metaObject(); 15 | 16 | for (int i = 0 ; i < meta->propertyCount(); i++) { 17 | const QMetaProperty property = meta->property(i); 18 | const char* name = property.name(); 19 | QString stringName = name; 20 | 21 | if (ignoreList.indexOf(stringName) >= 0) { 22 | continue; 23 | } 24 | 25 | QVariant value = source->property(name); 26 | 27 | if (value.canConvert()) { 28 | QObject* object = value.value(); 29 | if (!object) { 30 | continue; 31 | } 32 | value = dehydrator(object); 33 | } 34 | dest[stringName] = value; 35 | } 36 | return dest; 37 | }; 38 | }; 39 | 40 | static auto dehydrateQObject = dehydratorFunction(QStringList() << "parent" << "objectName"); 41 | static auto dehydrateQFObject = dehydratorFunction(QStringList() << "parent" << "objectName" << "children"); 42 | static auto dehydrateQFStore = dehydratorFunction(QStringList() << "parent" << "objectName" << "children" << "bindSource" << "redispatchTargets" << "filterFunctionEnabled"); 43 | 44 | /// Default dehydrator function 45 | static QVariantMap dehydrator(QObject* source) { 46 | QVariantMap ret; 47 | if (qobject_cast(source)) { 48 | ret = dehydrateQFStore(source); 49 | } else if (qobject_cast(source)) { 50 | ret = dehydrateQFObject(source); 51 | } else { 52 | ret = dehydrateQObject(source); 53 | } 54 | return ret; 55 | } 56 | 57 | /*! 58 | \qmltype Hydrate 59 | \inqmlmodule QuickFlux 60 | 61 | \code 62 | import QuickFlux 1.1 63 | \endcode 64 | 65 | Hydrate provides an interface to rehydrate / hydrate a Store component. Rehydration and dehydration are just another words for deserialize and serialize. It could be used to convert Store into JSON object, and vice versa. 66 | 67 | Remarks: Hydrate supports any QObject based type as the target of deserialize and serialize. 68 | 69 | \code 70 | Hydrate.rehydrate(store, { 71 | value1: 1, 72 | value2: 2.0, 73 | value3: "", 74 | value4: { 75 | subValue1: 1 76 | } 77 | }); 78 | 79 | var data = Hydrate.dehydrate(MainStore); 80 | console.log(JSON.stringify(data)); 81 | \endcode 82 | 83 | It is added since Quick Flux 1.1 84 | 85 | */ 86 | 87 | QFHydrate::QFHydrate(QObject *parent) : QObject(parent) 88 | { 89 | 90 | } 91 | 92 | /*! 93 | \qmlmethod Hydrate::rehydrate(target, source) 94 | 95 | Deserialize data from source and write to target object. 96 | 97 | \code 98 | Hydrate.rehydrate(store, { 99 | value1: 1, 100 | value2: 2.0, 101 | value3: "", 102 | value4: { 103 | subValue1: 1 104 | } 105 | \endcode 106 | 107 | */ 108 | 109 | void QFHydrate::rehydrate(QObject *dest, const QVariantMap &source) 110 | { 111 | const QMetaObject* meta = dest->metaObject(); 112 | 113 | QMap::const_iterator iter = source.begin(); 114 | while (iter != source.end()) { 115 | QByteArray key = iter.key().toLocal8Bit(); 116 | 117 | int index = meta->indexOfProperty(key.constData()); 118 | if (index < 0) { 119 | qWarning() << QString("Hydrate.rehydrate: %1 property is not existed").arg(iter.key()); 120 | iter++; 121 | continue; 122 | } 123 | 124 | QVariant orig = dest->property(key.constData()); 125 | QVariant value = source[iter.key()]; 126 | 127 | if (orig.canConvert()) { 128 | if (value.type() != QVariant::Map) { 129 | qWarning() << QString("Hydrate.rehydrate: expect a QVariantMap property but it is not: %1"); 130 | } else { 131 | rehydrate(orig.value(), value.toMap()); 132 | } 133 | 134 | } else if (orig != value) { 135 | dest->setProperty(key.constData(), value); 136 | } 137 | 138 | iter++; 139 | } 140 | } 141 | 142 | /*! 143 | \qmlmethod Hydrate::dehydrate(object) 144 | 145 | Serialize data from a object 146 | 147 | \code 148 | var data = Hydrate.dehydrate(MainStore); 149 | console.log(JSON.stringify(data)); 150 | \endcode 151 | 152 | */ 153 | 154 | QVariantMap QFHydrate::dehydrate(QObject *source) 155 | { 156 | return dehydrator(source); 157 | } 158 | -------------------------------------------------------------------------------- /src/qfhydrate.h: -------------------------------------------------------------------------------- 1 | #ifndef QFHYDRATE_H 2 | #define QFHYDRATE_H 3 | 4 | #include 5 | #include 6 | 7 | class QFHydrate : public QObject 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit QFHydrate(QObject *parent = 0); 12 | 13 | signals: 14 | 15 | public slots: 16 | void rehydrate(QObject *dest, const QVariantMap & source); 17 | 18 | QVariantMap dehydrate(QObject *source); 19 | }; 20 | 21 | #endif // QFHYDRATE_H 22 | -------------------------------------------------------------------------------- /src/qfkeytable.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "qfkeytable.h" 4 | 5 | /*! 6 | \qmltype KeyTable 7 | \brief Defines a key table 8 | 9 | KeyTable is an object with properties equal to its key name. Once it's construction is completed, 10 | it will search all of its string property. If it is a string type and not assigned to any value, 11 | it will set its value by its name. It can be used to create ActionTypes.qml in QuickFlux Application. 12 | 13 | Example 14 | 15 | \code 16 | 17 | KeyTable { 18 | 19 | // It will be set to "customField1" in Component.onCompleted callback. 20 | property string customField1; 21 | 22 | // Since it is already assigned a value, KeyTable will not modify this property. 23 | property string customField2 : "value"; 24 | 25 | } 26 | 27 | \endcode 28 | 29 | 30 | */ 31 | 32 | static QMap createTypes() { 33 | QMap types; 34 | types[QVariant::String] = "QString"; 35 | types[QVariant::Int] = "int"; 36 | types[QVariant::Double] = "qreal"; 37 | types[QVariant::Bool] = "bool"; 38 | types[QVariant::PointF] = "QPointF"; 39 | types[QVariant::RectF] = "QRectF"; 40 | 41 | return types; 42 | } 43 | 44 | QFKeyTable::QFKeyTable(QObject *parent) : QObject(parent) 45 | { 46 | 47 | } 48 | 49 | QString QFKeyTable::genHeaderFile(const QString& className) 50 | { 51 | 52 | QStringList header; 53 | QStringList clazz; 54 | bool includedPointHeader = false; 55 | bool includedRectHeader = false; 56 | 57 | header << "#pragma once"; 58 | header << "#include \n"; 59 | 60 | clazz << QString("class %1 {\n").arg(className); 61 | clazz << "public:\n"; 62 | 63 | const QMetaObject* meta = metaObject(); 64 | 65 | QMap types = createTypes(); 66 | 67 | int count = meta->propertyCount(); 68 | for (int i = 0 ; i < count ; i++) { 69 | const QMetaProperty p = meta->property(i); 70 | 71 | QString name(p.name()); 72 | 73 | if (name == "objectName") { 74 | continue; 75 | } 76 | 77 | if (types.contains(p.type())) { 78 | clazz << QString(" static %2 %1;\n").arg(name).arg(types[p.type()]); 79 | 80 | if (p.type() == QVariant::PointF && !includedPointHeader) { 81 | includedPointHeader = true; 82 | header << "#include "; 83 | } else if (p.type() == QVariant::RectF && !includedRectHeader) { 84 | includedRectHeader = true; 85 | header << "#include "; 86 | } 87 | } 88 | 89 | } 90 | 91 | clazz << "};\n"; 92 | 93 | QStringList content; 94 | content << header << clazz; 95 | 96 | return content.join("\n"); 97 | } 98 | 99 | QString QFKeyTable::genSourceFile(const QString &className, const QString &headerFile) 100 | { 101 | QMap types = createTypes(); 102 | 103 | QStringList source; 104 | 105 | source << QString("#include \"%1\"\n").arg(headerFile); 106 | 107 | const QMetaObject* meta = metaObject(); 108 | 109 | int count = meta->propertyCount(); 110 | for (int i = 0 ; i < count ; i++) { 111 | const QMetaProperty p = meta->property(i); 112 | QString name(p.name()); 113 | if (name == "objectName") { 114 | continue; 115 | } 116 | 117 | if (types.contains(p.type())) { 118 | QVariant v = property(p.name()); 119 | 120 | if (p.type() == QVariant::String) { 121 | source << QString("%4 %1::%2 = \"%3\";\n") 122 | .arg(className) 123 | .arg(p.name()) 124 | .arg(v.toString()) 125 | .arg(types[p.type()]); 126 | 127 | } else if (p.type() == QVariant::PointF) { 128 | QPointF pt = v.toPointF(); 129 | 130 | source << QString("QPointF %1::%2 = QPointF(%3,%4);\n") 131 | .arg(className) 132 | .arg(p.name()) 133 | .arg(pt.x()) 134 | .arg(pt.y()); 135 | 136 | } else if (p.type() == QVariant::RectF) { 137 | 138 | QRectF rect = v.toRectF(); 139 | 140 | source << QString("QRectF %1::%2 = QRect(%3,%4,%5,%6);\n") 141 | .arg(className) 142 | .arg(p.name()) 143 | .arg(rect.x()) 144 | .arg(rect.y()) 145 | .arg(rect.width()) 146 | .arg(rect.height()); 147 | 148 | } else { 149 | 150 | source << QString("%4 %1::%2 = %3;\n") 151 | .arg(className) 152 | .arg(p.name()) 153 | .arg(v.toString()) 154 | .arg(types[p.type()]); 155 | } 156 | } 157 | } 158 | 159 | return source.join("\n"); 160 | } 161 | 162 | void QFKeyTable::classBegin() 163 | { 164 | 165 | } 166 | 167 | void QFKeyTable::componentComplete() 168 | { 169 | const QMetaObject* meta = metaObject(); 170 | 171 | int count = meta->propertyCount(); 172 | for (int i = 0 ; i < count ; i++) { 173 | const QMetaProperty p = meta->property(i); 174 | QString name(p.name()); 175 | if (p.type() != QVariant::String || 176 | name == "objectName") { 177 | continue; 178 | } 179 | 180 | QVariant v = property(p.name()); 181 | if (!v.isNull()) { 182 | continue; 183 | } 184 | 185 | setProperty(p.name(), name); 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /src/qfkeytable.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class QFKeyTable : public QObject, public QQmlParserStatus 7 | { 8 | Q_OBJECT 9 | Q_INTERFACES(QQmlParserStatus) 10 | 11 | public: 12 | explicit QFKeyTable(QObject *parent = 0); 13 | 14 | public slots: 15 | QString genHeaderFile(const QString& className); 16 | QString genSourceFile(const QString& className, const QString& headerFile); 17 | 18 | protected: 19 | void classBegin(); 20 | void componentComplete(); 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /src/qflistener.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "qfdispatcher.h" 3 | #include "priv/qflistener.h" 4 | 5 | QFListener::QFListener(QObject *parent) : QObject(parent) 6 | { 7 | m_listenerId = 0; 8 | } 9 | 10 | QFListener::~QFListener() 11 | { 12 | } 13 | 14 | QJSValue QFListener::callback() const 15 | { 16 | return m_callback; 17 | } 18 | 19 | void QFListener::setCallback(const QJSValue &callback) 20 | { 21 | m_callback = callback; 22 | } 23 | 24 | void QFListener::dispatch(QFDispatcher *dispatcher,QString type, QJSValue message) 25 | { 26 | 27 | if (m_waitFor.size() > 0) { 28 | dispatcher->waitFor(m_waitFor); 29 | } 30 | 31 | if (m_callback.isCallable()) { 32 | QJSValueList args; 33 | args << type << message; 34 | QJSValue ret = m_callback.call(args); 35 | 36 | if (ret.isError()) { 37 | QString message = QString("%1:%2: %3: %4") 38 | .arg(ret.property("fileName").toString()) 39 | .arg(ret.property("lineNumber").toString()) 40 | .arg(ret.property("name").toString()) 41 | .arg(ret.property("message").toString()); 42 | qWarning() << message; 43 | } 44 | } 45 | 46 | emit dispatched(type,message); 47 | } 48 | 49 | int QFListener::listenerId() const 50 | { 51 | return m_listenerId; 52 | } 53 | 54 | void QFListener::setListenerId(int listenerId) 55 | { 56 | m_listenerId = listenerId; 57 | } 58 | 59 | QList QFListener::waitFor() const 60 | { 61 | return m_waitFor; 62 | } 63 | 64 | void QFListener::setWaitFor(const QList &waitFor) 65 | { 66 | m_waitFor = waitFor; 67 | } 68 | 69 | -------------------------------------------------------------------------------- /src/qfmiddleware.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "qfmiddleware.h" 3 | #include "priv/quickfluxfunctions.h" 4 | 5 | 6 | /*! 7 | \qmltype Middleware 8 | \inqmlmodule QuickFlux 9 | 10 | \code 11 | import QuickFlux 1.1 12 | \endcode 13 | 14 | The middleware in Quick Flux is similar to the one in Redux and those from server libraries like Express and Koa. It is some code you can put between the Dispatcher and Stores. It could modify/defer/remove received actions and insert new actions that will dispatch to Store components. Users may use it for logging, crash reporting, talking to asynchronous components like FileDialog / Camera etc. So that Store components remain “pure”, it holds application logic only and always return the same result for the same input. It is easier to write test cases. 15 | 16 | 17 | Event Flow 18 | 19 | \image middleware-data-flow.png "Concept" 20 | 21 | \e{Example Code} 22 | 23 | \code 24 | // Action Logger 25 | import QuickFlux 1.1 26 | 27 | Middleware { 28 | function dispatch(type, message) { 29 | console.log(type, JSON.string(message)); 30 | next(type, message); // propagate the action to next Middleware or Store component type. If you don’t call it, the action will be dropped. 31 | } 32 | } 33 | \endcode 34 | 35 | Whatever the middleware received a new action, it will invoke the "dispatch" function written by user. 36 | If the middleare accept the action, it should call the next() to propagate the action to anothter middleware. 37 | User may modify/insert/delay or remove the action. 38 | 39 | \code 40 | // Confirmation Dialog 41 | 42 | import QuickFlux 1.1 43 | import QtQuick.Dialogs 1.2 44 | 45 | Middleware { 46 | 47 | FileDialog { 48 | id: fileDialog 49 | onAccepted: { 50 | next(ActionTypes.removeItem); 51 | } 52 | } 53 | 54 | function dispatch(type, message) { 55 | 56 | if (type === ActionTypes.removeItem) { 57 | fileDialog.open(); 58 | } else { 59 | next(type, message); 60 | } 61 | } 62 | } 63 | \endcode 64 | */ 65 | 66 | QFMiddleware::QFMiddleware(QQuickItem* parent) : QQuickItem(parent), m_filterFunctionEnabled(false) 67 | { 68 | 69 | } 70 | 71 | /*! \qmlmethod Middleware::next(string type, object message) 72 | 73 | Pass an action message to next middleware. If it is already the last middleware, the action will be dispatched to Store component. 74 | */ 75 | 76 | /*! \qmlproperty bool Middleware::filterFunctionEnabled 77 | 78 | If this property is true, whatever the middleware component received a new action. Beside to invoke a dispatch signal, it will search for a function with a name as the action. If it exists, it will call also call the function. 79 | 80 | \code 81 | 82 | Middleware { 83 | filterFunctionEnabled: true 84 | 85 | function addItem(message) { 86 | //process 87 | next("addItem", message); 88 | } 89 | } 90 | \endcode 91 | 92 | The default value is false 93 | */ 94 | void QFMiddleware::next(QString type, QJSValue message) 95 | { 96 | QQmlEngine* engine = qmlEngine(this); 97 | QF_PRECHECK_DISPATCH(engine, type, message); 98 | 99 | if (m_nextCallback.isCallable()) { 100 | QJSValueList args; 101 | args << type; 102 | args << message; 103 | QJSValue result = m_nextCallback.call(args); 104 | if (result.isError()) { 105 | QuickFlux::printException(result); 106 | } 107 | } 108 | } 109 | 110 | QJSValue QFMiddleware::nextCallback() const 111 | { 112 | return m_nextCallback; 113 | } 114 | 115 | void QFMiddleware::setNextCallback(const QJSValue &value) 116 | { 117 | m_nextCallback = value; 118 | emit _nextCallbackChanged(); 119 | } 120 | -------------------------------------------------------------------------------- /src/qfmiddleware.h: -------------------------------------------------------------------------------- 1 | #ifndef QFMIDDLEWARE_H 2 | #define QFMIDDLEWARE_H 3 | 4 | #include 5 | #include 6 | 7 | class QFMiddleware : public QQuickItem 8 | { 9 | Q_OBJECT 10 | Q_PROPERTY(bool filterFunctionEnabled MEMBER m_filterFunctionEnabled NOTIFY filterFunctionEnabledChanged) 11 | Q_PROPERTY(QJSValue _nextCallback READ nextCallback WRITE setNextCallback NOTIFY _nextCallbackChanged) 12 | 13 | public: 14 | QFMiddleware(QQuickItem* parent = nullptr); 15 | 16 | QJSValue nextCallback() const; 17 | void setNextCallback(const QJSValue &nextCallback); 18 | 19 | signals: 20 | void dispatched(QString type, QJSValue message); 21 | void filterFunctionEnabledChanged(); 22 | void _nextCallbackChanged(); 23 | 24 | public slots: 25 | void next(QString type, QJSValue message = QJSValue()); 26 | 27 | private: 28 | bool m_filterFunctionEnabled; 29 | 30 | QJSValue m_nextCallback; 31 | 32 | }; 33 | 34 | #endif // QFMIDDLEWARE_H 35 | -------------------------------------------------------------------------------- /src/qfmiddlewarelist.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "qfmiddlewarelist.h" 5 | #include "priv/quickfluxfunctions.h" 6 | #include "priv/qfmiddlewareshook.h" 7 | 8 | /*! 9 | \qmltype MiddlewareList 10 | \inqmlmodule QuickFlux 11 | 12 | \code 13 | import QuickFlux 1.1 14 | \endcode 15 | 16 | MiddlewareList groups a list of Middleware and install to target Dispatcher / ActionCreator 17 | 18 | Example Code 19 | 20 | \code 21 | 22 | import QtQuick 2.0 23 | import QuickFlux 1.1 24 | import "./actions" 25 | import "./middlewares" 26 | 27 | // main.qml 28 | 29 | Item { 30 | MiddlewareList { 31 | applyTarget: AppDispatcher 32 | 33 | Middleware { 34 | id: debouncerMiddleware 35 | } 36 | 37 | Middleware { 38 | id: logger 39 | } 40 | } 41 | } 42 | 43 | \endcode 44 | 45 | It is added since QuickFlux 1.1 46 | 47 | */ 48 | 49 | /*! \qmlproperty var MiddlewareList::applyTarget 50 | * 51 | * This property specific the target object that of middlewares should be applied. 52 | * It can be either of an ActionCreator or Dispatcher object. 53 | * 54 | */ 55 | 56 | QFMiddlewareList::QFMiddlewareList(QQuickItem* parent) : QQuickItem(parent) 57 | { 58 | m_engine = nullptr; 59 | } 60 | 61 | void QFMiddlewareList::apply(QObject *source) 62 | { 63 | setApplyTarget(source); 64 | } 65 | 66 | void QFMiddlewareList::next(int senderIndex, QString type, QJSValue message) 67 | { 68 | QJSValueList args; 69 | 70 | args << QJSValue(senderIndex + 1); 71 | args << QJSValue(type); 72 | args << message; 73 | QJSValue result = invoke.call(args); 74 | if (result.isError()) { 75 | QuickFlux::printException(result); 76 | } 77 | } 78 | 79 | void QFMiddlewareList::classBegin() 80 | { 81 | 82 | } 83 | 84 | void QFMiddlewareList::componentComplete() 85 | { 86 | m_engine = qmlEngine(this); 87 | 88 | if (!m_applyTarget.isNull()) { 89 | setup(); 90 | } 91 | } 92 | 93 | void QFMiddlewareList::setup() 94 | { 95 | QFActionCreator *creator = nullptr; 96 | QFDispatcher* dispatcher = nullptr; 97 | 98 | creator = qobject_cast(m_applyTarget.data()); 99 | 100 | if (creator) { 101 | dispatcher = creator->dispatcher(); 102 | } else { 103 | dispatcher = qobject_cast(m_applyTarget.data()); 104 | } 105 | 106 | if (creator == nullptr && dispatcher == nullptr) { 107 | qWarning() << "Middlewares.apply(): Invalid input"; 108 | } 109 | 110 | if (m_actionCreator.data() == creator && 111 | m_dispatcher.data() == dispatcher) { 112 | // Nothing changed. 113 | return; 114 | } 115 | 116 | if (!m_actionCreator.isNull() && 117 | m_actionCreator.data() != creator) { 118 | // in case the action creator is not changed, do nothing. 119 | m_actionCreator->disconnect(this); 120 | } 121 | 122 | if (!m_dispatcher.isNull() && 123 | m_dispatcher.data() != dispatcher) { 124 | QFHook* hook = m_dispatcher->hook(); 125 | m_dispatcher->setHook(nullptr); 126 | m_dispatcher->disconnect(this); 127 | if (hook) { 128 | delete hook; 129 | } 130 | } 131 | 132 | m_actionCreator = creator; 133 | m_dispatcher = dispatcher; 134 | 135 | if (!m_actionCreator.isNull()) { 136 | connect(m_actionCreator.data(),SIGNAL(dispatcherChanged()), 137 | this,SLOT(setup())); 138 | } 139 | 140 | if (!m_dispatcher.isNull()) { 141 | QFMiddlewaresHook* hook = new QFMiddlewaresHook(); 142 | hook->setParent(this); 143 | hook->setup(m_engine.data(), this); 144 | 145 | if (!m_dispatcher.isNull()) { 146 | m_dispatcher->setHook(hook); 147 | } 148 | } 149 | } 150 | 151 | QObject *QFMiddlewareList::applyTarget() const 152 | { 153 | return m_applyTarget; 154 | } 155 | 156 | void QFMiddlewareList::setApplyTarget(QObject *applyTarget) 157 | { 158 | m_applyTarget = applyTarget; 159 | if (!m_engine.isNull()) { 160 | setup(); 161 | } 162 | 163 | emit applyTargetChanged(); 164 | } 165 | 166 | -------------------------------------------------------------------------------- /src/qfmiddlewarelist.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | class QFMiddlewareList : public QQuickItem 7 | { 8 | Q_OBJECT 9 | Q_PROPERTY(QObject* applyTarget READ applyTarget WRITE setApplyTarget NOTIFY applyTargetChanged) 10 | public: 11 | QFMiddlewareList(QQuickItem* parent = nullptr); 12 | 13 | QObject *applyTarget() const; 14 | void setApplyTarget(QObject *applyTarget); 15 | 16 | signals: 17 | void applyTargetChanged(); 18 | 19 | public slots: 20 | 21 | void apply(QObject* target); 22 | 23 | void next(int senderId, QString type, QJSValue message); 24 | 25 | protected: 26 | void classBegin(); 27 | void componentComplete(); 28 | 29 | private slots: 30 | void setup(); 31 | 32 | private: 33 | 34 | QPointer m_engine; 35 | 36 | QPointer m_actionCreator; 37 | QPointer m_dispatcher; 38 | QJSValue invoke; 39 | 40 | QPointer m_applyTarget; 41 | 42 | }; 43 | -------------------------------------------------------------------------------- /src/qfobject.cpp: -------------------------------------------------------------------------------- 1 | #include "qfobject.h" 2 | 3 | QFObject::QFObject(QObject *parent) : QObject(parent) 4 | { 5 | 6 | } 7 | 8 | QQmlListProperty QFObject::children() 9 | { 10 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) 11 | return QQmlListProperty(qobject_cast(this), 12 | &m_children); 13 | #else 14 | return QQmlListProperty(qobject_cast(this), 15 | m_children); 16 | #endif 17 | } 18 | -------------------------------------------------------------------------------- /src/qfobject.h: -------------------------------------------------------------------------------- 1 | #ifndef QFOBJECT_H 2 | #define QFOBJECT_H 3 | 4 | #include 5 | #include 6 | 7 | /// QFObject provides an QtObject with default children property to hold nested items 8 | 9 | class QFObject : public QObject 10 | { 11 | Q_OBJECT 12 | Q_PROPERTY(QQmlListProperty children READ children) 13 | Q_CLASSINFO("DefaultProperty", "children") 14 | 15 | public: 16 | explicit QFObject(QObject *parent = 0); 17 | 18 | QQmlListProperty children(); 19 | 20 | signals: 21 | 22 | public slots: 23 | 24 | private: 25 | QObjectList m_children; 26 | }; 27 | 28 | #endif // QFOBJECT_H 29 | -------------------------------------------------------------------------------- /src/qfqmltypes.cpp: -------------------------------------------------------------------------------- 1 | /// QML Type Registration Helper 2 | #include 3 | #include 4 | #include "qfmiddleware.h" 5 | #include "qfdispatcher.h" 6 | #include "qfapplistener.h" 7 | #include "qfappscript.h" 8 | #include "qfapplistenergroup.h" 9 | #include "qfappscriptgroup.h" 10 | #include "priv/qfappscriptrunnable.h" 11 | #include "qffilter.h" 12 | #include "qfkeytable.h" 13 | #include "qfactioncreator.h" 14 | #include "qfobject.h" 15 | #include "qfmiddlewarelist.h" 16 | #include "qfstore.h" 17 | #include "qfhydrate.h" 18 | 19 | static QObject *appDispatcherProvider(QQmlEngine *engine, QJSEngine *scriptEngine) 20 | { 21 | Q_UNUSED(engine); 22 | Q_UNUSED(scriptEngine); 23 | 24 | QFAppDispatcher* object = new QFAppDispatcher(); 25 | object->setEngine(engine); 26 | 27 | return object; 28 | } 29 | 30 | static QObject* hydrateProvider(QQmlEngine *engine, QJSEngine *scriptEngine) { 31 | Q_UNUSED(engine); 32 | Q_UNUSED(scriptEngine); 33 | 34 | QFHydrate* object = new QFHydrate(); 35 | return object; 36 | } 37 | 38 | 39 | void registerQuickFluxQmlTypes() 40 | { 41 | static bool registered = false; 42 | if (registered) { 43 | return; 44 | } 45 | 46 | registered = true; 47 | 48 | qmlRegisterSingletonType("QuickFlux", 1, 0, "AppDispatcher", appDispatcherProvider); 49 | qmlRegisterType("QuickFlux", 1, 0, "AppListener"); 50 | qmlRegisterType("QuickFlux", 1, 0, "AppScript"); 51 | qmlRegisterType("QuickFlux", 1, 0, "AppListenerGroup"); 52 | qmlRegisterType("QuickFlux", 1, 0, "AppScriptGroup"); 53 | 54 | #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0)) 55 | qmlRegisterAnonymousType("QuickFlux", 1); 56 | #else 57 | qmlRegisterType(); 58 | #endif 59 | 60 | qmlRegisterType("QuickFlux", 1, 0, "KeyTable"); 61 | qmlRegisterType("QuickFlux", 1, 0, "ActionCreator"); 62 | qmlRegisterType("QuickFlux", 1, 0, "Filter"); 63 | 64 | /* 1.1 */ 65 | qmlRegisterType("QuickFlux", 1, 1, "ActionCreator"); 66 | qmlRegisterType("QuickFlux", 1, 1, "AppListener"); 67 | qmlRegisterType("QuickFlux", 1, 1, "AppScript"); 68 | qmlRegisterType("QuickFlux", 1, 1, "AppListenerGroup"); 69 | qmlRegisterType("QuickFlux", 1, 1, "AppScriptGroup"); 70 | qmlRegisterType("QuickFlux", 1, 1, "Filter"); 71 | qmlRegisterType("QuickFlux", 1, 1, "KeyTable"); 72 | qmlRegisterType("QuickFlux", 1, 1, "ActionCreator"); 73 | 74 | /* New components in 1.1 */ 75 | qmlRegisterSingletonType("QuickFlux", 1, 1, "Hydrate", hydrateProvider); 76 | qmlRegisterType("QuickFlux", 1, 1, "Dispatcher"); 77 | qmlRegisterType("QuickFlux", 1, 1, "Store"); 78 | qmlRegisterType("QuickFlux", 1, 1, "MiddlewareList"); 79 | qmlRegisterType("QuickFlux", 1, 1, "Middleware"); 80 | // qmlRegisterType("QuickFlux", 1, 1, "Object"); 81 | } 82 | 83 | // Allow to disable QML types auto registration as required by #9 84 | #ifndef QUICK_FLUX_DISABLE_AUTO_QML_REGISTER 85 | Q_COREAPP_STARTUP_FUNCTION(registerQuickFluxQmlTypes) 86 | #endif 87 | -------------------------------------------------------------------------------- /src/qfstore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "priv/quickfluxfunctions.h" 5 | #include "qfactioncreator.h" 6 | #include "qfstore.h" 7 | 8 | /*! 9 | \qmltype Store 10 | \inqmlmodule QuickFlux 1.1 11 | \brief Store Component 12 | 13 | Store is a helper item for implement the data “Store” component in a Quick Flux application. It could listen from ActionCreator / Dispatcher component and redispatch the received action to another store components (e.g children store). 14 | 15 | It is a replacement of AppListener component 16 | 17 | Example: 18 | 19 | \code 20 | 21 | import QuickFlux 1.1 22 | 23 | Store { 24 | bindSource: AppDispatcher 25 | 26 | Filter { 27 | type: ActionTypes.addItem 28 | onDispatched: { 29 | /// Process 30 | } 31 | } 32 | 33 | } 34 | 35 | \endcode 36 | 37 | \b{The order of action delivery:} 38 | 39 | \code 40 | 41 | Store { 42 | id: rootStore 43 | 44 | bindSource: AppDispatcher 45 | 46 | property alias page1 : page1 47 | 48 | Store { 49 | id: page1 50 | } 51 | 52 | Store { 53 | id: page2 54 | } 55 | 56 | Filter { 57 | id: filter1 58 | } 59 | 60 | } 61 | \endcode 62 | 63 | In the example above, the rootStore is bind to AppDispatcher, whatever the dispatcher dispatch an action, it will first re-dispatch the action to its children sequentially. Then emit the dispatched signal on itself. Therefore, the order of receivers is: page1, page2 then filter1. 64 | 65 | If the redispatchTargets property is set, Store component will also dispatch the received action to the listed objects. 66 | 67 | */ 68 | 69 | /*! 70 | \qmlsignal Store::dispatched(string type, object message) 71 | 72 | This signal is emitted when a message is received by this store. 73 | 74 | There has two suggested methods to listen this signal: 75 | 76 | Method 1 - Use Filter component 77 | 78 | \code 79 | 80 | Store { 81 | Filter { 82 | type: ActionTypes.addItem 83 | onDispatched: { 84 | // process here 85 | } 86 | } 87 | } 88 | \endcode 89 | 90 | Method 2 - Use filter function 91 | 92 | \code 93 | Store { 94 | filterFunctionEnabled: true 95 | 96 | function addItem(message) { 97 | 98 | } 99 | } 100 | \endcode 101 | 102 | */ 103 | 104 | /*! \qmlproperty bool Store::filterFunctionEnabled 105 | If this property is true, whatever the store component received a new action. Beside to emit a dispatched signal, it will search for a function with a name as the action. If it exists, it will call also call the function. 106 | 107 | \code 108 | 109 | Store { 110 | filterFunctionEnabled: true 111 | 112 | function addItem(message) { 113 | } 114 | } 115 | \endcode 116 | 117 | The default value is false 118 | */ 119 | 120 | 121 | QFStore::QFStore(QObject *parent) : QObject(parent) , m_filterFunctionEnabled(false) 122 | { 123 | 124 | } 125 | 126 | QQmlListProperty QFStore::children() 127 | { 128 | return QQmlListProperty(qobject_cast(this), m_children); 129 | } 130 | 131 | void QFStore::dispatch(QString type, QJSValue message) 132 | { 133 | QQmlEngine* engine = qmlEngine(this); 134 | QF_PRECHECK_DISPATCH(engine, type, message); 135 | 136 | foreach(QObject* child , m_children) { 137 | QFStore* store = qobject_cast(child); 138 | if (!store) { 139 | continue; 140 | } 141 | store->dispatch(type, message); 142 | } 143 | 144 | foreach(QObject* child , m_redispatchTargets) { 145 | QFStore* store = qobject_cast(child); 146 | 147 | if (!store) { 148 | continue; 149 | } 150 | store->dispatch(type, message); 151 | } 152 | 153 | if (m_filterFunctionEnabled) { 154 | const QMetaObject *meta = metaObject(); 155 | QByteArray signature; 156 | int index; 157 | 158 | signature = QMetaObject::normalizedSignature(QString("%1(QVariant)").arg(type).toUtf8().constData()); 159 | if ( (index = meta->indexOfMethod(signature.constData())) >= 0 ) { 160 | QMetaMethod method = meta->method(index); 161 | QVariant value = QVariant::fromValue(message); 162 | 163 | method.invoke(this,Qt::DirectConnection, Q_ARG(QVariant, value)); 164 | } 165 | 166 | signature = QMetaObject::normalizedSignature(QString("%1()").arg(type).toUtf8().constData()); 167 | if ( (index = meta->indexOfMethod(signature.constData())) >= 0 ) { 168 | QMetaMethod method = meta->method(index); 169 | 170 | method.invoke(this); 171 | } 172 | } 173 | 174 | emit dispatched(type, message); 175 | } 176 | 177 | void QFStore::bind(QObject *source) 178 | { 179 | setBindSource(source); 180 | } 181 | 182 | void QFStore::setup() 183 | { 184 | QFActionCreator *creator = 0; 185 | QFDispatcher* dispatcher = 0; 186 | 187 | creator = qobject_cast(m_bindSource.data()); 188 | 189 | if (creator) { 190 | dispatcher = creator->dispatcher(); 191 | } else { 192 | dispatcher = qobject_cast(m_bindSource.data()); 193 | } 194 | 195 | if (m_actionCreator.data() == creator && 196 | m_dispatcher.data() == dispatcher) { 197 | // Nothing changed. 198 | return; 199 | } 200 | 201 | if (!m_actionCreator.isNull() && 202 | m_actionCreator.data() != creator) { 203 | m_actionCreator->disconnect(this); 204 | } 205 | 206 | if (!m_dispatcher.isNull() && 207 | m_dispatcher.data() != dispatcher) { 208 | m_dispatcher->disconnect(this); 209 | } 210 | 211 | m_actionCreator = creator; 212 | m_dispatcher = dispatcher; 213 | 214 | if (!m_actionCreator.isNull()) { 215 | connect(m_actionCreator.data(),SIGNAL(dispatcherChanged()), 216 | this,SLOT(setup())); 217 | } 218 | 219 | if (!m_dispatcher.isNull()) { 220 | connect(dispatcher,SIGNAL(dispatched(QString,QJSValue)), 221 | this,SLOT(dispatch(QString,QJSValue))); 222 | } 223 | 224 | } 225 | 226 | /*! \qmlproperty array Store::redispatchTargets 227 | 228 | By default, the Store component redispatch the received action to its children sequentially. If this property is set, 229 | the action will be re-dispatch to the target objects too. 230 | 231 | \code 232 | 233 | Store { 234 | id: bridgeStore 235 | 236 | redispatchTargets: [ 237 | SingletonStore1, 238 | SingletonStore2 239 | ] 240 | } 241 | \endcode 242 | 243 | 244 | */ 245 | 246 | QQmlListProperty QFStore::redispatchTargets() 247 | { 248 | return QQmlListProperty(qobject_cast(this), m_redispatchTargets); 249 | } 250 | 251 | 252 | /*! \qmlproperty object Store::bindSource 253 | * 254 | * This property holds the source of action. It can be an ActionCreator / Dispatcher component 255 | * 256 | * The default value is null, and it listens to AppDispatcher 257 | */ 258 | 259 | 260 | QObject *QFStore::bindSource() const 261 | { 262 | return m_bindSource.data(); 263 | } 264 | 265 | void QFStore::setBindSource(QObject *source) 266 | { 267 | m_bindSource = source; 268 | setup(); 269 | emit bindSourceChanged(); 270 | } 271 | -------------------------------------------------------------------------------- /src/qfstore.h: -------------------------------------------------------------------------------- 1 | #ifndef QFSTORE_H 2 | #define QFSTORE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "qfactioncreator.h" 10 | #include "qfdispatcher.h" 11 | 12 | class QFStore : public QObject 13 | { 14 | Q_OBJECT 15 | Q_PROPERTY(QObject* bindSource READ bindSource WRITE setBindSource NOTIFY bindSourceChanged) 16 | Q_PROPERTY(QQmlListProperty children READ children) 17 | Q_PROPERTY(QQmlListProperty redispatchTargets READ redispatchTargets) 18 | Q_PROPERTY(bool filterFunctionEnabled MEMBER m_filterFunctionEnabled NOTIFY filterFunctionEnabledChanged) 19 | 20 | Q_CLASSINFO("DefaultProperty", "children") 21 | 22 | public: 23 | explicit QFStore(QObject *parent = nullptr); 24 | QQmlListProperty children(); 25 | 26 | QObject* bindSource() const; 27 | void setBindSource(QObject* source); 28 | 29 | QQmlListProperty redispatchTargets(); 30 | 31 | signals: 32 | void dispatched(QString type, QJSValue message); 33 | 34 | void bindSourceChanged(); 35 | 36 | void filterFunctionEnabledChanged(); 37 | 38 | public slots: 39 | void dispatch(QString type, QJSValue message = QJSValue()); 40 | 41 | void bind(QObject* source); 42 | 43 | 44 | protected: 45 | void classBegin(); 46 | void componentComplete(); 47 | 48 | private slots: 49 | void setup(); 50 | 51 | private: 52 | QObjectList m_children; 53 | 54 | QPointer m_bindSource; 55 | 56 | QPointer m_actionCreator; 57 | 58 | QPointer m_dispatcher; 59 | 60 | QObjectList m_redispatchTargets; 61 | 62 | bool m_filterFunctionEnabled; 63 | 64 | }; 65 | 66 | #endif // QFSTORE_H 67 | -------------------------------------------------------------------------------- /src/src.pri: -------------------------------------------------------------------------------- 1 | INCLUDEPATH += $$PWD 2 | 3 | HEADERS += \ 4 | $$PWD/qfapplistener.h \ 5 | $$PWD/qfappscript.h \ 6 | $$PWD/priv/qfappscriptrunnable.h \ 7 | $$PWD/priv/qfappscriptdispatcherwrapper.h \ 8 | $$PWD/priv/qflistener.h \ 9 | $$PWD/qfapplistenergroup.h \ 10 | $$PWD/qfappscriptgroup.h \ 11 | $$PWD/qffilter.h \ 12 | $$PWD/qfkeytable.h \ 13 | $$PWD/priv/qfsignalproxy.h \ 14 | $$PWD/qfactioncreator.h \ 15 | $$PWD/QFAppDispatcher \ 16 | $$PWD/QFKeyTable \ 17 | $$PWD/QuickFlux \ 18 | $$PWD/qfobject.h \ 19 | $$PWD/priv/qfhook.h \ 20 | $$PWD/qfdispatcher.h \ 21 | $$PWD/qfappdispatcher.h \ 22 | $$PWD/priv/quickfluxfunctions.h \ 23 | $$PWD/priv/qfmiddlewareshook.h \ 24 | $$PWD/qfstore.h \ 25 | $$PWD/qfhydrate.h \ 26 | $$PWD/qfmiddleware.h \ 27 | $$PWD/qfmiddlewarelist.h 28 | 29 | SOURCES += \ 30 | $$PWD/qfapplistener.cpp \ 31 | $$PWD/qfappscript.cpp \ 32 | $$PWD/qfappscriptrunnable.cpp \ 33 | $$PWD/qfappscriptdispatcherwrapper.cpp \ 34 | $$PWD/qflistener.cpp \ 35 | $$PWD/qfqmltypes.cpp \ 36 | $$PWD/qfapplistenergroup.cpp \ 37 | $$PWD/qfappscriptgroup.cpp \ 38 | $$PWD/qffilter.cpp \ 39 | $$PWD/qfkeytable.cpp \ 40 | $$PWD/priv/qfsignalproxy.cpp \ 41 | $$PWD/qfactioncreator.cpp \ 42 | $$PWD/qfobject.cpp \ 43 | $$PWD/priv/qfhook.cpp \ 44 | $$PWD/qfdispatcher.cpp \ 45 | $$PWD/qfappdispatcher.cpp \ 46 | $$PWD/priv/quickfluxfunctions.cpp \ 47 | $$PWD/priv/qfmiddlewareshook.cpp \ 48 | $$PWD/qfstore.cpp \ 49 | $$PWD/qfhydrate.cpp \ 50 | $$PWD/qfmiddleware.cpp \ 51 | $$PWD/qfmiddlewarelist.cpp 52 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | build-* 75 | vendor 76 | 77 | *.properties 78 | !gradle-wrapper.jar 79 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/actions/ActionTypes.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | 5 | KeyTable { 6 | // Call it when the initialization is finished. 7 | // Now, it is able to start and show the application 8 | property string startApp 9 | 10 | // Call it to quit the application 11 | property string quitApp 12 | } 13 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/actions/AppActions.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | 5 | ActionCreator { 6 | 7 | signal startApp 8 | 9 | signal quitApp 10 | 11 | } 12 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/actions/qmldir: -------------------------------------------------------------------------------- 1 | singleton ActionTypes 1.0 ActionTypes.qml 2 | singleton AppActions 1.0 AppActions.qml 3 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/adapters/StoreAdapter.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.0 3 | import "../storeworkers" 4 | 5 | Item { 6 | id: adapter 7 | 8 | Item { 9 | id: model 10 | 11 | MainStoreWorker { 12 | 13 | } 14 | 15 | } 16 | 17 | AppListener { 18 | onDispatched: { 19 | var workers = model.data; 20 | for (var i in workers) { 21 | workers[i].dispatched(type, message); 22 | } 23 | } 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/adapters/qmldir: -------------------------------------------------------------------------------- 1 | StoreAdapter 1.0 StoreAdapter.qml 2 | 3 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/constants/Constants.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | 5 | KeyTable { 6 | } 7 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/constants/qmldir: -------------------------------------------------------------------------------- 1 | singleton Constants 1.0 Constants.qml 2 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/stores/MainStore.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | 4 | QtObject { 5 | property string text : "Hello World!" 6 | } 7 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/stores/qmldir: -------------------------------------------------------------------------------- 1 | singleton MainStore 1.0 MainStore.qml 2 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/storeworkers/MainStoreWorker.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.0 3 | import App.actions 1.0 4 | import App.stores 1.0 5 | 6 | StoreWorker { 7 | id: worker 8 | 9 | Filter { 10 | type: ActionTypes.startApp 11 | onDispatched: { 12 | mainWindow.visible = true; 13 | } 14 | } 15 | 16 | Filter { 17 | type: ActionTypes.quitApp 18 | onDispatched: { 19 | Qt.quit(); 20 | } 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/storeworkers/StoreWorker.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | QtObject { 4 | id: worker 5 | 6 | signal dispatched(string type, var message); 7 | 8 | default property alias children: worker.__children 9 | 10 | property list __children: [QtObject {}] 11 | } 12 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/App/storeworkers/qmldir: -------------------------------------------------------------------------------- 1 | MainStoreWorker 1.0 MainStoreWorker.qml 2 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/README.md: -------------------------------------------------------------------------------- 1 | That is a template created for application with using Quick Flux. This template provides: 2 | 3 | 1. qpm.json - Install QuickFlux via qpm 4 | 2. Template of ActionTypes and AppActions 5 | 3. Templates of Store / StoreWorker and Adapter 6 | 4. AppView C++ - Initialize QML environment and listen message from AppDispatcher 7 | 8 | File Structre: 9 | 10 | ``` 11 | . 12 | ├── App 13 | │   ├── actions 14 | │   │   ├── ActionTypes.qml 15 | │   │   ├── AppActions.qml 16 | │   │   └── qmldir 17 | │   ├── adapters 18 | │   │   ├── StoreAdapter.qml 19 | │   │   └── qmldir 20 | │   ├── constants 21 | │   │   ├── Constants.qml 22 | │   │   └── qmldir 23 | │   ├── stores 24 | │   │   ├── MainStore.qml 25 | │   │   └── qmldir 26 | │   └── storeworkers 27 | │   ├── MainStoreWorker.qml 28 | │   ├── StoreWorker.qml 29 | │   └── qmldir 30 | ├── app.pri 31 | ├── app.pro 32 | ├── app.qrc 33 | ├── appview.cpp 34 | ├── appview.h 35 | ├── deployment.pri 36 | ├── main.cpp 37 | ├── main.qml 38 | └── qpm.json 39 | 40 | ``` 41 | 42 | To use this project template, just copy this folder to your workspace. Rename the app.pro to your project name. Then you could start coding. 43 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/app.pri: -------------------------------------------------------------------------------- 1 | CONFIG += c++11 2 | 3 | SOURCES += $$PWD/appview.cpp 4 | 5 | RESOURCES += \ 6 | $$PWD/app.qrc 7 | 8 | INCLUDEPATH += $$PWD 9 | 10 | # Additional import path used to resolve QML modules in Qt Creator's code model 11 | QML_IMPORT_PATH = $$PWD 12 | 13 | HEADERS += \ 14 | $$PWD/appview.h 15 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/app.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = app 2 | 3 | QT += qml quick 4 | CONFIG += c++11 5 | 6 | QT += quick qml 7 | 8 | include(app.pri) 9 | 10 | SOURCES += main.cpp 11 | 12 | ROOT_DIR = $$PWD 13 | 14 | # Default rules for deployment. 15 | include(deployment.pri) 16 | include(vendor/vendor.pri) 17 | 18 | DISTFILES += \ 19 | qpm.json 20 | 21 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/app.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | main.qml 4 | App/stores/MainStore.qml 5 | App/stores/qmldir 6 | App/actions/AppActions.qml 7 | App/actions/ActionTypes.qml 8 | App/actions/qmldir 9 | App/constants/Constants.qml 10 | App/constants/qmldir 11 | App/adapters/StoreAdapter.qml 12 | App/adapters/qmldir 13 | App/storeworkers/MainStoreWorker.qml 14 | App/storeworkers/qmldir 15 | App/storeworkers/StoreWorker.qml 16 | 17 | 18 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/appview.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "appview.h" 7 | 8 | AppView::AppView(QObject *parent) : QObject(parent) 9 | { 10 | 11 | } 12 | 13 | int AppView::exec() 14 | { 15 | m_engine.addImportPath("qrc:///"); 16 | m_engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); 17 | 18 | QFAppDispatcher* dispatcher = QFAppDispatcher::instance(&m_engine); 19 | connect(dispatcher,SIGNAL(dispatched(QString,QJSValue)), 20 | this,SLOT(onDispatched(QString,QJSValue))); 21 | 22 | dispatcher->dispatch("startApp"); 23 | 24 | QCoreApplication* app = QCoreApplication::instance(); 25 | return app->exec(); 26 | } 27 | 28 | void AppView::onDispatched(QString type, QJSValue message) 29 | { 30 | Q_UNUSED(type); 31 | Q_UNUSED(message); 32 | } 33 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/appview.h: -------------------------------------------------------------------------------- 1 | #ifndef APPVIEW_H 2 | #define APPVIEW_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class AppView : public QObject 9 | { 10 | Q_OBJECT 11 | public: 12 | explicit AppView(QObject *parent = 0); 13 | 14 | int exec(); 15 | 16 | signals: 17 | 18 | public slots: 19 | 20 | private slots: 21 | void onDispatched(QString type, QJSValue message); 22 | 23 | private: 24 | 25 | QQmlApplicationEngine m_engine; 26 | 27 | }; 28 | 29 | #endif // APPVIEW_H 30 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/deployment.pri: -------------------------------------------------------------------------------- 1 | unix:!android { 2 | isEmpty(target.path) { 3 | qnx { 4 | target.path = /tmp/$${TARGET}/bin 5 | } else { 6 | target.path = /opt/$${TARGET}/bin 7 | } 8 | export(target.path) 9 | } 10 | INSTALLS += target 11 | } 12 | 13 | export(INSTALLS) 14 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "appview.h" 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QGuiApplication::setAttribute(Qt::AA_EnableHighDpiScaling); 8 | 9 | QGuiApplication app(argc, argv); 10 | Q_UNUSED(app); 11 | 12 | AppView view; 13 | 14 | return view.exec(); 15 | } 16 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/main.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.3 2 | import QtQuick.Window 2.2 3 | import QtQuick.Controls 1.4 4 | import App.constants 1.0 5 | import App.adapters 1.0 6 | import App.actions 1.0 7 | import App.stores 1.0 8 | 9 | Window { 10 | id: mainWindow 11 | visible: false 12 | width: 480 13 | height: 640 14 | 15 | StoreAdapter { 16 | } 17 | 18 | MouseArea { 19 | anchors.fill: parent 20 | onClicked: { 21 | AppActions.quitApp(); 22 | } 23 | } 24 | 25 | Text { 26 | text: qsTr(MainStore.text) 27 | anchors.centerIn: parent 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /templates/quickflux-project-template/qpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "", 3 | "description": "", 4 | "dependencies": [ 5 | "com.github.benlau.quickflux@1.0.5.4" 6 | ], 7 | "license": "NONE", 8 | "pri_filename": "", 9 | "webpage": "" 10 | } -------------------------------------------------------------------------------- /tests/quickfluxunittests/.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | 4 | *~ 5 | *.autosave 6 | *.a 7 | *.core 8 | *.moc 9 | *.o 10 | *.obj 11 | *.orig 12 | *.rej 13 | *.so 14 | *.so.* 15 | *_pch.h.cpp 16 | *_resource.rc 17 | *.qm 18 | .#* 19 | *.*# 20 | core 21 | !core/ 22 | tags 23 | .DS_Store 24 | .directory 25 | *.debug 26 | Makefile* 27 | *.prl 28 | *.app 29 | moc_*.cpp 30 | ui_*.h 31 | qrc_*.cpp 32 | Thumbs.db 33 | *.res 34 | *.rc 35 | /.qmake.cache 36 | /.qmake.stash 37 | 38 | # qtcreator generated files 39 | *.pro.user* 40 | 41 | # xemacs temporary files 42 | *.flc 43 | 44 | # Vim temporary files 45 | .*.swp 46 | 47 | # Visual Studio generated files 48 | *.ib_pdb_index 49 | *.idb 50 | *.ilk 51 | *.pdb 52 | *.sln 53 | *.suo 54 | *.vcproj 55 | *vcproj.*.*.user 56 | *.ncb 57 | *.sdf 58 | *.opensdf 59 | *.vcxproj 60 | *vcxproj.* 61 | 62 | # MinGW generated files 63 | *.Debug 64 | *.Release 65 | 66 | # Python byte code 67 | *.pyc 68 | 69 | # Binaries 70 | # -------- 71 | *.dll 72 | *.exe 73 | 74 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/QuickFluxTests/ActionTypes.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | 5 | KeyTable { 6 | 7 | property string value1; 8 | 9 | property int value2 : 2; 10 | 11 | property real value3: 3.3 12 | 13 | property bool value4: true 14 | 15 | property bool value4b: false 16 | 17 | property point value5 : Qt.point(5,6) 18 | 19 | property rect value6 : Qt.rect(6,7,8,9) 20 | 21 | } 22 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/QuickFluxTests/AppActions.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | 5 | ActionCreator { 6 | 7 | signal test1; 8 | 9 | signal test2; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/QuickFluxTests/AppActionsKeyTable.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | import QuickFlux 1.0 4 | 5 | KeyTable { 6 | 7 | property string test1; 8 | 9 | property string test2; 10 | 11 | } -------------------------------------------------------------------------------- /tests/quickfluxunittests/QuickFluxTests/DispatcherTests.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QuickFlux 1.0 3 | 4 | Item { 5 | 6 | objectName: "DispatcherTests"; 7 | 8 | property var messages: new Array 9 | 10 | AppListener { 11 | onDispatched: { 12 | messages.push([type, message]); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/QuickFluxTests/DummyAction.qml: -------------------------------------------------------------------------------- 1 | pragma Singleton 2 | import QtQuick 2.0 3 | 4 | QtObject { 5 | property int value: 13 6 | } 7 | 8 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/QuickFluxTests/qmldir: -------------------------------------------------------------------------------- 1 | module QuickFluxTests 2 | singleton DummyAction 1.0 DummyAction.qml 3 | singleton ActionTypes 1.0 ActionTypes.qml 4 | singleton AppActions 1.0 AppActions.qml 5 | singleton AppActionsKeyTable 1.0 AppActionsKeyTable.qml 6 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/actiontypes.cpp: -------------------------------------------------------------------------------- 1 | #include "actiontypes.h" 2 | 3 | QString ActionTypes::value1 = "value1"; 4 | 5 | int ActionTypes::value2 = 2; 6 | 7 | qreal ActionTypes::value3 = 3.3; 8 | 9 | bool ActionTypes::value4 = true; 10 | 11 | bool ActionTypes::value4b = false; 12 | 13 | QPointF ActionTypes::value5 = QPointF(5,6); 14 | 15 | QRectF ActionTypes::value6 = QRect(6,7,8,9); 16 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/actiontypes.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #include 5 | #include 6 | class ActionTypes { 7 | 8 | public: 9 | 10 | static QString value1; 11 | 12 | static int value2; 13 | 14 | static qreal value3; 15 | 16 | static bool value4; 17 | 18 | static bool value4b; 19 | 20 | static QPointF value5; 21 | 22 | static QRectF value6; 23 | 24 | }; 25 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/dummy.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | 3 | Item { 4 | 5 | } 6 | 7 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "testrunner.h" 4 | #include "quickfluxunittests.h" 5 | #include "messagelogger.h" 6 | 7 | namespace AutoTestRegister { 8 | QUICK_TEST_MAIN(QuickTests) 9 | } 10 | 11 | int main(int argc, char *argv[]) 12 | { 13 | qputenv("QML_DISABLE_DISK_CACHE", "true"); 14 | 15 | QGuiApplication app(argc,argv); 16 | 17 | TestRunner runner; 18 | runner.add(); 19 | runner.add(QString(SRCDIR) + "/qmltests"); 20 | 21 | bool error = runner.exec(app.arguments()); 22 | 23 | if (!error) { 24 | qWarning() << "All test cases passed!"; 25 | } 26 | 27 | return error; 28 | } 29 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/messagelogger.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "messagelogger.h" 4 | 5 | static QStringList s_messages; 6 | 7 | static void logFunc(QtMsgType type, const QMessageLogContext &context, const QString &msg) { 8 | Q_UNUSED(type); 9 | Q_UNUSED(context); 10 | Q_UNUSED(msg); 11 | 12 | MessageLogger::globalInstance()->log(msg); 13 | 14 | QByteArray localMsg = msg.toLocal8Bit(); 15 | fprintf(stderr, "%s (%s:%u, %s)\n", localMsg.constData(), context.file, context.line, context.function); 16 | } 17 | 18 | MessageLogger::MessageLogger(QObject *parent) : QObject(parent) 19 | { 20 | } 21 | 22 | void MessageLogger::install() 23 | { 24 | qInstallMessageHandler(logFunc); 25 | } 26 | 27 | void MessageLogger::log(const QString &message) 28 | { 29 | s_messages << message; 30 | } 31 | 32 | QStringList MessageLogger::messages() const 33 | { 34 | return s_messages; 35 | } 36 | 37 | void MessageLogger::setMessages(const QStringList &messages) 38 | { 39 | s_messages = messages; 40 | } 41 | 42 | MessageLogger *MessageLogger::globalInstance() 43 | { 44 | static MessageLogger* instance = nullptr; 45 | if (!instance) { 46 | instance = new MessageLogger(QCoreApplication::instance()); 47 | } 48 | return instance; 49 | } 50 | 51 | void MessageLogger::clear() 52 | { 53 | s_messages.clear(); 54 | } 55 | 56 | static QObject *provider(QQmlEngine *engine, QJSEngine *scriptEngine) { 57 | Q_UNUSED(engine); 58 | Q_UNUSED(scriptEngine); 59 | 60 | MessageLogger* logger = new MessageLogger(engine); 61 | return logger; 62 | } 63 | 64 | static void init() { 65 | qmlRegisterSingletonType("MessageLogger", 1, 0, "MessageLogger", provider); 66 | } 67 | 68 | Q_COREAPP_STARTUP_FUNCTION(init) 69 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/messagelogger.h: -------------------------------------------------------------------------------- 1 | #ifndef MESSAGELOGGER_H 2 | #define MESSAGELOGGER_H 3 | 4 | #include 5 | #include 6 | 7 | class MessageLogger : public QObject 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit MessageLogger(QObject *parent = nullptr); 12 | 13 | void log(const QString& message); 14 | 15 | void setMessages(const QStringList &messages); 16 | 17 | static MessageLogger* globalInstance(); 18 | 19 | public slots: 20 | void install(); 21 | void clear(); 22 | QStringList messages() const; 23 | 24 | private: 25 | }; 26 | 27 | #endif // MESSAGELOGGER_H 28 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qml.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | dummy.qml 4 | QuickFluxTests/DummyAction.qml 5 | QuickFluxTests/qmldir 6 | QuickFluxTests/DispatcherTests.qml 7 | QuickFluxTests/ActionTypes.qml 8 | QuickFluxTests/AppActions.qml 9 | QuickFluxTests/AppActionsKeyTable.qml 10 | 11 | 12 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_actioncreator.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.1 4 | 5 | TestCase { 6 | name : "ActionCreatorTests" 7 | 8 | ActionCreator { 9 | id: actionCreator 10 | signal test1(); 11 | signal test2(int v1, string v2, real v3, var v4) 12 | } 13 | 14 | AppListener { 15 | id: listener 16 | property var payload: new Array 17 | onDispatched: { 18 | payload.push({ 19 | type: type, 20 | message: message 21 | }); 22 | } 23 | } 24 | 25 | function test_emission() { 26 | listener.payload = []; 27 | actionCreator.test1(); 28 | compare(listener.payload.length, 1); 29 | compare(listener.payload[0].type, "test1"); 30 | 31 | var data = { 32 | f1 : "f1" 33 | } 34 | 35 | actionCreator.test2(1,"v2",3.0,data); 36 | compare(listener.payload.length, 2); 37 | compare(listener.payload[1].type, "test2"); 38 | 39 | var message = listener.payload[1].message; 40 | compare(message.v1, 1); 41 | compare(message.v2, "v2"); 42 | compare(message.v3, 3.0); 43 | compare(message.v4, data); 44 | } 45 | 46 | ActionCreator { 47 | // No signals 48 | id: dummy 49 | } 50 | 51 | ActionCreator { 52 | id: actionCreator2 53 | objectName: "actionCreator2"; 54 | 55 | signal test1 56 | 57 | dispatcher: Dispatcher { 58 | id: dispatcher2 59 | property var payload: new Array 60 | onDispatched: { 61 | dispatcher2.payload.push({ 62 | type: type, 63 | message: message 64 | }); 65 | } 66 | } 67 | } 68 | 69 | AppListener { 70 | id: listener2 71 | signal test1(); 72 | property var payload: new Array 73 | onDispatched: { 74 | listener2.payload.push({ 75 | type: type, 76 | message: message 77 | }); 78 | } 79 | } 80 | 81 | function test_customDispatcher() { 82 | listener2.payload = []; 83 | actionCreator2.test1(); 84 | compare(listener2.payload.length, 0); 85 | compare(dispatcher2.payload.length, 1); 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_appdispatcher.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.0 4 | 5 | TestCase { 6 | name : "AppDispatcher" 7 | 8 | property var messages : new Array 9 | 10 | Connections { 11 | target : AppDispatcher 12 | onDispatched: { 13 | messages.push([type,message]); 14 | } 15 | } 16 | 17 | function test_actionDispatcher() { 18 | compare(messages.length,0); 19 | 20 | AppDispatcher.dispatch("test1",{}); 21 | compare(messages.length,1); 22 | 23 | // Verify can it pass function over the message. 24 | 25 | AppDispatcher.dispatch("test2",{ 26 | func : function(i) { 27 | return i+i; 28 | } 29 | }); 30 | compare(messages.length,2); 31 | var message = messages[messages.length - 1][1]; 32 | compare(message.func(2),4); 33 | } 34 | 35 | } 36 | 37 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_appdispatcher_dispatch_reentrant.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.0 4 | 5 | TestCase { 6 | name : "Dispatcher_dispatch_reentrant" 7 | 8 | property var messages : new Array 9 | 10 | Connections { 11 | target : AppDispatcher 12 | onDispatched: { 13 | messages.push([type,message]); 14 | if (type === "ping") { 15 | AppDispatcher.dispatch("pong",{}) 16 | } 17 | } 18 | } 19 | 20 | Connections { 21 | target : AppDispatcher 22 | onDispatched: { 23 | messages.push([type,message]); 24 | if (type === "ping") { 25 | AppDispatcher.dispatch("pong",{}) 26 | } 27 | } 28 | } 29 | 30 | function test_dispatch_reentrant() { 31 | var i; 32 | compare(messages.length,0); 33 | 34 | AppDispatcher.dispatch("ping",{}); 35 | 36 | compare(messages.length,6); // ping x 2 , pong x 4 37 | 38 | compare(messages[0][0],"ping"); 39 | compare(messages[1][0],"ping"); 40 | compare(messages[2][0],"pong"); 41 | compare(messages[3][0],"pong"); 42 | compare(messages[4][0],"pong"); 43 | compare(messages[5][0],"pong"); 44 | } 45 | 46 | } 47 | 48 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_appdispatcher_waitfor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.0 4 | 5 | TestCase { 6 | name : "Dispatcher_waitFor" 7 | 8 | function test_waitFor() { 9 | var count1 = 0, count2 = 0; 10 | var id1,id2; 11 | var realSeq = [], seq = []; 12 | 13 | function callback1() { 14 | realSeq.push("callback1"); 15 | AppDispatcher.waitFor([id2]); 16 | seq.push("callback1"); 17 | count1++; 18 | } 19 | 20 | function callback2() { 21 | realSeq.push("callback2"); 22 | seq.push("callback2"); 23 | count2++; 24 | } 25 | 26 | //Nothing happen 27 | AppDispatcher.waitFor([]); 28 | 29 | id1 = AppDispatcher.addListener(callback1); 30 | compare(id1 > 0,true); 31 | 32 | id2 = AppDispatcher.addListener(callback2); 33 | compare(id2 > 0,true); 34 | compare(id1 !== id2,true); 35 | 36 | AppDispatcher.dispatch("a-message"); 37 | 38 | compare(count1,1); 39 | compare(count2,1); 40 | compare(realSeq,["callback1","callback2"]); 41 | compare(seq,["callback2","callback1"]); 42 | 43 | AppDispatcher.removeListener(id1); 44 | AppDispatcher.removeListener(id2); 45 | 46 | AppDispatcher.dispatch("a-message"); 47 | compare(count1,1); 48 | compare(count2,1); 49 | } 50 | 51 | function test_waitForItself() { 52 | var count1 = 0, count2 = 0; 53 | var id1,id2; 54 | var realSeq = [], seq = []; 55 | 56 | function callback1() { 57 | realSeq.push("callback1"); 58 | 59 | // Wait for itself 60 | AppDispatcher.waitFor([id1]); 61 | seq.push("callback1"); 62 | count1++; 63 | } 64 | 65 | function callback2() { 66 | realSeq.push("callback2"); 67 | AppDispatcher.waitFor([id1]); 68 | 69 | seq.push("callback2"); 70 | count2++; 71 | } 72 | 73 | id1 = AppDispatcher.addListener(callback1); 74 | compare(id1 > 0,true); 75 | 76 | id2 = AppDispatcher.addListener(callback2); 77 | compare(id2 > 0,true); 78 | compare(id1 !== id2,true); 79 | 80 | AppDispatcher.dispatch("a-message"); 81 | 82 | compare(count1,1); 83 | compare(count2,1); 84 | compare(realSeq,["callback1","callback2"]); 85 | compare(seq,["callback1","callback2"]); 86 | 87 | AppDispatcher.removeListener(id1); 88 | AppDispatcher.removeListener(id2); 89 | 90 | AppDispatcher.dispatch("a-message"); 91 | compare(count1,1); 92 | compare(count2,1); 93 | } 94 | 95 | function test_cyclicDependency() { 96 | 97 | var count1 = 0, count2 = 0 , count3 = 0; 98 | var id1,id2,id3; 99 | var realSeq = [], seq = []; 100 | 101 | function callback1() { 102 | realSeq.push("callback1"); 103 | AppDispatcher.waitFor([id3]); 104 | seq.push("callback1"); 105 | count1++; 106 | } 107 | 108 | function callback2() { 109 | realSeq.push("callback2"); 110 | 111 | AppDispatcher.waitFor([id1]); 112 | seq.push("callback2"); 113 | count2++; 114 | } 115 | 116 | function callback3() { 117 | realSeq.push("callback3"); 118 | 119 | AppDispatcher.waitFor([id2]); 120 | seq.push("callback3"); 121 | count3++; 122 | } 123 | 124 | id1 = AppDispatcher.addListener(callback1); 125 | compare(id1 > 0,true); 126 | 127 | id2 = AppDispatcher.addListener(callback2); 128 | compare(id2 > 0,true); 129 | compare(id1 !== id2,true); 130 | 131 | id3 = AppDispatcher.addListener(callback3); 132 | 133 | AppDispatcher.dispatch("a-message"); 134 | 135 | compare(count1,1); 136 | compare(count2,1); 137 | compare(count3,1); 138 | 139 | compare(realSeq,["callback1","callback3","callback2"]); 140 | compare(seq,["callback2","callback3","callback1"]); 141 | 142 | AppDispatcher.removeListener(id1); 143 | AppDispatcher.removeListener(id2); 144 | AppDispatcher.removeListener(id3); 145 | 146 | AppDispatcher.dispatch("a-message"); 147 | compare(count1,1); 148 | compare(count2,1); 149 | compare(count3,1); 150 | 151 | } 152 | 153 | } 154 | 155 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_applistener.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.0 4 | 5 | TestCase { 6 | 7 | name : "AppListener" 8 | 9 | Item { 10 | id: container 11 | 12 | AppListener { 13 | id: listener 14 | onDispatched: { 15 | } 16 | } 17 | } 18 | 19 | 20 | function test_on() { 21 | var test1, test2; 22 | compare(listener.listenerId > 0,true); 23 | listener.on("test_on_Test1",function(value) { 24 | test1 = value; 25 | }).on("test_on_Test2",function(value) { 26 | test2 = value; 27 | }); 28 | 29 | AppDispatcher.dispatch("test_on_Test1","x"); 30 | compare(test1,"x"); 31 | compare(test2,undefined); 32 | 33 | AppDispatcher.dispatch("test_on_Test2",5676); 34 | compare(test1,"x"); 35 | compare(test2,5676); 36 | } 37 | 38 | function test_enabled() { 39 | var count = 0; 40 | listener.on("test_enabled",function() { 41 | count++; 42 | }); 43 | 44 | compare(count,0); 45 | AppDispatcher.dispatch("test_enabled",null); 46 | compare(count,1); 47 | 48 | container.enabled = false; 49 | AppDispatcher.dispatch("test_enabled",null); 50 | compare(count,1); 51 | 52 | container.enabled = true; 53 | AppDispatcher.dispatch("test_enabled",null); 54 | compare(count,2); 55 | } 56 | 57 | function test_removeListener() { 58 | var count1 = 0,count2=0; 59 | 60 | var func1 = function() { 61 | count1++; 62 | } 63 | 64 | var func2 = function() { 65 | count2++; 66 | } 67 | 68 | listener.on("test_removeListener1",func1).on("test_removeListener2",func2); 69 | 70 | AppDispatcher.dispatch("test_removeListener1",null); 71 | AppDispatcher.dispatch("test_removeListener2",null); 72 | compare(count1,1); 73 | compare(count2,1); 74 | 75 | listener.removeListener("test_removeListener1",func1); 76 | AppDispatcher.dispatch("test_removeListener1",null); 77 | AppDispatcher.dispatch("test_removeListener2",null); 78 | 79 | compare(count1,1); 80 | compare(count2,2); 81 | 82 | } 83 | 84 | function test_removeAllListener() { 85 | var count1 = 0,count2=0; 86 | 87 | var func1 = function() { 88 | count1++; 89 | } 90 | 91 | var func2 = function() { 92 | count2++; 93 | } 94 | 95 | listener.on("test_removeAllListener",func1).on("test_removeAllListener",func2); 96 | 97 | AppDispatcher.dispatch("test_removeAllListener"); 98 | AppDispatcher.dispatch("test_removeAllListener"); 99 | compare(count1,2); 100 | compare(count2,2); 101 | 102 | listener.removeAllListener("test_removeAllListener"); 103 | AppDispatcher.dispatch("test_removeAllListener",null); 104 | 105 | compare(count1,2); 106 | compare(count2,2); 107 | } 108 | 109 | } 110 | 111 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_applistener_alwayson.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.0 4 | 5 | TestCase { 6 | 7 | name : "AppListener_alwaysOn" 8 | 9 | property int count1 : 0 10 | property int count2 : 0 11 | 12 | Item { 13 | enabled: false 14 | AppListener { 15 | id: listener1 16 | alwaysOn: true 17 | onDispatched: count1++; 18 | } 19 | } 20 | 21 | Item { 22 | enabled: false 23 | AppListener { 24 | id: listener2 25 | onDispatched: count2++; 26 | } 27 | } 28 | 29 | function test_alwayson() { 30 | var name = "test_filter" 31 | compare(listener1.enabled,false); 32 | compare(listener2.enabled,false); 33 | AppDispatcher.dispatch(name); 34 | AppDispatcher.dispatch(name); 35 | 36 | compare(count1,2); 37 | compare(count2,0); 38 | 39 | } 40 | 41 | 42 | } 43 | 44 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_applistener_filter.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.0 4 | 5 | TestCase { 6 | 7 | name : "AppListener" 8 | 9 | property int count1 : 0 10 | property int count2 : 0 11 | 12 | AppListener { 13 | id: listener1 14 | onDispatched: count1++; 15 | } 16 | 17 | AppListener { 18 | id: listener2 19 | onDispatched: count2++; 20 | } 21 | 22 | function test_filter() { 23 | var name = "test_filter" 24 | listener1.filter = name; 25 | listener1.filters = []; 26 | listener2.filter = ""; 27 | listener2.filters = []; 28 | count1 = count2 = 0; 29 | 30 | AppDispatcher.dispatch("Non-related message"); 31 | compare(count1,0); 32 | compare(count2,1); 33 | 34 | AppDispatcher.dispatch(name); 35 | compare(count1,1); 36 | compare(count2,2); 37 | 38 | compare(listener1.filters.length,0); 39 | } 40 | 41 | function test_filters() { 42 | var name1 = "test_filter1"; 43 | var name2 = "test_filter2"; 44 | 45 | listener1.filter = ""; 46 | listener1.filters = [name1,name2]; 47 | listener2.filter = ""; 48 | listener2.filters = []; 49 | count1 = count2 = 0; 50 | 51 | AppDispatcher.dispatch("Non-related message"); 52 | compare(count1,0); 53 | compare(count2,1); 54 | 55 | AppDispatcher.dispatch(name1); 56 | compare(count1,1); 57 | compare(count2,2); 58 | 59 | AppDispatcher.dispatch(name2); 60 | compare(count1,2); 61 | compare(count2,3); 62 | 63 | compare(listener1.filters.length,2); 64 | 65 | } 66 | 67 | } 68 | 69 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_applistener_waitfor.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.0 4 | 5 | TestCase { 6 | name : "AppListener_waitFor" 7 | 8 | property var seq : new Array; 9 | 10 | AppListener { 11 | id: listener1 12 | property int count : 0; 13 | waitFor: [] 14 | onDispatched: { 15 | count++; 16 | seq.push("listener1"); 17 | } 18 | } 19 | 20 | AppListener { 21 | id: listener2 22 | property int count : 0; 23 | onDispatched: { 24 | count++; 25 | seq.push("listener2"); 26 | } 27 | } 28 | 29 | AppListener { 30 | id: listener3 31 | property int count : 0; 32 | onDispatched: { 33 | count++; 34 | seq.push("listener3"); 35 | } 36 | } 37 | 38 | function test_waitFor() { 39 | AppDispatcher.dispatch("test-message","test-message"); 40 | listener2.waitFor = [listener1.listenerId,listener3.listenerId]; 41 | listener3.waitFor = [listener1.listenerId]; 42 | seq = new Array; 43 | AppDispatcher.dispatch("test-message","test-message"); 44 | compare(listener1.count,2); 45 | compare(listener2.count,2); 46 | compare(listener3.count,2); 47 | compare(seq,["listener1","listener3","listener2"]); 48 | } 49 | 50 | 51 | } 52 | 53 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_applistenergroup.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.0 4 | 5 | TestCase { 6 | id: testCase 7 | name : "AppListenerGroup" 8 | 9 | property var seq : new Array; 10 | 11 | Component { 12 | id: creator 13 | AppListenerGroup { 14 | id: group 15 | property string prefix; 16 | property int count: 0 17 | property alias listener1: listener1 18 | property alias listener2: listener2 19 | property alias listener3: listener3 20 | 21 | AppListener { 22 | id: listener1; 23 | onDispatched: { 24 | count++; 25 | seq.push(prefix + "Listener1"); 26 | } 27 | } 28 | 29 | AppListener { 30 | id: listener2; 31 | 32 | onDispatched: { 33 | count++; 34 | seq.push(prefix + "Listener2"); 35 | } 36 | } 37 | 38 | AppListener { 39 | id: listener3; 40 | 41 | onDispatched: { 42 | count++; 43 | seq.push(prefix + "Listener3"); 44 | } 45 | } 46 | } 47 | } 48 | 49 | function test_grouping() { 50 | var group = creator.createObject(testCase); 51 | 52 | compare(group.listenerIds.length,3); 53 | compare(group.listener1.waitFor.length,1); 54 | compare(group.listener2.waitFor.length,1); 55 | compare(group.listener3.waitFor.length,1); 56 | compare(group.listener1.waitFor[0],group.listener2.waitFor[0]); 57 | compare(group.listener3.waitFor[0],group.listener2.waitFor[0]); 58 | 59 | group.destroy(); 60 | } 61 | 62 | function test_waitFor() { 63 | seq = new Array; 64 | var group1 = creator.createObject(testCase); 65 | group1.prefix = "group1"; 66 | var group2 = creator.createObject(testCase); 67 | group2.prefix = "group2"; 68 | 69 | // group1 should receive message first, but now it depend on group2 70 | group1.waitFor = group2.listenerIds; 71 | 72 | AppDispatcher.dispatch("example message"); 73 | 74 | compare(seq,["group2Listener1", 75 | "group2Listener2", 76 | "group2Listener3", 77 | "group1Listener3", 78 | "group1Listener2", 79 | "group1Listener1"]); 80 | } 81 | 82 | } 83 | 84 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_appscriptgroup.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.0 4 | 5 | TestCase { 6 | name : "AppScriptGroupTests" 7 | 8 | AppScript { 9 | id: script1 10 | script: { 11 | once("dummy-message", function() { 12 | 13 | }); 14 | } 15 | } 16 | 17 | AppScript { 18 | id: script2 19 | script: { 20 | once("dummy-message", function() { 21 | 22 | }); 23 | } 24 | } 25 | 26 | AppScriptGroup { 27 | id: group1 28 | scripts: [script1,script2]; 29 | } 30 | 31 | function test_scripgroup() { 32 | script1.run(); 33 | compare(script1.running, true); 34 | compare(script2.running, false); 35 | 36 | script2.run(); 37 | compare(script1.running, false); 38 | compare(script2.running, true); 39 | 40 | group1.exitAll(); 41 | 42 | compare(script1.running, false); 43 | compare(script2.running, false); 44 | } 45 | 46 | AppScriptGroup { 47 | id: group2; 48 | } 49 | 50 | function test_error_handling() { 51 | group2.scripts = 3; 52 | group2.scripts = [group1]; 53 | } 54 | 55 | } 56 | 57 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_filter.qml: -------------------------------------------------------------------------------- 1 | // To prove that it could pass QImage via AppDispatcher 2 | import QtQuick 2.0 3 | import QtTest 1.0 4 | import QuickFlux 1.0 5 | 6 | TestCase { 7 | name : "FilterTests" 8 | 9 | AppListener { 10 | id: listener1 11 | property int count : 0 12 | property var lastMessage : null; 13 | 14 | Filter { 15 | type: "testFilter" 16 | onDispatched: { 17 | listener1.count++; 18 | listener1.lastMessage = message; 19 | } 20 | } 21 | } 22 | 23 | function test_filter() { 24 | compare(listener1.count, 0); 25 | compare(listener1.lastMessage, null); 26 | AppDispatcher.dispatch("non-related-action"); 27 | compare(listener1.count, 0); 28 | compare(listener1.lastMessage, null); 29 | 30 | var obj = {} 31 | AppDispatcher.dispatch("testFilter", obj); 32 | compare(listener1.count, 1); 33 | compare(listener1.lastMessage, obj); 34 | 35 | AppDispatcher.dispatch("testFilter", obj); 36 | compare(listener1.count, 2); 37 | compare(listener1.lastMessage, obj); 38 | } 39 | 40 | Item { 41 | id: listener2 42 | 43 | signal dispatched(string type, var message); 44 | 45 | property int count : 0 46 | property string lastType: "" 47 | property var lastMessage : null; 48 | 49 | Filter { 50 | type: "testFilterWithItem" 51 | onDispatched: { 52 | listener2.count++; 53 | listener2.lastType = type; 54 | listener2.lastMessage = message; 55 | } 56 | } 57 | } 58 | 59 | function test_filter_with_item() { 60 | listener2.dispatched("testFilterWithItem", { a:1, b:"2"}); 61 | compare(listener2.count, 1); 62 | compare(listener2.lastType,"testFilterWithItem"); 63 | compare(listener2.lastMessage, {a:1, b:"2"}); 64 | 65 | } 66 | 67 | AppListener { 68 | 69 | id: listener3 70 | property int count: 0 71 | 72 | property var messages: ([]); 73 | 74 | Filter { 75 | id: filter3 76 | types: ["action1" , "action2"] 77 | onDispatched: { 78 | listener3.count++; 79 | listener3.messages.push(type); 80 | } 81 | } 82 | } 83 | 84 | function test_types() { 85 | listener3.count = 0; 86 | AppDispatcher.dispatch("action1"); 87 | compare(listener3.count, 1); 88 | AppDispatcher.dispatch("action2"); 89 | compare(listener3.count, 2); 90 | AppDispatcher.dispatch("action3"); 91 | compare(listener3.count, 2); 92 | compare(listener3.messages, ["action1", "action2"]); 93 | } 94 | 95 | AppListener { 96 | id: listener4 97 | 98 | Filter { 99 | id: filter4 100 | 101 | Connections { 102 | // Test can a filter object hold children 103 | } 104 | } 105 | } 106 | 107 | 108 | } 109 | 110 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_hydrate.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.1 4 | 5 | Item { 6 | width: 640 7 | height: 480 8 | 9 | TestCase { 10 | name: "HydrateTests" 11 | 12 | QtObject { 13 | id: target1 14 | property int value1 : 0; 15 | property real value2 : 0; 16 | property string value3: ""; 17 | property var value4; 18 | } 19 | 20 | function test_hydrate_qtobject() { 21 | var input = { 22 | value1: 1, 23 | value2: 2.0, 24 | value3: "3", 25 | value4: { 26 | value1: 41, 27 | value2: [ 28 | {index: 0}, 29 | {index: 1}, 30 | ] 31 | } 32 | } 33 | 34 | Hydrate.rehydrate(target1, input); 35 | compare(target1.value1, 1); 36 | compare(target1.value2, 2.0); 37 | compare(target1.value3, "3"); 38 | compare(target1.value4, {value1: 41, value2:[{index:0},{index:1}]}); 39 | 40 | var data = Hydrate.dehydrate(target1); 41 | compare(JSON.stringify(data), JSON.stringify(input)); 42 | } 43 | 44 | Store { 45 | id: target2 46 | 47 | bindSource: Dispatcher { 48 | } 49 | 50 | property int value1 : 0; 51 | property real value2 : 0; 52 | property string value3: ""; 53 | 54 | property var value4 : QtObject { 55 | id: target2Child1 56 | property var value41 57 | } 58 | 59 | Item { 60 | id: dummy 61 | property int value1: 0 62 | } 63 | } 64 | 65 | function test_hydrate_store() { 66 | var input = { 67 | value1: 1, 68 | value2: 2.0, 69 | value3: "3", 70 | value4: { 71 | value41: 4 72 | } 73 | } 74 | 75 | Hydrate.rehydrate(target2, input); 76 | compare(target2.value1, 1); 77 | compare(target2.value2, 2.0); 78 | compare(target2.value3, "3"); 79 | compare(target2.value4.value41, 4); 80 | 81 | var data = Hydrate.dehydrate(target2); 82 | compare(JSON.stringify(data), JSON.stringify(input)); 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_keytable.qml: -------------------------------------------------------------------------------- 1 | // To prove that it could pass QImage via AppDispatcher 2 | import QtQuick 2.0 3 | import QtTest 1.0 4 | import QuickFlux 1.0 5 | 6 | TestCase { 7 | name : "KeyTableTests" 8 | 9 | KeyTable { 10 | id: table1 11 | 12 | property string value1; 13 | 14 | property int value2; 15 | 16 | property string value3; 17 | 18 | property string custom: "customValue"; 19 | 20 | property real value4 : 5.5; 21 | } 22 | 23 | 24 | function test_stringTable() { 25 | compare(table1.objectName, ""); 26 | compare(table1.value1 , "value1"); 27 | compare(table1.value2 , 0); 28 | compare(table1.value3 , "value3"); 29 | compare(table1.custom , "customValue"); 30 | 31 | } 32 | 33 | function test_code_generator() { 34 | 35 | table1.genHeaderFile("ActionTypes"); 36 | table1.genSourceFile("ActionTypes","actiontypes.h"); 37 | } 38 | 39 | } 40 | 41 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_middleware_exception.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.1 4 | import MessageLogger 1.0 5 | 6 | TestCase { 7 | name : "Middleware_Exception" 8 | 9 | Loader { 10 | id: loader 11 | active: false 12 | sourceComponent: MiddlewareList { 13 | id: list 14 | applyTarget: AppDispatcher 15 | 16 | Middleware { 17 | } 18 | 19 | Middleware { 20 | function dispatch(type, message) { 21 | i = 10; 22 | } 23 | } 24 | } 25 | } 26 | 27 | function init() { 28 | loader.active = true; 29 | MessageLogger.clear(); 30 | MessageLogger.install(); 31 | } 32 | 33 | function cleanup() { 34 | loader.active = false; 35 | } 36 | 37 | function test_Middleware_dispatch_should_show_exception_error() { 38 | AppDispatcher.dispatch("test_exception"); 39 | var messages = MessageLogger.messages(); 40 | compare(messages.length, 1); 41 | compare(messages[0].indexOf("Invalid write to global") >= 0, true); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_middleware_filterFunctionEnabled.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.1 4 | 5 | TestCase { 6 | name : "Middleware_FilterFunctionEnabled" 7 | 8 | ActionCreator { 9 | id: actions 10 | 11 | signal test1(); 12 | } 13 | 14 | MiddlewareList { 15 | id: middlewares 16 | 17 | Middleware { 18 | id: middleware1 19 | 20 | property var actions : new Array 21 | 22 | filterFunctionEnabled: true 23 | 24 | function test1(message) { 25 | middleware1.actions.push("test1"); 26 | next("test1", message); 27 | } 28 | 29 | function dispatch(type , message) { 30 | } 31 | } 32 | 33 | Middleware { 34 | id: middleware2 35 | 36 | function dispatch(type , message) { 37 | next(type , message); 38 | next(type , message); 39 | } 40 | } 41 | } 42 | 43 | AppListener { 44 | id: listener1 45 | property var actions : new Array 46 | 47 | onDispatched: { 48 | listener1.actions.push(type); 49 | } 50 | } 51 | 52 | function test_basic() { 53 | compare(middlewares.data.length, 2); 54 | compare(middleware1._nextCallback, undefined); // It won't set the next function until the middleware is applied 55 | 56 | middlewares.apply(actions); 57 | compare(typeof middleware1._nextCallback, "function"); 58 | compare(typeof middleware2._nextCallback, "function"); 59 | 60 | actions.test1(); 61 | 62 | compare(middleware1.actions, ["test1"]); 63 | compare(listener1.actions, ["test1","test1"]); 64 | 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_middlewarelist.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.1 4 | 5 | TestCase { 6 | name : "MiddlewareList" 7 | 8 | ActionCreator { 9 | id: actions 10 | 11 | signal test1(); 12 | } 13 | 14 | MiddlewareList { 15 | id: middlewares 16 | 17 | Middleware { 18 | id: middleware1 19 | 20 | property var actions : new Array 21 | 22 | function dispatch(type , message) { 23 | middleware1.actions.push(type); 24 | next(type , message); 25 | } 26 | } 27 | 28 | Middleware { 29 | id: middleware2 30 | 31 | function dispatch(type , message) { 32 | next(type , message); 33 | next(type , message); 34 | } 35 | } 36 | } 37 | 38 | AppListener { 39 | id: listener1 40 | property var actions : new Array 41 | 42 | onDispatched: { 43 | listener1.actions.push(type); 44 | } 45 | } 46 | 47 | function test_basic() { 48 | compare(middlewares.data.length, 2); 49 | compare(middleware1._nextCallback, undefined); // It won't set the next function until the middleware is applied 50 | 51 | middlewares.apply(actions); 52 | compare(typeof middleware1._nextCallback, "function"); 53 | compare(typeof middleware2._nextCallback, "function"); 54 | 55 | actions.test1(); 56 | 57 | compare(middleware1.actions, ["test1"]); 58 | compare(listener1.actions, ["test1","test1"]); 59 | 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_middlewarelist_applyTarget.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.1 4 | 5 | TestCase { 6 | name : "MiddlewaresTests_applyTarget" 7 | 8 | ActionCreator { 9 | id: actions1 10 | signal test1(); 11 | } 12 | 13 | ActionCreator { 14 | id: actions2 15 | 16 | function test1() { 17 | dispatch("test1"); 18 | } 19 | } 20 | 21 | MiddlewareList { 22 | id: middlewares 23 | applyTarget: actions1 24 | 25 | QtObject { 26 | id: middleware1 27 | property var next 28 | 29 | property var actions : new Array 30 | 31 | function dispatch(type , message) { 32 | middleware1.actions.push(type); 33 | next(type , message); 34 | } 35 | } 36 | } 37 | 38 | function test_basic() { 39 | actions1.test1(); 40 | compare(middleware1.actions, ["test1"]); 41 | actions2.test1(); 42 | 43 | compare(middleware1.actions, ["test1" ,"test1"]); // Both of the actions use sample dispatcher 44 | 45 | middlewares.apply(null); 46 | actions1.test1(); 47 | compare(middleware1.actions, ["test1" ,"test1"]); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_qimage.qml: -------------------------------------------------------------------------------- 1 | // To prove that it could pass QImage via AppDispatcher 2 | import QtQuick 2.0 3 | import QtTest 1.0 4 | import QuickFlux 1.0 5 | 6 | TestCase { 7 | name : "QImageTests" 8 | 9 | function test_passby_appdispatcher() { 10 | var image = TestEnv.createQImage(100,100); 11 | compare(image !== null, true); 12 | 13 | var listener1Image,listener2Image; 14 | 15 | var listener1 = AppDispatcher.addListener(function(type,message) { 16 | listener1Image = message.image; 17 | }); 18 | 19 | var listener2 = AppDispatcher.addListener(function(type,message) { 20 | listener2Image = message.image; 21 | }); 22 | 23 | AppDispatcher.dispatch("any-message",{image: image}); 24 | 25 | compare(image,listener1Image); 26 | compare(image,listener2Image); 27 | 28 | AppDispatcher.removeListener(listener1); 29 | AppDispatcher.removeListener(listener2); 30 | } 31 | 32 | AppListener { 33 | id: listener1 34 | property var image; 35 | enabled: false; 36 | onDispatched: { 37 | image = message.image; 38 | } 39 | } 40 | 41 | AppListener { 42 | id: listener2 43 | property var image; 44 | enabled: false; 45 | onDispatched: { 46 | image = message.image; 47 | } 48 | } 49 | 50 | function test_passby_applistener() { 51 | listener1.enabled = true; 52 | listener2.enabled = true; 53 | 54 | var image = TestEnv.createQImage(100,100); 55 | compare(image !== null, true); 56 | 57 | AppDispatcher.dispatch("any-message",{image: image}); 58 | 59 | compare(image,listener1.image); 60 | compare(image,listener2.image); 61 | 62 | listener1.enabled = false; 63 | listener2.enabled = false; 64 | } 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_store.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.1 4 | 5 | TestCase { 6 | name : "StoreTests" 7 | 8 | ActionCreator { 9 | id: defaultActionCreator 10 | signal test1(); 11 | } 12 | 13 | ActionCreator { 14 | id: actionCreator1 15 | dispatcher: Dispatcher { 16 | id: dispatcher1 17 | } 18 | 19 | signal test1(); 20 | } 21 | 22 | Store { 23 | id: store1 24 | property int test1: 0 25 | 26 | Filter { 27 | type: "test1" 28 | onDispatched: { 29 | store1.test1++; 30 | } 31 | } 32 | } 33 | 34 | function test_dispatch() { 35 | 36 | // Unlike AppListener, Store component do not from AppDispatcher directly. 37 | compare(store1.bindSource, null); 38 | 39 | store1.dispatch("test1"); 40 | compare(store1.test1, 1); 41 | 42 | defaultActionCreator.test1(); 43 | compare(store1.test1, 1); 44 | 45 | actionCreator1.test1(); 46 | compare(store1.test1, 1); // It is not changed 47 | 48 | store1.bind(actionCreator1); 49 | compare(store1.bindSource, actionCreator1); 50 | 51 | actionCreator1.test1(); 52 | compare(store1.test1, 2); 53 | 54 | defaultActionCreator.test1(); 55 | compare(store1.test1, 2); 56 | 57 | } 58 | 59 | 60 | Store { 61 | id: store2 62 | property int test1Count: 0 63 | property int test2Count: 0 64 | property var messages: []; 65 | 66 | function test1(message) { 67 | store2.test1Count++; 68 | messages.push(message); 69 | } 70 | 71 | function test2() { 72 | store2.test2Count++; 73 | } 74 | } 75 | 76 | function test_filterFunctionEnabled() { 77 | var m = {value: 1}; 78 | store2.dispatch("test1", m); 79 | compare(store2.test1Count, 0); 80 | store2.filterFunctionEnabled = true; 81 | store2.dispatch("test1", m); 82 | compare(store2.test1Count, 1); 83 | compare(store2.messages[0], m); 84 | 85 | store2.dispatch("test2"); 86 | compare(store2.test2Count, 1); 87 | } 88 | 89 | } 90 | 91 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_store_bridge.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.1 4 | 5 | TestCase { 6 | name : "StoreBridgeTests" 7 | 8 | property var seq: new Array; 9 | 10 | ActionCreator { 11 | id: defaultActionCreator 12 | 13 | signal test1(); 14 | } 15 | 16 | ActionCreator { 17 | id: actionCreator 18 | 19 | dispatcher: Dispatcher {} 20 | 21 | signal test1 22 | } 23 | 24 | Store { 25 | id: store1 26 | 27 | property int test1: 0 28 | 29 | Filter { 30 | type: "test1" 31 | onDispatched: { 32 | store1.test1++; 33 | seq.push("store1"); 34 | } 35 | } 36 | } 37 | 38 | Store { 39 | id: store2 40 | property int test1: 0 41 | 42 | Filter { 43 | type: "test1" 44 | onDispatched: { 45 | store2.test1++; 46 | seq.push("store2"); 47 | } 48 | } 49 | } 50 | 51 | Store { 52 | id: rootStore 53 | property int test1: 0 54 | 55 | redispatchTargets: [ 56 | store1, 57 | store2 58 | ] 59 | 60 | Filter { 61 | type: "test1" 62 | onDispatched: { 63 | rootStore.test1++; 64 | seq.push("store"); 65 | } 66 | } 67 | } 68 | 69 | function test_dispatch() { 70 | compare(store1.bindSource, null); 71 | compare(store2.bindSource, null); 72 | 73 | rootStore.dispatch("test1"); 74 | compare(rootStore.test1, 1); 75 | compare(store1.test1, 1); 76 | compare(store2.test1, 1); 77 | compare(seq,["store1", "store2", "store"]); 78 | 79 | rootStore.bind(actionCreator); 80 | 81 | defaultActionCreator.test1(); 82 | 83 | compare(rootStore.test1, 1); 84 | compare(store1.test1, 1); 85 | compare(store2.test1, 1); 86 | compare(seq,["store1", "store2", "store"]); 87 | 88 | actionCreator.test1(); 89 | 90 | compare(rootStore.test1, 2); 91 | compare(store1.test1, 2); 92 | compare(store2.test1, 2); 93 | compare(seq,["store1", "store2", "store", "store1", "store2", "store"]); 94 | 95 | } 96 | 97 | } 98 | 99 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qmltests/tst_store_children.qml: -------------------------------------------------------------------------------- 1 | import QtQuick 2.0 2 | import QtTest 1.0 3 | import QuickFlux 1.1 4 | 5 | TestCase { 6 | name : "StoreChildrenTests" 7 | 8 | property var seq: new Array; 9 | 10 | ActionCreator { 11 | id: defaultActionCreator 12 | 13 | signal test1(); 14 | } 15 | 16 | ActionCreator { 17 | id: actionCreator 18 | 19 | dispatcher: Dispatcher {} 20 | 21 | signal test1 22 | } 23 | 24 | Store { 25 | id: store 26 | property int test1: 0 27 | 28 | Store { 29 | id: child1 30 | 31 | property int test1: 0 32 | 33 | Filter { 34 | type: "test1" 35 | onDispatched: { 36 | child1.test1++; 37 | seq.push("child1"); 38 | } 39 | } 40 | } 41 | 42 | Store { 43 | id: child2 44 | property int test1: 0 45 | 46 | Filter { 47 | type: "test1" 48 | onDispatched: { 49 | child2.test1++; 50 | seq.push("child2"); 51 | } 52 | } 53 | } 54 | 55 | Filter { 56 | type: "test1" 57 | onDispatched: { 58 | store.test1++; 59 | seq.push("store"); 60 | } 61 | } 62 | } 63 | 64 | function test_dispatch() { 65 | compare(child1.bindSource, null); 66 | compare(child2.bindSource, null); 67 | 68 | store.dispatch("test1"); 69 | compare(store.test1, 1); 70 | compare(child1.test1, 1); 71 | compare(child2.test1, 1); 72 | compare(seq,["child1", "child2", "store"]); 73 | 74 | store.bind(actionCreator); 75 | 76 | defaultActionCreator.test1(); 77 | 78 | compare(store.test1, 1); 79 | compare(child1.test1, 1); 80 | compare(child2.test1, 1); 81 | compare(seq,["child1", "child2", "store"]); 82 | 83 | actionCreator.test1(); 84 | 85 | compare(store.test1, 2); 86 | compare(child1.test1, 2); 87 | compare(child2.test1, 2); 88 | compare(seq,["child1", "child2", "store", "child1", "child2", "store"]); 89 | 90 | } 91 | 92 | } 93 | 94 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/qpm.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | "com.github.benlau.testable@1.0.2.24", 4 | "com.github.benlau.qtshell@0.4.3" 5 | ] 6 | } -------------------------------------------------------------------------------- /tests/quickfluxunittests/quickfluxunittests.h: -------------------------------------------------------------------------------- 1 | #ifndef QUICKFLUXUNITTESTS_H 2 | #define QUICKFLUXUNITTESTS_H 3 | 4 | #include 5 | 6 | class QuickFluxUnitTests : public QObject 7 | { 8 | Q_OBJECT 9 | 10 | public: 11 | QuickFluxUnitTests(); 12 | 13 | signals: 14 | void dummySignal(int v1); 15 | void dummySignal(int v1,int v2); 16 | 17 | private Q_SLOTS: 18 | void initTestCase(); 19 | void cleanupTestCase(); 20 | void instance(); 21 | 22 | void singletonObject(); 23 | 24 | void signalProxy(); 25 | 26 | void dispatch_qvariant(); 27 | 28 | void keyTable(); 29 | 30 | void actionCreator_genKeyTable(); 31 | 32 | void actionCreator_changeDispatcher(); 33 | 34 | void dispatcherHook(); 35 | 36 | void loading(); 37 | void loading_data(); 38 | 39 | }; 40 | 41 | #endif // QUICKFLUXUNITTESTS_H 42 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/quickfluxunittests.pro: -------------------------------------------------------------------------------- 1 | QT += core 2 | QT -= gui 3 | 4 | CONFIG += c++11 5 | 6 | TARGET = quickfluxunittests 7 | CONFIG += console 8 | CONFIG -= app_bundle 9 | 10 | TEMPLATE = app 11 | 12 | DEFINES += SRCDIR=\\\"$$PWD/\\\" QUICK_TEST_SOURCE_DIR=\\\"$$PWD/qmltests\\\" 13 | 14 | SOURCES += main.cpp \ 15 | quickfluxunittests.cpp \ 16 | testenv.cpp \ 17 | actiontypes.cpp \ 18 | messagelogger.cpp 19 | 20 | include(vendor/vendor.pri) 21 | include(../../quickflux.pri) 22 | 23 | HEADERS += \ 24 | quickfluxunittests.h \ 25 | testenv.h \ 26 | actiontypes.h \ 27 | messagelogger.h 28 | 29 | RESOURCES += \ 30 | qml.qrc 31 | 32 | !win32 { 33 | message("Enable -Werror"); 34 | QMAKE_CXXFLAGS += -Werror 35 | } 36 | 37 | DISTFILES += \ 38 | qmltests/tst_appdispatcher_dispatch_reentrant.qml \ 39 | qmltests/tst_appdispatcher_waitfor.qml \ 40 | qmltests/tst_appdispatcher.qml \ 41 | qmltests/tst_applistener_alwayson.qml \ 42 | qmltests/tst_applistener_filter.qml \ 43 | qmltests/tst_applistener_waitfor.qml \ 44 | qmltests/tst_applistener.qml \ 45 | qmltests/tst_applistenergroup.qml \ 46 | qmltests/tst_appscript.qml \ 47 | qmltests/tst_appscriptgroup.qml \ 48 | qmltests/tst_filter.qml \ 49 | qmltests/tst_keytable.qml \ 50 | qmltests/tst_qimage.qml \ 51 | qmltests/tst_actioncreator.qml \ 52 | qmltests/tst_store.qml \ 53 | qmltests/tst_store_children.qml \ 54 | qmltests/tst_hydrate.qml \ 55 | qmltests/tst_store_bridge.qml \ 56 | qpm.json \ 57 | qmltests/tst_middleware_filterFunctionEnabled.qml \ 58 | qmltests/tst_middlewarelist.qml \ 59 | qmltests/tst_middlewarelist_applyTarget.qml \ 60 | ../../appveyor.yml \ 61 | qmltests/tst_middleware_exception.qml 62 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/testenv.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "testenv.h" 3 | 4 | TestEnv::TestEnv(QObject *parent) : QObject(parent) 5 | { 6 | 7 | } 8 | 9 | QImage TestEnv::createQImage(int width, int height) 10 | { 11 | return QImage(width,height,QImage::Format_RGB32); 12 | } 13 | 14 | 15 | static QObject *provider(QQmlEngine *engine, QJSEngine *scriptEngine) { 16 | Q_UNUSED(engine); 17 | Q_UNUSED(scriptEngine); 18 | 19 | TestEnv* object = new TestEnv(); 20 | 21 | return object; 22 | } 23 | 24 | class TestEnvRegisterHelper { 25 | 26 | public: 27 | TestEnvRegisterHelper() { 28 | qmlRegisterSingletonType("QuickFlux", 1, 0, "TestEnv", provider); 29 | } 30 | }; 31 | 32 | static TestEnvRegisterHelper registerHelper; 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/quickfluxunittests/testenv.h: -------------------------------------------------------------------------------- 1 | #ifndef TESTENV_H 2 | #define TESTENV_H 3 | 4 | #include 5 | #include 6 | 7 | class TestEnv : public QObject 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit TestEnv(QObject *parent = 0); 12 | 13 | signals: 14 | 15 | public slots: 16 | QImage createQImage(int width,int height); 17 | }; 18 | 19 | #endif // TESTENV_H 20 | --------------------------------------------------------------------------------