Merge remote-tracking branch 'origin/master' into 144-add-zone-support
Dieser Commit ist enthalten in:
Commit
8ee1e9bc6e
13
.clang-tidy
13
.clang-tidy
@ -1,11 +1,22 @@
|
||||
---
|
||||
Checks: >
|
||||
-*,
|
||||
misc-*,
|
||||
-misc-no-recursion,
|
||||
modernize-*,
|
||||
-modernize-avoid-bind,
|
||||
-modernize-avoid-c-arrays,
|
||||
-modernize-use-trailing-return-type,
|
||||
readability-*,
|
||||
-readability-braces-around-statements,
|
||||
-readability-implicit-bool-conversion,
|
||||
-readability-identifier-length,
|
||||
-readability-magic-numbers
|
||||
-readability-magic-numbers,
|
||||
-readability-suspicious-call-argument
|
||||
|
||||
WarningsAsErrors: >
|
||||
readability-*,
|
||||
-readability-function-cognitive-complexity
|
||||
|
||||
CheckOptions:
|
||||
- {key: readability-identifier-naming.NamespaceCase, value: CamelCase}
|
||||
|
||||
126
.github/workflows/build.yml
vendored
126
.github/workflows/build.yml
vendored
@ -20,16 +20,6 @@ jobs:
|
||||
build_deb: false
|
||||
defines: ""
|
||||
|
||||
- name: "ubuntu_22.04"
|
||||
os: ubuntu-22.04
|
||||
generator: "Unix Makefiles"
|
||||
arch: ""
|
||||
target: traintastic-client
|
||||
jobs: 4
|
||||
build_type: Release
|
||||
build_deb: true
|
||||
defines: ""
|
||||
|
||||
- name: "ubuntu_24.04"
|
||||
os: ubuntu-24.04
|
||||
generator: "Unix Makefiles"
|
||||
@ -70,26 +60,6 @@ jobs:
|
||||
build_deb: false
|
||||
defines: ""
|
||||
|
||||
- name: "macos-14"
|
||||
os: "macos-14"
|
||||
generator: "Unix Makefiles"
|
||||
arch: ""
|
||||
target: traintastic-client
|
||||
jobs: 3
|
||||
build_type: Release
|
||||
build_deb: false
|
||||
defines: ""
|
||||
|
||||
- name: "macos-15"
|
||||
os: "macos-15"
|
||||
generator: "Unix Makefiles"
|
||||
arch: ""
|
||||
target: traintastic-client
|
||||
jobs: 3
|
||||
build_type: Release
|
||||
build_deb: false
|
||||
defines: ""
|
||||
|
||||
steps:
|
||||
- uses: FranzDiebold/github-env-vars-action@v2
|
||||
|
||||
@ -112,8 +82,16 @@ jobs:
|
||||
uses: jurplel/install-qt-action@v4
|
||||
with:
|
||||
cache: true
|
||||
modules: qtwebsockets
|
||||
version: 6.5.*
|
||||
|
||||
- name: Set CMAKE_OSX_ARCHITECTURES
|
||||
if: startswith(matrix.config.os, 'macos')
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('CMAKE_OSX_ARCHITECTURES', 'x86_64');
|
||||
|
||||
# Ubuntu only:
|
||||
- name: apt update
|
||||
if: startswith(matrix.config.os, 'ubuntu')
|
||||
@ -122,7 +100,7 @@ jobs:
|
||||
# Ubuntu only:
|
||||
- name: Install packages
|
||||
if: startswith(matrix.config.os, 'ubuntu')
|
||||
run: sudo apt install qtbase5-dev qtbase5-dev-tools libqt5svg5-dev
|
||||
run: sudo apt install qtbase5-dev qtbase5-dev-tools libqt5svg5-dev libqt5websockets5-dev
|
||||
|
||||
# All:
|
||||
- name: Create Build Environment
|
||||
@ -173,6 +151,8 @@ jobs:
|
||||
build-server:
|
||||
name: server ${{matrix.config.name}}
|
||||
runs-on: ${{matrix.config.os}}
|
||||
env:
|
||||
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -186,33 +166,9 @@ jobs:
|
||||
jobs: 4
|
||||
build_type: Release
|
||||
build_deb: false
|
||||
defines: "-DENABLE_CLANG_TIDY=ON"
|
||||
ccov: false
|
||||
|
||||
- name: "windows_x64_mingw"
|
||||
os: windows-2022
|
||||
generator: "MinGW Makefiles"
|
||||
arch: ""
|
||||
toolset: ""
|
||||
target: all
|
||||
jobs: 4
|
||||
build_type: Release
|
||||
build_deb: false
|
||||
defines: ""
|
||||
ccov: false
|
||||
|
||||
- name: "ubuntu_22.04"
|
||||
os: ubuntu-22.04
|
||||
generator: "Unix Makefiles"
|
||||
arch: ""
|
||||
toolset: ""
|
||||
target: all
|
||||
jobs: 4
|
||||
build_type: Release
|
||||
build_deb: true
|
||||
defines: "-DENABLE_CLANG_TIDY=ON -DINSTALL_SYSTEMD_SERVICE=ON"
|
||||
ccov: false
|
||||
|
||||
- name: "ubuntu_24.04"
|
||||
os: ubuntu-24.04
|
||||
generator: "Unix Makefiles"
|
||||
@ -222,11 +178,11 @@ jobs:
|
||||
jobs: 4
|
||||
build_type: Release
|
||||
build_deb: true
|
||||
defines: "-DENABLE_CLANG_TIDY=ON -DINSTALL_SYSTEMD_SERVICE=ON"
|
||||
defines: "-DINSTALL_SYSTEMD_SERVICE=ON"
|
||||
ccov: false
|
||||
|
||||
- name: "ubuntu_latest (debug+ccov)"
|
||||
os: ubuntu-latest
|
||||
- name: "ubuntu_24.04 (debug+ccov)"
|
||||
os: ubuntu-24.04
|
||||
generator: "Unix Makefiles"
|
||||
arch: ""
|
||||
toolset: ""
|
||||
@ -234,7 +190,7 @@ jobs:
|
||||
jobs: 4
|
||||
build_type: Debug
|
||||
build_deb: false
|
||||
defines: "-DENABLE_CLANG_TIDY=ON -DCODE_COVERAGE=ON"
|
||||
defines: "-DCODE_COVERAGE=ON"
|
||||
ccov: true
|
||||
|
||||
- name: "raspberrypios_arm7"
|
||||
@ -273,30 +229,6 @@ jobs:
|
||||
defines: ""
|
||||
ccov: false
|
||||
|
||||
- name: "macos-14"
|
||||
os: "macos-14"
|
||||
generator: "Unix Makefiles"
|
||||
arch: ""
|
||||
toolset: ""
|
||||
target: all
|
||||
jobs: 3
|
||||
build_type: Release
|
||||
build_deb: false
|
||||
defines: ""
|
||||
ccov: false
|
||||
|
||||
- name: "macos-15"
|
||||
os: "macos-15"
|
||||
generator: "Unix Makefiles"
|
||||
arch: ""
|
||||
toolset: ""
|
||||
target: all
|
||||
jobs: 3
|
||||
build_type: Release
|
||||
build_deb: false
|
||||
defines: ""
|
||||
ccov: false
|
||||
|
||||
steps:
|
||||
- uses: FranzDiebold/github-env-vars-action@v2
|
||||
|
||||
@ -306,6 +238,27 @@ jobs:
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
- name: Export GitHub Actions cache environment variables
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
|
||||
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
|
||||
|
||||
- name: Set VCPKG_ROOT
|
||||
if: startswith(matrix.config.os, 'windows') || startswith(matrix.config.os, 'macos')
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('VCPKG_ROOT', process.env.VCPKG_INSTALLATION_ROOT || '');
|
||||
|
||||
- name: Set CMAKE_OSX_ARCHITECTURES
|
||||
if: startswith(matrix.config.os, 'macos')
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
core.exportVariable('CMAKE_OSX_ARCHITECTURES', 'x86_64');
|
||||
|
||||
# Ubuntu only:
|
||||
- name: apt update
|
||||
if: startswith(matrix.config.os, 'ubuntu')
|
||||
@ -408,10 +361,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
config:
|
||||
- name: "ubuntu_22.04"
|
||||
os: ubuntu-22.04
|
||||
defines: ""
|
||||
|
||||
- name: "ubuntu_24.04"
|
||||
os: ubuntu-24.04
|
||||
defines: ""
|
||||
@ -594,12 +543,14 @@ jobs:
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: traintastic-client-deb-*
|
||||
merge-multiple: true
|
||||
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
|
||||
|
||||
- name: Download artifacts 3/6
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: traintastic-server-deb-*
|
||||
merge-multiple: true
|
||||
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
|
||||
|
||||
- name: Download artifacts 4/6
|
||||
@ -618,6 +569,7 @@ jobs:
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: traintastic-data-deb-*
|
||||
merge-multiple: true
|
||||
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
|
||||
|
||||
- name: "Download artifact: manual-lua"
|
||||
|
||||
@ -90,17 +90,17 @@ file(GLOB SOURCES
|
||||
"thirdparty/QtWaitingSpinner/*.hpp"
|
||||
"thirdparty/QtWaitingSpinner/*.cpp")
|
||||
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets Network Svg Xml)
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets Network Svg Xml WebSockets)
|
||||
if(QT_VERSION_MAJOR EQUAL 5)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets Network Svg Xml)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets Network Svg Xml WebSockets)
|
||||
else()
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets Network Svg Xml SvgWidgets)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets Network Svg Xml SvgWidgets WebSockets)
|
||||
endif()
|
||||
message(STATUS "Found Qt ${QT_VERSION}")
|
||||
|
||||
target_sources(traintastic-client PRIVATE ${SOURCES})
|
||||
|
||||
target_link_libraries(traintastic-client PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Svg Qt${QT_VERSION_MAJOR}::Xml)
|
||||
target_link_libraries(traintastic-client PRIVATE Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Widgets Qt${QT_VERSION_MAJOR}::Network Qt${QT_VERSION_MAJOR}::Svg Qt${QT_VERSION_MAJOR}::Xml Qt${QT_VERSION_MAJOR}::WebSockets)
|
||||
if(QT_VERSION_MAJOR GREATER 5)
|
||||
target_link_libraries(traintastic-client PRIVATE Qt${QT_VERSION_MAJOR}::SvgWidgets)
|
||||
endif()
|
||||
|
||||
@ -333,11 +333,13 @@
|
||||
"protocol_dr5000_usb": {
|
||||
"title": "$wizard.add_interface.protocol:title$",
|
||||
"text": "$wizard.add_interface.protocol:text$",
|
||||
"bottom_text": "$wizard.add_interface.protocol_dr5000_usb:bottom_text$",
|
||||
"type": "radio",
|
||||
"options": [
|
||||
{
|
||||
"name": "LocoNet",
|
||||
"name": "LocoNet ($wizard.add_interface:recommended$)",
|
||||
"next": "serial_port",
|
||||
"checked": true,
|
||||
"actions": {
|
||||
"create_interface": {
|
||||
"class_id": "interface.loconet",
|
||||
|
||||
94
client/gfx/dark/circle/add.svg
Normale Datei
94
client/gfx/dark/circle/add.svg
Normale Datei
@ -0,0 +1,94 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="96"
|
||||
height="96"
|
||||
viewBox="0 0 25.399999 25.400001"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="add.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="14.375"
|
||||
inkscape:cy="40.446429"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3713"
|
||||
spacingx="0.26458333"
|
||||
empspacing="4" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:prohibits
|
||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-271.59998)">
|
||||
<circle
|
||||
style="fill:#ea80fc;fill-opacity:1;stroke:none;stroke-width:2.51975;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="path252"
|
||||
cx="12.7"
|
||||
cy="284.29996"
|
||||
r="12.7" />
|
||||
<path
|
||||
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 12.7,274.77498 c -1.172633,0 -2.116667,0.94403 -2.116667,2.11666 v 5.29167 H 5.2916667 c -1.1726347,0 -2.1166667,0.94404 -2.1166667,2.11667 0,1.17263 0.944032,2.11667 2.1166667,2.11667 h 5.2916663 v 5.29167 c 0,1.17263 0.944034,2.11666 2.116667,2.11666 1.172633,0 2.116666,-0.94403 2.116666,-2.11666 v -5.29167 h 5.291667 c 1.172635,0 2.116667,-0.94404 2.116667,-2.11667 0,-1.17263 -0.944032,-2.11667 -2.116667,-2.11667 h -5.291667 v -5.29167 c 0,-1.17263 -0.944033,-2.11666 -2.116666,-2.11666 z"
|
||||
id="rect843"
|
||||
sodipodi:nodetypes="sscssscssscssscss" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Nachher Breite: | Höhe: | Größe: 3.5 KiB |
@ -95,5 +95,7 @@
|
||||
<file>zone.svg</file>
|
||||
<file>clear_persistent_variables.svg</file>
|
||||
<file>highlight_zone.svg</file>
|
||||
<file>swap.svg</file>
|
||||
<file>circle/add.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
116
client/gfx/dark/swap.svg
Normale Datei
116
client/gfx/dark/swap.svg
Normale Datei
@ -0,0 +1,116 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="96"
|
||||
height="96"
|
||||
viewBox="0 0 25.399999 25.400001"
|
||||
version="1.1"
|
||||
id="svg8"
|
||||
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
|
||||
sodipodi:docname="swap.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="5.6"
|
||||
inkscape:cx="14.017857"
|
||||
inkscape:cy="40.089286"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1">
|
||||
<inkscape:grid
|
||||
type="xygrid"
|
||||
id="grid3713"
|
||||
spacingx="0.26458333"
|
||||
empspacing="4" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata5">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<cc:license
|
||||
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
|
||||
</cc:Work>
|
||||
<cc:License
|
||||
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Reproduction" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#Distribution" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Notice" />
|
||||
<cc:requires
|
||||
rdf:resource="http://creativecommons.org/ns#Attribution" />
|
||||
<cc:prohibits
|
||||
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
|
||||
<cc:permits
|
||||
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
|
||||
</cc:License>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(0,-271.59998)">
|
||||
<circle
|
||||
style="fill:#ea80fc;fill-opacity:1;stroke:none;stroke-width:2.51975;stroke-linecap:round;stroke-linejoin:round"
|
||||
id="path252"
|
||||
cx="12.7"
|
||||
cy="284.29996"
|
||||
r="12.7" />
|
||||
<g
|
||||
id="g1229">
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 20.108333,280.06665 H 5.2916667"
|
||||
id="path819"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 8.4666667,276.89165 -3.175,3.175 3.175,3.175"
|
||||
id="path821"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
</g>
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 5.2916667,288.53331 14.8166663,0"
|
||||
id="path819-6"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:bevel;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 16.933333,285.35831 3.175,3.175 -3.175,3.175"
|
||||
id="path821-7"
|
||||
inkscape:connector-curvature="0"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Nachher Breite: | Höhe: | Größe: 4.1 KiB |
@ -68,14 +68,14 @@ constexpr QRect updateTileRect(const int x, const int y, const int w, const int
|
||||
}
|
||||
|
||||
|
||||
BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
|
||||
BoardAreaWidget::BoardAreaWidget(std::shared_ptr<Board> board, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
m_colorScheme{&BoardColorScheme::dark},
|
||||
m_board{board},
|
||||
m_boardLeft{board.board().getProperty("left")},
|
||||
m_boardTop{board.board().getProperty("top")},
|
||||
m_boardRight{board.board().getProperty("right")},
|
||||
m_boardBottom{board.board().getProperty("bottom")},
|
||||
m_board{std::move(board)},
|
||||
m_boardLeft{m_board->getProperty("left")},
|
||||
m_boardTop{m_board->getProperty("top")},
|
||||
m_boardRight{m_board->getProperty("right")},
|
||||
m_boardBottom{m_board->getProperty("bottom")},
|
||||
m_grid{Grid::Dot},
|
||||
m_zoomLevel{0},
|
||||
m_blockHighlight{MainWindow::instance->blockHighlight()},
|
||||
@ -108,7 +108,7 @@ BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
|
||||
update();
|
||||
});
|
||||
|
||||
for(const auto& [l, object] : m_board.board().tileObjects())
|
||||
for(const auto& [l, object] : m_board->tileObjects())
|
||||
tileObjectAdded(l.x, l.y, object);
|
||||
|
||||
settingsChanged();
|
||||
@ -124,7 +124,7 @@ void BoardAreaWidget::tileObjectAdded(int16_t x, int16_t y, const ObjectPtr& obj
|
||||
{
|
||||
try
|
||||
{
|
||||
const TileData& tileData = m_board.board().tileData().at(l);
|
||||
const TileData& tileData = m_board->tileData().at(l);
|
||||
update(updateTileRect(l.x - boardLeft(), l.y - boardTop(), tileData.width(), tileData.height(), getTileSize()));
|
||||
}
|
||||
catch(...)
|
||||
@ -139,7 +139,7 @@ void BoardAreaWidget::tileObjectAdded(int16_t x, int16_t y, const ObjectPtr& obj
|
||||
connect(property, &BaseProperty::valueChanged, this, handler);
|
||||
};
|
||||
|
||||
switch(m_board.board().getTileId(l))
|
||||
switch(m_board->getTileId(l))
|
||||
{
|
||||
case TileId::RailTurnoutLeft45:
|
||||
case TileId::RailTurnoutLeft90:
|
||||
@ -204,6 +204,7 @@ void BoardAreaWidget::tileObjectAdded(int16_t x, int16_t y, const ObjectPtr& obj
|
||||
case TileId::RailTunnel:
|
||||
case TileId::RailOneWay:
|
||||
case TileId::RailLink:
|
||||
case TileId::HiddenRailCrossOver:
|
||||
case TileId::ReservedForFutureExpension:
|
||||
break;
|
||||
|
||||
@ -319,7 +320,7 @@ void BoardAreaWidget::setMouseMoveTileSizeMax(uint8_t width, uint8_t height)
|
||||
|
||||
TurnoutPosition BoardAreaWidget::getTurnoutPosition(const TileLocation& l) const
|
||||
{
|
||||
if(ObjectPtr object = m_board.board().getTileObject(l))
|
||||
if(ObjectPtr object = m_board->getTileObject(l))
|
||||
if(const auto* p = object->getProperty("position"))
|
||||
return p->toEnum<TurnoutPosition>();
|
||||
return TurnoutPosition::Unknown;
|
||||
@ -327,7 +328,7 @@ TurnoutPosition BoardAreaWidget::getTurnoutPosition(const TileLocation& l) const
|
||||
|
||||
SensorState BoardAreaWidget::getSensorState(const TileLocation& l) const
|
||||
{
|
||||
if(ObjectPtr object = m_board.board().getTileObject(l))
|
||||
if(ObjectPtr object = m_board->getTileObject(l))
|
||||
if(const auto* p = object->getProperty("state"))
|
||||
return p->toEnum<SensorState>();
|
||||
return SensorState::Unknown;
|
||||
@ -335,7 +336,7 @@ SensorState BoardAreaWidget::getSensorState(const TileLocation& l) const
|
||||
|
||||
DirectionControlState BoardAreaWidget::getDirectionControlState(const TileLocation& l) const
|
||||
{
|
||||
if(ObjectPtr object = m_board.board().getTileObject(l))
|
||||
if(ObjectPtr object = m_board->getTileObject(l))
|
||||
if(const auto* p = object->getProperty("state"))
|
||||
return p->toEnum<DirectionControlState>();
|
||||
return DirectionControlState::Both;
|
||||
@ -343,7 +344,7 @@ DirectionControlState BoardAreaWidget::getDirectionControlState(const TileLocati
|
||||
|
||||
SignalAspect BoardAreaWidget::getSignalAspect(const TileLocation& l) const
|
||||
{
|
||||
if(ObjectPtr object = m_board.board().getTileObject(l))
|
||||
if(ObjectPtr object = m_board->getTileObject(l))
|
||||
if(const auto* p = object->getProperty("aspect"))
|
||||
return p->toEnum<SignalAspect>();
|
||||
return SignalAspect::Unknown;
|
||||
@ -351,7 +352,7 @@ SignalAspect BoardAreaWidget::getSignalAspect(const TileLocation& l) const
|
||||
|
||||
Color BoardAreaWidget::getColor(const TileLocation& l) const
|
||||
{
|
||||
if(ObjectPtr object = m_board.board().getTileObject(l))
|
||||
if(ObjectPtr object = m_board->getTileObject(l))
|
||||
if(const auto* p = object->getProperty("color"))
|
||||
return p->toEnum<Color>();
|
||||
return Color::None;
|
||||
@ -359,7 +360,7 @@ Color BoardAreaWidget::getColor(const TileLocation& l) const
|
||||
|
||||
DecouplerState BoardAreaWidget::getDecouplerState(const TileLocation& l) const
|
||||
{
|
||||
if(ObjectPtr object = m_board.board().getTileObject(l))
|
||||
if(ObjectPtr object = m_board->getTileObject(l))
|
||||
if(const auto* p = object->getProperty("state"))
|
||||
return p->toEnum<DecouplerState>();
|
||||
return DecouplerState::Deactivated;
|
||||
@ -367,7 +368,7 @@ DecouplerState BoardAreaWidget::getDecouplerState(const TileLocation& l) const
|
||||
|
||||
bool BoardAreaWidget::getNXButtonEnabled(const TileLocation& l) const
|
||||
{
|
||||
if(auto object = std::dynamic_pointer_cast<NXButtonRailTile>(m_board.board().getTileObject(l)))
|
||||
if(auto object = std::dynamic_pointer_cast<NXButtonRailTile>(m_board->getTileObject(l)))
|
||||
{
|
||||
return object->getPropertyValueBool("enabled", false);
|
||||
}
|
||||
@ -376,7 +377,7 @@ bool BoardAreaWidget::getNXButtonEnabled(const TileLocation& l) const
|
||||
|
||||
bool BoardAreaWidget::getNXButtonPressed(const TileLocation& l) const
|
||||
{
|
||||
if(auto object = std::dynamic_pointer_cast<NXButtonRailTile>(m_board.board().getTileObject(l)))
|
||||
if(auto object = std::dynamic_pointer_cast<NXButtonRailTile>(m_board->getTileObject(l)))
|
||||
{
|
||||
return object->isPressed();
|
||||
}
|
||||
@ -391,11 +392,11 @@ TileLocation BoardAreaWidget::pointToTileLocation(const QPoint& p)
|
||||
|
||||
QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
|
||||
{
|
||||
const auto tileId = m_board.board().getTileId(l);
|
||||
const auto tileId = m_board->getTileId(l);
|
||||
|
||||
if(isRailTurnout(tileId))
|
||||
{
|
||||
if(auto turnout = m_board.board().getTileObject(l))
|
||||
if(auto turnout = m_board->getTileObject(l))
|
||||
{
|
||||
QString text = "<b>" + turnout->getPropertyValueString("name") + "</b>";
|
||||
if(auto* position = turnout->getProperty("position")) /*[[likely]]*/
|
||||
@ -407,7 +408,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
|
||||
}
|
||||
else if(isRailSignal(tileId))
|
||||
{
|
||||
if(auto signal = m_board.board().getTileObject(l))
|
||||
if(auto signal = m_board->getTileObject(l))
|
||||
{
|
||||
QString text = "<b>" + signal->getPropertyValueString("name") + "</b>";
|
||||
if(auto* aspect = signal->getProperty("aspect")) /*[[likely]]*/
|
||||
@ -419,7 +420,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
|
||||
}
|
||||
else if(tileId == TileId::RailSensor)
|
||||
{
|
||||
if(auto sensor = m_board.board().getTileObject(l))
|
||||
if(auto sensor = m_board->getTileObject(l))
|
||||
{
|
||||
QString text = "<b>" + sensor->getPropertyValueString("name") + "</b>";
|
||||
if(auto* state = sensor->getProperty("state")) /*[[likely]]*/
|
||||
@ -431,7 +432,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
|
||||
}
|
||||
else if(tileId == TileId::RailBlock)
|
||||
{
|
||||
if(auto block = m_board.board().getTileObject(l))
|
||||
if(auto block = m_board->getTileObject(l))
|
||||
{
|
||||
QString text = "<b>" + block->getPropertyValueString("name") + "</b>";
|
||||
if(auto* state = block->getProperty("state")) /*[[likely]]*/
|
||||
@ -443,7 +444,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
|
||||
}
|
||||
else if(tileId == TileId::RailDirectionControl)
|
||||
{
|
||||
if(auto directionControl = m_board.board().getTileObject(l))
|
||||
if(auto directionControl = m_board->getTileObject(l))
|
||||
{
|
||||
QString text = "<b>" + directionControl->getPropertyValueString("name") + "</b>";
|
||||
if(auto* state = directionControl->getProperty("state")) /*[[likely]]*/
|
||||
@ -455,7 +456,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
|
||||
}
|
||||
else if(tileId == TileId::RailSensor)
|
||||
{
|
||||
if(auto sensor = m_board.board().getTileObject(l))
|
||||
if(auto sensor = m_board->getTileObject(l))
|
||||
{
|
||||
QString text = "<b>" + sensor->getPropertyValueString("name") + "</b>";
|
||||
if(auto* state = sensor->getProperty("state")) /*[[likely]]*/
|
||||
@ -467,7 +468,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
|
||||
}
|
||||
else if(tileId == TileId::Switch)
|
||||
{
|
||||
if(auto switch_ = m_board.board().getTileObject(l))
|
||||
if(auto switch_ = m_board->getTileObject(l))
|
||||
{
|
||||
QString text = "<b>" + switch_->getPropertyValueString("name") + "</b>";
|
||||
if(auto* value = switch_->getProperty("value")) /*[[likely]]*/
|
||||
@ -492,7 +493,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
|
||||
else if(tileId == TileId::PushButton ||
|
||||
tileId == TileId::RailNXButton)
|
||||
{
|
||||
if(auto tile = m_board.board().getTileObject(l))
|
||||
if(auto tile = m_board->getTileObject(l))
|
||||
{
|
||||
return "<b>" + tile->getPropertyValueString("name") + "</b>";
|
||||
}
|
||||
@ -679,7 +680,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
|
||||
painter.save();
|
||||
|
||||
for(auto it : m_board.board().tileData())
|
||||
for(auto it : m_board->tileData())
|
||||
if(it.first.x + it.second.width() - 1 >= tiles.left() && it.first.x <= tiles.right() &&
|
||||
it.first.y + it.second.height() - 1 >= tiles.top() && it.first.y <= tiles.bottom())
|
||||
{
|
||||
@ -740,7 +741,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
|
||||
case TileId::RailBlock:
|
||||
{
|
||||
auto block = m_board.board().getTileObject(it.first);
|
||||
auto block = m_board->getTileObject(it.first);
|
||||
tilePainter.drawBlock(id, r, a, state & 0x01, state & 0x02, block);
|
||||
if(auto itColors = m_blockHighlight.blockColors().find(block->getPropertyValueString("id"));
|
||||
itColors != m_blockHighlight.blockColors().end() && !itColors->isEmpty())
|
||||
@ -783,7 +784,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
|
||||
case TileId::Label:
|
||||
{
|
||||
if(auto label = m_board.board().getTileObject(it.first)) /*[[likely]]*/
|
||||
if(auto label = m_board->getTileObject(it.first)) /*[[likely]]*/
|
||||
{
|
||||
tilePainter.drawLabel(r, a,
|
||||
label->getPropertyValueString("text"),
|
||||
@ -798,7 +799,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
break;
|
||||
}
|
||||
case TileId::Switch:
|
||||
if(auto sw = m_board.board().getTileObject(it.first)) /*[[likely]]*/
|
||||
if(auto sw = m_board->getTileObject(it.first)) /*[[likely]]*/
|
||||
{
|
||||
tilePainter.drawSwitch(r,
|
||||
sw->getPropertyValueBool("value", false),
|
||||
@ -893,7 +894,7 @@ void BoardAreaWidget::dragMoveEvent(QDragMoveEvent* event)
|
||||
{
|
||||
m_dragMoveTileLocation = l;
|
||||
if(event->mimeData()->hasFormat(AssignTrainMimeData::mimeType) &&
|
||||
m_board.board().getTileId(l) == TileId::RailBlock)
|
||||
m_board->getTileId(l) == TileId::RailBlock)
|
||||
{
|
||||
return event->accept();
|
||||
}
|
||||
@ -909,12 +910,12 @@ void BoardAreaWidget::dropEvent(QDropEvent* event)
|
||||
const TileLocation l = pointToTileLocation(event->position().toPoint());
|
||||
#endif
|
||||
|
||||
switch(m_board.board().getTileId(l))
|
||||
switch(m_board->getTileId(l))
|
||||
{
|
||||
case TileId::RailBlock:
|
||||
if(auto* assignTrain = dynamic_cast<const AssignTrainMimeData*>(event->mimeData()))
|
||||
{
|
||||
if(auto tile = std::dynamic_pointer_cast<BlockRailTile>(m_board.board().getTileObject(l)))
|
||||
if(auto tile = std::dynamic_pointer_cast<BlockRailTile>(m_board->getTileObject(l)))
|
||||
{
|
||||
if(auto* method = tile->getMethod("assign_train"))
|
||||
{
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
|
||||
class BoardWidget;
|
||||
class BlockHighlight;
|
||||
class Board;
|
||||
|
||||
class BoardAreaWidget : public QWidget
|
||||
{
|
||||
@ -67,7 +68,7 @@ class BoardAreaWidget : public QWidget
|
||||
protected:
|
||||
static constexpr int boardMargin = 1; // tile
|
||||
|
||||
BoardWidget& m_board;
|
||||
std::shared_ptr<Board> m_board;
|
||||
AbstractProperty* m_boardLeft;
|
||||
AbstractProperty* m_boardTop;
|
||||
AbstractProperty* m_boardRight;
|
||||
@ -132,7 +133,7 @@ class BoardAreaWidget : public QWidget
|
||||
static constexpr int zoomLevelMin = -2;
|
||||
static constexpr int zoomLevelMax = 15;
|
||||
|
||||
BoardAreaWidget(BoardWidget& board, QWidget* parent = nullptr);
|
||||
BoardAreaWidget(std::shared_ptr<Board> board, QWidget* parent = nullptr);
|
||||
|
||||
Grid grid() const { return m_grid; }
|
||||
void nextGrid();
|
||||
|
||||
@ -83,7 +83,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
QWidget(parent),
|
||||
m_object{std::move(object)},
|
||||
m_nxManagerRequestId{Connection::invalidRequestId},
|
||||
m_boardArea{new BoardAreaWidget(*this, this)},
|
||||
m_boardArea{new BoardAreaWidget(m_object, this)},
|
||||
m_statusBar{new QStatusBar(this)},
|
||||
m_statusBarMessage{new QLabel(this)},
|
||||
m_statusBarCoords{new QLabel(this)},
|
||||
|
||||
@ -177,6 +177,7 @@ void TilePainter::draw(TileId id, const QRectF& r, TileRotate rotate, bool isRes
|
||||
break;
|
||||
|
||||
case TileId::None:
|
||||
case TileId::HiddenRailCrossOver:
|
||||
case TileId::ReservedForFutureExpension:
|
||||
break;
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021,2023 Reinder Feenstra
|
||||
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -56,8 +56,14 @@ ConnectDialog::ConnectDialog(QWidget* parent, const QString& url) :
|
||||
m_password->setEchoMode(QLineEdit::Password);
|
||||
|
||||
m_connectAutomatically->setChecked(GeneralSettings::instance().connectAutomaticallyToDiscoveredServer.value());
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
|
||||
connect(m_connectAutomatically, &QCheckBox::stateChanged, this,
|
||||
[](int state)
|
||||
#else
|
||||
connect(m_connectAutomatically, &QCheckBox::checkStateChanged, this,
|
||||
[](Qt::CheckState state)
|
||||
#endif
|
||||
{
|
||||
GeneralSettings::instance().connectAutomaticallyToDiscoveredServer.setValue(state == Qt::Checked);
|
||||
});
|
||||
@ -150,8 +156,10 @@ void ConnectDialog::socketReadyRead()
|
||||
QString name = QString::fromUtf8(message.read<QByteArray>());
|
||||
|
||||
QUrl url;
|
||||
url.setScheme("ws");
|
||||
url.setHost(host.toString());
|
||||
url.setPort(port);
|
||||
url.setPath("/client");
|
||||
|
||||
auto it = m_servers.find(url);
|
||||
if(it == m_servers.end())
|
||||
@ -230,6 +238,12 @@ void ConnectDialog::serverTextChanged(const QString& text)
|
||||
{
|
||||
QString url{text};
|
||||
m_url = QUrl::fromUserInput(url.remove(QRegularExpression("\\s*\\(.*\\)$")));
|
||||
m_url.setScheme("ws");
|
||||
m_url.setPath("/client");
|
||||
if(m_url.port() == -1)
|
||||
{
|
||||
m_url.setPort(Connection::defaultPort);
|
||||
}
|
||||
m_connect->setEnabled(m_url.isValid());
|
||||
}
|
||||
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
#include "../widget/tablewidget.hpp"
|
||||
#include "../widget/alertwidget.hpp"
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
#include <unordered_set>
|
||||
|
||||
ObjectSelectListDialog::ObjectSelectListDialog(Method& method, bool multiSelect, QWidget* parent) :
|
||||
ObjectSelectListDialog(static_cast<InterfaceItem&>(method), multiSelect, parent)
|
||||
@ -147,9 +148,16 @@ void ObjectSelectListDialog::acceptRows(const QModelIndexList& indexes)
|
||||
{
|
||||
if(auto* m = dynamic_cast<Method*>(&m_item))
|
||||
{
|
||||
// Call method only once for each row, not for every index in row
|
||||
std::unordered_set<int> rowsDone;
|
||||
|
||||
for(const auto& index : indexes)
|
||||
{
|
||||
if(rowsDone.find(index.row()) != rowsDone.end())
|
||||
continue;
|
||||
|
||||
callMethod(*m, nullptr, m_tableWidget->getRowObjectId(index.row()));
|
||||
rowsDone.insert(index.row());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020,2023 Reinder Feenstra
|
||||
* Copyright (C) 2019-2020,2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -25,8 +25,13 @@
|
||||
#include <QTableView>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QPushButton>
|
||||
#include <QLineEdit>
|
||||
#include <QListView>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QItemDelegate>
|
||||
#include <QPainter>
|
||||
#include <QApplication>
|
||||
#include <QtWaitingSpinner/waitingspinnerwidget.h>
|
||||
#include "../widget/tablewidget.hpp"
|
||||
#include "../network/connection.hpp"
|
||||
#include "../network/object.hpp"
|
||||
#include "../network/tablemodel.hpp"
|
||||
@ -35,14 +40,70 @@
|
||||
#include "../widget/alertwidget.hpp"
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
|
||||
constexpr int columnUUID = 1;
|
||||
|
||||
class WorldListItemDelegate : public QItemDelegate
|
||||
{
|
||||
public:
|
||||
inline WorldListItemDelegate(QListView* parent)
|
||||
: QItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
inline void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const final
|
||||
{
|
||||
static const QSize iconSize{40, 40};
|
||||
|
||||
auto* model = qobject_cast<QListView*>(parent())->model();
|
||||
const auto name = model->data(model->index(index.row(), 0)).toString();
|
||||
const auto uuid = model->data(model->index(index.row(), 1)).toString();
|
||||
|
||||
const auto r = option.rect.adjusted(5, 5, -5, -5);
|
||||
const int iconOffset = (r.height() - iconSize.height()) / 2;
|
||||
|
||||
const auto palette = QApplication::palette();
|
||||
|
||||
if((option.state & QStyle::State_Selected) != 0)
|
||||
{
|
||||
painter->fillRect(option.rect, palette.brush(QPalette::Highlight));
|
||||
}
|
||||
|
||||
QTextOption textOption;
|
||||
textOption.setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
|
||||
const auto classIcon = Theme::getIcon("world");
|
||||
painter->drawPixmap(r.topLeft() + QPoint(iconOffset, iconOffset), classIcon.pixmap(iconSize));
|
||||
|
||||
painter->setPen(palette.color(QPalette::Disabled, QPalette::Text));
|
||||
painter->drawText(r.adjusted(r.height() + 10, r.height() / 2, 0, 0), uuid, textOption);
|
||||
|
||||
painter->setPen(palette.color(QPalette::Active, QPalette::Text));
|
||||
painter->drawText(r.adjusted(r.height() + 10, 0, 0, -r.height() / 2), name, textOption);
|
||||
|
||||
painter->setPen(QColor(0x80, 0x80, 0x80, 0x30));
|
||||
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
|
||||
}
|
||||
|
||||
inline QSize sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const final
|
||||
{
|
||||
return QSize(-1, 50);
|
||||
}
|
||||
};
|
||||
|
||||
WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget* parent) :
|
||||
QDialog(parent, Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint),
|
||||
m_connection{std::move(connection)},
|
||||
m_buttons{new QDialogButtonBox(this)},
|
||||
m_tableWidget{new TableWidget()}
|
||||
m_search{new QLineEdit(this)},
|
||||
m_list{new QListView(this)}
|
||||
{
|
||||
setWindowTitle(Locale::tr("qtapp.world_list_dialog:world_list"));
|
||||
setWindowIcon(Theme::getIcon("world_load"));
|
||||
resize(500, 400);
|
||||
|
||||
m_search->setPlaceholderText(Locale::tr("qtapp.world_list_dialog:search"));
|
||||
|
||||
m_list->setItemDelegate(new WorldListItemDelegate(m_list));
|
||||
|
||||
m_buttons->setStandardButtons(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
|
||||
m_buttons->button(QDialogButtonBox::Open)->setText(Locale::tr("qtapp.world_list_dialog:load"));
|
||||
@ -50,14 +111,15 @@ WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget
|
||||
connect(m_buttons->button(QDialogButtonBox::Open), &QPushButton::clicked, this,
|
||||
[this]()
|
||||
{
|
||||
m_uuid = m_tableWidget->getRowObjectId(m_tableWidget->selectionModel()->selectedIndexes().first().row());
|
||||
m_uuid = m_list->model()->data(m_list->model()->index(m_list->selectionModel()->selectedIndexes().first().row(), columnUUID)).toString();
|
||||
accept();
|
||||
});
|
||||
m_buttons->button(QDialogButtonBox::Cancel)->setText(Locale::tr("qtapp.world_list_dialog:cancel"));
|
||||
connect(m_buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &WorldListDialog::reject);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
layout->addWidget(m_tableWidget);
|
||||
layout->addWidget(m_search);
|
||||
layout->addWidget(m_list);
|
||||
layout->addWidget(m_buttons);
|
||||
setLayout(layout);
|
||||
|
||||
@ -78,20 +140,26 @@ WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget
|
||||
{
|
||||
m_requestId = Connection::invalidRequestId;
|
||||
|
||||
m_tableWidget->setTableModel(tableModel);
|
||||
m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
connect(m_tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
m_tableModel = tableModel;
|
||||
m_tableModel->setRegionAll(true);
|
||||
auto* filter = new QSortFilterProxyModel(this);
|
||||
filter->setSourceModel(m_tableModel.get());
|
||||
filter->setFilterCaseSensitivity(Qt::CaseInsensitive);
|
||||
connect(m_search, &QLineEdit::textChanged, filter, &QSortFilterProxyModel::setFilterFixedString);
|
||||
filter->setFilterFixedString(m_search->text());
|
||||
m_list->setModel(filter);
|
||||
m_list->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
connect(m_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
[this](const QItemSelection&, const QItemSelection&)
|
||||
{
|
||||
m_buttons->button(QDialogButtonBox::Open)->setEnabled(m_tableWidget->selectionModel()->selectedRows().count() == 1);
|
||||
m_buttons->button(QDialogButtonBox::Open)->setEnabled(m_list->selectionModel()->selectedRows().count() == 1);
|
||||
});
|
||||
connect(m_tableWidget, &TableWidget::doubleClicked, this,
|
||||
connect(m_list, &QListView::doubleClicked, this,
|
||||
[this](const QModelIndex& index)
|
||||
{
|
||||
m_uuid = m_tableWidget->getRowObjectId(index.row());
|
||||
m_uuid = m_list->model()->data(m_list->model()->index(index.row(), columnUUID)).toString();
|
||||
accept();
|
||||
});
|
||||
|
||||
delete spinner;
|
||||
}
|
||||
else if(err)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2019-2020,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -25,9 +25,11 @@
|
||||
|
||||
#include <QDialog>
|
||||
#include "../network/objectptr.hpp"
|
||||
#include "../network/tablemodelptr.hpp"
|
||||
|
||||
class QDialogButtonBox;
|
||||
class TableWidget;
|
||||
class QLineEdit;
|
||||
class QListView;
|
||||
class Connection;
|
||||
|
||||
class WorldListDialog final : public QDialog
|
||||
@ -39,8 +41,10 @@ class WorldListDialog final : public QDialog
|
||||
int m_requestId;
|
||||
ObjectPtr m_object;
|
||||
QDialogButtonBox* m_buttons; // TODO: m_buttonLoad;
|
||||
TableWidget* m_tableWidget;
|
||||
QLineEdit* m_search;
|
||||
QListView* m_list;
|
||||
QString m_uuid;
|
||||
TableModelPtr m_tableModel;
|
||||
|
||||
public:
|
||||
explicit WorldListDialog(std::shared_ptr<Connection> connection, QWidget* parent = nullptr);
|
||||
|
||||
@ -26,6 +26,9 @@
|
||||
#endif
|
||||
#include <QCommandLineParser>
|
||||
#include <QMessageBox>
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
#include <QStyleHints>
|
||||
#endif
|
||||
#include <version.hpp>
|
||||
#include "mainwindow.hpp"
|
||||
#include "settings/generalsettings.hpp"
|
||||
@ -124,9 +127,15 @@ int main(int argc, char* argv[])
|
||||
if(logMissingStrings)
|
||||
const_cast<Locale*>(Locale::instance.get())->enableMissingLogging();
|
||||
|
||||
// Auto select icon set based on background color lightness:
|
||||
const qreal backgroundLightness = QApplication::style()->standardPalette().window().color().lightnessF();
|
||||
Theme::setIconSet(backgroundLightness < 0.5 ? Theme::IconSet::Dark : Theme::IconSet::Light);
|
||||
// Detect light/dark:
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 5, 0)
|
||||
const QPalette defaultPalette;
|
||||
const auto text = defaultPalette.color(QPalette::WindowText);
|
||||
const auto window = defaultPalette.color(QPalette::Window);
|
||||
Theme::setDark(text.lightness() > window.lightness());
|
||||
#else
|
||||
Theme::setDark(QGuiApplication::styleHints()->colorScheme() == Qt::ColorScheme::Dark);
|
||||
#endif
|
||||
|
||||
MainWindow mw;
|
||||
if(options.fullscreen)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2024 Reinder Feenstra
|
||||
* Copyright (C) 2019-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -124,6 +124,7 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||
QAction* boardsAction;
|
||||
QAction* trainsAction;
|
||||
|
||||
m_mdiArea->setBackground(palette().window().color().darker());
|
||||
m_mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
m_mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
|
||||
|
||||
@ -433,6 +434,7 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||
auto* tabs = new QTabWidget(m_trainAndRailVehiclesSubWindow);
|
||||
tabs->addTab(new ObjectEditWidget("world.trains"), Locale::tr("world:trains"));
|
||||
tabs->addTab(new ObjectEditWidget("world.rail_vehicles"), Locale::tr("world:rail_vehicles"));
|
||||
tabs->addTab(new ObjectEditWidget("world.throttles"), Locale::tr("hardware:throttles"));
|
||||
m_trainAndRailVehiclesSubWindow->setWidget(tabs);
|
||||
m_mdiArea->addSubWindow(m_trainAndRailVehiclesSubWindow);
|
||||
m_trainAndRailVehiclesSubWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||
@ -519,7 +521,7 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||
})->setShortcut(QKeySequence::HelpContents);
|
||||
auto* subMenu = menu->addMenu(Locale::tr("qtapp.mainmenu:wizards"));
|
||||
subMenu->addAction(Locale::tr("wizard.introduction:title"), this, &MainWindow::showIntroductionWizard);
|
||||
subMenu->addAction(Locale::tr("wizard.add_interface.welcome:title"), this, &MainWindow::showAddInterfaceWizard);
|
||||
m_actionAddInterfaceWizard = subMenu->addAction(Locale::tr("wizard.add_interface.welcome:title"), this, &MainWindow::showAddInterfaceWizard);
|
||||
//menu->addSeparator();
|
||||
//menu->addAction(Locale::tr("qtapp.mainmenu:about_qt") + "...", qApp, &QApplication::aboutQt);
|
||||
menu->addAction(Locale::tr("qtapp.mainmenu:about") + "...", this, &MainWindow::showAbout);
|
||||
@ -676,7 +678,8 @@ void MainWindow::changeEvent(QEvent* event)
|
||||
|
||||
void MainWindow::worldChanged()
|
||||
{
|
||||
m_newWorldWizard.reset();
|
||||
m_wizard.newWorld.reset();
|
||||
m_wizard.addInterface.reset();
|
||||
|
||||
if(m_world)
|
||||
m_mdiArea->closeAllSubWindows();
|
||||
@ -747,13 +750,13 @@ void MainWindow::worldChanged()
|
||||
if(m_newWorldRequested && m_world)
|
||||
{
|
||||
m_newWorldRequested = false;
|
||||
m_newWorldWizard = std::make_unique<NewWorldWizard>(m_world, this);
|
||||
connect(m_newWorldWizard.get(), &NewWorldWizard::finished,
|
||||
m_wizard.newWorld = std::make_unique<NewWorldWizard>(m_world, this);
|
||||
connect(m_wizard.newWorld.get(), &NewWorldWizard::finished,
|
||||
[this]()
|
||||
{
|
||||
m_newWorldWizard.release()->deleteLater();
|
||||
m_wizard.newWorld.release()->deleteLater();
|
||||
});
|
||||
m_newWorldWizard->open();
|
||||
m_wizard.newWorld->open();
|
||||
}
|
||||
}
|
||||
|
||||
@ -946,17 +949,18 @@ IntroductionWizard* MainWindow::showIntroductionWizard()
|
||||
return introductionWizard;
|
||||
}
|
||||
|
||||
AddInterfaceWizard* MainWindow::showAddInterfaceWizard()
|
||||
void MainWindow::showAddInterfaceWizard()
|
||||
{
|
||||
if(!m_world) /*[[unlikely]]*/
|
||||
if(m_world && !m_wizard.addInterface) /*[[likely]]*/
|
||||
{
|
||||
return nullptr;
|
||||
m_wizard.addInterface = std::make_unique<AddInterfaceWizard>(m_world, this);
|
||||
connect(m_wizard.addInterface.get(), &AddInterfaceWizard::finished,
|
||||
[this]()
|
||||
{
|
||||
m_wizard.addInterface.release()->deleteLater();
|
||||
});
|
||||
m_wizard.addInterface->open();
|
||||
}
|
||||
|
||||
auto* addInterfaceWizard = new AddInterfaceWizard(m_world, this);
|
||||
addInterfaceWizard->setAttribute(Qt::WA_DeleteOnClose);
|
||||
addInterfaceWizard->open();
|
||||
return addInterfaceWizard;
|
||||
}
|
||||
|
||||
NewBoardWizard* MainWindow::showNewBoardWizard(const ObjectPtr& board)
|
||||
@ -1010,7 +1014,6 @@ void MainWindow::updateActions()
|
||||
const bool connected = m_connection && m_connection->state() == Connection::State::Connected;
|
||||
const bool haveWorld = connected && m_connection->world();
|
||||
|
||||
|
||||
m_actionConnectToServer->setEnabled(!m_connection);
|
||||
m_actionConnectToServer->setVisible(!connected);
|
||||
m_actionDisconnectFromServer->setVisible(connected);
|
||||
@ -1034,6 +1037,7 @@ void MainWindow::updateActions()
|
||||
m_actionServerShutdown->setEnabled(m && m->getAttributeBool(AttributeName::Enabled, false));
|
||||
}
|
||||
m_menuProgramming->setEnabled(haveWorld);
|
||||
m_actionAddInterfaceWizard->setEnabled(haveWorld);
|
||||
|
||||
setMenuEnabled(m_menuWorld, haveWorld);
|
||||
m_worldOnlineOfflineToolButton->setEnabled(haveWorld);
|
||||
|
||||
@ -58,7 +58,11 @@ class MainWindow final : public QMainWindow
|
||||
ObjectPtr m_world;
|
||||
bool m_newWorldRequested = false;
|
||||
std::unique_ptr<WorldListDialog> m_loadWorldDialog;
|
||||
std::unique_ptr<NewWorldWizard> m_newWorldWizard;
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<AddInterfaceWizard> addInterface;
|
||||
std::unique_ptr<NewWorldWizard> newWorld;
|
||||
} m_wizard;
|
||||
int m_clockRequest;
|
||||
ObjectPtr m_clock;
|
||||
QSplitter* m_splitter;
|
||||
@ -103,6 +107,7 @@ class MainWindow final : public QMainWindow
|
||||
QAction* m_actionServerShutdown;
|
||||
QAction* m_actionServerLog;
|
||||
QMenu* m_menuProgramming;
|
||||
QAction* m_actionAddInterfaceWizard;
|
||||
// Main toolbar:
|
||||
QToolBar* m_toolbar;
|
||||
QToolButton* m_worldOnlineOfflineToolButton;
|
||||
@ -146,7 +151,7 @@ class MainWindow final : public QMainWindow
|
||||
const ObjectPtr& worldClock() const { return m_clock; }
|
||||
|
||||
IntroductionWizard* showIntroductionWizard();
|
||||
AddInterfaceWizard* showAddInterfaceWizard();
|
||||
void showAddInterfaceWizard();
|
||||
NewBoardWizard* showNewBoardWizard(const ObjectPtr& board);
|
||||
void showLuaScriptsList();
|
||||
|
||||
|
||||
@ -133,7 +133,10 @@ void MainWindowStatusBar::updateStatuses()
|
||||
m_mainWindow.connection()->cancelRequest(m_statusesRequest);
|
||||
|
||||
if(statuses->empty())
|
||||
{
|
||||
clearStatuses();
|
||||
return;
|
||||
}
|
||||
|
||||
m_statusesRequest = statuses->getObjects(0, statuses->size() - 1,
|
||||
[this](const std::vector<ObjectPtr>& objects, std::optional<const Error> error)
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
*/
|
||||
|
||||
#include "connection.hpp"
|
||||
#include <QTcpSocket>
|
||||
#include <QWebSocket>
|
||||
#include <QUrl>
|
||||
#include <QCryptographicHash>
|
||||
#include <traintastic/network/message.hpp>
|
||||
@ -130,23 +130,31 @@ inline static QStringList readObjectIdArray(const Message& message, const int le
|
||||
|
||||
Connection::Connection() :
|
||||
QObject(),
|
||||
m_socket{new QTcpSocket(this)},
|
||||
m_socket{new QWebSocket()},
|
||||
m_state{State::Disconnected},
|
||||
m_worldProperty{nullptr},
|
||||
m_worldRequestId{invalidRequestId}
|
||||
, m_serverLogTableModel{nullptr}
|
||||
{
|
||||
connect(m_socket, &QTcpSocket::connected, this, &Connection::socketConnected);
|
||||
connect(m_socket, &QTcpSocket::disconnected, this, &Connection::socketDisconnected);
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
|
||||
connect(m_socket, static_cast<void(QTcpSocket::*)(QAbstractSocket::SocketError)>(&QTcpSocket::error), this, &Connection::socketError);
|
||||
connect(m_socket, &QWebSocket::connected, this, &Connection::socketConnected);
|
||||
connect(m_socket, &QWebSocket::disconnected, this, &Connection::socketDisconnected);
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
|
||||
connect(m_socket, &QWebSocket::errorOccurred, this, &Connection::socketError);
|
||||
#else
|
||||
connect(m_socket, &QTcpSocket::errorOccurred, this, &Connection::socketError);
|
||||
connect(m_socket, static_cast<void(QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error), this, &Connection::socketError);
|
||||
#endif
|
||||
|
||||
m_socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
|
||||
|
||||
connect(m_socket, &QTcpSocket::readyRead, this, &Connection::socketReadyRead);
|
||||
connect(m_socket, &QWebSocket::binaryMessageReceived,
|
||||
[this](const QByteArray& data)
|
||||
{
|
||||
const Message::Header& header = *reinterpret_cast<const Message::Header*>(data.data());
|
||||
auto message = std::make_shared<Message>(header);
|
||||
if(header.dataSize != 0)
|
||||
{
|
||||
std::memcpy(message->data(), data.data() + sizeof(header), message->dataSize());
|
||||
}
|
||||
processMessage(message);
|
||||
});
|
||||
}
|
||||
|
||||
bool Connection::isDisconnected() const
|
||||
@ -172,12 +180,12 @@ void Connection::connectToHost(const QUrl& url, const QString& username, const Q
|
||||
else
|
||||
m_password = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha256);
|
||||
setState(State::Connecting);
|
||||
m_socket->connectToHost(url.host(), static_cast<quint16>(url.port(defaultPort)));
|
||||
m_socket->open(url);
|
||||
}
|
||||
|
||||
void Connection::disconnectFromHost()
|
||||
{
|
||||
m_socket->disconnectFromHost();
|
||||
m_socket->close();
|
||||
}
|
||||
|
||||
void Connection::cancelRequest(int requestId)
|
||||
@ -417,7 +425,7 @@ void Connection::releaseTableModel(TableModel* tableModel)
|
||||
tableModel->m_handle = invalidHandle;
|
||||
}
|
||||
|
||||
void Connection::setTableModelRegion(TableModel* tableModel, int columnMin, int columnMax, int rowMin, int rowMax)
|
||||
void Connection::setTableModelRegion(TableModel* tableModel, uint32_t columnMin, uint32_t columnMax, uint32_t rowMin, uint32_t rowMax)
|
||||
{
|
||||
auto event = Message::newEvent(Message::Command::TableModelSetRegion);
|
||||
event->write(tableModel->handle());
|
||||
@ -443,7 +451,8 @@ int Connection::getTileData(Board& object)
|
||||
void Connection::send(std::unique_ptr<Message>& message)
|
||||
{
|
||||
Q_ASSERT(!message->isRequest());
|
||||
m_socket->write(static_cast<const char*>(**message), message->size());
|
||||
QByteArray bytes(static_cast<const char*>(**message), message->size()); // Deep copy :(
|
||||
m_socket->sendBinaryMessage(bytes); // sendBinaryMessage only supports QByteArray
|
||||
}
|
||||
|
||||
void Connection::send(std::unique_ptr<Message>& message, std::function<void(const std::shared_ptr<Message>&)> callback)
|
||||
@ -451,7 +460,8 @@ void Connection::send(std::unique_ptr<Message>& message, std::function<void(cons
|
||||
Q_ASSERT(message->isRequest());
|
||||
Q_ASSERT(!m_requestCallback.contains(message->requestId()));
|
||||
m_requestCallback[message->requestId()] = callback;
|
||||
m_socket->write(static_cast<const char*>(**message), message->size());
|
||||
QByteArray bytes(static_cast<const char*>(**message), message->size()); // Deep copy :(
|
||||
m_socket->sendBinaryMessage(bytes); // sendBinaryMessage only supports QByteArray
|
||||
}
|
||||
|
||||
ObjectPtr Connection::readObject(const Message& message)
|
||||
@ -1050,14 +1060,14 @@ void Connection::socketConnected()
|
||||
else
|
||||
{
|
||||
setState(State::ErrorNewSessionFailed);
|
||||
m_socket->disconnectFromHost();
|
||||
m_socket->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
setState(State::ErrorAuthenticationFailed);
|
||||
m_socket->disconnectFromHost();
|
||||
m_socket->close();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1071,32 +1081,3 @@ void Connection::socketError(QAbstractSocket::SocketError)
|
||||
{
|
||||
setState(State::SocketError);
|
||||
}
|
||||
|
||||
void Connection::socketReadyRead()
|
||||
{
|
||||
while(m_socket->bytesAvailable() != 0)
|
||||
{
|
||||
if(!m_readBuffer.message) // read header
|
||||
{
|
||||
m_readBuffer.offset += m_socket->read(reinterpret_cast<char*>(&m_readBuffer.header) + m_readBuffer.offset, sizeof(m_readBuffer.header) - m_readBuffer.offset);
|
||||
if(m_readBuffer.offset == sizeof(m_readBuffer.header))
|
||||
{
|
||||
if(m_readBuffer.header.dataSize != 0)
|
||||
m_readBuffer.message = std::make_shared<Message>(m_readBuffer.header);
|
||||
else
|
||||
processMessage(std::make_shared<Message>(m_readBuffer.header));
|
||||
m_readBuffer.offset = 0;
|
||||
}
|
||||
}
|
||||
else // read data
|
||||
{
|
||||
m_readBuffer.offset += m_socket->read(reinterpret_cast<char*>(m_readBuffer.message->data()) + m_readBuffer.offset, m_readBuffer.message->dataSize() - m_readBuffer.offset);
|
||||
if(m_readBuffer.offset == m_readBuffer.message->dataSize())
|
||||
{
|
||||
processMessage(m_readBuffer.message);
|
||||
m_readBuffer.message.reset();
|
||||
m_readBuffer.offset = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021,2023 Reinder Feenstra
|
||||
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -35,7 +35,7 @@
|
||||
#include "objectptr.hpp"
|
||||
#include "tablemodelptr.hpp"
|
||||
|
||||
class QTcpSocket;
|
||||
class QWebSocket;
|
||||
class ServerLogTableModel;
|
||||
class Property;
|
||||
class ObjectProperty;
|
||||
@ -73,7 +73,7 @@ class Connection : public QObject, public std::enable_shared_from_this<Connectio
|
||||
using SocketError = QAbstractSocket::SocketError;
|
||||
|
||||
protected:
|
||||
QTcpSocket* m_socket;
|
||||
QWebSocket* m_socket;
|
||||
State m_state;
|
||||
QString m_username;
|
||||
QByteArray m_password;
|
||||
@ -108,7 +108,6 @@ class Connection : public QObject, public std::enable_shared_from_this<Connectio
|
||||
void socketConnected();
|
||||
void socketDisconnected();
|
||||
void socketError(QAbstractSocket::SocketError);
|
||||
void socketReadyRead();
|
||||
|
||||
public:
|
||||
static const quint16 defaultPort = 5740;
|
||||
@ -153,7 +152,7 @@ class Connection : public QObject, public std::enable_shared_from_this<Connectio
|
||||
|
||||
[[nodiscard]] int getTableModel(const ObjectPtr& object, std::function<void(const TableModelPtr&, std::optional<const Error>)> callback);
|
||||
void releaseTableModel(TableModel* tableModel);
|
||||
void setTableModelRegion(TableModel* tableModel, int columnMin, int columnMax, int rowMin, int rowMax);
|
||||
void setTableModelRegion(TableModel* tableModel, uint32_t columnMin, uint32_t columnMax, uint32_t rowMin, uint32_t rowMax);
|
||||
|
||||
[[nodiscard]] int getTileData(Board& object);
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021,2023 Reinder Feenstra
|
||||
* Copyright (C) 2019-2021,2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -72,7 +72,16 @@ QString TableModel::getValue(int column, int row) const
|
||||
return m_texts.value(ColumnRow(column, row));
|
||||
}
|
||||
|
||||
void TableModel::setRegion(int columnMin, int columnMax, int rowMin, int rowMax)
|
||||
void TableModel::setRegionAll(bool enable)
|
||||
{
|
||||
m_regionAll = enable;
|
||||
if(m_regionAll)
|
||||
{
|
||||
updateRegionAll();
|
||||
}
|
||||
}
|
||||
|
||||
void TableModel::setRegion(uint32_t columnMin, uint32_t columnMax, uint32_t rowMin, uint32_t rowMax)
|
||||
{
|
||||
if(m_region.columnMin != columnMin ||
|
||||
m_region.columnMax != columnMax ||
|
||||
@ -94,6 +103,10 @@ void TableModel::setColumnHeaders(const QVector<QString>& values)
|
||||
{
|
||||
beginResetModel();
|
||||
m_columnHeaders = values;
|
||||
if(m_regionAll)
|
||||
{
|
||||
updateRegionAll();
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
@ -104,6 +117,22 @@ void TableModel::setRowCount(int value)
|
||||
{
|
||||
beginResetModel();
|
||||
m_rowCount = value;
|
||||
if(m_regionAll)
|
||||
{
|
||||
updateRegionAll();
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
|
||||
void TableModel::updateRegionAll()
|
||||
{
|
||||
if(columnCount() == 0 || rowCount() == 0)
|
||||
{
|
||||
setRegion(1, 0, 1, 0); // select nothing
|
||||
}
|
||||
else
|
||||
{
|
||||
setRegion(0, columnCount() - 1, 0, rowCount() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
|
||||
* Copyright (C) 2019-2021,2023-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -45,16 +45,20 @@ class TableModel final : public QAbstractTableModel
|
||||
int m_rowCount;
|
||||
struct Region
|
||||
{
|
||||
int rowMin = 0;
|
||||
int rowMax = -1;
|
||||
int columnMin = 0;
|
||||
int columnMax = -1;
|
||||
// Default to invalid region
|
||||
uint32_t rowMin = 1;
|
||||
uint32_t rowMax = 0;
|
||||
uint32_t columnMin = 1;
|
||||
uint32_t columnMax = 0;
|
||||
} m_region;
|
||||
bool m_regionAll = false;
|
||||
QMap<ColumnRow, QString> m_texts;
|
||||
|
||||
void setColumnHeaders(const QVector<QString>& values);
|
||||
void setRowCount(int value);
|
||||
|
||||
void updateRegionAll();
|
||||
|
||||
public:
|
||||
explicit TableModel(std::shared_ptr<Connection> connection, Handle handle, const QString& classId, QObject* parent = nullptr);
|
||||
~TableModel() final;
|
||||
@ -71,7 +75,8 @@ class TableModel final : public QAbstractTableModel
|
||||
QString getRowObjectId(int row) const;
|
||||
QString getValue(int column, int row) const;
|
||||
|
||||
void setRegion(int columnMin, int columnMax, int rowMin, int rowMax);
|
||||
void setRegionAll(bool enable);
|
||||
void setRegion(uint32_t columnMin, uint32_t columnMax, uint32_t rowMin, uint32_t rowMax);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021,2023 Reinder Feenstra
|
||||
* Copyright (C) 2021,2023-2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -23,6 +23,7 @@
|
||||
#include "theme.hpp"
|
||||
#include <cassert>
|
||||
#include <QFile>
|
||||
#include <QPalette>
|
||||
|
||||
const QString iconPathDefault = QStringLiteral(":/");
|
||||
const QString iconPathDark = QStringLiteral(":/dark/");
|
||||
@ -31,8 +32,20 @@ const QString iconExtension = QStringLiteral(".svg");
|
||||
const std::array<const QString*, 3> iconPathsDark = {&iconPathDark, &iconPathDefault, &iconPathLight};
|
||||
const std::array<const QString*, 3> iconPathsLight = {&iconPathLight, &iconPathDefault, &iconPathDark};
|
||||
|
||||
bool Theme::s_isDark = false;
|
||||
Theme::IconSet Theme::s_iconSet = Theme::IconSet::Light;
|
||||
|
||||
bool Theme::isDark()
|
||||
{
|
||||
return s_isDark;
|
||||
}
|
||||
|
||||
void Theme::setDark(bool value)
|
||||
{
|
||||
s_isDark = value;
|
||||
setIconSet(value ? Theme::IconSet::Dark : Theme::IconSet::Light);
|
||||
}
|
||||
|
||||
void Theme::setIconSet(IconSet value)
|
||||
{
|
||||
s_iconSet = value;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021 Reinder Feenstra
|
||||
* Copyright (C) 2021,2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -39,11 +39,15 @@ class Theme
|
||||
private:
|
||||
Theme() = delete;
|
||||
|
||||
static bool s_isDark;
|
||||
static IconSet s_iconSet;
|
||||
|
||||
static const std::array<const QString*, 3>& getIconPaths();
|
||||
|
||||
public:
|
||||
static bool isDark();
|
||||
static void setDark(bool value);
|
||||
|
||||
static void setIconSet(IconSet value);
|
||||
|
||||
static QString getIconFile(const QString& id);
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -23,6 +23,7 @@
|
||||
#include "createwidget.hpp"
|
||||
#include "list/marklincanlocomotivelistwidget.hpp"
|
||||
#include "objectlist/boardlistwidget.hpp"
|
||||
#include "objectlist/interfacelistwidget.hpp"
|
||||
#include "objectlist/throttleobjectlistwidget.hpp"
|
||||
#include "objectlist/trainlistwidget.hpp"
|
||||
#include "objectlist/zoneblocklistwidget.hpp"
|
||||
@ -36,6 +37,7 @@
|
||||
#include "propertydoublespinbox.hpp"
|
||||
#include "propertyspinbox.hpp"
|
||||
#include "propertylineedit.hpp"
|
||||
#include "propertypairoutputaction.hpp"
|
||||
#include "../board/boardwidget.hpp"
|
||||
#include "../network/object.hpp"
|
||||
#include "../network/inputmonitor.hpp"
|
||||
@ -47,8 +49,10 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
|
||||
{
|
||||
const QString& classId = object->classId();
|
||||
|
||||
if(classId == "command_station_list")
|
||||
return new ObjectListWidget(object, parent); // todo remove
|
||||
if(classId == "list.interface")
|
||||
{
|
||||
return new InterfaceListWidget(object, parent);
|
||||
}
|
||||
else if(classId == "decoder_list")
|
||||
return new ThrottleObjectListWidget(object, parent); // todo remove
|
||||
else if(classId == "controller_list")
|
||||
@ -127,6 +131,10 @@ QWidget* createWidget(Property& property, QWidget* parent)
|
||||
break; // TODO
|
||||
|
||||
case ValueType::Enum:
|
||||
if(property.enumName() == "pair_output_action")
|
||||
{
|
||||
return new PropertyPairOutputAction(property, parent);
|
||||
}
|
||||
return new PropertyComboBox(property, parent);
|
||||
|
||||
case ValueType::Integer:
|
||||
|
||||
95
client/src/widget/methodicon.cpp
Normale Datei
95
client/src/widget/methodicon.cpp
Normale Datei
@ -0,0 +1,95 @@
|
||||
/**
|
||||
* client/src/widget/methodicon.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "methodicon.hpp"
|
||||
#include <QMouseEvent>
|
||||
#include <QIcon>
|
||||
#include "../network/method.hpp"
|
||||
|
||||
MethodIcon::MethodIcon(Method& method, QIcon icon, QWidget* parent) :
|
||||
QLabel(parent),
|
||||
m_method{method}
|
||||
{
|
||||
setPixmap(icon.pixmap(32, 32));
|
||||
setCursor(Qt::PointingHandCursor);
|
||||
setEnabled(m_method.getAttributeBool(AttributeName::Enabled, true));
|
||||
setVisible(m_method.getAttributeBool(AttributeName::Visible, true));
|
||||
setToolTip(m_method.displayName());
|
||||
connect(&m_method, &Method::attributeChanged, this,
|
||||
[this](AttributeName name, const QVariant& value)
|
||||
{
|
||||
switch(name)
|
||||
{
|
||||
case AttributeName::Enabled:
|
||||
setEnabled(value.toBool());
|
||||
break;
|
||||
|
||||
case AttributeName::Visible:
|
||||
setVisible(value.toBool());
|
||||
break;
|
||||
|
||||
case AttributeName::DisplayName:
|
||||
setToolTip(m_method.displayName());
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
MethodIcon::MethodIcon(Method& item, QIcon icon, std::function<void()> triggered, QWidget* parent)
|
||||
: MethodIcon(item, icon, parent)
|
||||
{
|
||||
m_triggered = std::move(triggered);
|
||||
}
|
||||
|
||||
void MethodIcon::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
if(event->button() == Qt::LeftButton)
|
||||
{
|
||||
m_mouseLeftButtonPressed = true;
|
||||
}
|
||||
}
|
||||
|
||||
void MethodIcon::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
if(m_mouseLeftButtonPressed && event->button() == Qt::LeftButton)
|
||||
{
|
||||
m_mouseLeftButtonPressed = false;
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
if(rect().contains(event->localPos().toPoint())) // test if mouse release in widget
|
||||
#else
|
||||
if(rect().contains(event->position().toPoint())) // test if mouse release in widget
|
||||
#endif
|
||||
{
|
||||
if(m_triggered)
|
||||
{
|
||||
m_triggered();
|
||||
}
|
||||
else
|
||||
{
|
||||
m_method.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/hardware/throttle/throttlefunction.hpp
|
||||
* client/src/widget/methodicon.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2022 Reinder Feenstra
|
||||
* Copyright (C) 2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -20,32 +20,26 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_HARDWARE_THROTTLE_THROTTLEFUNCTION_HPP
|
||||
#define TRAINTASTIC_SERVER_HARDWARE_THROTTLE_THROTTLEFUNCTION_HPP
|
||||
#ifndef TRAINTASTIC_CLIENT_WIDGET_METHODICON_HPP
|
||||
#define TRAINTASTIC_CLIENT_WIDGET_METHODICON_HPP
|
||||
|
||||
#include "../../core/object.hpp"
|
||||
#include "../../core/property.hpp"
|
||||
#include "../../core/method.hpp"
|
||||
#include <QLabel>
|
||||
|
||||
class Throttle;
|
||||
class Method;
|
||||
|
||||
class ThrottleFunction : public Object
|
||||
class MethodIcon : public QLabel
|
||||
{
|
||||
CLASS_ID("throttle_function")
|
||||
protected:
|
||||
Method& m_method;
|
||||
std::function<void()> m_triggered;
|
||||
bool m_mouseLeftButtonPressed;
|
||||
|
||||
private:
|
||||
Throttle& m_throttle;
|
||||
void mousePressEvent(QMouseEvent* event) final;
|
||||
void mouseReleaseEvent(QMouseEvent* event) final;
|
||||
|
||||
public:
|
||||
Property<uint32_t> number;
|
||||
Property<std::string> name;
|
||||
Property<bool> value;
|
||||
Method<void()> press;
|
||||
Method<void()> release;
|
||||
|
||||
ThrottleFunction(Throttle& throttle, uint32_t number);
|
||||
|
||||
std::string getObjectId() const final;
|
||||
MethodIcon(Method& item, QIcon icon, QWidget* parent = nullptr);
|
||||
MethodIcon(Method& item, QIcon icon, std::function<void()> triggered, QWidget* parent = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
119
client/src/widget/objectlist/interfacelistwidget.cpp
Normale Datei
119
client/src/widget/objectlist/interfacelistwidget.cpp
Normale Datei
@ -0,0 +1,119 @@
|
||||
/**
|
||||
* client/src/widget/objectlist/interfacelistwidget.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "interfacelistwidget.hpp"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QItemDelegate>
|
||||
#include <QListView>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
|
||||
#include <traintastic/enum/interfacestate.hpp>
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
|
||||
#include "../methodicon.hpp"
|
||||
#include "../../theme/theme.hpp"
|
||||
#include "../../mainwindow.hpp"
|
||||
|
||||
class InterfaceListItemDelegate : public QItemDelegate
|
||||
{
|
||||
public:
|
||||
inline InterfaceListItemDelegate(QListView* parent)
|
||||
: QItemDelegate(parent)
|
||||
{
|
||||
}
|
||||
|
||||
inline void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const final
|
||||
{
|
||||
static const QSize classIconSize{40, 40};
|
||||
static const QSize stateIconSize{16, 16};
|
||||
|
||||
auto* model = qobject_cast<QListView*>(parent())->model();
|
||||
const auto id = model->data(model->index(index.row(), 0)).toString().prepend('#');
|
||||
const auto name = model->data(model->index(index.row(), 1)).toString();
|
||||
const auto state = model->data(model->index(index.row(), 2)).toString();
|
||||
const auto classId = model->data(model->index(index.row(), 3)).toString();
|
||||
|
||||
const auto r = option.rect.adjusted(5, 5, -5, -5);
|
||||
const int iconOffset = (r.height() - classIconSize.height()) / 2;
|
||||
|
||||
const auto palette = QApplication::palette();
|
||||
|
||||
QTextOption textOption;
|
||||
textOption.setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
|
||||
const auto classIcon = Theme::getIconForClassId(classId);
|
||||
if(!classIcon.isNull())
|
||||
{
|
||||
painter->drawPixmap(r.topLeft() + QPoint(iconOffset, iconOffset), classIcon.pixmap(classIconSize));
|
||||
}
|
||||
|
||||
painter->setPen(palette.color(QPalette::Disabled, QPalette::Text));
|
||||
painter->drawText(r.adjusted((classIcon.isNull() ? 0 : r.height() + 10), r.height() / 2, 0, 0), id, textOption);
|
||||
|
||||
painter->setPen(palette.color(QPalette::Active, QPalette::Text));
|
||||
painter->drawText(r.adjusted((classIcon.isNull() ? 0 : r.height() + 10), 0, 0, -r.height() / 2), name, textOption);
|
||||
|
||||
textOption.setAlignment(Qt::AlignRight | Qt::AlignVCenter);
|
||||
const auto stateText = Locale::tr(QString(EnumName<InterfaceState>::value).append(":").append(state));
|
||||
const auto stateRect = r.adjusted(0, r.height() / 2, 0, 0);
|
||||
const auto stateTextWidth = qCeil(painter->boundingRect(stateRect, stateText, textOption).width());
|
||||
painter->drawText(stateRect, stateText, textOption);
|
||||
|
||||
auto stateIcon = QIcon(Theme::getIconFile(QString("interface_state.").append(state)));
|
||||
painter->drawPixmap(
|
||||
stateRect.topRight() - QPoint(stateTextWidth + stateIconSize.width() + 5, -(stateRect.height() - stateIconSize.height()) / 2),
|
||||
stateIcon.pixmap(stateIconSize));
|
||||
|
||||
painter->setPen(QColor(0x80, 0x80, 0x80, 0x30));
|
||||
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
|
||||
}
|
||||
|
||||
inline QSize sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const final
|
||||
{
|
||||
return QSize(-1, 50);
|
||||
}
|
||||
};
|
||||
|
||||
InterfaceListWidget::InterfaceListWidget(const ObjectPtr& object, QWidget* parent)
|
||||
: StackedObjectListWidget(object, parent)
|
||||
{
|
||||
m_list->setItemDelegate(new InterfaceListItemDelegate(m_list));
|
||||
m_listEmptyLabel->setText(Locale::tr("interface_list:list_is_empty"));
|
||||
|
||||
if(m_create) /*[[likely]]*/
|
||||
{
|
||||
m_create->setToolTip(Locale::tr("interface_list:create"));
|
||||
}
|
||||
|
||||
if(m_createMenu) /*[[likely]]*/
|
||||
{
|
||||
m_createMenu->addSeparator();
|
||||
m_createMenu->addAction(Theme::getIcon("wizard"), Locale::tr("list:setup_using_wizard") + "...",
|
||||
[]()
|
||||
{
|
||||
MainWindow::instance->showAddInterfaceWizard();
|
||||
});
|
||||
}
|
||||
}
|
||||
34
client/src/widget/objectlist/interfacelistwidget.hpp
Normale Datei
34
client/src/widget/objectlist/interfacelistwidget.hpp
Normale Datei
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* client/src/widget/objectlist/interfacelistwidget.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_INTERFACELISTWIDGET_HPP
|
||||
#define TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_INTERFACELISTWIDGET_HPP
|
||||
|
||||
#include "stackedobjectlistwidget.hpp"
|
||||
|
||||
class InterfaceListWidget : public StackedObjectListWidget
|
||||
{
|
||||
public:
|
||||
explicit InterfaceListWidget(const ObjectPtr& object, QWidget* parent = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
291
client/src/widget/objectlist/stackedobjectlistwidget.cpp
Normale Datei
291
client/src/widget/objectlist/stackedobjectlistwidget.cpp
Normale Datei
@ -0,0 +1,291 @@
|
||||
/**
|
||||
* client/src/widget/objectlist/stackedobjectlistwidget.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "stackedobjectlistwidget.hpp"
|
||||
|
||||
#include <QEvent>
|
||||
#include <QLabel>
|
||||
#include <QListView>
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QStackedWidget>
|
||||
#include <QToolBar>
|
||||
#include <QVBoxLayout>
|
||||
#include <QIdentityProxyModel>
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
|
||||
#include "../createwidget.hpp"
|
||||
#include "../tablewidget.hpp"
|
||||
#include "../methodicon.hpp"
|
||||
#include "../../mainwindow.hpp"
|
||||
#include "../../network/object.hpp"
|
||||
#include "../../network/method.hpp"
|
||||
#include "../../network/connection.hpp"
|
||||
#include "../../network/error.hpp"
|
||||
#include "../../network/callmethod.hpp"
|
||||
#include "../../network/tablemodel.hpp"
|
||||
#include "../../theme/theme.hpp"
|
||||
#include "../../misc/methodaction.hpp"
|
||||
|
||||
namespace
|
||||
{
|
||||
class StackedObjectListProxyModel final : public QIdentityProxyModel
|
||||
{
|
||||
public:
|
||||
StackedObjectListProxyModel(QAbstractItemModel* sourceModel)
|
||||
: QIdentityProxyModel()
|
||||
{
|
||||
setSourceModel(sourceModel);
|
||||
}
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const final
|
||||
{
|
||||
if (role == Qt::ToolTipRole)
|
||||
{
|
||||
return Locale::tr("stacked_object_list:click_to_edit_ctrl_click_to_open_in_a_new_window");
|
||||
}
|
||||
return QIdentityProxyModel::data(index, role);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
StackedObjectListWidget::StackedObjectListWidget(const ObjectPtr& object, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_object{object}
|
||||
, m_navBar{new QToolBar(this)}
|
||||
, m_stack{new QStackedWidget(this)}
|
||||
, m_list{new QListView(this)}
|
||||
, m_listEmptyLabel{new QLabel(Locale::tr("stacked_object_list:list_is_empty"), m_list)}
|
||||
, m_requestId{Connection::invalidRequestId}
|
||||
{
|
||||
m_navBar->hide();
|
||||
|
||||
m_navBar->addAction(Theme::getIcon("previous_page"), Locale::tr("stacked_object_list:back"), this, &StackedObjectListWidget::back);
|
||||
|
||||
m_navLabel = new QLabel(this);
|
||||
m_navLabel->setAlignment(Qt::AlignCenter);
|
||||
m_navLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
m_navLabel->show();
|
||||
m_navBar->addWidget(m_navLabel);
|
||||
|
||||
if(auto* method = object->getMethod("delete"))
|
||||
{
|
||||
m_actionRemove = new MethodAction(Theme::getIcon("delete"), *method,
|
||||
[this]()
|
||||
{
|
||||
if(!m_listObjectId.isEmpty())
|
||||
{
|
||||
callMethod(m_actionRemove->method(), nullptr, m_listObjectId);
|
||||
back();
|
||||
}
|
||||
});
|
||||
m_navBar->addAction(m_actionRemove);
|
||||
}
|
||||
|
||||
connect(m_stack, &QStackedWidget::currentChanged,
|
||||
[this](int index)
|
||||
{
|
||||
m_navBar->setVisible(index > 0);
|
||||
});
|
||||
|
||||
m_requestId = object->connection()->getTableModel(object,
|
||||
[this](const TableModelPtr& tableModel, std::optional<const Error> error)
|
||||
{
|
||||
m_requestId = Connection::invalidRequestId;
|
||||
|
||||
if(tableModel)
|
||||
{
|
||||
m_tableModel = tableModel;
|
||||
m_tableModel->setRegionAll(true);
|
||||
m_list->setModel(new StackedObjectListProxyModel(m_tableModel.get()));
|
||||
connect(m_tableModel.get(), &TableModel::modelReset,
|
||||
[this]()
|
||||
{
|
||||
m_listEmptyLabel->setVisible(m_tableModel->rowCount() == 0);
|
||||
});
|
||||
}
|
||||
else if(error)
|
||||
{
|
||||
QMessageBox::critical(this, "Error", error->toString());
|
||||
}
|
||||
});
|
||||
|
||||
if(auto* create = object->getMethod("create"))
|
||||
{
|
||||
if(create->argumentTypes().size() == 0) // Create method witout argument
|
||||
{
|
||||
m_create = new MethodIcon(*create, Theme::getIcon("circle/add"), m_list);
|
||||
}
|
||||
else if(create->argumentTypes().size() == 1)
|
||||
{
|
||||
m_createMenu = new QMenu(this);
|
||||
m_createMenu->installEventFilter(this);
|
||||
|
||||
QStringList classList = create->getAttribute(AttributeName::ClassList, QVariant()).toStringList();
|
||||
for(const QString& classId : classList)
|
||||
{
|
||||
QAction* action = m_createMenu->addAction(Locale::tr("class_id:" + classId));
|
||||
action->setData(classId);
|
||||
connect(action, &QAction::triggered, this,
|
||||
[this, create, action]()
|
||||
{
|
||||
cancelRequest();
|
||||
|
||||
m_requestId = create->call(action->data().toString(),
|
||||
[this](const ObjectPtr& addedObject, std::optional<const Error> error)
|
||||
{
|
||||
m_requestId = Connection::invalidRequestId;
|
||||
|
||||
if(addedObject)
|
||||
{
|
||||
show(addedObject);
|
||||
}
|
||||
else if(error)
|
||||
{
|
||||
QMessageBox::critical(this, "Error", error->toString());
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
m_create = new MethodIcon(*create, Theme::getIcon("circle/add"),
|
||||
[this]()
|
||||
{
|
||||
m_createMenu->popup(m_create->mapToGlobal(m_create->rect().topRight()));
|
||||
}, m_list);
|
||||
}
|
||||
|
||||
m_create->setEnabled(create->getAttributeBool(AttributeName::Enabled, true));
|
||||
if(!create->getAttributeBool(AttributeName::Visible, true))
|
||||
{
|
||||
m_create->hide();
|
||||
}
|
||||
|
||||
m_create->installEventFilter(this);
|
||||
}
|
||||
|
||||
m_list->installEventFilter(this);
|
||||
m_list->setSelectionMode(QListView::NoSelection);
|
||||
connect(m_list, &QListView::clicked,
|
||||
[this](const QModelIndex &index)
|
||||
{
|
||||
if(!m_tableModel) /*[[unlikely]]*/
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const bool openInSubWindow = (QGuiApplication::queryKeyboardModifiers() & Qt::ControlModifier);
|
||||
|
||||
cancelRequest();
|
||||
|
||||
m_requestId = m_object->connection()->getObject(m_tableModel->getRowObjectId(index.row()),
|
||||
[this, openInSubWindow](const ObjectPtr& selectedObject, std::optional<const Error> error)
|
||||
{
|
||||
m_requestId = Connection::invalidRequestId;
|
||||
|
||||
if(selectedObject)
|
||||
{
|
||||
if(openInSubWindow)
|
||||
{
|
||||
MainWindow::instance->showObject(selectedObject);
|
||||
}
|
||||
else
|
||||
{
|
||||
show(selectedObject);
|
||||
}
|
||||
}
|
||||
else if(error)
|
||||
{
|
||||
QMessageBox::critical(this, "Error", error->toString());
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
m_listEmptyLabel->installEventFilter(this);
|
||||
m_listEmptyLabel->setWordWrap(true);
|
||||
|
||||
m_stack->addWidget(m_list);
|
||||
|
||||
auto* l = new QVBoxLayout();
|
||||
l->setContentsMargins(0, 0, 0, 0);
|
||||
l->addWidget(m_navBar);
|
||||
l->addWidget(m_stack);
|
||||
setLayout(l);
|
||||
}
|
||||
|
||||
StackedObjectListWidget::~StackedObjectListWidget()
|
||||
{
|
||||
cancelRequest();
|
||||
}
|
||||
|
||||
bool StackedObjectListWidget::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
if(m_listEmptyLabel->isVisible() && ((object == m_list && event->type() == QEvent::Resize) || (object == m_listEmptyLabel && event->type() == QEvent::Show)))
|
||||
{
|
||||
m_listEmptyLabel->setMaximumWidth(qRound(width() * 0.9f));
|
||||
m_listEmptyLabel->adjustSize();
|
||||
m_listEmptyLabel->setFixedHeight(m_listEmptyLabel->heightForWidth(m_listEmptyLabel->maximumWidth()));
|
||||
m_listEmptyLabel->move((rect().bottomRight() - m_listEmptyLabel->rect().bottomRight()) / 2);
|
||||
}
|
||||
if(m_create && ((object == m_list && event->type() == QEvent::Resize) || (object == m_create && event->type() == QEvent::Show)))
|
||||
{
|
||||
auto pnt = m_create->rect().bottomRight();
|
||||
pnt = m_list->rect().bottomRight() - pnt - pnt / 3;
|
||||
m_create->move(pnt.x(), pnt.y());
|
||||
}
|
||||
if(m_createMenu && object == m_createMenu && event->type() == QEvent::Show)
|
||||
{
|
||||
m_createMenu->move(m_createMenu->pos() - QPoint{m_createMenu->width(), m_createMenu->height()});
|
||||
return true;
|
||||
}
|
||||
return QWidget::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void StackedObjectListWidget::cancelRequest()
|
||||
{
|
||||
if(m_requestId != Connection::invalidRequestId)
|
||||
{
|
||||
m_object->connection()->cancelRequest(m_requestId);
|
||||
}
|
||||
}
|
||||
|
||||
void StackedObjectListWidget::back()
|
||||
{
|
||||
if(m_stack->currentIndex() > 0)
|
||||
{
|
||||
m_listObjectId.clear();
|
||||
delete m_stack->currentWidget();
|
||||
}
|
||||
}
|
||||
|
||||
void StackedObjectListWidget::show(const ObjectPtr& listObject)
|
||||
{
|
||||
if(auto* w = createWidget(listObject, this)) /*[[likely]]*/
|
||||
{
|
||||
m_listObjectId = listObject->getPropertyValueString("id");
|
||||
connect(w, &QWidget::windowTitleChanged, m_navLabel, &QLabel::setText);
|
||||
m_navLabel->setText(w->windowTitle());
|
||||
m_stack->setCurrentIndex(m_stack->addWidget(w));
|
||||
}
|
||||
}
|
||||
67
client/src/widget/objectlist/stackedobjectlistwidget.hpp
Normale Datei
67
client/src/widget/objectlist/stackedobjectlistwidget.hpp
Normale Datei
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* client/src/widget/objectlist/stackedobjectlistwidget.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_STACKEDOBJECTLISTWIDGET_HPP
|
||||
#define TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_STACKEDOBJECTLISTWIDGET_HPP
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "../../network/objectptr.hpp"
|
||||
#include "../../network/tablemodelptr.hpp"
|
||||
|
||||
class QToolBar;
|
||||
class QStackedWidget;
|
||||
class QListView;
|
||||
class QLabel;
|
||||
class QMenu;
|
||||
class MethodIcon;
|
||||
class MethodAction;
|
||||
|
||||
class StackedObjectListWidget : public QWidget
|
||||
{
|
||||
protected:
|
||||
ObjectPtr m_object;
|
||||
TableModelPtr m_tableModel;
|
||||
QToolBar* m_navBar;
|
||||
QLabel* m_navLabel;
|
||||
QStackedWidget* m_stack;
|
||||
QListView* m_list;
|
||||
QLabel* m_listEmptyLabel;
|
||||
MethodIcon* m_create = nullptr;
|
||||
QMenu* m_createMenu = nullptr;
|
||||
MethodAction* m_actionRemove = nullptr;
|
||||
QString m_listObjectId;
|
||||
int m_requestId;
|
||||
|
||||
void cancelRequest();
|
||||
|
||||
void back();
|
||||
void show(const ObjectPtr& listObject);
|
||||
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
public:
|
||||
explicit StackedObjectListWidget(const ObjectPtr& object, QWidget* parent = nullptr);
|
||||
~StackedObjectListWidget() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -27,14 +27,18 @@
|
||||
#include <QTableWidget>
|
||||
#include <QHeaderView>
|
||||
#include <QPushButton>
|
||||
#include <QEvent>
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
#include "createwidget.hpp"
|
||||
#include "interfaceitemnamelabel.hpp"
|
||||
#include "propertycheckbox.hpp"
|
||||
#include "propertycombobox.hpp"
|
||||
#include "propertypairoutputaction.hpp"
|
||||
#include "propertyspinbox.hpp"
|
||||
#include "objectpropertycombobox.hpp"
|
||||
#include "propertyaddresses.hpp"
|
||||
#include "outputmapoutputactionwidget.hpp"
|
||||
#include "methodicon.hpp"
|
||||
#include "../board/tilepainter.hpp"
|
||||
#include "../board/getboardcolorscheme.hpp"
|
||||
#include "../dialog/objectselectlistdialog.hpp"
|
||||
@ -117,6 +121,17 @@ OutputMapWidget::OutputMapWidget(ObjectPtr object, QWidget* parent)
|
||||
|
||||
l->addWidget(m_table);
|
||||
|
||||
if(auto* swapOutputs = m_object->getMethod("swap_outputs"))
|
||||
{
|
||||
m_swapOutputs = new MethodIcon(*swapOutputs, Theme::getIcon("swap"), m_table);
|
||||
if(!swapOutputs->getAttributeBool(AttributeName::Visible, true))
|
||||
{
|
||||
m_swapOutputs->hide();
|
||||
}
|
||||
m_table->installEventFilter(this);
|
||||
m_swapOutputs->installEventFilter(this);
|
||||
}
|
||||
|
||||
setLayout(l);
|
||||
|
||||
if(auto* parentObject = m_object->getObjectProperty("parent"))
|
||||
@ -324,6 +339,17 @@ void OutputMapWidget::updateTableOutputColumns()
|
||||
}
|
||||
}
|
||||
|
||||
bool OutputMapWidget::eventFilter(QObject* object, QEvent* event)
|
||||
{
|
||||
if(m_swapOutputs && ((object == m_table && event->type() == QEvent::Resize) || (object == m_swapOutputs && event->type() == QEvent::Show)))
|
||||
{
|
||||
auto pnt = m_swapOutputs->rect().bottomRight();
|
||||
pnt = m_table->rect().bottomRight() - pnt - pnt / 4;
|
||||
m_swapOutputs->move(pnt.x(), pnt.y());
|
||||
}
|
||||
return QWidget::eventFilter(object, event);
|
||||
}
|
||||
|
||||
void OutputMapWidget::updateTableOutputActions(ObjectVectorProperty& property, int row)
|
||||
{
|
||||
if(!property.empty())
|
||||
@ -345,7 +371,7 @@ void OutputMapWidget::updateTableOutputActions(ObjectVectorProperty& property, i
|
||||
{
|
||||
if(auto* action = dynamic_cast<Property*>(object->getProperty("action")))
|
||||
{
|
||||
m_table->setCellWidget(row, column, new PropertyComboBox(*action, this));
|
||||
m_table->setCellWidget(row, column, createWidget(*action, this));
|
||||
}
|
||||
else if(auto* aspect = dynamic_cast<Property*>(object->getProperty("aspect")))
|
||||
{
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
class QTableWidget;
|
||||
class Method;
|
||||
class MethodAction;
|
||||
class MethodIcon;
|
||||
class AbstractProperty;
|
||||
class AbstractVectorProperty;
|
||||
class ObjectVectorProperty;
|
||||
@ -47,6 +48,7 @@ class OutputMapWidget : public QWidget
|
||||
Property* m_ecosObject;
|
||||
ObjectVectorProperty* m_items;
|
||||
QTableWidget* m_table;
|
||||
MethodIcon* m_swapOutputs = nullptr;
|
||||
std::vector<ObjectPtr> m_itemObjects;
|
||||
std::vector<std::vector<ObjectPtr>> m_actions;
|
||||
int m_getParentRequestId;
|
||||
@ -58,6 +60,8 @@ class OutputMapWidget : public QWidget
|
||||
void updateKeyIcons();
|
||||
void updateTableOutputColumns();
|
||||
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
public:
|
||||
explicit OutputMapWidget(ObjectPtr object, QWidget* parent = nullptr);
|
||||
~OutputMapWidget() override;
|
||||
|
||||
168
client/src/widget/propertypairoutputaction.cpp
Normale Datei
168
client/src/widget/propertypairoutputaction.cpp
Normale Datei
@ -0,0 +1,168 @@
|
||||
/**
|
||||
* client/src/widget/propertypairoutputaction.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "propertypairoutputaction.hpp"
|
||||
#include <QKeyEvent>
|
||||
#include <QPainter>
|
||||
#include "../network/property.hpp"
|
||||
|
||||
PropertyPairOutputAction::PropertyPairOutputAction(Property& property, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_property{property}
|
||||
{
|
||||
assert(m_property.enumName() == "pair_output_action");
|
||||
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
|
||||
connect(&m_property, &Property::valueChanged, this,
|
||||
[this]()
|
||||
{
|
||||
update(rect());
|
||||
});
|
||||
}
|
||||
|
||||
PairOutputAction PropertyPairOutputAction::value() const
|
||||
{
|
||||
return m_property.toEnum<PairOutputAction>();
|
||||
}
|
||||
|
||||
void PropertyPairOutputAction::setValue(PairOutputAction newValue)
|
||||
{
|
||||
m_property.setValueEnum(newValue);
|
||||
}
|
||||
|
||||
void PropertyPairOutputAction::toggleValue()
|
||||
{
|
||||
switch(value())
|
||||
{
|
||||
case PairOutputAction::None:
|
||||
setValue(PairOutputAction::First);
|
||||
break;
|
||||
|
||||
case PairOutputAction::First:
|
||||
setValue(PairOutputAction::Second);
|
||||
break;
|
||||
|
||||
case PairOutputAction::Second:
|
||||
default:
|
||||
setValue(PairOutputAction::None);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyPairOutputAction::toggleValue(PairOutputAction action)
|
||||
{
|
||||
setValue(action == value() ? PairOutputAction::None : action);
|
||||
}
|
||||
|
||||
void PropertyPairOutputAction::keyPressEvent(QKeyEvent* event)
|
||||
{
|
||||
switch(event->key())
|
||||
{
|
||||
case Qt::Key_Enter:
|
||||
case Qt::Key_Space:
|
||||
toggleValue();
|
||||
return;
|
||||
|
||||
case Qt::Key_1:
|
||||
case Qt::Key_R:
|
||||
toggleValue(PairOutputAction::First);
|
||||
return;
|
||||
|
||||
case Qt::Key_2:
|
||||
case Qt::Key_G:
|
||||
toggleValue(PairOutputAction::Second);
|
||||
return;
|
||||
}
|
||||
QWidget::keyPressEvent(event);
|
||||
}
|
||||
|
||||
void PropertyPairOutputAction::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
if(event->button() == Qt::LeftButton)
|
||||
{
|
||||
m_mouseLeftClickPos = event->pos();
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyPairOutputAction::mouseReleaseEvent(QMouseEvent* event)
|
||||
{
|
||||
if(event->button() == Qt::LeftButton && m_mouseLeftClickPos)
|
||||
{
|
||||
const auto [first, second] = outputRects();
|
||||
|
||||
if(first.contains(*m_mouseLeftClickPos) && first.contains(event->pos()))
|
||||
{
|
||||
toggleValue(PairOutputAction::First);
|
||||
}
|
||||
else if(second.contains(*m_mouseLeftClickPos) && second.contains(event->pos()))
|
||||
{
|
||||
toggleValue(PairOutputAction::Second);
|
||||
}
|
||||
|
||||
m_mouseLeftClickPos.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void PropertyPairOutputAction::paintEvent(QPaintEvent* /*event*/)
|
||||
{
|
||||
constexpr int thinkness = 2;
|
||||
const QColor firstOnColor(Qt::red);
|
||||
const QColor secondOnColor(Qt::green);
|
||||
const auto textOnColor = palette().color(QPalette::Active, QPalette::WindowText);
|
||||
const auto offColor = palette().color(QPalette::Disabled, QPalette::WindowText);
|
||||
const auto [left, right] = outputRects();
|
||||
|
||||
QPainter painter(this);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
painter.save();
|
||||
|
||||
QPen p(painter.pen());
|
||||
p.setWidth(thinkness);
|
||||
|
||||
p.setColor(value() == PairOutputAction::First ? firstOnColor : offColor);
|
||||
painter.setPen(p);
|
||||
painter.drawEllipse(left.adjusted(thinkness, thinkness, -thinkness, -thinkness));
|
||||
|
||||
p.setColor(value() == PairOutputAction::Second ? secondOnColor : offColor);
|
||||
painter.setPen(p);
|
||||
painter.drawEllipse(right.adjusted(thinkness, thinkness, -thinkness, -thinkness));
|
||||
|
||||
painter.restore();
|
||||
|
||||
painter.setPen(value() == PairOutputAction::First ? textOnColor : offColor);
|
||||
painter.drawText(left, Qt::AlignCenter, "R");
|
||||
|
||||
painter.setPen(value() == PairOutputAction::Second ? textOnColor : offColor);
|
||||
painter.drawText(right, Qt::AlignCenter, "G");
|
||||
}
|
||||
|
||||
std::pair<QRect, QRect> PropertyPairOutputAction::outputRects() const
|
||||
{
|
||||
constexpr int margin = 1;
|
||||
const int height = rect().height();
|
||||
const int hCenter = rect().width() / 2;
|
||||
return {
|
||||
{hCenter - margin - height, 0, height, height},
|
||||
{hCenter + margin, 0, height, height}};
|
||||
}
|
||||
55
client/src/widget/propertypairoutputaction.hpp
Normale Datei
55
client/src/widget/propertypairoutputaction.hpp
Normale Datei
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* client/src/widget/propertypairoutputaction.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_CLIENT_WIDGET_PROPERTYPAIROUTPUTACTION_HPP
|
||||
#define TRAINTASTIC_CLIENT_WIDGET_PROPERTYPAIROUTPUTACTION_HPP
|
||||
|
||||
#include <optional>
|
||||
#include <QWidget>
|
||||
#include <traintastic/enum/pairoutputaction.hpp>
|
||||
|
||||
class Property;
|
||||
|
||||
class PropertyPairOutputAction : public QWidget
|
||||
{
|
||||
private:
|
||||
Property& m_property;
|
||||
std::optional<QPoint> m_mouseLeftClickPos = std::nullopt;
|
||||
|
||||
std::pair<QRect, QRect> outputRects() const;
|
||||
|
||||
protected:
|
||||
void keyPressEvent(QKeyEvent* event) final;
|
||||
void mousePressEvent(QMouseEvent* event) final;
|
||||
void mouseReleaseEvent(QMouseEvent* event) final;
|
||||
void paintEvent(QPaintEvent* event) final;
|
||||
|
||||
public:
|
||||
PropertyPairOutputAction(Property& property, QWidget* parent = nullptr);
|
||||
|
||||
PairOutputAction value() const;
|
||||
void setValue(PairOutputAction action);
|
||||
void toggleValue();
|
||||
void toggleValue(PairOutputAction output);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -136,19 +136,34 @@ void TableWidget::updateRegion()
|
||||
|
||||
int rowMin = qMax(topLeft.row(), 0);
|
||||
int rowMax = indexAt(r.bottomLeft()).row();
|
||||
if(rowMax == -1)
|
||||
|
||||
if(rowCount == 0)
|
||||
{
|
||||
// Invalid region to represent empty model
|
||||
rowMin = 1;
|
||||
rowMax = 0;
|
||||
}
|
||||
else if(rowMax == -1)
|
||||
rowMax = rowCount - 1;
|
||||
else
|
||||
rowMax = qMin(rowMax + 1, rowCount - 1);
|
||||
|
||||
int columnMin = qMax(topLeft.column(), 0);
|
||||
int columnMax = indexAt(r.topRight()).column();
|
||||
|
||||
if(columnCount == 0)
|
||||
{
|
||||
// Invalid region to represent empty model
|
||||
columnMin = 1;
|
||||
columnMax = 0;
|
||||
}
|
||||
if(columnMax == -1)
|
||||
columnMax = columnCount - 1;
|
||||
else
|
||||
columnMax = qMin(columnMax + 1, columnCount - 1);
|
||||
|
||||
m_model->setRegion(columnMin, columnMax, rowMin, rowMax);
|
||||
m_model->setRegion(uint32_t(columnMin), uint32_t(columnMax),
|
||||
uint32_t(rowMin), uint32_t(rowMax));
|
||||
}
|
||||
|
||||
void TableWidget::mouseMoveEvent(QMouseEvent* event)
|
||||
|
||||
@ -225,13 +225,17 @@ class RadioPageJSON : public RadioPage, public PageJSON
|
||||
|
||||
void initializePage() override
|
||||
{
|
||||
setTitleAndText(*static_cast<JSONWizard*>(wizard()), this, m_pageData);
|
||||
auto* jsonWizard = static_cast<JSONWizard*>(wizard());
|
||||
|
||||
setTitleAndText(*jsonWizard, this, m_pageData);
|
||||
|
||||
for(const auto& option : m_pageData["options"].toArray())
|
||||
{
|
||||
auto item = option.toObject();
|
||||
addItem(static_cast<JSONWizard*>(wizard())->translateAndReplaceVariables(item["name"].toString()), item["disabled"].toBool());
|
||||
addItem(jsonWizard->translateAndReplaceVariables(item["name"].toString()), item["checked"].toBool(), item["disabled"].toBool());
|
||||
}
|
||||
|
||||
setBottomText(jsonWizard->translateAndReplaceVariables(m_pageData["bottom_text"].toString()));
|
||||
}
|
||||
|
||||
void cleanupPage() override
|
||||
|
||||
@ -24,11 +24,19 @@
|
||||
#include <QLayout>
|
||||
#include <QButtonGroup>
|
||||
#include <QRadioButton>
|
||||
#include <QLabel>
|
||||
|
||||
RadioPage::RadioPage(QWidget* parent)
|
||||
: TextPage(parent)
|
||||
, m_group{new QButtonGroup(this)}
|
||||
, m_bottomText{new QLabel(this)}
|
||||
{
|
||||
m_bottomText->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
|
||||
m_bottomText->setWordWrap(true);
|
||||
setBottomText({});
|
||||
|
||||
static_cast<QVBoxLayout*>(layout())->addStretch();
|
||||
layout()->addWidget(m_bottomText);
|
||||
}
|
||||
|
||||
int RadioPage::currentIndex() const
|
||||
@ -36,23 +44,25 @@ int RadioPage::currentIndex() const
|
||||
return m_group->id(m_group->checkedButton());
|
||||
}
|
||||
|
||||
void RadioPage::addItem(const QString& label, bool disabled)
|
||||
void RadioPage::addItem(const QString& label, bool checked, bool disabled)
|
||||
{
|
||||
auto* button = new QRadioButton(label);
|
||||
button->setChecked(checked && !disabled);
|
||||
button->setDisabled(disabled);
|
||||
m_group->addButton(button, m_group->buttons().size());
|
||||
layout()->addWidget(button);
|
||||
static_cast<QVBoxLayout*>(layout())->insertWidget(layout()->count() - 2, button);
|
||||
}
|
||||
|
||||
void RadioPage::clear()
|
||||
{
|
||||
while(layout()->count() > 1) // remove all but first (=text label)
|
||||
for(auto* button : m_group->buttons())
|
||||
{
|
||||
auto* item = layout()->itemAt(layout()->count() - 1);
|
||||
if(item->widget())
|
||||
delete button;
|
||||
}
|
||||
}
|
||||
|
||||
void RadioPage::setBottomText(const QString& text)
|
||||
{
|
||||
delete item->widget();
|
||||
}
|
||||
layout()->removeItem(item);
|
||||
}
|
||||
m_bottomText->setText(text);
|
||||
m_bottomText->setVisible(!text.isEmpty());
|
||||
}
|
||||
|
||||
@ -31,14 +31,17 @@ class RadioPage : public TextPage
|
||||
{
|
||||
protected:
|
||||
QButtonGroup* m_group;
|
||||
QLabel* m_bottomText;
|
||||
|
||||
public:
|
||||
explicit RadioPage(QWidget* parent = nullptr);
|
||||
|
||||
int currentIndex() const;
|
||||
|
||||
void addItem(const QString& label, bool disabled = false);
|
||||
void addItem(const QString& label, bool checked = false, bool disabled = false);
|
||||
void clear();
|
||||
|
||||
void setBottomText(const QString& text);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -54,9 +54,6 @@ Name: "firewall_wlanmaus"; Description: "{cm:firewall_allow_wlanmaus_z21}"; Grou
|
||||
[Files]
|
||||
; Server
|
||||
Source: "..\..\server\build\{#ServerExeName}"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
|
||||
Source: "..\..\server\thirdparty\lua5.4\bin\win64\lua54.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
|
||||
Source: "..\..\server\thirdparty\libarchive\bin\archive.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
|
||||
Source: "..\..\server\thirdparty\zlib\bin\zlib1.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
|
||||
; Client
|
||||
Source: "..\..\client\build\Release\{#ClientExeName}"; DestDir: "{app}\client"; Flags: ignoreversion; Check: InstallClient
|
||||
Source: "..\..\client\build\Release\*.dll"; DestDir: "{app}\client"; Flags: ignoreversion; Check: InstallClient
|
||||
@ -88,6 +85,11 @@ Type: files; Name: "{commonappdata}\traintastic\translations\en-us.txt"
|
||||
Type: files; Name: "{commonappdata}\traintastic\translations\nl-nl.txt"
|
||||
Type: files; Name: "{commonappdata}\traintastic\translations\de-de.txt"
|
||||
Type: files; Name: "{commonappdata}\traintastic\translations\it-it.txt"
|
||||
; Delete unused DLLs, now statically linked (TODO: remove in 0.4)
|
||||
Type: files; Name: "{app}\server\lua53.dll"
|
||||
Type: files; Name: "{app}\server\lua54.dll"
|
||||
Type: files; Name: "{app}\server\archive.dll"
|
||||
Type: files; Name: "{app}\server\zlib1.dll"
|
||||
|
||||
[UninstallRun]
|
||||
Filename: {sys}\netsh.exe; Parameters: "advfirewall firewall delete rule name=""Traintastic server (TCP)"""; Flags: runhidden; Check: InstallServer; Tasks: firewall_traintastic
|
||||
@ -107,7 +109,7 @@ Root: HKLM; Subkey: "{#CompanySubKey}"; Flags: uninsdeletekeyifempty
|
||||
Root: HKLM; Subkey: "{#AppSubKey}"; Flags: uninsdeletekey
|
||||
|
||||
[INI]
|
||||
Filename: {commonappdata}\traintastic\traintastic-client.ini; Section: general_; Key: language; String: {code:GetTraintasticClientLanguage}; Flags: uninsdeleteentry uninsdeletesectionifempty;
|
||||
Filename: {commonappdata}\traintastic\traintastic-client.ini; Section: general_; Key: language; String: {code:GetTraintasticLanguage}; Flags: uninsdeleteentry uninsdeletesectionifempty;
|
||||
|
||||
[Code]
|
||||
const
|
||||
@ -179,6 +181,7 @@ begin
|
||||
ClientAndServerRadioButton.Checked := (Components = 'ClientAndServer');
|
||||
ClientAndServerRadioButton.Font.Style := [fsBold];
|
||||
ClientAndServerRadioButton.Height := ScaleY(23);
|
||||
ClientAndServerRadioButton.Width := ComponentsPage.SurfaceWidth;
|
||||
ClientAndServerRadioButton.Parent := ComponentsPage.Surface;
|
||||
ClientAndServerRadioButton.OnClick := @ComponentRadioButtonClick;
|
||||
|
||||
@ -195,6 +198,7 @@ begin
|
||||
ClientOnlyRadioButton.Font.Style := [fsBold];
|
||||
ClientOnlyRadioButton.Top := Lbl.Top + Lbl.Height + ScaleY(10);
|
||||
ClientOnlyRadioButton.Height := ScaleY(23);
|
||||
ClientOnlyRadioButton.Width := ComponentsPage.SurfaceWidth;
|
||||
ClientOnlyRadioButton.Parent := ComponentsPage.Surface;
|
||||
ClientOnlyRadioButton.OnClick := @ComponentRadioButtonClick;
|
||||
|
||||
@ -206,7 +210,7 @@ begin
|
||||
Lbl.Parent := ComponentsPage.Surface;
|
||||
end;
|
||||
|
||||
function GetTraintasticClientLanguage(Param: String) : String;
|
||||
function GetTraintasticLanguage(Param: String) : String;
|
||||
begin
|
||||
case ActiveLanguage of
|
||||
'nl': Result := 'nl-nl';
|
||||
@ -219,6 +223,19 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure CurStepChanged(CurStep: TSetupStep);
|
||||
var
|
||||
ServerSettingsFile: String;
|
||||
begin
|
||||
if CurStep = ssPostInstall then begin
|
||||
// Server: only write language if there is no setting file yet:
|
||||
ServerSettingsFile := ExpandConstant('{localappdata}\traintastic\server\settings.json');
|
||||
if not FileExists(ServerSettingsFile) then begin
|
||||
SaveStringToFile(ServerSettingsFile, '{"language":"' + GetTraintasticLanguage('') + '"}', False);
|
||||
end;
|
||||
end
|
||||
end;
|
||||
|
||||
function VC2019RedistNeedsInstall: Boolean;
|
||||
var
|
||||
Version: String;
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
cmake_minimum_required(VERSION 3.9)
|
||||
cmake_minimum_required(VERSION 3.18)
|
||||
include(../shared/traintastic.cmake)
|
||||
project(traintastic-server VERSION ${TRAINTASTIC_VERSION} DESCRIPTION "Traintastic server")
|
||||
include(GNUInstallDirs)
|
||||
@ -23,7 +23,7 @@ endif()
|
||||
|
||||
add_executable(traintastic-server src/main.cpp src/options.hpp)
|
||||
add_dependencies(traintastic-server traintastic-lang)
|
||||
set_target_properties(traintastic-server PROPERTIES CXX_STANDARD 17)
|
||||
set_target_properties(traintastic-server PROPERTIES CXX_STANDARD 20)
|
||||
target_include_directories(traintastic-server PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
../shared/src)
|
||||
@ -33,11 +33,20 @@ target_include_directories(traintastic-server SYSTEM PRIVATE
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(thirdparty/catch2)
|
||||
set_target_properties(Catch2 PROPERTIES CXX_STANDARD 17)
|
||||
set_target_properties(Catch2 PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_CLANG_TIDY ""
|
||||
)
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
target_compile_options(Catch2 PRIVATE -Wno-restrict) # workaround GCC bug
|
||||
endif()
|
||||
add_executable(traintastic-server-test test/main.cpp)
|
||||
add_dependencies(traintastic-server-test traintastic-lang)
|
||||
target_compile_definitions(traintastic-server-test PRIVATE -DTRAINTASTIC_TEST)
|
||||
set_target_properties(traintastic-server-test PROPERTIES CXX_STANDARD 17)
|
||||
set_target_properties(traintastic-server-test PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_CLANG_TIDY ""
|
||||
)
|
||||
target_include_directories(traintastic-server-test PRIVATE
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
../shared/src)
|
||||
@ -58,6 +67,7 @@ file(GLOB SOURCES
|
||||
"src/board/nx/*.cpp"
|
||||
"src/board/tile/*.hpp"
|
||||
"src/board/tile/*.cpp"
|
||||
"src/board/tile/hidden/*.cpp"
|
||||
"src/board/tile/misc/*.hpp"
|
||||
"src/board/tile/misc/*.cpp"
|
||||
"src/board/tile/rail/*.hpp"
|
||||
@ -189,6 +199,38 @@ file(GLOB TEST_SOURCES
|
||||
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DENABLE_LOG_DEBUG")
|
||||
|
||||
### VCPKG
|
||||
if(DEFINED ENV{VCPKG_ROOT})
|
||||
message(STATUS "Using VCPKG (VCPKG_ROOT=$ENV{VCPKG_ROOT})")
|
||||
if(WIN32)
|
||||
set(VCPKG_TARGET_TRIPLET "x64-windows-static-md")
|
||||
endif()
|
||||
include($ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
|
||||
endif()
|
||||
|
||||
### RESOURCES ###
|
||||
|
||||
include(cmake/add-resource.cmake)
|
||||
|
||||
add_resource(resource-www
|
||||
FILES
|
||||
www/css/normalize.css
|
||||
www/css/throttle.css
|
||||
www/js/throttle.js
|
||||
www/throttle.html
|
||||
)
|
||||
|
||||
add_resource(resource-shared
|
||||
BASE_DIR ../
|
||||
FILES
|
||||
shared/gfx/appicon.ico
|
||||
)
|
||||
|
||||
add_dependencies(traintastic-server resource-www resource-shared)
|
||||
if(BUILD_TESTING)
|
||||
add_dependencies(traintastic-server-test resource-www resource-shared)
|
||||
endif()
|
||||
|
||||
### OPTIONS ###
|
||||
|
||||
if(NO_LOCALHOST_ONLY_SETTING)
|
||||
@ -280,154 +322,30 @@ if(WIN32 AND NOT MSVC)
|
||||
endif()
|
||||
|
||||
# boost
|
||||
if(LINUX)
|
||||
find_package(Boost 1.71 REQUIRED COMPONENTS program_options)
|
||||
|
||||
find_package(Boost 1.81 REQUIRED COMPONENTS program_options)
|
||||
target_include_directories(traintastic-server SYSTEM PRIVATE ${Boost_INCLUDE_DIRS})
|
||||
target_link_libraries(traintastic-server PRIVATE ${Boost_LIBRARIES})
|
||||
if(BUILD_TESTING)
|
||||
target_include_directories(traintastic-server-test SYSTEM PRIVATE ${Boost_INCLUDE_DIRS})
|
||||
target_link_libraries(traintastic-server-test PRIVATE ${Boost_LIBRARIES})
|
||||
endif()
|
||||
else()
|
||||
add_definitions(
|
||||
-DBOOST_ALL_NO_LIB
|
||||
-DBOOST_ERROR_CODE_HEADER_ONLY
|
||||
-DBOOST_CHRONO_HEADER_ONLY
|
||||
-DBOOST_ASIO_HEADER_ONLY
|
||||
-DBOOST_SYSTEM_NO_DEPRECATED)
|
||||
|
||||
if(NOT MSVC)
|
||||
set_source_files_properties(
|
||||
thirdparty/boost/libs/program_options/src/cmdline.cpp
|
||||
thirdparty/boost/libs/program_options/src/config_file.cpp
|
||||
thirdparty/boost/libs/program_options/src/convert.cpp
|
||||
thirdparty/boost/libs/program_options/src/options_description.cpp
|
||||
thirdparty/boost/libs/program_options/src/parsers.cpp
|
||||
thirdparty/boost/libs/program_options/src/positional_options.cpp
|
||||
thirdparty/boost/libs/program_options/src/split.cpp
|
||||
thirdparty/boost/libs/program_options/src/utf8_codecvt_facet.cpp
|
||||
thirdparty/boost/libs/program_options/src/value_semantic.cpp
|
||||
thirdparty/boost/libs/program_options/src/variables_map.cpp
|
||||
thirdparty/boost/libs/program_options/src/winmain.cpp
|
||||
PROPERTIES
|
||||
COMPILE_FLAGS -Wno-shadow)
|
||||
endif()
|
||||
|
||||
target_include_directories(traintastic-server SYSTEM PRIVATE thirdparty/boost)
|
||||
if(BUILD_TESTING)
|
||||
target_include_directories(traintastic-server-test SYSTEM PRIVATE thirdparty/boost)
|
||||
endif()
|
||||
|
||||
file(GLOB SOURCES_BOOST "thirdparty/boost/libs/program_options/src/*.cpp")
|
||||
list(APPEND SOURCES ${SOURCES_BOOST})
|
||||
endif()
|
||||
|
||||
# zlib
|
||||
if(WIN32)
|
||||
set(ZLIB_INCLUDE_DIRS "thirdparty/zlib/include")
|
||||
|
||||
if(MSVC)
|
||||
set(ZLIB_LIBRARIES zlib1)
|
||||
add_custom_command(TARGET traintastic-server PRE_LINK
|
||||
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.def" /out:zlib1.lib /machine:x64)
|
||||
add_custom_command(TARGET traintastic-server-test PRE_LINK
|
||||
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.def" /out:zlib1.lib /machine:x64)
|
||||
else()
|
||||
# MinGW can directly link .dll without import lib
|
||||
set(ZLIB_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.dll")
|
||||
endif()
|
||||
|
||||
# copy zlib1.dll to build directory:
|
||||
add_custom_command(TARGET traintastic-server POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.dll" .)
|
||||
else()
|
||||
find_package(ZLIB REQUIRED)
|
||||
endif()
|
||||
target_include_directories(traintastic-server PRIVATE ${ZLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(traintastic-server PRIVATE ${ZLIB_LIBRARIES})
|
||||
target_link_libraries(traintastic-server PRIVATE ZLIB::ZLIB)
|
||||
if(BUILD_TESTING)
|
||||
target_include_directories(traintastic-server-test PRIVATE ${ZLIB_INCLUDE_DIRS})
|
||||
target_link_libraries(traintastic-server-test PRIVATE ${ZLIB_LIBRARIES})
|
||||
target_link_libraries(traintastic-server-test PRIVATE ZLIB::ZLIB)
|
||||
endif()
|
||||
|
||||
# libarchive
|
||||
if(WIN32)
|
||||
set(LibArchive_INCLUDE_DIRS "thirdparty/libarchive/include")
|
||||
|
||||
if(MSVC)
|
||||
set(LibArchive_LIBRARIES archive)
|
||||
add_custom_command(TARGET traintastic-server PRE_LINK
|
||||
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/libarchive/bin/archive.def" /out:archive.lib /machine:x64)
|
||||
add_custom_command(TARGET traintastic-server-test PRE_LINK
|
||||
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/libarchive/bin/archive.def" /out:archive.lib /machine:x64)
|
||||
else()
|
||||
# MinGW can directly link .dll without import lib
|
||||
set(LibArchive_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/libarchive/bin/archive.dll")
|
||||
endif()
|
||||
|
||||
# copy archive.dll to build directory:
|
||||
add_custom_command(TARGET traintastic-server POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/thirdparty/libarchive/bin/archive.dll" .)
|
||||
elseif(APPLE)
|
||||
find_path(LibArchive_INCLUDE_DIRS
|
||||
NAMES archive.h
|
||||
PATHS
|
||||
"/usr/local/opt/libarchive/include" # x86_64
|
||||
"/opt/homebrew/opt/libarchive/include" # arm64
|
||||
)
|
||||
find_library(LibArchive_LIBRARIES
|
||||
NAMES archive libarchive
|
||||
PATHS
|
||||
"/usr/local/opt/libarchive/lib/" # x86_64
|
||||
"/opt/homebrew/opt/libarchive/lib" # arm64
|
||||
)
|
||||
else()
|
||||
find_package(LibArchive REQUIRED)
|
||||
endif()
|
||||
target_include_directories(traintastic-server PRIVATE ${LibArchive_INCLUDE_DIRS})
|
||||
target_link_libraries(traintastic-server PRIVATE ${LibArchive_LIBRARIES})
|
||||
target_link_libraries(traintastic-server PRIVATE LibArchive::LibArchive)
|
||||
if(BUILD_TESTING)
|
||||
target_include_directories(traintastic-server-test PRIVATE ${LibArchive_INCLUDE_DIRS})
|
||||
target_link_libraries(traintastic-server-test PRIVATE ${LibArchive_LIBRARIES})
|
||||
target_link_libraries(traintastic-server-test PRIVATE LibArchive::LibArchive)
|
||||
endif()
|
||||
|
||||
# liblua5.4
|
||||
if(WIN32)
|
||||
add_definitions(-DLUA_BUILD_AS_DLL)
|
||||
set(LUA_INCLUDE_DIR "thirdparty/lua5.4/include")
|
||||
|
||||
if(MSVC)
|
||||
set(LUA_LIBRARIES lua54)
|
||||
add_custom_command(TARGET traintastic-server PRE_LINK
|
||||
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.def" /out:lua54.lib /machine:x64)
|
||||
add_custom_command(TARGET traintastic-server-test PRE_LINK
|
||||
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.def" /out:lua54.lib /machine:x64)
|
||||
else()
|
||||
# MinGW can directly link .dll without import lib
|
||||
set(LUA_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.dll")
|
||||
endif()
|
||||
|
||||
|
||||
# copy lua54.dll to build directory, to be able to run the tests:
|
||||
add_custom_command(TARGET traintastic-server-test POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.dll" .)
|
||||
elseif(APPLE)
|
||||
find_path(LUA_INCLUDE_DIR
|
||||
NAMES lua.h
|
||||
PATHS
|
||||
"/usr/local/opt/lua@5.4/include/lua" # x86_64
|
||||
"/opt/homebrew/opt/lua@5.4/include/lua" # arm64
|
||||
)
|
||||
find_library(LUA_LIBRARIES
|
||||
NAMES lua5.4 liblua5.4
|
||||
PATHS
|
||||
"/usr/local/opt/lua@5.4/lib" # x86_64
|
||||
"/opt/homebrew/opt/lua@5.4/lib" # arm64
|
||||
)
|
||||
else()
|
||||
find_package(Lua 5.4 REQUIRED)
|
||||
endif()
|
||||
# lua
|
||||
find_package(Lua REQUIRED)
|
||||
target_include_directories(traintastic-server PRIVATE ${LUA_INCLUDE_DIR})
|
||||
target_link_libraries(traintastic-server PRIVATE ${LUA_LIBRARIES})
|
||||
if(BUILD_TESTING)
|
||||
|
||||
@ -1,122 +0,0 @@
|
||||
# Locate Lua library
|
||||
# This module defines
|
||||
# LUA_EXECUTABLE, if found
|
||||
# LUA_FOUND, if false, do not try to link to Lua
|
||||
# LUA_LIBRARIES
|
||||
# LUA_INCLUDE_DIR, where to find lua.h
|
||||
# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8)
|
||||
#
|
||||
# Note that the expected include convention is
|
||||
# #include "lua.h"
|
||||
# and not
|
||||
# #include <lua/lua.h>
|
||||
# This is because, the lua location is not standardized and may exist
|
||||
# in locations other than lua/
|
||||
|
||||
#=============================================================================
|
||||
# Copyright 2007-2009 Kitware, Inc.
|
||||
# Modified to support Lua 5.2 by LuaDist 2012
|
||||
# Modified to support Lua 5.3 by Reinder Feenstra 2019
|
||||
# Modified to support Lua 5.4 by Reinder Feenstra 2024
|
||||
#
|
||||
# Distributed under the OSI-approved BSD License (the "License");
|
||||
# see accompanying file Copyright.txt for details.
|
||||
#
|
||||
# This software is distributed WITHOUT ANY WARRANTY; without even the
|
||||
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
# See the License for more information.
|
||||
#=============================================================================
|
||||
# (To distribute this file outside of CMake, substitute the full
|
||||
# License text for the above reference.)
|
||||
#
|
||||
# The required version of Lua can be specified using the
|
||||
# standard syntax, e.g. FIND_PACKAGE(Lua 5.1)
|
||||
# Otherwise the module will search for any available Lua implementation
|
||||
|
||||
# Always search for non-versioned lua first (recommended)
|
||||
SET(_POSSIBLE_LUA_INCLUDE include include/lua)
|
||||
SET(_POSSIBLE_LUA_EXECUTABLE lua)
|
||||
SET(_POSSIBLE_LUA_LIBRARY lua)
|
||||
|
||||
# Determine possible naming suffixes (there is no standard for this)
|
||||
IF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
|
||||
SET(_POSSIBLE_SUFFIXES "${Lua_FIND_VERSION_MAJOR}${Lua_FIND_VERSION_MINOR}" "${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}" "-${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}")
|
||||
ELSE(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
|
||||
SET(_POSSIBLE_SUFFIXES "54" "5.4" "-5.4" "53" "5.3" "-5.3" "52" "5.2" "-5.2" "51" "5.1" "-5.1")
|
||||
ENDIF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
|
||||
|
||||
# Set up possible search names and locations
|
||||
FOREACH(_SUFFIX ${_POSSIBLE_SUFFIXES})
|
||||
LIST(APPEND _POSSIBLE_LUA_INCLUDE "include/lua${_SUFFIX}")
|
||||
LIST(APPEND _POSSIBLE_LUA_EXECUTABLE "lua${_SUFFIX}")
|
||||
LIST(APPEND _POSSIBLE_LUA_LIBRARY "lua${_SUFFIX}")
|
||||
ENDFOREACH(_SUFFIX)
|
||||
|
||||
# Find the lua executable
|
||||
FIND_PROGRAM(LUA_EXECUTABLE
|
||||
NAMES ${_POSSIBLE_LUA_EXECUTABLE}
|
||||
)
|
||||
|
||||
# Find the lua header
|
||||
FIND_PATH(LUA_INCLUDE_DIR lua.h
|
||||
HINTS
|
||||
$ENV{LUA_DIR}
|
||||
PATH_SUFFIXES ${_POSSIBLE_LUA_INCLUDE}
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local
|
||||
/usr
|
||||
/sw # Fink
|
||||
/opt/homebrew # MacOS Apple Silicone
|
||||
/opt/local # DarwinPorts
|
||||
/opt/csw # Blastwave
|
||||
/opt
|
||||
)
|
||||
|
||||
# Find the lua library
|
||||
FIND_LIBRARY(LUA_LIBRARY
|
||||
NAMES ${_POSSIBLE_LUA_LIBRARY}
|
||||
HINTS
|
||||
$ENV{LUA_DIR}
|
||||
PATH_SUFFIXES lib64 lib
|
||||
PATHS
|
||||
~/Library/Frameworks
|
||||
/Library/Frameworks
|
||||
/usr/local
|
||||
/usr
|
||||
/sw
|
||||
/opt/homebrew # MacOS Apple Silicone
|
||||
/opt/local
|
||||
/opt/csw
|
||||
/opt
|
||||
)
|
||||
|
||||
IF(LUA_LIBRARY)
|
||||
# include the math library for Unix
|
||||
IF(UNIX AND NOT APPLE)
|
||||
FIND_LIBRARY(LUA_MATH_LIBRARY m)
|
||||
SET( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries")
|
||||
# For Windows and Mac, don't need to explicitly include the math library
|
||||
ELSE(UNIX AND NOT APPLE)
|
||||
SET( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries")
|
||||
ENDIF(UNIX AND NOT APPLE)
|
||||
ENDIF(LUA_LIBRARY)
|
||||
|
||||
# Determine Lua version
|
||||
IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/lua.h")
|
||||
FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua .+\"")
|
||||
|
||||
STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}")
|
||||
UNSET(lua_version_str)
|
||||
ENDIF()
|
||||
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if
|
||||
# all listed variables are TRUE
|
||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lua
|
||||
REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR
|
||||
VERSION_VAR LUA_VERSION_STRING)
|
||||
|
||||
MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY LUA_EXECUTABLE)
|
||||
|
||||
40
server/cmake/add-resource.cmake
Normale Datei
40
server/cmake/add-resource.cmake
Normale Datei
@ -0,0 +1,40 @@
|
||||
#
|
||||
# This file is part of the traintastic source code.
|
||||
# See <https://github.com/traintastic/traintastic>.
|
||||
#
|
||||
# Copyright (C) 2024 Reinder Feenstra
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
function(add_resource TARGET_NAME)
|
||||
cmake_parse_arguments(PARSE_ARG "" "BASE_DIR" "FILES" ${ARGN})
|
||||
if(PARSE_ARG_BASE_DIR)
|
||||
set(PARSE_ARG_BASE_DIR "${CMAKE_SOURCE_DIR}/${PARSE_ARG_BASE_DIR}")
|
||||
else()
|
||||
set(PARSE_ARG_BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||
endif()
|
||||
foreach(INPUT_FILE ${PARSE_ARG_FILES})
|
||||
set(OUTPUT_FILE ${CMAKE_BINARY_DIR}/resource/${INPUT_FILE}.hpp)
|
||||
add_custom_command(
|
||||
OUTPUT ${OUTPUT_FILE}
|
||||
COMMAND Python3::Interpreter ${CMAKE_SOURCE_DIR}/cmake/generateresourceheader.py ${PARSE_ARG_BASE_DIR} ${INPUT_FILE} ${OUTPUT_FILE}
|
||||
DEPENDS ${CMAKE_SOURCE_DIR}/cmake/generateresourceheader.py ${PARSE_ARG_BASE_DIR}/${INPUT_FILE}
|
||||
COMMENT "Generating resource header resource/${INPUT_FILE}.hpp"
|
||||
)
|
||||
list(APPEND OUTPUT_HEADERS ${OUTPUT_FILE})
|
||||
endforeach()
|
||||
add_custom_target(${TARGET_NAME} ALL DEPENDS ${OUTPUT_HEADERS})
|
||||
endfunction()
|
||||
@ -380,7 +380,7 @@ function(target_code_coverage TARGET_NAME)
|
||||
COMMAND ${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --zerocounters
|
||||
COMMAND $<TARGET_FILE:${TARGET_NAME}> ${target_code_coverage_ARGS}
|
||||
COMMAND
|
||||
${LCOV_PATH} --directory ${CMAKE_BINARY_DIR} --base-directory
|
||||
${LCOV_PATH} --ignore-errors mismatch --directory ${CMAKE_BINARY_DIR} --base-directory
|
||||
${CMAKE_SOURCE_DIR} --capture ${EXTERNAL_OPTION} --output-file
|
||||
${COVERAGE_INFO}
|
||||
COMMAND ${EXCLUDE_COMMAND}
|
||||
|
||||
88
server/cmake/generateresourceheader.py
Normale Datei
88
server/cmake/generateresourceheader.py
Normale Datei
@ -0,0 +1,88 @@
|
||||
#
|
||||
# This file is part of the traintastic source code.
|
||||
# See <https://github.com/traintastic/traintastic>.
|
||||
#
|
||||
# Copyright (C) 2024-2025 Reinder Feenstra
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import textwrap
|
||||
|
||||
if len(sys.argv) != 4:
|
||||
print(f"Usage: {sys.argv[0]} <input dir> <input file> <output header>")
|
||||
sys.exit(1)
|
||||
|
||||
input_file = os.path.join(sys.argv[1], sys.argv[2])
|
||||
input_file_ext = os.path.splitext(input_file)[1].lstrip('.')
|
||||
|
||||
namespaces = ['Resource'] + os.path.dirname(sys.argv[2]).replace('../', '').split('/')
|
||||
variable = re.sub(r'[\.]+','_', os.path.basename(sys.argv[2]).lower())
|
||||
guard = '_'.join(namespaces).upper() + '_' + re.sub(r'[\.]+','_', os.path.basename(sys.argv[3]).upper())
|
||||
|
||||
is_binary = input_file_ext not in ['html', 'css', 'js']
|
||||
|
||||
with open(input_file, 'rb' if is_binary else 'r') as f:
|
||||
contents = f.read()
|
||||
|
||||
os.makedirs(os.path.dirname(sys.argv[3]), exist_ok=True)
|
||||
|
||||
if is_binary:
|
||||
size = len(contents)
|
||||
contents = ', '.join(['std::byte{' + str(by) + '}' for by in contents])
|
||||
|
||||
contents = '\n '.join(textwrap.wrap(contents, width=120))
|
||||
|
||||
with open(sys.argv[3], 'w') as f:
|
||||
f.write(f'''// Auto-generated, do not edit, it will be overwritten
|
||||
|
||||
#ifndef {guard}
|
||||
#define {guard}
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace {'::'.join(namespaces)}
|
||||
{{
|
||||
|
||||
constexpr std::array<std::byte, {size}> {variable}{{{{
|
||||
{contents}
|
||||
}}}};
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
''')
|
||||
|
||||
else: # text
|
||||
with open(sys.argv[3], 'w') as f:
|
||||
f.write(f'''// Auto-generated, do not edit, it will be overwritten
|
||||
|
||||
#ifndef {guard}
|
||||
#define {guard}
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace {'::'.join(namespaces)}
|
||||
{{
|
||||
|
||||
constexpr std::string_view {variable} = R"~#!({contents})~#!";
|
||||
|
||||
}}
|
||||
|
||||
#endif
|
||||
''')
|
||||
@ -25,6 +25,7 @@
|
||||
#include "boardlisttablemodel.hpp"
|
||||
#include "map/link.hpp"
|
||||
#include "tile/tiles.hpp"
|
||||
#include "tile/hidden/hiddencrossoverrailtile.hpp"
|
||||
#include "../core/method.tpp"
|
||||
#include "../core/objectproperty.tpp"
|
||||
#include "../world/world.hpp"
|
||||
@ -33,6 +34,8 @@
|
||||
#include "../utils/displayname.hpp"
|
||||
#include <cassert>
|
||||
|
||||
#include "../log/log.hpp"
|
||||
|
||||
CREATE_IMPL(Board)
|
||||
|
||||
Board::Board(World& world, std::string_view _id) :
|
||||
@ -385,6 +388,43 @@ void Board::modified()
|
||||
Connector connector{startConnector.opposite()};
|
||||
while(auto nextTile = getTile(connector.location))
|
||||
{
|
||||
if(isIntercardinal(connector.direction)) // check for crossover
|
||||
{
|
||||
auto prevTile = getTile(TileLocation{nextTile->x, nextTile->y} + connector.direction);
|
||||
assert(prevTile);
|
||||
auto otherTile1 = getTile({prevTile->x, nextTile->y});
|
||||
auto otherTile2 = getTile({nextTile->x, prevTile->y});
|
||||
|
||||
if(otherTile1 && otherTile2)
|
||||
{
|
||||
const auto perpendicular =
|
||||
(connector.direction == Connector::Direction::NorthEast) || (connector.direction == Connector::Direction::SouthWest)
|
||||
? ~rotate90cw(connector.direction) : rotate90cw(connector.direction);
|
||||
|
||||
auto otherConnector1 = otherTile1->getConnector(perpendicular);
|
||||
auto otherConnector2 = otherTile2->getConnector(~perpendicular);
|
||||
|
||||
if(otherConnector1 && otherConnector2) // crossover found!
|
||||
{
|
||||
const TileLocation topLeft{std::min<int16_t>(prevTile->x, nextTile->x), std::min<int16_t>(prevTile->y, nextTile->y)};
|
||||
auto it = m_railCrossOver.find(topLeft);
|
||||
if(it == m_railCrossOver.end())
|
||||
{
|
||||
it = m_railCrossOver.emplace(topLeft, std::make_shared<HiddenCrossOverRailTile>(world())).first;
|
||||
it->second->x.setValueInternal(topLeft.x);
|
||||
it->second->y.setValueInternal(topLeft.y);
|
||||
}
|
||||
auto& crossOver = it->second;
|
||||
auto crossOverConnector = crossOver->getConnector(connector.direction);
|
||||
assert(crossOverConnector);
|
||||
|
||||
auto link = std::make_shared<Link>(std::move(tiles));
|
||||
link->connect(*startTile->node(), startConnector, *crossOver->node(), *crossOverConnector);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(nextTile->node())
|
||||
{
|
||||
auto link = std::make_shared<Link>(std::move(tiles));
|
||||
@ -429,6 +469,30 @@ void Board::modified()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// remove unconnected crossovers:
|
||||
auto it = m_railCrossOver.begin();
|
||||
while(it != m_railCrossOver.end())
|
||||
{
|
||||
bool remove = false;
|
||||
assert(it->second->node());
|
||||
for(const auto& link : (*it->second->node()).get().links())
|
||||
{
|
||||
if(!link)
|
||||
{
|
||||
remove = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(remove)
|
||||
{
|
||||
it = m_railCrossOver.erase(it);
|
||||
}
|
||||
else
|
||||
{
|
||||
it++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// notify board changed:
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2023 Reinder Feenstra
|
||||
* Copyright (C) 2020-2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -31,6 +31,7 @@
|
||||
|
||||
class Tile;
|
||||
struct TileData;
|
||||
class HiddenCrossOverRailTile;
|
||||
|
||||
class Board : public IdObject
|
||||
{
|
||||
@ -41,6 +42,7 @@ class Board : public IdObject
|
||||
|
||||
private:
|
||||
bool m_modified = false;
|
||||
std::unordered_map<TileLocation, std::shared_ptr<HiddenCrossOverRailTile>, TileLocationHash> m_railCrossOver;
|
||||
|
||||
void modified();
|
||||
void removeTile(int16_t x, int16_t y);
|
||||
@ -101,6 +103,13 @@ class Board : public IdObject
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
#ifdef TRAINTASTIC_TEST
|
||||
const auto& railCrossOver() const
|
||||
{
|
||||
return m_railCrossOver;
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2022-2024 Reinder Feenstra
|
||||
* Copyright (C) 2022-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -64,14 +64,6 @@ AbstractSignalPath::AbstractSignalPath(SignalRailTile& signal, size_t blocksAhea
|
||||
}
|
||||
}
|
||||
|
||||
AbstractSignalPath::~AbstractSignalPath()
|
||||
{
|
||||
for(auto& connection : m_connections)
|
||||
{
|
||||
connection.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
void AbstractSignalPath::evaluate()
|
||||
{
|
||||
const bool stop = !signal().hasReservedPath() && requireReservation();
|
||||
@ -84,7 +76,7 @@ bool AbstractSignalPath::requireReservation() const
|
||||
return (m_signal.requireReservation == AutoYesNo::Yes || (m_signal.requireReservation == AutoYesNo::Auto && m_requireReservation));
|
||||
}
|
||||
|
||||
const AbstractSignalPath::BlockItem* AbstractSignalPath::nextBlock(const Item* item) const
|
||||
const AbstractSignalPath::BlockItem* AbstractSignalPath::nextBlock(const Item* item)
|
||||
{
|
||||
while(item)
|
||||
{
|
||||
@ -97,7 +89,7 @@ const AbstractSignalPath::BlockItem* AbstractSignalPath::nextBlock(const Item* i
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::tuple<const AbstractSignalPath::BlockItem*, const AbstractSignalPath::SignalItem*> AbstractSignalPath::nextBlockOrSignal(const Item* item) const
|
||||
std::tuple<const AbstractSignalPath::BlockItem*, const AbstractSignalPath::SignalItem*> AbstractSignalPath::nextBlockOrSignal(const Item* item)
|
||||
{
|
||||
while(item)
|
||||
{
|
||||
@ -114,7 +106,7 @@ std::tuple<const AbstractSignalPath::BlockItem*, const AbstractSignalPath::Signa
|
||||
return {nullptr, nullptr};
|
||||
}
|
||||
|
||||
void AbstractSignalPath::getBlockStates(tcb::span<BlockState> blockStates) const
|
||||
void AbstractSignalPath::getBlockStates(std::span<BlockState> blockStates) const
|
||||
{
|
||||
size_t i = 0;
|
||||
const Item* item = m_root.get();
|
||||
@ -238,7 +230,7 @@ std::unique_ptr<const AbstractSignalPath::Item> AbstractSignalPath::findBlocks(c
|
||||
if(nextNode.getLink(0).get() == &link)
|
||||
return findBlocks(nextNode, *nextLink, blocksAhead);
|
||||
}
|
||||
else if(isRailBridge(tile->tileId) || isRailCross(tile->tileId))
|
||||
else if(isRailBridge(tile->tileId) || isRailCross(tile->tileId) || tile->tileId == TileId::HiddenRailCrossOver)
|
||||
{
|
||||
// 2 1 2 2 3
|
||||
// | \| |/
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2022-2023 Reinder Feenstra
|
||||
* Copyright (C) 2022-2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -155,7 +155,7 @@ class AbstractSignalPath : public Path
|
||||
private:
|
||||
std::unique_ptr<const Item> m_root;
|
||||
bool m_requireReservation = false;
|
||||
std::vector<boost::signals2::connection> m_connections;
|
||||
std::vector<boost::signals2::scoped_connection> m_connections;
|
||||
|
||||
std::unique_ptr<const Item> findBlocks(const Node& node, const Link& link, size_t blocksAhead);
|
||||
|
||||
@ -181,25 +181,25 @@ class AbstractSignalPath : public Path
|
||||
return m_root.get();
|
||||
}
|
||||
|
||||
const BlockItem* nextBlock(const Item* item) const;
|
||||
static const BlockItem* nextBlock(const Item* item);
|
||||
inline const BlockItem* nextBlock() const
|
||||
{
|
||||
return nextBlock(root());
|
||||
}
|
||||
|
||||
std::tuple<const BlockItem*, const SignalItem*> nextBlockOrSignal(const Item* item) const;
|
||||
static std::tuple<const BlockItem*, const SignalItem*> nextBlockOrSignal(const Item* item);
|
||||
inline std::tuple<const BlockItem*, const SignalItem*> nextBlockOrSignal() const
|
||||
{
|
||||
return nextBlockOrSignal(root());
|
||||
}
|
||||
|
||||
void getBlockStates(tcb::span<BlockState> blockStates) const;
|
||||
void getBlockStates(std::span<BlockState> blockStates) const;
|
||||
std::shared_ptr<BlockRailTile> getBlock(size_t index) const;
|
||||
|
||||
public:
|
||||
AbstractSignalPath(SignalRailTile& signal);
|
||||
AbstractSignalPath(SignalRailTile& signal, size_t blocksAhead);
|
||||
virtual ~AbstractSignalPath();
|
||||
virtual ~AbstractSignalPath() = default;
|
||||
|
||||
void evaluate();
|
||||
};
|
||||
|
||||
@ -25,6 +25,7 @@
|
||||
#include <traintastic/enum/crossstate.hpp>
|
||||
#include "node.hpp"
|
||||
#include "link.hpp"
|
||||
#include "../tile/hidden/hiddencrossoverrailtile.hpp"
|
||||
#include "../tile/rail/blockrailtile.hpp"
|
||||
#include "../tile/rail/bridgerailtile.hpp"
|
||||
#include "../tile/rail/crossrailtile.hpp"
|
||||
@ -284,6 +285,31 @@ std::vector<std::shared_ptr<BlockPath>> BlockPath::find(BlockRailTile& startBloc
|
||||
current.link = otherLink(nextNode, *current.link).get();
|
||||
break;
|
||||
|
||||
case TileId::HiddenRailCrossOver:
|
||||
{
|
||||
// 1 2
|
||||
// X
|
||||
// 0 3
|
||||
auto crossOver = tile.shared_ptr<HiddenCrossOverRailTile>();
|
||||
if(contains(current.path->m_crossOvers, crossOver))
|
||||
{
|
||||
todo.pop(); // drop it, can't pass crossover twice
|
||||
break;
|
||||
}
|
||||
|
||||
for(size_t i = 0; i < 4; i++)
|
||||
{
|
||||
if(nextNode.getLink(i).get() == current.link)
|
||||
{
|
||||
current.node = &nextNode;
|
||||
current.link = nextNode.getLink((i + 2) % 4).get(); // opposite
|
||||
current.path->m_crossOvers.emplace_back(crossOver, i % 2 == 0 ? CrossState::AC : CrossState::BD);
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default: // passive or non rail tiles
|
||||
assert(false); // this should never happen
|
||||
todo.pop(); // drop it in case it does, however that is a bug!
|
||||
@ -306,7 +332,7 @@ BlockPath::BlockPath(BlockRailTile& block, BlockSide side)
|
||||
|
||||
BlockPath::BlockPath(const BlockPath &other)
|
||||
: Path(other)
|
||||
, std::enable_shared_from_this<BlockPath>()
|
||||
, std::enable_shared_from_this<BlockPath>() // NOLINT(readability-redundant-member-init) -Wextra requires this
|
||||
, m_fromBlock(other.m_fromBlock)
|
||||
, m_fromSide(other.m_fromSide)
|
||||
, m_toBlock(other.m_toBlock)
|
||||
@ -358,7 +384,7 @@ bool BlockPath::isReady() const
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& [directionControlWeak, state] : m_directionControls)
|
||||
for(const auto& [directionControlWeak, state] : m_directionControls) // NOLINT(readability-use-anyofallof)
|
||||
{
|
||||
auto directionControl = directionControlWeak.lock();
|
||||
if(!directionControl) /*[[unlikely]]*/
|
||||
@ -461,6 +487,23 @@ bool BlockPath::reserve(const std::shared_ptr<Train>& train, bool dryRun)
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& [crossOverWeak, state] : m_crossOvers)
|
||||
{
|
||||
if(auto crossOver = crossOverWeak.lock())
|
||||
{
|
||||
if(!crossOver->reserve(state, dryRun))
|
||||
{
|
||||
assert(dryRun);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else /*[[unlikely]]*/
|
||||
{
|
||||
assert(dryRun);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& [bridgeWeak, path] : m_bridges)
|
||||
{
|
||||
if(auto bridge = bridgeWeak.lock())
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2023 Reinder Feenstra
|
||||
* Copyright (C) 2023-2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -37,6 +37,7 @@ class BlockRailTile;
|
||||
class BridgeRailTile;
|
||||
enum class BridgePath : uint8_t;
|
||||
class CrossRailTile;
|
||||
class HiddenCrossOverRailTile;
|
||||
enum class CrossState : uint8_t;
|
||||
class DirectionControlRailTile;
|
||||
enum class DirectionControlState : uint8_t;
|
||||
@ -60,6 +61,7 @@ class BlockPath : public Path, public std::enable_shared_from_this<BlockPath>
|
||||
std::vector<std::pair<std::weak_ptr<TurnoutRailTile>, TurnoutPosition>> m_turnouts; //!< required turnout positions for the path
|
||||
std::vector<std::pair<std::weak_ptr<DirectionControlRailTile>, DirectionControlState>> m_directionControls; //!< required direction control states for the path
|
||||
std::vector<std::pair<std::weak_ptr<CrossRailTile>, CrossState>> m_crossings; //!< required crossing states for the path
|
||||
std::vector<std::pair<std::weak_ptr<HiddenCrossOverRailTile>, CrossState>> m_crossOvers; //!< required crossing states for the path
|
||||
std::vector<std::pair<std::weak_ptr<BridgeRailTile>, BridgePath>> m_bridges; //!< bridges to reserve
|
||||
std::vector<std::weak_ptr<SignalRailTile>> m_signals; //!< signals in path
|
||||
std::weak_ptr<NXButtonRailTile> m_nxButtonFrom;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2022 Reinder Feenstra
|
||||
* Copyright (C) 2022,2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -72,6 +72,17 @@ constexpr Connector::Direction operator ~(Connector::Direction value)
|
||||
return static_cast<Connector::Direction>(n <= 4 ? n + 4 : n - 4);
|
||||
}
|
||||
|
||||
constexpr Connector::Direction rotate90cw(Connector::Direction value)
|
||||
{
|
||||
const auto n = static_cast<std::underlying_type_t<Connector::Direction>>(value);
|
||||
return static_cast<Connector::Direction>(n <= 6 ? n + 2 : n - 6);
|
||||
}
|
||||
|
||||
constexpr bool isIntercardinal(Connector::Direction value)
|
||||
{
|
||||
return (static_cast<std::underlying_type_t<Connector::Direction>>(value) & 1) == 0;
|
||||
}
|
||||
|
||||
constexpr Connector::Direction toConnectorDirection(TileRotate value)
|
||||
{
|
||||
const auto r = static_cast<std::underlying_type_t<TileRotate>>(value);
|
||||
|
||||
@ -24,9 +24,7 @@
|
||||
#include <cassert>
|
||||
#include "node.hpp"
|
||||
|
||||
Link::Link()
|
||||
{
|
||||
}
|
||||
Link::Link() = default;
|
||||
|
||||
Link::Link(std::vector<std::shared_ptr<Tile>> tiles)
|
||||
: m_tiles{std::move(tiles)}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2023-2024 Reinder Feenstra
|
||||
* Copyright (C) 2023-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -37,7 +37,7 @@ const std::shared_ptr<Link>& Path::otherLink(const Node& node, const Link& link)
|
||||
return noLink;
|
||||
}
|
||||
|
||||
tcb::span<const Path::TurnoutPositionLink> Path::getTurnoutLinks(TurnoutRailTile& turnout, const Link& link)
|
||||
std::span<const Path::TurnoutPositionLink> Path::getTurnoutLinks(TurnoutRailTile& turnout, const Link& link)
|
||||
{
|
||||
static constexpr std::array<TurnoutPositionLink, 1> straight0{{{TurnoutPosition::Straight, 0}}};
|
||||
static constexpr std::array<TurnoutPositionLink, 1> left0{{{TurnoutPosition::Left, 0}}};
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2023 Reinder Feenstra
|
||||
* Copyright (C) 2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -24,7 +24,7 @@
|
||||
#define TRAINTASTIC_SERVER_BOARD_MAP_PATH_HPP
|
||||
|
||||
#include <memory>
|
||||
#include <tcb/span.hpp>
|
||||
#include <span>
|
||||
|
||||
class TurnoutRailTile;
|
||||
enum class TurnoutPosition : uint8_t;
|
||||
@ -41,7 +41,7 @@ protected:
|
||||
};
|
||||
|
||||
static const std::shared_ptr<Link>& otherLink(const Node& node, const Link& link);
|
||||
static tcb::span<const TurnoutPositionLink> getTurnoutLinks(TurnoutRailTile& turnout, const Link& link);
|
||||
static std::span<const TurnoutPositionLink> getTurnoutLinks(TurnoutRailTile& turnout, const Link& link);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -92,7 +92,7 @@ void NXManager::released(NXButtonRailTile& button)
|
||||
|
||||
bool NXManager::selectPath(const NXButtonRailTile& from, const NXButtonRailTile& to)
|
||||
{
|
||||
for(auto& path : from.block->paths())
|
||||
for(const auto& path : from.block->paths())
|
||||
{
|
||||
if(path->nxButtonTo().get() == &to && path->nxButtonFrom().get() == &from)
|
||||
{
|
||||
|
||||
@ -37,15 +37,15 @@ class NXManager : public SubObject
|
||||
private:
|
||||
std::list<std::weak_ptr<NXButtonRailTile>> m_pressedButtons;
|
||||
|
||||
bool selectPath(const NXButtonRailTile& from, const NXButtonRailTile& to);
|
||||
static bool selectPath(const NXButtonRailTile& from, const NXButtonRailTile& to);
|
||||
|
||||
public:
|
||||
Method<void(const std::shared_ptr<NXButtonRailTile>&, const std::shared_ptr<NXButtonRailTile>&)> select;
|
||||
|
||||
NXManager(Object& parent_, std::string_view parentPropertyName);
|
||||
|
||||
void pressed(NXButtonRailTile& tile);
|
||||
void released(NXButtonRailTile& tile);
|
||||
void pressed(NXButtonRailTile& button);
|
||||
void released(NXButtonRailTile& button);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
78
server/src/board/tile/hidden/hiddencrossoverrailtile.cpp
Normale Datei
78
server/src/board/tile/hidden/hiddencrossoverrailtile.cpp
Normale Datei
@ -0,0 +1,78 @@
|
||||
/**
|
||||
* server/src/board/tile/hidden/hiddencrossoverrailtile.cpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#include "hiddencrossoverrailtile.hpp"
|
||||
#include <traintastic/enum/crossstate.hpp>
|
||||
|
||||
HiddenCrossOverRailTile::HiddenCrossOverRailTile(World& world)
|
||||
: HiddenTile(world, TileId::HiddenRailCrossOver)
|
||||
, m_node{*this, 4}
|
||||
, m_crossState{CrossState::Unset}
|
||||
{
|
||||
}
|
||||
|
||||
std::string_view HiddenCrossOverRailTile::getClassId() const
|
||||
{
|
||||
assert(false);
|
||||
return {};
|
||||
}
|
||||
|
||||
void HiddenCrossOverRailTile::getConnectors(std::vector<Connector>& connectors) const
|
||||
{
|
||||
// x x+1
|
||||
// +--+--+
|
||||
// y | | |
|
||||
// +--X--+
|
||||
// y+1 | | |
|
||||
// +--+--+
|
||||
//
|
||||
// The hidden crossing is actually at (x+0.5, y+0.5), but that can't be stored.
|
||||
// So we store (x, y) and trick it a bit.
|
||||
connectors.emplace_back(location().adjusted(0, 1), Connector::Direction::NorthEast, Connector::Type::Rail);
|
||||
connectors.emplace_back(location(), Connector::Direction::SouthEast, Connector::Type::Rail);
|
||||
connectors.emplace_back(location().adjusted(1, 0), Connector::Direction::SouthWest, Connector::Type::Rail);
|
||||
connectors.emplace_back(location().adjusted(1, 1), Connector::Direction::NorthWest, Connector::Type::Rail);
|
||||
}
|
||||
|
||||
bool HiddenCrossOverRailTile::reserve(CrossState crossState, bool dryRun)
|
||||
{
|
||||
if(m_crossState != CrossState::Unset)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!dryRun)
|
||||
{
|
||||
m_crossState = crossState;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HiddenCrossOverRailTile::release(bool dryRun)
|
||||
{
|
||||
if(!dryRun)
|
||||
{
|
||||
m_crossState = CrossState::Unset;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
50
server/src/board/tile/hidden/hiddencrossoverrailtile.hpp
Normale Datei
50
server/src/board/tile/hidden/hiddencrossoverrailtile.hpp
Normale Datei
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* server/src/board/tile/hidden/hiddencrossoverrailtile.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_BOARD_TILE_HIDDEN_HIDDENCROSSOVERRAILTILE_HPP
|
||||
#define TRAINTASTIC_SERVER_BOARD_TILE_HIDDEN_HIDDENCROSSOVERRAILTILE_HPP
|
||||
|
||||
#include "hiddentile.hpp"
|
||||
#include "../../map/node.hpp"
|
||||
|
||||
enum class CrossState : uint8_t;
|
||||
|
||||
class HiddenCrossOverRailTile : public HiddenTile
|
||||
{
|
||||
private:
|
||||
Node m_node;
|
||||
CrossState m_crossState; //!< indicates which path is reserved
|
||||
|
||||
public:
|
||||
HiddenCrossOverRailTile(World& world);
|
||||
|
||||
std::string_view getClassId() const final;
|
||||
|
||||
std::optional<std::reference_wrapper<const Node>> node() const final { return m_node; }
|
||||
std::optional<std::reference_wrapper<Node>> node() final { return m_node; }
|
||||
void getConnectors(std::vector<Connector>& connectors) const final;
|
||||
|
||||
bool reserve(CrossState crossState, bool dryRun = false);
|
||||
bool release(bool dryRun = false);
|
||||
};
|
||||
|
||||
#endif
|
||||
37
server/src/board/tile/hidden/hiddentile.hpp
Normale Datei
37
server/src/board/tile/hidden/hiddentile.hpp
Normale Datei
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* server/src/board/tile/hidden/hiddentile.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2024 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
* as published by the Free Software Foundation; either version 2
|
||||
* of the License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_BOARD_TILE_HIDDEN_HIDDENTILE_HPP
|
||||
#define TRAINTASTIC_SERVER_BOARD_TILE_HIDDEN_HIDDENTILE_HPP
|
||||
|
||||
#include "../tile.hpp"
|
||||
|
||||
class HiddenTile : public Tile
|
||||
{
|
||||
protected:
|
||||
HiddenTile(World& world, TileId tileId_)
|
||||
: Tile(world, {}, tileId_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2024 Reinder Feenstra
|
||||
* Copyright (C) 2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -74,7 +74,7 @@ SwitchTile::SwitchTile(World& world, std::string_view _id)
|
||||
m_interfaceItems.add(colorOff);
|
||||
|
||||
Attributes::addObjectEditor(value, false);
|
||||
Attributes::addAliases(value, tcb::span<const bool>(valueAliasKeys), tcb::span<const std::string>(valueAliasValues));
|
||||
Attributes::addAliases(value, std::span<const bool>(valueAliasKeys), std::span<const std::string>(valueAliasValues));
|
||||
m_interfaceItems.add(value);
|
||||
|
||||
Attributes::addDisplayName(outputMap, DisplayName::BoardTile::outputMap);
|
||||
|
||||
@ -264,14 +264,12 @@ void BlockRailTile::inputItemValueChanged(BlockInputMapItem& item)
|
||||
//! \todo log something (at least in debug)
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
train = status->train.value();
|
||||
direction = path->toSide() == BlockSide::A ? BlockTrainDirection::TowardsB : BlockTrainDirection::TowardsA;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(train)
|
||||
{
|
||||
@ -291,7 +289,7 @@ void BlockRailTile::inputItemValueChanged(BlockInputMapItem& item)
|
||||
|
||||
if(enterA != enterB)
|
||||
{
|
||||
auto& blockStatus = enterA ? trains.front() : trains.back();
|
||||
const auto& blockStatus = enterA ? trains.front() : trains.back();
|
||||
TrainTracking::enter(blockStatus);
|
||||
}
|
||||
else
|
||||
@ -389,7 +387,7 @@ void BlockRailTile::identificationEvent(BlockInputMapItem& /*item*/, Identificat
|
||||
}
|
||||
}
|
||||
|
||||
const std::shared_ptr<BlockPath> BlockRailTile::getReservedPath(BlockSide side) const
|
||||
std::shared_ptr<BlockPath> BlockRailTile::getReservedPath(BlockSide side) const
|
||||
{
|
||||
assert(side == BlockSide::A || side == BlockSide::B);
|
||||
return m_reservedPaths[static_cast<uint8_t>(side)].lock();
|
||||
|
||||
@ -112,7 +112,7 @@ class BlockRailTile : public RailTile
|
||||
void inputItemValueChanged(BlockInputMapItem& item);
|
||||
void identificationEvent(BlockInputMapItem& item, IdentificationEventType eventType, uint16_t identifier, Direction direction, uint8_t category);
|
||||
|
||||
const std::shared_ptr<BlockPath> getReservedPath(BlockSide side) const;
|
||||
std::shared_ptr<BlockPath> getReservedPath(BlockSide side) const;
|
||||
bool reserve(const std::shared_ptr<BlockPath>& blockPath, const std::shared_ptr<Train>& train, BlockSide side, bool dryRun = false);
|
||||
bool release(BlockSide side, bool dryRun = false);
|
||||
|
||||
|
||||
@ -62,7 +62,7 @@ class DirectionControlRailTile final : public StraightRailTile
|
||||
std::optional<std::reference_wrapper<const Node>> node() const final { return m_node; }
|
||||
std::optional<std::reference_wrapper<Node>> node() final { return m_node; }
|
||||
|
||||
bool reserve(DirectionControlState turnoutPosition, bool dryRun = false);
|
||||
bool reserve(DirectionControlState directionControlState, bool dryRun = false);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -71,7 +71,7 @@ Signal2AspectRailTile::Signal2AspectRailTile(World& world, std::string_view _id)
|
||||
SignalRailTile(world, _id, TileId::RailSignal2Aspect)
|
||||
{
|
||||
// Skip Unknown aspect
|
||||
tcb::span<const SignalAspect, 2> setAspectValues = tcb::make_span(aspectValues).subspan<1>();
|
||||
std::span<const SignalAspect, 2> setAspectValues = std::span(aspectValues).subspan<1>();
|
||||
|
||||
outputMap.setValueInternal(std::make_shared<SignalOutputMap>(*this, outputMap.name(), std::initializer_list<SignalAspect>{SignalAspect::Stop, SignalAspect::Proceed}, getDefaultActionValue));
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -114,7 +114,7 @@ Signal3AspectRailTile::Signal3AspectRailTile(World& world, std::string_view _id)
|
||||
SignalRailTile(world, _id, TileId::RailSignal3Aspect)
|
||||
{
|
||||
// Skip Unknown aspect
|
||||
tcb::span<const SignalAspect, 3> setAspectValues = tcb::make_span(aspectValues).subspan<1>();
|
||||
std::span<const SignalAspect, 3> setAspectValues = std::span(aspectValues).subspan<1>();
|
||||
|
||||
outputMap.setValueInternal(std::make_shared<SignalOutputMap>(*this, outputMap.name(), std::initializer_list<SignalAspect>{SignalAspect::Stop, SignalAspect::ProceedReducedSpeed, SignalAspect::Proceed}, getDefaultActionValue));
|
||||
|
||||
|
||||
@ -64,11 +64,11 @@ std::optional<OutputActionValue> SignalRailTile::getDefaultActionValue(SignalAsp
|
||||
{
|
||||
return static_cast<int16_t>(0);
|
||||
}
|
||||
else if(signalAspect == SignalAspect::ProceedReducedSpeed)
|
||||
if(signalAspect == SignalAspect::ProceedReducedSpeed)
|
||||
{
|
||||
return static_cast<int16_t>(1);
|
||||
}
|
||||
else if(signalAspect == SignalAspect::Proceed)
|
||||
if(signalAspect == SignalAspect::Proceed)
|
||||
{
|
||||
return static_cast<int16_t>(16);
|
||||
}
|
||||
@ -98,6 +98,7 @@ SignalRailTile::SignalRailTile(World& world, std::string_view _id, TileId tileId
|
||||
Attributes::addEnabled(name, editable);
|
||||
m_interfaceItems.add(name);
|
||||
|
||||
Attributes::addDisplayName(requireReservation, "board_tile.rail.signal:require_reservation");
|
||||
Attributes::addEnabled(requireReservation, editable);
|
||||
Attributes::addValues(requireReservation, autoYesNoValues);
|
||||
m_interfaceItems.add(requireReservation);
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2022,2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -63,7 +63,7 @@ Turnout3WayRailTile::Turnout3WayRailTile(World& world, std::string_view _id)
|
||||
: TurnoutRailTile(world, _id, TileId::RailTurnout3Way, 4)
|
||||
{
|
||||
// Skip Unknown position
|
||||
tcb::span<const TurnoutPosition, 3> setPositionValues = tcb::make_span(positionValues).subspan<1>();
|
||||
std::span<const TurnoutPosition, 3> setPositionValues = std::span(positionValues).subspan<1>();
|
||||
|
||||
outputMap.setValueInternal(std::make_shared<TurnoutOutputMap>(*this, outputMap.name(), std::initializer_list<TurnoutPosition>{TurnoutPosition::Straight, TurnoutPosition::Left, TurnoutPosition::Right}, getDefaultActionValue));
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2022,2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -37,10 +37,10 @@ namespace PositionValues
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr tcb::span<const TurnoutPosition> positionValuesSingleMotor = tcb::make_span(PositionValues::singleMotor);
|
||||
static constexpr tcb::span<const TurnoutPosition> positionValuesDualMotor = tcb::make_span(PositionValues::dualMotor);
|
||||
static constexpr tcb::span<const TurnoutPosition> setPositionValuesSingleMotor = tcb::make_span(PositionValues::singleMotor).subspan<1>();
|
||||
static constexpr tcb::span<const TurnoutPosition> setPositionValuesDualMotor = tcb::make_span(PositionValues::dualMotor).subspan<1>();
|
||||
static constexpr std::span<const TurnoutPosition> positionValuesSingleMotor = std::span(PositionValues::singleMotor);
|
||||
static constexpr std::span<const TurnoutPosition> positionValuesDualMotor = std::span(PositionValues::dualMotor);
|
||||
static constexpr std::span<const TurnoutPosition> setPositionValuesSingleMotor = std::span(PositionValues::singleMotor).subspan<1>();
|
||||
static constexpr std::span<const TurnoutPosition> setPositionValuesDualMotor = std::span(PositionValues::dualMotor).subspan<1>();
|
||||
|
||||
TurnoutDoubleSlipRailTile::TurnoutDoubleSlipRailTile(World& world, std::string_view _id)
|
||||
: TurnoutSlipRailTile(world, _id, TileId::RailTurnoutDoubleSlip)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2022,2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -67,7 +67,7 @@ TurnoutLeftRailTile::TurnoutLeftRailTile(World& world, std::string_view _id, Til
|
||||
: TurnoutRailTile(world, _id, tileId_, 3)
|
||||
{
|
||||
// Skip Unknown position
|
||||
tcb::span<const TurnoutPosition, 2> setPositionValues = tcb::make_span(positionValues).subspan<1>();
|
||||
std::span<const TurnoutPosition, 2> setPositionValues = std::span(positionValues).subspan<1>();
|
||||
|
||||
outputMap.setValueInternal(std::make_shared<TurnoutOutputMap>(*this, outputMap.name(), std::initializer_list<TurnoutPosition>{TurnoutPosition::Straight, TurnoutPosition::Left}, getDefaultActionValue));
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2022,2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -67,7 +67,7 @@ TurnoutRightRailTile::TurnoutRightRailTile(World& world, std::string_view _id, T
|
||||
: TurnoutRailTile(world, _id, tileId_, 3)
|
||||
{
|
||||
// Skip Unknown position
|
||||
tcb::span<const TurnoutPosition, 2> setPositionValues = tcb::make_span(positionValues).subspan<1>();
|
||||
std::span<const TurnoutPosition, 2> setPositionValues = std::span(positionValues).subspan<1>();
|
||||
|
||||
outputMap.setValueInternal(std::make_shared<TurnoutOutputMap>(*this, outputMap.name(), std::initializer_list<TurnoutPosition>{TurnoutPosition::Straight, TurnoutPosition::Right}, getDefaultActionValue));
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2022,2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -37,10 +37,10 @@ namespace PositionValues
|
||||
};
|
||||
}
|
||||
|
||||
static constexpr tcb::span<const TurnoutPosition> positionValuesSingleMotor = tcb::make_span(PositionValues::singleMotor);
|
||||
static constexpr tcb::span<const TurnoutPosition> positionValuesDualMotor = tcb::make_span(PositionValues::dualMotor);
|
||||
static constexpr tcb::span<const TurnoutPosition> setPositionValuesSingleMotor = tcb::make_span(PositionValues::singleMotor).subspan<1>();
|
||||
static constexpr tcb::span<const TurnoutPosition> setPositionValuesDualMotor = tcb::make_span(PositionValues::dualMotor).subspan<1>();
|
||||
static constexpr std::span<const TurnoutPosition> positionValuesSingleMotor = std::span(PositionValues::singleMotor);
|
||||
static constexpr std::span<const TurnoutPosition> positionValuesDualMotor = std::span(PositionValues::dualMotor);
|
||||
static constexpr std::span<const TurnoutPosition> setPositionValuesSingleMotor = std::span(PositionValues::singleMotor).subspan<1>();
|
||||
static constexpr std::span<const TurnoutPosition> setPositionValuesDualMotor = std::span(PositionValues::dualMotor).subspan<1>();
|
||||
|
||||
TurnoutSingleSlipRailTile::TurnoutSingleSlipRailTile(World& world, std::string_view _id)
|
||||
: TurnoutSlipRailTile(world, _id, TileId::RailTurnoutSingleSlip)
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2022,2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -40,7 +40,7 @@ TurnoutWyeRailTile::TurnoutWyeRailTile(World& world, std::string_view _id)
|
||||
: TurnoutRailTile(world, _id, TileId::RailTurnoutWye, 3)
|
||||
{
|
||||
// Skip Unknown position
|
||||
tcb::span<const TurnoutPosition, 2> setPositionValues = tcb::make_span(positionValues).subspan<1>();
|
||||
std::span<const TurnoutPosition, 2> setPositionValues = std::span(positionValues).subspan<1>();
|
||||
|
||||
outputMap.setValueInternal(std::make_shared<TurnoutOutputMap>(*this, outputMap.name(), std::initializer_list<TurnoutPosition>{TurnoutPosition::Left, TurnoutPosition::Right}, getDefaultActionValue));
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2021,2023-2024 Reinder Feenstra
|
||||
* Copyright (C) 2020-2021,2023-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -37,7 +37,7 @@ Tile::Tile(World& world, std::string_view _id, TileId tileId_)
|
||||
, width{this, "width", 1, PropertyFlags::ReadOnly | PropertyFlags::Store}
|
||||
{
|
||||
Attributes::addObjectEditor(tileId, false);
|
||||
Attributes::addValues(tileId, tcb::span<const TileId, 0>{});
|
||||
Attributes::addValues(tileId, std::span<const TileId, 0>{});
|
||||
m_interfaceItems.add(tileId);
|
||||
|
||||
Attributes::addObjectEditor(x, false);
|
||||
@ -59,6 +59,21 @@ Tile::Tile(World& world, std::string_view _id, TileId tileId_)
|
||||
m_interfaceItems.add(width);
|
||||
}
|
||||
|
||||
std::optional<Connector> Tile::getConnector(Connector::Direction direction) const
|
||||
{
|
||||
std::vector<Connector> connectors;
|
||||
connectors.reserve(8);
|
||||
getConnectors(connectors);
|
||||
for(const auto& c : connectors)
|
||||
{
|
||||
if(c.direction == direction)
|
||||
{
|
||||
return c;
|
||||
}
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Board& Tile::getBoard()
|
||||
{
|
||||
for(const auto& board : *m_world.boards)
|
||||
|
||||
@ -68,6 +68,7 @@ class Tile : public IdObject
|
||||
virtual std::optional<std::reference_wrapper<const Node>> node() const { return {}; }
|
||||
virtual std::optional<std::reference_wrapper<Node>> node() { return {}; }
|
||||
virtual void getConnectors(std::vector<Connector>& /*connectors*/) const {}
|
||||
std::optional<Connector> getConnector(Connector::Direction direction) const;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021-2022 Reinder Feenstra
|
||||
* Copyright (C) 2021-2022,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -25,7 +25,7 @@
|
||||
|
||||
#include "interfaceitem.hpp"
|
||||
#include <list>
|
||||
#include <tcb/span.hpp>
|
||||
#include <span>
|
||||
#include "eventflags.hpp"
|
||||
#include "argument.hpp"
|
||||
#include "typeinfo.hpp"
|
||||
@ -54,7 +54,7 @@ class AbstractEvent : public InterfaceItem
|
||||
|
||||
inline EventFlags flags() const { return m_flags; }
|
||||
|
||||
virtual tcb::span<const TypeInfo> argumentTypeInfo() const = 0;
|
||||
virtual std::span<const TypeInfo> argumentTypeInfo() const = 0;
|
||||
|
||||
void connect(std::shared_ptr<AbstractEventHandler> handler);
|
||||
bool disconnect(const std::shared_ptr<AbstractEventHandler>& handler);
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -28,7 +28,7 @@
|
||||
#include <vector>
|
||||
#include <variant>
|
||||
#include <stdexcept>
|
||||
#include <tcb/span.hpp>
|
||||
#include <span>
|
||||
#include "argument.hpp"
|
||||
#include "typeinfo.hpp"
|
||||
|
||||
@ -113,7 +113,7 @@ class AbstractMethod : public InterfaceItem
|
||||
|
||||
inline MethodFlags flags() const { return m_flags; }
|
||||
|
||||
virtual tcb::span<const TypeInfo> argumentTypeInfo() const = 0;
|
||||
virtual std::span<const TypeInfo> argumentTypeInfo() const = 0;
|
||||
virtual TypeInfo resultTypeInfo() const = 0;
|
||||
virtual Result call(const Arguments& args) = 0;
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021,2023 Reinder Feenstra
|
||||
* Copyright (C) 2021,2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -24,7 +24,7 @@
|
||||
#define TRAINTASTIC_SERVER_CORE_ABSTRACTVECTORPROPERTY_HPP
|
||||
|
||||
#include "baseproperty.hpp"
|
||||
#include <tcb/span.hpp>
|
||||
#include <span>
|
||||
#include "objectptr.hpp"
|
||||
|
||||
class AbstractVectorProperty : public BaseProperty
|
||||
@ -52,7 +52,7 @@ class AbstractVectorProperty : public BaseProperty
|
||||
virtual void setObject(size_t index, const ObjectPtr& value) = 0;
|
||||
|
||||
virtual void loadJSON(const nlohmann::json& values) = 0;
|
||||
virtual void loadObjects(tcb::span<ObjectPtr> values) = 0;
|
||||
virtual void loadObjects(std::span<ObjectPtr> values) = 0;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2024 Reinder Feenstra
|
||||
* Copyright (C) 2019-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -30,7 +30,7 @@
|
||||
#include "property.hpp"
|
||||
#include "unitproperty.hpp"
|
||||
#include "vectorproperty.hpp"
|
||||
#include <tcb/span.hpp>
|
||||
#include <span>
|
||||
|
||||
struct Attributes
|
||||
{
|
||||
@ -59,7 +59,7 @@ struct Attributes
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static inline void addAliases(Property<T>& property, tcb::span<const T> keys, tcb::span<const std::string> values)
|
||||
static inline void addAliases(Property<T>& property, std::span<const T> keys, std::span<const std::string> values)
|
||||
{
|
||||
assert(keys.size() == values.size());
|
||||
property.addAttribute(AttributeName::AliasKeys, keys);
|
||||
@ -67,7 +67,7 @@ struct Attributes
|
||||
}
|
||||
|
||||
template<class T>
|
||||
static inline void setAliases(Property<T>& property, tcb::span<const T> keys, tcb::span<const std::string> values)
|
||||
static inline void setAliases(Property<T>& property, std::span<const T> keys, std::span<const std::string> values)
|
||||
{
|
||||
assert(keys.size() == values.size());
|
||||
property.setAttribute(AttributeName::AliasKeys, keys);
|
||||
@ -75,7 +75,7 @@ struct Attributes
|
||||
}
|
||||
|
||||
template<class T, typename Unit>
|
||||
static inline void addAliases(UnitProperty<T, Unit>& property, tcb::span<const T> keys, tcb::span<const std::string> values)
|
||||
static inline void addAliases(UnitProperty<T, Unit>& property, std::span<const T> keys, std::span<const std::string> values)
|
||||
{
|
||||
assert(keys.size() == values.size());
|
||||
property.addAttribute(AttributeName::AliasKeys, keys);
|
||||
@ -88,7 +88,7 @@ struct Attributes
|
||||
}
|
||||
|
||||
template<size_t N>
|
||||
static inline void addClassList(InterfaceItem& item, tcb::span<const std::string_view, N> classList)
|
||||
static inline void addClassList(InterfaceItem& item, std::span<const std::string_view, N> classList)
|
||||
{
|
||||
item.addAttribute(AttributeName::ClassList, classList);
|
||||
}
|
||||
@ -175,6 +175,19 @@ struct Attributes
|
||||
property.setAttribute(AttributeName::Min, value);
|
||||
}
|
||||
|
||||
template<class T, class Unit>
|
||||
static inline T getMin(UnitProperty<T, Unit>& property)
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>);
|
||||
return property.template getAttribute<T>(AttributeName::Min);
|
||||
}
|
||||
|
||||
template<class T, class Unit>
|
||||
static inline T getMin(UnitProperty<T, Unit>& property, Unit unit)
|
||||
{
|
||||
return convertUnit(getMin(property), property.unit(), unit);
|
||||
}
|
||||
|
||||
template<class T, class Unit>
|
||||
static inline void setMin(UnitProperty<T, Unit>& property, T value, Unit unit)
|
||||
{
|
||||
@ -189,6 +202,19 @@ struct Attributes
|
||||
property.setAttribute(AttributeName::Max, value);
|
||||
}
|
||||
|
||||
template<class T, class Unit>
|
||||
static inline T getMax(UnitProperty<T, Unit>& property)
|
||||
{
|
||||
static_assert(std::is_floating_point_v<T>);
|
||||
return property.template getAttribute<T>(AttributeName::Max);
|
||||
}
|
||||
|
||||
template<class T, class Unit>
|
||||
static inline T getMax(UnitProperty<T, Unit>& property, Unit unit)
|
||||
{
|
||||
return convertUnit(getMax(property), property.unit(), unit);
|
||||
}
|
||||
|
||||
template<class T, class Unit>
|
||||
static inline void setMax(UnitProperty<T, Unit>& property, T value, Unit unit)
|
||||
{
|
||||
@ -290,19 +316,19 @@ struct Attributes
|
||||
}
|
||||
|
||||
template<class R, class T, size_t N>
|
||||
static inline void addValues(Method<R(T)>& method, tcb::span<const T, N> values)
|
||||
static inline void addValues(Method<R(T)>& method, std::span<const T, N> values)
|
||||
{
|
||||
method.addAttribute(AttributeName::Values, values);
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
static inline void addValues(Property<T>& property, tcb::span<const T, N> values)
|
||||
static inline void addValues(Property<T>& property, std::span<const T, N> values)
|
||||
{
|
||||
property.addAttribute(AttributeName::Values, values);
|
||||
}
|
||||
|
||||
template<typename T, typename Unit, size_t N>
|
||||
static inline void addValues(UnitProperty<T, Unit>& property, tcb::span<const T, N> values)
|
||||
static inline void addValues(UnitProperty<T, Unit>& property, std::span<const T, N> values)
|
||||
{
|
||||
property.addAttribute(AttributeName::Values, values);
|
||||
}
|
||||
@ -344,13 +370,13 @@ struct Attributes
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
static inline void setValues(Property<T>& property, tcb::span<const T, N> values)
|
||||
static inline void setValues(Property<T>& property, std::span<const T, N> values)
|
||||
{
|
||||
property.setAttribute(AttributeName::Values, values);
|
||||
}
|
||||
|
||||
template<typename T, typename Unit, size_t N>
|
||||
static inline void setValues(UnitProperty<T, Unit>& property, tcb::span<const T, N> values)
|
||||
static inline void setValues(UnitProperty<T, Unit>& property, std::span<const T, N> values)
|
||||
{
|
||||
property.setAttribute(AttributeName::Values, values);
|
||||
}
|
||||
@ -374,7 +400,7 @@ struct Attributes
|
||||
}
|
||||
|
||||
template<class R, class T, size_t N>
|
||||
static inline void setValues(Method<R(T)>& method, tcb::span<const T, N> values)
|
||||
static inline void setValues(Method<R(T)>& method, std::span<const T, N> values)
|
||||
{
|
||||
method.setAttribute(AttributeName::Values, values);
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021-2023 Reinder Feenstra
|
||||
* Copyright (C) 2021-2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -67,7 +67,7 @@ class Event : public AbstractEvent
|
||||
{
|
||||
}
|
||||
|
||||
tcb::span<const TypeInfo> argumentTypeInfo() const final
|
||||
std::span<const TypeInfo> argumentTypeInfo() const final
|
||||
{
|
||||
return {typeInfoArray<Args...>};
|
||||
}
|
||||
|
||||
@ -48,6 +48,10 @@ class EventLoop
|
||||
#ifdef TRAINTASTIC_TEST
|
||||
threadId = std::this_thread::get_id();
|
||||
#endif
|
||||
if(ioContext.stopped())
|
||||
{
|
||||
ioContext.restart();
|
||||
}
|
||||
auto work = std::make_shared<boost::asio::io_context::work>(ioContext);
|
||||
ioContext.run();
|
||||
}
|
||||
|
||||
@ -49,10 +49,7 @@ IdObject::IdObject(World& world, std::string_view _id) :
|
||||
m_interfaceItems.add(id);
|
||||
}
|
||||
|
||||
IdObject::~IdObject()
|
||||
{
|
||||
//assert(m_world.expired()); // is destroy() called ??
|
||||
}
|
||||
IdObject::~IdObject() = default;
|
||||
|
||||
void IdObject::destroying()
|
||||
{
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2022 Reinder Feenstra
|
||||
* Copyright (C) 2019-2022,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -54,7 +54,7 @@ class InterfaceItem
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
void addAttribute(AttributeName name, tcb::span<const T, N> values)
|
||||
void addAttribute(AttributeName name, std::span<const T, N> values)
|
||||
{
|
||||
assert(m_attributes.find(name) == m_attributes.end());
|
||||
m_attributes.emplace(name, std::make_unique<SpanAttribute<T, N>>(*this, name, values));
|
||||
@ -63,7 +63,7 @@ class InterfaceItem
|
||||
template<typename T, size_t N>
|
||||
void addAttribute(AttributeName name, const std::array<T, N>& values)
|
||||
{
|
||||
addAttribute(name, tcb::span<const T, N>{values.data(), values.size()});
|
||||
addAttribute(name, std::span<const T, N>{values.data(), values.size()});
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@ -88,7 +88,7 @@ class InterfaceItem
|
||||
}
|
||||
|
||||
template<typename T, size_t N>
|
||||
void setAttribute(AttributeName name, tcb::span<const T, N> values)
|
||||
void setAttribute(AttributeName name, std::span<const T, N> values)
|
||||
{
|
||||
assert(m_attributes.find(name) != m_attributes.end());
|
||||
static_cast<SpanAttribute<T, N>*>(m_attributes[name].get())->setValues(values);
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2023 Reinder Feenstra
|
||||
* Copyright (C) 2019-2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -47,7 +47,7 @@ class Method<R(A...)> : public AbstractMethod
|
||||
|
||||
R operator()(A... args);
|
||||
|
||||
tcb::span<const TypeInfo> argumentTypeInfo() const final;
|
||||
std::span<const TypeInfo> argumentTypeInfo() const final;
|
||||
|
||||
TypeInfo resultTypeInfo() const final;
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2023 Reinder Feenstra
|
||||
* Copyright (C) 2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -115,7 +115,7 @@ R Method<R(A...)>::operator()(A... args)
|
||||
}
|
||||
|
||||
template<class R, class... A>
|
||||
tcb::span<const TypeInfo> Method<R(A...)>::argumentTypeInfo() const
|
||||
std::span<const TypeInfo> Method<R(A...)>::argumentTypeInfo() const
|
||||
{
|
||||
return {typeInfoArray<A...>};
|
||||
}
|
||||
|
||||
@ -42,7 +42,7 @@ class ObjectList : public AbstractObjectList
|
||||
|
||||
protected:
|
||||
Items m_items;
|
||||
std::unordered_map<Object*, boost::signals2::connection> m_propertyChanged;
|
||||
std::unordered_map<Object*, boost::signals2::scoped_connection> m_propertyChanged;
|
||||
std::vector<ObjectListTableModel<T>*> m_models;
|
||||
|
||||
void deleteMethodHandler(const std::shared_ptr<T>& object)
|
||||
@ -121,12 +121,6 @@ class ObjectList : public AbstractObjectList
|
||||
static_assert(std::is_base_of_v<Object, T>);
|
||||
}
|
||||
|
||||
~ObjectList()
|
||||
{
|
||||
for(auto& it : m_propertyChanged)
|
||||
it.second.disconnect();
|
||||
}
|
||||
|
||||
inline const_iterator begin() const noexcept { return m_items.begin(); }
|
||||
inline const_iterator end() const noexcept { return m_items.end(); }
|
||||
inline const std::shared_ptr<T>& front() const noexcept { return m_items.front(); }
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021,2023 Reinder Feenstra
|
||||
* Copyright (C) 2021,2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -159,7 +159,7 @@ class ObjectVectorProperty : public AbstractObjectVectorProperty
|
||||
m_values = std::move(values);
|
||||
}
|
||||
|
||||
void loadObjects(tcb::span<ObjectPtr> values) final;
|
||||
void loadObjects(std::span<ObjectPtr> values) final;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021,2023 Reinder Feenstra
|
||||
* Copyright (C) 2021,2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -47,7 +47,7 @@ void ObjectVectorProperty<T>::setObject(size_t index, const ObjectPtr& value)
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void ObjectVectorProperty<T>::loadObjects(tcb::span<ObjectPtr> values)
|
||||
void ObjectVectorProperty<T>::loadObjects(std::span<ObjectPtr> values)
|
||||
{
|
||||
std::vector<std::shared_ptr<T>> objects;
|
||||
objects.reserve(values.size());
|
||||
|
||||
@ -40,8 +40,3 @@ SerialDeviceProperty::SerialDeviceProperty(Object* object, std::string_view name
|
||||
static_cast<VectorRefAttribute<std::string>&>(*it->second).internalChanged();
|
||||
});
|
||||
}
|
||||
|
||||
SerialDeviceProperty::~SerialDeviceProperty()
|
||||
{
|
||||
m_serialPortListChanged.disconnect();
|
||||
}
|
||||
|
||||
@ -29,11 +29,11 @@
|
||||
class SerialDeviceProperty final : public Property<std::string>
|
||||
{
|
||||
private:
|
||||
boost::signals2::connection m_serialPortListChanged;
|
||||
boost::signals2::scoped_connection m_serialPortListChanged;
|
||||
|
||||
public:
|
||||
SerialDeviceProperty(Object* object, std::string_view name, const std::string& value, PropertyFlags flags);
|
||||
~SerialDeviceProperty() final;
|
||||
~SerialDeviceProperty() final = default;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020,2023-2024 Reinder Feenstra
|
||||
* Copyright (C) 2019-2020,2023-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -23,15 +23,15 @@
|
||||
#ifndef TRAINTASTIC_SERVER_CORE_SPANATTRIBUTE_HPP
|
||||
#define TRAINTASTIC_SERVER_CORE_SPANATTRIBUTE_HPP
|
||||
|
||||
#include <tcb/span.hpp>
|
||||
#include <span>
|
||||
#include "abstractvaluesattribute.hpp"
|
||||
#include "to.hpp"
|
||||
|
||||
template<typename T, size_t N = tcb::dynamic_extent>
|
||||
template<typename T, size_t N = std::dynamic_extent>
|
||||
class SpanAttribute : public AbstractValuesAttribute
|
||||
{
|
||||
public:
|
||||
typedef tcb::span<const T, N> Span;
|
||||
typedef std::span<const T, N> Span;
|
||||
|
||||
protected:
|
||||
Span m_values;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2024 Reinder Feenstra
|
||||
* Copyright (C) 2024-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -27,7 +27,7 @@
|
||||
constexpr std::array<double, 1> aliasKeys = {{SpeedLimitProperty::noLimitValue}};
|
||||
inline static const std::array<std::string, 1> aliasValues{{"$speed_limit_property:no_limit$"}};
|
||||
|
||||
static tcb::span<const double> getValues(SpeedUnit unit)
|
||||
static std::span<const double> getValues(SpeedUnit unit)
|
||||
{
|
||||
switch(unit)
|
||||
{
|
||||
@ -97,7 +97,7 @@ void SpeedLimitProperty::addAttributes()
|
||||
auto values = getValues(unit());
|
||||
::Attributes::addMin(*this, values.front());
|
||||
::Attributes::addValues(*this, values);
|
||||
::Attributes::addAliases(*this, tcb::span<const double>(aliasKeys), tcb::span<const std::string>(aliasValues));
|
||||
::Attributes::addAliases(*this, std::span<const double>(aliasKeys), std::span<const std::string>(aliasValues));
|
||||
m_attributeUnit = m_unit;
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021,2023-2024 Reinder Feenstra
|
||||
* Copyright (C) 2021,2023-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -332,7 +332,7 @@ class VectorProperty : public AbstractVectorProperty
|
||||
throw conversion_error();
|
||||
}
|
||||
|
||||
void loadObjects(tcb::span<ObjectPtr> /*values*/) final
|
||||
void loadObjects(std::span<ObjectPtr> /*values*/) final
|
||||
{
|
||||
throw conversion_error();
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2023 Reinder Feenstra
|
||||
* Copyright (C) 2019-2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -125,7 +125,7 @@ Decoder::Decoder(World& world, std::string_view _id) :
|
||||
m_interfaceItems.add(interface);
|
||||
|
||||
Attributes::addEnabled(protocol, false);
|
||||
Attributes::addValues(protocol, tcb::span<const DecoderProtocol>{});
|
||||
Attributes::addValues(protocol, std::span<const DecoderProtocol>{});
|
||||
Attributes::addVisible(protocol, false);
|
||||
m_interfaceItems.add(protocol);
|
||||
|
||||
@ -151,7 +151,7 @@ Decoder::Decoder(World& world, std::string_view _id) :
|
||||
|
||||
Attributes::addDisplayName(speedSteps, DisplayName::Hardware::speedSteps);
|
||||
Attributes::addEnabled(speedSteps, false);
|
||||
Attributes::addValues(speedSteps, tcb::span<const uint8_t>{});
|
||||
Attributes::addValues(speedSteps, std::span<const uint8_t>{});
|
||||
Attributes::addVisible(speedSteps, false);
|
||||
m_interfaceItems.add(speedSteps);
|
||||
|
||||
@ -193,10 +193,15 @@ void Decoder::loaded()
|
||||
|
||||
bool Decoder::hasFunction(uint32_t number) const
|
||||
{
|
||||
for(const auto& f : *functions)
|
||||
if(f->number == number)
|
||||
return true;
|
||||
return false;
|
||||
return std::any_of(functions->begin(), functions->end(),
|
||||
[number](const auto& f)
|
||||
{
|
||||
return f->number == number;
|
||||
});
|
||||
//for(const auto& f : *functions)
|
||||
// if(f->number == number)
|
||||
// return true;
|
||||
//return false;
|
||||
}
|
||||
|
||||
std::shared_ptr<const DecoderFunction> Decoder::getFunction(uint32_t number) const
|
||||
@ -376,7 +381,7 @@ void Decoder::protocolChanged()
|
||||
checkSpeedSteps();
|
||||
}
|
||||
else
|
||||
Attributes::setValues(speedSteps, tcb::span<const uint8_t>{});
|
||||
Attributes::setValues(speedSteps, std::span<const uint8_t>{});
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -390,7 +395,7 @@ bool Decoder::checkProtocol()
|
||||
{
|
||||
const auto protocols = protocol.getSpanAttribute<DecoderProtocol>(AttributeName::Values).values();
|
||||
assert(!protocols.empty());
|
||||
if(auto it = std::find(protocols.begin(), protocols.end(), protocol); it == protocols.end())
|
||||
if(const auto it = std::find(protocols.begin(), protocols.end(), protocol); it == protocols.end())
|
||||
{
|
||||
protocol = protocols.front();
|
||||
return true;
|
||||
@ -413,7 +418,7 @@ bool Decoder::checkAddress()
|
||||
bool Decoder::checkSpeedSteps()
|
||||
{
|
||||
const auto values = speedSteps.getSpanAttribute<uint8_t>(AttributeName::Values).values();
|
||||
if(auto it = std::find(values.begin(), values.end(), speedSteps); it == values.end())
|
||||
if(const auto it = std::find(values.begin(), values.end(), speedSteps); it == values.end())
|
||||
{
|
||||
speedSteps = values.back();
|
||||
return true;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021-2023 Reinder Feenstra
|
||||
* Copyright (C) 2021-2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -65,7 +65,7 @@ std::pair<uint16_t, uint16_t> DecoderController::decoderAddressMinMax(DecoderPro
|
||||
return noAddressMinMax;
|
||||
}
|
||||
|
||||
tcb::span<const uint8_t> DecoderController::decoderSpeedSteps(DecoderProtocol protocol) const
|
||||
std::span<const uint8_t> DecoderController::decoderSpeedSteps(DecoderProtocol protocol) const
|
||||
{
|
||||
static constexpr std::array<uint8_t, 3> dccSpeedSteps{{14, 28, 128}};
|
||||
static constexpr std::array<uint8_t, 3> motorolaSpeedSteps{{14, 27, 28}};
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021-2023 Reinder Feenstra
|
||||
* Copyright (C) 2021-2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -26,7 +26,7 @@
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <tcb/span.hpp>
|
||||
#include <span>
|
||||
#include "../../core/objectproperty.hpp"
|
||||
|
||||
#ifdef interface
|
||||
@ -70,7 +70,7 @@ class DecoderController
|
||||
|
||||
//! \brief Get supported protocols
|
||||
//! \return Supported protocols, may not be empty and must be constant for the instance!
|
||||
virtual tcb::span<const DecoderProtocol> decoderProtocols() const = 0;
|
||||
virtual std::span<const DecoderProtocol> decoderProtocols() const = 0;
|
||||
|
||||
//! \brief Get address range for given protocol
|
||||
//! \param[in] protocol The decoder protocol
|
||||
@ -80,7 +80,7 @@ class DecoderController
|
||||
//! \brief Get speed step options for given protocol
|
||||
//! \param[in] protocol The decoder protocol
|
||||
//! \return Speed step options for the given protocol
|
||||
virtual tcb::span<const uint8_t> decoderSpeedSteps(DecoderProtocol protocol) const;
|
||||
virtual std::span<const uint8_t> decoderSpeedSteps(DecoderProtocol protocol) const;
|
||||
|
||||
[[nodiscard]] bool addDecoder(Decoder& decoder);
|
||||
[[nodiscard]] bool removeDecoder(Decoder& decoder);
|
||||
|
||||
@ -44,7 +44,7 @@ DecoderFunctions::DecoderFunctions(Object& _parent, std::string_view parentPrope
|
||||
}
|
||||
|
||||
auto function = std::make_shared<DecoderFunction>(decoder, number);
|
||||
function->name = "F" + std::to_string(number);
|
||||
function->name = std::to_string(number).insert(0, 1, 'F');
|
||||
function->number = number;
|
||||
if(number == 0) // F0 is (almost) always the light function
|
||||
function->function = DecoderFunctionFunction::Light;
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2021,2023 Reinder Feenstra
|
||||
* Copyright (C) 2019-2021,2023,2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -49,6 +49,11 @@ class DecoderFunctions : public SubObject
|
||||
|
||||
inline const_iterator begin() const { return items.begin(); }
|
||||
inline const_iterator end() const { return items.end(); }
|
||||
|
||||
inline bool empty() const
|
||||
{
|
||||
return items.empty();
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2021-2024 Reinder Feenstra
|
||||
* Copyright (C) 2021-2025 Reinder Feenstra
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License
|
||||
@ -127,10 +127,10 @@ DCCEXInterface::DCCEXInterface(World& world, std::string_view _id)
|
||||
updateVisible();
|
||||
}
|
||||
|
||||
tcb::span<const DecoderProtocol> DCCEXInterface::decoderProtocols() const
|
||||
std::span<const DecoderProtocol> DCCEXInterface::decoderProtocols() const
|
||||
{
|
||||
static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
|
||||
return tcb::span<const DecoderProtocol>{protocols.data(), protocols.size()};
|
||||
return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
|
||||
}
|
||||
|
||||
std::pair<uint16_t, uint16_t> DCCEXInterface::decoderAddressMinMax(DecoderProtocol protocol) const
|
||||
@ -140,13 +140,13 @@ std::pair<uint16_t, uint16_t> DCCEXInterface::decoderAddressMinMax(DecoderProtoc
|
||||
return DecoderController::decoderAddressMinMax(protocol);
|
||||
}
|
||||
|
||||
tcb::span<const uint8_t> DCCEXInterface::decoderSpeedSteps(DecoderProtocol protocol) const
|
||||
std::span<const uint8_t> DCCEXInterface::decoderSpeedSteps(DecoderProtocol protocol) const
|
||||
{
|
||||
(void)protocol; // silence unused warning for release build
|
||||
assert(protocol == DecoderProtocol::DCCShort || protocol == DecoderProtocol::DCCLong);
|
||||
const auto& speedStepValues = DCCEX::Settings::speedStepValues;
|
||||
// find value in array so we can create a span, using a span of a variable won't work due to the compare with prevous value in the attribute setter
|
||||
if(auto it = std::find(speedStepValues.begin(), speedStepValues.end(), dccex->speedSteps); it != speedStepValues.end()) /*[[likely]]/*/
|
||||
if(const auto it = std::find(speedStepValues.begin(), speedStepValues.end(), dccex->speedSteps); it != speedStepValues.end()) /*[[likely]]/*/ // NOLINT(readability-qualified-auto) windows requires const auto
|
||||
return {&(*it), 1};
|
||||
assert(false);
|
||||
return {};
|
||||
@ -158,7 +158,7 @@ void DCCEXInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags c
|
||||
m_kernel->decoderChanged(decoder, changes, functionNumber);
|
||||
}
|
||||
|
||||
std::pair<uint32_t, uint32_t> DCCEXInterface::inputAddressMinMax(uint32_t) const
|
||||
std::pair<uint32_t, uint32_t> DCCEXInterface::inputAddressMinMax(uint32_t /*channel*/) const
|
||||
{
|
||||
return {DCCEX::Kernel::idMin, DCCEX::Kernel::idMax};
|
||||
}
|
||||
@ -169,7 +169,7 @@ void DCCEXInterface::inputSimulateChange(uint32_t channel, uint32_t address, Sim
|
||||
m_kernel->simulateInputChange(address, action);
|
||||
}
|
||||
|
||||
tcb::span<const OutputChannel> DCCEXInterface::outputChannels() const
|
||||
std::span<const OutputChannel> DCCEXInterface::outputChannels() const
|
||||
{
|
||||
static const auto values = makeArray(OutputChannel::Accessory, OutputChannel::Turnout, OutputChannel::Output, OutputChannel::DCCext);
|
||||
return values;
|
||||
@ -373,7 +373,7 @@ void DCCEXInterface::check() const
|
||||
checkDecoder(*decoder);
|
||||
}
|
||||
|
||||
void DCCEXInterface::checkDecoder(const Decoder& decoder) const
|
||||
void DCCEXInterface::checkDecoder(const Decoder& decoder)
|
||||
{
|
||||
for(const auto& function : *decoder.functions)
|
||||
if(function->number > DCCEX::Config::functionNumberMax)
|
||||
|
||||
Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen
Laden…
x
In neuem Issue referenzieren
Einen Benutzer sperren