Merge remote-tracking branch 'origin/master' into c++20-server
Dieser Commit ist enthalten in:
Commit
989311e069
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}
|
||||
|
||||
196
.github/workflows/build.yml
vendored
196
.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"
|
||||
@ -60,16 +50,6 @@ jobs:
|
||||
build_deb: true
|
||||
defines: "-DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
|
||||
|
||||
- name: "macos-12"
|
||||
os: "macos-12"
|
||||
generator: "Unix Makefiles"
|
||||
arch: ""
|
||||
target: traintastic-client
|
||||
jobs: 3
|
||||
build_type: Release
|
||||
build_deb: false
|
||||
defines: ""
|
||||
|
||||
- name: "macos-13"
|
||||
os: "macos-13"
|
||||
generator: "Unix Makefiles"
|
||||
@ -80,16 +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: ""
|
||||
|
||||
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
|
||||
@ -157,22 +135,24 @@ jobs:
|
||||
# Windows only:
|
||||
- name: Upload artifact
|
||||
if: startswith(matrix.config.os, 'windows')
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: traintastic-client
|
||||
name: traintastic-client-windows
|
||||
path: ${{github.workspace}}/client/build/${{matrix.config.build_type}}
|
||||
|
||||
# Linux only:
|
||||
- name: Upload debian package artifact
|
||||
if: matrix.config.build_deb
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: traintastic-client-deb
|
||||
name: traintastic-client-deb-${{matrix.config.name}}
|
||||
path: ${{github.workspace}}/client/build/*.deb
|
||||
|
||||
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"
|
||||
@ -261,18 +217,6 @@ jobs:
|
||||
defines: "-DINSTALL_SYSTEMD_SERVICE=ON -DCMAKE_CXX_COMPILER_LAUNCHER=ccache"
|
||||
ccov: false
|
||||
|
||||
- name: "macos-12"
|
||||
os: "macos-12"
|
||||
generator: "Unix Makefiles"
|
||||
arch: ""
|
||||
toolset: ""
|
||||
target: all
|
||||
jobs: 3
|
||||
build_type: Release
|
||||
build_deb: false
|
||||
defines: ""
|
||||
ccov: false
|
||||
|
||||
- name: "macos-13"
|
||||
os: "macos-13"
|
||||
generator: "Unix Makefiles"
|
||||
@ -285,18 +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
|
||||
|
||||
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')
|
||||
@ -314,12 +267,12 @@ jobs:
|
||||
# Ubuntu only:
|
||||
- name: Install packages
|
||||
if: startswith(matrix.config.os, 'ubuntu')
|
||||
run: sudo apt install libboost-program-options-dev liblua5.3-dev lcov libarchive-dev clang-tidy libsystemd-dev
|
||||
run: sudo apt install libboost-program-options-dev liblua5.4-dev lcov libarchive-dev clang-tidy libsystemd-dev
|
||||
|
||||
# MacOS only:
|
||||
- name: Install brew packages
|
||||
if: startswith(matrix.config.os, 'macos')
|
||||
run: brew install libarchive lua@5.3
|
||||
run: brew install libarchive lua@5.4
|
||||
|
||||
# All:
|
||||
- name: Create Build Environment
|
||||
@ -366,17 +319,17 @@ jobs:
|
||||
# Windows only:
|
||||
- name: Upload artifact
|
||||
if: matrix.config.name == 'windows_x64_clang'
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: traintastic-server
|
||||
name: traintastic-server-windows
|
||||
path: ${{github.workspace}}/server/build/Release/traintastic-server.exe
|
||||
|
||||
# Linux only:
|
||||
- name: Upload debian package artifact
|
||||
if: matrix.config.build_deb
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: traintastic-server-deb
|
||||
name: traintastic-server-deb-${{matrix.config.name}}
|
||||
path: ${{github.workspace}}/server/build/*.deb
|
||||
|
||||
# Code coverage:
|
||||
@ -395,7 +348,7 @@ jobs:
|
||||
|
||||
- name: "Code coverage: upload artifact"
|
||||
if: matrix.config.ccov
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: traintastic-server-test-code-coverage
|
||||
path: ${{github.workspace}}/server/build/ccov/*
|
||||
@ -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: ""
|
||||
@ -429,7 +378,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: "Download artifact: lang"
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-lang
|
||||
path: ${{github.workspace}}/shared/translations
|
||||
@ -447,9 +396,9 @@ jobs:
|
||||
run: cpack
|
||||
|
||||
- name: Upload debian package artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: traintastic-data-deb
|
||||
name: traintastic-data-deb-${{matrix.config.name}}
|
||||
path: ${{github.workspace}}/shared/build/*.deb
|
||||
|
||||
build-lang:
|
||||
@ -469,7 +418,7 @@ jobs:
|
||||
run: python3 json2lang.py
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: traintastic-lang
|
||||
path: ${{github.workspace}}/shared/translations/*.lang
|
||||
@ -494,7 +443,7 @@ jobs:
|
||||
run: ./builddoc.py html-single-page --output-dir build
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: traintastic-manual
|
||||
path: ${{github.workspace}}/manual/build/*
|
||||
@ -516,7 +465,7 @@ jobs:
|
||||
run: ./buildluadoc.py
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: traintastic-manual-lua
|
||||
path: ${{github.workspace}}/manual/build.luadoc/*
|
||||
@ -535,31 +484,31 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Download artifacts 1/2
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-client
|
||||
name: traintastic-client-windows
|
||||
path: ${{github.workspace}}/client/build/Release
|
||||
|
||||
- name: Download artifacts 2/2
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-server
|
||||
name: traintastic-server-windows
|
||||
path: ${{github.workspace}}/server/build
|
||||
|
||||
- name: "Download artifact: lang"
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-lang
|
||||
path: ${{github.workspace}}/shared/translations
|
||||
|
||||
- name: "Download artifact: manual"
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-manual
|
||||
path: ${{github.workspace}}/manual/build
|
||||
|
||||
- name: "Download artifact: manual-lua"
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-manual-lua
|
||||
path: ${{github.workspace}}/manual/build.luadoc
|
||||
@ -570,7 +519,7 @@ jobs:
|
||||
"C:/Program Files (x86)/Inno Setup 6/ISCC.exe" %GITHUB_WORKSPACE%/package/innosetup/traintastic.iss
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: package-innosetup
|
||||
path: ${{github.workspace}}/package/innosetup/output
|
||||
@ -585,43 +534,46 @@ jobs:
|
||||
- uses: FranzDiebold/github-env-vars-action@v2
|
||||
|
||||
- name: Download artifacts 1/6
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: package-innosetup
|
||||
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
|
||||
|
||||
- name: Download artifacts 2/6
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-client-deb
|
||||
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@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-server-deb
|
||||
pattern: traintastic-server-deb-*
|
||||
merge-multiple: true
|
||||
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
|
||||
|
||||
- name: Download artifacts 4/6
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-server-test-code-coverage
|
||||
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}/ccov
|
||||
|
||||
- name: Download artifacts 5/6
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-manual
|
||||
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}/manual
|
||||
|
||||
- name: Download artifacts 6/6
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-data-deb
|
||||
pattern: traintastic-data-deb-*
|
||||
merge-multiple: true
|
||||
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
|
||||
|
||||
- name: "Download artifact: manual-lua"
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: traintastic-manual-lua
|
||||
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}/manual-lua
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,3 +1,6 @@
|
||||
[submodule "shared/data/lncv"]
|
||||
path = shared/data/lncv
|
||||
url = ../lncv.git
|
||||
[submodule "server/thirdparty/catch2"]
|
||||
path = server/thirdparty/catch2
|
||||
url = https://github.com/catchorg/Catch2.git
|
||||
|
||||
@ -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()
|
||||
@ -130,6 +130,11 @@ install(TARGETS traintastic-client
|
||||
RUNTIME DESTINATION "/opt/traintastic/bin/"
|
||||
)
|
||||
|
||||
if(LINUX)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/debian/traintastic-client.desktop DESTINATION share/applications)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/debian/traintastic_256.png DESTINATION "/opt/traintastic/icons/")
|
||||
endif()
|
||||
|
||||
### BUILD TYPE ###
|
||||
|
||||
if (CMAKE_BUILD_TYPE EQUAL "Release")
|
||||
|
||||
@ -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",
|
||||
|
||||
8
client/debian/traintastic-client.desktop
Normale Datei
8
client/debian/traintastic-client.desktop
Normale Datei
@ -0,0 +1,8 @@
|
||||
[Desktop Entry]
|
||||
Type=Application
|
||||
Name=Traintastic client
|
||||
Comment=Model railroad control and automation software
|
||||
Exec=/opt/traintastic/bin/traintastic-client
|
||||
Icon=/opt/traintastic/icons/traintastic_256.png
|
||||
Terminal=false
|
||||
Categories=Science;Engineering;
|
||||
BIN
client/debian/traintastic_256.png
Normale Datei
BIN
client/debian/traintastic_256.png
Normale Datei
Binäre Datei nicht angezeigt.
|
Nachher Breite: | Höhe: | Größe: 7.6 KiB |
101
client/gfx/dark/clear_persistent_variables.svg
Normale Datei
101
client/gfx/dark/clear_persistent_variables.svg
Normale Datei
@ -0,0 +1,101 @@
|
||||
<?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.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="clear_persistent_variables.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="58.303571"
|
||||
inkscape:cy="31.785714"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1015"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<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)">
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:1.5875;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 3.175,292.76665 -10e-8,-16.93334 c 2.0852342,1e-5 3.0686586,0 4.2333333,0 4.2333328,2.11667 4.2333328,6.35 0,9.525 H 3.1749999"
|
||||
id="path3223"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#ffffff;stroke-width:1.5875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 13.758333,275.83331 17.991666,292.76665 22.225,275.83331"
|
||||
id="path4097"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#cd5c5c;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 2.1166666,294.88331 23.283333,273.71665"
|
||||
id="path1540"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#cd5c5c;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 2.1166666,273.71665 23.283333,294.88331"
|
||||
id="path1542"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Nachher Breite: | Höhe: | Größe: 3.7 KiB |
@ -92,5 +92,7 @@
|
||||
<file>board_tile.rail.nx_button.svg</file>
|
||||
<file>board_tile.misc.switch.svg</file>
|
||||
<file>board_tile.misc.label.svg</file>
|
||||
<file>clear_persistent_variables.svg</file>
|
||||
<file>swap.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 |
102
client/gfx/light/clear_persistent_variables.svg
Normale Datei
102
client/gfx/light/clear_persistent_variables.svg
Normale Datei
@ -0,0 +1,102 @@
|
||||
<?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.1.2 (0a00cf5339, 2022-02-04)"
|
||||
sodipodi:docname="clear_persistent_variables.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="58.303571"
|
||||
inkscape:cy="31.785714"
|
||||
inkscape:document-units="mm"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="true"
|
||||
units="px"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1015"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:pagecheckerboard="0">
|
||||
<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" />
|
||||
<dc:title />
|
||||
<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)">
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="m 3.175,292.76665 -10e-8,-16.93334 c 2.0852342,1e-5 3.0686586,0 4.2333333,0 4.2333328,2.11667 4.2333328,6.35 0,9.525 H 3.1749999"
|
||||
id="path3223"
|
||||
sodipodi:nodetypes="ccccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#000000;stroke-width:1.5875;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 13.758333,275.83331 17.991666,292.76665 22.225,275.83331"
|
||||
id="path4097"
|
||||
sodipodi:nodetypes="ccc" />
|
||||
<path
|
||||
style="fill:none;stroke:#b00020;stroke-width:2.11666667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 2.1166666,294.88331 23.283333,273.71665"
|
||||
id="path1540"
|
||||
sodipodi:nodetypes="cc" />
|
||||
<path
|
||||
style="fill:none;stroke:#b00020;stroke-width:2.11666667;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 2.1166666,273.71665 23.283333,294.88331"
|
||||
id="path1542"
|
||||
sodipodi:nodetypes="cc" />
|
||||
</g>
|
||||
</svg>
|
||||
|
Nachher Breite: | Höhe: | Größe: 3.7 KiB |
@ -66,5 +66,6 @@
|
||||
<file>board_tile.rail.nx_button.svg</file>
|
||||
<file>board_tile.misc.switch.svg</file>
|
||||
<file>board_tile.misc.label.svg</file>
|
||||
<file>clear_persistent_variables.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -32,6 +32,7 @@
|
||||
#include "getboardcolorscheme.hpp"
|
||||
#include "tilepainter.hpp"
|
||||
#include "../network/board.hpp"
|
||||
#include "../network/callmethod.hpp"
|
||||
#include "../network/object.tpp"
|
||||
#include "../network/object/blockrailtile.hpp"
|
||||
#include "../network/object/nxbuttonrailtile.hpp"
|
||||
@ -39,6 +40,7 @@
|
||||
#include "../network/abstractvectorproperty.hpp"
|
||||
#include "../utils/enum.hpp"
|
||||
#include "../utils/rectf.hpp"
|
||||
#include "../misc/mimedata.hpp"
|
||||
#include "../settings/boardsettings.hpp"
|
||||
|
||||
QRect rectToViewport(const QRect& r, const int gridSize)
|
||||
@ -64,14 +66,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_mouseLeftButtonPressed{false},
|
||||
@ -84,6 +86,7 @@ BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
|
||||
{
|
||||
setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setAcceptDrops(true);
|
||||
|
||||
if(Q_LIKELY(m_boardLeft))
|
||||
connect(m_boardLeft, &AbstractProperty::valueChanged, this, &BoardAreaWidget::updateMinimumSize);
|
||||
@ -96,7 +99,7 @@ BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
|
||||
|
||||
connect(&BoardSettings::instance(), &SettingsBase::changed, this, &BoardAreaWidget::settingsChanged);
|
||||
|
||||
for(const auto& [l, object] : m_board.board().tileObjects())
|
||||
for(const auto& [l, object] : m_board->tileObjects())
|
||||
tileObjectAdded(l.x, l.y, object);
|
||||
|
||||
settingsChanged();
|
||||
@ -112,7 +115,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(...)
|
||||
@ -127,7 +130,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:
|
||||
@ -192,6 +195,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;
|
||||
|
||||
@ -307,7 +311,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;
|
||||
@ -315,7 +319,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;
|
||||
@ -323,7 +327,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;
|
||||
@ -331,7 +335,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;
|
||||
@ -339,7 +343,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;
|
||||
@ -347,7 +351,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;
|
||||
@ -355,7 +359,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);
|
||||
}
|
||||
@ -364,7 +368,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();
|
||||
}
|
||||
@ -379,11 +383,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]]*/
|
||||
@ -395,7 +399,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]]*/
|
||||
@ -407,7 +411,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]]*/
|
||||
@ -419,7 +423,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]]*/
|
||||
@ -431,7 +435,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]]*/
|
||||
@ -443,7 +447,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]]*/
|
||||
@ -455,7 +459,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]]*/
|
||||
@ -480,7 +484,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>";
|
||||
}
|
||||
@ -667,7 +671,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())
|
||||
{
|
||||
@ -727,7 +731,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
break;
|
||||
|
||||
case TileId::RailBlock:
|
||||
tilePainter.drawBlock(id, r, a, state & 0x01, state & 0x02, m_board.board().getTileObject(it.first));
|
||||
tilePainter.drawBlock(id, r, a, state & 0x01, state & 0x02, m_board->getTileObject(it.first));
|
||||
break;
|
||||
|
||||
case TileId::RailDirectionControl:
|
||||
@ -748,7 +752,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"),
|
||||
@ -763,7 +767,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),
|
||||
@ -837,6 +841,65 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
|
||||
}
|
||||
}
|
||||
|
||||
void BoardAreaWidget::dragEnterEvent(QDragEnterEvent *event)
|
||||
{
|
||||
if(event->mimeData()->hasFormat(AssignTrainMimeData::mimeType))
|
||||
{
|
||||
m_dragMoveTileLocation = TileLocation::invalid;
|
||||
event->acceptProposedAction();
|
||||
}
|
||||
}
|
||||
|
||||
void BoardAreaWidget::dragMoveEvent(QDragMoveEvent* event)
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
const TileLocation l = pointToTileLocation(event->pos());
|
||||
#else
|
||||
const TileLocation l = pointToTileLocation(event->position().toPoint());
|
||||
#endif
|
||||
|
||||
if(m_dragMoveTileLocation != l)
|
||||
{
|
||||
m_dragMoveTileLocation = l;
|
||||
if(event->mimeData()->hasFormat(AssignTrainMimeData::mimeType) &&
|
||||
m_board->getTileId(l) == TileId::RailBlock)
|
||||
{
|
||||
return event->accept();
|
||||
}
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void BoardAreaWidget::dropEvent(QDropEvent* event)
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
const TileLocation l = pointToTileLocation(event->pos());
|
||||
#else
|
||||
const TileLocation l = pointToTileLocation(event->position().toPoint());
|
||||
#endif
|
||||
|
||||
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->getTileObject(l)))
|
||||
{
|
||||
if(auto* method = tile->getMethod("assign_train"))
|
||||
{
|
||||
callMethod(*method, nullptr, assignTrain->trainId());
|
||||
return event->accept();
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
event->ignore();
|
||||
}
|
||||
|
||||
void BoardAreaWidget::settingsChanged()
|
||||
{
|
||||
const auto& s = BoardSettings::instance();
|
||||
|
||||
@ -38,7 +38,7 @@
|
||||
#include "../network/abstractproperty.hpp"
|
||||
#include "../network/objectptr.hpp"
|
||||
|
||||
class BoardWidget;
|
||||
class Board;
|
||||
|
||||
class BoardAreaWidget : public QWidget
|
||||
{
|
||||
@ -66,7 +66,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;
|
||||
@ -89,6 +89,8 @@ class BoardAreaWidget : public QWidget
|
||||
uint8_t m_mouseMoveTileWidthMax;
|
||||
uint8_t m_mouseMoveTileHeightMax;
|
||||
|
||||
TileLocation m_dragMoveTileLocation;
|
||||
|
||||
inline int boardLeft() const { return Q_LIKELY(m_boardLeft) ? m_boardLeft->toInt() - boardMargin : 0; }
|
||||
inline int boardTop() const { return Q_LIKELY(m_boardTop) ? m_boardTop->toInt() - boardMargin: 0; }
|
||||
inline int boardRight() const { return Q_LIKELY(m_boardRight) ? m_boardRight->toInt() + boardMargin: 0; }
|
||||
@ -115,6 +117,9 @@ class BoardAreaWidget : public QWidget
|
||||
void mouseMoveEvent(QMouseEvent* event) final;
|
||||
void wheelEvent(QWheelEvent* event) final;
|
||||
void paintEvent(QPaintEvent* event) final;
|
||||
void dragEnterEvent(QDragEnterEvent* event) final;
|
||||
void dragMoveEvent(QDragMoveEvent* event) final;
|
||||
void dropEvent(QDropEvent* event) final;
|
||||
|
||||
protected slots:
|
||||
void settingsChanged();
|
||||
@ -124,7 +129,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();
|
||||
|
||||
@ -34,7 +34,6 @@
|
||||
#include <QApplication>
|
||||
#include <QKeyEvent>
|
||||
#include <QPainter>
|
||||
#include <QTimer>
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
#include "getboardcolorscheme.hpp"
|
||||
#include "tilepainter.hpp"
|
||||
@ -84,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)},
|
||||
@ -92,6 +91,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
m_editActions{new QActionGroup(this)}
|
||||
, m_tileMoveStarted{false}
|
||||
, m_tileResizeStarted{false}
|
||||
, m_nxButtonTimerId(0)
|
||||
{
|
||||
setWindowIcon(Theme::getIconForClassId(object->classId));
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
@ -447,6 +447,8 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
|
||||
|
||||
BoardWidget::~BoardWidget()
|
||||
{
|
||||
stopTimerAndReleaseButtons();
|
||||
|
||||
if(m_nxManagerRequestId != Connection::invalidRequestId)
|
||||
{
|
||||
m_object->connection()->cancelRequest(m_nxManagerRequestId);
|
||||
@ -459,6 +461,10 @@ void BoardWidget::worldEditChanged(bool value)
|
||||
m_editActionNone->activate(QAction::Trigger);
|
||||
m_toolbarEdit->setVisible(value);
|
||||
m_statusBar->setVisible(value);
|
||||
|
||||
// Stop timers in edit mode
|
||||
if(value)
|
||||
stopTimerAndReleaseButtons();
|
||||
}
|
||||
|
||||
void BoardWidget::gridChanged(BoardAreaWidget::Grid value)
|
||||
@ -650,7 +656,8 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
|
||||
|
||||
if(nxButton->isPressed())
|
||||
{
|
||||
releaseNXButton(nxButton);
|
||||
stopTimerAndReleaseButtons();
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -669,31 +676,13 @@ void BoardWidget::tileClicked(int16_t x, int16_t y)
|
||||
|
||||
m_nxButtonPressed.reset();
|
||||
|
||||
QTimer::singleShot(nxButtonReleaseDelay, this,
|
||||
[this, weak1=std::weak_ptr<NXButtonRailTile>(firstButton), weak2=std::weak_ptr<NXButtonRailTile>(nxButton)]()
|
||||
{
|
||||
if(auto btn = weak1.lock())
|
||||
{
|
||||
releaseNXButton(btn);
|
||||
}
|
||||
if(auto btn = weak2.lock())
|
||||
{
|
||||
releaseNXButton(btn);
|
||||
}
|
||||
});
|
||||
startReleaseTimer(firstButton, nxButton);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_nxButtonPressed = nxButton;
|
||||
|
||||
QTimer::singleShot(nxButtonHoldTime, this,
|
||||
[this, weak=std::weak_ptr<NXButtonRailTile>(nxButton)]()
|
||||
{
|
||||
if(auto btn = weak.lock())
|
||||
{
|
||||
releaseNXButton(btn);
|
||||
}
|
||||
});
|
||||
startHoldTimer(nxButton);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -778,6 +767,53 @@ void BoardWidget::rightClicked()
|
||||
rotateTile(true);
|
||||
}
|
||||
|
||||
void BoardWidget::startHoldTimer(const std::shared_ptr<NXButtonRailTile>& nxButton)
|
||||
{
|
||||
stopTimerAndReleaseButtons();
|
||||
|
||||
m_releaseButton1 = nxButton;
|
||||
|
||||
assert(m_nxButtonTimerId == 0);
|
||||
m_nxButtonTimerId = startTimer(nxButtonHoldTime);
|
||||
}
|
||||
|
||||
void BoardWidget::startReleaseTimer(const std::shared_ptr<NXButtonRailTile> &firstButton,
|
||||
const std::shared_ptr<NXButtonRailTile> &nxButton)
|
||||
{
|
||||
// Do not release first button yet
|
||||
m_releaseButton1.reset();
|
||||
|
||||
stopTimerAndReleaseButtons();
|
||||
|
||||
m_releaseButton1 = firstButton;
|
||||
m_releaseButton2 = nxButton;
|
||||
|
||||
assert(m_nxButtonTimerId == 0);
|
||||
m_nxButtonTimerId = startTimer(nxButtonReleaseDelay);
|
||||
}
|
||||
|
||||
void BoardWidget::stopTimerAndReleaseButtons()
|
||||
{
|
||||
if(m_nxButtonTimerId)
|
||||
{
|
||||
// Instantly release buttons
|
||||
if(auto btn = m_releaseButton1.lock())
|
||||
{
|
||||
releaseNXButton(btn);
|
||||
}
|
||||
m_releaseButton1.reset();
|
||||
|
||||
if(auto btn = m_releaseButton2.lock())
|
||||
{
|
||||
releaseNXButton(btn);
|
||||
}
|
||||
m_releaseButton2.reset();
|
||||
|
||||
killTimer(m_nxButtonTimerId);
|
||||
m_nxButtonTimerId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void BoardWidget::actionSelected(const Board::TileInfo* info)
|
||||
{
|
||||
m_boardArea->setMouseMoveAction(BoardAreaWidget::MouseMoveAction::None);
|
||||
@ -825,6 +861,17 @@ void BoardWidget::keyPressEvent(QKeyEvent* event)
|
||||
}
|
||||
}
|
||||
|
||||
void BoardWidget::timerEvent(QTimerEvent *e)
|
||||
{
|
||||
if(e->timerId() == m_nxButtonTimerId)
|
||||
{
|
||||
stopTimerAndReleaseButtons();
|
||||
return;
|
||||
}
|
||||
|
||||
QWidget::timerEvent(e);
|
||||
}
|
||||
|
||||
void BoardWidget::rotateTile(bool ccw)
|
||||
{
|
||||
if(m_tileRotates != 0)
|
||||
|
||||
@ -78,11 +78,25 @@ class BoardWidget : public QWidget
|
||||
TileRotate m_tileRotateLast = TileRotate::Deg0; //!< Last used tile rotate for add/move
|
||||
std::weak_ptr<NXButtonRailTile> m_nxButtonPressed;
|
||||
|
||||
int m_nxButtonTimerId;
|
||||
std::weak_ptr<NXButtonRailTile> m_releaseButton1;
|
||||
std::weak_ptr<NXButtonRailTile> m_releaseButton2;
|
||||
|
||||
void startHoldTimer(const std::shared_ptr<NXButtonRailTile> &nxButton);
|
||||
void startReleaseTimer(const std::shared_ptr<NXButtonRailTile> &firstButton,
|
||||
const std::shared_ptr<NXButtonRailTile> &nxButton);
|
||||
|
||||
void actionSelected(const Board::TileInfo* tile);
|
||||
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void timerEvent(QTimerEvent *e) override;
|
||||
|
||||
void rotateTile(bool ccw = false);
|
||||
void releaseNXButton(const std::shared_ptr<NXButtonRailTile>& nxButton);
|
||||
|
||||
protected slots:
|
||||
void stopTimerAndReleaseButtons();
|
||||
|
||||
protected slots:
|
||||
void worldEditChanged(bool value);
|
||||
void gridChanged(BoardAreaWidget::Grid value);
|
||||
|
||||
@ -39,7 +39,7 @@ std::unique_ptr<QMenu> TileMenu::getBlockRailTileMenu(const ObjectPtr& tile, QWi
|
||||
menu->addAction(new MethodAction(*assignTrain,
|
||||
[parent, assignTrain]()
|
||||
{
|
||||
std::make_unique<ObjectSelectListDialog>(*assignTrain, parent)->exec();
|
||||
std::make_unique<ObjectSelectListDialog>(*assignTrain, false, parent)->exec();
|
||||
}));
|
||||
if(auto* removeTrain = tile->getMethod("remove_train"))
|
||||
{
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -774,7 +775,7 @@ void TilePainter::drawTriangle(const QRectF& r)
|
||||
{r.right(), r.bottom()},
|
||||
{r.left(), r.bottom()}}};
|
||||
|
||||
m_painter.drawConvexPolygon(points.data(), points.size());
|
||||
m_painter.drawConvexPolygon(points.data(), static_cast<int>(points.size()));
|
||||
}
|
||||
|
||||
void TilePainter::drawLED(const QRectF& r, const QColor& color, const QColor& borderColor)
|
||||
|
||||
@ -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
|
||||
@ -150,8 +150,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 +232,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());
|
||||
}
|
||||
|
||||
|
||||
@ -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-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
|
||||
@ -34,19 +34,20 @@
|
||||
#include "../widget/alertwidget.hpp"
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
|
||||
ObjectSelectListDialog::ObjectSelectListDialog(Method& method, QWidget* parent) :
|
||||
ObjectSelectListDialog(static_cast<InterfaceItem&>(method), parent)
|
||||
ObjectSelectListDialog::ObjectSelectListDialog(Method& method, bool multiSelect, QWidget* parent) :
|
||||
ObjectSelectListDialog(static_cast<InterfaceItem&>(method), multiSelect, parent)
|
||||
{
|
||||
}
|
||||
|
||||
ObjectSelectListDialog::ObjectSelectListDialog(ObjectProperty& property, QWidget* parent) :
|
||||
ObjectSelectListDialog(static_cast<InterfaceItem&>(property), parent)
|
||||
ObjectSelectListDialog(static_cast<InterfaceItem&>(property), false, parent)
|
||||
{
|
||||
}
|
||||
|
||||
ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, QWidget* parent) :
|
||||
ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, bool multiSelect, QWidget* parent) :
|
||||
QDialog(parent, Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint),
|
||||
m_item{item},
|
||||
m_multiSelect{multiSelect},
|
||||
m_buttons{new QDialogButtonBox(this)},
|
||||
m_tableWidget{new TableWidget()}
|
||||
{
|
||||
@ -58,7 +59,14 @@ ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, QWidget* par
|
||||
connect(m_buttons->button(QDialogButtonBox::Ok), &QPushButton::clicked, this,
|
||||
[this]()
|
||||
{
|
||||
acceptRow(m_tableWidget->selectionModel()->selectedIndexes().first().row());
|
||||
if(m_multiSelect)
|
||||
{
|
||||
acceptRows(m_tableWidget->selectionModel()->selectedIndexes());
|
||||
}
|
||||
else
|
||||
{
|
||||
acceptRow(m_tableWidget->selectionModel()->selectedIndexes().first().row());
|
||||
}
|
||||
});
|
||||
m_buttons->button(QDialogButtonBox::Cancel)->setText(Locale::tr("qtapp.object_select_list_dialog:cancel"));
|
||||
connect(m_buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &ObjectSelectListDialog::reject);
|
||||
@ -91,7 +99,8 @@ ObjectSelectListDialog::ObjectSelectListDialog(InterfaceItem& item, QWidget* par
|
||||
connect(m_tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this,
|
||||
[this](const QItemSelection&, const QItemSelection&)
|
||||
{
|
||||
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(m_tableWidget->selectionModel()->selectedRows().count() == 1);
|
||||
const auto selectionCount = m_tableWidget->selectionModel()->selectedRows().count();
|
||||
m_buttons->button(QDialogButtonBox::Ok)->setEnabled(m_multiSelect ? selectionCount > 0 : selectionCount == 1);
|
||||
});
|
||||
connect(m_tableWidget, &TableWidget::doubleClicked, this,
|
||||
[this](const QModelIndex& index)
|
||||
@ -133,3 +142,16 @@ void ObjectSelectListDialog::acceptRow(int row)
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
void ObjectSelectListDialog::acceptRows(const QModelIndexList& indexes)
|
||||
{
|
||||
if(auto* m = dynamic_cast<Method*>(&m_item))
|
||||
{
|
||||
for(const auto& index : indexes)
|
||||
{
|
||||
callMethod(*m, nullptr, m_tableWidget->getRowObjectId(index.row()));
|
||||
}
|
||||
}
|
||||
|
||||
accept();
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* Copyright (C) 2019-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
|
||||
@ -24,6 +24,7 @@
|
||||
#define TRAINTASTIC_CLIENT_DIALOG_OBJECTSELECTLISTDIALOG_HPP
|
||||
|
||||
#include <QDialog>
|
||||
#include <QModelIndexList>
|
||||
#include "../network/objectptr.hpp"
|
||||
|
||||
class QDialogButtonBox;
|
||||
@ -39,17 +40,19 @@ class ObjectSelectListDialog : public QDialog
|
||||
|
||||
protected:
|
||||
InterfaceItem& m_item;
|
||||
const bool m_multiSelect;
|
||||
ObjectPtr m_object;
|
||||
int m_requestId;
|
||||
QDialogButtonBox* m_buttons;
|
||||
TableWidget* m_tableWidget;
|
||||
|
||||
ObjectSelectListDialog(InterfaceItem& item, QWidget* parent);
|
||||
ObjectSelectListDialog(InterfaceItem& item, bool multiSelect, QWidget* parent);
|
||||
|
||||
void acceptRow(int row);
|
||||
void acceptRows(const QModelIndexList& indexes);
|
||||
|
||||
public:
|
||||
ObjectSelectListDialog(Method& method, QWidget* parent);
|
||||
ObjectSelectListDialog(Method& method, bool multiSelect, QWidget* parent);
|
||||
ObjectSelectListDialog(ObjectProperty& property, QWidget* parent);
|
||||
};
|
||||
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -122,6 +122,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);
|
||||
|
||||
@ -171,7 +172,6 @@ MainWindow::MainWindow(QWidget* parent) :
|
||||
if(const ObjectPtr& traintastic = m_connection->traintastic())
|
||||
traintastic->callMethod("close_world");
|
||||
});
|
||||
m_actionCloseWorld->setShortcut(QKeySequence::Close);
|
||||
menu->addSeparator();
|
||||
m_actionImportWorld = menu->addAction(Theme::getIcon("world_import"), Locale::tr("qtapp.mainmenu:import_world") + "...",
|
||||
[this]()
|
||||
@ -510,7 +510,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);
|
||||
@ -667,7 +667,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();
|
||||
@ -738,13 +739,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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -761,13 +762,21 @@ void MainWindow::loadWorld()
|
||||
if(!m_connection)
|
||||
return;
|
||||
|
||||
std::unique_ptr<WorldListDialog> d = std::make_unique<WorldListDialog>(m_connection, this);
|
||||
if(d->exec() == QDialog::Accepted)
|
||||
{
|
||||
Method* method = m_connection->traintastic()->getMethod("load_world");
|
||||
if(Q_LIKELY(method))
|
||||
method->call(d->uuid());
|
||||
}
|
||||
m_loadWorldDialog = std::make_unique<WorldListDialog>(m_connection, this);
|
||||
connect(m_loadWorldDialog.get(), &WorldListDialog::finished,
|
||||
[this](int result)
|
||||
{
|
||||
if(result == QDialog::Accepted)
|
||||
{
|
||||
if(Method* method = m_connection->traintastic()->getMethod("load_world")) /*[[likely]]*/
|
||||
{
|
||||
method->call(m_loadWorldDialog->uuid());
|
||||
}
|
||||
}
|
||||
m_loadWorldDialog.release()->deleteLater();
|
||||
});
|
||||
m_loadWorldDialog->setModal(true);
|
||||
m_loadWorldDialog->show();
|
||||
}
|
||||
|
||||
void MainWindow::toggleFullScreen()
|
||||
@ -929,17 +938,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)
|
||||
@ -970,6 +980,10 @@ void MainWindow::connectionStateChanged()
|
||||
if(m_connection && m_connection->state() == Connection::State::Disconnected)
|
||||
{
|
||||
m_connection.reset();
|
||||
if(m_loadWorldDialog)
|
||||
{
|
||||
m_loadWorldDialog->reject();
|
||||
}
|
||||
if(m_serverLog)
|
||||
{
|
||||
delete m_serverLog;
|
||||
@ -989,7 +1003,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);
|
||||
@ -1013,6 +1026,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);
|
||||
|
||||
@ -46,6 +46,7 @@ class NewWorldWizard;
|
||||
class IntroductionWizard;
|
||||
class AddInterfaceWizard;
|
||||
class NewBoardWizard;
|
||||
class WorldListDialog;
|
||||
|
||||
class MainWindow final : public QMainWindow
|
||||
{
|
||||
@ -55,7 +56,12 @@ class MainWindow final : public QMainWindow
|
||||
std::shared_ptr<Connection> m_connection;
|
||||
ObjectPtr m_world;
|
||||
bool m_newWorldRequested = false;
|
||||
std::unique_ptr<NewWorldWizard> m_newWorldWizard;
|
||||
std::unique_ptr<WorldListDialog> m_loadWorldDialog;
|
||||
struct
|
||||
{
|
||||
std::unique_ptr<AddInterfaceWizard> addInterface;
|
||||
std::unique_ptr<NewWorldWizard> newWorld;
|
||||
} m_wizard;
|
||||
int m_clockRequest;
|
||||
ObjectPtr m_clock;
|
||||
QSplitter* m_splitter;
|
||||
@ -99,6 +105,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;
|
||||
@ -137,7 +144,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();
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2022-2023 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
|
||||
@ -32,6 +32,7 @@
|
||||
#include "../settings/statusbarsettings.hpp"
|
||||
#include "../widget/status/interfacestatuswidget.hpp"
|
||||
#include "../widget/status/luastatuswidget.hpp"
|
||||
#include "../widget/status/simulationstatuswidget.hpp"
|
||||
|
||||
MainWindowStatusBar::MainWindowStatusBar(MainWindow& mainWindow)
|
||||
: QStatusBar(&mainWindow)
|
||||
@ -148,6 +149,8 @@ void MainWindowStatusBar::updateStatuses()
|
||||
m_statuses->layout()->addWidget(new InterfaceStatusWidget(object, this));
|
||||
else if(object->classId() == "status.lua")
|
||||
m_statuses->layout()->addWidget(new LuaStatusWidget(object, this));
|
||||
else if(object->classId() == "status.simulation")
|
||||
m_statuses->layout()->addWidget(new SimulationStatusWidget(object, this));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
44
client/src/misc/mimedata.hpp
Normale Datei
44
client/src/misc/mimedata.hpp
Normale Datei
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* client/src/misc/mimedata.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_MISC_MIMEDATA_HPP
|
||||
#define TRAINTASTIC_CLIENT_MISC_MIMEDATA_HPP
|
||||
|
||||
#include <QMimeData>
|
||||
|
||||
class AssignTrainMimeData : public QMimeData
|
||||
{
|
||||
public:
|
||||
inline static const auto mimeType = QLatin1String("application/vnd.traintastic.assign_train");
|
||||
|
||||
explicit AssignTrainMimeData(const QString& trainId)
|
||||
{
|
||||
setData(mimeType, trainId.toUtf8());
|
||||
}
|
||||
|
||||
inline QString trainId() const
|
||||
{
|
||||
return QString::fromUtf8(data(mimeType));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -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)
|
||||
@ -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;
|
||||
|
||||
@ -70,7 +70,7 @@ class ServerLogTableModel final : public QAbstractTableModel
|
||||
ServerLogTableModel(std::shared_ptr<Connection> connection);
|
||||
~ServerLogTableModel();
|
||||
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) const final { Q_UNUSED(parent); return m_columnHeaders.size(); }
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) const final { Q_UNUSED(parent); return static_cast<int>(m_columnHeaders.size()); }
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const final;
|
||||
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const final;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
#include <traintastic/enum/decouplerstate.hpp>
|
||||
#include <traintastic/enum/direction.hpp>
|
||||
#include <traintastic/enum/directioncontrolstate.hpp>
|
||||
#include <traintastic/enum/externaloutputchangeaction.hpp>
|
||||
#include <traintastic/enum/lengthunit.hpp>
|
||||
#include <traintastic/enum/loconetf9f28.hpp>
|
||||
#include <traintastic/enum/loconetfastclock.hpp>
|
||||
@ -105,6 +106,7 @@ QString translateEnum(const QString& enumName, qint64 value)
|
||||
TRANSLATE_ENUM(DecouplerState)
|
||||
TRANSLATE_ENUM(Direction)
|
||||
TRANSLATE_ENUM(DirectionControlState)
|
||||
TRANSLATE_ENUM(ExternalOutputChangeAction)
|
||||
TRANSLATE_ENUM(LengthUnit)
|
||||
TRANSLATE_ENUM(LocoNetF9F28)
|
||||
TRANSLATE_ENUM(LocoNetFastClock)
|
||||
|
||||
@ -24,6 +24,7 @@
|
||||
#include "list/marklincanlocomotivelistwidget.hpp"
|
||||
#include "objectlist/boardlistwidget.hpp"
|
||||
#include "objectlist/throttleobjectlistwidget.hpp"
|
||||
#include "objectlist/trainlistwidget.hpp"
|
||||
#include "object/luascripteditwidget.hpp"
|
||||
#include "object/objecteditwidget.hpp"
|
||||
#include "object/itemseditwidget.hpp"
|
||||
@ -34,6 +35,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,7 +49,7 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
|
||||
|
||||
if(classId == "command_station_list")
|
||||
return new ObjectListWidget(object, parent); // todo remove
|
||||
else if(classId == "decoder_list" || classId == "list.train")
|
||||
else if(classId == "decoder_list")
|
||||
return new ThrottleObjectListWidget(object, parent); // todo remove
|
||||
else if(classId == "controller_list")
|
||||
return new ObjectListWidget(object, parent); // todo remove
|
||||
@ -61,6 +63,10 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
|
||||
{
|
||||
return new BoardListWidget(object, parent);
|
||||
}
|
||||
if(classId == "list.train")
|
||||
{
|
||||
return new TrainListWidget(object, parent);
|
||||
}
|
||||
else if(object->classId().startsWith("list."))
|
||||
return new ObjectListWidget(object, parent);
|
||||
else if(classId == "lua.script")
|
||||
@ -117,6 +123,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:
|
||||
|
||||
@ -160,7 +160,7 @@ void InputMonitorWidget::keyReleaseEvent(QKeyEvent* event)
|
||||
|
||||
uint32_t InputMonitorWidget::pageCount() const
|
||||
{
|
||||
return static_cast<uint32_t>(m_addressMax->toInt64() - m_addressMin->toInt64() + m_leds.size()) / m_leds.size();
|
||||
return static_cast<uint32_t>(m_addressMax->toInt64() - m_addressMin->toInt64() + m_leds.size()) / static_cast<uint32_t>(m_leds.size());
|
||||
}
|
||||
|
||||
void InputMonitorWidget::setPage(uint32_t value)
|
||||
@ -184,7 +184,7 @@ void InputMonitorWidget::setGroupBy(uint32_t value)
|
||||
|
||||
LEDWidget* InputMonitorWidget::getLED(uint32_t address)
|
||||
{
|
||||
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * m_leds.size();
|
||||
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * static_cast<uint32_t>(m_leds.size());
|
||||
|
||||
if(address >= first && (address - first) < m_leds.size())
|
||||
return m_leds[address - first];
|
||||
@ -199,7 +199,7 @@ void InputMonitorWidget::updateLEDs()
|
||||
|
||||
const uint32_t addressMin = static_cast<uint32_t>(m_addressMin->toInt64());
|
||||
const uint32_t addressMax = static_cast<uint32_t>(m_addressMax->toInt64());
|
||||
uint32_t address = addressMin + m_page * m_leds.size();
|
||||
uint32_t address = addressMin + m_page * static_cast<uint32_t>(m_leds.size());
|
||||
|
||||
for(auto* led : m_leds)
|
||||
{
|
||||
|
||||
82
client/src/widget/methodicon.cpp
Normale Datei
82
client/src/widget/methodicon.cpp
Normale Datei
@ -0,0 +1,82 @@
|
||||
/**
|
||||
* client/src/widget/methodicon.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 "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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
m_method.call();
|
||||
}
|
||||
}
|
||||
}
|
||||
43
client/src/widget/methodicon.hpp
Normale Datei
43
client/src/widget/methodicon.hpp
Normale Datei
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* client/src/widget/methodicon.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_METHODICON_HPP
|
||||
#define TRAINTASTIC_CLIENT_WIDGET_METHODICON_HPP
|
||||
|
||||
#include <QLabel>
|
||||
|
||||
class Method;
|
||||
|
||||
class MethodIcon : public QLabel
|
||||
{
|
||||
protected:
|
||||
Method& m_method;
|
||||
bool m_mouseLeftButtonPressed;
|
||||
|
||||
void mousePressEvent(QMouseEvent* event) final;
|
||||
void mouseReleaseEvent(QMouseEvent* event) final;
|
||||
|
||||
public:
|
||||
MethodIcon(Method& item, QIcon icon, QWidget* parent = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -29,6 +29,7 @@
|
||||
#include <version.hpp>
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
#include <traintastic/utils/standardpaths.hpp>
|
||||
#include "../../misc/methodaction.hpp"
|
||||
#include "../../network/object.hpp"
|
||||
#include "../../network/property.hpp"
|
||||
#include "../../network/method.hpp"
|
||||
@ -108,6 +109,17 @@ void LuaScriptEditWidget::buildForm()
|
||||
m_stop->setEnabled(value.toBool());
|
||||
});
|
||||
|
||||
|
||||
if(auto* method = m_object->getMethod("clear_persistent_variables"))
|
||||
{
|
||||
QWidget* spacer = new QWidget(this);
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
spacer->show();
|
||||
toolbar->addWidget(spacer);
|
||||
|
||||
toolbar->addAction(new MethodAction(Theme::getIcon("clear_persistent_variables"), *method, this));
|
||||
}
|
||||
|
||||
QWidget* spacer = new QWidget(this);
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
spacer->show();
|
||||
|
||||
@ -40,6 +40,7 @@
|
||||
#include "../propertydirectioncontrol.hpp"
|
||||
#include "../propertyvaluelabel.hpp"
|
||||
#include "../methodpushbutton.hpp"
|
||||
#include "../unitpropertycombobox.hpp"
|
||||
#include "../unitpropertyedit.hpp"
|
||||
#include "../createwidget.hpp"
|
||||
#include "../../theme/theme.hpp"
|
||||
@ -107,7 +108,16 @@ void ObjectEditWidget::buildForm()
|
||||
{
|
||||
Property* property = static_cast<Property*>(baseProperty);
|
||||
if(UnitProperty* unitProperty = dynamic_cast<UnitProperty*>(property))
|
||||
w = new UnitPropertyEdit(*unitProperty, this);
|
||||
{
|
||||
if(unitProperty->hasAttribute(AttributeName::Values))
|
||||
{
|
||||
w = new UnitPropertyComboBox(*unitProperty, this);
|
||||
}
|
||||
else
|
||||
{
|
||||
w = new UnitPropertyEdit(*unitProperty, this);
|
||||
}
|
||||
}
|
||||
else if(!property->isWritable())
|
||||
w = new PropertyValueLabel(*property, this);
|
||||
else if(property->type() == ValueType::Boolean)
|
||||
|
||||
@ -146,10 +146,11 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object_, QWidget* parent) :
|
||||
method->argumentTypes().size() == 1 && method->argumentTypes()[0] == ValueType::Object &&
|
||||
method->resultType() == ValueType::Invalid)
|
||||
{
|
||||
const bool multiSelect = object_->classId() == "list.train_vehicle";
|
||||
m_actionAdd = m_toolbar->addAction(Theme::getIcon("add"), method->displayName(),
|
||||
[this, method]()
|
||||
[this, method, multiSelect]()
|
||||
{
|
||||
std::make_unique<ObjectSelectListDialog>(*method, this)->exec();
|
||||
std::make_unique<ObjectSelectListDialog>(*method, multiSelect, this)->exec();
|
||||
});
|
||||
m_actionAdd->setEnabled(method->getAttributeBool(AttributeName::Enabled, true));
|
||||
connect(method, &Method::attributeChanged, this,
|
||||
@ -398,6 +399,16 @@ ObjectListWidget::ObjectListWidget(const ObjectPtr& object_, QWidget* parent) :
|
||||
}
|
||||
}
|
||||
|
||||
if(auto* method = object()->getMethod("clear_persistent_variables"))
|
||||
{
|
||||
QWidget* spacer = new QWidget(this);
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
|
||||
spacer->show();
|
||||
m_toolbar->addWidget(spacer);
|
||||
|
||||
m_toolbar->addAction(new MethodAction(Theme::getIcon("clear_persistent_variables"), *method, this));
|
||||
}
|
||||
|
||||
if(!m_toolbar->actions().empty())
|
||||
{
|
||||
static_cast<QVBoxLayout*>(this->layout())->insertWidget(0, m_toolbar);
|
||||
|
||||
41
client/src/widget/objectlist/trainlistwidget.cpp
Normale Datei
41
client/src/widget/objectlist/trainlistwidget.cpp
Normale Datei
@ -0,0 +1,41 @@
|
||||
/**
|
||||
* client/src/widget/objectlist/trainlistwidget.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 "trainlistwidget.hpp"
|
||||
#include <QDrag>
|
||||
#include "../tablewidget.hpp"
|
||||
#include "../../misc/mimedata.hpp"
|
||||
|
||||
TrainListWidget::TrainListWidget(const ObjectPtr& object, QWidget* parent)
|
||||
: ThrottleObjectListWidget(object, parent)
|
||||
{
|
||||
connect(m_tableWidget, &TableWidget::rowDragged,
|
||||
[this](int row)
|
||||
{
|
||||
if(auto trainId = m_tableWidget->getRowObjectId(row); !trainId.isEmpty())
|
||||
{
|
||||
QDrag* drag = new QDrag(m_tableWidget);
|
||||
drag->setMimeData(new AssignTrainMimeData(trainId));
|
||||
drag->exec(Qt::CopyAction);
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/enum/turnoutposition.hpp
|
||||
* client/src/widget/objectlist/trainlistwidget.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2020-2021 Reinder Feenstra
|
||||
* 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
|
||||
@ -20,9 +20,15 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_ENUM_TURNOUTPOSITION_HPP
|
||||
#define TRAINTASTIC_SERVER_ENUM_TURNOUTPOSITION_HPP
|
||||
#ifndef TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_TRAINLISTWIDGET_HPP
|
||||
#define TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_TRAINLISTWIDGET_HPP
|
||||
|
||||
#include <traintastic/enum/turnoutposition.hpp>
|
||||
#include "throttleobjectlistwidget.hpp"
|
||||
|
||||
class TrainListWidget : public ThrottleObjectListWidget
|
||||
{
|
||||
public:
|
||||
explicit TrainListWidget(const ObjectPtr& object, QWidget* parent = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -161,7 +161,7 @@ OutputKeyboardWidget::OutputKeyboardWidget(std::shared_ptr<OutputKeyboard> objec
|
||||
connect(led, &LEDWidget::clicked, this,
|
||||
[this, index=i]()
|
||||
{
|
||||
const uint32_t address = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * m_leds.size() / 2 + index / 2;
|
||||
const uint32_t address = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * static_cast<uint32_t>(m_leds.size()) / 2 + index / 2;
|
||||
const auto value = (index & 0x1) ? OutputPairValue::Second : OutputPairValue::First;
|
||||
callMethod(*m_setOutputValue, nullptr, address, value);
|
||||
});
|
||||
@ -251,7 +251,7 @@ uint32_t OutputKeyboardWidget::pageCount() const
|
||||
{
|
||||
leds *= 2;
|
||||
}
|
||||
return static_cast<uint32_t>(leds + m_leds.size() - 1) / m_leds.size();
|
||||
return static_cast<uint32_t>(leds + m_leds.size() - 1) / static_cast<uint32_t>(m_leds.size());
|
||||
}
|
||||
|
||||
void OutputKeyboardWidget::setPage(uint32_t value)
|
||||
@ -277,7 +277,7 @@ LEDWidget* OutputKeyboardWidget::getLED(uint32_t address)
|
||||
{
|
||||
assert(m_object->outputType() == OutputType::Single);
|
||||
|
||||
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * m_leds.size();
|
||||
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * static_cast<uint32_t>(m_leds.size());
|
||||
|
||||
if(address >= first && (address - first) < m_leds.size())
|
||||
return m_leds[address - first];
|
||||
@ -289,7 +289,7 @@ std::pair<LEDWidget*, LEDWidget*> OutputKeyboardWidget::getLEDs(uint32_t address
|
||||
{
|
||||
assert(m_object->outputType() == OutputType::Pair);
|
||||
|
||||
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * m_leds.size() / 2;
|
||||
const uint32_t first = static_cast<uint32_t>(m_addressMin->toInt64()) + m_page * static_cast<uint32_t>(m_leds.size()) / 2;
|
||||
|
||||
if(address >= first && (address - first) < m_leds.size())
|
||||
return {m_leds[(address - first) * 2], m_leds[(address - first) * 2 + 1]};
|
||||
@ -309,7 +309,7 @@ void OutputKeyboardWidget::updateLEDs()
|
||||
{
|
||||
case OutputType::Single:
|
||||
{
|
||||
uint32_t address = addressMin + m_page * m_leds.size();
|
||||
uint32_t address = addressMin + m_page * static_cast<uint32_t>(m_leds.size());
|
||||
for(auto* led : m_leds)
|
||||
{
|
||||
const auto& outputState = m_object->getOutputState(address);
|
||||
@ -329,7 +329,7 @@ void OutputKeyboardWidget::updateLEDs()
|
||||
}
|
||||
case OutputType::Pair:
|
||||
{
|
||||
uint32_t address = addressMin + m_page * m_leds.size() / 2;
|
||||
uint32_t address = addressMin + m_page * static_cast<uint32_t>(m_leds.size()) / 2;
|
||||
bool second = false;
|
||||
for(auto* led : m_leds)
|
||||
{
|
||||
|
||||
@ -27,14 +27,20 @@
|
||||
#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"
|
||||
#include "../network/callmethod.hpp"
|
||||
#include "../network/method.hpp"
|
||||
@ -46,9 +52,12 @@
|
||||
#include "../theme/theme.hpp"
|
||||
#include "../misc/methodaction.hpp"
|
||||
|
||||
constexpr int columnCountNonOutput = 2;
|
||||
constexpr int columnUse = 0;
|
||||
constexpr int columnKey = 1;
|
||||
constexpr int columnKey = 0;
|
||||
|
||||
static bool hasUseColumn(const QString& classId)
|
||||
{
|
||||
return classId == "output_map.signal";
|
||||
}
|
||||
|
||||
static void setComboBoxMinimumWidth(QComboBox* comboBox)
|
||||
{
|
||||
@ -58,10 +67,14 @@ static void setComboBoxMinimumWidth(QComboBox* comboBox)
|
||||
OutputMapWidget::OutputMapWidget(ObjectPtr object, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_object{std::move(object)}
|
||||
, m_hasUseColumn{hasUseColumn(m_object->classId())}
|
||||
, m_columnCountNonOutput{m_hasUseColumn ? 2 : 1}
|
||||
, m_addresses{m_object->getVectorProperty("addresses")}
|
||||
, m_ecosObject{dynamic_cast<Property*>(m_object->getProperty("ecos_object"))}
|
||||
, m_items{m_object->getObjectVectorProperty("items")}
|
||||
, m_table{new QTableWidget(this)}
|
||||
, m_getParentRequestId{Connection::invalidRequestId}
|
||||
, m_getItemsRequestId{Connection::invalidRequestId}
|
||||
{
|
||||
QVBoxLayout* l = new QVBoxLayout();
|
||||
|
||||
@ -92,16 +105,49 @@ OutputMapWidget::OutputMapWidget(ObjectPtr object, QWidget* parent)
|
||||
}
|
||||
l->addLayout(form);
|
||||
|
||||
m_table->setColumnCount(columnCountNonOutput);
|
||||
const int listViewIconSize = m_table->style()->pixelMetric(QStyle::PM_ListViewIconSize);
|
||||
m_table->setIconSize({listViewIconSize, listViewIconSize});
|
||||
m_table->setColumnCount(m_columnCountNonOutput);
|
||||
m_table->setRowCount(0);
|
||||
m_table->setHorizontalHeaderLabels({Locale::tr("output_map:use"), Locale::tr(m_object->classId() + ":key")});
|
||||
QStringList labels;
|
||||
labels.append(Locale::tr(m_object->classId() + ":key"));
|
||||
if(m_hasUseColumn)
|
||||
{
|
||||
labels.append(Locale::tr("output_map:use"));
|
||||
}
|
||||
m_table->setHorizontalHeaderLabels(labels);
|
||||
m_table->verticalHeader()->setVisible(false);
|
||||
m_table->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
||||
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"))
|
||||
{
|
||||
m_getParentRequestId = parentObject->getObject(
|
||||
[this](const ObjectPtr& obj, std::optional<const Error> ec)
|
||||
{
|
||||
if(obj && !ec)
|
||||
{
|
||||
m_getParentRequestId = Connection::invalidRequestId;
|
||||
m_parentObject = obj;
|
||||
updateKeyIcons();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if(m_items) /*[[likely]]*/
|
||||
{
|
||||
m_getItemsRequestId = m_items->getObjects(
|
||||
@ -112,26 +158,30 @@ OutputMapWidget::OutputMapWidget(ObjectPtr object, QWidget* parent)
|
||||
updateItems(objects);
|
||||
}
|
||||
});
|
||||
|
||||
connect(&BoardSettings::instance(), &BoardSettings::changed, this, &OutputMapWidget::updateKeyIcons);
|
||||
}
|
||||
}
|
||||
|
||||
OutputMapWidget::~OutputMapWidget()
|
||||
{
|
||||
if(m_getParentRequestId != Connection::invalidRequestId)
|
||||
{
|
||||
m_object->connection()->cancelRequest(m_getParentRequestId);
|
||||
}
|
||||
if(m_getItemsRequestId != Connection::invalidRequestId)
|
||||
{
|
||||
m_object->connection()->cancelRequest(m_getItemsRequestId);
|
||||
}
|
||||
}
|
||||
|
||||
void OutputMapWidget::updateItems(const std::vector<ObjectPtr>& items)
|
||||
{
|
||||
m_table->setRowCount(items.size());
|
||||
m_table->setRowCount(static_cast<int>(items.size()));
|
||||
m_itemObjects = items;
|
||||
m_actions.resize(items.size());
|
||||
for(size_t i = 0; i < items.size(); i++)
|
||||
{
|
||||
if(auto* p = dynamic_cast<Property*>(items[i]->getProperty("use")))
|
||||
{
|
||||
QWidget* w = new QWidget(m_table);
|
||||
QHBoxLayout* l = new QHBoxLayout();
|
||||
l->setAlignment(Qt::AlignCenter);
|
||||
l->addWidget(new PropertyCheckBox(*p, w));
|
||||
w->setLayout(l);
|
||||
m_table->setCellWidget(i, columnUse, w);
|
||||
}
|
||||
|
||||
if(auto* p = items[i]->getProperty("key"))
|
||||
{
|
||||
QString text;
|
||||
@ -162,34 +212,116 @@ void OutputMapWidget::updateItems(const std::vector<ObjectPtr>& items)
|
||||
assert(false);
|
||||
text = "?";
|
||||
}
|
||||
m_table->setItem(i, columnKey, new QTableWidgetItem(text));
|
||||
m_table->setItem(static_cast<int>(i), columnKey, new QTableWidgetItem(text));
|
||||
}
|
||||
|
||||
if(m_hasUseColumn)
|
||||
{
|
||||
const int columnUse = columnKey + 1;
|
||||
|
||||
if(auto* p = dynamic_cast<Property*>(items[i]->getProperty("use")))
|
||||
{
|
||||
QWidget* w = new QWidget(m_table);
|
||||
QHBoxLayout* l = new QHBoxLayout();
|
||||
l->setAlignment(Qt::AlignCenter);
|
||||
l->addWidget(new PropertyCheckBox(*p, w));
|
||||
w->setLayout(l);
|
||||
m_table->setCellWidget(static_cast<int>(i), columnUse, w);
|
||||
}
|
||||
}
|
||||
|
||||
if(auto* p = items[i]->getProperty("visible"))
|
||||
{
|
||||
m_table->setRowHidden(static_cast<int>(i), !p->toBool());
|
||||
|
||||
connect(p, &Property::valueChangedBool, this,
|
||||
[this, row=static_cast<int>(i)](bool value)
|
||||
{
|
||||
m_table->setRowHidden(row, !value);
|
||||
});
|
||||
}
|
||||
|
||||
if(auto* outputActions = dynamic_cast<ObjectVectorProperty*>(items[i]->getVectorProperty("output_actions")))
|
||||
{
|
||||
updateTableOutputActions(*outputActions, i);
|
||||
updateTableOutputActions(*outputActions, static_cast<int>(i));
|
||||
|
||||
connect(outputActions, &ObjectVectorProperty::valueChanged, this,
|
||||
[this, row=i]()
|
||||
[this, row=static_cast<int>(i)]()
|
||||
{
|
||||
updateTableOutputActions(*dynamic_cast<ObjectVectorProperty*>(sender()), row);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
updateKeyIcons();
|
||||
updateTableOutputColumns();
|
||||
}
|
||||
|
||||
void OutputMapWidget::updateKeyIcons()
|
||||
{
|
||||
if(!m_parentObject)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if(auto tileIdProperty = m_parentObject->getProperty("tile_id"))
|
||||
{
|
||||
const bool darkBackground = m_table->palette().window().color().lightnessF() < 0.5;
|
||||
const auto tileId = tileIdProperty->toEnum<TileId>();
|
||||
|
||||
const int iconSize = m_table->iconSize().height();
|
||||
QImage image(iconSize, iconSize, QImage::Format_ARGB32);
|
||||
QPainter painter{&image};
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
TilePainter tilePainter{painter, iconSize, *getBoardColorScheme(darkBackground ? BoardSettings::ColorScheme::Dark : BoardSettings::ColorScheme::Light)};
|
||||
|
||||
for(size_t i = 0; i < m_itemObjects.size(); i++)
|
||||
{
|
||||
if(auto* key = m_itemObjects[i]->getProperty("key"))
|
||||
{
|
||||
image.fill(Qt::transparent);
|
||||
|
||||
if(isRailTurnout(tileId))
|
||||
{
|
||||
tilePainter.drawTurnout(tileId, image.rect(), TileRotate::Deg0, TurnoutPosition::Unknown, static_cast<TurnoutPosition>(key->toInt()));
|
||||
}
|
||||
else if(isRailSignal(tileId))
|
||||
{
|
||||
tilePainter.drawSignal(tileId, image.rect(), TileRotate::Deg0, false, static_cast<SignalAspect>(key->toInt()));
|
||||
}
|
||||
else if(tileId == TileId::RailDirectionControl)
|
||||
{
|
||||
tilePainter.drawDirectionControl(tileId, image.rect(), TileRotate::Deg0, false, static_cast<DirectionControlState>(key->toInt()));
|
||||
}
|
||||
else if(tileId == TileId::RailDecoupler)
|
||||
{
|
||||
tilePainter.drawRailDecoupler(image.rect(), TileRotate::Deg90, false, static_cast<DecouplerState>(key->toInt()));
|
||||
}
|
||||
else if(tileId == TileId::Switch)
|
||||
{
|
||||
tilePainter.drawSwitch(image.rect(), key->toBool());
|
||||
}
|
||||
else
|
||||
{
|
||||
break; // tileId not supported (yet)
|
||||
}
|
||||
|
||||
m_table->item(static_cast<int>(i), columnKey)->setIcon(QPixmap::fromImage(image));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void OutputMapWidget::updateTableOutputColumns()
|
||||
{
|
||||
if(m_addresses && m_addresses->getAttributeBool(AttributeName::Visible, true))
|
||||
{
|
||||
const auto size = m_addresses->size();
|
||||
|
||||
m_table->setColumnCount(columnCountNonOutput + size);
|
||||
m_table->setColumnCount(m_columnCountNonOutput + size);
|
||||
for(int i = 0; i < size; i++)
|
||||
{
|
||||
const int column = columnCountNonOutput + i;
|
||||
const int column = m_columnCountNonOutput + i;
|
||||
const int address = m_addresses->getInt(i);
|
||||
auto* item = new QTableWidgetItem(QString("#%1").arg(address));
|
||||
item->setToolTip(Locale::tr("output_map:address_x").arg(address));
|
||||
@ -198,15 +330,26 @@ void OutputMapWidget::updateTableOutputColumns()
|
||||
}
|
||||
else if(m_ecosObject && m_ecosObject->getAttributeBool(AttributeName::Visible, true))
|
||||
{
|
||||
m_table->setColumnCount(columnCountNonOutput + 1);
|
||||
m_table->setHorizontalHeaderItem(columnCountNonOutput, new QTableWidgetItem(Locale::tr("output.ecos_object:state")));
|
||||
m_table->setColumnCount(m_columnCountNonOutput + 1);
|
||||
m_table->setHorizontalHeaderItem(m_columnCountNonOutput, new QTableWidgetItem(Locale::tr("output.ecos_object:state")));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_table->setColumnCount(columnCountNonOutput);
|
||||
m_table->setColumnCount(m_columnCountNonOutput);
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
@ -214,21 +357,21 @@ void OutputMapWidget::updateTableOutputActions(ObjectVectorProperty& property, i
|
||||
m_dummy = property.getObjects(
|
||||
[this, row](const std::vector<ObjectPtr>& objects, std::optional<const Error> /*ec*/)
|
||||
{
|
||||
const int columnCount = static_cast<int>(columnCountNonOutput + objects.size());
|
||||
const int columnCount = static_cast<int>(m_columnCountNonOutput + objects.size());
|
||||
if(columnCount > m_table->columnCount())
|
||||
{
|
||||
m_table->setColumnCount(columnCount);
|
||||
}
|
||||
|
||||
auto& rowActions = m_actions[row];
|
||||
int column = columnCountNonOutput;
|
||||
int column = m_columnCountNonOutput;
|
||||
for(auto& object : objects)
|
||||
{
|
||||
if(column >= static_cast<int>(rowActions.size()) || object.get() != rowActions[column].get())
|
||||
{
|
||||
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;
|
||||
@ -40,21 +41,30 @@ class OutputMapWidget : public QWidget
|
||||
|
||||
protected:
|
||||
ObjectPtr m_object;
|
||||
ObjectPtr m_parentObject;
|
||||
const bool m_hasUseColumn;
|
||||
const int m_columnCountNonOutput;
|
||||
AbstractVectorProperty* m_addresses;
|
||||
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;
|
||||
int m_getItemsRequestId;
|
||||
int m_dummy;
|
||||
|
||||
void updateTableOutputActions(ObjectVectorProperty& property, int row);
|
||||
void updateItems(const std::vector<ObjectPtr>& items);
|
||||
void updateKeyIcons();
|
||||
void updateTableOutputColumns();
|
||||
|
||||
bool eventFilter(QObject* object, QEvent* event) override;
|
||||
|
||||
public:
|
||||
explicit OutputMapWidget(ObjectPtr object, QWidget* parent = nullptr);
|
||||
~OutputMapWidget() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -189,6 +189,7 @@ PropertyLuaCodeEdit::Highlighter::Highlighter(QTextDocument* parent) :
|
||||
QStringLiteral("\\bworld(?=\\.)"),
|
||||
QStringLiteral("\\bset(?=\\.)"),
|
||||
QStringLiteral("\\benum(?=\\.)"),
|
||||
QStringLiteral("\\bpv(?=\\.)"),
|
||||
};
|
||||
for(const auto& regex : globals)
|
||||
m_rules.append(Rule(regex, QColor(0xFF, 0x8C, 0x00)));
|
||||
|
||||
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
|
||||
@ -35,7 +35,11 @@ PropertySpinBox::PropertySpinBox(Property& property, QWidget* parent) :
|
||||
Q_ASSERT(m_property.type() == ValueType::Integer);
|
||||
setEnabled(m_property.getAttributeBool(AttributeName::Enabled, true));
|
||||
setVisible(m_property.getAttributeBool(AttributeName::Visible, true));
|
||||
setRange(std::numeric_limits<int>::min(), std::numeric_limits<int>::max());
|
||||
updateRange();
|
||||
if(auto unit = m_property.getAttributeString(AttributeName::Unit, ""); !unit.isEmpty())
|
||||
{
|
||||
setSuffix(unit.prepend(" "));
|
||||
}
|
||||
setValue(m_property.toInt());
|
||||
connect(&m_property, &AbstractProperty::valueChangedInt, this,
|
||||
[this](int value)
|
||||
@ -58,6 +62,22 @@ PropertySpinBox::PropertySpinBox(Property& property, QWidget* parent) :
|
||||
setVisible(value.toBool());
|
||||
break;
|
||||
|
||||
case AttributeName::Min:
|
||||
case AttributeName::Max:
|
||||
updateRange();
|
||||
break;
|
||||
|
||||
case AttributeName::Unit:
|
||||
if(auto unit = value.toString(); !unit.isEmpty())
|
||||
{
|
||||
setSuffix(unit.prepend(" "));
|
||||
}
|
||||
else
|
||||
{
|
||||
setSuffix("");
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -105,3 +125,10 @@ void PropertySpinBox::focusOutEvent(QFocusEvent* event)
|
||||
QSpinBox::focusOutEvent(event);
|
||||
setValue(m_property.toInt());
|
||||
}
|
||||
|
||||
void PropertySpinBox::updateRange()
|
||||
{
|
||||
setRange(
|
||||
m_property.getAttributeInt(AttributeName::Min, std::numeric_limits<int>::min()),
|
||||
m_property.getAttributeInt(AttributeName::Max, std::numeric_limits<int>::max()));
|
||||
}
|
||||
|
||||
@ -36,6 +36,8 @@ class PropertySpinBox : public QSpinBox
|
||||
void cancelRequest();
|
||||
void showError(const QString& error);
|
||||
|
||||
void updateRange();
|
||||
|
||||
protected:
|
||||
void focusOutEvent(QFocusEvent* event) override;
|
||||
|
||||
|
||||
66
client/src/widget/status/simulationstatuswidget.cpp
Normale Datei
66
client/src/widget/status/simulationstatuswidget.cpp
Normale Datei
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* client/src/widget/status/simulationstatuswidget.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 "simulationstatuswidget.hpp"
|
||||
#include <QResizeEvent>
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
#include "../../network/object.hpp"
|
||||
#include "../../network/property.hpp"
|
||||
#include "../../theme/theme.hpp"
|
||||
|
||||
SimulationStatusWidget::SimulationStatusWidget(const ObjectPtr& object, QWidget* parent)
|
||||
: QSvgWidget(parent)
|
||||
, m_object{object}
|
||||
{
|
||||
assert(m_object);
|
||||
assert(m_object->classId() == "status.simulation");
|
||||
|
||||
load(Theme::getIconFile("simulation"));
|
||||
|
||||
if(auto* property = m_object->getProperty("label"))
|
||||
{
|
||||
connect(property, &Property::valueChanged, this, &SimulationStatusWidget::labelChanged);
|
||||
}
|
||||
|
||||
labelChanged();
|
||||
}
|
||||
|
||||
void SimulationStatusWidget::labelChanged()
|
||||
{
|
||||
QString label;
|
||||
|
||||
if(auto* property = m_object->getProperty("label"))
|
||||
{
|
||||
label = Locale::instance->parse(property->toString());
|
||||
}
|
||||
|
||||
setToolTip(label);
|
||||
}
|
||||
|
||||
void SimulationStatusWidget::resizeEvent(QResizeEvent* event)
|
||||
{
|
||||
QSvgWidget::resizeEvent(event);
|
||||
|
||||
// force same width as height:
|
||||
setMinimumWidth(event->size().height());
|
||||
setMaximumWidth(event->size().height());
|
||||
}
|
||||
44
client/src/widget/status/simulationstatuswidget.hpp
Normale Datei
44
client/src/widget/status/simulationstatuswidget.hpp
Normale Datei
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* client/src/widget/status/simulationstatuswidget.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_STATUS_SIMULATIONSTATUSWIDGET_HPP
|
||||
#define TRAINTASTIC_CLIENT_WIDGET_STATUS_SIMULATIONSTATUSWIDGET_HPP
|
||||
|
||||
#include <QSvgWidget>
|
||||
#include "../../network/objectptr.hpp"
|
||||
|
||||
class SimulationStatusWidget : public QSvgWidget
|
||||
{
|
||||
private:
|
||||
ObjectPtr m_object;
|
||||
|
||||
void labelChanged();
|
||||
void stateChanged();
|
||||
|
||||
protected:
|
||||
void resizeEvent(QResizeEvent* event) override;
|
||||
|
||||
public:
|
||||
explicit SimulationStatusWidget(const ObjectPtr& object, QWidget* parent = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -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
|
||||
@ -24,6 +24,8 @@
|
||||
#include <QHeaderView>
|
||||
#include <QScrollBar>
|
||||
#include <QSettings>
|
||||
#include <QMouseEvent>
|
||||
#include <QApplication>
|
||||
#include "../network/tablemodel.hpp"
|
||||
|
||||
TableWidget::TableWidget(QWidget* parent) :
|
||||
@ -113,3 +115,23 @@ void TableWidget::updateRegion()
|
||||
|
||||
m_model->setRegion(columnMin, columnMax, rowMin, rowMax);
|
||||
}
|
||||
|
||||
void TableWidget::mouseMoveEvent(QMouseEvent* event)
|
||||
{
|
||||
QTableView::mouseMoveEvent(event);
|
||||
|
||||
if(event->button() == Qt::LeftButton)
|
||||
{
|
||||
m_dragStartPosition = event->pos();
|
||||
}
|
||||
}
|
||||
|
||||
void TableWidget::mousePressEvent(QMouseEvent* event)
|
||||
{
|
||||
QTableView::mousePressEvent(event);
|
||||
|
||||
if((event->buttons() & Qt::LeftButton) && (event->pos() - m_dragStartPosition).manhattanLength() >= QApplication::startDragDistance())
|
||||
{
|
||||
emit rowDragged(indexAt(m_dragStartPosition).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-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
|
||||
@ -33,6 +33,10 @@ class TableWidget : public QTableView
|
||||
protected:
|
||||
TableModelPtr m_model;
|
||||
int m_selectedRow = -1;
|
||||
QPoint m_dragStartPosition;
|
||||
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
|
||||
protected slots:
|
||||
void updateRegion();
|
||||
@ -44,6 +48,9 @@ class TableWidget : public QTableView
|
||||
QString getRowObjectId(int row) const;
|
||||
|
||||
void setTableModel(const TableModelPtr& model);
|
||||
|
||||
signals:
|
||||
void rowDragged(int row);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
193
client/src/widget/unitpropertycombobox.cpp
Normale Datei
193
client/src/widget/unitpropertycombobox.cpp
Normale Datei
@ -0,0 +1,193 @@
|
||||
/**
|
||||
* client/src/widget/unitpropertycombobox.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 "unitpropertycombobox.hpp"
|
||||
#include <QHBoxLayout>
|
||||
#include <QComboBox>
|
||||
#include <traintastic/locale/locale.hpp>
|
||||
#include "../network/unitproperty.hpp"
|
||||
#include "../utils/internalupdateholder.hpp"
|
||||
#include "../utils/enum.hpp"
|
||||
|
||||
UnitPropertyComboBox::UnitPropertyComboBox(UnitProperty& property, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_property{property}
|
||||
, m_valueComboBox{new QComboBox(this)}
|
||||
, m_unitComboBox{new QComboBox(this)}
|
||||
{
|
||||
setEnabled(m_property.getAttributeBool(AttributeName::Enabled, true));
|
||||
setVisible(m_property.getAttributeBool(AttributeName::Visible, true));
|
||||
|
||||
connect(&m_property, &UnitProperty::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::AliasKeys:
|
||||
case AttributeName::AliasValues:
|
||||
case AttributeName::Values:
|
||||
updateValues();
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
connect(&m_property, &Property::valueChangedDouble, this,
|
||||
[this](double value)
|
||||
{
|
||||
InternalUpdateHolder hold(m_internalUpdate);
|
||||
if(int index = m_valueComboBox->findData(value); index != -1) // predefined value
|
||||
{
|
||||
m_valueComboBox->setCurrentIndex(index);
|
||||
}
|
||||
else // custom value
|
||||
{
|
||||
m_valueComboBox->setCurrentText(QString::number(value));
|
||||
}
|
||||
});
|
||||
|
||||
m_valueComboBox->setEditable(true); // FIXME
|
||||
m_valueComboBox->setInsertPolicy(QComboBox::NoInsert);
|
||||
|
||||
if(m_valueComboBox->isEditable())
|
||||
{
|
||||
connect(m_valueComboBox, &QComboBox::currentTextChanged,
|
||||
[this](const QString& value)
|
||||
{
|
||||
if(m_internalUpdate == 0)
|
||||
{
|
||||
const QVariant v = m_valueComboBox->currentData();
|
||||
if(v.isValid()) // predefined value
|
||||
{
|
||||
m_property.setValueDouble(v.value<double>());
|
||||
}
|
||||
else // custom value
|
||||
{
|
||||
m_property.setValueString(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
connect(m_valueComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
|
||||
[this](int)
|
||||
{
|
||||
if(m_internalUpdate == 0)
|
||||
{
|
||||
const QVariant v = m_valueComboBox->currentData();
|
||||
m_property.setValueDouble(v.value<double>());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QHBoxLayout* l = new QHBoxLayout();
|
||||
l->setContentsMargins(0, 0, 0, 0);
|
||||
l->addWidget(m_valueComboBox, 1);
|
||||
|
||||
for(qint64 value : enumValues(m_property.unitName()))
|
||||
{
|
||||
m_unitComboBox->addItem(translateEnum(m_property.unitName(), value), value);
|
||||
if(m_property.unitValue() == value)
|
||||
m_unitComboBox->setCurrentIndex(m_unitComboBox->count() - 1);
|
||||
}
|
||||
l->addWidget(m_unitComboBox);
|
||||
connect(m_unitComboBox, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
[this](int)
|
||||
{
|
||||
if(QVariant v = m_unitComboBox->currentData(); v.canConvert<qint64>())
|
||||
if(qint64 value = v.toLongLong(); value != m_property.unitValue())
|
||||
m_property.setUnitValue(value);
|
||||
});
|
||||
|
||||
setLayout(l);
|
||||
updateValues();
|
||||
}
|
||||
|
||||
void UnitPropertyComboBox::updateValues()
|
||||
{
|
||||
QVariant values = m_property.getAttribute(AttributeName::Values, QVariant());
|
||||
if(Q_LIKELY(values.isValid()))
|
||||
{
|
||||
InternalUpdateHolder hold(m_internalUpdate);
|
||||
|
||||
m_valueComboBox->clear();
|
||||
|
||||
if(Q_LIKELY(values.userType() == QMetaType::QVariantList))
|
||||
{
|
||||
bool currentIndexSet = false;
|
||||
|
||||
switch(m_property.type())
|
||||
{
|
||||
case ValueType::Float:
|
||||
{
|
||||
const QVariantList aliasKeys = m_property.getAttribute(AttributeName::AliasKeys, QVariant()).toList();
|
||||
const QVariantList aliasValues = m_property.getAttribute(AttributeName::AliasValues, QVariant()).toList();
|
||||
|
||||
for(QVariant& v : values.toList())
|
||||
{
|
||||
const auto value = v.toDouble();
|
||||
if(int index = aliasKeys.indexOf(v); index != -1)
|
||||
{
|
||||
m_valueComboBox->addItem(Locale::instance->parse(aliasValues[index].toString()), value);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_valueComboBox->addItem(QString::number(value), value);
|
||||
}
|
||||
|
||||
if(qFuzzyCompare(m_property.toDouble(), value) || m_property.toDouble() == value)
|
||||
{
|
||||
m_valueComboBox->setCurrentIndex(m_valueComboBox->count() - 1);
|
||||
currentIndexSet = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case ValueType::Invalid:
|
||||
case ValueType::Boolean:
|
||||
case ValueType::Integer:
|
||||
case ValueType::Enum:
|
||||
case ValueType::String:
|
||||
case ValueType::Object:
|
||||
case ValueType::Set:
|
||||
assert(false);
|
||||
break;
|
||||
}
|
||||
|
||||
if(m_valueComboBox->isEditable() && !currentIndexSet)
|
||||
{
|
||||
m_valueComboBox->setCurrentText(m_property.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
client/src/widget/unitpropertycombobox.hpp
Normale Datei
47
client/src/widget/unitpropertycombobox.hpp
Normale Datei
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* client/src/widget/unitpropertycombobox.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_UNITPROPERTYCOMBOBOX_HPP
|
||||
#define TRAINTASTIC_CLIENT_WIDGET_UNITPROPERTYCOMBOBOX_HPP
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class UnitProperty;
|
||||
class QComboBox;
|
||||
|
||||
class UnitPropertyComboBox : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
UnitProperty& m_property;
|
||||
size_t m_internalUpdate = 0;
|
||||
QComboBox* m_valueComboBox;
|
||||
QComboBox* m_unitComboBox;
|
||||
|
||||
void updateValues();
|
||||
|
||||
public:
|
||||
explicit UnitPropertyComboBox(UnitProperty& property, QWidget* parent = nullptr);
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -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
|
||||
@ -469,4 +473,4 @@ Properties JSONWizard::toProperties(const QJsonObject& object)
|
||||
}
|
||||
}
|
||||
return properties;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 item->widget();
|
||||
}
|
||||
layout()->removeItem(item);
|
||||
delete button;
|
||||
}
|
||||
}
|
||||
|
||||
void RadioPage::setBottomText(const QString& text)
|
||||
{
|
||||
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
|
||||
|
||||
@ -20,6 +20,7 @@ class LuaDoc:
|
||||
DEFAULT_LANGUAGE = 'en-us'
|
||||
FILENAME_INDEX = 'index.html'
|
||||
FILENAME_GLOBALS = 'globals.html'
|
||||
FILENAME_PV = 'pv.html'
|
||||
FILENAME_ENUM = 'enum.html'
|
||||
FILENAME_SET = 'set.html'
|
||||
FILENAME_OBJECT = 'object.html'
|
||||
@ -30,6 +31,7 @@ class LuaDoc:
|
||||
version = None
|
||||
|
||||
def __init__(self, project_root: str) -> None:
|
||||
self._project_root = project_root
|
||||
self._globals = LuaDoc._find_globals(project_root)
|
||||
self._enums = LuaDoc._find_enums(project_root)
|
||||
self._sets = LuaDoc._find_sets(project_root)
|
||||
@ -52,6 +54,7 @@ class LuaDoc:
|
||||
|
||||
definition = self._terms[term]
|
||||
definition = re.sub(r'`(.+?)`', r'<code>\1</code>', definition)
|
||||
definition = re.sub(r'\[([^\]]+)]\(([^\)]+)\)', r'<a href="\2">\1</a>', definition)
|
||||
definition = re.sub(r'{ref:([a-z0-9_\.]+?)(|#[a-z0-9_]+)(|\|.+?)}', self._ref_link, definition)
|
||||
return definition
|
||||
|
||||
@ -100,6 +103,16 @@ class LuaDoc:
|
||||
for object in self._objects:
|
||||
if object['lua_name'] == id:
|
||||
return '<a href="' + object['filename'] + fragment + '">' + (self._get_term(object['name']) if title == '' else title) + '</a>'
|
||||
elif id == 'globals':
|
||||
return '<a href="' + self.FILENAME_GLOBALS + fragment + '">' + (self._get_term('globals:title') if title == '' else title) + '</a>'
|
||||
elif id == 'enum':
|
||||
return '<a href="' + self.FILENAME_ENUM + fragment + '">' + (self._get_term('enum:title') if title == '' else title) + '</a>'
|
||||
elif id == 'set':
|
||||
return '<a href="' + self.FILENAME_SET + fragment + '">' + (self._get_term('set:title') if title == '' else title) + '</a>'
|
||||
elif id == 'object':
|
||||
return '<a href="' + self.FILENAME_OBJECT + fragment + '">' + (self._get_term('object:title') if title == '' else title) + '</a>'
|
||||
elif id == 'pv':
|
||||
return '<a href="' + self.FILENAME_PV + fragment + '">' + (self._get_term('pv:title') if title == '' else title) + '</a>'
|
||||
|
||||
return '<span style="color:red">' + m.group(0) + '</span>'
|
||||
|
||||
@ -347,7 +360,7 @@ class LuaDoc:
|
||||
hpp = LuaDoc._read_file(filename_hpp)
|
||||
cpp = LuaDoc._read_file(filename_cpp) if os.path.exists(filename_cpp) else hpp
|
||||
for cpp_type, cpp_template_type, cpp_item_name in re.findall(r'(Property|VectorProperty|ObjectProperty|ObjectVectorProperty|Method|Event)<(.*?)>\s+([A-Za-z0-9_]+);', hpp):
|
||||
m = re.search(cpp_item_name + r'({|\()\s*[\*]?this\s*,\s*"([a-z0-9_]+)"[^}]*(PropertyFlags::ScriptReadOnly|PropertyFlags::ScriptReadWrite|MethodFlags::ScriptCallable|EventFlags::Scriptable)[^}]*}', cpp)
|
||||
m = re.search(cpp_item_name + r'({|\()\s*[\*]?this\s*,\s*"([a-z0-9_]+)".*?(PropertyFlags::ScriptReadOnly|PropertyFlags::ScriptReadWrite|MethodFlags::ScriptCallable|EventFlags::Scriptable)[^}]*}', cpp)
|
||||
if m is None:
|
||||
continue
|
||||
|
||||
@ -446,6 +459,7 @@ class LuaDoc:
|
||||
|
||||
self._build_index(output_dir)
|
||||
self._build_globals(output_dir, nav)
|
||||
self._build_pv(output_dir, nav)
|
||||
self._build_enums(output_dir, nav)
|
||||
self._build_sets(output_dir, nav)
|
||||
for _, lib in self._libs.items():
|
||||
@ -648,6 +662,30 @@ class LuaDoc:
|
||||
html += self._build_items_html(self._globals, 'globals.')
|
||||
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_GLOBALS), self._add_toc(html))
|
||||
|
||||
def _build_pv(self, output_dir: str, nav: list) -> None:
|
||||
title = self._get_term('pv:title')
|
||||
html = self._get_header(title, nav + [{'title': title, 'href': LuaDoc.FILENAME_PV}])
|
||||
html += '<p>' + self._get_term('pv:paragraph_1') + '</p>' + os.linesep
|
||||
html += '<p>' + self._get_term('pv:paragraph_2') + '</p>' + os.linesep
|
||||
|
||||
html += '<h2 id="storing">' + self._get_term('pv.storing:title') + '</h2>' + os.linesep
|
||||
html += '<p>' + self._get_term('pv.storing:paragraph_1') + '</p>' + os.linesep
|
||||
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'storingpersistentdata.lua'))) + '</code></pre>'
|
||||
|
||||
html += '<h2 id="retrieving">' + self._get_term('pv.retrieving:title') + '</h2>' + os.linesep
|
||||
html += '<p>' + self._get_term('pv.retrieving:paragraph_1') + '</p>' + os.linesep
|
||||
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'retrievingpersistentdata.lua'))) + '</code></pre>'
|
||||
|
||||
html += '<h2 id="deleting">' + self._get_term('pv.deleting:title') + '</h2>' + os.linesep
|
||||
html += '<p>' + self._get_term('pv.deleting:paragraph_1') + '</p>' + os.linesep
|
||||
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'deletingpersistentdata.lua'))) + '</code></pre>'
|
||||
|
||||
html += '<h2 id="checking">' + self._get_term('pv.checking:title') + '</h2>' + os.linesep
|
||||
html += '<p>' + self._get_term('pv.checking:paragraph_1') + '</p>' + os.linesep
|
||||
html += '<pre lang="lua"><code>' + highlight_lua(LuaDoc._read_file(os.path.join(self._project_root, 'manual', 'luadoc', 'example', 'pv', 'checkingforpersistentdata.lua'))) + '</code></pre>'
|
||||
|
||||
LuaDoc._write_file(os.path.join(output_dir, LuaDoc.FILENAME_PV), self._add_toc(html))
|
||||
|
||||
def _build_enums(self, output_dir: str, nav: list) -> None:
|
||||
title = self._get_term('enum:title')
|
||||
nav_enums = nav + [{'title': title, 'href': LuaDoc.FILENAME_ENUM}]
|
||||
@ -708,7 +746,6 @@ class LuaDoc:
|
||||
category['title'] = self._get_term('object.category.' + key + ':title')
|
||||
category['items'] = []
|
||||
for object in category['objects']:
|
||||
print(object)
|
||||
for item in items:
|
||||
if item['id'] == object:
|
||||
category['items'].append(item)
|
||||
@ -854,6 +891,7 @@ class LuaDoc:
|
||||
|
||||
def _get_header(self, title: str, nav: list) -> str:
|
||||
menu = ' <li><a href="' + LuaDoc.FILENAME_GLOBALS + '">' + self._get_term('globals:title') + '</a></li>' + os.linesep
|
||||
menu += ' <li><a href="' + LuaDoc.FILENAME_PV + '">' + self._get_term('pv:title') + '</a></li>' + os.linesep
|
||||
for k in sorted(list(self._libs.keys()) + ['enum', 'set']):
|
||||
if k == 'enum':
|
||||
menu += ' <li><a href="' + LuaDoc.FILENAME_ENUM + '">' + self._get_term('enum:title') + '</a></li>' + os.linesep
|
||||
|
||||
6
manual/luadoc/example/pv/checkingforpersistentdata.lua
Normale Datei
6
manual/luadoc/example/pv/checkingforpersistentdata.lua
Normale Datei
@ -0,0 +1,6 @@
|
||||
if pv.freight_car_1 == nil then
|
||||
pv.freight_car_1 = {
|
||||
cargo = 'none',
|
||||
destination = 'unset'
|
||||
}
|
||||
end
|
||||
4
manual/luadoc/example/pv/deletingpersistentdata.lua
Normale Datei
4
manual/luadoc/example/pv/deletingpersistentdata.lua
Normale Datei
@ -0,0 +1,4 @@
|
||||
pv.number = nil
|
||||
pv.title = nil
|
||||
pv.very_cool = nil
|
||||
pv.freight_car_1 = nil
|
||||
9
manual/luadoc/example/pv/retrievingpersistentdata.lua
Normale Datei
9
manual/luadoc/example/pv/retrievingpersistentdata.lua
Normale Datei
@ -0,0 +1,9 @@
|
||||
log.debug(pv.number)
|
||||
log.debug(pv.title)
|
||||
log.debug(pv.very_cool)
|
||||
|
||||
log.debug(pv.freight_car_1.cargo)
|
||||
|
||||
for k, v in pairs(pv['freight_car_1']) do
|
||||
log.debug(k, v)
|
||||
end
|
||||
8
manual/luadoc/example/pv/storingpersistentdata.lua
Normale Datei
8
manual/luadoc/example/pv/storingpersistentdata.lua
Normale Datei
@ -0,0 +1,8 @@
|
||||
pv.number = 42
|
||||
pv.title = 'Traintastic is awesome!'
|
||||
pv.very_cool = true
|
||||
|
||||
pv['freight_car_1'] = {
|
||||
cargo = 'grain',
|
||||
destination = 'upper yard'
|
||||
}
|
||||
@ -67,6 +67,10 @@
|
||||
"type": "constant",
|
||||
"since": "0.1"
|
||||
},
|
||||
"pv": {
|
||||
"type": "object",
|
||||
"since": "0.3"
|
||||
},
|
||||
"world": {
|
||||
"type": "object",
|
||||
"since": "0.1"
|
||||
@ -102,4 +106,4 @@
|
||||
"type": "library",
|
||||
"since": "0.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
"name": {
|
||||
"since": "0.3"
|
||||
},
|
||||
"trains": {},
|
||||
"on_train_assigned": {
|
||||
"parameters": [
|
||||
{
|
||||
@ -64,4 +65,4 @@
|
||||
],
|
||||
"since": "0.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
{
|
||||
"active_train": {}
|
||||
}
|
||||
"active_train": {},
|
||||
"trains": {}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
"powered": {},
|
||||
"active": {},
|
||||
"mode": {},
|
||||
"blocks": {},
|
||||
"on_block_assigned": {
|
||||
"parameters": [
|
||||
{
|
||||
|
||||
3
manual/luadoc/object/turnoutsliprailtile.json
Normale Datei
3
manual/luadoc/object/turnoutsliprailtile.json
Normale Datei
@ -0,0 +1,3 @@
|
||||
{
|
||||
"dual_motor": {}
|
||||
}
|
||||
@ -2006,5 +2006,77 @@
|
||||
{
|
||||
"term": "object.train.on_block_reserved.parameter.direction:description",
|
||||
"definition": "Train direction from the block perspective, a {ref:enum.block_train_direction} value."
|
||||
},
|
||||
{
|
||||
"term": "object.turnoutsliprailtile.dual_motor:description",
|
||||
"definition": "`true` if the slip turnout has two motors/coils, `false` if it has one motor/coil."
|
||||
},
|
||||
{
|
||||
"term": "object.turnoutrailtile.name:description",
|
||||
"definition": "Turnout name."
|
||||
},
|
||||
{
|
||||
"term": "object.turnoutrailtile.position:description",
|
||||
"definition": "Current turnout position, a {ref:enum.turnout_position} value. To change the turnout position call [`set_position`](#set_position)."
|
||||
},
|
||||
{
|
||||
"term": "object.turnoutrailtile.set_position:description",
|
||||
"definition": "Change turnout position."
|
||||
},
|
||||
{
|
||||
"term": "object.turnoutrailtile.set_position.parameter.position:description",
|
||||
"definition": "Requested turnout position, a {ref:enum.turnout_position} value."
|
||||
},
|
||||
{
|
||||
"term": "object.turnoutrailtile.set_position:return_values",
|
||||
"definition": "`true` if the position is changed, `false` if position value is invalid or turnout is locked e.g. due to a reserved path."
|
||||
},
|
||||
{
|
||||
"term": "globals.pv:description",
|
||||
"definition": "The {ref:pv|persistent variable} table."
|
||||
},
|
||||
{
|
||||
"term": "pv:title",
|
||||
"definition": "Persistent variables"
|
||||
},
|
||||
{
|
||||
"term": "pv:paragraph_1",
|
||||
"definition": "Persistent variables allow you to store and retrieve data that remains available across multiple executions of the Lua script. This can be particularly useful for maintaining state information that needs to be retained beyond the current script's lifetime."
|
||||
},
|
||||
{
|
||||
"term": "pv:paragraph_2",
|
||||
"definition": "The {ref:globals#pv|`pv`} global provides a simple and efficient interface for interacting with persistent data. Any values stored in {ref:globals#pv|`pv`} are saved across script executions and world save and loads. Below is a detailed breakdown of how to use the {ref:globals#pv|`pv`} global."
|
||||
},
|
||||
{
|
||||
"term": "pv.storing:title",
|
||||
"definition": "Storing persistent data"
|
||||
},
|
||||
{
|
||||
"term": "pv.storing:paragraph_1",
|
||||
"definition": "You can store data in {ref:globals#pv|`pv`} just like you would with a regular Lua table. Supported data types are numbers, strings, booleans, tables, {ref:enum|enums}, {ref:set|sets}, {ref:object|objects} and object methods."
|
||||
},
|
||||
{
|
||||
"term": "pv.retrieving:title",
|
||||
"definition": "Retrieving persistent data"
|
||||
},
|
||||
{
|
||||
"term": "pv.retrieving:paragraph_1",
|
||||
"definition": "To retrieve a previously stored value, including tables, access the corresponding key in the {ref:globals#pv|`pv`} global:"
|
||||
},
|
||||
{
|
||||
"term": "pv.deleting:title",
|
||||
"definition": "Deleting persistent data"
|
||||
},
|
||||
{
|
||||
"term": "pv.deleting:paragraph_1",
|
||||
"definition": "To delete a stored persistent value, including tables, simply assign `nil` to the desired key:"
|
||||
},
|
||||
{
|
||||
"term": "pv.checking:title",
|
||||
"definition": "Checking for persistent data"
|
||||
},
|
||||
{
|
||||
"term": "pv.checking:paragraph_1",
|
||||
"definition": "To determine if a persistent variable has been set, use an `if` statement with `nil` checks. Variables in {ref:globals#pv|`pv`} that haven't been initialized or have been deleted will return `nil`. This pattern is useful for initializing default values or handling cases where the persistent variables are cleared."
|
||||
}
|
||||
]
|
||||
|
||||
@ -320,7 +320,8 @@ ul.index-az-nav li a
|
||||
|
||||
/** Lua **********************************************************************/
|
||||
|
||||
pre[lang="lua"]
|
||||
pre[lang="lua"],
|
||||
pre[lang="bash"]
|
||||
{
|
||||
border: solid 1px darkgray;
|
||||
background-color: #f8f8f8;
|
||||
@ -423,7 +424,8 @@ pre[lang="lua"] code span.function
|
||||
border-left-color: blue;
|
||||
}
|
||||
|
||||
pre[lang="lua"]
|
||||
pre[lang="lua"],
|
||||
pre[lang="bash"]
|
||||
{
|
||||
border-color: gray;
|
||||
background-color: #222;
|
||||
|
||||
@ -4,7 +4,7 @@ When Traintastic server is running the Traintastic client can be started.
|
||||
|
||||
**Windows:** The Traintastic client can be started using the desktop icon (if installed) or by selecting *Traintastic* -> *Traintastic client* from the Windows start menu.
|
||||
|
||||
**Linux:** TODO
|
||||
**Linux:** To start Traintastic client, open your desktop environment's application launcher, search for "Traintastic Client," and select it from the list.
|
||||
|
||||
## Connect to the server
|
||||
|
||||
|
||||
@ -2,13 +2,36 @@
|
||||
|
||||
When running Traintastic, the server should be started first.
|
||||
|
||||
**Windows:** The Traintastic server can be started using the desktop icon (if installed) or by selecting *Traintastic* -> *Traintastic server* from the Windows start menu.
|
||||
## Windows
|
||||
The Traintastic server can be started using the desktop icon (if installed) or by selecting *Traintastic* -> *Traintastic server* from the Windows start menu.
|
||||
Traintastic server runs as background process, a Traintastic icon will appear in the system tray next to the clock.
|
||||
A Windows notification is displayed when it is running in the background.
|
||||
Traintastic server can be quit by clicking on the Traintastic icon and selecting *Quit* from the popup menu.
|
||||
|
||||

|
||||
|
||||
**Linux:** TODO
|
||||
## Linux
|
||||
When installing Traintastic server using a Debian package it is installed as systemd service.
|
||||
|
||||
When Traintastic server is running proceed to [start Traintastic client](start-client.md).
|
||||
To start the Traintastic server using systemd, open a terminal and run the command:
|
||||
```bash
|
||||
sudo systemctl start traintastic-server.service
|
||||
```
|
||||
|
||||
To stop the service, use the command:
|
||||
```bash
|
||||
sudo systemctl stop traintastic-server.service
|
||||
```
|
||||
|
||||
Ensure you have the necessary permissions (typically root) to manage systemd services.
|
||||
|
||||
### Auto start on system boot
|
||||
To enable Traintastic server to start automatically at boot, run the following command in a terminal:
|
||||
```bash
|
||||
sudo systemctl enable traintastic-server.service
|
||||
```
|
||||
|
||||
To disable automatic start at boot, run the following command in a terminal:
|
||||
```bash
|
||||
sudo systemctl disable traintastic-server.service
|
||||
```
|
||||
|
||||
@ -25,7 +25,7 @@ def highlight_replace(code: str, css_class: str, clickable_links: bool = False)
|
||||
|
||||
|
||||
def highlight_lua(code: str) -> str:
|
||||
code = re.sub(r'\b(math|table|string|class|enum|set|log|world)\b', r'<span class="global">\1</span>', code) # globals
|
||||
code = re.sub(r'\b(math|table|string|class|enum|set|log|world|pv)\b', r'<span class="global">\1</span>', code) # globals
|
||||
code = re.sub(r'\b([A-Z_][A-Z0-9_]*)\b', r'<span class="const">\1</span>', code) # CONSTANTS
|
||||
code = re.sub(r'\b(and|break|do|else|elseif|end|false|for|function|goto|if|in|local|nil|not|or|repeat|return|then|true|until|while)\b', r'<span class="keyword">\1</span>', code) # keywords
|
||||
code = re.sub(r'\b((|-|\+)[0-9]+(\\.[0-9]*|)((e|E)(|-|\+)[0-9]+|))\b', r'<span class="number">\1</span>', code) # numbers: infloat, decimal
|
||||
|
||||
@ -54,7 +54,7 @@ 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.3\bin\win64\lua53.dll"; 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
|
||||
@ -107,7 +107,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 +179,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 +196,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 +208,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 +221,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.15)
|
||||
include(../shared/traintastic.cmake)
|
||||
project(traintastic-server VERSION ${TRAINTASTIC_VERSION} DESCRIPTION "Traintastic server")
|
||||
include(GNUInstallDirs)
|
||||
@ -32,16 +32,25 @@ target_include_directories(traintastic-server SYSTEM PRIVATE
|
||||
thirdparty)
|
||||
|
||||
if(BUILD_TESTING)
|
||||
add_subdirectory(thirdparty/catch2)
|
||||
set_target_properties(Catch2 PROPERTIES
|
||||
CXX_STANDARD 20
|
||||
CXX_CLANG_TIDY ""
|
||||
)
|
||||
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 20)
|
||||
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)
|
||||
target_include_directories(traintastic-server-test SYSTEM PRIVATE
|
||||
../shared/thirdparty
|
||||
thirdparty)
|
||||
target_link_libraries(traintastic-server-test PRIVATE Catch2::Catch2WithMain)
|
||||
endif()
|
||||
|
||||
file(GLOB SOURCES
|
||||
@ -55,6 +64,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"
|
||||
@ -184,6 +194,30 @@ 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-shared
|
||||
BASE_DIR ../
|
||||
FILES
|
||||
shared/gfx/appicon.ico
|
||||
)
|
||||
|
||||
add_dependencies(traintastic-server resource-shared)
|
||||
if(BUILD_TESTING)
|
||||
add_dependencies(traintastic-server-test resource-shared)
|
||||
endif()
|
||||
|
||||
### OPTIONS ###
|
||||
|
||||
if(NO_LOCALHOST_ONLY_SETTING)
|
||||
@ -215,6 +249,9 @@ if(LINUX)
|
||||
if(BUILD_TESTING)
|
||||
target_link_libraries(traintastic-server-test PRIVATE PkgConfig::LIBSYSTEMD)
|
||||
endif()
|
||||
else()
|
||||
# Use inotify for monitoring serial ports:
|
||||
list(APPEND SOURCES "src/os/linux/serialportlistimplinotify.hpp" "src/os/linux/serialportlistimplinotify.cpp")
|
||||
endif()
|
||||
else()
|
||||
# socket CAN is only available on linux:
|
||||
@ -272,47 +309,12 @@ if(WIN32 AND NOT MSVC)
|
||||
endif()
|
||||
|
||||
# boost
|
||||
if(LINUX)
|
||||
find_package(Boost 1.71 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})
|
||||
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()
|
||||
|
||||
# zlib
|
||||
@ -384,41 +386,41 @@ if(BUILD_TESTING)
|
||||
target_link_libraries(traintastic-server-test PRIVATE ${LibArchive_LIBRARIES})
|
||||
endif()
|
||||
|
||||
# liblua5.3
|
||||
# liblua5.4
|
||||
if(WIN32)
|
||||
add_definitions(-DLUA_BUILD_AS_DLL)
|
||||
set(LUA_INCLUDE_DIR "thirdparty/lua5.3/include")
|
||||
set(LUA_INCLUDE_DIR "thirdparty/lua5.4/include")
|
||||
|
||||
if(MSVC)
|
||||
set(LUA_LIBRARIES lua53)
|
||||
set(LUA_LIBRARIES lua54)
|
||||
add_custom_command(TARGET traintastic-server PRE_LINK
|
||||
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/lua5.3/bin/win64/lua53.def" /out:lua53.lib /machine:x64)
|
||||
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.3/bin/win64/lua53.def" /out:lua53.lib /machine:x64)
|
||||
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.3/bin/win64/lua53.dll")
|
||||
set(LUA_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.dll")
|
||||
endif()
|
||||
|
||||
|
||||
# copy lua53.dll to build directory, to be able to run the tests:
|
||||
# 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.3/bin/win64/lua53.dll" .)
|
||||
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.3/include/lua" # x86_64
|
||||
"/opt/homebrew/opt/lua@5.3/include/lua" # arm64
|
||||
"/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.3 liblua5.3
|
||||
NAMES lua5.4 liblua5.4
|
||||
PATHS
|
||||
"/usr/local/opt/lua@5.3/lib" # x86_64
|
||||
"/opt/homebrew/opt/lua@5.3/lib" # arm64
|
||||
"/usr/local/opt/lua@5.4/lib" # x86_64
|
||||
"/opt/homebrew/opt/lua@5.4/lib" # arm64
|
||||
)
|
||||
else()
|
||||
find_package(Lua 5.3 REQUIRED)
|
||||
find_package(Lua 5.4 REQUIRED)
|
||||
endif()
|
||||
target_include_directories(traintastic-server PRIVATE ${LUA_INCLUDE_DIR})
|
||||
target_link_libraries(traintastic-server PRIVATE ${LUA_LIBRARIES})
|
||||
@ -457,8 +459,7 @@ endif()
|
||||
|
||||
if(BUILD_TESTING)
|
||||
include(Catch)
|
||||
target_include_directories(traintastic-server-test PRIVATE thirdparty/catch2)
|
||||
catch_discover_tests(traintastic-server-test)
|
||||
catch_discover_tests(traintastic-server-test DISCOVERY_MODE PRE_TEST)
|
||||
endif()
|
||||
|
||||
### Doxygen ###
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
# 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.
|
||||
@ -41,7 +42,7 @@ SET(_POSSIBLE_LUA_LIBRARY lua)
|
||||
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 "53" "5.3" "-5.3" "52" "5.2" "-5.2" "51" "5.1" "-5.1")
|
||||
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
|
||||
|
||||
@ -1,206 +0,0 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
Catch
|
||||
-----
|
||||
|
||||
This module defines a function to help use the Catch test framework.
|
||||
|
||||
The :command:`catch_discover_tests` discovers tests by asking the compiled test
|
||||
executable to enumerate its tests. This does not require CMake to be re-run
|
||||
when tests change. However, it may not work in a cross-compiling environment,
|
||||
and setting test properties is less convenient.
|
||||
|
||||
This command is intended to replace use of :command:`add_test` to register
|
||||
tests, and will create a separate CTest test for each Catch test case. Note
|
||||
that this is in some cases less efficient, as common set-up and tear-down logic
|
||||
cannot be shared by multiple test cases executing in the same instance.
|
||||
However, it provides more fine-grained pass/fail information to CTest, which is
|
||||
usually considered as more beneficial. By default, the CTest test name is the
|
||||
same as the Catch name; see also ``TEST_PREFIX`` and ``TEST_SUFFIX``.
|
||||
|
||||
.. command:: catch_discover_tests
|
||||
|
||||
Automatically add tests with CTest by querying the compiled test executable
|
||||
for available tests::
|
||||
|
||||
catch_discover_tests(target
|
||||
[TEST_SPEC arg1...]
|
||||
[EXTRA_ARGS arg1...]
|
||||
[WORKING_DIRECTORY dir]
|
||||
[TEST_PREFIX prefix]
|
||||
[TEST_SUFFIX suffix]
|
||||
[PROPERTIES name1 value1...]
|
||||
[TEST_LIST var]
|
||||
[REPORTER reporter]
|
||||
[OUTPUT_DIR dir]
|
||||
[OUTPUT_PREFIX prefix}
|
||||
[OUTPUT_SUFFIX suffix]
|
||||
)
|
||||
|
||||
``catch_discover_tests`` sets up a post-build command on the test executable
|
||||
that generates the list of tests by parsing the output from running the test
|
||||
with the ``--list-test-names-only`` argument. This ensures that the full
|
||||
list of tests is obtained. Since test discovery occurs at build time, it is
|
||||
not necessary to re-run CMake when the list of tests changes.
|
||||
However, it requires that :prop_tgt:`CROSSCOMPILING_EMULATOR` is properly set
|
||||
in order to function in a cross-compiling environment.
|
||||
|
||||
Additionally, setting properties on tests is somewhat less convenient, since
|
||||
the tests are not available at CMake time. Additional test properties may be
|
||||
assigned to the set of tests as a whole using the ``PROPERTIES`` option. If
|
||||
more fine-grained test control is needed, custom content may be provided
|
||||
through an external CTest script using the :prop_dir:`TEST_INCLUDE_FILES`
|
||||
directory property. The set of discovered tests is made accessible to such a
|
||||
script via the ``<target>_TESTS`` variable.
|
||||
|
||||
The options are:
|
||||
|
||||
``target``
|
||||
Specifies the Catch executable, which must be a known CMake executable
|
||||
target. CMake will substitute the location of the built executable when
|
||||
running the test.
|
||||
|
||||
``TEST_SPEC arg1...``
|
||||
Specifies test cases, wildcarded test cases, tags and tag expressions to
|
||||
pass to the Catch executable with the ``--list-test-names-only`` argument.
|
||||
|
||||
``EXTRA_ARGS arg1...``
|
||||
Any extra arguments to pass on the command line to each test case.
|
||||
|
||||
``WORKING_DIRECTORY dir``
|
||||
Specifies the directory in which to run the discovered test cases. If this
|
||||
option is not provided, the current binary directory is used.
|
||||
|
||||
``TEST_PREFIX prefix``
|
||||
Specifies a ``prefix`` to be prepended to the name of each discovered test
|
||||
case. This can be useful when the same test executable is being used in
|
||||
multiple calls to ``catch_discover_tests()`` but with different
|
||||
``TEST_SPEC`` or ``EXTRA_ARGS``.
|
||||
|
||||
``TEST_SUFFIX suffix``
|
||||
Similar to ``TEST_PREFIX`` except the ``suffix`` is appended to the name of
|
||||
every discovered test case. Both ``TEST_PREFIX`` and ``TEST_SUFFIX`` may
|
||||
be specified.
|
||||
|
||||
``PROPERTIES name1 value1...``
|
||||
Specifies additional properties to be set on all tests discovered by this
|
||||
invocation of ``catch_discover_tests``.
|
||||
|
||||
``TEST_LIST var``
|
||||
Make the list of tests available in the variable ``var``, rather than the
|
||||
default ``<target>_TESTS``. This can be useful when the same test
|
||||
executable is being used in multiple calls to ``catch_discover_tests()``.
|
||||
Note that this variable is only available in CTest.
|
||||
|
||||
``REPORTER reporter``
|
||||
Use the specified reporter when running the test case. The reporter will
|
||||
be passed to the Catch executable as ``--reporter reporter``.
|
||||
|
||||
``OUTPUT_DIR dir``
|
||||
If specified, the parameter is passed along as
|
||||
``--out dir/<test_name>`` to Catch executable. The actual file name is the
|
||||
same as the test name. This should be used instead of
|
||||
``EXTRA_ARGS --out foo`` to avoid race conditions writing the result output
|
||||
when using parallel test execution.
|
||||
|
||||
``OUTPUT_PREFIX prefix``
|
||||
May be used in conjunction with ``OUTPUT_DIR``.
|
||||
If specified, ``prefix`` is added to each output file name, like so
|
||||
``--out dir/prefix<test_name>``.
|
||||
|
||||
``OUTPUT_SUFFIX suffix``
|
||||
May be used in conjunction with ``OUTPUT_DIR``.
|
||||
If specified, ``suffix`` is added to each output file name, like so
|
||||
``--out dir/<test_name>suffix``. This can be used to add a file extension to
|
||||
the output e.g. ".xml".
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
#------------------------------------------------------------------------------
|
||||
function(catch_discover_tests TARGET)
|
||||
cmake_parse_arguments(
|
||||
""
|
||||
""
|
||||
"TEST_PREFIX;TEST_SUFFIX;WORKING_DIRECTORY;TEST_LIST;REPORTER;OUTPUT_DIR;OUTPUT_PREFIX;OUTPUT_SUFFIX"
|
||||
"TEST_SPEC;EXTRA_ARGS;PROPERTIES"
|
||||
${ARGN}
|
||||
)
|
||||
|
||||
if(NOT _WORKING_DIRECTORY)
|
||||
set(_WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}")
|
||||
endif()
|
||||
if(NOT _TEST_LIST)
|
||||
set(_TEST_LIST ${TARGET}_TESTS)
|
||||
endif()
|
||||
|
||||
## Generate a unique name based on the extra arguments
|
||||
string(SHA1 args_hash "${_TEST_SPEC} ${_EXTRA_ARGS} ${_REPORTER} ${_OUTPUT_DIR} ${_OUTPUT_PREFIX} ${_OUTPUT_SUFFIX}")
|
||||
string(SUBSTRING ${args_hash} 0 7 args_hash)
|
||||
|
||||
# Define rule to generate test list for aforementioned test executable
|
||||
set(ctest_include_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_include-${args_hash}.cmake")
|
||||
set(ctest_tests_file "${CMAKE_CURRENT_BINARY_DIR}/${TARGET}_tests-${args_hash}.cmake")
|
||||
get_property(crosscompiling_emulator
|
||||
TARGET ${TARGET}
|
||||
PROPERTY CROSSCOMPILING_EMULATOR
|
||||
)
|
||||
add_custom_command(
|
||||
TARGET ${TARGET} POST_BUILD
|
||||
BYPRODUCTS "${ctest_tests_file}"
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
-D "TEST_TARGET=${TARGET}"
|
||||
-D "TEST_EXECUTABLE=$<TARGET_FILE:${TARGET}>"
|
||||
-D "TEST_EXECUTOR=${crosscompiling_emulator}"
|
||||
-D "TEST_WORKING_DIR=${_WORKING_DIRECTORY}"
|
||||
-D "TEST_SPEC=${_TEST_SPEC}"
|
||||
-D "TEST_EXTRA_ARGS=${_EXTRA_ARGS}"
|
||||
-D "TEST_PROPERTIES=${_PROPERTIES}"
|
||||
-D "TEST_PREFIX=${_TEST_PREFIX}"
|
||||
-D "TEST_SUFFIX=${_TEST_SUFFIX}"
|
||||
-D "TEST_LIST=${_TEST_LIST}"
|
||||
-D "TEST_REPORTER=${_REPORTER}"
|
||||
-D "TEST_OUTPUT_DIR=${_OUTPUT_DIR}"
|
||||
-D "TEST_OUTPUT_PREFIX=${_OUTPUT_PREFIX}"
|
||||
-D "TEST_OUTPUT_SUFFIX=${_OUTPUT_SUFFIX}"
|
||||
-D "CTEST_FILE=${ctest_tests_file}"
|
||||
-P "${_CATCH_DISCOVER_TESTS_SCRIPT}"
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
file(WRITE "${ctest_include_file}"
|
||||
"if(EXISTS \"${ctest_tests_file}\")\n"
|
||||
" include(\"${ctest_tests_file}\")\n"
|
||||
"else()\n"
|
||||
" add_test(${TARGET}_NOT_BUILT-${args_hash} ${TARGET}_NOT_BUILT-${args_hash})\n"
|
||||
"endif()\n"
|
||||
)
|
||||
|
||||
if(NOT ${CMAKE_VERSION} VERSION_LESS "3.10.0")
|
||||
# Add discovered tests to directory TEST_INCLUDE_FILES
|
||||
set_property(DIRECTORY
|
||||
APPEND PROPERTY TEST_INCLUDE_FILES "${ctest_include_file}"
|
||||
)
|
||||
else()
|
||||
# Add discovered tests as directory TEST_INCLUDE_FILE if possible
|
||||
get_property(test_include_file_set DIRECTORY PROPERTY TEST_INCLUDE_FILE SET)
|
||||
if (NOT ${test_include_file_set})
|
||||
set_property(DIRECTORY
|
||||
PROPERTY TEST_INCLUDE_FILE "${ctest_include_file}"
|
||||
)
|
||||
else()
|
||||
message(FATAL_ERROR
|
||||
"Cannot set more than one TEST_INCLUDE_FILE"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
endfunction()
|
||||
|
||||
###############################################################################
|
||||
|
||||
set(_CATCH_DISCOVER_TESTS_SCRIPT
|
||||
${CMAKE_CURRENT_LIST_DIR}/CatchAddTests.cmake
|
||||
CACHE INTERNAL "Catch2 full path to CatchAddTests.cmake helper file"
|
||||
)
|
||||
@ -1,132 +0,0 @@
|
||||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
set(prefix "${TEST_PREFIX}")
|
||||
set(suffix "${TEST_SUFFIX}")
|
||||
set(spec ${TEST_SPEC})
|
||||
set(extra_args ${TEST_EXTRA_ARGS})
|
||||
set(properties ${TEST_PROPERTIES})
|
||||
set(reporter ${TEST_REPORTER})
|
||||
set(output_dir ${TEST_OUTPUT_DIR})
|
||||
set(output_prefix ${TEST_OUTPUT_PREFIX})
|
||||
set(output_suffix ${TEST_OUTPUT_SUFFIX})
|
||||
set(script)
|
||||
set(suite)
|
||||
set(tests)
|
||||
|
||||
function(add_command NAME)
|
||||
set(_args "")
|
||||
foreach(_arg ${ARGN})
|
||||
if(_arg MATCHES "[^-./:a-zA-Z0-9_]")
|
||||
set(_args "${_args} [==[${_arg}]==]") # form a bracket_argument
|
||||
else()
|
||||
set(_args "${_args} ${_arg}")
|
||||
endif()
|
||||
endforeach()
|
||||
set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Run test executable to get list of available tests
|
||||
if(NOT EXISTS "${TEST_EXECUTABLE}")
|
||||
message(FATAL_ERROR
|
||||
"Specified test executable '${TEST_EXECUTABLE}' does not exist"
|
||||
)
|
||||
endif()
|
||||
execute_process(
|
||||
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-test-names-only
|
||||
OUTPUT_VARIABLE output
|
||||
RESULT_VARIABLE result
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
)
|
||||
# Catch --list-test-names-only reports the number of tests, so 0 is... surprising
|
||||
if(${result} EQUAL 0)
|
||||
message(WARNING
|
||||
"Test executable '${TEST_EXECUTABLE}' contains no tests!\n"
|
||||
)
|
||||
elseif(${result} LESS 0)
|
||||
message(FATAL_ERROR
|
||||
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||
" Result: ${result}\n"
|
||||
" Output: ${output}\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
string(REPLACE "\n" ";" output "${output}")
|
||||
|
||||
# Run test executable to get list of available reporters
|
||||
execute_process(
|
||||
COMMAND ${TEST_EXECUTOR} "${TEST_EXECUTABLE}" ${spec} --list-reporters
|
||||
OUTPUT_VARIABLE reporters_output
|
||||
RESULT_VARIABLE reporters_result
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
)
|
||||
if(${reporters_result} EQUAL 0)
|
||||
message(WARNING
|
||||
"Test executable '${TEST_EXECUTABLE}' contains no reporters!\n"
|
||||
)
|
||||
elseif(${reporters_result} LESS 0)
|
||||
message(FATAL_ERROR
|
||||
"Error running test executable '${TEST_EXECUTABLE}':\n"
|
||||
" Result: ${reporters_result}\n"
|
||||
" Output: ${reporters_output}\n"
|
||||
)
|
||||
endif()
|
||||
string(FIND "${reporters_output}" "${reporter}" reporter_is_valid)
|
||||
if(reporter AND ${reporter_is_valid} EQUAL -1)
|
||||
message(FATAL_ERROR
|
||||
"\"${reporter}\" is not a valid reporter!\n"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Prepare reporter
|
||||
if(reporter)
|
||||
set(reporter_arg "--reporter ${reporter}")
|
||||
endif()
|
||||
|
||||
# Prepare output dir
|
||||
if(output_dir AND NOT IS_ABSOLUTE ${output_dir})
|
||||
set(output_dir "${TEST_WORKING_DIR}/${output_dir}")
|
||||
if(NOT EXISTS ${output_dir})
|
||||
file(MAKE_DIRECTORY ${output_dir})
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Parse output
|
||||
foreach(line ${output})
|
||||
set(test ${line})
|
||||
# Escape characters in test case names that would be parsed by Catch2
|
||||
set(test_name ${test})
|
||||
foreach(char , [ ])
|
||||
string(REPLACE ${char} "\\${char}" test_name ${test_name})
|
||||
endforeach(char)
|
||||
# ...add output dir
|
||||
if(output_dir)
|
||||
string(REGEX REPLACE "[^A-Za-z0-9_]" "_" test_name_clean ${test_name})
|
||||
set(output_dir_arg "--out ${output_dir}/${output_prefix}${test_name_clean}${output_suffix}")
|
||||
endif()
|
||||
|
||||
# ...and add to script
|
||||
add_command(add_test
|
||||
"${prefix}${test}${suffix}"
|
||||
${TEST_EXECUTOR}
|
||||
"${TEST_EXECUTABLE}"
|
||||
"${test_name}"
|
||||
${extra_args}
|
||||
"${reporter_arg}"
|
||||
"${output_dir_arg}"
|
||||
)
|
||||
add_command(set_tests_properties
|
||||
"${prefix}${test}${suffix}"
|
||||
PROPERTIES
|
||||
WORKING_DIRECTORY "${TEST_WORKING_DIR}"
|
||||
${properties}
|
||||
)
|
||||
list(APPEND tests "${prefix}${test}${suffix}")
|
||||
endforeach()
|
||||
|
||||
# Create a list of all discovered tests, which users may use to e.g. set
|
||||
# properties on the tests
|
||||
add_command(set ${TEST_LIST} ${tests})
|
||||
|
||||
# Write CTest script
|
||||
file(WRITE "${CTEST_FILE}" "${script}")
|
||||
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}
|
||||
|
||||
86
server/cmake/generateresourceheader.py
Normale Datei
86
server/cmake/generateresourceheader.py
Normale Datei
@ -0,0 +1,86 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
|
||||
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]
|
||||
|
||||
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') 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
|
||||
''')
|
||||
@ -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
|
||||
@ -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) :
|
||||
@ -53,7 +56,7 @@ Board::Board(World& world, std::string_view _id) :
|
||||
{
|
||||
const TileRotate tileRotate = it->second->rotate;
|
||||
|
||||
if(it->second->tileId() == TileId::RailStraight && tileClassId == StraightRailTile::classId) // merge to bridge
|
||||
if(it->second->tileId == TileId::RailStraight && tileClassId == StraightRailTile::classId) // merge to bridge
|
||||
{
|
||||
if((tileRotate == rotate + TileRotate::Deg90 || tileRotate == rotate - TileRotate::Deg90) && deleteTile(x, y))
|
||||
{
|
||||
@ -73,7 +76,7 @@ Board::Board(World& world, std::string_view _id) :
|
||||
else
|
||||
return false;
|
||||
}
|
||||
else if(it->second->tileId() == TileId::RailStraight && // replace straight by a straight with something extra
|
||||
else if(it->second->tileId == TileId::RailStraight && // replace straight by a straight with something extra
|
||||
Tiles::canUpgradeStraightRail(tileClassId) &&
|
||||
(tileRotate == rotate || (tileRotate + TileRotate::Deg180) == rotate) &&
|
||||
deleteTile(x, y))
|
||||
@ -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-2023 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
|
||||
@ -84,7 +84,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 +97,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)
|
||||
{
|
||||
@ -238,7 +238,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
|
||||
// | \| |/
|
||||
@ -257,7 +257,7 @@ std::unique_ptr<const AbstractSignalPath::Item> AbstractSignalPath::findBlocks(c
|
||||
if(auto linkNode = linkTile->link->node())
|
||||
return findBlocks(linkNode->get(), linkNode->get().getLink(0), blocksAhead);
|
||||
}
|
||||
else if(tile->tileId() != TileId::RailBufferStop)
|
||||
else if(tile->tileId != TileId::RailBufferStop)
|
||||
{
|
||||
if(const auto& nextLink = otherLink(nextNode, link))
|
||||
{
|
||||
|
||||
@ -181,13 +181,13 @@ 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());
|
||||
|
||||
@ -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
|
||||
@ -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"
|
||||
@ -33,6 +34,8 @@
|
||||
#include "../tile/rail/turnout/turnoutrailtile.hpp"
|
||||
#include "../tile/rail/linkrailtile.hpp"
|
||||
#include "../tile/rail/nxbuttonrailtile.hpp"
|
||||
#include "../../train/trainblockstatus.hpp"
|
||||
#include "../../core/eventloop.hpp"
|
||||
#include "../../core/objectproperty.tpp"
|
||||
#include "../../enum/bridgepath.hpp"
|
||||
|
||||
@ -101,11 +104,11 @@ std::vector<std::shared_ptr<BlockPath>> BlockPath::find(BlockRailTile& startBloc
|
||||
const auto& nextNode = current.link->getNext(*current.node);
|
||||
auto& tile = nextNode.tile();
|
||||
|
||||
switch(tile.tileId())
|
||||
switch(tile.tileId.value())
|
||||
{
|
||||
case TileId::RailBlock:
|
||||
{
|
||||
if(current.node->tile().tileId() == TileId::RailNXButton)
|
||||
if(current.node->tile().tileId == TileId::RailNXButton)
|
||||
{
|
||||
current.path->m_nxButtonTo = current.node->tile().shared_ptr<NXButtonRailTile>();
|
||||
}
|
||||
@ -282,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!
|
||||
@ -296,9 +324,34 @@ BlockPath::BlockPath(BlockRailTile& block, BlockSide side)
|
||||
: m_fromBlock{block}
|
||||
, m_fromSide{side}
|
||||
, m_toSide{static_cast<BlockSide>(-1)}
|
||||
, m_delayReleaseTimer{EventLoop::ioContext}
|
||||
, m_isReserved(false)
|
||||
, m_delayedReleaseScheduled(false)
|
||||
{
|
||||
}
|
||||
|
||||
BlockPath::BlockPath(const BlockPath &other)
|
||||
: Path(other)
|
||||
, 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)
|
||||
, m_toSide(other.m_toSide)
|
||||
, m_tiles(other.m_tiles)
|
||||
, m_turnouts(other.m_turnouts)
|
||||
, m_directionControls(other.m_directionControls)
|
||||
, m_crossings(other.m_crossings)
|
||||
, m_bridges(other.m_bridges)
|
||||
, m_signals(other.m_signals)
|
||||
, m_nxButtonFrom(other.m_nxButtonFrom)
|
||||
, m_nxButtonTo(other.m_nxButtonTo)
|
||||
, m_delayReleaseTimer{EventLoop::ioContext}
|
||||
, m_isReserved(false)
|
||||
, m_delayedReleaseScheduled(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool BlockPath::operator ==(const BlockPath& other) const noexcept
|
||||
{
|
||||
return
|
||||
@ -331,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]]*/
|
||||
@ -387,7 +440,7 @@ bool BlockPath::reserve(const std::shared_ptr<Train>& train, bool dryRun)
|
||||
{
|
||||
if(auto turnout = turnoutWeak.lock())
|
||||
{
|
||||
if(!turnout->reserve(position, dryRun))
|
||||
if(!turnout->reserve(shared_from_this(), position, dryRun))
|
||||
{
|
||||
assert(dryRun);
|
||||
return false;
|
||||
@ -434,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())
|
||||
@ -489,6 +559,9 @@ bool BlockPath::reserve(const std::shared_ptr<Train>& train, bool dryRun)
|
||||
}
|
||||
}
|
||||
|
||||
if(!dryRun)
|
||||
m_isReserved = true;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -499,22 +572,47 @@ bool BlockPath::release(bool dryRun)
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!dryRun)
|
||||
m_delayReleaseTimer.cancel();
|
||||
|
||||
auto toBlock = m_toBlock.lock();
|
||||
if(!toBlock) /*[[unlikely]]*/
|
||||
return false;
|
||||
|
||||
BlockState fromState = m_fromBlock.state.value();
|
||||
BlockState toState = toBlock->state.value();
|
||||
|
||||
if((fromState == BlockState::Occupied || fromState == BlockState::Unknown)
|
||||
&& (toState == BlockState::Occupied || toState == BlockState::Unknown)
|
||||
&& !m_fromBlock.trains.empty() && !toBlock->trains.empty())
|
||||
{
|
||||
// Check if train head is beyond toBlock while its end is still in fromBlock
|
||||
const auto& status1 = fromSide() == BlockSide::A ? m_fromBlock.trains.front() : m_fromBlock.trains.back();
|
||||
const auto& status2 = toSide() == BlockSide::A ? toBlock->trains.front() : toBlock->trains.back();
|
||||
|
||||
if(status1->train.value() == status2->train.value())
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!m_fromBlock.release(m_fromSide, dryRun))
|
||||
{
|
||||
assert(dryRun);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(auto toBlock = m_toBlock.lock()) /*[[likely]]*/
|
||||
if(!dryRun && toBlock->state.value() == BlockState::Reserved)
|
||||
{
|
||||
if(!toBlock->release(m_toSide, dryRun))
|
||||
if(toBlock->trains.size() == 1)
|
||||
{
|
||||
assert(dryRun);
|
||||
return false;
|
||||
//TODO: this bypasses some checks
|
||||
toBlock->removeTrainInternal(toBlock->trains[0]);
|
||||
//TODO: dryRun? what if it fails?
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
if(!toBlock->release(m_toSide, dryRun))
|
||||
{
|
||||
assert(dryRun);
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -597,5 +695,28 @@ bool BlockPath::release(bool dryRun)
|
||||
}
|
||||
}
|
||||
|
||||
if(!dryRun)
|
||||
m_isReserved = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlockPath::delayedRelease(uint16_t timeoutMillis)
|
||||
{
|
||||
if(m_delayedReleaseScheduled)
|
||||
return false;
|
||||
|
||||
m_delayedReleaseScheduled = true;
|
||||
|
||||
m_delayReleaseTimer.expires_after(boost::asio::chrono::milliseconds(timeoutMillis));
|
||||
m_delayReleaseTimer.async_wait([this](const boost::system::error_code& ec)
|
||||
{
|
||||
m_delayedReleaseScheduled = false;
|
||||
|
||||
if(ec)
|
||||
return;
|
||||
|
||||
release();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -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
|
||||
@ -29,6 +29,7 @@
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <utility>
|
||||
#include <boost/asio/steady_timer.hpp>
|
||||
#include "../../enum/blockside.hpp"
|
||||
|
||||
class RailTile;
|
||||
@ -36,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;
|
||||
@ -59,15 +61,21 @@ 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;
|
||||
std::weak_ptr<NXButtonRailTile> m_nxButtonTo;
|
||||
|
||||
boost::asio::steady_timer m_delayReleaseTimer;
|
||||
bool m_isReserved;
|
||||
bool m_delayedReleaseScheduled;
|
||||
|
||||
public:
|
||||
static std::vector<std::shared_ptr<BlockPath>> find(BlockRailTile& block);
|
||||
|
||||
BlockPath(BlockRailTile& block, BlockSide side);
|
||||
BlockPath(const BlockPath& other);
|
||||
|
||||
bool operator ==(const BlockPath& other) const noexcept;
|
||||
|
||||
@ -99,11 +107,17 @@ class BlockPath : public Path, public std::enable_shared_from_this<BlockPath>
|
||||
return m_toSide;
|
||||
}
|
||||
|
||||
inline bool isReserved() const
|
||||
{
|
||||
return m_isReserved;
|
||||
}
|
||||
|
||||
std::shared_ptr<NXButtonRailTile> nxButtonFrom() const;
|
||||
std::shared_ptr<NXButtonRailTile> nxButtonTo() const;
|
||||
|
||||
bool reserve(const std::shared_ptr<Train>& train, bool dryRun = false);
|
||||
bool release(bool dryRun = false);
|
||||
bool delayedRelease(uint16_t timeoutMillis);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -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 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
|
||||
@ -45,7 +45,7 @@ tcb::span<const Path::TurnoutPositionLink> Path::getTurnoutLinks(TurnoutRailTile
|
||||
|
||||
const auto& node = turnout.node()->get();
|
||||
|
||||
switch(turnout.tileId())
|
||||
switch(turnout.tileId.value())
|
||||
{
|
||||
case TileId::RailTurnoutLeft45:
|
||||
case TileId::RailTurnoutLeft90:
|
||||
|
||||
@ -92,12 +92,21 @@ 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)
|
||||
{
|
||||
LOG_DEBUG("Path found:", path->fromBlock().name.value(), "->", path->toBlock()->name.value());
|
||||
|
||||
if(path->isReserved())
|
||||
{
|
||||
// If user clicked an already reserved path we release it.
|
||||
// TODO: make some logic to prevent releasing a path while train is inside it?
|
||||
// Also releasing a path when we already set signal to "Proceed" is dangerous because train might
|
||||
// have already started moving towards out path
|
||||
return path->release();
|
||||
}
|
||||
|
||||
if(from.block->trains.empty())
|
||||
{
|
||||
continue; // no train in from block
|
||||
|
||||
@ -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
|
||||
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* server/src/enum/worldevent.hpp
|
||||
* server/src/board/tile/hidden/hiddentile.hpp
|
||||
*
|
||||
* This file is part of the traintastic source code.
|
||||
*
|
||||
* Copyright (C) 2019-2020 Reinder Feenstra
|
||||
* 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
|
||||
@ -20,9 +20,18 @@
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
*/
|
||||
|
||||
#ifndef TRAINTASTIC_SERVER_ENUM_WORLDEVENT_HPP
|
||||
#define TRAINTASTIC_SERVER_ENUM_WORLDEVENT_HPP
|
||||
#ifndef TRAINTASTIC_SERVER_BOARD_TILE_HIDDEN_HIDDENTILE_HPP
|
||||
#define TRAINTASTIC_SERVER_BOARD_TILE_HIDDEN_HIDDENTILE_HPP
|
||||
|
||||
#include <traintastic/enum/worldevent.hpp>
|
||||
#include "../tile.hpp"
|
||||
|
||||
class HiddenTile : public Tile
|
||||
{
|
||||
protected:
|
||||
HiddenTile(World& world, TileId tileId_)
|
||||
: Tile(world, {}, tileId_)
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
@ -89,6 +89,18 @@ SwitchTile::SwitchTile(World& world, std::string_view _id)
|
||||
m_interfaceItems.add(onValueChanged);
|
||||
}
|
||||
|
||||
void SwitchTile::destroying()
|
||||
{
|
||||
outputMap->parentObject.setValueInternal(nullptr);
|
||||
Tile::destroying();
|
||||
}
|
||||
|
||||
void SwitchTile::addToWorld()
|
||||
{
|
||||
outputMap->parentObject.setValueInternal(shared_from_this());
|
||||
Tile::addToWorld();
|
||||
}
|
||||
|
||||
void SwitchTile::worldEvent(WorldState worldState, WorldEvent worldEvent)
|
||||
{
|
||||
Tile::worldEvent(worldState, worldEvent);
|
||||
|
||||
@ -38,6 +38,8 @@ class SwitchTile : public Tile
|
||||
CREATE_DEF(SwitchTile)
|
||||
|
||||
protected:
|
||||
void destroying() override;
|
||||
void addToWorld() final;
|
||||
void worldEvent(WorldState worldState, WorldEvent worldEvent) final;
|
||||
|
||||
public:
|
||||
|
||||
@ -133,35 +133,7 @@ BlockRailTile::BlockRailTile(World& world, std::string_view _id) :
|
||||
throw LogMessageException(LogMessage::E3006_CANT_REMOVE_TRAIN_TRAIN_CAN_ONLY_BE_REMOVED_FROM_HEAD_OR_TAIL_BLOCK);
|
||||
}
|
||||
|
||||
status->destroy();
|
||||
status.reset();
|
||||
|
||||
updateTrainMethodEnabled();
|
||||
if(state == BlockState::Reserved)
|
||||
updateState();
|
||||
Log::log(*this, LogMessage::N3002_REMOVED_TRAIN_X_FROM_BLOCK_X, oldTrain->name.value(), name.value());
|
||||
|
||||
if(oldTrain->blocks.empty())
|
||||
{
|
||||
oldTrain->active = false;
|
||||
}
|
||||
|
||||
if(m_world.simulation)
|
||||
{
|
||||
for(const auto& item : *inputMap)
|
||||
{
|
||||
if(item->input && item->input->interface)
|
||||
{
|
||||
if(item->type == SensorType::OccupancyDetector)
|
||||
item->input->simulateChange(item->invert.value() ? SimulateInputAction::SetTrue : SimulateInputAction::SetFalse);
|
||||
else
|
||||
assert(false); // not yet implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldTrain->fireBlockRemoved(shared_ptr<BlockRailTile>());
|
||||
fireEvent(onTrainRemoved, oldTrain, self);
|
||||
removeTrainInternal(status);
|
||||
}
|
||||
}}
|
||||
, flipTrain{*this, "flip_train",
|
||||
@ -285,11 +257,9 @@ 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;
|
||||
}
|
||||
|
||||
train = status->train.value();
|
||||
direction = path->toSide() == BlockSide::A ? BlockTrainDirection::TowardsB : BlockTrainDirection::TowardsA;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -312,7 +282,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
|
||||
@ -374,6 +344,7 @@ void BlockRailTile::identificationEvent(BlockInputMapItem& /*item*/, Identificat
|
||||
case IdentificationEventType::Present:
|
||||
//!< \todo assign train (if allowed and possible)
|
||||
trains.appendInternal(TrainBlockStatus::create(*this, std::string("#").append(std::to_string(identifier)), blockDirection));
|
||||
updateTrainMethodEnabled();
|
||||
if(state == BlockState::Free || state == BlockState::Unknown)
|
||||
updateState();
|
||||
break;
|
||||
@ -409,7 +380,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();
|
||||
@ -475,6 +446,46 @@ bool BlockRailTile::release(BlockSide side, bool dryRun)
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BlockRailTile::removeTrainInternal(const std::shared_ptr<TrainBlockStatus> &status)
|
||||
{
|
||||
if(!status)
|
||||
return false;
|
||||
|
||||
const auto self = shared_ptr<BlockRailTile>();
|
||||
const std::shared_ptr<Train> oldTrain = status->train.value();
|
||||
|
||||
status->destroy();
|
||||
|
||||
updateTrainMethodEnabled();
|
||||
if(state == BlockState::Reserved)
|
||||
updateState();
|
||||
Log::log(*this, LogMessage::N3002_REMOVED_TRAIN_X_FROM_BLOCK_X, oldTrain->name.value(), name.value());
|
||||
|
||||
if(oldTrain->blocks.empty())
|
||||
{
|
||||
oldTrain->active = false;
|
||||
}
|
||||
|
||||
if(m_world.simulation)
|
||||
{
|
||||
for(const auto& item : *inputMap)
|
||||
{
|
||||
if(item->input && item->input->interface)
|
||||
{
|
||||
if(item->type == SensorType::OccupancyDetector)
|
||||
item->input->simulateChange(item->invert.value() ? SimulateInputAction::SetTrue : SimulateInputAction::SetFalse);
|
||||
else
|
||||
assert(false); // not yet implemented
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
oldTrain->fireBlockRemoved(shared_ptr<BlockRailTile>());
|
||||
fireEvent(onTrainRemoved, oldTrain, self);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BlockRailTile::updateState()
|
||||
{
|
||||
if(!inputMap->items.empty())
|
||||
|
||||
@ -107,9 +107,11 @@ 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);
|
||||
|
||||
bool removeTrainInternal(const std::shared_ptr<TrainBlockStatus>& status);
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@ -53,6 +53,7 @@ bool CrossRailTile::release(bool dryRun)
|
||||
|
||||
if(!dryRun)
|
||||
{
|
||||
m_crossState = CrossState::Unset;
|
||||
RailTile::release();
|
||||
}
|
||||
return true;
|
||||
|
||||
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