├── .clang-format ├── .cmake-format.py ├── .codespellrc ├── .github └── workflows │ ├── build.yml │ ├── documentation.yml │ └── static_checks.yml ├── .gitignore ├── .krazy ├── .markdownlint.json ├── .pre-commit-config.yaml ├── CMakeLists.txt ├── CMakePresets.json ├── CMakeSettings.json ├── CMakeUserPresets-example.json ├── ChangeLog ├── LICENSE.txt ├── LICENSES ├── BSD-3-Clause.txt └── MIT.txt ├── README.md ├── REUSE.toml ├── cmake ├── ECM │ └── modules │ │ ├── ECMSetupVersion.cmake │ │ ├── ECMUninstallTarget.cmake │ │ ├── ECMVersionHeader.h.in │ │ └── ecm_uninstall.cmake.in ├── InstallLocation.cmake └── KDBindingsConfig.cmake.in ├── docs ├── CMakeLists.txt └── api │ ├── CMakeLists.txt │ ├── Doxyfile.cmake │ ├── DoxygenLayout.xml │ ├── docs │ ├── getting-started │ │ ├── data-binding.md │ │ ├── index.md │ │ ├── properties.md │ │ └── signals-slots.md │ └── license.md │ ├── doxygen-awesome.css │ ├── footer.html │ ├── kdab-logo-16x16.png │ └── kdab-logo-22x22.png ├── examples ├── 01-simple-connection │ ├── CMakeLists.txt │ └── main.cpp ├── 02-signal-member │ ├── CMakeLists.txt │ └── main.cpp ├── 03-member-arguments │ ├── CMakeLists.txt │ └── main.cpp ├── 04-simple-property │ ├── CMakeLists.txt │ └── main.cpp ├── 05-property-bindings │ ├── CMakeLists.txt │ └── main.cpp ├── 06-lazy-property-bindings │ ├── CMakeLists.txt │ └── main.cpp ├── 07-advanced-connections │ ├── CMakeLists.txt │ └── main.cpp ├── 08-managing-connections │ ├── CMakeLists.txt │ └── main.cpp └── CMakeLists.txt ├── src └── kdbindings │ ├── CMakeLists.txt │ ├── KDBindingsConfig.h │ ├── binding.h │ ├── binding_evaluator.h │ ├── connection_evaluator.h │ ├── connection_handle.h │ ├── genindex_array.h │ ├── make_node.h │ ├── node.h │ ├── node_functions.h │ ├── node_operators.h │ ├── property.h │ ├── property_updater.h │ ├── signal.h │ └── utils.h └── tests ├── CMakeLists.txt ├── binding ├── CMakeLists.txt └── tst_binding.cpp ├── doctest └── doctest.h ├── node ├── CMakeLists.txt └── tst_node.cpp ├── property ├── CMakeLists.txt └── tst_property.cpp ├── signal ├── CMakeLists.txt └── tst_signal.cpp └── utils ├── CMakeLists.txt ├── tst_gen_index_array.cpp ├── tst_get_arity.cpp └── tst_utils_main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: WebKit 3 | Standard: Cpp11 4 | SortIncludes: false 5 | 6 | ColumnLimit: 0 7 | ContinuationIndentWidth: 8 8 | 9 | FixNamespaceComments: true 10 | NamespaceIndentation: None 11 | AlignAfterOpenBracket: true 12 | 13 | PointerBindsToType: false 14 | SpaceAfterTemplateKeyword: false 15 | 16 | AllowShortFunctionsOnASingleLine: Inline 17 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 18 | AlwaysBreakTemplateDeclarations: true 19 | BreakBeforeBinaryOperators: None 20 | 21 | SpaceBeforeCpp11BracedList: false 22 | BreakBeforeBraces: Custom 23 | BraceWrapping: 24 | AfterClass: true 25 | AfterControlStatement: false 26 | AfterEnum: false 27 | AfterFunction: true 28 | AfterNamespace: false 29 | AfterObjCDeclaration: false 30 | AfterStruct: false 31 | AfterUnion: false 32 | BeforeCatch: false 33 | BeforeElse: false 34 | IndentBraces: false 35 | 36 | CommentPragmas: '/\*(.+\n.+)+\*/' 37 | -------------------------------------------------------------------------------- /.cmake-format.py: -------------------------------------------------------------------------------- 1 | # ---------------------------------- 2 | # Options affecting listfile parsing 3 | # ---------------------------------- 4 | with section("parse"): 5 | 6 | # Specify structure for custom cmake functions 7 | additional_commands = {'foo': {'flags': ['BAR', 'BAZ'], 8 | 'kwargs': {'DEPENDS': '*', 'HEADERS': '*', 'SOURCES': '*'}}} 9 | 10 | # Override configurations per-command where available 11 | override_spec = {} 12 | 13 | # Specify variable tags. 14 | vartags = [] 15 | 16 | # Specify property tags. 17 | proptags = [] 18 | 19 | # ----------------------------- 20 | # Options affecting formatting. 21 | # ----------------------------- 22 | with section("format"): 23 | 24 | # Disable formatting entirely, making cmake-format a no-op 25 | disable = False 26 | 27 | # How wide to allow formatted cmake files 28 | line_width = 120 29 | 30 | # How many spaces to tab for indent 31 | tab_size = 2 32 | 33 | # If true, lines are indented using tab characters (utf-8 0x09) instead of 34 | # space characters (utf-8 0x20). In cases where the layout would 35 | # require a fractional tab character, the behavior of the fractional 36 | # indentation is governed by 37 | use_tabchars = False 38 | 39 | # If is True, then the value of this variable indicates how 40 | # fractional indentions are handled during whitespace replacement. If set to 41 | # 'use-space', fractional indentation is left as spaces (utf-8 0x20). If set 42 | # to `round-up` fractional indentation is replaced with a single tab character 43 | # (utf-8 0x09) effectively shifting the column to the next tabstop 44 | fractional_tab_policy = 'use-space' 45 | 46 | # If an argument group contains more than this many sub-groups (parg or kwarg 47 | # groups) then force it to a vertical layout. 48 | max_subgroups_hwrap = 2 49 | 50 | # If a positional argument group contains more than this many arguments, then 51 | # force it to a vertical layout. 52 | max_pargs_hwrap = 6 53 | 54 | # If a cmdline positional group consumes more than this many lines without 55 | # nesting, then invalidate the layout (and nest) 56 | max_rows_cmdline = 2 57 | 58 | # If true, separate flow control names from their parentheses with a space 59 | separate_ctrl_name_with_space = False 60 | 61 | # If true, separate function names from parentheses with a space 62 | separate_fn_name_with_space = False 63 | 64 | # If a statement is wrapped to more than one line, than dangle the closing 65 | # parenthesis on its own line. 66 | dangle_parens = True 67 | 68 | # If the trailing parenthesis must be 'dangled' on its on line, then align it 69 | # to this reference: `prefix`: the start of the statement, `prefix-indent`: 70 | # the start of the statement, plus one indentation level, `child`: align to 71 | # the column of the arguments 72 | dangle_align = 'prefix' 73 | 74 | # If the statement spelling length (including space and parenthesis) is 75 | # smaller than this amount, then force reject nested layouts. 76 | min_prefix_chars = 4 77 | 78 | # If the statement spelling length (including space and parenthesis) is larger 79 | # than the tab width by more than this amount, then force reject un-nested 80 | # layouts. 81 | max_prefix_chars = 10 82 | 83 | # If a candidate layout is wrapped horizontally but it exceeds this many 84 | # lines, then reject the layout. 85 | max_lines_hwrap = 2 86 | 87 | # What style line endings to use in the output. 88 | line_ending = 'unix' 89 | 90 | # Format command names consistently as 'lower' or 'upper' case 91 | command_case = 'lower' 92 | 93 | # Format keywords consistently as 'lower' or 'upper' case 94 | keyword_case = 'upper' 95 | 96 | # A list of command names which should always be wrapped 97 | always_wrap = [] 98 | 99 | # If true, the argument lists which are known to be sortable will be sorted 100 | # lexicographicall 101 | enable_sort = True 102 | 103 | # If true, the parsers may infer whether or not an argument list is sortable 104 | # (without annotation). 105 | autosort = False 106 | 107 | # By default, if cmake-format cannot successfully fit everything into the 108 | # desired linewidth it will apply the last, most agressive attempt that it 109 | # made. If this flag is True, however, cmake-format will print error, exit 110 | # with non-zero status code, and write-out nothing 111 | require_valid_layout = False 112 | 113 | # A dictionary mapping layout nodes to a list of wrap decisions. See the 114 | # documentation for more information. 115 | layout_passes = {} 116 | 117 | # ------------------------------------------------ 118 | # Options affecting comment reflow and formatting. 119 | # ------------------------------------------------ 120 | with section("markup"): 121 | 122 | # What character to use for bulleted lists 123 | bullet_char = '*' 124 | 125 | # What character to use as punctuation after numerals in an enumerated list 126 | enum_char = '.' 127 | 128 | # If comment markup is enabled, don't reflow the first comment block in each 129 | # listfile. Use this to preserve formatting of your copyright/license 130 | # statements. 131 | first_comment_is_literal = False 132 | 133 | # If comment markup is enabled, don't reflow any comment block which matches 134 | # this (regex) pattern. Default is `None` (disabled). 135 | literal_comment_pattern = None 136 | 137 | # Regular expression to match preformat fences in comments default= 138 | # ``r'^\s*([`~]{3}[`~]*)(.*)$'`` 139 | fence_pattern = '^\\s*([`~]{3}[`~]*)(.*)$' 140 | 141 | # Regular expression to match rulers in comments default= 142 | # ``r'^\s*[^\w\s]{3}.*[^\w\s]{3}$'`` 143 | ruler_pattern = '^\\s*[^\\w\\s]{3}.*[^\\w\\s]{3}$' 144 | 145 | # If a comment line matches starts with this pattern then it is explicitly a 146 | # trailing comment for the preceeding argument. Default is '#<' 147 | explicit_trailing_pattern = '#<' 148 | 149 | # If a comment line starts with at least this many consecutive hash 150 | # characters, then don't lstrip() them off. This allows for lazy hash rulers 151 | # where the first hash char is not separated by space 152 | hashruler_min_length = 10 153 | 154 | # If true, then insert a space between the first hash char and remaining hash 155 | # chars in a hash ruler, and normalize its length to fill the column 156 | canonicalize_hashrulers = True 157 | 158 | # enable comment markup parsing and reflow 159 | enable_markup = False 160 | 161 | # ---------------------------- 162 | # Options affecting the linter 163 | # ---------------------------- 164 | with section("lint"): 165 | 166 | # a list of lint codes to disable 167 | disabled_codes = [] 168 | 169 | # regular expression pattern describing valid function names 170 | function_pattern = '[0-9A-Za-z_]+' 171 | 172 | # regular expression pattern describing valid macro names 173 | macro_pattern = '[0-9a-z_]+' 174 | 175 | # regular expression pattern describing valid names for variables with global 176 | # (cache) scope 177 | global_var_pattern = '[A-Z][0-9A-Z_]+' 178 | 179 | # regular expression pattern describing valid names for variables with global 180 | # scope (but internal semantic) 181 | internal_var_pattern = '[A-Z][0-9A-Z_]+' 182 | 183 | # regular expression pattern describing valid names for variables with local 184 | # scope 185 | local_var_pattern = '[_A-Za-z][A-Za-z0-9_]+' 186 | 187 | # regular expression pattern describing valid names for privatedirectory 188 | # variables 189 | private_var_pattern = '[0-9a-z_]+' 190 | 191 | # regular expression pattern describing valid names for public directory 192 | # variables 193 | public_var_pattern = '.*' 194 | 195 | # regular expression pattern describing valid names for function/macro 196 | # arguments and loop variables. 197 | argument_var_pattern = '[a-z_][a-z0-9_]+' 198 | 199 | # regular expression pattern describing valid names for keywords used in 200 | # functions or macros 201 | keyword_pattern = '[A-Z][0-9A-Z_]+' 202 | 203 | # In the heuristic for C0201, how many conditionals to match within a loop in 204 | # before considering the loop a parser. 205 | max_conditionals_custom_parser = 2 206 | 207 | # Require at least this many newlines between statements 208 | min_statement_spacing = 1 209 | 210 | # Require no more than this many newlines between statements 211 | max_statement_spacing = 2 212 | max_returns = 6 213 | max_branches = 15 214 | max_arguments = 10 215 | max_localvars = 15 216 | max_statements = 50 217 | 218 | # ------------------------------- 219 | # Options affecting file encoding 220 | # ------------------------------- 221 | with section("encode"): 222 | 223 | # If true, emit the unicode byte-order mark (BOM) at the start of the file 224 | emit_byteorder_mark = False 225 | 226 | # Specify the encoding of the input file. Defaults to utf-8 227 | input_encoding = 'utf-8' 228 | 229 | # Specify the encoding of the output file. Defaults to utf-8. Note that cmake 230 | # only claims to support utf-8 so be careful when using anything else 231 | output_encoding = 'utf-8' 232 | 233 | # ------------------------------------- 234 | # Miscellaneous configurations options. 235 | # ------------------------------------- 236 | with section("misc"): 237 | 238 | # A dictionary containing any per-command configuration overrides. Currently 239 | # only `command_case` is supported. 240 | per_command = {} 241 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | skip = *.ts,./build-*,.git 3 | interactive = 3 4 | ignore-words-list = seh 5 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: CI 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | build: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: true 20 | matrix: 21 | os: 22 | - ubuntu-latest 23 | - windows-latest 24 | - macos-latest 25 | 26 | steps: 27 | - name: Checkout sources 28 | uses: actions/checkout@v4 29 | 30 | - name: Install ninja-build tool 31 | uses: aseprite/get-ninja@main 32 | 33 | - name: Make sure MSVC is found when Ninja generator is in use 34 | uses: ilammy/msvc-dev-cmd@v1 35 | if: ${{ runner.os == 'Windows' }} 36 | 37 | - name: Configure project 38 | run: cmake --preset=ci 39 | 40 | - name: Build Project 41 | run: cmake --build --preset=ci 42 | 43 | - name: Run tests 44 | run: ctest --preset=ci 45 | 46 | - name: Read tests log when it fails 47 | uses: andstor/file-reader-action@v1 48 | if: ${{ failure() }} 49 | with: 50 | path: "./build/Testing/Temporary/LastTest.log" 51 | -------------------------------------------------------------------------------- /.github/workflows/documentation.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: Deploy doxygen to GitHub Pages 6 | 7 | on: 8 | workflow_dispatch: 9 | push: 10 | branches: 11 | - main 12 | 13 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 14 | permissions: 15 | contents: read 16 | pages: write 17 | id-token: write 18 | 19 | # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. 20 | # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. 21 | concurrency: 22 | group: "pages" 23 | cancel-in-progress: false 24 | 25 | jobs: 26 | build: 27 | runs-on: ubuntu-latest 28 | 29 | steps: 30 | - name: Install Dependencies on Linux 31 | run: | 32 | sudo apt update -qq 33 | sudo apt install -y doxygen 34 | 35 | - name: Checkout sources 36 | uses: actions/checkout@v4 37 | with: 38 | submodules: recursive 39 | 40 | - name: Install ninja-build tool 41 | uses: aseprite/get-ninja@main 42 | 43 | - name: Configure project 44 | run: cmake -S . --preset=docs 45 | 46 | - name: Create docs 47 | run: cmake --build --preset=docs 48 | 49 | - name: Upload artifact 50 | uses: actions/upload-pages-artifact@v3 51 | with: 52 | path: build-docs/docs/api/html/ 53 | 54 | # Deployment job, what was uploaded to artifact 55 | deploy: 56 | needs: build 57 | runs-on: ubuntu-latest 58 | environment: 59 | name: github-pages 60 | url: ${{ steps.deployment.outputs.page_url }} 61 | 62 | steps: 63 | - name: Deploy to GitHub Pages 64 | id: deployment 65 | uses: actions/deploy-pages@v4 66 | -------------------------------------------------------------------------------- /.github/workflows/static_checks.yml: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company 2 | # 3 | # SPDX-License-Identifier: MIT 4 | 5 | name: CI Static Checks 6 | 7 | on: 8 | push: 9 | branches: 10 | - main 11 | pull_request: 12 | branches: 13 | - main 14 | 15 | jobs: 16 | build: 17 | runs-on: ${{ matrix.os }} 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | os: 22 | - ubuntu-latest 23 | 24 | config: 25 | - name: clang-tidy 26 | 27 | - name: clazy 28 | apt_pgks: 29 | - clazy cppcheck 30 | 31 | steps: 32 | - name: Install ninja-build tool 33 | uses: turtlesec-no/get-ninja@main 34 | 35 | - name: Install dependencies on Ubuntu (${{ join(matrix.config.apt_pgks, ' ') }}) 36 | if: ${{ runner.os == 'Linux' && matrix.config.apt_pgks }} 37 | run: | 38 | sudo apt update -qq 39 | echo ${{ join(matrix.config.apt_pgks, ' ') }} | xargs sudo apt install -y 40 | 41 | - uses: actions/checkout@v4 42 | 43 | - name: Fetch Git submodules 44 | run: git submodule update --init --recursive 45 | 46 | - name: Configure project 47 | run: cmake --preset=${{ matrix.config.name }} 48 | 49 | - name: Build Project 50 | id: ctest 51 | run: cmake --build --preset=${{ matrix.config.name }} 52 | 53 | - name: Run cppcheck 54 | if: ${{ matrix.config.name == 'clazy' }} 55 | run: cmake --build --preset=${{ matrix.config.name }} --target cppcheck 56 | -------------------------------------------------------------------------------- /.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 | CMakeLists.txt.user 75 | CMakeLists.txt.user.* 76 | CMakeUserPresets.json 77 | *.spv 78 | .vs/* 79 | imgui.ini 80 | 81 | .vscode/* 82 | build/* 83 | build-* 84 | serenity_metatype.* 85 | output.json 86 | 87 | # clangd 88 | compile_commands.json 89 | .cache/ 90 | -------------------------------------------------------------------------------- /.krazy: -------------------------------------------------------------------------------- 1 | CHECKSETS c++ 2 | 3 | #KDAB-specific checks 4 | EXTRA kdabcopyright-reuse,kdabcontactus,fosslicense-reuse 5 | 6 | #additional checks 7 | #EXTRA defines,null 8 | 9 | #exclude checks now being done by clazy or clang-tools 10 | EXCLUDE strings,explicit,normalize,passbyvalue,operators,nullstrcompare,nullstrassign,doublequote_chars,qobject,sigsandslots,staticobjects,dpointer,inline,postfixop 11 | #exclude more checks 12 | EXCLUDE style 13 | 14 | #if you have a build subdir, skip it 15 | SKIP /build- 16 | #skip 3rdparty 17 | SKIP /genindex_array.c|/genindex_array.h 18 | #skip generated files 19 | SKIP /tests/doctest/doctest.h 20 | #skip CMake files 21 | SKIP Doxyfile.cmake 22 | SKIP /mkdocs.yml.cmake 23 | SKIP /KDBindingsConfig.cmake.in 24 | #skip more files 25 | SKIP CMakePresets.json 26 | SKIP \.cmake-format\.py 27 | #skip the borrowed code in the cmake subdir 28 | SKIP /cmake/ECM/|/cmake/InstallLocation.cmake 29 | -------------------------------------------------------------------------------- /.markdownlint.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": true, 3 | "MD007": { 4 | "indent": 2, 5 | "start_indented": false 6 | }, 7 | "MD013": { 8 | "line_length": 100, 9 | "tables": false, 10 | "code_blocks": false 11 | }, 12 | "MD029": { 13 | "style": "ordered" 14 | }, 15 | "MD033": false 16 | } 17 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | ci: 4 | autoupdate_schedule: monthly 5 | 6 | exclude: ^(cmake/|3rdparty/|docs/api/doxygen-awesome.css) 7 | repos: 8 | - repo: https://github.com/pre-commit/pre-commit-hooks 9 | rev: v4.5.0 10 | hooks: 11 | - id: trailing-whitespace 12 | - id: end-of-file-fixer 13 | - id: check-added-large-files 14 | - id: check-case-conflict 15 | - repo: https://github.com/pre-commit/mirrors-clang-format 16 | rev: v17.0.4 17 | hooks: 18 | - id: clang-format 19 | exclude: (.json) 20 | - repo: https://github.com/codespell-project/codespell 21 | rev: v2.2.6 22 | hooks: 23 | - id: codespell 24 | - repo: https://github.com/cheshirekow/cmake-format-precommit 25 | rev: v0.6.13 26 | hooks: 27 | - id: cmake-lint 28 | exclude: (.py.cmake|Doxyfile.cmake) 29 | - id: cmake-format 30 | exclude: (.py.cmake|Doxyfile.cmake) 31 | - repo: https://github.com/fsfe/reuse-tool 32 | rev: v3.1.0a1 33 | hooks: 34 | - id: reuse 35 | args: [--suppress-deprecation] 36 | - repo: https://github.com/DavidAnson/markdownlint-cli2 37 | rev: v0.12.0 38 | hooks: 39 | - id: markdownlint-cli2 40 | files: \.(md|mdown|markdown)$ 41 | exclude: (docs/api/docs/license.md) 42 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | # This is the top-level CMakeLists.txt file for the KDBindings project. 12 | # 13 | # Pass the following variables to cmake to control the build: 14 | # (See INSTALL.txt for more information) 15 | # 16 | # -DKDBindings_TESTS=[true|false] 17 | # Build the test harness. 18 | # Default=true 19 | # 20 | # -DKDBindings_EXAMPLES=[true|false] 21 | # Build the examples. 22 | # Default=true 23 | # 24 | # -DKDBindings_DOCS=[true|false] 25 | # Build the API documentation. Enables the 'docs' build target. 26 | # Default=false 27 | # 28 | 29 | cmake_minimum_required(VERSION 3.12) # for `project(... HOMEPAGE_URL ...)` 30 | 31 | project( 32 | KDBindings 33 | DESCRIPTION "Bindings, from the comfort and speed of C++ and without Qt" 34 | LANGUAGES CXX 35 | VERSION 1.0.95 36 | HOMEPAGE_URL "https://github.com/KDAB/KDBindings" 37 | ) 38 | 39 | include(FeatureSummary) 40 | 41 | option(${PROJECT_NAME}_TESTS "Build the tests" ON) 42 | option(${PROJECT_NAME}_EXAMPLES "Build the examples" ON) 43 | option(${PROJECT_NAME}_DOCS "Build the API documentation" OFF) 44 | option(${PROJECT_NAME}_ENABLE_WARN_UNUSED "Enable warnings for unused ConnectionHandles" ON) 45 | option(${PROJECT_NAME}_ERROR_ON_WARNING "Enable all compiler warnings and treat them as errors" OFF) 46 | option(${PROJECT_NAME}_QT_NO_EMIT "Qt Compatibility: Disable Qt's `emit` keyword" OFF) 47 | 48 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) 49 | list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/ECM/modules) 50 | 51 | # Set a default build type if none was specified 52 | set(default_build_type "Release") 53 | if(EXISTS "${CMAKE_SOURCE_DIR}/.git") 54 | set(default_build_type "Debug") 55 | endif() 56 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 57 | message(STATUS "Setting build type to ${default_build_type} as none was specified.") 58 | set(CMAKE_BUILD_TYPE 59 | "${default_build_type}" 60 | CACHE STRING "Choose the type of build." FORCE 61 | ) 62 | # Set the possible values of build type for cmake-gui 63 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") 64 | endif() 65 | 66 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 67 | 68 | set(CMAKE_CXX_STANDARD 17) 69 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 70 | 71 | # setup default install locations 72 | include(InstallLocation) 73 | 74 | if(CMAKE_SOURCE_DIR STREQUAL PROJECT_SOURCE_DIR) 75 | set(${PROJECT_NAME}_IS_ROOT_PROJECT TRUE) 76 | 77 | message(STATUS "Building ${PROJECT_NAME} ${${PROJECT_NAME}_VERSION} in ${CMAKE_BUILD_TYPE} mode.") 78 | 79 | install(FILES README.md DESTINATION ${INSTALL_DOC_DIR}) 80 | install(DIRECTORY LICENSES DESTINATION ${INSTALL_DOC_DIR}) 81 | else() 82 | #Always disable tests, examples, docs when used as a submodule 83 | set(${PROJECT_NAME}_IS_ROOT_PROJECT FALSE) 84 | set(${PROJECT_NAME}_TESTS FALSE) 85 | set(${PROJECT_NAME}_EXAMPLES FALSE) 86 | set(${PROJECT_NAME}_DOCS FALSE) 87 | endif() 88 | 89 | if(${PROJECT_NAME}_TESTS) 90 | enable_testing() 91 | endif() 92 | 93 | add_subdirectory(src/kdbindings) 94 | 95 | if(${PROJECT_NAME}_TESTS) 96 | add_subdirectory(tests) 97 | endif() 98 | if(${PROJECT_NAME}_EXAMPLES) 99 | add_subdirectory(examples) 100 | endif() 101 | 102 | if(${PROJECT_NAME}_DOCS) 103 | add_subdirectory(docs) # needs to go last, in case there are build source files 104 | endif() 105 | 106 | if(${PROJECT_NAME}_IS_ROOT_PROJECT) 107 | # Add uninstall target (not for submodules since parent projects typically have uninstall too) 108 | include(ECMUninstallTarget) 109 | endif() 110 | 111 | feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) 112 | 113 | # TODO once cppcheck is passing change --error-exitcode=1 so CI step fails on new errors 114 | add_custom_target( 115 | cppcheck 116 | COMMENT "Run cppcheck on sources" 117 | USES_TERMINAL 118 | WORKING_DIRECTORY ${PROJECT_BINARY_DIR} 119 | COMMAND 120 | ${CMAKE_COMMAND} -E env cppcheck --project=${PROJECT_BINARY_DIR}/compile_commands.json --enable=all 121 | --error-exitcode=0 --language=c++ --inline-suppr --quiet --disable=missingInclude,unusedFunction 122 | --check-level=exhaustive --library=qt.cfg -i3rdParty/ --suppress=*:*.moc --suppress=*:*moc_*.cpp -i/cmake/ECM/ 123 | -i/cmake/KDAB/ 124 | ) 125 | -------------------------------------------------------------------------------- /CMakePresets.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "configurePresets": [ 4 | { 5 | "name": "dev", 6 | "displayName": "dev", 7 | "generator": "Ninja", 8 | "binaryDir": "${sourceDir}/build-dev", 9 | "cacheVariables": { 10 | "CMAKE_BUILD_TYPE": "Debug", 11 | "CMAKE_EXPORT_COMPILE_COMMANDS" : "ON", 12 | "KDBindings_TESTS" : "ON", 13 | "KDBindings_DOCS" : "ON", 14 | "KDBindings_EXAMPLES" : "ON", 15 | "KDBindings_ERROR_ON_WARNING": "ON" 16 | } 17 | }, 18 | { 19 | "name": "ci", 20 | "displayName": "ci", 21 | "generator": "Ninja", 22 | "binaryDir": "${sourceDir}/build-ci", 23 | "cacheVariables": { 24 | "CMAKE_BUILD_TYPE": "Release", 25 | "CMAKE_EXPORT_COMPILE_COMMANDS" : "ON", 26 | "KDBindings_TESTS" : "ON", 27 | "KDBindings_EXAMPLES" : "ON", 28 | "KDBindings_DOCS" : "ON", 29 | "KDBindings_ERROR_ON_WARNING": "ON" 30 | } 31 | }, 32 | { 33 | "name": "clazy", 34 | "displayName": "clazy", 35 | "generator": "Ninja", 36 | "binaryDir": "${sourceDir}/build-clazy", 37 | "cacheVariables": { 38 | "CMAKE_BUILD_TYPE": "Debug", 39 | "CMAKE_CXX_COMPILER" : "clazy", 40 | "CMAKE_EXPORT_COMPILE_COMMANDS" : "ON", 41 | "KDBindings_TESTS" : "ON", 42 | "KDBindings_EXAMPLES" : "ON", 43 | "KDBindings_ERROR_ON_WARNING" : "ON" 44 | }, 45 | "warnings": { 46 | "uninitialized": true 47 | }, 48 | "errors": { 49 | "dev": true 50 | } 51 | }, 52 | { 53 | "name": "clang-tidy", 54 | "displayName": "clang-tidy", 55 | "generator": "Ninja", 56 | "binaryDir": "${sourceDir}/build-clang-tidy", 57 | "cacheVariables": { 58 | "CMAKE_BUILD_TYPE": "Debug", 59 | "CMAKE_CXX_CLANG_TIDY" : "clang-tidy", 60 | "KDBindings_TESTS" : "ON", 61 | "KDBindings_EXAMPLES" : "ON" 62 | }, 63 | "warnings": { 64 | "uninitialized": true 65 | }, 66 | "errors": { 67 | "dev": true 68 | } 69 | }, 70 | { 71 | "name": "docs", 72 | "displayName": "docs", 73 | "generator": "Ninja", 74 | "binaryDir": "${sourceDir}/build-docs", 75 | "cacheVariables": { 76 | "KDBindings_DOCS" : "ON" 77 | } 78 | } 79 | 80 | ], 81 | "buildPresets": [ 82 | { 83 | "name": "ci", 84 | "configurePreset": "ci" 85 | }, 86 | { 87 | "name": "dev", 88 | "configurePreset": "dev" 89 | }, 90 | { 91 | "name": "clazy", 92 | "configurePreset": "clazy" 93 | }, 94 | { 95 | "name": "clang-tidy", 96 | "configurePreset": "clang-tidy" 97 | }, 98 | { 99 | "name": "docs", 100 | "configurePreset": "docs", 101 | "targets": ["docs"] 102 | } 103 | ], 104 | "testPresets": [ 105 | { 106 | "name": "ci", 107 | "configurePreset": "ci", 108 | "output": {"outputOnFailure": true}, 109 | "execution": {"noTestsAction": "error", "stopOnFailure": true} 110 | }, 111 | { 112 | "name": "dev", 113 | "configurePreset": "dev", 114 | "output": {"outputOnFailure": true}, 115 | "execution": {"noTestsAction": "error", "stopOnFailure": false} 116 | }, 117 | { 118 | "name": "clazy", 119 | "configurePreset": "clazy", 120 | "output": {"outputOnFailure": true}, 121 | "execution": {"noTestsAction": "error", "stopOnFailure": false} 122 | } 123 | ] 124 | } 125 | -------------------------------------------------------------------------------- /CMakeSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "x64-Debug", 5 | "generator": "Ninja", 6 | "configurationType": "Debug", 7 | "inheritEnvironments": [ 8 | "msvc_x64_x64" 9 | ], 10 | "buildRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\build\\${name}", 11 | "installRoot": "${env.USERPROFILE}\\CMakeBuilds\\${workspaceHash}\\install\\${name}", 12 | "cmakeCommandArgs": "", 13 | "buildCommandArgs": "-v", 14 | "ctestCommandArgs": "" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /CMakeUserPresets-example.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 3, 3 | "configurePresets": [ 4 | { 5 | "name": "msvc_x64-dev", 6 | "displayName": "dev (msvc_x64)", 7 | "inherits": ["dev"], 8 | "architecture": { 9 | "value": "x64", 10 | "strategy": "external" 11 | }, 12 | "toolset": { 13 | "value": "host=x64", 14 | "strategy": "external" 15 | }, 16 | "cacheVariables": { 17 | "CMAKE_C_COMPILER": "cl", 18 | "CMAKE_CXX_COMPILER": "cl" 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | * v1.1.0 (unreleased) 2 | - Feature: ConnectionEvaluator for deferred Signal/Slot evaluation and easy integration into multi-threaded environments (#48) 3 | - Feature: Add ScopedConnection for RAII-style connection management (#31) 4 | 5 | * v1.0.4 6 | - Avoid error in presence of Windows min/max macros (#63) 7 | - Fix Clang-Tidy failures (#49) 8 | 9 | * v1.0.3 10 | - Fix for Windows compilation error due to `interface` keyword (#40) 11 | 12 | * v1.0.2 13 | - Make KDBINDINGS_DECLARE_FUNCTION callable outside of KDBindings namespace (#38) 14 | 15 | * v1.0.1 16 | - Property: Make moved Signal private (#24 & #26) - BREAKING! 17 | - Documentation: Small fixes & updates 18 | 19 | * v1.0.0 20 | - Initial Release 21 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | License 2 | ======= 3 | KDBindings is Copyright 2025 Klarälvdalens Datakonsult AB (KDAB), 4 | and is available under the terms of the MIT license. 5 | 6 | See REUSE.toml for licenses of included 3rdparty code. 7 | 8 | See the full license text in the LICENSES folder. 9 | -------------------------------------------------------------------------------- /LICENSES/BSD-3-Clause.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) . All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 4 | 5 | 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 6 | 7 | 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 8 | 9 | 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 10 | 11 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 12 | -------------------------------------------------------------------------------- /LICENSES/MIT.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KDBindings 2 | 3 | Reactive programming & data binding in C++ 4 | 5 | From plain C++ you get: 6 | 7 | * Signals + Slots. 8 | * Properties templated on the contained type. 9 | * Property bindings allowing reactive code to be written without having to do all the low-level, 10 | error prone plumbing by hand. 11 | * Lazy evaluation of property bindings. 12 | * No more broken bindings. 13 | * Totally stand-alone "header-only" library. No heavy Qt dependency. 14 | * Can still be used with Qt if you so wish. 15 | 16 | ## Using KDBindings 17 | 18 | KDBindings requires a C++ compiler with C++17 support. 19 | 20 | Find more information at: 21 | 22 | * [detailed browsable API reference](https://kdab.github.io/KDBindings/md__home_runner_work_KDBindings_KDBindings_docs_api_docs_getting_started_index.html) 23 | * [our assorted example programs](https://github.com/KDAB/KDBindings/blob/main/examples) 24 | 25 | ## Compatibility with Qt 26 | 27 | Because Qt defines `emit` as a keyword, Qt is by default incompatible with KDBindings, which 28 | uses `emit` as a function name. 29 | 30 | This can only be fixed by defining `QT_NO_EMIT` or `QT_NO_KEYWORDS` and simply omitting 31 | `emit` in your Qt code. 32 | 33 | To make this easy, KDBindings includes a CMake option: `KDBindings_QT_NO_EMIT`, which defines this 34 | for all dependents of KDBindings. 35 | 36 | See also: [#79](https://github.com/KDAB/KDBindings/issues/79) 37 | 38 | ## Contact 39 | 40 | * Visit us on GitHub: 41 | * Email for questions about copyright, licensing or commercial support. 42 | 43 | Stay up-to-date with KDAB product announcements: 44 | 45 | * [KDAB Newsletter](https://news.kdab.com) 46 | * [KDAB Blogs](https://www.kdab.com/category/blogs) 47 | * [KDAB on Twitter](https://twitter.com/KDABQt) 48 | 49 | ## Licensing 50 | 51 | KDBindings is © Klarälvdalens Datakonsult AB and available under the terms of 52 | the [MIT](https://github.com/KDAB/KDBindings/blob/main/LICENSES/MIT.txt) license. 53 | 54 | Contact KDAB at if you need different licensing options. 55 | 56 | KDBindings includes these source files, also available under the terms of the MIT license: 57 | 58 | * [doctest.h](https://github.com/onqtam/doctest) - the lightest feature-rich C++ single-header 59 | testing framework for unit tests and TDD (C) 2016-2021 Viktor Kirilov 60 | * [genindex_array.h](https://gist.github.com/jaburns/ca72487198832f6203e831133ffdfff4) 61 | (C) 2021 Jeremy Burns (adapted by KDAB for KDBindings) 62 | 63 | ## Get Involved 64 | 65 | Please submit your contributions or issue reports from our GitHub space at 66 | . 67 | 68 | Contact for more information. 69 | 70 | ## About KDAB 71 | 72 | KDBindings is supported and maintained by Klarälvdalens Datakonsult AB (KDAB). 73 | 74 | The KDAB Group is the global No.1 software consultancy for Qt, C++ and 75 | OpenGL applications across desktop, embedded and mobile platforms. 76 | 77 | The KDAB Group provides consulting and mentoring for developing Qt applications 78 | from scratch and in porting from all popular and legacy frameworks to Qt. 79 | We continue to help develop parts of Qt and are one of the major contributors 80 | to the Qt Project. We can give advanced or standard trainings anywhere 81 | around the globe on Qt as well as C++, OpenGL, 3D and more. 82 | 83 | Please visit to meet the people who write code like this. 84 | -------------------------------------------------------------------------------- /REUSE.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | SPDX-PackageName = "KDBindings" 3 | SPDX-PackageSupplier = "" 4 | SPDX-PackageDownloadLocation = "https://www.github.com/KDAB/KDBindings" 5 | 6 | #documentation 7 | [[annotations]] 8 | path = ["README.md", "ChangeLog", "docs/api/**"] 9 | precedence = "aggregate" 10 | SPDX-FileCopyrightText = "Klarälvdalens Datakonsult AB, a KDAB Group company " 11 | SPDX-License-Identifier = "MIT" 12 | 13 | #artwork 14 | [[annotations]] 15 | path = ["docs/api/**.png", "docs/api/mkdocs/docs/assets/**.svg"] 16 | precedence = "aggregate" 17 | SPDX-FileCopyrightText = "Klarälvdalens Datakonsult AB, a KDAB Group company " 18 | SPDX-License-Identifier = "MIT" 19 | 20 | #3rdparty cmake 21 | [[annotations]] 22 | path = "cmake/ECM/modules/**" 23 | precedence = "aggregate" 24 | SPDX-FileCopyrightText = "The KDE Project" 25 | SPDX-License-Identifier = "BSD-3-Clause" 26 | 27 | #misc config files 28 | [[annotations]] 29 | path = [".pre-commit-config.yaml", ".codespellrc", ".krazy", ".cmake-format.py", ".clang-format", ".clazy", ".gitignore", ".markdownlint.json", ".pep8", ".pylintrc", "CMakePresets.json", "CMakeSettings.json", "CMakeUserPresets-example.json", "docs/api/Doxyfile.cmake", "distro/**", "REUSE.toml"] 30 | precedence = "aggregate" 31 | SPDX-FileCopyrightText = "Klarälvdalens Datakonsult AB, a KDAB Group company " 32 | SPDX-License-Identifier = "BSD-3-Clause" 33 | 34 | #3rdparty C++ 35 | [[annotations]] 36 | path = "src/kdbindings/genindex_array.h" 37 | precedence = "aggregate" 38 | SPDX-FileCopyrightText = "2021 Jeremy Burns" 39 | SPDX-License-Identifier = "MIT" 40 | 41 | [[annotations]] 42 | path = "tests/doctest/doctest.h" 43 | precedence = "aggregate" 44 | SPDX-FileCopyrightText = "2016-2021 Viktor Kirilov " 45 | SPDX-License-Identifier = "MIT" 46 | 47 | # doxygen awesome 48 | [[annotations]] 49 | path = "docs/api/doxygen-awesome.css" 50 | precedence = "aggregate" 51 | SPDX-FileCopyrightText = "2021 - 2023 jothepro" 52 | SPDX-License-Identifier = "MIT" 53 | -------------------------------------------------------------------------------- /cmake/ECM/modules/ECMSetupVersion.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2014 Alex Merry 2 | # SPDX-FileCopyrightText: 2012 Alexander Neundorf 3 | # 4 | # SPDX-License-Identifier: BSD-3-Clause 5 | 6 | #[=======================================================================[.rst: 7 | ECMSetupVersion 8 | --------------- 9 | 10 | Handle library version information. 11 | 12 | :: 13 | 14 | ecm_setup_version( 15 | VARIABLE_PREFIX 16 | [SOVERSION ] 17 | [VERSION_HEADER ] 18 | [PACKAGE_VERSION_FILE [COMPATIBILITY ]] ) 19 | 20 | This parses a version string and sets up a standard set of version variables. 21 | It can optionally also create a C version header file and a CMake package 22 | version file to install along with the library. 23 | 24 | If the ```` argument is of the form ``..`` 25 | (or ``...``), The following CMake variables are 26 | set:: 27 | 28 | _VERSION_MAJOR - 29 | _VERSION_MINOR - 30 | _VERSION_PATCH - 31 | _VERSION - 32 | _SOVERSION - , or if SOVERSION was not given 33 | 34 | For backward-compatibility also this variable is set (only if the minimum required 35 | version of ECM is < 5.83):: 36 | 37 | _VERSION_STRING - (use _VERSION instead) 38 | 39 | If CMake policy CMP0048 is not NEW, the following CMake variables will also 40 | be set:: 41 | 42 | PROJECT_VERSION_MAJOR - 43 | PROJECT_VERSION_MINOR - 44 | PROJECT_VERSION_PATCH - 45 | PROJECT_VERSION - 46 | 47 | For backward-compatibility, if CMake policy CMP0048 is not NEW, also this variable is set 48 | (only if the minimum required version of ECM is < 5.83):: 49 | 50 | PROJECT_VERSION_STRING - (use PROJECT_VERSION instead) 51 | 52 | If the VERSION_HEADER option is used, a simple C header is generated with the 53 | given filename. If filename is a relative path, it is interpreted as relative 54 | to CMAKE_CURRENT_BINARY_DIR. The generated header contains the following 55 | macros:: 56 | 57 | _VERSION_MAJOR - as an integer 58 | _VERSION_MINOR - as an integer 59 | _VERSION_PATCH - as an integer 60 | _VERSION_STRING - as a C string 61 | _VERSION - the version as an integer 62 | 63 | ``_VERSION`` has ```` in the bottom 8 bits, ```` in the 64 | next 8 bits and ```` in the remaining bits. Note that ```` and 65 | ```` must be less than 256. 66 | 67 | If the PACKAGE_VERSION_FILE option is used, a simple CMake package version 68 | file is created using the write_basic_package_version_file() macro provided by 69 | CMake. It should be installed in the same location as the Config.cmake file of 70 | the library so that it can be found by find_package(). If the filename is a 71 | relative path, it is interpreted as relative to CMAKE_CURRENT_BINARY_DIR. The 72 | optional COMPATIBILITY option is forwarded to 73 | write_basic_package_version_file(), and defaults to AnyNewerVersion. 74 | 75 | If CMake policy CMP0048 is NEW, an alternative form of the command is 76 | available:: 77 | 78 | ecm_setup_version(PROJECT 79 | [VARIABLE_PREFIX ] 80 | [SOVERSION ] 81 | [VERSION_HEADER ] 82 | [PACKAGE_VERSION_FILE ] ) 83 | 84 | This will use the version information set by the project() command. 85 | VARIABLE_PREFIX defaults to the project name. Note that PROJECT must be the 86 | first argument. In all other respects, it behaves like the other form of the 87 | command. 88 | 89 | Since pre-1.0.0. 90 | 91 | COMPATIBILITY option available since 1.6.0. 92 | #]=======================================================================] 93 | 94 | include(CMakePackageConfigHelpers) 95 | 96 | # save the location of the header template while CMAKE_CURRENT_LIST_DIR 97 | # has the value we want 98 | set(_ECM_SETUP_VERSION_HEADER_TEMPLATE "${CMAKE_CURRENT_LIST_DIR}/ECMVersionHeader.h.in") 99 | 100 | function(ecm_setup_version _version) 101 | set(options ) 102 | set(oneValueArgs VARIABLE_PREFIX SOVERSION VERSION_HEADER PACKAGE_VERSION_FILE COMPATIBILITY) 103 | set(multiValueArgs ) 104 | 105 | cmake_parse_arguments(ESV "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) 106 | 107 | if(ESV_UNPARSED_ARGUMENTS) 108 | message(FATAL_ERROR "Unknown keywords given to ECM_SETUP_VERSION(): \"${ESV_UNPARSED_ARGUMENTS}\"") 109 | endif() 110 | 111 | set(project_manages_version FALSE) 112 | set(use_project_version FALSE) 113 | cmake_policy(GET CMP0048 project_version_policy) 114 | if(project_version_policy STREQUAL "NEW") 115 | set(project_manages_version TRUE) 116 | if(_version STREQUAL "PROJECT") 117 | set(use_project_version TRUE) 118 | endif() 119 | elseif(_version STREQUAL "PROJECT") 120 | message(FATAL_ERROR "ecm_setup_version given PROJECT argument, but CMP0048 is not NEW") 121 | endif() 122 | 123 | set(should_set_prefixed_vars TRUE) 124 | if(NOT ESV_VARIABLE_PREFIX) 125 | if(use_project_version) 126 | set(ESV_VARIABLE_PREFIX "${PROJECT_NAME}") 127 | set(should_set_prefixed_vars FALSE) 128 | else() 129 | message(FATAL_ERROR "Required argument PREFIX missing in ECM_SETUP_VERSION() call") 130 | endif() 131 | endif() 132 | 133 | if(use_project_version) 134 | set(_version "${PROJECT_VERSION}") 135 | set(_major "${PROJECT_VERSION_MAJOR}") 136 | set(_minor "${PROJECT_VERSION_MINOR}") 137 | set(_patch "${PROJECT_VERSION_PATCH}") 138 | else() 139 | string(REGEX REPLACE "^0*([0-9]+)\\.[0-9]+\\.[0-9]+.*" "\\1" _major "${_version}") 140 | string(REGEX REPLACE "^[0-9]+\\.0*([0-9]+)\\.[0-9]+.*" "\\1" _minor "${_version}") 141 | string(REGEX REPLACE "^[0-9]+\\.[0-9]+\\.0*([0-9]+).*" "\\1" _patch "${_version}") 142 | endif() 143 | 144 | if(NOT ESV_SOVERSION) 145 | set(ESV_SOVERSION ${_major}) 146 | endif() 147 | 148 | if(ECM_GLOBAL_FIND_VERSION VERSION_LESS 5.83.0) 149 | set(_set_backward_compat_version_string_vars TRUE) 150 | else() 151 | set(_set_backward_compat_version_string_vars FALSE) 152 | endif() 153 | 154 | if(should_set_prefixed_vars) 155 | set(${ESV_VARIABLE_PREFIX}_VERSION "${_version}") 156 | set(${ESV_VARIABLE_PREFIX}_VERSION_MAJOR ${_major}) 157 | set(${ESV_VARIABLE_PREFIX}_VERSION_MINOR ${_minor}) 158 | set(${ESV_VARIABLE_PREFIX}_VERSION_PATCH ${_patch}) 159 | endif() 160 | 161 | set(${ESV_VARIABLE_PREFIX}_SOVERSION ${ESV_SOVERSION}) 162 | 163 | if(NOT project_manages_version) 164 | set(PROJECT_VERSION "${_version}") 165 | set(PROJECT_VERSION_MAJOR "${_major}") 166 | set(PROJECT_VERSION_MINOR "${_minor}") 167 | set(PROJECT_VERSION_PATCH "${_patch}") 168 | endif() 169 | 170 | if(_set_backward_compat_version_string_vars) 171 | set(PROJECT_VERSION_STRING "${PROJECT_VERSION}") 172 | set(${ESV_VARIABLE_PREFIX}_VERSION_STRING "${${ESV_VARIABLE_PREFIX}_VERSION}") 173 | endif() 174 | 175 | if(ESV_VERSION_HEADER) 176 | set(HEADER_PREFIX "${ESV_VARIABLE_PREFIX}") 177 | set(HEADER_VERSION "${_version}") 178 | set(HEADER_VERSION_MAJOR "${_major}") 179 | set(HEADER_VERSION_MINOR "${_minor}") 180 | set(HEADER_VERSION_PATCH "${_patch}") 181 | configure_file("${_ECM_SETUP_VERSION_HEADER_TEMPLATE}" "${ESV_VERSION_HEADER}") 182 | endif() 183 | 184 | if(ESV_PACKAGE_VERSION_FILE) 185 | if(NOT ESV_COMPATIBILITY) 186 | set(ESV_COMPATIBILITY AnyNewerVersion) 187 | endif() 188 | write_basic_package_version_file("${ESV_PACKAGE_VERSION_FILE}" VERSION ${_version} COMPATIBILITY ${ESV_COMPATIBILITY}) 189 | endif() 190 | 191 | if(should_set_prefixed_vars) 192 | set(${ESV_VARIABLE_PREFIX}_VERSION_MAJOR "${${ESV_VARIABLE_PREFIX}_VERSION_MAJOR}" PARENT_SCOPE) 193 | set(${ESV_VARIABLE_PREFIX}_VERSION_MINOR "${${ESV_VARIABLE_PREFIX}_VERSION_MINOR}" PARENT_SCOPE) 194 | set(${ESV_VARIABLE_PREFIX}_VERSION_PATCH "${${ESV_VARIABLE_PREFIX}_VERSION_PATCH}" PARENT_SCOPE) 195 | set(${ESV_VARIABLE_PREFIX}_VERSION "${${ESV_VARIABLE_PREFIX}_VERSION}" PARENT_SCOPE) 196 | endif() 197 | 198 | # always set the soversion 199 | set(${ESV_VARIABLE_PREFIX}_SOVERSION "${${ESV_VARIABLE_PREFIX}_SOVERSION}" PARENT_SCOPE) 200 | 201 | if(NOT project_manages_version) 202 | set(PROJECT_VERSION "${PROJECT_VERSION}" PARENT_SCOPE) 203 | set(PROJECT_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}" PARENT_SCOPE) 204 | set(PROJECT_VERSION_MINOR "${PROJECT_VERSION_MINOR}" PARENT_SCOPE) 205 | set(PROJECT_VERSION_PATCH "${PROJECT_VERSION_PATCH}" PARENT_SCOPE) 206 | endif() 207 | 208 | if(_set_backward_compat_version_string_vars) 209 | set(PROJECT_VERSION_STRING "${PROJECT_VERSION_STRING}" PARENT_SCOPE) 210 | set(${ESV_VARIABLE_PREFIX}_VERSION_STRING "${${ESV_VARIABLE_PREFIX}_VERSION}" PARENT_SCOPE) 211 | endif() 212 | endfunction() 213 | -------------------------------------------------------------------------------- /cmake/ECM/modules/ECMUninstallTarget.cmake: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2015 Alex Merry 2 | # 3 | # SPDX-License-Identifier: BSD-3-Clause 4 | 5 | #[=======================================================================[.rst: 6 | ECMUninstallTarget 7 | ------------------ 8 | 9 | Add an ``uninstall`` target. 10 | 11 | By including this module, an ``uninstall`` target will be added to your CMake 12 | project. This will remove all files installed (or updated) by a previous 13 | invocation of the ``install`` target. It will not remove files created or 14 | modified by an ``install(SCRIPT)`` or ``install(CODE)`` command; you should 15 | create a custom uninstallation target for these and use ``add_dependency`` to 16 | make the ``uninstall`` target depend on it: 17 | 18 | .. code-block:: cmake 19 | 20 | include(ECMUninstallTarget) 21 | install(SCRIPT install-foo.cmake) 22 | add_custom_target(uninstall_foo COMMAND ${CMAKE_COMMAND} -P uninstall-foo.cmake) 23 | add_dependency(uninstall uninstall_foo) 24 | 25 | The target will fail if the ``install`` target has not yet been run (so it is 26 | not possible to run CMake on the project and then immediately run the 27 | ``uninstall`` target). 28 | 29 | .. warning:: 30 | 31 | CMake deliberately does not provide an ``uninstall`` target by default on 32 | the basis that such a target has the potential to remove important files 33 | from a user's computer. Use with caution. 34 | 35 | Since 1.7.0. 36 | #]=======================================================================] 37 | 38 | if (NOT TARGET uninstall) 39 | configure_file( 40 | "${CMAKE_CURRENT_LIST_DIR}/ecm_uninstall.cmake.in" 41 | "${CMAKE_BINARY_DIR}/ecm_uninstall.cmake" 42 | IMMEDIATE 43 | @ONLY 44 | ) 45 | 46 | add_custom_target(uninstall 47 | COMMAND "${CMAKE_COMMAND}" -P "${CMAKE_BINARY_DIR}/ecm_uninstall.cmake" 48 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" 49 | ) 50 | endif() 51 | -------------------------------------------------------------------------------- /cmake/ECM/modules/ECMVersionHeader.h.in: -------------------------------------------------------------------------------- 1 | // This file was generated by ecm_setup_version(): DO NOT EDIT! 2 | 3 | #ifndef @HEADER_PREFIX@_VERSION_H 4 | #define @HEADER_PREFIX@_VERSION_H 5 | 6 | #define @HEADER_PREFIX@_VERSION_STRING "@HEADER_VERSION@" 7 | #define @HEADER_PREFIX@_VERSION_MAJOR @HEADER_VERSION_MAJOR@ 8 | #define @HEADER_PREFIX@_VERSION_MINOR @HEADER_VERSION_MINOR@ 9 | #define @HEADER_PREFIX@_VERSION_PATCH @HEADER_VERSION_PATCH@ 10 | #define @HEADER_PREFIX@_VERSION @HEADER_PREFIX@_VERSION_CHECK(@HEADER_PREFIX@_VERSION_MAJOR, @HEADER_PREFIX@_VERSION_MINOR, @HEADER_PREFIX@_VERSION_PATCH) 11 | 12 | /* 13 | for example: @HEADER_PREFIX@_VERSION >= @HEADER_PREFIX@_VERSION_CHECK(1, 2, 2)) 14 | */ 15 | #define @HEADER_PREFIX@_VERSION_CHECK(major, minor, patch) ((major<<16)|(minor<<8)|(patch)) 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /cmake/ECM/modules/ecm_uninstall.cmake.in: -------------------------------------------------------------------------------- 1 | if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") 2 | message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") 3 | endif() 4 | 5 | file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) 6 | string(REGEX REPLACE "\n" ";" files "${files}") 7 | foreach(file ${files}) 8 | message(STATUS "Uninstalling $ENV{DESTDIR}${file}") 9 | if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") 10 | exec_program( 11 | "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" 12 | OUTPUT_VARIABLE rm_out 13 | RETURN_VALUE rm_retval 14 | ) 15 | if(NOT "${rm_retval}" STREQUAL 0) 16 | message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") 17 | endif() 18 | else() 19 | message(STATUS "File $ENV{DESTDIR}${file} does not exist.") 20 | endif() 21 | endforeach() 22 | -------------------------------------------------------------------------------- /cmake/InstallLocation.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # SPDX-FileCopyrightText: 2012 Klarälvdalens Datakonsult AB, a KDAB Group company 3 | # 4 | # SPDX-License-Identifier: BSD-3-Clause 5 | # 6 | 7 | # Some default installation locations. These should be global, with any project 8 | # specific locations added to the end. These paths are all relative to the 9 | # install prefix. 10 | # 11 | # These paths attempt to adhere to the FHS, and are similar to those provided 12 | # by autotools and used in many Linux distributions. 13 | 14 | # Use GNU install directories 15 | include(GNUInstallDirs) 16 | 17 | if(NOT INSTALL_RUNTIME_DIR) 18 | set(INSTALL_RUNTIME_DIR ${CMAKE_INSTALL_BINDIR}) 19 | endif() 20 | if(NOT INSTALL_LIBRARY_DIR) 21 | set(INSTALL_LIBRARY_DIR ${CMAKE_INSTALL_LIBDIR}) 22 | endif() 23 | if(NOT INSTALL_ARCHIVE_DIR) 24 | set(INSTALL_ARCHIVE_DIR ${CMAKE_INSTALL_LIBDIR}) 25 | endif() 26 | if(NOT INSTALL_INCLUDE_DIR) 27 | set(INSTALL_INCLUDE_DIR ${CMAKE_INSTALL_INCLUDEDIR}) 28 | endif() 29 | if(NOT INSTALL_DATADIR) 30 | set(INSTALL_DATADIR ${CMAKE_INSTALL_DATADIR}) 31 | endif() 32 | if(NOT INSTALL_DOC_DIR) 33 | set(INSTALL_DOC_DIR ${CMAKE_INSTALL_DOCDIR}) 34 | endif() 35 | 36 | set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) 37 | if(APPLE) 38 | set(CMAKE_MACOSX_RPATH ON) 39 | else() 40 | set(CMAKE_INSTALL_RPATH "$ORIGIN/../${INSTALL_LIBRARY_DIR}") 41 | endif() 42 | -------------------------------------------------------------------------------- /cmake/KDBindingsConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Juan Casafranca 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | @PACKAGE_INIT@ 12 | 13 | include("${CMAKE_CURRENT_LIST_DIR}/KDBindingsTargets.cmake") 14 | 15 | check_required_components("KDBindings") 16 | -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Allen Winter 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | # Doxygen 12 | find_package(Doxygen) 13 | set_package_properties( 14 | Doxygen PROPERTIES 15 | TYPE OPTIONAL 16 | DESCRIPTION "API Documentation system" 17 | URL "https://www.doxygen.org" 18 | PURPOSE "Needed to build the API documentation." 19 | ) 20 | 21 | if(DOXYGEN_FOUND) 22 | add_subdirectory(api) 23 | endif() 24 | -------------------------------------------------------------------------------- /docs/api/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Allen Winter 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | # dot should come with Doxygen find_package(Doxygen) 12 | if(DOXYGEN_DOT_EXECUTABLE) 13 | set(HAVE_DOT "YES") 14 | else() 15 | # cmake-lint: disable=C0301 16 | set(HAVE_DOT "NO") 17 | message( 18 | STATUS 19 | "Unable to provide inheritance diagrams for the API documentation. To fix, install the graphviz project from https://www.graphviz.org" 20 | ) 21 | endif() 22 | 23 | file(GLOB _dox_deps *.dox *.html) 24 | set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) 25 | 26 | # apidox generation using doxygen 27 | configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile.cmake ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) 28 | 29 | add_custom_target( 30 | docs 31 | # Execute Doxygen 32 | COMMAND ${DOXYGEN_EXECUTABLE} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 33 | COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_CURRENT_SOURCE_DIR}/docs ${DOXYGEN_OUTPUT_DIR}/docs 34 | # copy some files by-hand that are referred to by the markdown README. 35 | COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CMAKE_SOURCE_DIR}/LICENSES/MIT.txt 36 | ${DOXYGEN_OUTPUT_DIR}/docs/license.md 37 | DEPENDS ${_dox_deps} ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile 38 | WORKING_DIRECTORY ${CMAKE_BINARY_DIR} 39 | COMMENT "Building Documentation" 40 | ) 41 | 42 | # The tags file is only created when 'make docs' is run first 43 | if(EXISTS "${DOXYGEN_OUTPUT_DIR}/kdbindings.tags") 44 | install(FILES ${DOXYGEN_OUTPUT_DIR}/kdbindings.tags DESTINATION ${INSTALL_DOC_DIR}) 45 | endif() 46 | -------------------------------------------------------------------------------- /docs/api/DoxygenLayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /docs/api/docs/getting-started/data-binding.md: -------------------------------------------------------------------------------- 1 | # Data Binding 2 | 3 | [Data Binding](https://en.wikipedia.org/wiki/Data_binding), or 4 | [Property Binding](https://doc.qt.io/qt-5/qtqml-syntax-propertybinding.html) in Qt terminology, 5 | is a programming paradigm that allows binding of data sources to their respective consumers. 6 | That means that if a value depends on another, it will automatically be updated whenever the 7 | input value changes. 8 | 9 | ## Basic Data Binding in KDBindings 10 | 11 | The basis for Data Binding in KDBindings is formed by [Properties](@ref KDBindings::Property). 12 | 13 | They can be combined using arithmetic operators or just plain functions, to create new properties 14 | that are bound to the input properties. This means the result of the calculation will be updated 15 | automatically every time the input of the calculation changes. 16 | 17 | To create a binding, use the free KDBindings::makeBoundProperty function. 18 | 19 | ### Example: basic data binding 20 | 21 | ``` cpp 22 | #include 23 | 24 | using namespace KDBindings; 25 | 26 | Property width(2); 27 | Property height(3); 28 | Property area = makeBoundProperty(width * height); 29 | 30 | std::cout << area << std::endl; // will print 6 31 | width = 5; 32 | std::cout << area << std::endl; // will print 15 33 | ``` 34 | 35 | Pretty much all arithmetic operators are supported, as well as combinations of properties with 36 | constant values. 37 | 38 | ## Declaring functions for use in data binding 39 | 40 | KDBindings also allows you to declare arbitrary functions as usable in the context of data binding. 41 | See the [`node_functions.h`](./node__functions_8h.html) file for documentation on the associated macros. 42 | 43 | This is already done for some of the `std` arithmetic functions, like abs and floor. 44 | You can use the KDBINDINGS_DECLARE_STD_FUNCTION macro to declare more of these when necessary. 45 | 46 | ### Example: declaring functions for data binding 47 | 48 | ``` cpp 49 | #include 50 | 51 | using namespace KDBindings; 52 | 53 | Property value(-1); 54 | Property positiveValue = makeBoundProperty(abs(value)); 55 | // positiveValue is 1 56 | ``` 57 | 58 | ## Binding arbitrary functions 59 | 60 | KDBindings::makeBoundProperty also accepts functions as binding, so arbitrary code can be executed 61 | to produce the new property value. 62 | 63 | ### Example: binding arbitrary functions 64 | 65 | ``` cpp 66 | #include 67 | 68 | using namespace KDBindings; 69 | 70 | Property text; 71 | Property lowerCase = makeBoundProperty( 72 | [](std::string value) { 73 | std::transform(value.begin(), value.end(), value.begin(), 74 | [](unsigned char c) { return std::tolower(c); }); 75 | return value; 76 | }, 77 | text /*don't forget to pass in the source property*/); 78 | 79 | text = "Hello World!"; 80 | std::cout << lowerCase.get() << std::endl; 81 | ``` 82 | 83 | Expected output: 84 | 85 | ```text 86 | hello world! 87 | ``` 88 | 89 | ## No more broken bindings 90 | 91 | A common mistake in data binding is accidentally breaking a binding by assigning a value to it. 92 | 93 | With KDBindings this is no longer possible. 94 | Assigning a value to a property that is the result of a data binding will result in an error! 95 | 96 | ### Example: broken bindings 97 | 98 | ``` cpp 99 | #include 100 | 101 | using namespace KDBindings; 102 | 103 | Property value = 2; 104 | Property result = makeBoundProperty(2 * value); 105 | // result is now 4 106 | 107 | result = 5; // this will throw a KDBindings::ReadOnlyProperty error! 108 | ``` 109 | 110 | To intentionally remove a binding from a property, use its 111 | [`reset`](/ref KDBindings::Property::reset) function. 112 | 113 | ### Reassigning a Binding 114 | 115 | Even though KDBindings prevents you from accidentally overriding a binding with a concrete value, 116 | assigning a new binding to a Property with an existing binding is possible. 117 | 118 | For this, use the KDBindings::makeBinding function instead of KDBindings::makeBoundProperty to 119 | create the binding. 120 | 121 | It is also possible to use makeBoundProperty, which will move-assign a new Property into the 122 | existing one, therefore completely replacing the existing Property with a new one. This means that 123 | all signals of the old property will be disconnected (see [signals & slots](signals-slots.md)) and 124 | any existing data bindings that depended on the property will be removed, which might not be what 125 | you want! 126 | 127 | Rule of thumb: If you want to create a new property, use makeBoundProperty, if you want to add 128 | a new binding to an existing Property, use makeBinding. 129 | 130 | #### Example: reassign a binding 131 | 132 | ``` cpp 133 | #include 134 | 135 | using namespace KDBindings; 136 | 137 | Property value = 2; 138 | auto result = makeBoundProperty(2 * value); 139 | // result is 4 140 | 141 | result = makeBinding(3 * value); // This does not throw any exceptions. 142 | // result is now 6 143 | result = makeBoundProperty(4 * value); // This works too, but will override all existing connections to result. 144 | // result is now 8 145 | ``` 146 | 147 | ## Deferred evaluation 148 | 149 | KDBindings optionally offers data bindings with controlled, deferred evaluation. 150 | 151 | This feature is enabled by passing a KDBindings::BindingEvaluator to makeBoundProperty. The binding 152 | will then only be evaluated when [`evaluateAll()`](@ref KDbindings::BindingEvaluator::evaluateAll) 153 | is called on the evaluator instance. 154 | 155 | Deferred evaluation is useful to avoid unnecessary reevaluation of a binding, as well as controlling 156 | the frequency of binding evaluation. Especially in UI applications, only updating the displayed 157 | values once per second can help in making them readable, compared to a bunch of values updating at 60Hz. 158 | 159 | See the [06-lazy-property-bindings](./06-lazy-property-bindings_2main_8cpp-example.html) example for 160 | more details on how to use deferred evaluation. 161 | 162 | ## Further reading 163 | 164 | Classes involved in data binding are KDBindings::Property, KDBindings::Binding, and KDBindings::BindingEvaluator. 165 | 166 | See KDBindings::makeBoundProperty for the different ways to create a binding. 167 | 168 | We also recommend you take a look at our [examples](../examples.md). 169 | -------------------------------------------------------------------------------- /docs/api/docs/getting-started/index.md: -------------------------------------------------------------------------------- 1 | # Getting Started 2 | 3 | ## Installation 4 | 5 | KDBindings is a header-only library, so you could just add the `/src/kdbindings/` directory to your 6 | projects include path or copy it into your project and start using KDBindings. 7 | 8 | However, we recommend using [CMake](https://cmake.org/) to install KDBindings so it's available to 9 | any CMake project on your computer. 10 | 11 | For this, first clone the project and install it: 12 | 13 | ### Linux/macOS 14 | 15 | ``` shell 16 | mkdir build 17 | cd build 18 | cmake .. 19 | cmake --build . --target install # this command might need super user privileges (i.e. sudo) 20 | ``` 21 | 22 | ### Windows 23 | 24 | Navigate to the repository and create a folder named `build`. 25 | Navigate into the folder. 26 | Use `Shift`+`Right Click` and select `Open command window here`. 27 | In the terminal window, run: 28 | 29 | ``` cmd 30 | cmake .. 31 | cmake --build . --target install 32 | ``` 33 | 34 | ## Accessing KDBindings 35 | 36 | ### Linking using CMake 37 | 38 | After installation, KDBindings is available using CMake's 39 | [find_package](https://cmake.org/cmake/help/latest/command/find_package.html) directive. 40 | This directive then exposes a KDAB::KDBindings library that can be linked against. 41 | 42 | Example CMake code: 43 | 44 | ``` cmake 45 | find_package(KDBindings) 46 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 47 | ``` 48 | 49 | Also make sure C++17 or later is enabled in your project: 50 | 51 | ``` cmake 52 | set(CMAKE_CXX_STANDARD 17) 53 | ``` 54 | 55 | ### Including KDBindings 56 | 57 | Once the library is correctly added to your project, the different features of KDBindings are 58 | available for include under the `kdbindings` directory. 59 | 60 | All parts of KDBindings are then accessible in the KDBindings namespace. 61 | 62 | ``` cpp 63 | // Example includes 64 | #include 65 | #include 66 | 67 | KDBindings::Signal mySignal; 68 | 69 | // alternatively add a using directive for the namespace 70 | using namespace KDBindings; 71 | Signal myOtherSignal; 72 | ``` 73 | 74 | For the list of files that can be included, see [the files page](./files.html). 75 | 76 | ## KDBindings Features 77 | 78 | KDBindings uses plain C++17 to give you: 79 | 80 | - [Signals & Slots](signals-slots.md) 81 | - [Properties](properties.md) 82 | - [Data Bindings](data-binding.md) 83 | -------------------------------------------------------------------------------- /docs/api/docs/getting-started/properties.md: -------------------------------------------------------------------------------- 1 | # Properties 2 | 3 | Properties are values that can notify observers of changes. 4 | They can therefore be used as values in [data bindings](data-binding.md). 5 | 6 | Unlike [Qts Properties](https://doc.qt.io/qt-5/properties.html), KDBindings Properties don't 7 | require a Meta Object Compiler and are available in pure C++17. They can even be used outside 8 | of classes as free values. 9 | 10 | ## Declaring Properties 11 | 12 | Properties can be declared for most types by creating a `KDBindings::Property` instance. 13 | 14 | The property instance will then emit [signals](signals-slots.md) every time the properties value 15 | changes, the property is moved or destructed. 16 | 17 | ### Minimal example: declaring properties 18 | 19 | ``` cpp 20 | #include 21 | 22 | using namespace KDBindings; 23 | 24 | Property myProperty; 25 | myProperty.valueChanged().connect([](const std::string& value) { 26 | std::cout << value << std::endl; 27 | }); 28 | 29 | myProperty = "Hello World!"; 30 | ``` 31 | 32 | Expected output: 33 | 34 | ```text 35 | Hello World! 36 | ``` 37 | 38 | For more information and examples see the KDBindings::Property documentation. 39 | -------------------------------------------------------------------------------- /docs/api/docs/getting-started/signals-slots.md: -------------------------------------------------------------------------------- 1 | # Signals and Slots 2 | 3 | Signals and Slots are the basis on which [Properties](properties.md) and 4 | [Data binding](data-binding.md) are built. 5 | 6 | The concept is a central feature of the Qt framework. 7 | It allows objects to declare events they may encounter as "Signals". 8 | The object can then "emit" the Signal at any time. 9 | Signals can be connected to functions of another object, referred to as "Slots". 10 | 11 | Signals & Slots therefore provide an easy way to implement the 12 | [observer pattern](https://en.wikipedia.org/wiki/Observer_pattern). This is especially useful for 13 | UI libraries that need to dynamically react to events and observe values. 14 | 15 | For an explanation of the original Signals & Slots in Qt, follow 16 | [this link](https://doc.qt.io/qt-5/signalsandslots.html). 17 | 18 | KDBindings introduces its own Signals that can be used without the need for Qts Meta Object Compiler. 19 | They also don't need to be attached to any specific object. 20 | 21 | ## KDBindings Signals 22 | 23 | A Signal in KDBindings is just an object that can be freely used anywhere in your code. 24 | They are templated over the types of values they emit, which may be of any type. 25 | 26 | Slots can be connected to such a Signal by simply calling `connect(...)` on the Signal object. 27 | To emit the Signal, call `emit(...)` and pass the arguments the Signal emits. 28 | 29 | ### Minimal example: signals 30 | 31 | ``` cpp 32 | #include 33 | 34 | using namespace KDBindings; 35 | Signal mySignal; 36 | 37 | mySignal.connect([](const std::string& value){ 38 | std::cout << value << std::endl; 39 | }); 40 | 41 | mySignal.emit("Hello World!"); 42 | ``` 43 | 44 | Expected output: 45 | 46 | ```text 47 | Hello World! 48 | ``` 49 | 50 | For the full class reference, see: KDBindings::Signal. 51 | 52 | ## KDBindings Slots 53 | 54 | In KDBindings, a slot is anything that can be invoked, so any function, lambda or other object that 55 | implements the `operator()`. 56 | 57 | Generally, the signature of the slot must match the arguments emitted by the Signal. 58 | However, the Signal class provides an overload to the `connect()` function that allows binding some 59 | of the slot arguments to default values. This is especially useful to connect member functions to 60 | a Signal by binding a pointer to the object to the first (implicit) argument. 61 | 62 | ### Minimal example: slots 63 | 64 | ``` cpp 65 | #include 66 | 67 | using namespace KDBindings; 68 | 69 | class MyWidget { 70 | public: 71 | void onClicked() { 72 | std::cout << "Hello World!" << std::endl; 73 | } 74 | }; 75 | 76 | Signal<> clicked; 77 | MyWidget widget; 78 | 79 | clicked.connect(&MyWidget::onClicked, &widget); 80 | clicked.emit(); 81 | ``` 82 | 83 | Expected output: 84 | 85 | ```text 86 | Hello World! 87 | ``` 88 | 89 | Note that in KDBindings, the order of arguments is in reverse to the order required by Qt. 90 | KDBindings expects the member function first, and the pointer to the class object second. 91 | 92 | See the examples: 93 | 94 | - [02-signal-member](./02-signal-member_2main_8cpp-example.html) 95 | - [03-member-arguments](./03-member-arguments_2main_8cpp-example.html) 96 | - [07-advanced connections](./07-advanced-connections_2main_8cpp-example.html) 97 | 98 | Also see the documentation of the [connect](@ref KDBindings::Signal::connect) function. 99 | 100 | ## Managing a connection 101 | 102 | Calling the connect function of a Signal returns a KDBindings::ConnectionHandle. 103 | These objects reference the connection and can be used to disconnect or temporarily block it. 104 | 105 | It's important to note that, unlike Qt, KDBindings requires you to manually disconnect a connection 106 | when any of the bound arguments are destroyed. For that purpose it's important to keep the 107 | ConnectionHandle around! You must use it to disconnect the connection, should the object that 108 | contains the slot go out of scope! Otherwise, you will encounter a dangling pointer whenever the 109 | Signal is emitted. 110 | 111 | For further information, see the KDBindings::ConnectionHandle documentation. 112 | 113 | ## Some Notes on Mutability and const Best Practices 114 | 115 | When Signals are incorporated into another object, mutability of these Signals can become a concern. 116 | By default functions that modify a Signal, like "connect", are not const, as they modify the Signal. 117 | 118 | That does mean however that by default just connecting to a Signal means you'll require a non-const 119 | reference to the object the Signal is contained in. For some applications, this does not make much 120 | sense, as the Signal is just meant to notify of a change, and changing the Signal by connecting to 121 | it doesn't really "modify" the enclosing objects. This is for example the case in the Property class 122 | in KDBindings. 123 | 124 | For these cases we actually recommend using the C++ "mutable" keyword to allow the use of 125 | non-const Signal functions even in const-contexts. Whilst using mutable is uncommon, in our opinion 126 | this is the correct place to use it, as it accurately marks which Signals change the behavior of the 127 | enclosing object by being connected, and which ones don't. 128 | -------------------------------------------------------------------------------- /docs/api/docs/license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /docs/api/footer.html: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 | © Klarälvdalens Datakonsult AB (KDAB) 5 |
6 | "The Qt, C++ and OpenGL Experts"
7 | https://www.kdab.com/ 8 |
9 | 10 |
11 | 12 | KDBindings 13 |
14 | Reactive programming & data binding in C++
15 | https://github.com/KDAB/KDBindings/
16 | $generatedby doxygen $doxygenversion
17 |
18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /docs/api/kdab-logo-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDAB/KDBindings/b3605389e1c91d01a52aeb73cc5370f9bee8b5ce/docs/api/kdab-logo-16x16.png -------------------------------------------------------------------------------- /docs/api/kdab-logo-22x22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KDAB/KDBindings/b3605389e1c91d01a52aeb73cc5370f9bee8b5ce/docs/api/kdab-logo-22x22.png -------------------------------------------------------------------------------- /examples/01-simple-connection/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | 01-simple-connection 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} main.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | -------------------------------------------------------------------------------- /examples/01-simple-connection/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | using namespace KDBindings; 18 | 19 | int main() 20 | { 21 | // Create new signal 22 | Signal signal; 23 | 24 | // Connect a lambda 25 | (void)signal.connect([](std::string arg1, int arg2) { 26 | std::cout << arg1 << " " << arg2 << std::endl; 27 | }); 28 | 29 | // Emit the signal and the lambda is called 30 | signal.emit("The answer:", 42); 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /examples/02-signal-member/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | 02-signal-member 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} main.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | -------------------------------------------------------------------------------- /examples/02-signal-member/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | using namespace KDBindings; 18 | 19 | class Button 20 | { 21 | public: 22 | Signal<> clicked; 23 | }; 24 | 25 | class Message 26 | { 27 | public: 28 | void display() const 29 | { 30 | std::cout << "Hello World!" << std::endl; 31 | } 32 | }; 33 | 34 | int main() 35 | { 36 | Button button; 37 | Message message; 38 | 39 | auto connection = button.clicked.connect(&Message::display, &message); 40 | button.clicked.emit(); 41 | connection.disconnect(); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /examples/03-member-arguments/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | 03-member-arguments 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} main.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | -------------------------------------------------------------------------------- /examples/03-member-arguments/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | using namespace KDBindings; 18 | 19 | class Person 20 | { 21 | public: 22 | Person(std::string const &name) 23 | : m_name(name) 24 | { 25 | } 26 | 27 | Signal speak; 28 | 29 | void listen(std::string const &message) 30 | { 31 | std::cout << m_name << " received: " << message << std::endl; 32 | } 33 | 34 | private: 35 | std::string m_name; 36 | }; 37 | 38 | int main() 39 | { 40 | Person alice("Alice"); 41 | Person bob("Bob"); 42 | 43 | auto connection1 = alice.speak.connect(&Person::listen, &bob); 44 | auto connection2 = bob.speak.connect(&Person::listen, &alice); 45 | 46 | alice.speak.emit("Have a nice day!"); 47 | bob.speak.emit("Thank you!"); 48 | 49 | connection1.disconnect(); 50 | connection2.disconnect(); 51 | 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /examples/04-simple-property/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | 04-simple-property 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} main.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | -------------------------------------------------------------------------------- /examples/04-simple-property/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | using namespace KDBindings; 18 | 19 | class Widget 20 | { 21 | public: 22 | Widget(std::string const &name) 23 | : value(0) 24 | , m_name(name) 25 | { 26 | } 27 | 28 | Property value; 29 | 30 | private: 31 | std::string m_name; 32 | }; 33 | 34 | int main() 35 | { 36 | Widget w("A cool widget"); 37 | 38 | (void)w.value.valueChanged().connect([](int newValue) { 39 | std::cout << "The new value is " << newValue << std::endl; 40 | }); 41 | 42 | w.value = 42; 43 | w.value = 69; 44 | 45 | std::cout << "Property value is " << w.value << std::endl; 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /examples/05-property-bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | 05-property-bindings 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} main.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | -------------------------------------------------------------------------------- /examples/05-property-bindings/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | using namespace KDBindings; 19 | 20 | class Image 21 | { 22 | public: 23 | const int bytesPerPixel = 4; 24 | Property width{ 800 }; 25 | Property height{ 600 }; 26 | const Property byteSize = makeBoundProperty(bytesPerPixel * width * height); 27 | }; 28 | 29 | int main() 30 | { 31 | Image img; 32 | std::cout << "The initial size of the image = " << img.byteSize.get() << " bytes" << std::endl; 33 | 34 | (void)img.byteSize.valueChanged().connect([](const int &newValue) { 35 | std::cout << "The new size of the image = " << newValue << " bytes" << std::endl; 36 | }); 37 | 38 | img.width = 1920; 39 | img.height = 1080; 40 | 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /examples/06-lazy-property-bindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | 06-lazy-property-bindings 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} main.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | -------------------------------------------------------------------------------- /examples/06-lazy-property-bindings/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | using namespace KDBindings; 19 | 20 | static BindingEvaluator evaluator; 21 | 22 | class Image 23 | { 24 | public: 25 | const int bytesPerPixel = 4; 26 | Property width{ 800 }; 27 | Property height{ 600 }; 28 | const Property byteSize = makeBoundProperty(evaluator, bytesPerPixel *width *height); 29 | }; 30 | 31 | int main() 32 | { 33 | Image img; 34 | std::cout << "The initial size of the image = " << img.byteSize.get() << " bytes" << std::endl; 35 | 36 | (void)img.byteSize.valueChanged().connect([](const int &newValue) { 37 | std::cout << "The new size of the image = " << newValue << " bytes" << std::endl; 38 | }); 39 | 40 | img.width = 1920; 41 | img.height = 1080; 42 | 43 | evaluator.evaluateAll(); 44 | 45 | return 0; 46 | } 47 | -------------------------------------------------------------------------------- /examples/07-advanced-connections/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Leon Matthes 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | 07-advanced-connections 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} main.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | -------------------------------------------------------------------------------- /examples/07-advanced-connections/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Leon Matthes 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | using namespace KDBindings; 19 | 20 | void display() 21 | { 22 | std::cout << "Hello World!" << std::endl; 23 | } 24 | 25 | void displayLabelled(const std::string &label, int value) 26 | { 27 | std::cout << label << ": " << value << std::endl; 28 | } 29 | 30 | class SignalHandler 31 | { 32 | public: 33 | bool received = 0; 34 | 35 | void receive() 36 | { 37 | received = true; 38 | } 39 | }; 40 | 41 | int main() 42 | { 43 | Signal signal; 44 | std::vector connections; 45 | 46 | // Signal::connect allows connecting functions that take too few arguments. 47 | connections.emplace_back(signal.connect(display)); 48 | 49 | // As well as functions with too many arguments, as long as default values are provided. 50 | connections.emplace_back(signal.connect(displayLabelled, "Emitted value")); 51 | 52 | // This is very useful to connect member functions, where the first implicit argument 53 | // is a pointer to the "this" object. 54 | SignalHandler handler; 55 | connections.emplace_back(signal.connect(&SignalHandler::receive, &handler)); 56 | 57 | // This will print "Hello World!" and "Emitted value: 5" in an unspecified order. 58 | // It will also set handler.received to true 59 | signal.emit(5); 60 | 61 | std::cout << std::boolalpha << handler.received << std::endl; 62 | 63 | for (auto &connection : connections) { 64 | connection.disconnect(); 65 | } 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /examples/08-managing-connections/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Leon Matthes 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | 08-managing-connections 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} main.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | -------------------------------------------------------------------------------- /examples/08-managing-connections/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Leon Matthes 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | using namespace KDBindings; 19 | 20 | void displayLabelled(const std::string &label, int value) 21 | { 22 | std::cout << label << ": " << value << std::endl; 23 | } 24 | 25 | int main() 26 | { 27 | Signal signal; 28 | 29 | { 30 | // A ScopedConnection will disconnect the connection once it goes out of scope. 31 | // It is especially useful if you're connecting to a member function. 32 | // Storing a ScopedConnection in the object that contains the slot ensures the connection 33 | // is disconnected when the object is destructed. 34 | // This ensures that there are no dangling connections. 35 | ScopedConnection guard = signal.connect(displayLabelled, "Guard is connected"); 36 | 37 | signal.emit(1); 38 | } // The connection is disconnected here 39 | 40 | signal.emit(2); 41 | 42 | ConnectionHandle handle = signal.connect(displayLabelled, "Connection is not blocked"); 43 | 44 | signal.emit(3); 45 | { 46 | // A ConnectionBlocker will block a connection for the duration of its scope. 47 | // This is a good way to avoid endless-recursion, or to suppress updates for a short time. 48 | ConnectionBlocker blocker(handle); // The connection is blocked here 49 | 50 | signal.emit(4); 51 | } // The connection is un-blocked here 52 | 53 | signal.emit(5); 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | add_subdirectory(01-simple-connection) 12 | add_subdirectory(02-signal-member) 13 | add_subdirectory(03-member-arguments) 14 | add_subdirectory(04-simple-property) 15 | add_subdirectory(05-property-bindings) 16 | add_subdirectory(06-lazy-property-bindings) 17 | add_subdirectory(07-advanced-connections) 18 | add_subdirectory(08-managing-connections) 19 | -------------------------------------------------------------------------------- /src/kdbindings/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 2 | # Author: Sean Harmer 3 | # 4 | # SPDX-License-Identifier: MIT 5 | # 6 | # Contact KDAB at for commercial licensing options. 7 | # 8 | 9 | set(HEADERS 10 | binding.h 11 | binding_evaluator.h 12 | genindex_array.h 13 | make_node.h 14 | node.h 15 | node_functions.h 16 | node_operators.h 17 | property.h 18 | property_updater.h 19 | signal.h 20 | connection_evaluator.h 21 | connection_handle.h 22 | utils.h 23 | KDBindingsConfig.h 24 | ) 25 | 26 | if(CMAKE_VERSION VERSION_GREATER_EQUAL "3.19.0") 27 | # Since CMake 3.19 we can add SOURCES here (cf. https://gitlab.kitware.com/cmake/cmake/-/merge_requests/5078) 28 | add_library(KDBindings INTERFACE ${HEADERS}) 29 | else() 30 | add_library(KDBindings INTERFACE) 31 | endif() 32 | add_library(KDAB::KDBindings ALIAS KDBindings) 33 | 34 | set_target_properties(KDBindings PROPERTIES INTERFACE_COMPILE_FEATURES cxx_std_17) 35 | 36 | if(KDBindings_ENABLE_WARN_UNUSED) 37 | target_compile_definitions(KDBindings INTERFACE KDBINDINGS_ENABLE_WARN_UNUSED=1) 38 | endif() 39 | if(KDBindings_QT_NO_EMIT) 40 | target_compile_definitions(KDBindings INTERFACE QT_NO_EMIT) 41 | endif() 42 | 43 | target_include_directories( 44 | KDBindings INTERFACE $ $ 45 | ) 46 | if(KDBindings_ERROR_ON_WARNING) 47 | if(MSVC) 48 | target_compile_options(KDBindings INTERFACE /W3 /WX) 49 | else() 50 | target_compile_options(KDBindings INTERFACE -Wall -Wextra -Wpedantic -Werror) 51 | endif() 52 | endif() 53 | 54 | # Generate library version files 55 | include(ECMSetupVersion) 56 | ecm_setup_version( 57 | ${PROJECT_VERSION} 58 | VARIABLE_PREFIX 59 | KDBINDINGS 60 | VERSION_HEADER 61 | "${CMAKE_BINARY_DIR}/kdbindings_version.h" 62 | PACKAGE_VERSION_FILE 63 | "${PROJECT_BINARY_DIR}/KDBindingsConfigVersion.cmake" 64 | COMPATIBILITY 65 | AnyNewerVersion 66 | ) 67 | install(FILES "${CMAKE_BINARY_DIR}/kdbindings_version.h" DESTINATION ${INSTALL_INCLUDE_DIR}/kdbindings) 68 | install(FILES ${HEADERS} DESTINATION ${INSTALL_INCLUDE_DIR}/kdbindings) 69 | 70 | configure_package_config_file( 71 | "${PROJECT_SOURCE_DIR}/cmake/KDBindingsConfig.cmake.in" "${PROJECT_BINARY_DIR}/KDBindingsConfig.cmake" 72 | INSTALL_DESTINATION "${INSTALL_LIBRARY_DIR}/cmake/KDBindings" 73 | PATH_VARS INSTALL_INCLUDE_DIR 74 | ) 75 | 76 | install(FILES "${PROJECT_BINARY_DIR}/KDBindingsConfig.cmake" "${PROJECT_BINARY_DIR}/KDBindingsConfigVersion.cmake" 77 | DESTINATION "${INSTALL_LIBRARY_DIR}/cmake/KDBindings" 78 | ) 79 | 80 | export( 81 | TARGETS KDBindings 82 | NAMESPACE KDAB:: 83 | FILE "${PROJECT_BINARY_DIR}/KDBindingsTargets.cmake" 84 | ) 85 | 86 | install( 87 | TARGETS KDBindings 88 | EXPORT KDBindingsTargets 89 | LIBRARY DESTINATION ${INSTALL_LIBRARY_DIR} 90 | ARCHIVE DESTINATION ${INSTALL_ARCHIVE_DIR} 91 | RUNTIME DESTINATION ${INSTALL_RUNTIME_DIR} 92 | ) 93 | 94 | install( 95 | EXPORT KDBindingsTargets 96 | DESTINATION ${INSTALL_LIBRARY_DIR}/cmake/KDBindings 97 | NAMESPACE KDAB:: 98 | FILE KDBindingsTargets.cmake 99 | ) 100 | -------------------------------------------------------------------------------- /src/kdbindings/KDBindingsConfig.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2024 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | 6 | SPDX-License-Identifier: MIT 7 | 8 | Contact KDAB at for commercial licensing options. 9 | */ 10 | 11 | #pragma once 12 | 13 | #ifdef KDBINDINGS_ENABLE_WARN_UNUSED 14 | #define KDBINDINGS_WARN_UNUSED [[nodiscard]] 15 | #else 16 | #define KDBINDINGS_WARN_UNUSED 17 | #endif 18 | -------------------------------------------------------------------------------- /src/kdbindings/binding.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace KDBindings { 22 | 23 | /** 24 | * @brief A combination of a root Node with an evaluator. 25 | * 26 | * A root Node is formed whenever multiple properties are combined inside 27 | * a expression and an evaluator is responsible for re-evaluating such 28 | * an expression whenever any of the constituent properties change. 29 | * 30 | * @tparam T The type of the value that the Binding expression evaluates to. 31 | * @tparam EvaluatorT The type of the evaluator that is used to evaluate the Binding. 32 | */ 33 | template 34 | class Binding : public PropertyUpdater, public Private::Dirtyable 35 | { 36 | static_assert( 37 | std::is_base_of::value, 38 | "The EvaluatorT type must inherit from BindingEvaluator."); 39 | 40 | public: 41 | /** 42 | * @brief Construct a new Binding with a specific evaluator. 43 | * 44 | * @param rootNode Represents that expression contained in the Binding. 45 | * @param evaluator Used to evaluate the expression contained in the Binding. 46 | */ 47 | explicit Binding(Private::Node &&rootNode, EvaluatorT const &evaluator) 48 | : m_rootNode{ std::move(rootNode) } 49 | , m_evaluator{ evaluator } 50 | { 51 | m_bindingId = m_evaluator.insert(this); 52 | m_rootNode.setParent(this); 53 | } 54 | 55 | /** Destructs the Binding by deregistering it from its evaluator. */ 56 | ~Binding() override 57 | { 58 | m_evaluator.remove(m_bindingId); 59 | } 60 | 61 | /** A Binding is not default constructible. */ 62 | Binding() = delete; 63 | 64 | /** A Binding cannot be copy constructed. */ 65 | Binding(Binding const &other) = delete; 66 | /** A Binding cannot be copy assigned. */ 67 | Binding &operator=(Binding const &other) = delete; 68 | 69 | // Move construction would invalidate the this pointer passed to the evaluator 70 | // in the constructor 71 | /** A Binding can not be move constructed. */ 72 | Binding(Binding &&other) = delete; 73 | /** A Binding can not be move assigned. */ 74 | Binding &operator=(Binding &&other) = delete; 75 | 76 | /** Set the function that should be used to notify 77 | * associated properties when the Binding re-evaluates. 78 | */ 79 | void setUpdateFunction(std::function const &updateFunction) override 80 | { 81 | m_propertyUpdateFunction = updateFunction; 82 | } 83 | 84 | /** Returns the current value of the Binding. */ 85 | T get() const override { return m_rootNode.evaluate(); } 86 | 87 | /** Re-evaluates the value of the Binding and notifies all dependants of the change. */ 88 | void evaluate() 89 | { 90 | T value = m_rootNode.evaluate(); 91 | 92 | // Use this to update any associated property via the PropertyUpdater's update function 93 | m_propertyUpdateFunction(std::move(value)); 94 | } 95 | 96 | protected: 97 | Private::Dirtyable **parentVariable() override { return nullptr; } 98 | const bool *dirtyVariable() const override { return nullptr; } 99 | 100 | /** The root Node of the Binding represents the expression contained by the Binding. */ 101 | Private::Node m_rootNode; 102 | /** The evaluator responsible for evaluating this Binding. */ 103 | EvaluatorT m_evaluator; 104 | /** The function used to notify associated properties when the Binding re-evaluates */ 105 | std::function m_propertyUpdateFunction = [](T &&) {}; 106 | /** The id of the Binding, used for keeping track of the Binding in its evaluator. */ 107 | int m_bindingId = -1; 108 | }; 109 | 110 | /** 111 | * @brief Helper function to create a Binding from a Property. 112 | * 113 | * @tparam T The type of the value that the Binding expression evaluates to. 114 | * @tparam EvaluatorT The type of the evaluator that is used to evaluate the Binding. 115 | * @param evaluator The evaluator that is used to evaluate the Binding. 116 | * @param property The Property to create a Binding from. 117 | * @return std::unique_ptr> A new Binding that is powered by the evaluator. 118 | * 119 | * *Note: For the difference between makeBinding and makeBoundProperty, see the 120 | * ["Reassigning a Binding"](../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.* 121 | */ 122 | template 123 | inline std::unique_ptr> makeBinding(EvaluatorT &evaluator, Property &property) 124 | { 125 | return std::make_unique>(Private::makeNode(property), evaluator); 126 | } 127 | 128 | /** 129 | * @brief Creates a binding from a const property using a specified evaluator. 130 | * 131 | * @tparam T The type of the value that the Binding expression evaluates to. 132 | * @tparam EvaluatorT The type of the evaluator that is used to evaluate the Binding. 133 | * @param evaluator The evaluator that is used to evaluate the Binding. 134 | * @param property The const Property to create a Binding from. 135 | * @return std::unique_ptr> A new Binding that is powered by the evaluator. 136 | * 137 | * *Note: Using a const Property ensures that the source cannot be modified through this binding, 138 | * maintaining data integrity and supporting scenarios where data should only be observed, not altered.* 139 | */ 140 | template 141 | inline std::unique_ptr> makeBinding(EvaluatorT &evaluator, const Property &property) 142 | { 143 | return std::make_unique>(Private::makeNode(property), evaluator); 144 | } 145 | 146 | /** 147 | * @brief Helper function to create a Binding from a root Node. 148 | * 149 | * @tparam T The type of the value that the Binding expression evaluates to. 150 | * @tparam EvaluatorT The type of the evaluator that is used to evaluate the Binding. 151 | * @param evaluator The evaluator that is used to evaluate the Binding. 152 | * @param rootNode Represents the expression that will be evaluated by the Binding. 153 | * @return std::unique_ptr> A new Binding that combines the rootNode with the evaluator. 154 | * 155 | * *Note: For the difference between makeBinding and makeBoundProperty, see the 156 | * ["Reassigning a Binding"](../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.* 157 | */ 158 | template 159 | inline std::unique_ptr> makeBinding(EvaluatorT &evaluator, Private::Node &&rootNode) 160 | { 161 | return std::make_unique>(std::move(rootNode), evaluator); 162 | } 163 | 164 | /** 165 | * @brief Helper function to create a Binding from a function and its arguments. 166 | * 167 | * @tparam EvaluatorT The type of the evaluator that is used to evaluate the Binding. 168 | * @param evaluator The evaluator that is used to evaluate the Binding. 169 | * @tparam Func The type of the function - may be any type that implements operator(). 170 | * @param func The function object. 171 | * @tparam Args The function argument types 172 | * @param args The function arguments - Possible values include: Properties, Constants and Nodes 173 | * They will be automatically unwrapped, i.e. a Property will pass a value of type T to func. 174 | * @return std::unique_ptr> where ReturnType is the type that results from evaluationg func with the given arguments. 175 | * The Binding will be powered by the new evaluator. 176 | * 177 | * *Note: For the difference between makeBinding and makeBoundProperty, see the 178 | * ["Reassigning a Binding"](../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.* 179 | */ 180 | template, typename ResultType = Private::operator_node_result_t> 181 | inline std::unique_ptr> makeBinding(EvaluatorT &evaluator, Func &&func, Args &&...args) 182 | { 183 | return std::make_unique>(Private::makeNode(std::forward(func), std::forward(args)...), evaluator); 184 | } 185 | 186 | /** 187 | * @brief Provides a convenience for old-school, immediate mode Bindings. 188 | * 189 | * This works in conjunction with a do-nothing ImmediateBindingEvaluator class to update the 190 | * result of the Binding immediately upon any of the dependent bindables (i.e. Property instances) 191 | * notifying that they have changed. This can lead to a Property Binding being evaluated many 192 | * times before the result is ever used in a typical GUI application. 193 | * 194 | * @tparam T The type of the value that the Binding expression evaluates to. 195 | */ 196 | template 197 | class Binding : public Binding 198 | { 199 | public: 200 | /** 201 | * @brief Construct a new Binding with an immediate mode evaluator. 202 | * 203 | * @param rootNode Represents that expression contained in the Binding. 204 | */ 205 | explicit Binding(Private::Node &&rootNode) 206 | : Binding(std::move(rootNode), ImmediateBindingEvaluator::instance()) 207 | { 208 | } 209 | 210 | /** A Binding is not default constructible. */ 211 | Binding() = delete; 212 | 213 | virtual ~Binding() = default; 214 | 215 | /** A Binding cannot be copy constructed. */ 216 | Binding(Binding const &other) = delete; 217 | /** A Binding cannot be copy assigned. */ 218 | Binding &operator=(Binding const &other) = delete; 219 | 220 | /** A Binding can not be move constructed. */ 221 | Binding(Binding &&other) = delete; 222 | /** A Binding can not be move assigned. */ 223 | Binding &operator=(Binding &&other) = delete; 224 | 225 | void markDirty() override 226 | { 227 | Binding::evaluate(); 228 | } 229 | }; 230 | 231 | /** 232 | * @brief Helper function to create an immediate mode Binding from a Property. 233 | * 234 | * @tparam T The type of the value that the Binding expression evaluates to. 235 | * @param property The Property to create a Binding from. 236 | * @return std::unique_ptr> 237 | * An new Binding bound to an existing Property with immediate evaluation. 238 | * 239 | * *Note: For the difference between makeBinding and makeBoundProperty, see the 240 | * ["Reassigning a Binding"](../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.* 241 | */ 242 | template 243 | inline std::unique_ptr> makeBinding(Property &property) 244 | { 245 | return std::make_unique>(Private::makeNode(property)); 246 | } 247 | 248 | /** 249 | * @brief Creates an immediate mode binding from a const property. 250 | * 251 | * @tparam T The type of the value that the Binding expression evaluates to. 252 | * @param property The const Property to create a Binding from. 253 | * @return std::unique_ptr> A new Binding that is powered by an 254 | * immediate mode evaluator, ensuring quick updates to changes. 255 | * 256 | * *Note: This binding type is especially suited for scenarios where you need to reflect 257 | * changes in a property to some dependent components without the need to alter the 258 | * source property itself.* 259 | */ 260 | template 261 | inline std::unique_ptr> makeBinding(const Property &property) 262 | { 263 | return std::make_unique>(Private::makeNode(property)); 264 | } 265 | 266 | /** 267 | * @brief Helper function to create an immediate mode Binding from a root Node. 268 | * 269 | * @tparam T The type of the value that the Binding expression evaluates to. 270 | * @param rootNode Represents the expression that will be evaluated by the Binding. 271 | * Typically constructed from a unary/binary operator on a Property. 272 | * @return std::unique_ptr> An new Binding bound to a root Node with immediate evaluation. 273 | * 274 | * *Note: For the difference between makeBinding and makeBoundProperty, see the 275 | * ["Reassigning a Binding"](../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.* 276 | */ 277 | template 278 | inline std::unique_ptr> makeBinding(Private::Node &&rootNode) 279 | { 280 | return std::make_unique>(std::move(rootNode)); 281 | } 282 | 283 | /** 284 | * @brief Helper function to create an immediate mode Binding from a function and its arguments. 285 | * 286 | * @tparam Func The type of the function - may be any type that implements operator(). 287 | * @param func The function object. 288 | * @tparam Args The function argument types 289 | * @param args The function arguments - Possible values include: Properties, Constants and Nodes 290 | * They will be automatically unwrapped, i.e. a Property will pass a value of type T to func. 291 | * @return std::unique_ptr> where ReturnType is the type that results from evaluationg func with the given arguments. 292 | * The Binding will feature immediate evaluation. 293 | * 294 | * *Note: For the difference between makeBinding and makeBoundProperty, see the 295 | * ["Reassigning a Binding"](../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.* 296 | */ 297 | template, typename ResultType = Private::operator_node_result_t> 298 | inline std::unique_ptr> makeBinding(Func &&func, Args &&...args) 299 | { 300 | return std::make_unique>(Private::makeNode(std::forward(func), std::forward(args)...)); 301 | } 302 | 303 | /** 304 | * @brief Helper function to create a Property with a Binding. 305 | * 306 | * This function can take: 307 | * - Another Property. 308 | * - A Node, typically created by combining Property instances using operators. 309 | * - A function with arguments (Nodes, Constants or Properties) 310 | * By default this will construct a Property with an immediate binding evaluation. 311 | * 312 | * Alternatively a BindingEvaluator can be passed as the first argument to this function to control 313 | * when evaluation takes place. 314 | * 315 | * See the documentation for the various overloads of the free @ref makeBinding function for a 316 | * detailed description of which arguments can be used in which order. 317 | * 318 | * Examples: 319 | * - @ref 05-property-bindings/main.cpp 320 | * - @ref 06-lazy-property-bindings/main.cpp 321 | * 322 | * @return Property A new Property that is bound to the inputs 323 | * 324 | * *Note: For the difference between makeBinding and makeBoundProperty, see the 325 | * ["Reassigning a Binding"](../getting-started/data-binding/#reassigning-a-binding) section in the Getting Started guide.* 326 | */ 327 | template 328 | inline auto makeBoundProperty(T &&...args) 329 | { 330 | auto binding = makeBinding(std::forward(args)...); 331 | return Propertyget())>(std::move(binding)); 332 | } 333 | 334 | } // namespace KDBindings 335 | -------------------------------------------------------------------------------- /src/kdbindings/binding_evaluator.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace KDBindings { 19 | 20 | /** 21 | * @brief A BindingEvaluator provides a mechanism to control the exact time 22 | * when a KDBindings::Binding is reevaluated. 23 | * 24 | * A BindingEvaluator represents a collection of Binding instances that can be 25 | * selectively reevaluated. 26 | * 27 | * If a Binding is created using KDBindings::makeBoundProperty with a BindingEvaluator, 28 | * the Binding will only be evaluated if BindingEvaluator::evaluateAll is called 29 | * on the given evaluator. 30 | * 31 | * Note that instances of BindingEvaluator internally wrap their collection of 32 | * Bindings in such a way that copying a BindingEvaluator does not actually 33 | * copy the collection of Bindings. Therefore adding a Binding to a copy of a 34 | * BindingEvaluator will also add it to the original. 35 | * This is done for ease of use, so evaluators can be passed around easily throughout 36 | * the codebase. 37 | * 38 | * Examples: 39 | * - @ref 06-lazy-property-bindings/main.cpp 40 | */ 41 | class BindingEvaluator 42 | { 43 | // We use pimpl here so that we can pass evaluators around by value (copies) 44 | // yet each copy refers to the same set of data 45 | struct Private { 46 | // TODO: Use std::vector here? 47 | std::map> m_bindingEvalFunctions; 48 | int m_currentId; 49 | }; 50 | 51 | public: 52 | /** A BindingEvaluator can be default constructed */ 53 | BindingEvaluator() = default; 54 | 55 | /** 56 | * A BindingEvaluator can be copy constructed. 57 | * 58 | * Note that copying the evaluator will NOT create a new collection of 59 | * Binding instances, but the new evaluator will refer to the same collection, 60 | * so creating a new Binding with this evaluator will also modify the previous one. 61 | */ 62 | BindingEvaluator(const BindingEvaluator &) noexcept = default; 63 | 64 | /** 65 | * A BindingEvaluator can be copy assigned. 66 | * 67 | * Note that copying the evaluator will NOT create a new collection of 68 | * Binding instances, but the new evaluator will refer to the same collection, 69 | * so creating a new Binding with this evaluator will also modify the previous one. 70 | */ 71 | BindingEvaluator &operator=(const BindingEvaluator &) noexcept = default; 72 | 73 | /** 74 | * A BindingEvaluator can not be move constructed. 75 | */ 76 | BindingEvaluator(BindingEvaluator &&other) noexcept = delete; 77 | 78 | /** 79 | * A BindingEvaluator can not be move assigned. 80 | */ 81 | BindingEvaluator &operator=(BindingEvaluator &&other) noexcept = delete; 82 | 83 | /** 84 | * This function evaluates all Binding instances that were constructed with this 85 | * evaluator, in the order they were inserted. 86 | * 87 | * It will therefore update the associated Property instances as well. 88 | */ 89 | void evaluateAll() const 90 | { 91 | // a std::map's ordering is deterministic, so the bindings are evaluated 92 | // in the order they were inserted, ensuring correct transitive dependency 93 | // evaluation. 94 | for (auto &[id, func] : m_d->m_bindingEvalFunctions) 95 | func(); 96 | } 97 | 98 | private: 99 | template 100 | int insert(BindingType *binding) 101 | { 102 | m_d->m_bindingEvalFunctions.insert({ ++(m_d->m_currentId), 103 | [=]() { binding->evaluate(); } }); 104 | return m_d->m_currentId; 105 | } 106 | 107 | void remove(int id) 108 | { 109 | m_d->m_bindingEvalFunctions.erase(id); 110 | } 111 | 112 | std::shared_ptr m_d{ std::make_shared() }; 113 | 114 | template 115 | friend class Binding; 116 | }; 117 | 118 | /** 119 | * This subclass of BindingEvaluator doesn't do anything special on its own. 120 | * It is used together with a template specialization of Binding to provide 121 | * old-school, immediate mode Bindings. 122 | * 123 | * Any Binding that is constructed with an ImmediateBindingEvaluator will not wait 124 | * for the evaluator to call evaluateAll, but rather evaluate the Binding immediately 125 | * when any of its bindables (i.e. Property instances) change. 126 | * This can lead to a Property Binding being evaluated many 127 | * times before the result is ever used in a typical GUI application. 128 | */ 129 | class ImmediateBindingEvaluator final : public BindingEvaluator 130 | { 131 | public: 132 | static inline ImmediateBindingEvaluator instance() 133 | { 134 | static ImmediateBindingEvaluator evaluator; 135 | return evaluator; 136 | } 137 | }; 138 | 139 | } // namespace KDBindings 140 | 141 | /** 142 | * @example 06-lazy-property-bindings/main.cpp 143 | * 144 | * An example of how to use KDBindings::BindingEvaluator together 145 | * with a KDBindings::Property to create a Property binding that is 146 | * only reevaluated on demand. 147 | * 148 | * The output of this example is: 149 | * ``` 150 | * The initial size of the image = 1920000 bytes 151 | * The new size of the image = 8294400 bytes 152 | * ``` 153 | * 154 | * Note the difference to @ref 05-property-bindings/main.cpp, where the 155 | * new size of the image is calculated twice. 156 | * 157 | * This feature is especially useful to reduce the performance impact of 158 | * bindings and to create bindings that only update in specific intervals. 159 | *
162 | */ 163 | -------------------------------------------------------------------------------- /src/kdbindings/connection_evaluator.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2023 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Shivam Kunwar 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include 18 | 19 | namespace KDBindings { 20 | 21 | /** 22 | * @brief Manages and evaluates deferred Signal connections. 23 | * 24 | * @warning Deferred connections are experimental and may be removed or changed in the future. 25 | * 26 | * The ConnectionEvaluator class is responsible for managing and evaluating connections 27 | * to Signals. It provides mechanisms to delay and control the evaluation of connections. 28 | * It therefore allows controlling when and on which thread slots connected to a Signal are executed. 29 | * 30 | * @see Signal::connectDeferred() 31 | */ 32 | class ConnectionEvaluator 33 | { 34 | 35 | public: 36 | /** ConnectionEvaluators are default constructible */ 37 | ConnectionEvaluator() = default; 38 | 39 | /** Connectionevaluators are not copyable */ 40 | // As it is designed to manage connections, 41 | // and copying it could lead to unexpected behavior, including duplication of connections and issues 42 | // related to connection lifetimes. Therefore, it is intentionally made non-copyable. 43 | ConnectionEvaluator(const ConnectionEvaluator &) noexcept = delete; 44 | 45 | ConnectionEvaluator &operator=(const ConnectionEvaluator &) noexcept = delete; 46 | 47 | /** ConnectionEvaluators are not moveable */ 48 | // As they are captures by-reference 49 | // by the Signal, so moving them would lead to a dangling reference. 50 | ConnectionEvaluator(ConnectionEvaluator &&other) noexcept = delete; 51 | 52 | ConnectionEvaluator &operator=(ConnectionEvaluator &&other) noexcept = delete; 53 | 54 | virtual ~ConnectionEvaluator() = default; 55 | 56 | /** 57 | * @brief Evaluate the deferred connections. 58 | * 59 | * This function is responsible for evaluating and executing deferred connections. 60 | * This function is thread safe. 61 | * 62 | * @warning Evaluating slots that throw an exception is currently undefined behavior. 63 | */ 64 | void evaluateDeferredConnections() 65 | { 66 | std::lock_guard lock(m_slotInvocationMutex); 67 | 68 | if (m_isEvaluating) { 69 | // We're already evaluating, so we don't want to re-enter this function. 70 | return; 71 | } 72 | m_isEvaluating = true; 73 | 74 | // Current best-effort error handling will remove any further invocations that were queued. 75 | // We could use a queue and use a `while(!empty) { pop_front() }` loop instead to avoid this. 76 | // However, we would then ideally use a ring-buffer to avoid excessive allocations, which isn't in the STL. 77 | try { 78 | for (auto &pair : m_deferredSlotInvocations) { 79 | pair.second(); 80 | } 81 | } catch (...) { 82 | // Best-effort: Reset the ConnectionEvaluator so that it at least doesn't execute the same erroneous slot multiple times. 83 | m_deferredSlotInvocations.clear(); 84 | m_isEvaluating = false; 85 | throw; 86 | } 87 | 88 | m_deferredSlotInvocations.clear(); 89 | m_isEvaluating = false; 90 | } 91 | 92 | protected: 93 | /** 94 | * @brief Called when a new slot invocation is added. 95 | * 96 | * This function can be overwritten by subclasses to get notified whenever a new invocation is added to this evaluator. 97 | * The default implementation does nothing and does not have to be called by subclasses when overriding. 98 | * 99 | * ⚠️ *Note that this function will be executed on the thread that enqueued the slot invocation (i.e. the thread that called .emit() on the signal), 100 | * which is usually not the thread that is responsible for evaluating the connections! 101 | * Therefore it is usually not correct to call evaluateDeferredConnections() within this function! 102 | * User code is responsible for ensuring that the threads are synchronized correctly.* 103 | * 104 | * For example, if you plan to evaluate (execute) the slot invocations in some "main" thread A 105 | * and a signal is emitted in thread B, than this method will be called on thread B. 106 | * It is a good place to "wake up" the event loop of thread A so that thread A can call `evaluateDeferredConnections()`. 107 | */ 108 | virtual void onInvocationAdded() { } 109 | 110 | private: 111 | template 112 | friend class Signal; 113 | 114 | void enqueueSlotInvocation(const ConnectionHandle &handle, const std::function &slotInvocation) 115 | { 116 | { 117 | std::lock_guard lock(m_slotInvocationMutex); 118 | m_deferredSlotInvocations.push_back({ handle, std::move(slotInvocation) }); 119 | } 120 | onInvocationAdded(); 121 | } 122 | 123 | // Note: This function is marked with noexcept but may theoretically encounter an exception and terminate the program if locking the mutex fails. 124 | // If this does happen though, there's likely something very wrong, so std::terminate is actually a reasonable way to handle this. 125 | // 126 | // In addition, we do need to use a recursive_mutex, as otherwise a slot from `enqueueSlotInvocation` may theoretically call this function and cause undefined behavior. 127 | void dequeueSlotInvocation(const ConnectionHandle &handle) noexcept 128 | { 129 | std::lock_guard lock(m_slotInvocationMutex); 130 | 131 | if (m_isEvaluating) { 132 | // It's too late, we're already evaluating the deferred connections. 133 | // We can't remove the invocation now, as it might be currently evaluated. 134 | // And removing any invocations would be undefined behavior as we would invalidate 135 | // the loop indices in `evaluateDeferredConnections`. 136 | return; 137 | } 138 | 139 | auto handleMatches = [&handle](const auto &invocationPair) { 140 | return invocationPair.first == handle; 141 | }; 142 | 143 | // Remove all invocations that match the handle 144 | m_deferredSlotInvocations.erase( 145 | std::remove_if(m_deferredSlotInvocations.begin(), m_deferredSlotInvocations.end(), handleMatches), 146 | m_deferredSlotInvocations.end()); 147 | } 148 | 149 | std::vector>> m_deferredSlotInvocations; 150 | // We need to use a recursive mutex here, as `evaluateDeferredConnections` executes arbitrary user code. 151 | // This may end up in a call to dequeueSlotInvocation, which locks the same mutex. 152 | // We'll also need to add a flag to make sure we don't actually dequeue invocations while we're evaluating them. 153 | std::recursive_mutex m_slotInvocationMutex; 154 | bool m_isEvaluating = false; 155 | }; 156 | } // namespace KDBindings 157 | -------------------------------------------------------------------------------- /src/kdbindings/connection_handle.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | namespace KDBindings { 19 | 20 | template 21 | class Signal; 22 | 23 | class ConnectionHandle; 24 | 25 | namespace Private { 26 | // 27 | // This class defines a virtual interface, that the Signal this ConnectionHandle refers 28 | // to must implement. 29 | // It allows ConnectionHandle to refer to this non-template class, which then dispatches 30 | // to the template implementation using virtual function calls. 31 | // It allows ConnectionHandle to be a non-template class. 32 | class SignalImplBase : public std::enable_shared_from_this 33 | { 34 | public: 35 | SignalImplBase() = default; 36 | 37 | virtual ~SignalImplBase() = default; 38 | 39 | virtual void disconnect(const ConnectionHandle &handle) noexcept = 0; 40 | virtual bool blockConnection(const GenerationalIndex &id, bool blocked) = 0; 41 | virtual bool isConnectionActive(const GenerationalIndex &id) const noexcept = 0; 42 | virtual bool isConnectionBlocked(const GenerationalIndex &id) const = 0; 43 | }; 44 | 45 | } // namespace Private 46 | 47 | /** 48 | * @brief A ConnectionHandle represents the connection of a Signal 49 | * to a slot (i.e. a function that is called when the Signal is emitted). 50 | * 51 | * It is returned from a Signal when a connection is created and used to 52 | * manage the connection by disconnecting, (un)blocking it and checking its state. 53 | **/ 54 | class ConnectionHandle 55 | { 56 | public: 57 | /** 58 | * A ConnectionHandle can be default constructed. 59 | * In this case the ConnectionHandle will not reference any active connection (i.e. isActive() will return false), 60 | * and not belong to any Signal. 61 | **/ 62 | ConnectionHandle() = default; 63 | 64 | /** 65 | * A ConnectionHandle can be copied. 66 | **/ 67 | ConnectionHandle(const ConnectionHandle &) noexcept = default; 68 | ConnectionHandle &operator=(const ConnectionHandle &) noexcept = default; 69 | 70 | /** 71 | * A ConnectionHandle can be moved. 72 | **/ 73 | ConnectionHandle(ConnectionHandle &&) noexcept = default; 74 | ConnectionHandle &operator=(ConnectionHandle &&) noexcept = default; 75 | 76 | /** 77 | * Disconnect the slot. 78 | * 79 | * When this function is called, the function that was passed to Signal::connect 80 | * to create this ConnectionHandle will no longer be called when the Signal is emitted. 81 | * 82 | * If the ConnectionHandle is not active or the connection has already been disconnected, 83 | * nothing happens. 84 | * 85 | * After this call, the ConnectionHandle will be inactive (i.e. isActive() returns false) 86 | * and will no longer belong to any Signal (i.e. belongsTo returns false). 87 | * 88 | * @warning While this function is marked with noexcept, it *may* terminate the program 89 | * if it is not possible to allocate memory or if mutex locking isn't possible. 90 | **/ 91 | void disconnect() noexcept 92 | { 93 | if (auto shared_impl = checkedLock()) { 94 | shared_impl->disconnect(*this); 95 | } 96 | 97 | // ConnectionHandle is no longer active; 98 | m_signalImpl.reset(); 99 | } 100 | 101 | /** 102 | * Check whether the connection of this ConnectionHandle is active. 103 | * 104 | * @return true if the ConnectionHandle refers to an active Signal 105 | * and the connection was not disconnected previously, false otherwise. 106 | **/ 107 | bool isActive() const 108 | { 109 | return static_cast(checkedLock()); 110 | } 111 | 112 | /** 113 | * Sets the block state of the connection. 114 | * If a connection is blocked, emitting the Signal will no longer call this 115 | * connections slot, until the connection is unblocked. 116 | * 117 | * Behaves the same as calling Signal::blockConnection with this 118 | * ConnectionHandle as argument. 119 | * 120 | * To temporarily block a connection, consider using an instance of ConnectionBlocker, 121 | * which offers a RAII-style implementation that makes sure the connection is always 122 | * returned to its original state. 123 | * 124 | * @param blocked The new blocked state of the connection. 125 | * @return whether the connection was previously blocked. 126 | * @throw std::out_of_range Throws if the connection is not active (i.e. isActive() returns false). 127 | **/ 128 | bool block(bool blocked) 129 | { 130 | if (auto shared_impl = checkedLock()) { 131 | return shared_impl->blockConnection(*m_id, blocked); 132 | } 133 | throw std::out_of_range("Cannot block a non-active connection!"); 134 | } 135 | 136 | /** 137 | * Checks whether the connection is currently blocked. 138 | * 139 | * To change the blocked state of a connection, call ConnectionHandle::block. 140 | * 141 | * @return whether the connection is currently blocked. 142 | **/ 143 | bool isBlocked() const 144 | { 145 | if (auto shared_impl = checkedLock()) { 146 | return shared_impl->isConnectionBlocked(*m_id); 147 | } 148 | throw std::out_of_range("Cannot check whether a non-active connection is blocked!"); 149 | } 150 | 151 | /** 152 | * Check whether this ConnectionHandle belongs to the given Signal. 153 | * 154 | * @return true if this ConnectionHandle refers to a connection within the given Signal 155 | **/ 156 | template 157 | bool belongsTo(const Signal &signal) const 158 | { 159 | auto shared_impl = m_signalImpl.lock(); 160 | return shared_impl && shared_impl == std::static_pointer_cast(signal.m_impl); 161 | } 162 | 163 | // Define an operator== function to compare ConnectionHandle objects. 164 | bool operator==(const ConnectionHandle &other) const 165 | { 166 | auto thisSignalImpl = m_signalImpl.lock(); 167 | auto otherSignalImpl = other.m_signalImpl.lock(); 168 | 169 | // If both signalImpl pointers are valid, compare them along with the IDs. 170 | if (thisSignalImpl && otherSignalImpl) { 171 | return (thisSignalImpl == otherSignalImpl) && (m_id == other.m_id); 172 | } 173 | 174 | // If neither instance has an ID, and both signalImpl pointers are invalid, consider them equal. 175 | if (!m_id.has_value() && !other.m_id.has_value() && !thisSignalImpl && !otherSignalImpl) { 176 | return true; 177 | } 178 | 179 | // In all other cases, they are not equal. 180 | return false; 181 | } 182 | 183 | /** 184 | * This function exists to intentionally silence the [[nodiscard]] warning generated when connecting. 185 | * 186 | * In cases where you know for certain that the lifetime of the signal does not extend beyond the lifetime of the slot, you may not need the ConnectionHandle, so you can use release to discard it. 187 | * 188 | * Example: 189 | * ```cpp 190 | * bool called = false; 191 | * { 192 | * Signal mySignal; 193 | * // The signal will not outlive the reference to the `called` bool, so it's fine to ignore the ConnectionHandle. 194 | * mySignal.connect([&called](){ called = true; }).release(); 195 | * } 196 | * ``` 197 | */ 198 | void release() const { } 199 | 200 | private: 201 | template 202 | friend class Signal; 203 | 204 | std::weak_ptr m_signalImpl; 205 | std::optional m_id; 206 | 207 | // private, so it is only available from Signal 208 | ConnectionHandle(std::weak_ptr signalImpl, std::optional id) 209 | : m_signalImpl{ std::move(signalImpl) }, m_id{ std::move(id) } 210 | { 211 | } 212 | void setId(const Private::GenerationalIndex &id) 213 | { 214 | m_id = id; 215 | } 216 | 217 | // Checks that the weak_ptr can be locked and that the connection is 218 | // still active 219 | std::shared_ptr checkedLock() const noexcept 220 | { 221 | if (m_id.has_value()) { 222 | auto shared_impl = m_signalImpl.lock(); 223 | if (shared_impl && shared_impl->isConnectionActive(*m_id)) { 224 | return shared_impl; 225 | } 226 | } 227 | return nullptr; 228 | } 229 | }; 230 | 231 | /** 232 | * @brief A ScopedConnection is a RAII-style way to make sure a Connection is disconnected. 233 | * 234 | * When the ScopedConnections scope ends, the connection this ScopedConnection guards will be disconnected. 235 | * 236 | * Example: 237 | * - @ref 08-managing-connections/main.cpp 238 | */ 239 | class ScopedConnection 240 | { 241 | public: 242 | /** 243 | * @brief A ScopedConnection can be default constructed 244 | * 245 | * A default constructed ScopedConnection has no connection to guard. 246 | * Therefore it does nothing when it is destructed, unless a ConnectionHandle is assigned to it. 247 | */ 248 | ScopedConnection() = default; 249 | 250 | /** A ScopedConnection can be move constructed */ 251 | ScopedConnection(ScopedConnection &&) noexcept = default; 252 | 253 | /** A ScopedConnection cannot be copied */ 254 | ScopedConnection(const ScopedConnection &) = delete; 255 | /** A ScopedConnection cannot be copied */ 256 | ScopedConnection &operator=(const ScopedConnection &) = delete; 257 | 258 | /** A ScopedConnection can be move assigned */ 259 | ScopedConnection &operator=(ScopedConnection &&other) noexcept 260 | { 261 | m_connection.disconnect(); 262 | m_connection = std::move(other.m_connection); 263 | return *this; 264 | } 265 | 266 | /** 267 | * A ScopedConnection can be constructed from a ConnectionHandle 268 | */ 269 | ScopedConnection(ConnectionHandle &&h) noexcept 270 | : m_connection(std::move(h)) 271 | { 272 | } 273 | 274 | /** 275 | * A ScopedConnection can be assigned from a ConnectionHandle 276 | */ 277 | ScopedConnection &operator=(ConnectionHandle &&h) noexcept 278 | { 279 | return *this = ScopedConnection(std::move(h)); 280 | } 281 | 282 | /** 283 | * @return the handle to the connection this instance is managing 284 | */ 285 | ConnectionHandle &handle() 286 | { 287 | return m_connection; 288 | } 289 | 290 | /** 291 | * @overload 292 | */ 293 | const ConnectionHandle &handle() const 294 | { 295 | return m_connection; 296 | } 297 | 298 | /** 299 | * Convenience access to the underlying ConnectionHandle using the `->` operator. 300 | */ 301 | ConnectionHandle *operator->() 302 | { 303 | return &m_connection; 304 | } 305 | 306 | /** 307 | * @overload 308 | */ 309 | const ConnectionHandle *operator->() const 310 | { 311 | return &m_connection; 312 | } 313 | 314 | /** 315 | * When a ConnectionHandle is destructed it disconnects the connection it guards. 316 | * 317 | * @warning While this function isn't marked as throwing, it *may* throw and terminate the program 318 | * if it is not possible to allocate memory or if mutex locking isn't possible. 319 | */ 320 | ~ScopedConnection() noexcept 321 | { 322 | m_connection.disconnect(); 323 | } 324 | 325 | private: 326 | ConnectionHandle m_connection; 327 | }; 328 | 329 | } // namespace KDBindings 330 | -------------------------------------------------------------------------------- /src/kdbindings/genindex_array.h: -------------------------------------------------------------------------------- 1 | /* 2 | This code has been adapted from MIT licensed code, originally by Jeremy burns and available at 3 | https://gist.github.com/jaburns/ca72487198832f6203e831133ffdfff4. 4 | The original license is provided below: 5 | 6 | Copyright 2021 Jeremy Burns 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 9 | (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 10 | publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 11 | subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 16 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 17 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 18 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | */ 20 | 21 | #pragma once 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | namespace KDBindings { 33 | 34 | namespace Private { 35 | 36 | struct GenerationalIndex { 37 | uint32_t index = 0; 38 | uint32_t generation = 0; 39 | 40 | bool operator==(const GenerationalIndex &rhs) const 41 | { 42 | return index == rhs.index && generation == rhs.generation; 43 | } 44 | }; 45 | 46 | class GenerationalIndexAllocator 47 | { 48 | struct AllocatorEntry { 49 | bool isLive = false; 50 | uint32_t generation = 0; 51 | }; 52 | 53 | std::vector m_entries; 54 | std::vector m_freeIndices; 55 | 56 | public: 57 | GenerationalIndex allocate() 58 | { 59 | if (m_freeIndices.size() > 0) { 60 | uint32_t index = m_freeIndices.back(); 61 | m_freeIndices.pop_back(); 62 | 63 | m_entries[index].generation += 1; 64 | m_entries[index].isLive = true; 65 | 66 | return { index, m_entries[index].generation }; 67 | } else { 68 | // check that we are still within the bounds of uint32_t 69 | // (parentheses added to avoid Windows min/max macros) 70 | if (m_entries.size() + 1 >= (std::numeric_limits::max)()) { 71 | throw std::length_error(std::string("Maximum number of values inside GenerationalIndexArray reached: ") + std::to_string(m_entries.size())); 72 | } 73 | 74 | m_entries.push_back({ true, 0 }); 75 | return { static_cast(m_entries.size()) - 1, 0 }; 76 | } 77 | } 78 | 79 | bool deallocate(GenerationalIndex index) 80 | { 81 | if (isLive(index)) { 82 | m_entries[index.index].isLive = false; 83 | m_freeIndices.emplace_back(index.index); 84 | return true; 85 | } 86 | 87 | return false; 88 | } 89 | 90 | bool isLive(GenerationalIndex index) const noexcept 91 | { 92 | return index.index < m_entries.size() && 93 | m_entries[index.index].generation == index.generation && 94 | m_entries[index.index].isLive; 95 | } 96 | }; 97 | 98 | // A GenerationalIndexArray stores elements in contiguous memory just like an std::vector 99 | // and also allows items to be retrieved in constant time through indexed access, but it keeps 100 | // track of the "version"/generation of values at indices so that it can inform an accessor 101 | // when the item at the index it is trying to access is no longer the item that it wants. 102 | template 103 | class GenerationalIndexArray 104 | { 105 | struct Entry { 106 | uint32_t generation; 107 | T value; 108 | }; 109 | 110 | // TODO: m_entries never shrinks after an entry has been deleted, it might be 111 | // a good idea to add a "trim" function at some point if this becomes an issue 112 | 113 | std::vector> m_entries; 114 | GenerationalIndexAllocator m_allocator; 115 | 116 | public: 117 | // Sets the value at a specific index inside the array 118 | void set(const GenerationalIndex index, T &&value) 119 | { 120 | while (m_entries.size() <= index.index) 121 | m_entries.emplace_back(std::nullopt); 122 | 123 | #ifndef NDEBUG 124 | uint32_t previousGeneration = 0; 125 | 126 | const auto &previousEntry = m_entries[index.index]; 127 | if (previousEntry) 128 | previousGeneration = previousEntry->generation; 129 | 130 | assert(index.generation >= previousGeneration); 131 | #endif 132 | 133 | m_entries[index.index] = std::optional{ { index.generation, std::move(value) } }; 134 | } 135 | 136 | // Insert a value at the first free index and get the index back 137 | GenerationalIndex insert(T &&value) 138 | { 139 | const auto index = m_allocator.allocate(); 140 | set(index, std::move(value)); 141 | return index; 142 | } 143 | 144 | // Erase the value at the specified index and free up the index again 145 | void erase(GenerationalIndex index) 146 | { 147 | if (m_allocator.deallocate(index)) 148 | m_entries[index.index] = std::nullopt; 149 | } 150 | 151 | // Get a pointer to the value at the specified index 152 | T *get(GenerationalIndex index) 153 | { 154 | if (index.index >= m_entries.size()) 155 | return nullptr; 156 | 157 | auto &entry = m_entries[index.index]; 158 | if (entry && entry->generation == index.generation) { 159 | return &entry->value; 160 | } 161 | 162 | return nullptr; 163 | } 164 | 165 | // Get a const pointer to the value at the specified index 166 | const T *get(GenerationalIndex index) const noexcept 167 | { 168 | return const_cast(const_cast(this)->get(index)); 169 | } 170 | 171 | // Erase all the values in the array and thus free up all indices too 172 | void clear() 173 | { 174 | const auto numEntries = entriesSize(); 175 | 176 | for (auto i = decltype(numEntries){ 0 }; i < numEntries; ++i) { 177 | const auto index = indexAtEntry(i); 178 | 179 | if (index != std::nullopt) 180 | erase(*index); 181 | } 182 | } 183 | 184 | // The number entries currently in the array, not all necessarily correspond to valid indices, 185 | // use "indexAtEntry" to translate from an entry index to a optional GenerationalIndex 186 | uint32_t entriesSize() const noexcept 187 | { 188 | // this cast is safe because the allocator checks that we never exceed the capacity of uint32_t 189 | return static_cast(m_entries.size()); 190 | } 191 | 192 | // Convert an entry index into a GenerationalIndex, if possible otherwise returns nullopt 193 | std::optional indexAtEntry(uint32_t entryIndex) const 194 | { 195 | if (entryIndex >= entriesSize()) 196 | return std::nullopt; 197 | 198 | const auto &entry = m_entries[entryIndex]; 199 | if (!entry) 200 | return std::nullopt; 201 | 202 | GenerationalIndex index = { entryIndex, entry->generation }; 203 | 204 | if (m_allocator.isLive(index)) 205 | return index; 206 | 207 | return std::nullopt; 208 | } 209 | }; 210 | 211 | } // namespace Private 212 | 213 | } // namespace KDBindings 214 | -------------------------------------------------------------------------------- /src/kdbindings/make_node.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | namespace KDBindings { 18 | 19 | namespace Private { 20 | 21 | template 22 | struct bindable_value_type_ { 23 | using type = T; 24 | }; 25 | 26 | template 27 | struct bindable_value_type_> { 28 | using type = T; 29 | }; 30 | 31 | template 32 | struct bindable_value_type_> { 33 | using type = T; 34 | }; 35 | 36 | template 37 | struct bindable_value_type_> { 38 | using type = T; 39 | }; 40 | 41 | template 42 | struct bindable_value_type_> { 43 | using type = T; 44 | }; 45 | 46 | template 47 | struct bindable_value_type : bindable_value_type_> { 48 | }; 49 | 50 | template 51 | using bindable_value_type_t = typename bindable_value_type::type; 52 | 53 | // Find the type of a Node wrapping an operator and arguments 54 | template 55 | using operator_node_result = 56 | std::decay< 57 | std::invoke_result_t< 58 | std::decay_t, 59 | bindable_value_type_t...>>; 60 | 61 | template 62 | using operator_node_result_t = typename operator_node_result::type; 63 | 64 | // Node creation helpers 65 | template 66 | inline Node> makeNode(T &&value) 67 | { 68 | return Node>(std::make_unique>>(std::move(value))); 69 | } 70 | 71 | template 72 | inline Node makeNode(Property &property) 73 | { 74 | return Node(std::make_unique>(property)); 75 | } 76 | 77 | template 78 | inline Node makeNode(const Property &property) 79 | { 80 | return Node(std::make_unique>(property)); 81 | } 82 | 83 | template 84 | inline Node makeNode(Node &&node) 85 | { 86 | return std::move(node); 87 | } 88 | 89 | template= 1>, typename ResultType = operator_node_result_t> 90 | inline Node makeNode(Operator &&op, Ts &&...args) 91 | { 92 | return Node(std::make_unique, bindable_value_type_t...>>( 93 | std::forward(op), 94 | makeNode(std::forward(args))...)); 95 | } 96 | 97 | // Needed by function and operator helpers 98 | template 99 | struct is_bindable : std::integral_constant< 100 | bool, 101 | is_property::value || is_node::value> { 102 | }; 103 | 104 | } // namespace Private 105 | 106 | } // namespace KDBindings 107 | -------------------------------------------------------------------------------- /src/kdbindings/node.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | namespace KDBindings { 24 | 25 | /** 26 | * @brief A PropertyDestroyedError is thrown whenever a binding is evaluated 27 | * that references a property that no longer exists. 28 | */ 29 | class PropertyDestroyedError : public std::runtime_error 30 | { 31 | public: 32 | PropertyDestroyedError() = delete; 33 | 34 | using std::runtime_error::runtime_error; 35 | }; 36 | 37 | namespace Private { 38 | 39 | class Dirtyable 40 | { 41 | public: 42 | virtual ~Dirtyable() = default; 43 | 44 | Dirtyable() = default; 45 | 46 | void setParent(Dirtyable *newParent) 47 | { 48 | auto **parentVar = parentVariable(); 49 | if (parentVar) { 50 | *parentVar = newParent; 51 | } 52 | } 53 | 54 | // overridden by Binding 55 | virtual void markDirty() 56 | { 57 | auto *dirtyVar = dirtyVariable(); 58 | if (dirtyVar) { 59 | if (*dirtyVar) { 60 | return; 61 | // We are already dirty, don't bother marking the whole tree again. 62 | } 63 | 64 | // we only want to have one override for dirtyVariable, 65 | // which is const, so we have to const cast here. 66 | *const_cast(dirtyVar) = true; 67 | } 68 | 69 | auto **parentVar = parentVariable(); 70 | if (parentVar && *parentVar) { 71 | (*parentVar)->markDirty(); 72 | } 73 | } 74 | 75 | bool isDirty() const 76 | { 77 | auto *dirtyVar = dirtyVariable(); 78 | return dirtyVar && *dirtyVar; 79 | } 80 | 81 | protected: 82 | virtual Dirtyable **parentVariable() = 0; 83 | virtual const bool *dirtyVariable() const = 0; 84 | }; 85 | 86 | template 87 | class NodeInterface : public Dirtyable 88 | { 89 | public: 90 | // Returns a reference, because we cache each evaluated value. 91 | // const, because it shouldn't modify the return value of the AST. 92 | // Requires mutable caches 93 | virtual const ResultType &evaluate() const = 0; 94 | 95 | protected: 96 | NodeInterface() = default; 97 | }; 98 | 99 | template 100 | class Node 101 | { 102 | public: 103 | Node(std::unique_ptr> &&nodeInterface) 104 | : m_interface(std::move(nodeInterface)) 105 | { 106 | } 107 | 108 | const ResultType &evaluate() const 109 | { 110 | return m_interface->evaluate(); 111 | } 112 | 113 | void setParent(Dirtyable *newParent) 114 | { 115 | m_interface->setParent(newParent); 116 | } 117 | 118 | bool isDirty() const 119 | { 120 | return m_interface->isDirty(); 121 | } 122 | 123 | private: 124 | std::unique_ptr> m_interface; 125 | }; 126 | 127 | template 128 | class ConstantNode : public NodeInterface 129 | { 130 | public: 131 | explicit ConstantNode(const T &value) 132 | : m_value{ value } 133 | { 134 | } 135 | 136 | const T &evaluate() const override 137 | { 138 | return m_value; 139 | } 140 | 141 | protected: 142 | // A constant can never be dirty, so it doesn't need to 143 | // know its parent, as it doesn't have to notify it. 144 | Dirtyable **parentVariable() override { return nullptr; } 145 | const bool *dirtyVariable() const override { return nullptr; } 146 | 147 | private: 148 | T m_value; 149 | }; 150 | 151 | template 152 | class PropertyNode : public NodeInterface 153 | { 154 | public: 155 | explicit PropertyNode(const Property &property) 156 | : m_parent(nullptr), m_dirty(false) 157 | { 158 | setProperty(property); 159 | } 160 | 161 | // PropertyNodes cannot be moved 162 | PropertyNode(PropertyNode &&) = delete; 163 | 164 | PropertyNode(const PropertyNode &other) 165 | : Dirtyable(other.isDirty()) 166 | { 167 | setProperty(*other.m_property); 168 | } 169 | 170 | virtual ~PropertyNode() 171 | { 172 | m_valueChangedHandle.disconnect(); 173 | m_movedHandle.disconnect(); 174 | m_destroyedHandle.disconnect(); 175 | } 176 | 177 | const PropertyType &evaluate() const override 178 | { 179 | if (!m_property) { 180 | throw PropertyDestroyedError("The Property this node refers to no longer exists!"); 181 | } 182 | 183 | m_dirty = false; 184 | return m_property->get(); 185 | } 186 | 187 | void propertyMoved(const Property &property) 188 | { 189 | if (&property != m_property) { 190 | m_property = &property; 191 | } else { 192 | // Another property was moved into the property this node refers to. 193 | // Therefore it will no longer update this Node. 194 | m_property = nullptr; 195 | } 196 | } 197 | 198 | void propertyDestroyed() 199 | { 200 | m_property = nullptr; 201 | } 202 | 203 | protected: 204 | Dirtyable **parentVariable() override { return &m_parent; } 205 | const bool *dirtyVariable() const override { return &m_dirty; } 206 | 207 | private: 208 | void setProperty(const Property &property) 209 | { 210 | m_property = &property; 211 | 212 | // Connect to all signals, even for const properties 213 | m_valueChangedHandle = m_property->valueChanged().connect([this]() { this->markDirty(); }); 214 | m_movedHandle = m_property->m_moved.connect([this](const Property &newProp) { this->propertyMoved(newProp); }); 215 | m_destroyedHandle = m_property->destroyed().connect([this]() { this->propertyDestroyed(); }); 216 | } 217 | 218 | const Property *m_property; 219 | ConnectionHandle m_movedHandle; 220 | ConnectionHandle m_valueChangedHandle; 221 | ConnectionHandle m_destroyedHandle; 222 | 223 | Dirtyable *m_parent; 224 | mutable bool m_dirty; 225 | }; 226 | 227 | template 228 | class OperatorNode : public NodeInterface 229 | { 230 | public: 231 | // add another typename template for the Operator type, so 232 | // it can be a universal reference. 233 | template 234 | explicit OperatorNode(Op &&op, Node &&...arguments) 235 | : m_parent{ nullptr }, m_dirty{ true /*dirty until reevaluated*/ }, m_op{ std::move(op) }, m_values{ std::move(arguments)... }, m_result(reevaluate()) 236 | { 237 | static_assert( 238 | std::is_convertible_v()...)), ResultType>, 239 | "The result of the Operator must be convertible to the ReturnType of the Node"); 240 | 241 | setParents<0>(); 242 | } 243 | 244 | template 245 | auto setParents() -> std::enable_if_t 246 | { 247 | } 248 | 249 | // The enable_if_t confuses clang-format into thinking the 250 | // first "<" is a comparison, and not the second. 251 | // clang-format off 252 | template 253 | auto setParents() -> std::enable_if_t 254 | // clang-format on 255 | { 256 | std::get(m_values).setParent(this); 257 | setParents(); 258 | } 259 | 260 | virtual ~OperatorNode() = default; 261 | 262 | const ResultType &evaluate() const override 263 | { 264 | if (Dirtyable::isDirty()) { 265 | m_result = reevaluate(); 266 | } 267 | 268 | return m_result; 269 | } 270 | 271 | protected: 272 | Dirtyable **parentVariable() override { return &m_parent; } 273 | const bool *dirtyVariable() const override { return &m_dirty; } 274 | 275 | private: 276 | template 277 | ResultType reevaluate_helper(std::index_sequence) const 278 | { 279 | return m_op(std::get(m_values).evaluate()...); 280 | } 281 | 282 | ResultType reevaluate() const 283 | { 284 | m_dirty = false; 285 | 286 | return reevaluate_helper(std::make_index_sequence()); 287 | } 288 | 289 | Dirtyable *m_parent; 290 | mutable bool m_dirty; 291 | 292 | Operator m_op; 293 | std::tuple...> m_values; 294 | 295 | // Note: it is important that m_result is evaluated last! 296 | // Otherwise the call to reevaluate in the constructor will fail. 297 | mutable ResultType m_result; 298 | }; 299 | 300 | template 301 | struct is_node_helper : std::false_type { 302 | }; 303 | 304 | template 305 | struct is_node_helper> : std::true_type { 306 | }; 307 | 308 | template 309 | struct is_node : is_node_helper { 310 | }; 311 | 312 | } // namespace Private 313 | 314 | } // namespace KDBindings 315 | -------------------------------------------------------------------------------- /src/kdbindings/node_functions.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | 16 | #include 17 | 18 | namespace KDBindings { 19 | 20 | namespace Private { 21 | 22 | template 23 | struct any_bindables; 24 | 25 | // Check to see if a single type is a bindable (node or property) 26 | template 27 | struct any_bindables : is_bindable { 28 | }; 29 | 30 | // Check the head of the typelist and recurse 31 | template 32 | struct any_bindables : std::integral_constant< 33 | bool, 34 | any_bindables::value || any_bindables::value> { 35 | }; 36 | 37 | } // namespace Private 38 | 39 | /** 40 | * @brief KDBINDINGS_DECLARE_FUNCTION is a helper macro to declare and define functions for use in data binding. 41 | * 42 | * This macro can take any callable object or function reference and create a new function that may be used 43 | * in data binding expressions. 44 | * The result function that can be called with a Property or the result of a data binding expression 45 | * to create another data binding expression. 46 | * 47 | * Note that if a function is overloaded, it is impossible to reference all of its overloads at once. 48 | * Therefore we recommend declaring a struct with a templated operator() to use as the function object. 49 | * See the KDBindings::node_abs struct for an example of how to do this. 50 | * 51 | * @param NAME The name of the function to generate. 52 | * @param FUNC The function to wrap. 53 | */ 54 | #define KDBINDINGS_DECLARE_FUNCTION(NAME, FUNC) \ 55 | template \ 56 | inline auto NAME(Ts &&...args)->std::enable_if_t<::KDBindings::Private::any_bindables::value, ::KDBindings::Private::Node<::KDBindings::Private::operator_node_result_t>> \ 57 | { \ 58 | return ::KDBindings::Private::makeNode(FUNC, std::forward(args)...); \ 59 | } 60 | 61 | /** 62 | * @brief An example struct that is used with a call to KDBINDINGS_DECLARE_FUNCTION to declare all overloads 63 | * of std::abs as usable in data binding. 64 | * 65 | * Because of the way node_abs overloads its operator(), it can be used in a call to KDBINDINGS_DECLARE_FUNCTION like this: 66 | * @code 67 | * KDBINDINGS_DECLARE_FUNCTION(abs, node_abs{}) 68 | * @endcode 69 | * 70 | * To generate such a struct for another function, use the KDBINDINGS_DECLARE_FUNCTION_OBJECT macro. 71 | */ 72 | struct node_abs { 73 | /** 74 | * @brief The operator() is overloaded so the struct can be used as a function object. 75 | * 76 | * Because this operator is templated, a single instance of node_abs 77 | * can refer to all overloads of std::abs. 78 | */ 79 | template 80 | auto operator()(Ts &&...x) const 81 | { 82 | return std::abs(std::forward(x)...); 83 | } 84 | }; 85 | KDBINDINGS_DECLARE_FUNCTION(abs, node_abs{}) 86 | 87 | /** 88 | * @brief This macro declares a callable struct that wraps a function with all 89 | * its overloads. 90 | * 91 | * The declared struct can be used as the FUNCTION argument to 92 | * KDBINDINGS_DECLARE_FUNCTION(NAME, FUNCTION) to pass a function with 93 | * all its overloads to the macro. 94 | * 95 | * See the KDBindings::node_abs struct for an example of what this macro would generate. 96 | * 97 | * @param NAME The name of the resulting struct. 98 | * @param FUNCTION The function to wrap. 99 | */ 100 | #define KDBINDINGS_DECLARE_FUNCTION_OBJECT(NAME, FUNCTION) \ 101 | struct NAME { \ 102 | template \ 103 | auto operator()(Ts &&...x) const \ 104 | { \ 105 | return FUNCTION(std::forward(x)...); \ 106 | } \ 107 | }; 108 | 109 | /** 110 | * @brief This macro allows you to declare any function in a non-nested namespace 111 | * as available in the context of data binding. 112 | * 113 | * @param NAMESPACE the name of the namespace the function is in. 114 | * @param NAME the name of the function to wrap. 115 | * 116 | * In comparison to KDBINDINGS_DECLARE_FUNCTION(NAME, FUNC), this macro will generate a 117 | * helper struct using #KDBINDINGS_DECLARE_FUNCTION_OBJECT, so all overloads of the function are 118 | * made available at once. 119 | * 120 | * #KDBINDINGS_DECLARE_STD_FUNCTION is basically just a call to this macro with 121 | * the NAMESPACE parameter set to `std`. 122 | */ 123 | #define KDBINDINGS_DECLARE_NAMESPACED_FUNCTION(NAMESPACE, NAME) \ 124 | KDBINDINGS_DECLARE_FUNCTION_OBJECT(node_##NAMESPACE_##NAME, NAMESPACE::NAME) \ 125 | KDBINDINGS_DECLARE_FUNCTION(NAME, node_##NAMESPACE_##NAME{}) 126 | 127 | /** 128 | * @brief This macro is based on KDBINDINGS_DECLARE_NAMESPACED_FUNCTION(NAMESPACE, FUNC) 129 | * to make it easier to declare any standard library function as available for data binding. 130 | * 131 | * It uses #KDBINDINGS_DECLARE_NAMESPACED_FUNCTION and can therefore make all overloads 132 | * of the `std::` function available at once. 133 | * 134 | * @param NAME The name of the function in the `std::` namespace. 135 | */ 136 | #define KDBINDINGS_DECLARE_STD_FUNCTION(NAME) \ 137 | KDBINDINGS_DECLARE_NAMESPACED_FUNCTION(std, NAME) 138 | 139 | // Define some common and useful functions 140 | KDBINDINGS_DECLARE_STD_FUNCTION(floor) 141 | KDBINDINGS_DECLARE_STD_FUNCTION(ceil) 142 | KDBINDINGS_DECLARE_STD_FUNCTION(sin) 143 | KDBINDINGS_DECLARE_STD_FUNCTION(cos) 144 | KDBINDINGS_DECLARE_STD_FUNCTION(tan) 145 | KDBINDINGS_DECLARE_STD_FUNCTION(asin) 146 | KDBINDINGS_DECLARE_STD_FUNCTION(acos) 147 | KDBINDINGS_DECLARE_STD_FUNCTION(atan) 148 | 149 | } // namespace KDBindings 150 | -------------------------------------------------------------------------------- /src/kdbindings/node_operators.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | namespace KDBindings { 18 | 19 | // Helper macro to declare free standing unary operators for Property and Node 20 | 21 | #define KDBINDINGS_DEFINE_UNARY_OP(OP) \ 22 | template \ 23 | inline auto operator OP(Property &arg) noexcept(noexcept(OP arg.get())) \ 24 | ->Private::Node> \ 25 | { \ 26 | return Private::makeNode([](auto &&v) { return (OP v); }, arg); \ 27 | } \ 28 | \ 29 | template \ 30 | inline auto operator OP(Private::Node &&arg) noexcept(noexcept(OP arg.evaluate())) \ 31 | ->Private::Node> \ 32 | { \ 33 | return Private::makeNode([](auto &&v) { return (OP v); }, std::move(arg)); \ 34 | } 35 | 36 | KDBINDINGS_DEFINE_UNARY_OP(!) 37 | KDBINDINGS_DEFINE_UNARY_OP(~) // Bitwise not 38 | KDBINDINGS_DEFINE_UNARY_OP(+) 39 | KDBINDINGS_DEFINE_UNARY_OP(-) 40 | 41 | // Helper macro to declare free standing binary operators for Property and Node. 42 | // The combinations we need are: 43 | // 44 | // operator op (Property &a, B&& b) [Property, value] 45 | // operator op (A&& a, Property &b) [value, Property] 46 | // operator op (Property &a, Property &b) [Property, Property] 47 | // 48 | // operator op (Node&& a, B&& b) [Node value] 49 | // operator op (A&& a, Node&& b) [value, Node] 50 | // operator op (Node&& a, Node&& b) [Node, Node] 51 | // 52 | // operator op (Property &a, Node&& b) [Property, Node] 53 | // operaotr op (Node&& a, Property &b) [Node, Property] 54 | 55 | #define KDBINDINGS_DEFINE_BINARY_OP(OP) \ 56 | template \ 57 | inline auto operator OP(Property &a, B &&b) noexcept(noexcept(a.get() OP b)) \ 58 | ->std::enable_if_t::value, \ 59 | Private::Node> \ 60 | { \ 61 | return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, a, std::forward(b)); \ 62 | } \ 63 | \ 64 | template \ 65 | inline auto operator OP(A &&a, Property &b) noexcept(noexcept(a OP b.get())) \ 66 | ->std::enable_if_t::value, \ 67 | Private::Node> \ 68 | { \ 69 | return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::forward(a), b); \ 70 | } \ 71 | \ 72 | template \ 73 | inline auto operator OP(Property &a, Property &b) noexcept(noexcept(a.get() OP b.get())) \ 74 | ->Private::Node \ 75 | { \ 76 | return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, a, b); \ 77 | } \ 78 | \ 79 | template \ 80 | inline auto operator OP(Private::Node &&a, B &&b) noexcept(noexcept(a.evaluate() OP b)) \ 81 | ->std::enable_if_t::value, \ 82 | Private::Node> \ 83 | { \ 84 | return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::move(a), std::forward(b)); \ 85 | } \ 86 | \ 87 | template \ 88 | inline auto operator OP(A &&a, Private::Node &&b) noexcept(noexcept(a OP b.evaluate())) \ 89 | ->std::enable_if_t::value, \ 90 | Private::Node> \ 91 | { \ 92 | return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::forward(a), std::move(b)); \ 93 | } \ 94 | \ 95 | template \ 96 | inline auto operator OP(Private::Node &&a, Private::Node &&b) noexcept(noexcept(a.evaluate() OP b.evaluate())) \ 97 | ->Private::Node \ 98 | { \ 99 | return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::move(a), std::move(b)); \ 100 | } \ 101 | \ 102 | template \ 103 | inline auto operator OP(Property &a, Private::Node &&b) noexcept(noexcept(a.get() OP b.evaluate())) \ 104 | ->Private::Node \ 105 | { \ 106 | return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, a, std::move(b)); \ 107 | } \ 108 | \ 109 | template \ 110 | inline auto operator OP(Private::Node &&a, Property &b) noexcept(noexcept(a.evaluate() OP b.get())) \ 111 | ->Private::Node \ 112 | { \ 113 | return Private::makeNode([](auto &&av, auto &&bv) { return (av OP bv); }, std::move(a), b); \ 114 | } 115 | 116 | KDBINDINGS_DEFINE_BINARY_OP(*) 117 | KDBINDINGS_DEFINE_BINARY_OP(/) 118 | KDBINDINGS_DEFINE_BINARY_OP(%) 119 | KDBINDINGS_DEFINE_BINARY_OP(+) 120 | KDBINDINGS_DEFINE_BINARY_OP(-) 121 | KDBINDINGS_DEFINE_BINARY_OP(<<) 122 | KDBINDINGS_DEFINE_BINARY_OP(>>) 123 | KDBINDINGS_DEFINE_BINARY_OP(<) 124 | KDBINDINGS_DEFINE_BINARY_OP(<=) 125 | KDBINDINGS_DEFINE_BINARY_OP(>) 126 | KDBINDINGS_DEFINE_BINARY_OP(>=) 127 | KDBINDINGS_DEFINE_BINARY_OP(==) 128 | KDBINDINGS_DEFINE_BINARY_OP(!=) 129 | KDBINDINGS_DEFINE_BINARY_OP(&) 130 | KDBINDINGS_DEFINE_BINARY_OP(^) 131 | KDBINDINGS_DEFINE_BINARY_OP(|) 132 | KDBINDINGS_DEFINE_BINARY_OP(&&) 133 | KDBINDINGS_DEFINE_BINARY_OP(||) 134 | 135 | } // namespace KDBindings 136 | -------------------------------------------------------------------------------- /src/kdbindings/property.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | #include 16 | 17 | #include 18 | #include 19 | #include 20 | 21 | namespace KDBindings { 22 | 23 | /** 24 | * @brief A namespace containing parts of KDBindings that are not part of the public API. 25 | * 26 | * The contents of this namespace may only be accessed by the implementation of KDBindings, they 27 | * are not part of KDBindings public API and may be altered at any time and provide no guarantees 28 | * of any kind when used directly. 29 | **/ 30 | namespace Private { 31 | 32 | template 33 | struct are_equality_comparable : std::false_type { 34 | }; 35 | 36 | template 37 | struct are_equality_comparable{}(std::declval(), std::declval()))>, 42 | bool>::value>> : std::true_type { 43 | }; 44 | 45 | template 46 | constexpr bool are_equality_comparable_v = are_equality_comparable::value; 47 | 48 | } // namespace Private 49 | 50 | /** 51 | * A ReadOnlyProperty is thrown when trying to set the value of a Property 52 | * that has a PropertyUpdater associated with it. 53 | * 54 | * Most commonly because the property holds the result of a binding expression. 55 | */ 56 | struct ReadOnlyProperty : std::runtime_error { 57 | ReadOnlyProperty() = delete; 58 | 59 | using std::runtime_error::runtime_error; 60 | }; 61 | 62 | /** 63 | * @brief An instance of the KDBindings::equal_to struct is used to decide whether 64 | * two values of type T are equal in the context of data binding. 65 | * 66 | * If a new value is assigned to a Property and the existing value is equal_to 67 | * the existing value, the Property will not emit the Property::valueChanged or 68 | * Property::valueAboutToChange signals and not change the stored value. 69 | * 70 | * By default, all classes T that are equality comparable using std::equal_to 71 | * delegate to std::equal_to for equality comparison. All other instances are 72 | * assumed to never be equal. 73 | * Therefore, to change the equality behavior of a Property, either: 74 | * - Implement operator== for T (std::equal_to uses operator== for equality comparison) 75 | * - Provide a template spezialization of KDBindings::equal_to and implement operator()() 76 | */ 77 | template 78 | struct equal_to { 79 | /** 80 | * This implementation of operator()() is only enabled if std::equal_to can be 81 | * used to compare values of type T. 82 | * In this case, std::equal_to is used to decide whether values of type T are equal. 83 | * 84 | * @return bool - Whether the values are equal. 85 | */ 86 | auto operator()(const T &x, const T &y) const noexcept 87 | -> std::enable_if_t, bool> 88 | { 89 | return std::equal_to<>{}(x, y); 90 | } 91 | 92 | /** 93 | * The fallback implementation of operator()() if the types are not equality comparable 94 | * using std::equal_to (i.e. no operator== implementation exists for this type). 95 | * In this case, two values of type T are assumed to never be equal. 96 | * 97 | * @return bool - Whether the values are equal - always false for this default implementation 98 | */ 99 | template 100 | auto operator()(const X &, const Y &) const noexcept 101 | -> std::enable_if_t, bool> 102 | { 103 | return false; 104 | } 105 | }; 106 | 107 | // This forwrad declaration is required so that 108 | // Property can declare PropertyNode as a friend 109 | // class. 110 | namespace Private { 111 | template 112 | class PropertyNode; 113 | } 114 | 115 | /** 116 | * @brief A property represents a value that can be part of or the result of data binding. 117 | * 118 | * Properties are at the basis of data binding. 119 | * They can contain a value of any type T. 120 | * The value can either represent the result of a data binding or a value that is used 121 | * in the calculation of a binding expression. 122 | * 123 | * If the value of a property is changed, either manually or because it is the result of a 124 | * binding expression, the Property will emit the valueAboutToChange(), and valueChanged() Signal. 125 | * If it is used as part of a binding expression, the expression will be marked 126 | * as dirty and (unless a custom BindingEvaluator is used) updated immediately. 127 | * 128 | * To create a property from a data binding expression, use the @ref makeBoundProperty or @ref makeBinding 129 | * functions in the @ref KDBindings namespace. 130 | * 131 | * Examples: 132 | * - @ref 04-simple-property/main.cpp 133 | * - @ref 05-property-bindings/main.cpp 134 | * - @ref 06-lazy-property-bindings/main.cpp 135 | */ 136 | template 137 | class Property 138 | { 139 | public: 140 | typedef T valuetype; 141 | 142 | /** 143 | * Properties are default constructable. 144 | * 145 | * The value of a default constructed property is then also default constructed. 146 | */ 147 | Property() = default; 148 | 149 | /** 150 | * If a Property is destroyed, it emits the destroyed() Signal. 151 | */ 152 | ~Property() 153 | { 154 | m_destroyed.emit(); 155 | } 156 | 157 | /** 158 | * Constructs a Property from the provided value. 159 | */ 160 | explicit Property(T value) noexcept(std::is_nothrow_move_constructible::value) 161 | : m_value{ std::move(value) } 162 | { 163 | } 164 | 165 | /** 166 | * Properties are not copyable. 167 | */ 168 | Property(Property const &other) = delete; 169 | Property &operator=(Property const &other) = delete; 170 | 171 | /** 172 | * @brief Properties are movable. 173 | * 174 | * All connections that were made to the signals of the original Property 175 | * are moved over to the newly-constructed Property. 176 | * 177 | * All data bindings that depend on the moved-from Property will update their references 178 | * to the newly move-constructed Property. 179 | */ 180 | Property(Property &&other) noexcept(std::is_nothrow_move_constructible::value) 181 | : m_value(std::move(other.m_value)) 182 | , m_valueAboutToChange(std::move(other.m_valueAboutToChange)) 183 | , m_valueChanged(std::move(other.m_valueChanged)) 184 | , m_destroyed(std::move(other.m_destroyed)) 185 | , m_updater(std::move(other.m_updater)) 186 | { 187 | // We do not move the m_moved signal yet so that objects interested in the moved-into 188 | // property can recreate any connections they need. 189 | 190 | // If we have an updater, let it know how to update our internal value 191 | if (m_updater) { 192 | using namespace std::placeholders; 193 | m_updater->setUpdateFunction( 194 | std::bind(&Property::setHelper, this, _1)); 195 | } 196 | 197 | // Emit the moved signals for the moved from and moved to properties 198 | m_moved.emit(*this); 199 | other.m_moved.emit(*this); 200 | m_moved = std::move(other.m_moved); 201 | } 202 | 203 | /** 204 | * See: Property(Property &&other) 205 | */ 206 | Property &operator=(Property &&other) noexcept(std::is_nothrow_move_assignable::value) 207 | { 208 | // We do not move the m_moved signal yet so that objects interested in the moved-into 209 | // property can recreate any connections they need. 210 | m_value = std::move(other.m_value); 211 | m_valueAboutToChange = std::move(other.m_valueAboutToChange); 212 | m_valueChanged = std::move(other.m_valueChanged); 213 | m_destroyed = std::move(other.m_destroyed); 214 | m_updater = std::move(other.m_updater); 215 | 216 | // If we have an updater, let it know how to update our internal value 217 | if (m_updater) { 218 | using namespace std::placeholders; 219 | m_updater->setUpdateFunction( 220 | std::bind(&Property::setHelper, this, _1)); 221 | } 222 | 223 | // Emit the moved signals for the moved from and moved to properties 224 | m_moved.emit(*this); 225 | other.m_moved.emit(*this); 226 | m_moved = std::move(other.m_moved); 227 | 228 | return *this; 229 | } 230 | 231 | /** 232 | * Construct a property that will be updated by the specified PropertyUpdater. 233 | * 234 | * This constructor is usually called by the creation of a data binding 235 | * and usually doesn't need to be called manually. 236 | */ 237 | template 238 | explicit Property(std::unique_ptr &&updater) 239 | { 240 | *this = std::move(updater); 241 | } 242 | 243 | /** 244 | * Assigns a Binding or other Updater to this Property. 245 | * 246 | * In comparison to the move assignment operator, this does NOT change any 247 | * of the existing Signal connections. They are all kept as-is. 248 | * Only the source of the update is changed. 249 | * 250 | * This will immediately set the value of this Property to the 251 | * result of the updater and will call the valueAboutToChange or valueChanged 252 | * Signals respectively if necessary. 253 | */ 254 | template 255 | Property &operator=(std::unique_ptr &&updater) 256 | { 257 | m_updater = std::move(updater); 258 | 259 | // Let the updater know how to update our internal value 260 | using namespace std::placeholders; 261 | m_updater->setUpdateFunction( 262 | std::bind(&Property::setHelper, this, _1)); 263 | 264 | // Now synchronise our value with whatever the updator has right now. 265 | setHelper(m_updater->get()); 266 | 267 | return *this; 268 | } 269 | 270 | /** 271 | * @brief Disconnects the binding from this Property 272 | * 273 | * If this Property has a binding, it will no longer update it. 274 | * Otherwise, this function does nothing. 275 | * 276 | * The value of the property does not change when it is reset. 277 | */ 278 | void reset() 279 | { 280 | m_updater.reset(); 281 | } 282 | 283 | /** 284 | * Returns a Signal that will be emitted before the value is changed. 285 | * 286 | * The first emitted value is the current value of the Property.
287 | * The second emitted value is the new value of the Property. 288 | */ 289 | Signal &valueAboutToChange() const { return m_valueAboutToChange; } 290 | 291 | /** 292 | * Returns a Signal that will be emitted after the value of the property changed. 293 | * 294 | * The emitted value is the current (new) value of the Property. 295 | */ 296 | Signal &valueChanged() const { return m_valueChanged; } 297 | 298 | /** 299 | * Returns a Signal that will be emitted when this Property is destructed. 300 | */ 301 | Signal<> &destroyed() const { return m_destroyed; } 302 | 303 | /** 304 | * Returns true if this Property has a binding associated with it. 305 | */ 306 | bool hasBinding() const noexcept { return m_updater.get() != nullptr; } 307 | 308 | /** 309 | * Assign a new value to this Property. 310 | * 311 | * If the new value is equal_to the existing value, the value will not be 312 | * changed and no Signal will be emitted. 313 | * 314 | * Otherwise, the valueAboutToChange() Signal will be emitted before the value 315 | * of the Property is changed. 316 | * Then, the provided value will be assigned, and the valueChanged() Signal 317 | * will be emitted. 318 | * 319 | * @throw ReadOnlyProperty If the Property has a PropertyUpdater associated with it (i.e. it is 320 | * the result of a binding expression). 321 | */ 322 | void set(T value) 323 | { 324 | if (m_updater) { 325 | throw ReadOnlyProperty{ 326 | "Cannot set value on a read-only property. This property likely holds the result of a binding expression." 327 | }; 328 | } 329 | setHelper(std::move(value)); 330 | } 331 | 332 | /** 333 | * Returns the value represented by this Property. 334 | */ 335 | T const &get() const 336 | { 337 | return m_value; 338 | } 339 | 340 | /** 341 | * Assigns a new value to this Property. 342 | * 343 | * See: set(). 344 | */ 345 | Property &operator=(T const &rhs) 346 | { 347 | set(std::move(rhs)); 348 | return *this; 349 | } 350 | 351 | /** 352 | * Returns the value represented by this Property. 353 | * 354 | * See: get(). 355 | */ 356 | T const &operator()() const 357 | { 358 | return Property::get(); 359 | } 360 | 361 | private: 362 | void setHelper(T value) 363 | { 364 | if (equal_to{}(value, m_value)) 365 | return; 366 | 367 | m_valueAboutToChange.emit(m_value, value); 368 | m_value = std::move(value); 369 | m_valueChanged.emit(m_value); 370 | } 371 | 372 | T m_value; 373 | // the signals in a property are mutable, as a property 374 | // being "const" should mean that it's value or binding does 375 | // not change, not that nobody can listen to it anymore. 376 | mutable Signal m_valueAboutToChange; 377 | mutable Signal m_valueChanged; // By const ref so we can emit the signal for move-only types of T e.g. std::unique_ptr 378 | 379 | // The PropertyNode needs to be a friend class of the Property, as it needs 380 | // access to the m_moved Signal. 381 | // The decision to make this Signal private was made after the suggestion by 382 | // @jm4R who reported issues with the move constructors noexcept guarantee. 383 | // (https://github.com/KDAB/KDBindings/issues/24) 384 | // Ideally we would like to figure out a way to remove the moved signal entirely 385 | // at some point. However currently it is still needed for Property bindings to 386 | // keep track of moved Properties. 387 | template 388 | friend class Private::PropertyNode; 389 | mutable Signal &> m_moved; 390 | 391 | mutable Signal<> m_destroyed; 392 | std::unique_ptr> m_updater; 393 | }; 394 | 395 | /** 396 | * Outputs the value of the Property onto an output stream. 397 | */ 398 | template 399 | std::ostream &operator<<(std::ostream &stream, Property const &property) 400 | { 401 | stream << property.get(); 402 | return stream; 403 | } 404 | 405 | /** 406 | * Reads a value of type T from the input stream and assigns it to 407 | * the Property using set(). 408 | */ 409 | template 410 | std::istream &operator>>(std::istream &stream, Property &prop) 411 | { 412 | T temp; 413 | stream >> temp; 414 | prop.set(std::move(temp)); 415 | return stream; 416 | } 417 | 418 | namespace Private { 419 | 420 | template 421 | struct is_property_helper : std::false_type { 422 | }; 423 | 424 | template 425 | struct is_property_helper> : std::true_type { 426 | }; 427 | 428 | template 429 | struct is_property : is_property_helper> { 430 | }; 431 | 432 | } // namespace Private 433 | 434 | /** 435 | * @example 04-simple-property/main.cpp 436 | * 437 | * An example of how to create a KDBindings::Property and use its valueChanged() KDBindings::Signal to receive notifications whenever the value of the KDBindigns::Property changes. 438 | * 439 | * The output of this example is: 440 | * ``` 441 | * The new value is 42 442 | * The new value is 69 443 | * Property value is 69 444 | * ``` 445 | */ 446 | 447 | /** 448 | * @example 05-property-bindings/main.cpp 449 | * 450 | * An example of how to use makeBoundProperty() to create a KDBindings::Property that is automatically updated once any of its inputs change. 451 | * 452 | * The output of this example is: 453 | * ``` 454 | * The initial size of the image = 1920000 bytes 455 | * The new size of the image = 4608000 bytes 456 | * The new size of the image = 8294400 bytes 457 | * ``` 458 | */ 459 | 460 | } // namespace KDBindings 461 | -------------------------------------------------------------------------------- /src/kdbindings/property_updater.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #pragma once 13 | 14 | #include 15 | 16 | namespace KDBindings { 17 | 18 | /** 19 | * @brief A PropertyUpdater defines the interface used to update a Property, e.g. from a binding expression. 20 | * 21 | * An instance of this class (wrapped in a std::unique_ptr) can be passed to the Property constructor. 22 | * The Property will then become read-only, meaning an instance of ReadOnlyProperty will be thrown if the 23 | * Property's value is updated through any other means than through the PropertyUpdater. 24 | * 25 | * The Property constructor will pass a function to setUpdateFunction() for this purpose. 26 | * This function is then the only way to update the Property without encountering a ReadOnlyProperty error. 27 | * 28 | * The most typical use of PropertyUpdater is in instances of Binding, which are created by makeBoundProperty(). 29 | */ 30 | template 31 | class PropertyUpdater 32 | { 33 | public: 34 | /** A PropertyUpdater can be default constructed. */ 35 | PropertyUpdater() = default; 36 | 37 | /** A PropertyUpdater has a virtual destructor. */ 38 | virtual ~PropertyUpdater() = default; 39 | 40 | /** A PropertyUpdater can be copy constructed. */ 41 | PropertyUpdater(PropertyUpdater const &other) = default; 42 | /** A PropertyUpdater can be copy assigned. */ 43 | PropertyUpdater &operator=(PropertyUpdater const &other) = default; 44 | 45 | /** A PropertyUpdater can be move constructed. */ 46 | PropertyUpdater(PropertyUpdater &&other) = default; 47 | /** A PropertyUpdater can be move assigned. */ 48 | PropertyUpdater &operator=(PropertyUpdater &&other) = default; 49 | 50 | /** 51 | * The Property will call this function when it constructed and pass a std::function as argument that allows 52 | * the PropertyUpdater to update the Property value. 53 | * 54 | * A PropertyUpdater typically saves this function and calls it once the value it computes changes. 55 | */ 56 | virtual void setUpdateFunction(std::function const &updateFunction) = 0; 57 | 58 | /** 59 | * The get() function must return the current value the PropertyUpdater wants to assign to the Property. 60 | * 61 | * It is called from the Property constructor. 62 | */ 63 | virtual T get() const = 0; 64 | }; 65 | 66 | } // namespace KDBindings 67 | -------------------------------------------------------------------------------- /src/kdbindings/utils.h: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Leon Matthes 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | namespace KDBindings { 18 | 19 | /** 20 | * The contents of this namespace may only be accessed by the implementation of KDBindings, they 21 | * are not part of KDBindings public API and may be altered at any time and provide no guarantees 22 | * of any kind when used directly. 23 | **/ 24 | namespace Private { 25 | 26 | // ------------------------ get_arity -------------------------- 27 | // get_arity is a template function that returns the number of arguments 28 | // of (almost) any callable object. 29 | // The easiest way is to simply call get_arity() for callable type T. 30 | // It needs to be constexpr in order so it can be used in template arguments. 31 | 32 | // To overload get_arity, it needs a marker type, as C++ doesn't allow partial 33 | // function specialization. 34 | template 35 | struct TypeMarker { 36 | constexpr TypeMarker() = default; 37 | }; 38 | 39 | // base implementation of get_arity refers to specialized implementations for each 40 | // type of callable object by using the overload for its specialized TypeMarker. 41 | template 42 | constexpr size_t get_arity() 43 | { 44 | return get_arity(TypeMarker>{}); 45 | } 46 | 47 | // Syntactic sugar version of get_arity, allows to pass any callable object 48 | // to get_arity, instead of having to pass its decltype as a template argument. 49 | template 50 | constexpr size_t get_arity(const T &) 51 | { 52 | return get_arity(); 53 | } 54 | 55 | // The arity of a function pointer is simply its number of arguments. 56 | template 57 | constexpr size_t get_arity(TypeMarker) 58 | { 59 | return sizeof...(Arguments); 60 | } 61 | 62 | template 63 | constexpr size_t get_arity(TypeMarker) 64 | { 65 | return sizeof...(Arguments); 66 | } 67 | 68 | // The arity of a generic callable object is the arity of its operator() - 1, as the this 69 | // pointer is already known for such an object. 70 | template 71 | constexpr size_t get_arity(TypeMarker) 72 | { 73 | return get_arity(TypeMarker{}) - 1; 74 | } 75 | 76 | // Macro to help define most combinations of possible member function qualifiers. 77 | // Add + 1 to sizeof...(Arguments) here as the "this" pointer is an implicit argument to any member function. 78 | #define KDBINDINGS_DEFINE_MEMBER_GET_ARITY(MODIFIERS) \ 79 | template \ 80 | constexpr size_t get_arity(::KDBindings::Private::TypeMarker) \ 81 | { \ 82 | return sizeof...(Arguments) + 1; \ 83 | } 84 | 85 | // Define the get_arity version without modifiers without using the macro. 86 | // MSVC otherwise complains about a call to the macro with too few arguments 87 | template 88 | constexpr size_t get_arity(::KDBindings::Private::TypeMarker) 89 | { 90 | return sizeof...(Arguments) + 1; 91 | } 92 | 93 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const) 94 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&) 95 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &) 96 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&&) 97 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &&) 98 | 99 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile) 100 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const) 101 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &) 102 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &) 103 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &&) 104 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &&) 105 | 106 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(noexcept) 107 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const noexcept) 108 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&noexcept) 109 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &noexcept) 110 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(&&noexcept) 111 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(const &&noexcept) 112 | 113 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile noexcept) 114 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const noexcept) 115 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &noexcept) 116 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &noexcept) 117 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile &&noexcept) 118 | KDBINDINGS_DEFINE_MEMBER_GET_ARITY(volatile const &&noexcept) 119 | 120 | // -------------------- placeholder and bind_first --------------------- 121 | // Inspired by https://gist.github.com/engelmarkus/fc1678adbed1b630584c90219f77eb48 122 | // A placeholder provides a way to construct something equivalent to a std::placeholders::_N 123 | // with N as a template argument. 124 | // 125 | // Note: As placeholders start at 1, therefore placeholder<0> is NOT a valid placeholder. 126 | template 127 | struct placeholder { 128 | }; 129 | 130 | template 131 | auto bind_first_helper(std::index_sequence, Func &&fun, Args... args) 132 | { 133 | return std::bind(std::forward(fun), std::forward(args)..., placeholder{}...); 134 | } 135 | 136 | // bind_first binds the first arguments to the callable object (i.e. function) to the values provided by args. 137 | // The return value is a new function taking get_arity - sizeof...(Args) many arguments, with the first 138 | // sizeof...(Args) arguments bound to the values of args. 139 | // This is different to a call with std::bind(fun, args...), as the callable object created by std::bind would 140 | // in this case now take zero arguments, whilst bind_first still expects the remaining arguments to be provided 141 | // 142 | // For now, providing instances of std::placeholders in Args is not allowed, as the implications of this are 143 | // unclear if sizeof...(Args) != get_arity. The enable_if_t makes sure none of the Args value is a placeholder. 144 | // 145 | // In the future, we could provide another overload of this function that allows placeholders, as long as all arguments 146 | // are bound. 147 | template< 148 | typename Func, 149 | typename... Args, 150 | /*Disallow any placeholder arguments, they would mess with the number and ordering of required and bound arguments, and are, for now, unsupported*/ 151 | typename = std::enable_if_t>...>>> 152 | auto bind_first(Func &&fun, Args &&...args) 153 | { 154 | return bind_first_helper(std::make_index_sequence() - sizeof...(Args)>{}, std::forward(fun), std::forward(args)...); 155 | } 156 | 157 | } // namespace Private 158 | 159 | } // namespace KDBindings 160 | 161 | namespace std { 162 | 163 | // This allows a placeholder to be used as a replacement of a std::placeholders. 164 | template 165 | struct is_placeholder> 166 | : integral_constant { 167 | }; 168 | 169 | } // namespace std 170 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | cmake_minimum_required(VERSION 3.12) 12 | project(KDBindings-Tests) 13 | 14 | # We use `SYSTEM` here, because we don't want to see warnings from doctest in clang-tidy. 15 | # See: https://www.reddit.com/r/cmake/comments/zhqq9f/comment/j34m17q/?utm_source=share&utm_medium=web2x&context=3 16 | include_directories(SYSTEM ./doctest) 17 | 18 | add_subdirectory(binding) 19 | add_subdirectory(node) 20 | add_subdirectory(property) 21 | add_subdirectory(signal) 22 | add_subdirectory(utils) 23 | -------------------------------------------------------------------------------- /tests/binding/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | test-binding 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} tst_binding.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | 20 | add_test(${PROJECT_NAME} ${PROJECT_NAME}) 21 | -------------------------------------------------------------------------------- /tests/node/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | test-node 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} tst_node.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | 20 | add_test(${PROJECT_NAME} ${PROJECT_NAME}) 21 | -------------------------------------------------------------------------------- /tests/property/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | test-property 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} tst_property.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | 20 | add_test(${PROJECT_NAME} ${PROJECT_NAME}) 21 | -------------------------------------------------------------------------------- /tests/property/tst_property.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Sean Harmer 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | 14 | #include 15 | 16 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 17 | #include 18 | 19 | // The expansion of TEST_CASE from doctest leads to a clazy warning. 20 | // As this issue originates from doctest, disable the warning. 21 | // clazy:excludeall=non-pod-global-static 22 | 23 | using namespace KDBindings; 24 | 25 | static_assert(std::is_nothrow_destructible>{}); 26 | static_assert(std::is_default_constructible>{}); 27 | static_assert(!std::is_copy_constructible>{}); 28 | static_assert(!std::is_copy_assignable>{}); 29 | static_assert(std::is_nothrow_move_constructible>{}); 30 | static_assert(std::is_nothrow_move_assignable>{}); 31 | 32 | struct CustomType { 33 | CustomType(int _a, uint64_t _b) 34 | : a(_a), b(_b) 35 | { 36 | } 37 | 38 | bool operator==(const CustomType &other) const 39 | { 40 | return a == other.a && b == other.b; 41 | } 42 | 43 | int a{ 0 }; 44 | uint64_t b{ 0 }; 45 | }; 46 | 47 | TEST_CASE("A property can be written to") 48 | { 49 | SUBCASE("Builtin type") 50 | { 51 | Property property(3); 52 | property = 7; 53 | REQUIRE(property.get() == 7); 54 | } 55 | 56 | SUBCASE("Custom type") 57 | { 58 | Property property(CustomType(3, 4)); 59 | property = { 6, 14 }; 60 | REQUIRE(property.get() == CustomType(6, 14)); 61 | } 62 | } 63 | 64 | struct ObjectWithSignal { 65 | void emitSignal() const 66 | { 67 | valueChanged.emit(); 68 | } 69 | 70 | int value{ 0 }; 71 | mutable Signal<> valueChanged; 72 | }; 73 | 74 | class ClassWithProperty 75 | { 76 | public: 77 | Property property; 78 | Signal<> changed; 79 | 80 | ClassWithProperty() 81 | { 82 | (void)property().valueChanged.connect( 83 | [this]() { 84 | this->changed.emit(); 85 | }); 86 | } 87 | }; 88 | 89 | TEST_CASE("An object with a Signal that is wrapped in a property can emit the Signal if it is mutable") 90 | { 91 | bool called = false; 92 | auto handler = [&called]() { called = true; }; 93 | 94 | ClassWithProperty outer; 95 | (void)outer.changed.connect(handler); 96 | 97 | outer.property().emitSignal(); 98 | 99 | REQUIRE(called == true); 100 | } 101 | 102 | class Handler 103 | { 104 | public: 105 | void doSomething(const int & /*value*/) 106 | { 107 | handlerCalled = true; 108 | } 109 | 110 | bool handlerCalled = false; 111 | }; 112 | 113 | class HandlerAboutToChange 114 | { 115 | public: 116 | void doSomething(const int & /*oldValue*/, const int & /*newValue*/) 117 | { 118 | handlerCalled = true; 119 | } 120 | 121 | bool handlerCalled = false; 122 | }; 123 | 124 | TEST_CASE("Signals") 125 | { 126 | SUBCASE("A property does not emit a signal when the value set is equal to the current value") 127 | { 128 | Property property(3); 129 | Handler handler; 130 | HandlerAboutToChange aboutToChangeHandler; 131 | 132 | (void)property.valueChanged().connect(&Handler::doSomething, &handler); 133 | (void)property.valueAboutToChange().connect(&HandlerAboutToChange::doSomething, &aboutToChangeHandler); 134 | 135 | property = 3; 136 | REQUIRE(property.get() == 3); 137 | REQUIRE_FALSE(handler.handlerCalled); 138 | REQUIRE_FALSE(aboutToChangeHandler.handlerCalled); 139 | } 140 | 141 | SUBCASE("A property does emit a signal when the value changes") 142 | { 143 | Property property(3); 144 | Handler handler; 145 | HandlerAboutToChange aboutToChangeHandler; 146 | 147 | (void)property.valueChanged().connect(&Handler::doSomething, &handler); 148 | (void)property.valueAboutToChange().connect(&HandlerAboutToChange::doSomething, &aboutToChangeHandler); 149 | 150 | property = 7; 151 | REQUIRE(property.get() == 7); 152 | REQUIRE(handler.handlerCalled); 153 | REQUIRE(aboutToChangeHandler.handlerCalled); 154 | } 155 | 156 | SUBCASE("A property emits the destroyed signal when it is destroyed") 157 | { 158 | bool notified = false; 159 | auto handler = [¬ified]() { notified = true; }; 160 | 161 | auto p = new Property{ 5 }; 162 | (void)p->destroyed().connect(handler); 163 | 164 | delete p; 165 | REQUIRE(notified == true); 166 | } 167 | } 168 | 169 | struct EqualityTestStruct { 170 | int value; 171 | }; 172 | 173 | // This equal_to specialization makes sure only EqualityTestStructs with 174 | // an increasing value can be assigned to the property 175 | namespace KDBindings { 176 | template<> 177 | struct equal_to { 178 | bool operator()(const EqualityTestStruct &a, const EqualityTestStruct &b) 179 | { 180 | return a.value < b.value; 181 | } 182 | }; 183 | } // namespace KDBindings 184 | 185 | TEST_CASE("Equality") 186 | { 187 | SUBCASE("the equal_to function template object can be specialized to implement custom equality behavior for properties") 188 | { 189 | auto callCount = 0; 190 | 191 | Property property(EqualityTestStruct{ 0 }); 192 | 193 | (void)property.valueChanged().connect([&callCount]() { ++callCount; }); 194 | 195 | property = EqualityTestStruct{ 1 }; 196 | REQUIRE(callCount == 1); 197 | REQUIRE(property.get().value == 1); 198 | 199 | property = EqualityTestStruct{ -1 }; 200 | REQUIRE(callCount == 1); 201 | REQUIRE(property.get().value == 1); 202 | } 203 | } 204 | 205 | class DummyPropertyUpdater : public PropertyUpdater 206 | { 207 | public: 208 | DummyPropertyUpdater(int value) 209 | : PropertyUpdater() 210 | , m_value{ value } 211 | { 212 | } 213 | 214 | void setUpdateFunction(std::function const &updateFunction) override 215 | { 216 | m_updateFunction = updateFunction; 217 | } 218 | 219 | int get() const override { return m_value; } 220 | 221 | void set(int value) 222 | { 223 | m_value = value; 224 | m_updateFunction(std::move(m_value)); 225 | } 226 | 227 | private: 228 | std::function m_updateFunction; 229 | int m_value; 230 | }; 231 | 232 | TEST_CASE("Property Updaters") 233 | { 234 | SUBCASE("Can construct a property with an updater and the property assumes its value") 235 | { 236 | auto updater = std::make_unique(42); 237 | auto property = Property(std::move(updater)); 238 | REQUIRE(property.get() == 42); 239 | } 240 | 241 | SUBCASE("A property with an updater throws when attempting to set it directly") 242 | { 243 | Property property(std::make_unique(7)); 244 | REQUIRE_THROWS_AS((property = 4), ReadOnlyProperty); 245 | } 246 | 247 | SUBCASE("A property with an updater notifies when it is updated via the updater") 248 | { 249 | auto updater = new DummyPropertyUpdater(7); 250 | Property property{ std::unique_ptr(updater) }; 251 | bool slotCalled = false; 252 | int updatedValue = 0; 253 | auto handler = [&slotCalled, &updatedValue](int value) { 254 | updatedValue = value; 255 | slotCalled = true; 256 | }; 257 | (void)property.valueChanged().connect(handler); 258 | 259 | updater->set(123); 260 | REQUIRE(property.get() == 123); 261 | REQUIRE(slotCalled); 262 | REQUIRE(updatedValue == 123); 263 | } 264 | 265 | SUBCASE("Can query a property to see if it has an updater") 266 | { 267 | Property property(std::make_unique(7)); 268 | REQUIRE(property.hasBinding() == true); 269 | 270 | Property property2(7); 271 | REQUIRE(property2.hasBinding() == false); 272 | } 273 | } 274 | 275 | TEST_CASE("Moving") 276 | { 277 | SUBCASE("move constructed property holds the correct value") 278 | { 279 | auto property = Property>{ std::make_unique(42) }; 280 | auto movedToProperty = Property>{ std::move(property) }; 281 | 282 | REQUIRE(*(movedToProperty.get()) == 42); 283 | } 284 | 285 | SUBCASE("move assigned property holds the correct value") 286 | { 287 | auto property = Property>{ std::make_unique(42) }; 288 | auto movedToProperty = std::move(property); 289 | 290 | REQUIRE(*(movedToProperty.get()) == 42); 291 | } 292 | 293 | SUBCASE("move constructed property maintains connections") 294 | { 295 | int countVoid = 0; 296 | auto handlerVoid = [&countVoid]() { ++countVoid; }; 297 | int countValue = 0; 298 | auto handlerValue = [&countValue](const std::unique_ptr &) { ++countValue; }; 299 | 300 | auto property = Property>{ std::make_unique(42) }; 301 | (void)property.valueChanged().connect(handlerVoid); 302 | (void)property.valueChanged().connect(handlerValue); 303 | 304 | auto movedProperty{ std::move(property) }; 305 | movedProperty.set(std::make_unique(123)); 306 | 307 | REQUIRE(countVoid == 1); 308 | REQUIRE(countValue == 1); 309 | REQUIRE(*(movedProperty.get()) == 123); 310 | } 311 | } 312 | -------------------------------------------------------------------------------- /tests/signal/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Sean Harmer 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | test-signal 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} tst_signal.cpp) 18 | 19 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 20 | 21 | # For some reason, CMake with gcc doesn't automatically include the pthread library 22 | # when using std::thread. This is a workaround for that. 23 | # IMHO, this is ridiculous, std::thread is part of the standard library, it should just work 24 | # when I use C++, but it is what it is. 25 | # See: https://cmake.cmake.narkive.com/wWDhK9RQ/undefined-reference-to-pthread-create 26 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 27 | find_package(Threads) 28 | target_link_libraries(${PROJECT_NAME} ${CMAKE_THREAD_LIBS_INIT}) 29 | endif() 30 | 31 | add_test(${PROJECT_NAME} ${PROJECT_NAME}) 32 | -------------------------------------------------------------------------------- /tests/utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file is part of KDBindings. 2 | # 3 | # SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 4 | # Author: Leon Matthes 5 | # 6 | # SPDX-License-Identifier: MIT 7 | # 8 | # Contact KDAB at for commercial licensing options. 9 | # 10 | 11 | project( 12 | test-utils 13 | VERSION 0.1 14 | LANGUAGES CXX 15 | ) 16 | 17 | add_executable(${PROJECT_NAME} tst_gen_index_array.cpp tst_get_arity.cpp tst_utils_main.cpp) 18 | target_link_libraries(${PROJECT_NAME} KDAB::KDBindings) 19 | 20 | add_test(${PROJECT_NAME} ${PROJECT_NAME}) 21 | -------------------------------------------------------------------------------- /tests/utils/tst_gen_index_array.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Leon Matthes 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | // The expansion of TEST_CASE from doctest leads to a clazy warning. 19 | // As this issue originates from doctest, disable the warning. 20 | // clazy:excludeall=non-pod-global-static 21 | 22 | using namespace KDBindings::Private; 23 | 24 | static_assert(std::is_nothrow_destructible>{}); 25 | static_assert(std::is_nothrow_default_constructible>{}); 26 | static_assert(std::is_copy_constructible>{}); 27 | static_assert(std::is_copy_assignable>{}); 28 | static_assert(std::is_nothrow_move_constructible>{}); 29 | static_assert(std::is_nothrow_move_assignable>{}); 30 | 31 | static_assert(std::is_nothrow_destructible{}); 32 | static_assert(std::is_nothrow_default_constructible{}); 33 | static_assert(std::is_nothrow_copy_constructible{}); 34 | static_assert(std::is_nothrow_copy_assignable{}); 35 | static_assert(std::is_nothrow_move_constructible{}); 36 | static_assert(std::is_nothrow_move_assignable{}); 37 | 38 | TEST_CASE("Construction") 39 | { 40 | SUBCASE("A default constructed Array is empty") 41 | { 42 | GenerationalIndexArray array; 43 | REQUIRE(array.entriesSize() == 0); 44 | } 45 | 46 | SUBCASE("A copy constructed array copies the values and keeps indices") 47 | { 48 | GenerationalIndexArray array; 49 | auto index = array.insert(1); 50 | auto index2 = array.insert(2); 51 | array.erase(index); 52 | index = array.insert(3); 53 | 54 | auto secondArray = array; 55 | REQUIRE(array.entriesSize() == secondArray.entriesSize()); 56 | REQUIRE(*array.get(index) == *secondArray.get(index)); 57 | REQUIRE(*array.get(index2) == *secondArray.get(index2)); 58 | } 59 | 60 | SUBCASE("A move constructed array empties the previous array") 61 | { 62 | GenerationalIndexArray array; 63 | auto index = array.insert(1); 64 | auto index2 = array.insert(2); 65 | array.erase(index); 66 | index = array.insert(3); 67 | 68 | auto secondArray = std::move(array); 69 | // NOLINTBEGIN(clang-analyzer-cplusplus.Move) 70 | // Usually, the moved-from object is undefined, but we 71 | // want to check the result of that move-construction, 72 | // so we do have to access it. 73 | REQUIRE(array.entriesSize() == 0); 74 | REQUIRE(array.get(index) == nullptr); 75 | REQUIRE(array.get(index2) == nullptr); 76 | // NOLINTEND(clang-analyzer-cplusplus.Move) 77 | 78 | REQUIRE(secondArray.entriesSize() == 2); 79 | REQUIRE(*secondArray.get(index) == 3); 80 | REQUIRE(*secondArray.get(index2) == 2); 81 | } 82 | } 83 | 84 | TEST_CASE("Insertion") 85 | { 86 | SUBCASE("values can be inserted and retrieved") 87 | { 88 | GenerationalIndexArray array; 89 | 90 | auto index = array.insert(5); 91 | auto index2 = array.insert(7); 92 | 93 | REQUIRE(array.entriesSize() == 2); 94 | REQUIRE(*array.get(index) == 5); 95 | REQUIRE(*array.get(index2) == 7); 96 | } 97 | } 98 | 99 | TEST_CASE("Deletion") 100 | { 101 | SUBCASE("Deletion removes the value") 102 | { 103 | GenerationalIndexArray array; 104 | 105 | auto index = array.insert(5); 106 | REQUIRE(array.entriesSize() == 1); 107 | 108 | array.erase(index); 109 | REQUIRE(array.get(index) == nullptr); 110 | REQUIRE_MESSAGE(array.entriesSize() == 1, "entriesSize doesn't get smaller during deletion"); 111 | } 112 | 113 | SUBCASE("Deletion only invalidates the deleted index") 114 | { 115 | GenerationalIndexArray array; 116 | 117 | auto index = array.insert(5); 118 | auto index2 = array.insert(7); 119 | auto index2Ptr = array.get(index2); 120 | 121 | array.erase(index); 122 | REQUIRE(array.get(index) == nullptr); 123 | REQUIRE(array.get(index2) == index2Ptr); 124 | REQUIRE(*array.get(index2) == 7); 125 | } 126 | 127 | SUBCASE("Clear invalidates all indices, but leaves capacity unchanged") 128 | { 129 | GenerationalIndexArray array; 130 | 131 | auto index = array.insert(5); 132 | auto index2 = array.insert(7); 133 | 134 | array.clear(); 135 | REQUIRE(array.entriesSize() == 2); 136 | REQUIRE(array.get(index) == nullptr); 137 | REQUIRE(array.get(index2) == nullptr); 138 | } 139 | 140 | SUBCASE("After clearing, spots in the array are reused") 141 | { 142 | GenerationalIndexArray array; 143 | std::set valueIndices; 144 | 145 | valueIndices.emplace(array.insert(5).index); 146 | valueIndices.emplace(array.insert(7).index); 147 | 148 | array.clear(); 149 | 150 | std::set newValueIndices; 151 | newValueIndices.emplace(array.insert(8).index); 152 | newValueIndices.emplace(array.insert(9).index); 153 | 154 | REQUIRE(array.entriesSize() == 2); 155 | REQUIRE(valueIndices == newValueIndices); 156 | } 157 | 158 | SUBCASE("After clearing, the generations are different") 159 | { 160 | GenerationalIndexArray array; 161 | std::set generations; 162 | 163 | generations.emplace(array.insert(5).generation); 164 | generations.emplace(array.insert(7).generation); 165 | 166 | array.clear(); 167 | 168 | std::set newGenerations; 169 | newGenerations.emplace(array.insert(8).generation); 170 | newGenerations.emplace(array.insert(9).generation); 171 | 172 | REQUIRE(array.entriesSize() == 2); 173 | for (const auto &generation : generations) { 174 | REQUIRE(newGenerations.find(generation) == newGenerations.end()); 175 | } 176 | } 177 | } 178 | 179 | TEST_CASE("indexAtEntry") 180 | { 181 | SUBCASE("An empty GenIndexArray never returns a valid index") 182 | { 183 | GenerationalIndexArray array; 184 | 185 | for (uint32_t i = 0; i < 10; ++i) { 186 | REQUIRE_FALSE(array.indexAtEntry(i)); 187 | } 188 | } 189 | 190 | SUBCASE("A full GenIndexArray returns a valid index for every entry, but not more") 191 | { 192 | GenerationalIndexArray array; 193 | 194 | for (auto i = 0; i < 10; ++i) { 195 | array.insert(std::move(i)); 196 | } 197 | 198 | for (uint32_t i = 0; i < array.entriesSize(); ++i) { 199 | REQUIRE(array.indexAtEntry(i)); 200 | REQUIRE_FALSE(array.indexAtEntry(i + array.entriesSize())); 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /tests/utils/tst_get_arity.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Leon Matthes 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace KDBindings::Private; 16 | 17 | // Test free function arity 18 | int function(int, int); 19 | int functionNoexcept(int, int) noexcept; 20 | 21 | static_assert(get_arity(function) == 2); 22 | static_assert(get_arity(functionNoexcept) == 2); 23 | 24 | // Test member function arity 25 | class GetArityTest 26 | { 27 | public: 28 | int member(int, int); 29 | void memberConst(int) const; 30 | bool memberVolatile() volatile; 31 | void memberRef(int, int) const &; 32 | void memberRRef(int, int) &&; 33 | 34 | int memberNoexcept(int, int) noexcept; 35 | void memberConstNoexcept(int) const noexcept; 36 | bool memberVolatileNoexcept() volatile noexcept; 37 | void memberRefNoexcept(int, int) const &noexcept; 38 | void memberRRefNoexcept(int, int) &&noexcept; 39 | 40 | // GetArityTest is also a callable object 41 | void operator()(int, int) const; 42 | }; 43 | 44 | static_assert(get_arity(&GetArityTest::member) == 3); 45 | static_assert(get_arity(&GetArityTest::memberConst) == 2); 46 | static_assert(get_arity(&GetArityTest::memberVolatile) == 1); 47 | static_assert(get_arity(&GetArityTest::memberRef) == 3); 48 | static_assert(get_arity(&GetArityTest::memberRRef) == 3); 49 | 50 | static_assert(get_arity(&GetArityTest::memberNoexcept) == 3); 51 | static_assert(get_arity(&GetArityTest::memberConstNoexcept) == 2); 52 | static_assert(get_arity(&GetArityTest::memberVolatileNoexcept) == 1); 53 | static_assert(get_arity(&GetArityTest::memberRefNoexcept) == 3); 54 | static_assert(get_arity(&GetArityTest::memberRRefNoexcept) == 3); 55 | 56 | // Test arity of a generic callable object 57 | static_assert(get_arity() == 2); 58 | 59 | // Test arity with noexcept(...) syntax 60 | template 61 | class GetArityTemplateTest 62 | { 63 | public: 64 | T memberOptionalNoexcept(T, T) noexcept(std::is_nothrow_default_constructible_v); 65 | }; 66 | 67 | // int constructor is noexcept 68 | static_assert(get_arity(&GetArityTemplateTest::memberOptionalNoexcept) == 3); 69 | // std::ostream constructor is not noexcept 70 | static_assert(get_arity(&GetArityTemplateTest::memberOptionalNoexcept) == 3); 71 | 72 | // Test lambda arity 73 | static_assert(get_arity([]() {}) == 0); 74 | static_assert(get_arity([](int, int) {}) == 2); 75 | static_assert(get_arity([](int, int) { return false; }) == 2); 76 | -------------------------------------------------------------------------------- /tests/utils/tst_utils_main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | This file is part of KDBindings. 3 | 4 | SPDX-FileCopyrightText: 2021 Klarälvdalens Datakonsult AB, a KDAB Group company 5 | Author: Leon Matthes 6 | 7 | SPDX-License-Identifier: MIT 8 | 9 | Contact KDAB at for commercial licensing options. 10 | */ 11 | 12 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 13 | #include 14 | --------------------------------------------------------------------------------