Merge remote-tracking branch 'origin/master' into 144-add-zone-support

Dieser Commit ist enthalten in:
Reinder Feenstra 2025-04-07 22:39:09 +02:00
Commit 8ee1e9bc6e
4529 geänderte Dateien mit 12925 neuen und 872278 gelöschten Zeilen

Datei anzeigen

@ -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}

Datei anzeigen

@ -20,16 +20,6 @@ jobs:
build_deb: false
defines: ""
- name: "ubuntu_22.04"
os: ubuntu-22.04
generator: "Unix Makefiles"
arch: ""
target: traintastic-client
jobs: 4
build_type: Release
build_deb: true
defines: ""
- name: "ubuntu_24.04"
os: ubuntu-24.04
generator: "Unix Makefiles"
@ -70,26 +60,6 @@ jobs:
build_deb: false
defines: ""
- name: "macos-14"
os: "macos-14"
generator: "Unix Makefiles"
arch: ""
target: traintastic-client
jobs: 3
build_type: Release
build_deb: false
defines: ""
- name: "macos-15"
os: "macos-15"
generator: "Unix Makefiles"
arch: ""
target: traintastic-client
jobs: 3
build_type: Release
build_deb: false
defines: ""
steps:
- uses: FranzDiebold/github-env-vars-action@v2
@ -112,8 +82,16 @@ jobs:
uses: jurplel/install-qt-action@v4
with:
cache: true
modules: qtwebsockets
version: 6.5.*
- name: Set CMAKE_OSX_ARCHITECTURES
if: startswith(matrix.config.os, 'macos')
uses: actions/github-script@v7
with:
script: |
core.exportVariable('CMAKE_OSX_ARCHITECTURES', 'x86_64');
# Ubuntu only:
- name: apt update
if: startswith(matrix.config.os, 'ubuntu')
@ -122,7 +100,7 @@ jobs:
# Ubuntu only:
- name: Install packages
if: startswith(matrix.config.os, 'ubuntu')
run: sudo apt install qtbase5-dev qtbase5-dev-tools libqt5svg5-dev
run: sudo apt install qtbase5-dev qtbase5-dev-tools libqt5svg5-dev libqt5websockets5-dev
# All:
- name: Create Build Environment
@ -173,6 +151,8 @@ jobs:
build-server:
name: server ${{matrix.config.name}}
runs-on: ${{matrix.config.os}}
env:
VCPKG_BINARY_SOURCES: "clear;x-gha,readwrite"
strategy:
fail-fast: false
matrix:
@ -186,33 +166,9 @@ jobs:
jobs: 4
build_type: Release
build_deb: false
defines: "-DENABLE_CLANG_TIDY=ON"
ccov: false
- name: "windows_x64_mingw"
os: windows-2022
generator: "MinGW Makefiles"
arch: ""
toolset: ""
target: all
jobs: 4
build_type: Release
build_deb: false
defines: ""
ccov: false
- name: "ubuntu_22.04"
os: ubuntu-22.04
generator: "Unix Makefiles"
arch: ""
toolset: ""
target: all
jobs: 4
build_type: Release
build_deb: true
defines: "-DENABLE_CLANG_TIDY=ON -DINSTALL_SYSTEMD_SERVICE=ON"
ccov: false
- name: "ubuntu_24.04"
os: ubuntu-24.04
generator: "Unix Makefiles"
@ -222,11 +178,11 @@ jobs:
jobs: 4
build_type: Release
build_deb: true
defines: "-DENABLE_CLANG_TIDY=ON -DINSTALL_SYSTEMD_SERVICE=ON"
defines: "-DINSTALL_SYSTEMD_SERVICE=ON"
ccov: false
- name: "ubuntu_latest (debug+ccov)"
os: ubuntu-latest
- name: "ubuntu_24.04 (debug+ccov)"
os: ubuntu-24.04
generator: "Unix Makefiles"
arch: ""
toolset: ""
@ -234,7 +190,7 @@ jobs:
jobs: 4
build_type: Debug
build_deb: false
defines: "-DENABLE_CLANG_TIDY=ON -DCODE_COVERAGE=ON"
defines: "-DCODE_COVERAGE=ON"
ccov: true
- name: "raspberrypios_arm7"
@ -273,30 +229,6 @@ jobs:
defines: ""
ccov: false
- name: "macos-14"
os: "macos-14"
generator: "Unix Makefiles"
arch: ""
toolset: ""
target: all
jobs: 3
build_type: Release
build_deb: false
defines: ""
ccov: false
- name: "macos-15"
os: "macos-15"
generator: "Unix Makefiles"
arch: ""
toolset: ""
target: all
jobs: 3
build_type: Release
build_deb: false
defines: ""
ccov: false
steps:
- uses: FranzDiebold/github-env-vars-action@v2
@ -306,6 +238,27 @@ jobs:
with:
submodules: recursive
- name: Export GitHub Actions cache environment variables
uses: actions/github-script@v7
with:
script: |
core.exportVariable('ACTIONS_CACHE_URL', process.env.ACTIONS_CACHE_URL || '');
core.exportVariable('ACTIONS_RUNTIME_TOKEN', process.env.ACTIONS_RUNTIME_TOKEN || '');
- name: Set VCPKG_ROOT
if: startswith(matrix.config.os, 'windows') || startswith(matrix.config.os, 'macos')
uses: actions/github-script@v7
with:
script: |
core.exportVariable('VCPKG_ROOT', process.env.VCPKG_INSTALLATION_ROOT || '');
- name: Set CMAKE_OSX_ARCHITECTURES
if: startswith(matrix.config.os, 'macos')
uses: actions/github-script@v7
with:
script: |
core.exportVariable('CMAKE_OSX_ARCHITECTURES', 'x86_64');
# Ubuntu only:
- name: apt update
if: startswith(matrix.config.os, 'ubuntu')
@ -408,10 +361,6 @@ jobs:
fail-fast: false
matrix:
config:
- name: "ubuntu_22.04"
os: ubuntu-22.04
defines: ""
- name: "ubuntu_24.04"
os: ubuntu-24.04
defines: ""
@ -594,12 +543,14 @@ jobs:
uses: actions/download-artifact@v4
with:
pattern: traintastic-client-deb-*
merge-multiple: true
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
- name: Download artifacts 3/6
uses: actions/download-artifact@v4
with:
pattern: traintastic-server-deb-*
merge-multiple: true
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
- name: Download artifacts 4/6
@ -618,6 +569,7 @@ jobs:
uses: actions/download-artifact@v4
with:
pattern: traintastic-data-deb-*
merge-multiple: true
path: ${{github.workspace}}/dist/${{env.CI_REF_NAME_SLUG}}/${{github.run_number}}
- name: "Download artifact: manual-lua"

Datei anzeigen

@ -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()

Datei anzeigen

@ -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",

Datei anzeigen

@ -0,0 +1,94 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="96"
height="96"
viewBox="0 0 25.399999 25.400001"
version="1.1"
id="svg8"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
sodipodi:docname="add.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs2" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="5.6"
inkscape:cx="14.375"
inkscape:cy="40.446429"
inkscape:document-units="mm"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="2"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1">
<inkscape:grid
type="xygrid"
id="grid3713"
spacingx="0.26458333"
empspacing="4" />
</sodipodi:namedview>
<metadata
id="metadata5">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<cc:license
rdf:resource="http://creativecommons.org/licenses/by-nc/4.0/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by-nc/4.0/">
<cc:permits
rdf:resource="http://creativecommons.org/ns#Reproduction" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#Distribution" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Notice" />
<cc:requires
rdf:resource="http://creativecommons.org/ns#Attribution" />
<cc:prohibits
rdf:resource="http://creativecommons.org/ns#CommercialUse" />
<cc:permits
rdf:resource="http://creativecommons.org/ns#DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-271.59998)">
<circle
style="fill:#ea80fc;fill-opacity:1;stroke:none;stroke-width:2.51975;stroke-linecap:round;stroke-linejoin:round"
id="path252"
cx="12.7"
cy="284.29996"
r="12.7" />
<path
style="fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:0;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
d="m 12.7,274.77498 c -1.172633,0 -2.116667,0.94403 -2.116667,2.11666 v 5.29167 H 5.2916667 c -1.1726347,0 -2.1166667,0.94404 -2.1166667,2.11667 0,1.17263 0.944032,2.11667 2.1166667,2.11667 h 5.2916663 v 5.29167 c 0,1.17263 0.944034,2.11666 2.116667,2.11666 1.172633,0 2.116666,-0.94403 2.116666,-2.11666 v -5.29167 h 5.291667 c 1.172635,0 2.116667,-0.94404 2.116667,-2.11667 0,-1.17263 -0.944032,-2.11667 -2.116667,-2.11667 h -5.291667 v -5.29167 c 0,-1.17263 -0.944033,-2.11666 -2.116666,-2.11666 z"
id="rect843"
sodipodi:nodetypes="sscssscssscssscss" />
</g>
</svg>

Nachher

Breite:  |  Höhe:  |  Größe: 3.5 KiB

Datei anzeigen

@ -95,5 +95,7 @@
<file>zone.svg</file>
<file>clear_persistent_variables.svg</file>
<file>highlight_zone.svg</file>
<file>swap.svg</file>
<file>circle/add.svg</file>
</qresource>
</RCC>

116
client/gfx/dark/swap.svg Normale Datei
Datei anzeigen

@ -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

Datei anzeigen

@ -68,14 +68,14 @@ constexpr QRect updateTileRect(const int x, const int y, const int w, const int
}
BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
BoardAreaWidget::BoardAreaWidget(std::shared_ptr<Board> board, QWidget* parent) :
QWidget(parent),
m_colorScheme{&BoardColorScheme::dark},
m_board{board},
m_boardLeft{board.board().getProperty("left")},
m_boardTop{board.board().getProperty("top")},
m_boardRight{board.board().getProperty("right")},
m_boardBottom{board.board().getProperty("bottom")},
m_board{std::move(board)},
m_boardLeft{m_board->getProperty("left")},
m_boardTop{m_board->getProperty("top")},
m_boardRight{m_board->getProperty("right")},
m_boardBottom{m_board->getProperty("bottom")},
m_grid{Grid::Dot},
m_zoomLevel{0},
m_blockHighlight{MainWindow::instance->blockHighlight()},
@ -108,7 +108,7 @@ BoardAreaWidget::BoardAreaWidget(BoardWidget& board, QWidget* parent) :
update();
});
for(const auto& [l, object] : m_board.board().tileObjects())
for(const auto& [l, object] : m_board->tileObjects())
tileObjectAdded(l.x, l.y, object);
settingsChanged();
@ -124,7 +124,7 @@ void BoardAreaWidget::tileObjectAdded(int16_t x, int16_t y, const ObjectPtr& obj
{
try
{
const TileData& tileData = m_board.board().tileData().at(l);
const TileData& tileData = m_board->tileData().at(l);
update(updateTileRect(l.x - boardLeft(), l.y - boardTop(), tileData.width(), tileData.height(), getTileSize()));
}
catch(...)
@ -139,7 +139,7 @@ void BoardAreaWidget::tileObjectAdded(int16_t x, int16_t y, const ObjectPtr& obj
connect(property, &BaseProperty::valueChanged, this, handler);
};
switch(m_board.board().getTileId(l))
switch(m_board->getTileId(l))
{
case TileId::RailTurnoutLeft45:
case TileId::RailTurnoutLeft90:
@ -204,6 +204,7 @@ void BoardAreaWidget::tileObjectAdded(int16_t x, int16_t y, const ObjectPtr& obj
case TileId::RailTunnel:
case TileId::RailOneWay:
case TileId::RailLink:
case TileId::HiddenRailCrossOver:
case TileId::ReservedForFutureExpension:
break;
@ -319,7 +320,7 @@ void BoardAreaWidget::setMouseMoveTileSizeMax(uint8_t width, uint8_t height)
TurnoutPosition BoardAreaWidget::getTurnoutPosition(const TileLocation& l) const
{
if(ObjectPtr object = m_board.board().getTileObject(l))
if(ObjectPtr object = m_board->getTileObject(l))
if(const auto* p = object->getProperty("position"))
return p->toEnum<TurnoutPosition>();
return TurnoutPosition::Unknown;
@ -327,7 +328,7 @@ TurnoutPosition BoardAreaWidget::getTurnoutPosition(const TileLocation& l) const
SensorState BoardAreaWidget::getSensorState(const TileLocation& l) const
{
if(ObjectPtr object = m_board.board().getTileObject(l))
if(ObjectPtr object = m_board->getTileObject(l))
if(const auto* p = object->getProperty("state"))
return p->toEnum<SensorState>();
return SensorState::Unknown;
@ -335,7 +336,7 @@ SensorState BoardAreaWidget::getSensorState(const TileLocation& l) const
DirectionControlState BoardAreaWidget::getDirectionControlState(const TileLocation& l) const
{
if(ObjectPtr object = m_board.board().getTileObject(l))
if(ObjectPtr object = m_board->getTileObject(l))
if(const auto* p = object->getProperty("state"))
return p->toEnum<DirectionControlState>();
return DirectionControlState::Both;
@ -343,7 +344,7 @@ DirectionControlState BoardAreaWidget::getDirectionControlState(const TileLocati
SignalAspect BoardAreaWidget::getSignalAspect(const TileLocation& l) const
{
if(ObjectPtr object = m_board.board().getTileObject(l))
if(ObjectPtr object = m_board->getTileObject(l))
if(const auto* p = object->getProperty("aspect"))
return p->toEnum<SignalAspect>();
return SignalAspect::Unknown;
@ -351,7 +352,7 @@ SignalAspect BoardAreaWidget::getSignalAspect(const TileLocation& l) const
Color BoardAreaWidget::getColor(const TileLocation& l) const
{
if(ObjectPtr object = m_board.board().getTileObject(l))
if(ObjectPtr object = m_board->getTileObject(l))
if(const auto* p = object->getProperty("color"))
return p->toEnum<Color>();
return Color::None;
@ -359,7 +360,7 @@ Color BoardAreaWidget::getColor(const TileLocation& l) const
DecouplerState BoardAreaWidget::getDecouplerState(const TileLocation& l) const
{
if(ObjectPtr object = m_board.board().getTileObject(l))
if(ObjectPtr object = m_board->getTileObject(l))
if(const auto* p = object->getProperty("state"))
return p->toEnum<DecouplerState>();
return DecouplerState::Deactivated;
@ -367,7 +368,7 @@ DecouplerState BoardAreaWidget::getDecouplerState(const TileLocation& l) const
bool BoardAreaWidget::getNXButtonEnabled(const TileLocation& l) const
{
if(auto object = std::dynamic_pointer_cast<NXButtonRailTile>(m_board.board().getTileObject(l)))
if(auto object = std::dynamic_pointer_cast<NXButtonRailTile>(m_board->getTileObject(l)))
{
return object->getPropertyValueBool("enabled", false);
}
@ -376,7 +377,7 @@ bool BoardAreaWidget::getNXButtonEnabled(const TileLocation& l) const
bool BoardAreaWidget::getNXButtonPressed(const TileLocation& l) const
{
if(auto object = std::dynamic_pointer_cast<NXButtonRailTile>(m_board.board().getTileObject(l)))
if(auto object = std::dynamic_pointer_cast<NXButtonRailTile>(m_board->getTileObject(l)))
{
return object->isPressed();
}
@ -391,11 +392,11 @@ TileLocation BoardAreaWidget::pointToTileLocation(const QPoint& p)
QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
{
const auto tileId = m_board.board().getTileId(l);
const auto tileId = m_board->getTileId(l);
if(isRailTurnout(tileId))
{
if(auto turnout = m_board.board().getTileObject(l))
if(auto turnout = m_board->getTileObject(l))
{
QString text = "<b>" + turnout->getPropertyValueString("name") + "</b>";
if(auto* position = turnout->getProperty("position")) /*[[likely]]*/
@ -407,7 +408,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
}
else if(isRailSignal(tileId))
{
if(auto signal = m_board.board().getTileObject(l))
if(auto signal = m_board->getTileObject(l))
{
QString text = "<b>" + signal->getPropertyValueString("name") + "</b>";
if(auto* aspect = signal->getProperty("aspect")) /*[[likely]]*/
@ -419,7 +420,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
}
else if(tileId == TileId::RailSensor)
{
if(auto sensor = m_board.board().getTileObject(l))
if(auto sensor = m_board->getTileObject(l))
{
QString text = "<b>" + sensor->getPropertyValueString("name") + "</b>";
if(auto* state = sensor->getProperty("state")) /*[[likely]]*/
@ -431,7 +432,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
}
else if(tileId == TileId::RailBlock)
{
if(auto block = m_board.board().getTileObject(l))
if(auto block = m_board->getTileObject(l))
{
QString text = "<b>" + block->getPropertyValueString("name") + "</b>";
if(auto* state = block->getProperty("state")) /*[[likely]]*/
@ -443,7 +444,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
}
else if(tileId == TileId::RailDirectionControl)
{
if(auto directionControl = m_board.board().getTileObject(l))
if(auto directionControl = m_board->getTileObject(l))
{
QString text = "<b>" + directionControl->getPropertyValueString("name") + "</b>";
if(auto* state = directionControl->getProperty("state")) /*[[likely]]*/
@ -455,7 +456,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
}
else if(tileId == TileId::RailSensor)
{
if(auto sensor = m_board.board().getTileObject(l))
if(auto sensor = m_board->getTileObject(l))
{
QString text = "<b>" + sensor->getPropertyValueString("name") + "</b>";
if(auto* state = sensor->getProperty("state")) /*[[likely]]*/
@ -467,7 +468,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
}
else if(tileId == TileId::Switch)
{
if(auto switch_ = m_board.board().getTileObject(l))
if(auto switch_ = m_board->getTileObject(l))
{
QString text = "<b>" + switch_->getPropertyValueString("name") + "</b>";
if(auto* value = switch_->getProperty("value")) /*[[likely]]*/
@ -492,7 +493,7 @@ QString BoardAreaWidget::getTileToolTip(const TileLocation& l) const
else if(tileId == TileId::PushButton ||
tileId == TileId::RailNXButton)
{
if(auto tile = m_board.board().getTileObject(l))
if(auto tile = m_board->getTileObject(l))
{
return "<b>" + tile->getPropertyValueString("name") + "</b>";
}
@ -679,7 +680,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
painter.save();
for(auto it : m_board.board().tileData())
for(auto it : m_board->tileData())
if(it.first.x + it.second.width() - 1 >= tiles.left() && it.first.x <= tiles.right() &&
it.first.y + it.second.height() - 1 >= tiles.top() && it.first.y <= tiles.bottom())
{
@ -740,7 +741,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
case TileId::RailBlock:
{
auto block = m_board.board().getTileObject(it.first);
auto block = m_board->getTileObject(it.first);
tilePainter.drawBlock(id, r, a, state & 0x01, state & 0x02, block);
if(auto itColors = m_blockHighlight.blockColors().find(block->getPropertyValueString("id"));
itColors != m_blockHighlight.blockColors().end() && !itColors->isEmpty())
@ -783,7 +784,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
case TileId::Label:
{
if(auto label = m_board.board().getTileObject(it.first)) /*[[likely]]*/
if(auto label = m_board->getTileObject(it.first)) /*[[likely]]*/
{
tilePainter.drawLabel(r, a,
label->getPropertyValueString("text"),
@ -798,7 +799,7 @@ void BoardAreaWidget::paintEvent(QPaintEvent* event)
break;
}
case TileId::Switch:
if(auto sw = m_board.board().getTileObject(it.first)) /*[[likely]]*/
if(auto sw = m_board->getTileObject(it.first)) /*[[likely]]*/
{
tilePainter.drawSwitch(r,
sw->getPropertyValueBool("value", false),
@ -893,7 +894,7 @@ void BoardAreaWidget::dragMoveEvent(QDragMoveEvent* event)
{
m_dragMoveTileLocation = l;
if(event->mimeData()->hasFormat(AssignTrainMimeData::mimeType) &&
m_board.board().getTileId(l) == TileId::RailBlock)
m_board->getTileId(l) == TileId::RailBlock)
{
return event->accept();
}
@ -909,12 +910,12 @@ void BoardAreaWidget::dropEvent(QDropEvent* event)
const TileLocation l = pointToTileLocation(event->position().toPoint());
#endif
switch(m_board.board().getTileId(l))
switch(m_board->getTileId(l))
{
case TileId::RailBlock:
if(auto* assignTrain = dynamic_cast<const AssignTrainMimeData*>(event->mimeData()))
{
if(auto tile = std::dynamic_pointer_cast<BlockRailTile>(m_board.board().getTileObject(l)))
if(auto tile = std::dynamic_pointer_cast<BlockRailTile>(m_board->getTileObject(l)))
{
if(auto* method = tile->getMethod("assign_train"))
{

Datei anzeigen

@ -40,6 +40,7 @@
class BoardWidget;
class BlockHighlight;
class Board;
class BoardAreaWidget : public QWidget
{
@ -67,7 +68,7 @@ class BoardAreaWidget : public QWidget
protected:
static constexpr int boardMargin = 1; // tile
BoardWidget& m_board;
std::shared_ptr<Board> m_board;
AbstractProperty* m_boardLeft;
AbstractProperty* m_boardTop;
AbstractProperty* m_boardRight;
@ -132,7 +133,7 @@ class BoardAreaWidget : public QWidget
static constexpr int zoomLevelMin = -2;
static constexpr int zoomLevelMax = 15;
BoardAreaWidget(BoardWidget& board, QWidget* parent = nullptr);
BoardAreaWidget(std::shared_ptr<Board> board, QWidget* parent = nullptr);
Grid grid() const { return m_grid; }
void nextGrid();

Datei anzeigen

@ -83,7 +83,7 @@ BoardWidget::BoardWidget(std::shared_ptr<Board> object, QWidget* parent) :
QWidget(parent),
m_object{std::move(object)},
m_nxManagerRequestId{Connection::invalidRequestId},
m_boardArea{new BoardAreaWidget(*this, this)},
m_boardArea{new BoardAreaWidget(m_object, this)},
m_statusBar{new QStatusBar(this)},
m_statusBarMessage{new QLabel(this)},
m_statusBarCoords{new QLabel(this)},

Datei anzeigen

@ -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;
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023 Reinder Feenstra
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -56,8 +56,14 @@ ConnectDialog::ConnectDialog(QWidget* parent, const QString& url) :
m_password->setEchoMode(QLineEdit::Password);
m_connectAutomatically->setChecked(GeneralSettings::instance().connectAutomaticallyToDiscoveredServer.value());
#if QT_VERSION < QT_VERSION_CHECK(6, 7, 0)
connect(m_connectAutomatically, &QCheckBox::stateChanged, this,
[](int state)
#else
connect(m_connectAutomatically, &QCheckBox::checkStateChanged, this,
[](Qt::CheckState state)
#endif
{
GeneralSettings::instance().connectAutomaticallyToDiscoveredServer.setValue(state == Qt::Checked);
});
@ -150,8 +156,10 @@ void ConnectDialog::socketReadyRead()
QString name = QString::fromUtf8(message.read<QByteArray>());
QUrl url;
url.setScheme("ws");
url.setHost(host.toString());
url.setPort(port);
url.setPath("/client");
auto it = m_servers.find(url);
if(it == m_servers.end())
@ -230,6 +238,12 @@ void ConnectDialog::serverTextChanged(const QString& text)
{
QString url{text};
m_url = QUrl::fromUserInput(url.remove(QRegularExpression("\\s*\\(.*\\)$")));
m_url.setScheme("ws");
m_url.setPath("/client");
if(m_url.port() == -1)
{
m_url.setPort(Connection::defaultPort);
}
m_connect->setEnabled(m_url.isValid());
}

Datei anzeigen

@ -33,6 +33,7 @@
#include "../widget/tablewidget.hpp"
#include "../widget/alertwidget.hpp"
#include <traintastic/locale/locale.hpp>
#include <unordered_set>
ObjectSelectListDialog::ObjectSelectListDialog(Method& method, bool multiSelect, QWidget* parent) :
ObjectSelectListDialog(static_cast<InterfaceItem&>(method), multiSelect, parent)
@ -147,9 +148,16 @@ void ObjectSelectListDialog::acceptRows(const QModelIndexList& indexes)
{
if(auto* m = dynamic_cast<Method*>(&m_item))
{
// Call method only once for each row, not for every index in row
std::unordered_set<int> rowsDone;
for(const auto& index : indexes)
{
if(rowsDone.find(index.row()) != rowsDone.end())
continue;
callMethod(*m, nullptr, m_tableWidget->getRowObjectId(index.row()));
rowsDone.insert(index.row());
}
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020,2023 Reinder Feenstra
* Copyright (C) 2019-2020,2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -25,8 +25,13 @@
#include <QTableView>
#include <QDialogButtonBox>
#include <QPushButton>
#include <QLineEdit>
#include <QListView>
#include <QSortFilterProxyModel>
#include <QItemDelegate>
#include <QPainter>
#include <QApplication>
#include <QtWaitingSpinner/waitingspinnerwidget.h>
#include "../widget/tablewidget.hpp"
#include "../network/connection.hpp"
#include "../network/object.hpp"
#include "../network/tablemodel.hpp"
@ -35,14 +40,70 @@
#include "../widget/alertwidget.hpp"
#include <traintastic/locale/locale.hpp>
constexpr int columnUUID = 1;
class WorldListItemDelegate : public QItemDelegate
{
public:
inline WorldListItemDelegate(QListView* parent)
: QItemDelegate(parent)
{
}
inline void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const final
{
static const QSize iconSize{40, 40};
auto* model = qobject_cast<QListView*>(parent())->model();
const auto name = model->data(model->index(index.row(), 0)).toString();
const auto uuid = model->data(model->index(index.row(), 1)).toString();
const auto r = option.rect.adjusted(5, 5, -5, -5);
const int iconOffset = (r.height() - iconSize.height()) / 2;
const auto palette = QApplication::palette();
if((option.state & QStyle::State_Selected) != 0)
{
painter->fillRect(option.rect, palette.brush(QPalette::Highlight));
}
QTextOption textOption;
textOption.setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
const auto classIcon = Theme::getIcon("world");
painter->drawPixmap(r.topLeft() + QPoint(iconOffset, iconOffset), classIcon.pixmap(iconSize));
painter->setPen(palette.color(QPalette::Disabled, QPalette::Text));
painter->drawText(r.adjusted(r.height() + 10, r.height() / 2, 0, 0), uuid, textOption);
painter->setPen(palette.color(QPalette::Active, QPalette::Text));
painter->drawText(r.adjusted(r.height() + 10, 0, 0, -r.height() / 2), name, textOption);
painter->setPen(QColor(0x80, 0x80, 0x80, 0x30));
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
}
inline QSize sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const final
{
return QSize(-1, 50);
}
};
WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget* parent) :
QDialog(parent, Qt::Dialog | Qt::WindowTitleHint | Qt::WindowCloseButtonHint),
m_connection{std::move(connection)},
m_buttons{new QDialogButtonBox(this)},
m_tableWidget{new TableWidget()}
m_search{new QLineEdit(this)},
m_list{new QListView(this)}
{
setWindowTitle(Locale::tr("qtapp.world_list_dialog:world_list"));
setWindowIcon(Theme::getIcon("world_load"));
resize(500, 400);
m_search->setPlaceholderText(Locale::tr("qtapp.world_list_dialog:search"));
m_list->setItemDelegate(new WorldListItemDelegate(m_list));
m_buttons->setStandardButtons(QDialogButtonBox::Open | QDialogButtonBox::Cancel);
m_buttons->button(QDialogButtonBox::Open)->setText(Locale::tr("qtapp.world_list_dialog:load"));
@ -50,14 +111,15 @@ WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget
connect(m_buttons->button(QDialogButtonBox::Open), &QPushButton::clicked, this,
[this]()
{
m_uuid = m_tableWidget->getRowObjectId(m_tableWidget->selectionModel()->selectedIndexes().first().row());
m_uuid = m_list->model()->data(m_list->model()->index(m_list->selectionModel()->selectedIndexes().first().row(), columnUUID)).toString();
accept();
});
m_buttons->button(QDialogButtonBox::Cancel)->setText(Locale::tr("qtapp.world_list_dialog:cancel"));
connect(m_buttons->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &WorldListDialog::reject);
QVBoxLayout* layout = new QVBoxLayout();
layout->addWidget(m_tableWidget);
layout->addWidget(m_search);
layout->addWidget(m_list);
layout->addWidget(m_buttons);
setLayout(layout);
@ -78,20 +140,26 @@ WorldListDialog::WorldListDialog(std::shared_ptr<Connection> connection, QWidget
{
m_requestId = Connection::invalidRequestId;
m_tableWidget->setTableModel(tableModel);
m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
connect(m_tableWidget->selectionModel(), &QItemSelectionModel::selectionChanged, this,
m_tableModel = tableModel;
m_tableModel->setRegionAll(true);
auto* filter = new QSortFilterProxyModel(this);
filter->setSourceModel(m_tableModel.get());
filter->setFilterCaseSensitivity(Qt::CaseInsensitive);
connect(m_search, &QLineEdit::textChanged, filter, &QSortFilterProxyModel::setFilterFixedString);
filter->setFilterFixedString(m_search->text());
m_list->setModel(filter);
m_list->setSelectionBehavior(QAbstractItemView::SelectRows);
connect(m_list->selectionModel(), &QItemSelectionModel::selectionChanged, this,
[this](const QItemSelection&, const QItemSelection&)
{
m_buttons->button(QDialogButtonBox::Open)->setEnabled(m_tableWidget->selectionModel()->selectedRows().count() == 1);
m_buttons->button(QDialogButtonBox::Open)->setEnabled(m_list->selectionModel()->selectedRows().count() == 1);
});
connect(m_tableWidget, &TableWidget::doubleClicked, this,
connect(m_list, &QListView::doubleClicked, this,
[this](const QModelIndex& index)
{
m_uuid = m_tableWidget->getRowObjectId(index.row());
m_uuid = m_list->model()->data(m_list->model()->index(index.row(), columnUUID)).toString();
accept();
});
delete spinner;
}
else if(err)

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020 Reinder Feenstra
* Copyright (C) 2019-2020,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -25,9 +25,11 @@
#include <QDialog>
#include "../network/objectptr.hpp"
#include "../network/tablemodelptr.hpp"
class QDialogButtonBox;
class TableWidget;
class QLineEdit;
class QListView;
class Connection;
class WorldListDialog final : public QDialog
@ -39,8 +41,10 @@ class WorldListDialog final : public QDialog
int m_requestId;
ObjectPtr m_object;
QDialogButtonBox* m_buttons; // TODO: m_buttonLoad;
TableWidget* m_tableWidget;
QLineEdit* m_search;
QListView* m_list;
QString m_uuid;
TableModelPtr m_tableModel;
public:
explicit WorldListDialog(std::shared_ptr<Connection> connection, QWidget* parent = nullptr);

Datei anzeigen

@ -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)

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2024 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -124,6 +124,7 @@ MainWindow::MainWindow(QWidget* parent) :
QAction* boardsAction;
QAction* trainsAction;
m_mdiArea->setBackground(palette().window().color().darker());
m_mdiArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded);
m_mdiArea->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
@ -433,6 +434,7 @@ MainWindow::MainWindow(QWidget* parent) :
auto* tabs = new QTabWidget(m_trainAndRailVehiclesSubWindow);
tabs->addTab(new ObjectEditWidget("world.trains"), Locale::tr("world:trains"));
tabs->addTab(new ObjectEditWidget("world.rail_vehicles"), Locale::tr("world:rail_vehicles"));
tabs->addTab(new ObjectEditWidget("world.throttles"), Locale::tr("hardware:throttles"));
m_trainAndRailVehiclesSubWindow->setWidget(tabs);
m_mdiArea->addSubWindow(m_trainAndRailVehiclesSubWindow);
m_trainAndRailVehiclesSubWindow->setAttribute(Qt::WA_DeleteOnClose);
@ -519,7 +521,7 @@ MainWindow::MainWindow(QWidget* parent) :
})->setShortcut(QKeySequence::HelpContents);
auto* subMenu = menu->addMenu(Locale::tr("qtapp.mainmenu:wizards"));
subMenu->addAction(Locale::tr("wizard.introduction:title"), this, &MainWindow::showIntroductionWizard);
subMenu->addAction(Locale::tr("wizard.add_interface.welcome:title"), this, &MainWindow::showAddInterfaceWizard);
m_actionAddInterfaceWizard = subMenu->addAction(Locale::tr("wizard.add_interface.welcome:title"), this, &MainWindow::showAddInterfaceWizard);
//menu->addSeparator();
//menu->addAction(Locale::tr("qtapp.mainmenu:about_qt") + "...", qApp, &QApplication::aboutQt);
menu->addAction(Locale::tr("qtapp.mainmenu:about") + "...", this, &MainWindow::showAbout);
@ -676,7 +678,8 @@ void MainWindow::changeEvent(QEvent* event)
void MainWindow::worldChanged()
{
m_newWorldWizard.reset();
m_wizard.newWorld.reset();
m_wizard.addInterface.reset();
if(m_world)
m_mdiArea->closeAllSubWindows();
@ -747,13 +750,13 @@ void MainWindow::worldChanged()
if(m_newWorldRequested && m_world)
{
m_newWorldRequested = false;
m_newWorldWizard = std::make_unique<NewWorldWizard>(m_world, this);
connect(m_newWorldWizard.get(), &NewWorldWizard::finished,
m_wizard.newWorld = std::make_unique<NewWorldWizard>(m_world, this);
connect(m_wizard.newWorld.get(), &NewWorldWizard::finished,
[this]()
{
m_newWorldWizard.release()->deleteLater();
m_wizard.newWorld.release()->deleteLater();
});
m_newWorldWizard->open();
m_wizard.newWorld->open();
}
}
@ -946,17 +949,18 @@ IntroductionWizard* MainWindow::showIntroductionWizard()
return introductionWizard;
}
AddInterfaceWizard* MainWindow::showAddInterfaceWizard()
void MainWindow::showAddInterfaceWizard()
{
if(!m_world) /*[[unlikely]]*/
if(m_world && !m_wizard.addInterface) /*[[likely]]*/
{
return nullptr;
m_wizard.addInterface = std::make_unique<AddInterfaceWizard>(m_world, this);
connect(m_wizard.addInterface.get(), &AddInterfaceWizard::finished,
[this]()
{
m_wizard.addInterface.release()->deleteLater();
});
m_wizard.addInterface->open();
}
auto* addInterfaceWizard = new AddInterfaceWizard(m_world, this);
addInterfaceWizard->setAttribute(Qt::WA_DeleteOnClose);
addInterfaceWizard->open();
return addInterfaceWizard;
}
NewBoardWizard* MainWindow::showNewBoardWizard(const ObjectPtr& board)
@ -1010,7 +1014,6 @@ void MainWindow::updateActions()
const bool connected = m_connection && m_connection->state() == Connection::State::Connected;
const bool haveWorld = connected && m_connection->world();
m_actionConnectToServer->setEnabled(!m_connection);
m_actionConnectToServer->setVisible(!connected);
m_actionDisconnectFromServer->setVisible(connected);
@ -1034,6 +1037,7 @@ void MainWindow::updateActions()
m_actionServerShutdown->setEnabled(m && m->getAttributeBool(AttributeName::Enabled, false));
}
m_menuProgramming->setEnabled(haveWorld);
m_actionAddInterfaceWizard->setEnabled(haveWorld);
setMenuEnabled(m_menuWorld, haveWorld);
m_worldOnlineOfflineToolButton->setEnabled(haveWorld);

Datei anzeigen

@ -58,7 +58,11 @@ class MainWindow final : public QMainWindow
ObjectPtr m_world;
bool m_newWorldRequested = false;
std::unique_ptr<WorldListDialog> m_loadWorldDialog;
std::unique_ptr<NewWorldWizard> m_newWorldWizard;
struct
{
std::unique_ptr<AddInterfaceWizard> addInterface;
std::unique_ptr<NewWorldWizard> newWorld;
} m_wizard;
int m_clockRequest;
ObjectPtr m_clock;
QSplitter* m_splitter;
@ -103,6 +107,7 @@ class MainWindow final : public QMainWindow
QAction* m_actionServerShutdown;
QAction* m_actionServerLog;
QMenu* m_menuProgramming;
QAction* m_actionAddInterfaceWizard;
// Main toolbar:
QToolBar* m_toolbar;
QToolButton* m_worldOnlineOfflineToolButton;
@ -146,7 +151,7 @@ class MainWindow final : public QMainWindow
const ObjectPtr& worldClock() const { return m_clock; }
IntroductionWizard* showIntroductionWizard();
AddInterfaceWizard* showAddInterfaceWizard();
void showAddInterfaceWizard();
NewBoardWizard* showNewBoardWizard(const ObjectPtr& board);
void showLuaScriptsList();

Datei anzeigen

@ -133,7 +133,10 @@ void MainWindowStatusBar::updateStatuses()
m_mainWindow.connection()->cancelRequest(m_statusesRequest);
if(statuses->empty())
{
clearStatuses();
return;
}
m_statusesRequest = statuses->getObjects(0, statuses->size() - 1,
[this](const std::vector<ObjectPtr>& objects, std::optional<const Error> error)

Datei anzeigen

@ -21,7 +21,7 @@
*/
#include "connection.hpp"
#include <QTcpSocket>
#include <QWebSocket>
#include <QUrl>
#include <QCryptographicHash>
#include <traintastic/network/message.hpp>
@ -130,23 +130,31 @@ inline static QStringList readObjectIdArray(const Message& message, const int le
Connection::Connection() :
QObject(),
m_socket{new QTcpSocket(this)},
m_socket{new QWebSocket()},
m_state{State::Disconnected},
m_worldProperty{nullptr},
m_worldRequestId{invalidRequestId}
, m_serverLogTableModel{nullptr}
{
connect(m_socket, &QTcpSocket::connected, this, &Connection::socketConnected);
connect(m_socket, &QTcpSocket::disconnected, this, &Connection::socketDisconnected);
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
connect(m_socket, static_cast<void(QTcpSocket::*)(QAbstractSocket::SocketError)>(&QTcpSocket::error), this, &Connection::socketError);
connect(m_socket, &QWebSocket::connected, this, &Connection::socketConnected);
connect(m_socket, &QWebSocket::disconnected, this, &Connection::socketDisconnected);
#if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
connect(m_socket, &QWebSocket::errorOccurred, this, &Connection::socketError);
#else
connect(m_socket, &QTcpSocket::errorOccurred, this, &Connection::socketError);
connect(m_socket, static_cast<void(QWebSocket::*)(QAbstractSocket::SocketError)>(&QWebSocket::error), this, &Connection::socketError);
#endif
m_socket->setSocketOption(QAbstractSocket::LowDelayOption, 1);
connect(m_socket, &QTcpSocket::readyRead, this, &Connection::socketReadyRead);
connect(m_socket, &QWebSocket::binaryMessageReceived,
[this](const QByteArray& data)
{
const Message::Header& header = *reinterpret_cast<const Message::Header*>(data.data());
auto message = std::make_shared<Message>(header);
if(header.dataSize != 0)
{
std::memcpy(message->data(), data.data() + sizeof(header), message->dataSize());
}
processMessage(message);
});
}
bool Connection::isDisconnected() const
@ -172,12 +180,12 @@ void Connection::connectToHost(const QUrl& url, const QString& username, const Q
else
m_password = QCryptographicHash::hash(password.toUtf8(), QCryptographicHash::Sha256);
setState(State::Connecting);
m_socket->connectToHost(url.host(), static_cast<quint16>(url.port(defaultPort)));
m_socket->open(url);
}
void Connection::disconnectFromHost()
{
m_socket->disconnectFromHost();
m_socket->close();
}
void Connection::cancelRequest(int requestId)
@ -417,7 +425,7 @@ void Connection::releaseTableModel(TableModel* tableModel)
tableModel->m_handle = invalidHandle;
}
void Connection::setTableModelRegion(TableModel* tableModel, int columnMin, int columnMax, int rowMin, int rowMax)
void Connection::setTableModelRegion(TableModel* tableModel, uint32_t columnMin, uint32_t columnMax, uint32_t rowMin, uint32_t rowMax)
{
auto event = Message::newEvent(Message::Command::TableModelSetRegion);
event->write(tableModel->handle());
@ -443,7 +451,8 @@ int Connection::getTileData(Board& object)
void Connection::send(std::unique_ptr<Message>& message)
{
Q_ASSERT(!message->isRequest());
m_socket->write(static_cast<const char*>(**message), message->size());
QByteArray bytes(static_cast<const char*>(**message), message->size()); // Deep copy :(
m_socket->sendBinaryMessage(bytes); // sendBinaryMessage only supports QByteArray
}
void Connection::send(std::unique_ptr<Message>& message, std::function<void(const std::shared_ptr<Message>&)> callback)
@ -451,7 +460,8 @@ void Connection::send(std::unique_ptr<Message>& message, std::function<void(cons
Q_ASSERT(message->isRequest());
Q_ASSERT(!m_requestCallback.contains(message->requestId()));
m_requestCallback[message->requestId()] = callback;
m_socket->write(static_cast<const char*>(**message), message->size());
QByteArray bytes(static_cast<const char*>(**message), message->size()); // Deep copy :(
m_socket->sendBinaryMessage(bytes); // sendBinaryMessage only supports QByteArray
}
ObjectPtr Connection::readObject(const Message& message)
@ -1050,14 +1060,14 @@ void Connection::socketConnected()
else
{
setState(State::ErrorNewSessionFailed);
m_socket->disconnectFromHost();
m_socket->close();
}
});
}
else
{
setState(State::ErrorAuthenticationFailed);
m_socket->disconnectFromHost();
m_socket->close();
}
});
}
@ -1071,32 +1081,3 @@ void Connection::socketError(QAbstractSocket::SocketError)
{
setState(State::SocketError);
}
void Connection::socketReadyRead()
{
while(m_socket->bytesAvailable() != 0)
{
if(!m_readBuffer.message) // read header
{
m_readBuffer.offset += m_socket->read(reinterpret_cast<char*>(&m_readBuffer.header) + m_readBuffer.offset, sizeof(m_readBuffer.header) - m_readBuffer.offset);
if(m_readBuffer.offset == sizeof(m_readBuffer.header))
{
if(m_readBuffer.header.dataSize != 0)
m_readBuffer.message = std::make_shared<Message>(m_readBuffer.header);
else
processMessage(std::make_shared<Message>(m_readBuffer.header));
m_readBuffer.offset = 0;
}
}
else // read data
{
m_readBuffer.offset += m_socket->read(reinterpret_cast<char*>(m_readBuffer.message->data()) + m_readBuffer.offset, m_readBuffer.message->dataSize() - m_readBuffer.offset);
if(m_readBuffer.offset == m_readBuffer.message->dataSize())
{
processMessage(m_readBuffer.message);
m_readBuffer.message.reset();
m_readBuffer.offset = 0;
}
}
}
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023 Reinder Feenstra
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -35,7 +35,7 @@
#include "objectptr.hpp"
#include "tablemodelptr.hpp"
class QTcpSocket;
class QWebSocket;
class ServerLogTableModel;
class Property;
class ObjectProperty;
@ -73,7 +73,7 @@ class Connection : public QObject, public std::enable_shared_from_this<Connectio
using SocketError = QAbstractSocket::SocketError;
protected:
QTcpSocket* m_socket;
QWebSocket* m_socket;
State m_state;
QString m_username;
QByteArray m_password;
@ -108,7 +108,6 @@ class Connection : public QObject, public std::enable_shared_from_this<Connectio
void socketConnected();
void socketDisconnected();
void socketError(QAbstractSocket::SocketError);
void socketReadyRead();
public:
static const quint16 defaultPort = 5740;
@ -153,7 +152,7 @@ class Connection : public QObject, public std::enable_shared_from_this<Connectio
[[nodiscard]] int getTableModel(const ObjectPtr& object, std::function<void(const TableModelPtr&, std::optional<const Error>)> callback);
void releaseTableModel(TableModel* tableModel);
void setTableModelRegion(TableModel* tableModel, int columnMin, int columnMax, int rowMin, int rowMax);
void setTableModelRegion(TableModel* tableModel, uint32_t columnMin, uint32_t columnMax, uint32_t rowMin, uint32_t rowMax);
[[nodiscard]] int getTileData(Board& object);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023 Reinder Feenstra
* Copyright (C) 2019-2021,2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -72,7 +72,16 @@ QString TableModel::getValue(int column, int row) const
return m_texts.value(ColumnRow(column, row));
}
void TableModel::setRegion(int columnMin, int columnMax, int rowMin, int rowMax)
void TableModel::setRegionAll(bool enable)
{
m_regionAll = enable;
if(m_regionAll)
{
updateRegionAll();
}
}
void TableModel::setRegion(uint32_t columnMin, uint32_t columnMax, uint32_t rowMin, uint32_t rowMax)
{
if(m_region.columnMin != columnMin ||
m_region.columnMax != columnMax ||
@ -94,6 +103,10 @@ void TableModel::setColumnHeaders(const QVector<QString>& values)
{
beginResetModel();
m_columnHeaders = values;
if(m_regionAll)
{
updateRegionAll();
}
endResetModel();
}
}
@ -104,6 +117,22 @@ void TableModel::setRowCount(int value)
{
beginResetModel();
m_rowCount = value;
if(m_regionAll)
{
updateRegionAll();
}
endResetModel();
}
}
void TableModel::updateRegionAll()
{
if(columnCount() == 0 || rowCount() == 0)
{
setRegion(1, 0, 1, 0); // select nothing
}
else
{
setRegion(0, columnCount() - 1, 0, rowCount() - 1);
}
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023-2024 Reinder Feenstra
* Copyright (C) 2019-2021,2023-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -45,16 +45,20 @@ class TableModel final : public QAbstractTableModel
int m_rowCount;
struct Region
{
int rowMin = 0;
int rowMax = -1;
int columnMin = 0;
int columnMax = -1;
// Default to invalid region
uint32_t rowMin = 1;
uint32_t rowMax = 0;
uint32_t columnMin = 1;
uint32_t columnMax = 0;
} m_region;
bool m_regionAll = false;
QMap<ColumnRow, QString> m_texts;
void setColumnHeaders(const QVector<QString>& values);
void setRowCount(int value);
void updateRegionAll();
public:
explicit TableModel(std::shared_ptr<Connection> connection, Handle handle, const QString& classId, QObject* parent = nullptr);
~TableModel() final;
@ -71,7 +75,8 @@ class TableModel final : public QAbstractTableModel
QString getRowObjectId(int row) const;
QString getValue(int column, int row) const;
void setRegion(int columnMin, int columnMax, int rowMin, int rowMax);
void setRegionAll(bool enable);
void setRegion(uint32_t columnMin, uint32_t columnMax, uint32_t rowMin, uint32_t rowMax);
};
#endif

Datei anzeigen

@ -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;

Datei anzeigen

@ -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);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2024 Reinder Feenstra
* Copyright (C) 2020-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -23,6 +23,7 @@
#include "createwidget.hpp"
#include "list/marklincanlocomotivelistwidget.hpp"
#include "objectlist/boardlistwidget.hpp"
#include "objectlist/interfacelistwidget.hpp"
#include "objectlist/throttleobjectlistwidget.hpp"
#include "objectlist/trainlistwidget.hpp"
#include "objectlist/zoneblocklistwidget.hpp"
@ -36,6 +37,7 @@
#include "propertydoublespinbox.hpp"
#include "propertyspinbox.hpp"
#include "propertylineedit.hpp"
#include "propertypairoutputaction.hpp"
#include "../board/boardwidget.hpp"
#include "../network/object.hpp"
#include "../network/inputmonitor.hpp"
@ -47,8 +49,10 @@ QWidget* createWidgetIfCustom(const ObjectPtr& object, QWidget* parent)
{
const QString& classId = object->classId();
if(classId == "command_station_list")
return new ObjectListWidget(object, parent); // todo remove
if(classId == "list.interface")
{
return new InterfaceListWidget(object, parent);
}
else if(classId == "decoder_list")
return new ThrottleObjectListWidget(object, parent); // todo remove
else if(classId == "controller_list")
@ -127,6 +131,10 @@ QWidget* createWidget(Property& property, QWidget* parent)
break; // TODO
case ValueType::Enum:
if(property.enumName() == "pair_output_action")
{
return new PropertyPairOutputAction(property, parent);
}
return new PropertyComboBox(property, parent);
case ValueType::Integer:

Datei anzeigen

@ -0,0 +1,95 @@
/**
* client/src/widget/methodicon.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "methodicon.hpp"
#include <QMouseEvent>
#include <QIcon>
#include "../network/method.hpp"
MethodIcon::MethodIcon(Method& method, QIcon icon, QWidget* parent) :
QLabel(parent),
m_method{method}
{
setPixmap(icon.pixmap(32, 32));
setCursor(Qt::PointingHandCursor);
setEnabled(m_method.getAttributeBool(AttributeName::Enabled, true));
setVisible(m_method.getAttributeBool(AttributeName::Visible, true));
setToolTip(m_method.displayName());
connect(&m_method, &Method::attributeChanged, this,
[this](AttributeName name, const QVariant& value)
{
switch(name)
{
case AttributeName::Enabled:
setEnabled(value.toBool());
break;
case AttributeName::Visible:
setVisible(value.toBool());
break;
case AttributeName::DisplayName:
setToolTip(m_method.displayName());
break;
default:
break;
}
});
}
MethodIcon::MethodIcon(Method& item, QIcon icon, std::function<void()> triggered, QWidget* parent)
: MethodIcon(item, icon, parent)
{
m_triggered = std::move(triggered);
}
void MethodIcon::mousePressEvent(QMouseEvent* event)
{
if(event->button() == Qt::LeftButton)
{
m_mouseLeftButtonPressed = true;
}
}
void MethodIcon::mouseReleaseEvent(QMouseEvent* event)
{
if(m_mouseLeftButtonPressed && event->button() == Qt::LeftButton)
{
m_mouseLeftButtonPressed = false;
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
if(rect().contains(event->localPos().toPoint())) // test if mouse release in widget
#else
if(rect().contains(event->position().toPoint())) // test if mouse release in widget
#endif
{
if(m_triggered)
{
m_triggered();
}
else
{
m_method.call();
}
}
}
}

Datei anzeigen

@ -1,9 +1,9 @@
/**
* server/src/hardware/throttle/throttlefunction.hpp
* client/src/widget/methodicon.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022 Reinder Feenstra
* Copyright (C) 2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -20,32 +20,26 @@
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_HARDWARE_THROTTLE_THROTTLEFUNCTION_HPP
#define TRAINTASTIC_SERVER_HARDWARE_THROTTLE_THROTTLEFUNCTION_HPP
#ifndef TRAINTASTIC_CLIENT_WIDGET_METHODICON_HPP
#define TRAINTASTIC_CLIENT_WIDGET_METHODICON_HPP
#include "../../core/object.hpp"
#include "../../core/property.hpp"
#include "../../core/method.hpp"
#include <QLabel>
class Throttle;
class Method;
class ThrottleFunction : public Object
class MethodIcon : public QLabel
{
CLASS_ID("throttle_function")
protected:
Method& m_method;
std::function<void()> m_triggered;
bool m_mouseLeftButtonPressed;
private:
Throttle& m_throttle;
void mousePressEvent(QMouseEvent* event) final;
void mouseReleaseEvent(QMouseEvent* event) final;
public:
Property<uint32_t> number;
Property<std::string> name;
Property<bool> value;
Method<void()> press;
Method<void()> release;
ThrottleFunction(Throttle& throttle, uint32_t number);
std::string getObjectId() const final;
MethodIcon(Method& item, QIcon icon, QWidget* parent = nullptr);
MethodIcon(Method& item, QIcon icon, std::function<void()> triggered, QWidget* parent = nullptr);
};
#endif

Datei anzeigen

@ -0,0 +1,119 @@
/**
* client/src/widget/objectlist/interfacelistwidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "interfacelistwidget.hpp"
#include <QApplication>
#include <QItemDelegate>
#include <QListView>
#include <QMenu>
#include <QPainter>
#include <QtMath>
#include <traintastic/enum/interfacestate.hpp>
#include <traintastic/locale/locale.hpp>
#include "../methodicon.hpp"
#include "../../theme/theme.hpp"
#include "../../mainwindow.hpp"
class InterfaceListItemDelegate : public QItemDelegate
{
public:
inline InterfaceListItemDelegate(QListView* parent)
: QItemDelegate(parent)
{
}
inline void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const final
{
static const QSize classIconSize{40, 40};
static const QSize stateIconSize{16, 16};
auto* model = qobject_cast<QListView*>(parent())->model();
const auto id = model->data(model->index(index.row(), 0)).toString().prepend('#');
const auto name = model->data(model->index(index.row(), 1)).toString();
const auto state = model->data(model->index(index.row(), 2)).toString();
const auto classId = model->data(model->index(index.row(), 3)).toString();
const auto r = option.rect.adjusted(5, 5, -5, -5);
const int iconOffset = (r.height() - classIconSize.height()) / 2;
const auto palette = QApplication::palette();
QTextOption textOption;
textOption.setAlignment(Qt::AlignLeft | Qt::AlignVCenter);
const auto classIcon = Theme::getIconForClassId(classId);
if(!classIcon.isNull())
{
painter->drawPixmap(r.topLeft() + QPoint(iconOffset, iconOffset), classIcon.pixmap(classIconSize));
}
painter->setPen(palette.color(QPalette::Disabled, QPalette::Text));
painter->drawText(r.adjusted((classIcon.isNull() ? 0 : r.height() + 10), r.height() / 2, 0, 0), id, textOption);
painter->setPen(palette.color(QPalette::Active, QPalette::Text));
painter->drawText(r.adjusted((classIcon.isNull() ? 0 : r.height() + 10), 0, 0, -r.height() / 2), name, textOption);
textOption.setAlignment(Qt::AlignRight | Qt::AlignVCenter);
const auto stateText = Locale::tr(QString(EnumName<InterfaceState>::value).append(":").append(state));
const auto stateRect = r.adjusted(0, r.height() / 2, 0, 0);
const auto stateTextWidth = qCeil(painter->boundingRect(stateRect, stateText, textOption).width());
painter->drawText(stateRect, stateText, textOption);
auto stateIcon = QIcon(Theme::getIconFile(QString("interface_state.").append(state)));
painter->drawPixmap(
stateRect.topRight() - QPoint(stateTextWidth + stateIconSize.width() + 5, -(stateRect.height() - stateIconSize.height()) / 2),
stateIcon.pixmap(stateIconSize));
painter->setPen(QColor(0x80, 0x80, 0x80, 0x30));
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
}
inline QSize sizeHint(const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const final
{
return QSize(-1, 50);
}
};
InterfaceListWidget::InterfaceListWidget(const ObjectPtr& object, QWidget* parent)
: StackedObjectListWidget(object, parent)
{
m_list->setItemDelegate(new InterfaceListItemDelegate(m_list));
m_listEmptyLabel->setText(Locale::tr("interface_list:list_is_empty"));
if(m_create) /*[[likely]]*/
{
m_create->setToolTip(Locale::tr("interface_list:create"));
}
if(m_createMenu) /*[[likely]]*/
{
m_createMenu->addSeparator();
m_createMenu->addAction(Theme::getIcon("wizard"), Locale::tr("list:setup_using_wizard") + "...",
[]()
{
MainWindow::instance->showAddInterfaceWizard();
});
}
}

Datei anzeigen

@ -0,0 +1,34 @@
/**
* client/src/widget/objectlist/interfacelistwidget.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_INTERFACELISTWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_INTERFACELISTWIDGET_HPP
#include "stackedobjectlistwidget.hpp"
class InterfaceListWidget : public StackedObjectListWidget
{
public:
explicit InterfaceListWidget(const ObjectPtr& object, QWidget* parent = nullptr);
};
#endif

Datei anzeigen

@ -0,0 +1,291 @@
/**
* client/src/widget/objectlist/stackedobjectlistwidget.cpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "stackedobjectlistwidget.hpp"
#include <QEvent>
#include <QLabel>
#include <QListView>
#include <QMenu>
#include <QMessageBox>
#include <QStackedWidget>
#include <QToolBar>
#include <QVBoxLayout>
#include <QIdentityProxyModel>
#include <QGuiApplication>
#include <traintastic/locale/locale.hpp>
#include "../createwidget.hpp"
#include "../tablewidget.hpp"
#include "../methodicon.hpp"
#include "../../mainwindow.hpp"
#include "../../network/object.hpp"
#include "../../network/method.hpp"
#include "../../network/connection.hpp"
#include "../../network/error.hpp"
#include "../../network/callmethod.hpp"
#include "../../network/tablemodel.hpp"
#include "../../theme/theme.hpp"
#include "../../misc/methodaction.hpp"
namespace
{
class StackedObjectListProxyModel final : public QIdentityProxyModel
{
public:
StackedObjectListProxyModel(QAbstractItemModel* sourceModel)
: QIdentityProxyModel()
{
setSourceModel(sourceModel);
}
QVariant data(const QModelIndex &index, int role) const final
{
if (role == Qt::ToolTipRole)
{
return Locale::tr("stacked_object_list:click_to_edit_ctrl_click_to_open_in_a_new_window");
}
return QIdentityProxyModel::data(index, role);
}
};
}
StackedObjectListWidget::StackedObjectListWidget(const ObjectPtr& object, QWidget* parent)
: QWidget(parent)
, m_object{object}
, m_navBar{new QToolBar(this)}
, m_stack{new QStackedWidget(this)}
, m_list{new QListView(this)}
, m_listEmptyLabel{new QLabel(Locale::tr("stacked_object_list:list_is_empty"), m_list)}
, m_requestId{Connection::invalidRequestId}
{
m_navBar->hide();
m_navBar->addAction(Theme::getIcon("previous_page"), Locale::tr("stacked_object_list:back"), this, &StackedObjectListWidget::back);
m_navLabel = new QLabel(this);
m_navLabel->setAlignment(Qt::AlignCenter);
m_navLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
m_navLabel->show();
m_navBar->addWidget(m_navLabel);
if(auto* method = object->getMethod("delete"))
{
m_actionRemove = new MethodAction(Theme::getIcon("delete"), *method,
[this]()
{
if(!m_listObjectId.isEmpty())
{
callMethod(m_actionRemove->method(), nullptr, m_listObjectId);
back();
}
});
m_navBar->addAction(m_actionRemove);
}
connect(m_stack, &QStackedWidget::currentChanged,
[this](int index)
{
m_navBar->setVisible(index > 0);
});
m_requestId = object->connection()->getTableModel(object,
[this](const TableModelPtr& tableModel, std::optional<const Error> error)
{
m_requestId = Connection::invalidRequestId;
if(tableModel)
{
m_tableModel = tableModel;
m_tableModel->setRegionAll(true);
m_list->setModel(new StackedObjectListProxyModel(m_tableModel.get()));
connect(m_tableModel.get(), &TableModel::modelReset,
[this]()
{
m_listEmptyLabel->setVisible(m_tableModel->rowCount() == 0);
});
}
else if(error)
{
QMessageBox::critical(this, "Error", error->toString());
}
});
if(auto* create = object->getMethod("create"))
{
if(create->argumentTypes().size() == 0) // Create method witout argument
{
m_create = new MethodIcon(*create, Theme::getIcon("circle/add"), m_list);
}
else if(create->argumentTypes().size() == 1)
{
m_createMenu = new QMenu(this);
m_createMenu->installEventFilter(this);
QStringList classList = create->getAttribute(AttributeName::ClassList, QVariant()).toStringList();
for(const QString& classId : classList)
{
QAction* action = m_createMenu->addAction(Locale::tr("class_id:" + classId));
action->setData(classId);
connect(action, &QAction::triggered, this,
[this, create, action]()
{
cancelRequest();
m_requestId = create->call(action->data().toString(),
[this](const ObjectPtr& addedObject, std::optional<const Error> error)
{
m_requestId = Connection::invalidRequestId;
if(addedObject)
{
show(addedObject);
}
else if(error)
{
QMessageBox::critical(this, "Error", error->toString());
}
});
});
}
m_create = new MethodIcon(*create, Theme::getIcon("circle/add"),
[this]()
{
m_createMenu->popup(m_create->mapToGlobal(m_create->rect().topRight()));
}, m_list);
}
m_create->setEnabled(create->getAttributeBool(AttributeName::Enabled, true));
if(!create->getAttributeBool(AttributeName::Visible, true))
{
m_create->hide();
}
m_create->installEventFilter(this);
}
m_list->installEventFilter(this);
m_list->setSelectionMode(QListView::NoSelection);
connect(m_list, &QListView::clicked,
[this](const QModelIndex &index)
{
if(!m_tableModel) /*[[unlikely]]*/
{
return;
}
const bool openInSubWindow = (QGuiApplication::queryKeyboardModifiers() & Qt::ControlModifier);
cancelRequest();
m_requestId = m_object->connection()->getObject(m_tableModel->getRowObjectId(index.row()),
[this, openInSubWindow](const ObjectPtr& selectedObject, std::optional<const Error> error)
{
m_requestId = Connection::invalidRequestId;
if(selectedObject)
{
if(openInSubWindow)
{
MainWindow::instance->showObject(selectedObject);
}
else
{
show(selectedObject);
}
}
else if(error)
{
QMessageBox::critical(this, "Error", error->toString());
}
});
});
m_listEmptyLabel->installEventFilter(this);
m_listEmptyLabel->setWordWrap(true);
m_stack->addWidget(m_list);
auto* l = new QVBoxLayout();
l->setContentsMargins(0, 0, 0, 0);
l->addWidget(m_navBar);
l->addWidget(m_stack);
setLayout(l);
}
StackedObjectListWidget::~StackedObjectListWidget()
{
cancelRequest();
}
bool StackedObjectListWidget::eventFilter(QObject* object, QEvent* event)
{
if(m_listEmptyLabel->isVisible() && ((object == m_list && event->type() == QEvent::Resize) || (object == m_listEmptyLabel && event->type() == QEvent::Show)))
{
m_listEmptyLabel->setMaximumWidth(qRound(width() * 0.9f));
m_listEmptyLabel->adjustSize();
m_listEmptyLabel->setFixedHeight(m_listEmptyLabel->heightForWidth(m_listEmptyLabel->maximumWidth()));
m_listEmptyLabel->move((rect().bottomRight() - m_listEmptyLabel->rect().bottomRight()) / 2);
}
if(m_create && ((object == m_list && event->type() == QEvent::Resize) || (object == m_create && event->type() == QEvent::Show)))
{
auto pnt = m_create->rect().bottomRight();
pnt = m_list->rect().bottomRight() - pnt - pnt / 3;
m_create->move(pnt.x(), pnt.y());
}
if(m_createMenu && object == m_createMenu && event->type() == QEvent::Show)
{
m_createMenu->move(m_createMenu->pos() - QPoint{m_createMenu->width(), m_createMenu->height()});
return true;
}
return QWidget::eventFilter(object, event);
}
void StackedObjectListWidget::cancelRequest()
{
if(m_requestId != Connection::invalidRequestId)
{
m_object->connection()->cancelRequest(m_requestId);
}
}
void StackedObjectListWidget::back()
{
if(m_stack->currentIndex() > 0)
{
m_listObjectId.clear();
delete m_stack->currentWidget();
}
}
void StackedObjectListWidget::show(const ObjectPtr& listObject)
{
if(auto* w = createWidget(listObject, this)) /*[[likely]]*/
{
m_listObjectId = listObject->getPropertyValueString("id");
connect(w, &QWidget::windowTitleChanged, m_navLabel, &QLabel::setText);
m_navLabel->setText(w->windowTitle());
m_stack->setCurrentIndex(m_stack->addWidget(w));
}
}

Datei anzeigen

@ -0,0 +1,67 @@
/**
* client/src/widget/objectlist/stackedobjectlistwidget.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_STACKEDOBJECTLISTWIDGET_HPP
#define TRAINTASTIC_CLIENT_WIDGET_OBJECTLIST_STACKEDOBJECTLISTWIDGET_HPP
#include <QWidget>
#include "../../network/objectptr.hpp"
#include "../../network/tablemodelptr.hpp"
class QToolBar;
class QStackedWidget;
class QListView;
class QLabel;
class QMenu;
class MethodIcon;
class MethodAction;
class StackedObjectListWidget : public QWidget
{
protected:
ObjectPtr m_object;
TableModelPtr m_tableModel;
QToolBar* m_navBar;
QLabel* m_navLabel;
QStackedWidget* m_stack;
QListView* m_list;
QLabel* m_listEmptyLabel;
MethodIcon* m_create = nullptr;
QMenu* m_createMenu = nullptr;
MethodAction* m_actionRemove = nullptr;
QString m_listObjectId;
int m_requestId;
void cancelRequest();
void back();
void show(const ObjectPtr& listObject);
bool eventFilter(QObject* object, QEvent* event) override;
public:
explicit StackedObjectListWidget(const ObjectPtr& object, QWidget* parent = nullptr);
~StackedObjectListWidget() override;
};
#endif

Datei anzeigen

@ -27,14 +27,18 @@
#include <QTableWidget>
#include <QHeaderView>
#include <QPushButton>
#include <QEvent>
#include <traintastic/locale/locale.hpp>
#include "createwidget.hpp"
#include "interfaceitemnamelabel.hpp"
#include "propertycheckbox.hpp"
#include "propertycombobox.hpp"
#include "propertypairoutputaction.hpp"
#include "propertyspinbox.hpp"
#include "objectpropertycombobox.hpp"
#include "propertyaddresses.hpp"
#include "outputmapoutputactionwidget.hpp"
#include "methodicon.hpp"
#include "../board/tilepainter.hpp"
#include "../board/getboardcolorscheme.hpp"
#include "../dialog/objectselectlistdialog.hpp"
@ -117,6 +121,17 @@ OutputMapWidget::OutputMapWidget(ObjectPtr object, QWidget* parent)
l->addWidget(m_table);
if(auto* swapOutputs = m_object->getMethod("swap_outputs"))
{
m_swapOutputs = new MethodIcon(*swapOutputs, Theme::getIcon("swap"), m_table);
if(!swapOutputs->getAttributeBool(AttributeName::Visible, true))
{
m_swapOutputs->hide();
}
m_table->installEventFilter(this);
m_swapOutputs->installEventFilter(this);
}
setLayout(l);
if(auto* parentObject = m_object->getObjectProperty("parent"))
@ -324,6 +339,17 @@ void OutputMapWidget::updateTableOutputColumns()
}
}
bool OutputMapWidget::eventFilter(QObject* object, QEvent* event)
{
if(m_swapOutputs && ((object == m_table && event->type() == QEvent::Resize) || (object == m_swapOutputs && event->type() == QEvent::Show)))
{
auto pnt = m_swapOutputs->rect().bottomRight();
pnt = m_table->rect().bottomRight() - pnt - pnt / 4;
m_swapOutputs->move(pnt.x(), pnt.y());
}
return QWidget::eventFilter(object, event);
}
void OutputMapWidget::updateTableOutputActions(ObjectVectorProperty& property, int row)
{
if(!property.empty())
@ -345,7 +371,7 @@ void OutputMapWidget::updateTableOutputActions(ObjectVectorProperty& property, i
{
if(auto* action = dynamic_cast<Property*>(object->getProperty("action")))
{
m_table->setCellWidget(row, column, new PropertyComboBox(*action, this));
m_table->setCellWidget(row, column, createWidget(*action, this));
}
else if(auto* aspect = dynamic_cast<Property*>(object->getProperty("aspect")))
{

Datei anzeigen

@ -29,6 +29,7 @@
class QTableWidget;
class Method;
class MethodAction;
class MethodIcon;
class AbstractProperty;
class AbstractVectorProperty;
class ObjectVectorProperty;
@ -47,6 +48,7 @@ class OutputMapWidget : public QWidget
Property* m_ecosObject;
ObjectVectorProperty* m_items;
QTableWidget* m_table;
MethodIcon* m_swapOutputs = nullptr;
std::vector<ObjectPtr> m_itemObjects;
std::vector<std::vector<ObjectPtr>> m_actions;
int m_getParentRequestId;
@ -58,6 +60,8 @@ class OutputMapWidget : public QWidget
void updateKeyIcons();
void updateTableOutputColumns();
bool eventFilter(QObject* object, QEvent* event) override;
public:
explicit OutputMapWidget(ObjectPtr object, QWidget* parent = nullptr);
~OutputMapWidget() override;

Datei anzeigen

@ -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}};
}

Datei anzeigen

@ -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

Datei anzeigen

@ -136,19 +136,34 @@ void TableWidget::updateRegion()
int rowMin = qMax(topLeft.row(), 0);
int rowMax = indexAt(r.bottomLeft()).row();
if(rowMax == -1)
if(rowCount == 0)
{
// Invalid region to represent empty model
rowMin = 1;
rowMax = 0;
}
else if(rowMax == -1)
rowMax = rowCount - 1;
else
rowMax = qMin(rowMax + 1, rowCount - 1);
int columnMin = qMax(topLeft.column(), 0);
int columnMax = indexAt(r.topRight()).column();
if(columnCount == 0)
{
// Invalid region to represent empty model
columnMin = 1;
columnMax = 0;
}
if(columnMax == -1)
columnMax = columnCount - 1;
else
columnMax = qMin(columnMax + 1, columnCount - 1);
m_model->setRegion(columnMin, columnMax, rowMin, rowMax);
m_model->setRegion(uint32_t(columnMin), uint32_t(columnMax),
uint32_t(rowMin), uint32_t(rowMax));
}
void TableWidget::mouseMoveEvent(QMouseEvent* event)

Datei anzeigen

@ -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

Datei anzeigen

@ -24,11 +24,19 @@
#include <QLayout>
#include <QButtonGroup>
#include <QRadioButton>
#include <QLabel>
RadioPage::RadioPage(QWidget* parent)
: TextPage(parent)
, m_group{new QButtonGroup(this)}
, m_bottomText{new QLabel(this)}
{
m_bottomText->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
m_bottomText->setWordWrap(true);
setBottomText({});
static_cast<QVBoxLayout*>(layout())->addStretch();
layout()->addWidget(m_bottomText);
}
int RadioPage::currentIndex() const
@ -36,23 +44,25 @@ int RadioPage::currentIndex() const
return m_group->id(m_group->checkedButton());
}
void RadioPage::addItem(const QString& label, bool disabled)
void RadioPage::addItem(const QString& label, bool checked, bool disabled)
{
auto* button = new QRadioButton(label);
button->setChecked(checked && !disabled);
button->setDisabled(disabled);
m_group->addButton(button, m_group->buttons().size());
layout()->addWidget(button);
static_cast<QVBoxLayout*>(layout())->insertWidget(layout()->count() - 2, button);
}
void RadioPage::clear()
{
while(layout()->count() > 1) // remove all but first (=text label)
for(auto* button : m_group->buttons())
{
auto* item = layout()->itemAt(layout()->count() - 1);
if(item->widget())
delete button;
}
}
void RadioPage::setBottomText(const QString& text)
{
delete item->widget();
}
layout()->removeItem(item);
}
m_bottomText->setText(text);
m_bottomText->setVisible(!text.isEmpty());
}

Datei anzeigen

@ -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

Datei anzeigen

@ -54,9 +54,6 @@ Name: "firewall_wlanmaus"; Description: "{cm:firewall_allow_wlanmaus_z21}"; Grou
[Files]
; Server
Source: "..\..\server\build\{#ServerExeName}"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
Source: "..\..\server\thirdparty\lua5.4\bin\win64\lua54.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
Source: "..\..\server\thirdparty\libarchive\bin\archive.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
Source: "..\..\server\thirdparty\zlib\bin\zlib1.dll"; DestDir: "{app}\server"; Flags: ignoreversion; Check: InstallServer
; Client
Source: "..\..\client\build\Release\{#ClientExeName}"; DestDir: "{app}\client"; Flags: ignoreversion; Check: InstallClient
Source: "..\..\client\build\Release\*.dll"; DestDir: "{app}\client"; Flags: ignoreversion; Check: InstallClient
@ -88,6 +85,11 @@ Type: files; Name: "{commonappdata}\traintastic\translations\en-us.txt"
Type: files; Name: "{commonappdata}\traintastic\translations\nl-nl.txt"
Type: files; Name: "{commonappdata}\traintastic\translations\de-de.txt"
Type: files; Name: "{commonappdata}\traintastic\translations\it-it.txt"
; Delete unused DLLs, now statically linked (TODO: remove in 0.4)
Type: files; Name: "{app}\server\lua53.dll"
Type: files; Name: "{app}\server\lua54.dll"
Type: files; Name: "{app}\server\archive.dll"
Type: files; Name: "{app}\server\zlib1.dll"
[UninstallRun]
Filename: {sys}\netsh.exe; Parameters: "advfirewall firewall delete rule name=""Traintastic server (TCP)"""; Flags: runhidden; Check: InstallServer; Tasks: firewall_traintastic
@ -107,7 +109,7 @@ Root: HKLM; Subkey: "{#CompanySubKey}"; Flags: uninsdeletekeyifempty
Root: HKLM; Subkey: "{#AppSubKey}"; Flags: uninsdeletekey
[INI]
Filename: {commonappdata}\traintastic\traintastic-client.ini; Section: general_; Key: language; String: {code:GetTraintasticClientLanguage}; Flags: uninsdeleteentry uninsdeletesectionifempty;
Filename: {commonappdata}\traintastic\traintastic-client.ini; Section: general_; Key: language; String: {code:GetTraintasticLanguage}; Flags: uninsdeleteentry uninsdeletesectionifempty;
[Code]
const
@ -179,6 +181,7 @@ begin
ClientAndServerRadioButton.Checked := (Components = 'ClientAndServer');
ClientAndServerRadioButton.Font.Style := [fsBold];
ClientAndServerRadioButton.Height := ScaleY(23);
ClientAndServerRadioButton.Width := ComponentsPage.SurfaceWidth;
ClientAndServerRadioButton.Parent := ComponentsPage.Surface;
ClientAndServerRadioButton.OnClick := @ComponentRadioButtonClick;
@ -195,6 +198,7 @@ begin
ClientOnlyRadioButton.Font.Style := [fsBold];
ClientOnlyRadioButton.Top := Lbl.Top + Lbl.Height + ScaleY(10);
ClientOnlyRadioButton.Height := ScaleY(23);
ClientOnlyRadioButton.Width := ComponentsPage.SurfaceWidth;
ClientOnlyRadioButton.Parent := ComponentsPage.Surface;
ClientOnlyRadioButton.OnClick := @ComponentRadioButtonClick;
@ -206,7 +210,7 @@ begin
Lbl.Parent := ComponentsPage.Surface;
end;
function GetTraintasticClientLanguage(Param: String) : String;
function GetTraintasticLanguage(Param: String) : String;
begin
case ActiveLanguage of
'nl': Result := 'nl-nl';
@ -219,6 +223,19 @@ begin
end;
end;
procedure CurStepChanged(CurStep: TSetupStep);
var
ServerSettingsFile: String;
begin
if CurStep = ssPostInstall then begin
// Server: only write language if there is no setting file yet:
ServerSettingsFile := ExpandConstant('{localappdata}\traintastic\server\settings.json');
if not FileExists(ServerSettingsFile) then begin
SaveStringToFile(ServerSettingsFile, '{"language":"' + GetTraintasticLanguage('') + '"}', False);
end;
end
end;
function VC2019RedistNeedsInstall: Boolean;
var
Version: String;

Datei anzeigen

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.9)
cmake_minimum_required(VERSION 3.18)
include(../shared/traintastic.cmake)
project(traintastic-server VERSION ${TRAINTASTIC_VERSION} DESCRIPTION "Traintastic server")
include(GNUInstallDirs)
@ -23,7 +23,7 @@ endif()
add_executable(traintastic-server src/main.cpp src/options.hpp)
add_dependencies(traintastic-server traintastic-lang)
set_target_properties(traintastic-server PROPERTIES CXX_STANDARD 17)
set_target_properties(traintastic-server PROPERTIES CXX_STANDARD 20)
target_include_directories(traintastic-server PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
../shared/src)
@ -33,11 +33,20 @@ target_include_directories(traintastic-server SYSTEM PRIVATE
if(BUILD_TESTING)
add_subdirectory(thirdparty/catch2)
set_target_properties(Catch2 PROPERTIES CXX_STANDARD 17)
set_target_properties(Catch2 PROPERTIES
CXX_STANDARD 20
CXX_CLANG_TIDY ""
)
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
target_compile_options(Catch2 PRIVATE -Wno-restrict) # workaround GCC bug
endif()
add_executable(traintastic-server-test test/main.cpp)
add_dependencies(traintastic-server-test traintastic-lang)
target_compile_definitions(traintastic-server-test PRIVATE -DTRAINTASTIC_TEST)
set_target_properties(traintastic-server-test PROPERTIES CXX_STANDARD 17)
set_target_properties(traintastic-server-test PROPERTIES
CXX_STANDARD 20
CXX_CLANG_TIDY ""
)
target_include_directories(traintastic-server-test PRIVATE
${CMAKE_CURRENT_BINARY_DIR}
../shared/src)
@ -58,6 +67,7 @@ file(GLOB SOURCES
"src/board/nx/*.cpp"
"src/board/tile/*.hpp"
"src/board/tile/*.cpp"
"src/board/tile/hidden/*.cpp"
"src/board/tile/misc/*.hpp"
"src/board/tile/misc/*.cpp"
"src/board/tile/rail/*.hpp"
@ -189,6 +199,38 @@ file(GLOB TEST_SOURCES
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -DENABLE_LOG_DEBUG")
### VCPKG
if(DEFINED ENV{VCPKG_ROOT})
message(STATUS "Using VCPKG (VCPKG_ROOT=$ENV{VCPKG_ROOT})")
if(WIN32)
set(VCPKG_TARGET_TRIPLET "x64-windows-static-md")
endif()
include($ENV{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake)
endif()
### RESOURCES ###
include(cmake/add-resource.cmake)
add_resource(resource-www
FILES
www/css/normalize.css
www/css/throttle.css
www/js/throttle.js
www/throttle.html
)
add_resource(resource-shared
BASE_DIR ../
FILES
shared/gfx/appicon.ico
)
add_dependencies(traintastic-server resource-www resource-shared)
if(BUILD_TESTING)
add_dependencies(traintastic-server-test resource-www resource-shared)
endif()
### OPTIONS ###
if(NO_LOCALHOST_ONLY_SETTING)
@ -280,154 +322,30 @@ if(WIN32 AND NOT MSVC)
endif()
# boost
if(LINUX)
find_package(Boost 1.71 REQUIRED COMPONENTS program_options)
find_package(Boost 1.81 REQUIRED COMPONENTS program_options)
target_include_directories(traintastic-server SYSTEM PRIVATE ${Boost_INCLUDE_DIRS})
target_link_libraries(traintastic-server PRIVATE ${Boost_LIBRARIES})
if(BUILD_TESTING)
target_include_directories(traintastic-server-test SYSTEM PRIVATE ${Boost_INCLUDE_DIRS})
target_link_libraries(traintastic-server-test PRIVATE ${Boost_LIBRARIES})
endif()
else()
add_definitions(
-DBOOST_ALL_NO_LIB
-DBOOST_ERROR_CODE_HEADER_ONLY
-DBOOST_CHRONO_HEADER_ONLY
-DBOOST_ASIO_HEADER_ONLY
-DBOOST_SYSTEM_NO_DEPRECATED)
if(NOT MSVC)
set_source_files_properties(
thirdparty/boost/libs/program_options/src/cmdline.cpp
thirdparty/boost/libs/program_options/src/config_file.cpp
thirdparty/boost/libs/program_options/src/convert.cpp
thirdparty/boost/libs/program_options/src/options_description.cpp
thirdparty/boost/libs/program_options/src/parsers.cpp
thirdparty/boost/libs/program_options/src/positional_options.cpp
thirdparty/boost/libs/program_options/src/split.cpp
thirdparty/boost/libs/program_options/src/utf8_codecvt_facet.cpp
thirdparty/boost/libs/program_options/src/value_semantic.cpp
thirdparty/boost/libs/program_options/src/variables_map.cpp
thirdparty/boost/libs/program_options/src/winmain.cpp
PROPERTIES
COMPILE_FLAGS -Wno-shadow)
endif()
target_include_directories(traintastic-server SYSTEM PRIVATE thirdparty/boost)
if(BUILD_TESTING)
target_include_directories(traintastic-server-test SYSTEM PRIVATE thirdparty/boost)
endif()
file(GLOB SOURCES_BOOST "thirdparty/boost/libs/program_options/src/*.cpp")
list(APPEND SOURCES ${SOURCES_BOOST})
endif()
# zlib
if(WIN32)
set(ZLIB_INCLUDE_DIRS "thirdparty/zlib/include")
if(MSVC)
set(ZLIB_LIBRARIES zlib1)
add_custom_command(TARGET traintastic-server PRE_LINK
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.def" /out:zlib1.lib /machine:x64)
add_custom_command(TARGET traintastic-server-test PRE_LINK
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.def" /out:zlib1.lib /machine:x64)
else()
# MinGW can directly link .dll without import lib
set(ZLIB_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.dll")
endif()
# copy zlib1.dll to build directory:
add_custom_command(TARGET traintastic-server POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/thirdparty/zlib/bin/zlib1.dll" .)
else()
find_package(ZLIB REQUIRED)
endif()
target_include_directories(traintastic-server PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(traintastic-server PRIVATE ${ZLIB_LIBRARIES})
target_link_libraries(traintastic-server PRIVATE ZLIB::ZLIB)
if(BUILD_TESTING)
target_include_directories(traintastic-server-test PRIVATE ${ZLIB_INCLUDE_DIRS})
target_link_libraries(traintastic-server-test PRIVATE ${ZLIB_LIBRARIES})
target_link_libraries(traintastic-server-test PRIVATE ZLIB::ZLIB)
endif()
# libarchive
if(WIN32)
set(LibArchive_INCLUDE_DIRS "thirdparty/libarchive/include")
if(MSVC)
set(LibArchive_LIBRARIES archive)
add_custom_command(TARGET traintastic-server PRE_LINK
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/libarchive/bin/archive.def" /out:archive.lib /machine:x64)
add_custom_command(TARGET traintastic-server-test PRE_LINK
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/libarchive/bin/archive.def" /out:archive.lib /machine:x64)
else()
# MinGW can directly link .dll without import lib
set(LibArchive_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/libarchive/bin/archive.dll")
endif()
# copy archive.dll to build directory:
add_custom_command(TARGET traintastic-server POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/thirdparty/libarchive/bin/archive.dll" .)
elseif(APPLE)
find_path(LibArchive_INCLUDE_DIRS
NAMES archive.h
PATHS
"/usr/local/opt/libarchive/include" # x86_64
"/opt/homebrew/opt/libarchive/include" # arm64
)
find_library(LibArchive_LIBRARIES
NAMES archive libarchive
PATHS
"/usr/local/opt/libarchive/lib/" # x86_64
"/opt/homebrew/opt/libarchive/lib" # arm64
)
else()
find_package(LibArchive REQUIRED)
endif()
target_include_directories(traintastic-server PRIVATE ${LibArchive_INCLUDE_DIRS})
target_link_libraries(traintastic-server PRIVATE ${LibArchive_LIBRARIES})
target_link_libraries(traintastic-server PRIVATE LibArchive::LibArchive)
if(BUILD_TESTING)
target_include_directories(traintastic-server-test PRIVATE ${LibArchive_INCLUDE_DIRS})
target_link_libraries(traintastic-server-test PRIVATE ${LibArchive_LIBRARIES})
target_link_libraries(traintastic-server-test PRIVATE LibArchive::LibArchive)
endif()
# liblua5.4
if(WIN32)
add_definitions(-DLUA_BUILD_AS_DLL)
set(LUA_INCLUDE_DIR "thirdparty/lua5.4/include")
if(MSVC)
set(LUA_LIBRARIES lua54)
add_custom_command(TARGET traintastic-server PRE_LINK
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.def" /out:lua54.lib /machine:x64)
add_custom_command(TARGET traintastic-server-test PRE_LINK
COMMAND lib "/def:${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.def" /out:lua54.lib /machine:x64)
else()
# MinGW can directly link .dll without import lib
set(LUA_LIBRARIES "${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.dll")
endif()
# copy lua54.dll to build directory, to be able to run the tests:
add_custom_command(TARGET traintastic-server-test POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy "${PROJECT_SOURCE_DIR}/thirdparty/lua5.4/bin/win64/lua54.dll" .)
elseif(APPLE)
find_path(LUA_INCLUDE_DIR
NAMES lua.h
PATHS
"/usr/local/opt/lua@5.4/include/lua" # x86_64
"/opt/homebrew/opt/lua@5.4/include/lua" # arm64
)
find_library(LUA_LIBRARIES
NAMES lua5.4 liblua5.4
PATHS
"/usr/local/opt/lua@5.4/lib" # x86_64
"/opt/homebrew/opt/lua@5.4/lib" # arm64
)
else()
find_package(Lua 5.4 REQUIRED)
endif()
# lua
find_package(Lua REQUIRED)
target_include_directories(traintastic-server PRIVATE ${LUA_INCLUDE_DIR})
target_link_libraries(traintastic-server PRIVATE ${LUA_LIBRARIES})
if(BUILD_TESTING)

Datei anzeigen

@ -1,122 +0,0 @@
# Locate Lua library
# This module defines
# LUA_EXECUTABLE, if found
# LUA_FOUND, if false, do not try to link to Lua
# LUA_LIBRARIES
# LUA_INCLUDE_DIR, where to find lua.h
# LUA_VERSION_STRING, the version of Lua found (since CMake 2.8.8)
#
# Note that the expected include convention is
# #include "lua.h"
# and not
# #include <lua/lua.h>
# This is because, the lua location is not standardized and may exist
# in locations other than lua/
#=============================================================================
# Copyright 2007-2009 Kitware, Inc.
# Modified to support Lua 5.2 by LuaDist 2012
# Modified to support Lua 5.3 by Reinder Feenstra 2019
# Modified to support Lua 5.4 by Reinder Feenstra 2024
#
# Distributed under the OSI-approved BSD License (the "License");
# see accompanying file Copyright.txt for details.
#
# This software is distributed WITHOUT ANY WARRANTY; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the License for more information.
#=============================================================================
# (To distribute this file outside of CMake, substitute the full
# License text for the above reference.)
#
# The required version of Lua can be specified using the
# standard syntax, e.g. FIND_PACKAGE(Lua 5.1)
# Otherwise the module will search for any available Lua implementation
# Always search for non-versioned lua first (recommended)
SET(_POSSIBLE_LUA_INCLUDE include include/lua)
SET(_POSSIBLE_LUA_EXECUTABLE lua)
SET(_POSSIBLE_LUA_LIBRARY lua)
# Determine possible naming suffixes (there is no standard for this)
IF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
SET(_POSSIBLE_SUFFIXES "${Lua_FIND_VERSION_MAJOR}${Lua_FIND_VERSION_MINOR}" "${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}" "-${Lua_FIND_VERSION_MAJOR}.${Lua_FIND_VERSION_MINOR}")
ELSE(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
SET(_POSSIBLE_SUFFIXES "54" "5.4" "-5.4" "53" "5.3" "-5.3" "52" "5.2" "-5.2" "51" "5.1" "-5.1")
ENDIF(Lua_FIND_VERSION_MAJOR AND Lua_FIND_VERSION_MINOR)
# Set up possible search names and locations
FOREACH(_SUFFIX ${_POSSIBLE_SUFFIXES})
LIST(APPEND _POSSIBLE_LUA_INCLUDE "include/lua${_SUFFIX}")
LIST(APPEND _POSSIBLE_LUA_EXECUTABLE "lua${_SUFFIX}")
LIST(APPEND _POSSIBLE_LUA_LIBRARY "lua${_SUFFIX}")
ENDFOREACH(_SUFFIX)
# Find the lua executable
FIND_PROGRAM(LUA_EXECUTABLE
NAMES ${_POSSIBLE_LUA_EXECUTABLE}
)
# Find the lua header
FIND_PATH(LUA_INCLUDE_DIR lua.h
HINTS
$ENV{LUA_DIR}
PATH_SUFFIXES ${_POSSIBLE_LUA_INCLUDE}
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local
/usr
/sw # Fink
/opt/homebrew # MacOS Apple Silicone
/opt/local # DarwinPorts
/opt/csw # Blastwave
/opt
)
# Find the lua library
FIND_LIBRARY(LUA_LIBRARY
NAMES ${_POSSIBLE_LUA_LIBRARY}
HINTS
$ENV{LUA_DIR}
PATH_SUFFIXES lib64 lib
PATHS
~/Library/Frameworks
/Library/Frameworks
/usr/local
/usr
/sw
/opt/homebrew # MacOS Apple Silicone
/opt/local
/opt/csw
/opt
)
IF(LUA_LIBRARY)
# include the math library for Unix
IF(UNIX AND NOT APPLE)
FIND_LIBRARY(LUA_MATH_LIBRARY m)
SET( LUA_LIBRARIES "${LUA_LIBRARY};${LUA_MATH_LIBRARY}" CACHE STRING "Lua Libraries")
# For Windows and Mac, don't need to explicitly include the math library
ELSE(UNIX AND NOT APPLE)
SET( LUA_LIBRARIES "${LUA_LIBRARY}" CACHE STRING "Lua Libraries")
ENDIF(UNIX AND NOT APPLE)
ENDIF(LUA_LIBRARY)
# Determine Lua version
IF(LUA_INCLUDE_DIR AND EXISTS "${LUA_INCLUDE_DIR}/lua.h")
FILE(STRINGS "${LUA_INCLUDE_DIR}/lua.h" lua_version_str REGEX "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua .+\"")
STRING(REGEX REPLACE "^#define[ \t]+LUA_RELEASE[ \t]+\"Lua ([^\"]+)\".*" "\\1" LUA_VERSION_STRING "${lua_version_str}")
UNSET(lua_version_str)
ENDIF()
INCLUDE(FindPackageHandleStandardArgs)
# handle the QUIETLY and REQUIRED arguments and set LUA_FOUND to TRUE if
# all listed variables are TRUE
FIND_PACKAGE_HANDLE_STANDARD_ARGS(Lua
REQUIRED_VARS LUA_LIBRARIES LUA_INCLUDE_DIR
VERSION_VAR LUA_VERSION_STRING)
MARK_AS_ADVANCED(LUA_INCLUDE_DIR LUA_LIBRARIES LUA_LIBRARY LUA_MATH_LIBRARY LUA_EXECUTABLE)

Datei anzeigen

@ -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()

Datei anzeigen

@ -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}

Datei anzeigen

@ -0,0 +1,88 @@
#
# This file is part of the traintastic source code.
# See <https://github.com/traintastic/traintastic>.
#
# Copyright (C) 2024-2025 Reinder Feenstra
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
import sys
import os
import re
import textwrap
if len(sys.argv) != 4:
print(f"Usage: {sys.argv[0]} <input dir> <input file> <output header>")
sys.exit(1)
input_file = os.path.join(sys.argv[1], sys.argv[2])
input_file_ext = os.path.splitext(input_file)[1].lstrip('.')
namespaces = ['Resource'] + os.path.dirname(sys.argv[2]).replace('../', '').split('/')
variable = re.sub(r'[\.]+','_', os.path.basename(sys.argv[2]).lower())
guard = '_'.join(namespaces).upper() + '_' + re.sub(r'[\.]+','_', os.path.basename(sys.argv[3]).upper())
is_binary = input_file_ext not in ['html', 'css', 'js']
with open(input_file, 'rb' if is_binary else 'r') as f:
contents = f.read()
os.makedirs(os.path.dirname(sys.argv[3]), exist_ok=True)
if is_binary:
size = len(contents)
contents = ', '.join(['std::byte{' + str(by) + '}' for by in contents])
contents = '\n '.join(textwrap.wrap(contents, width=120))
with open(sys.argv[3], 'w') as f:
f.write(f'''// Auto-generated, do not edit, it will be overwritten
#ifndef {guard}
#define {guard}
#include <array>
namespace {'::'.join(namespaces)}
{{
constexpr std::array<std::byte, {size}> {variable}{{{{
{contents}
}}}};
}}
#endif
''')
else: # text
with open(sys.argv[3], 'w') as f:
f.write(f'''// Auto-generated, do not edit, it will be overwritten
#ifndef {guard}
#define {guard}
#include <string_view>
namespace {'::'.join(namespaces)}
{{
constexpr std::string_view {variable} = R"~#!({contents})~#!";
}}
#endif
''')

Datei anzeigen

@ -25,6 +25,7 @@
#include "boardlisttablemodel.hpp"
#include "map/link.hpp"
#include "tile/tiles.hpp"
#include "tile/hidden/hiddencrossoverrailtile.hpp"
#include "../core/method.tpp"
#include "../core/objectproperty.tpp"
#include "../world/world.hpp"
@ -33,6 +34,8 @@
#include "../utils/displayname.hpp"
#include <cassert>
#include "../log/log.hpp"
CREATE_IMPL(Board)
Board::Board(World& world, std::string_view _id) :
@ -385,6 +388,43 @@ void Board::modified()
Connector connector{startConnector.opposite()};
while(auto nextTile = getTile(connector.location))
{
if(isIntercardinal(connector.direction)) // check for crossover
{
auto prevTile = getTile(TileLocation{nextTile->x, nextTile->y} + connector.direction);
assert(prevTile);
auto otherTile1 = getTile({prevTile->x, nextTile->y});
auto otherTile2 = getTile({nextTile->x, prevTile->y});
if(otherTile1 && otherTile2)
{
const auto perpendicular =
(connector.direction == Connector::Direction::NorthEast) || (connector.direction == Connector::Direction::SouthWest)
? ~rotate90cw(connector.direction) : rotate90cw(connector.direction);
auto otherConnector1 = otherTile1->getConnector(perpendicular);
auto otherConnector2 = otherTile2->getConnector(~perpendicular);
if(otherConnector1 && otherConnector2) // crossover found!
{
const TileLocation topLeft{std::min<int16_t>(prevTile->x, nextTile->x), std::min<int16_t>(prevTile->y, nextTile->y)};
auto it = m_railCrossOver.find(topLeft);
if(it == m_railCrossOver.end())
{
it = m_railCrossOver.emplace(topLeft, std::make_shared<HiddenCrossOverRailTile>(world())).first;
it->second->x.setValueInternal(topLeft.x);
it->second->y.setValueInternal(topLeft.y);
}
auto& crossOver = it->second;
auto crossOverConnector = crossOver->getConnector(connector.direction);
assert(crossOverConnector);
auto link = std::make_shared<Link>(std::move(tiles));
link->connect(*startTile->node(), startConnector, *crossOver->node(), *crossOverConnector);
return;
}
}
}
if(nextTile->node())
{
auto link = std::make_shared<Link>(std::move(tiles));
@ -429,6 +469,30 @@ void Board::modified()
}
}
}
// remove unconnected crossovers:
auto it = m_railCrossOver.begin();
while(it != m_railCrossOver.end())
{
bool remove = false;
assert(it->second->node());
for(const auto& link : (*it->second->node()).get().links())
{
if(!link)
{
remove = true;
break;
}
}
if(remove)
{
it = m_railCrossOver.erase(it);
}
else
{
it++;
}
}
}
// notify board changed:

Datei anzeigen

@ -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

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2024 Reinder Feenstra
* Copyright (C) 2022-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -64,14 +64,6 @@ AbstractSignalPath::AbstractSignalPath(SignalRailTile& signal, size_t blocksAhea
}
}
AbstractSignalPath::~AbstractSignalPath()
{
for(auto& connection : m_connections)
{
connection.disconnect();
}
}
void AbstractSignalPath::evaluate()
{
const bool stop = !signal().hasReservedPath() && requireReservation();
@ -84,7 +76,7 @@ bool AbstractSignalPath::requireReservation() const
return (m_signal.requireReservation == AutoYesNo::Yes || (m_signal.requireReservation == AutoYesNo::Auto && m_requireReservation));
}
const AbstractSignalPath::BlockItem* AbstractSignalPath::nextBlock(const Item* item) const
const AbstractSignalPath::BlockItem* AbstractSignalPath::nextBlock(const Item* item)
{
while(item)
{
@ -97,7 +89,7 @@ const AbstractSignalPath::BlockItem* AbstractSignalPath::nextBlock(const Item* i
return nullptr;
}
std::tuple<const AbstractSignalPath::BlockItem*, const AbstractSignalPath::SignalItem*> AbstractSignalPath::nextBlockOrSignal(const Item* item) const
std::tuple<const AbstractSignalPath::BlockItem*, const AbstractSignalPath::SignalItem*> AbstractSignalPath::nextBlockOrSignal(const Item* item)
{
while(item)
{
@ -114,7 +106,7 @@ std::tuple<const AbstractSignalPath::BlockItem*, const AbstractSignalPath::Signa
return {nullptr, nullptr};
}
void AbstractSignalPath::getBlockStates(tcb::span<BlockState> blockStates) const
void AbstractSignalPath::getBlockStates(std::span<BlockState> blockStates) const
{
size_t i = 0;
const Item* item = m_root.get();
@ -238,7 +230,7 @@ std::unique_ptr<const AbstractSignalPath::Item> AbstractSignalPath::findBlocks(c
if(nextNode.getLink(0).get() == &link)
return findBlocks(nextNode, *nextLink, blocksAhead);
}
else if(isRailBridge(tile->tileId) || isRailCross(tile->tileId))
else if(isRailBridge(tile->tileId) || isRailCross(tile->tileId) || tile->tileId == TileId::HiddenRailCrossOver)
{
// 2 1 2 2 3
// | \| |/

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2022-2023 Reinder Feenstra
* Copyright (C) 2022-2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -155,7 +155,7 @@ class AbstractSignalPath : public Path
private:
std::unique_ptr<const Item> m_root;
bool m_requireReservation = false;
std::vector<boost::signals2::connection> m_connections;
std::vector<boost::signals2::scoped_connection> m_connections;
std::unique_ptr<const Item> findBlocks(const Node& node, const Link& link, size_t blocksAhead);
@ -181,25 +181,25 @@ class AbstractSignalPath : public Path
return m_root.get();
}
const BlockItem* nextBlock(const Item* item) const;
static const BlockItem* nextBlock(const Item* item);
inline const BlockItem* nextBlock() const
{
return nextBlock(root());
}
std::tuple<const BlockItem*, const SignalItem*> nextBlockOrSignal(const Item* item) const;
static std::tuple<const BlockItem*, const SignalItem*> nextBlockOrSignal(const Item* item);
inline std::tuple<const BlockItem*, const SignalItem*> nextBlockOrSignal() const
{
return nextBlockOrSignal(root());
}
void getBlockStates(tcb::span<BlockState> blockStates) const;
void getBlockStates(std::span<BlockState> blockStates) const;
std::shared_ptr<BlockRailTile> getBlock(size_t index) const;
public:
AbstractSignalPath(SignalRailTile& signal);
AbstractSignalPath(SignalRailTile& signal, size_t blocksAhead);
virtual ~AbstractSignalPath();
virtual ~AbstractSignalPath() = default;
void evaluate();
};

Datei anzeigen

@ -25,6 +25,7 @@
#include <traintastic/enum/crossstate.hpp>
#include "node.hpp"
#include "link.hpp"
#include "../tile/hidden/hiddencrossoverrailtile.hpp"
#include "../tile/rail/blockrailtile.hpp"
#include "../tile/rail/bridgerailtile.hpp"
#include "../tile/rail/crossrailtile.hpp"
@ -284,6 +285,31 @@ std::vector<std::shared_ptr<BlockPath>> BlockPath::find(BlockRailTile& startBloc
current.link = otherLink(nextNode, *current.link).get();
break;
case TileId::HiddenRailCrossOver:
{
// 1 2
// X
// 0 3
auto crossOver = tile.shared_ptr<HiddenCrossOverRailTile>();
if(contains(current.path->m_crossOvers, crossOver))
{
todo.pop(); // drop it, can't pass crossover twice
break;
}
for(size_t i = 0; i < 4; i++)
{
if(nextNode.getLink(i).get() == current.link)
{
current.node = &nextNode;
current.link = nextNode.getLink((i + 2) % 4).get(); // opposite
current.path->m_crossOvers.emplace_back(crossOver, i % 2 == 0 ? CrossState::AC : CrossState::BD);
break;
}
}
break;
}
default: // passive or non rail tiles
assert(false); // this should never happen
todo.pop(); // drop it in case it does, however that is a bug!
@ -306,7 +332,7 @@ BlockPath::BlockPath(BlockRailTile& block, BlockSide side)
BlockPath::BlockPath(const BlockPath &other)
: Path(other)
, std::enable_shared_from_this<BlockPath>()
, std::enable_shared_from_this<BlockPath>() // NOLINT(readability-redundant-member-init) -Wextra requires this
, m_fromBlock(other.m_fromBlock)
, m_fromSide(other.m_fromSide)
, m_toBlock(other.m_toBlock)
@ -358,7 +384,7 @@ bool BlockPath::isReady() const
}
}
for(const auto& [directionControlWeak, state] : m_directionControls)
for(const auto& [directionControlWeak, state] : m_directionControls) // NOLINT(readability-use-anyofallof)
{
auto directionControl = directionControlWeak.lock();
if(!directionControl) /*[[unlikely]]*/
@ -461,6 +487,23 @@ bool BlockPath::reserve(const std::shared_ptr<Train>& train, bool dryRun)
}
}
for(const auto& [crossOverWeak, state] : m_crossOvers)
{
if(auto crossOver = crossOverWeak.lock())
{
if(!crossOver->reserve(state, dryRun))
{
assert(dryRun);
return false;
}
}
else /*[[unlikely]]*/
{
assert(dryRun);
return false;
}
}
for(const auto& [bridgeWeak, path] : m_bridges)
{
if(auto bridge = bridgeWeak.lock())

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 Reinder Feenstra
* Copyright (C) 2023-2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -37,6 +37,7 @@ class BlockRailTile;
class BridgeRailTile;
enum class BridgePath : uint8_t;
class CrossRailTile;
class HiddenCrossOverRailTile;
enum class CrossState : uint8_t;
class DirectionControlRailTile;
enum class DirectionControlState : uint8_t;
@ -60,6 +61,7 @@ class BlockPath : public Path, public std::enable_shared_from_this<BlockPath>
std::vector<std::pair<std::weak_ptr<TurnoutRailTile>, TurnoutPosition>> m_turnouts; //!< required turnout positions for the path
std::vector<std::pair<std::weak_ptr<DirectionControlRailTile>, DirectionControlState>> m_directionControls; //!< required direction control states for the path
std::vector<std::pair<std::weak_ptr<CrossRailTile>, CrossState>> m_crossings; //!< required crossing states for the path
std::vector<std::pair<std::weak_ptr<HiddenCrossOverRailTile>, CrossState>> m_crossOvers; //!< required crossing states for the path
std::vector<std::pair<std::weak_ptr<BridgeRailTile>, BridgePath>> m_bridges; //!< bridges to reserve
std::vector<std::weak_ptr<SignalRailTile>> m_signals; //!< signals in path
std::weak_ptr<NXButtonRailTile> m_nxButtonFrom;

Datei anzeigen

@ -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);

Datei anzeigen

@ -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)}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023-2024 Reinder Feenstra
* Copyright (C) 2023-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -37,7 +37,7 @@ const std::shared_ptr<Link>& Path::otherLink(const Node& node, const Link& link)
return noLink;
}
tcb::span<const Path::TurnoutPositionLink> Path::getTurnoutLinks(TurnoutRailTile& turnout, const Link& link)
std::span<const Path::TurnoutPositionLink> Path::getTurnoutLinks(TurnoutRailTile& turnout, const Link& link)
{
static constexpr std::array<TurnoutPositionLink, 1> straight0{{{TurnoutPosition::Straight, 0}}};
static constexpr std::array<TurnoutPositionLink, 1> left0{{{TurnoutPosition::Left, 0}}};

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 Reinder Feenstra
* Copyright (C) 2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -24,7 +24,7 @@
#define TRAINTASTIC_SERVER_BOARD_MAP_PATH_HPP
#include <memory>
#include <tcb/span.hpp>
#include <span>
class TurnoutRailTile;
enum class TurnoutPosition : uint8_t;
@ -41,7 +41,7 @@ protected:
};
static const std::shared_ptr<Link>& otherLink(const Node& node, const Link& link);
static tcb::span<const TurnoutPositionLink> getTurnoutLinks(TurnoutRailTile& turnout, const Link& link);
static std::span<const TurnoutPositionLink> getTurnoutLinks(TurnoutRailTile& turnout, const Link& link);
};
#endif

Datei anzeigen

@ -92,7 +92,7 @@ void NXManager::released(NXButtonRailTile& button)
bool NXManager::selectPath(const NXButtonRailTile& from, const NXButtonRailTile& to)
{
for(auto& path : from.block->paths())
for(const auto& path : from.block->paths())
{
if(path->nxButtonTo().get() == &to && path->nxButtonFrom().get() == &from)
{

Datei anzeigen

@ -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

Datei anzeigen

@ -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;
}

Datei anzeigen

@ -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

Datei anzeigen

@ -0,0 +1,37 @@
/**
* server/src/board/tile/hidden/hiddentile.hpp
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef TRAINTASTIC_SERVER_BOARD_TILE_HIDDEN_HIDDENTILE_HPP
#define TRAINTASTIC_SERVER_BOARD_TILE_HIDDEN_HIDDENTILE_HPP
#include "../tile.hpp"
class HiddenTile : public Tile
{
protected:
HiddenTile(World& world, TileId tileId_)
: Tile(world, {}, tileId_)
{
}
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
* Copyright (C) 2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -74,7 +74,7 @@ SwitchTile::SwitchTile(World& world, std::string_view _id)
m_interfaceItems.add(colorOff);
Attributes::addObjectEditor(value, false);
Attributes::addAliases(value, tcb::span<const bool>(valueAliasKeys), tcb::span<const std::string>(valueAliasValues));
Attributes::addAliases(value, std::span<const bool>(valueAliasKeys), std::span<const std::string>(valueAliasValues));
m_interfaceItems.add(value);
Attributes::addDisplayName(outputMap, DisplayName::BoardTile::outputMap);

Datei anzeigen

@ -264,14 +264,12 @@ void BlockRailTile::inputItemValueChanged(BlockInputMapItem& item)
//! \todo log something (at least in debug)
break;
}
else
{
train = status->train.value();
direction = path->toSide() == BlockSide::A ? BlockTrainDirection::TowardsB : BlockTrainDirection::TowardsA;
}
}
}
}
if(train)
{
@ -291,7 +289,7 @@ void BlockRailTile::inputItemValueChanged(BlockInputMapItem& item)
if(enterA != enterB)
{
auto& blockStatus = enterA ? trains.front() : trains.back();
const auto& blockStatus = enterA ? trains.front() : trains.back();
TrainTracking::enter(blockStatus);
}
else
@ -389,7 +387,7 @@ void BlockRailTile::identificationEvent(BlockInputMapItem& /*item*/, Identificat
}
}
const std::shared_ptr<BlockPath> BlockRailTile::getReservedPath(BlockSide side) const
std::shared_ptr<BlockPath> BlockRailTile::getReservedPath(BlockSide side) const
{
assert(side == BlockSide::A || side == BlockSide::B);
return m_reservedPaths[static_cast<uint8_t>(side)].lock();

Datei anzeigen

@ -112,7 +112,7 @@ class BlockRailTile : public RailTile
void inputItemValueChanged(BlockInputMapItem& item);
void identificationEvent(BlockInputMapItem& item, IdentificationEventType eventType, uint16_t identifier, Direction direction, uint8_t category);
const std::shared_ptr<BlockPath> getReservedPath(BlockSide side) const;
std::shared_ptr<BlockPath> getReservedPath(BlockSide side) const;
bool reserve(const std::shared_ptr<BlockPath>& blockPath, const std::shared_ptr<Train>& train, BlockSide side, bool dryRun = false);
bool release(BlockSide side, bool dryRun = false);

Datei anzeigen

@ -62,7 +62,7 @@ class DirectionControlRailTile final : public StraightRailTile
std::optional<std::reference_wrapper<const Node>> node() const final { return m_node; }
std::optional<std::reference_wrapper<Node>> node() final { return m_node; }
bool reserve(DirectionControlState turnoutPosition, bool dryRun = false);
bool reserve(DirectionControlState directionControlState, bool dryRun = false);
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2024 Reinder Feenstra
* Copyright (C) 2020-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -71,7 +71,7 @@ Signal2AspectRailTile::Signal2AspectRailTile(World& world, std::string_view _id)
SignalRailTile(world, _id, TileId::RailSignal2Aspect)
{
// Skip Unknown aspect
tcb::span<const SignalAspect, 2> setAspectValues = tcb::make_span(aspectValues).subspan<1>();
std::span<const SignalAspect, 2> setAspectValues = std::span(aspectValues).subspan<1>();
outputMap.setValueInternal(std::make_shared<SignalOutputMap>(*this, outputMap.name(), std::initializer_list<SignalAspect>{SignalAspect::Stop, SignalAspect::Proceed}, getDefaultActionValue));

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2024 Reinder Feenstra
* Copyright (C) 2020-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -114,7 +114,7 @@ Signal3AspectRailTile::Signal3AspectRailTile(World& world, std::string_view _id)
SignalRailTile(world, _id, TileId::RailSignal3Aspect)
{
// Skip Unknown aspect
tcb::span<const SignalAspect, 3> setAspectValues = tcb::make_span(aspectValues).subspan<1>();
std::span<const SignalAspect, 3> setAspectValues = std::span(aspectValues).subspan<1>();
outputMap.setValueInternal(std::make_shared<SignalOutputMap>(*this, outputMap.name(), std::initializer_list<SignalAspect>{SignalAspect::Stop, SignalAspect::ProceedReducedSpeed, SignalAspect::Proceed}, getDefaultActionValue));

Datei anzeigen

@ -64,11 +64,11 @@ std::optional<OutputActionValue> SignalRailTile::getDefaultActionValue(SignalAsp
{
return static_cast<int16_t>(0);
}
else if(signalAspect == SignalAspect::ProceedReducedSpeed)
if(signalAspect == SignalAspect::ProceedReducedSpeed)
{
return static_cast<int16_t>(1);
}
else if(signalAspect == SignalAspect::Proceed)
if(signalAspect == SignalAspect::Proceed)
{
return static_cast<int16_t>(16);
}
@ -98,6 +98,7 @@ SignalRailTile::SignalRailTile(World& world, std::string_view _id, TileId tileId
Attributes::addEnabled(name, editable);
m_interfaceItems.add(name);
Attributes::addDisplayName(requireReservation, "board_tile.rail.signal:require_reservation");
Attributes::addEnabled(requireReservation, editable);
Attributes::addValues(requireReservation, autoYesNoValues);
m_interfaceItems.add(requireReservation);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2022,2024 Reinder Feenstra
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -63,7 +63,7 @@ Turnout3WayRailTile::Turnout3WayRailTile(World& world, std::string_view _id)
: TurnoutRailTile(world, _id, TileId::RailTurnout3Way, 4)
{
// Skip Unknown position
tcb::span<const TurnoutPosition, 3> setPositionValues = tcb::make_span(positionValues).subspan<1>();
std::span<const TurnoutPosition, 3> setPositionValues = std::span(positionValues).subspan<1>();
outputMap.setValueInternal(std::make_shared<TurnoutOutputMap>(*this, outputMap.name(), std::initializer_list<TurnoutPosition>{TurnoutPosition::Straight, TurnoutPosition::Left, TurnoutPosition::Right}, getDefaultActionValue));

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2022,2024 Reinder Feenstra
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -37,10 +37,10 @@ namespace PositionValues
};
}
static constexpr tcb::span<const TurnoutPosition> positionValuesSingleMotor = tcb::make_span(PositionValues::singleMotor);
static constexpr tcb::span<const TurnoutPosition> positionValuesDualMotor = tcb::make_span(PositionValues::dualMotor);
static constexpr tcb::span<const TurnoutPosition> setPositionValuesSingleMotor = tcb::make_span(PositionValues::singleMotor).subspan<1>();
static constexpr tcb::span<const TurnoutPosition> setPositionValuesDualMotor = tcb::make_span(PositionValues::dualMotor).subspan<1>();
static constexpr std::span<const TurnoutPosition> positionValuesSingleMotor = std::span(PositionValues::singleMotor);
static constexpr std::span<const TurnoutPosition> positionValuesDualMotor = std::span(PositionValues::dualMotor);
static constexpr std::span<const TurnoutPosition> setPositionValuesSingleMotor = std::span(PositionValues::singleMotor).subspan<1>();
static constexpr std::span<const TurnoutPosition> setPositionValuesDualMotor = std::span(PositionValues::dualMotor).subspan<1>();
TurnoutDoubleSlipRailTile::TurnoutDoubleSlipRailTile(World& world, std::string_view _id)
: TurnoutSlipRailTile(world, _id, TileId::RailTurnoutDoubleSlip)

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2022,2024 Reinder Feenstra
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -67,7 +67,7 @@ TurnoutLeftRailTile::TurnoutLeftRailTile(World& world, std::string_view _id, Til
: TurnoutRailTile(world, _id, tileId_, 3)
{
// Skip Unknown position
tcb::span<const TurnoutPosition, 2> setPositionValues = tcb::make_span(positionValues).subspan<1>();
std::span<const TurnoutPosition, 2> setPositionValues = std::span(positionValues).subspan<1>();
outputMap.setValueInternal(std::make_shared<TurnoutOutputMap>(*this, outputMap.name(), std::initializer_list<TurnoutPosition>{TurnoutPosition::Straight, TurnoutPosition::Left}, getDefaultActionValue));

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2022,2024 Reinder Feenstra
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -67,7 +67,7 @@ TurnoutRightRailTile::TurnoutRightRailTile(World& world, std::string_view _id, T
: TurnoutRailTile(world, _id, tileId_, 3)
{
// Skip Unknown position
tcb::span<const TurnoutPosition, 2> setPositionValues = tcb::make_span(positionValues).subspan<1>();
std::span<const TurnoutPosition, 2> setPositionValues = std::span(positionValues).subspan<1>();
outputMap.setValueInternal(std::make_shared<TurnoutOutputMap>(*this, outputMap.name(), std::initializer_list<TurnoutPosition>{TurnoutPosition::Straight, TurnoutPosition::Right}, getDefaultActionValue));

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2022,2024 Reinder Feenstra
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -37,10 +37,10 @@ namespace PositionValues
};
}
static constexpr tcb::span<const TurnoutPosition> positionValuesSingleMotor = tcb::make_span(PositionValues::singleMotor);
static constexpr tcb::span<const TurnoutPosition> positionValuesDualMotor = tcb::make_span(PositionValues::dualMotor);
static constexpr tcb::span<const TurnoutPosition> setPositionValuesSingleMotor = tcb::make_span(PositionValues::singleMotor).subspan<1>();
static constexpr tcb::span<const TurnoutPosition> setPositionValuesDualMotor = tcb::make_span(PositionValues::dualMotor).subspan<1>();
static constexpr std::span<const TurnoutPosition> positionValuesSingleMotor = std::span(PositionValues::singleMotor);
static constexpr std::span<const TurnoutPosition> positionValuesDualMotor = std::span(PositionValues::dualMotor);
static constexpr std::span<const TurnoutPosition> setPositionValuesSingleMotor = std::span(PositionValues::singleMotor).subspan<1>();
static constexpr std::span<const TurnoutPosition> setPositionValuesDualMotor = std::span(PositionValues::dualMotor).subspan<1>();
TurnoutSingleSlipRailTile::TurnoutSingleSlipRailTile(World& world, std::string_view _id)
: TurnoutSlipRailTile(world, _id, TileId::RailTurnoutSingleSlip)

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2022,2024 Reinder Feenstra
* Copyright (C) 2020-2022,2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -40,7 +40,7 @@ TurnoutWyeRailTile::TurnoutWyeRailTile(World& world, std::string_view _id)
: TurnoutRailTile(world, _id, TileId::RailTurnoutWye, 3)
{
// Skip Unknown position
tcb::span<const TurnoutPosition, 2> setPositionValues = tcb::make_span(positionValues).subspan<1>();
std::span<const TurnoutPosition, 2> setPositionValues = std::span(positionValues).subspan<1>();
outputMap.setValueInternal(std::make_shared<TurnoutOutputMap>(*this, outputMap.name(), std::initializer_list<TurnoutPosition>{TurnoutPosition::Left, TurnoutPosition::Right}, getDefaultActionValue));

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2020-2021,2023-2024 Reinder Feenstra
* Copyright (C) 2020-2021,2023-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -37,7 +37,7 @@ Tile::Tile(World& world, std::string_view _id, TileId tileId_)
, width{this, "width", 1, PropertyFlags::ReadOnly | PropertyFlags::Store}
{
Attributes::addObjectEditor(tileId, false);
Attributes::addValues(tileId, tcb::span<const TileId, 0>{});
Attributes::addValues(tileId, std::span<const TileId, 0>{});
m_interfaceItems.add(tileId);
Attributes::addObjectEditor(x, false);
@ -59,6 +59,21 @@ Tile::Tile(World& world, std::string_view _id, TileId tileId_)
m_interfaceItems.add(width);
}
std::optional<Connector> Tile::getConnector(Connector::Direction direction) const
{
std::vector<Connector> connectors;
connectors.reserve(8);
getConnectors(connectors);
for(const auto& c : connectors)
{
if(c.direction == direction)
{
return c;
}
}
return std::nullopt;
}
Board& Tile::getBoard()
{
for(const auto& board : *m_world.boards)

Datei anzeigen

@ -68,6 +68,7 @@ class Tile : public IdObject
virtual std::optional<std::reference_wrapper<const Node>> node() const { return {}; }
virtual std::optional<std::reference_wrapper<Node>> node() { return {}; }
virtual void getConnectors(std::vector<Connector>& /*connectors*/) const {}
std::optional<Connector> getConnector(Connector::Direction direction) const;
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2022 Reinder Feenstra
* Copyright (C) 2021-2022,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -25,7 +25,7 @@
#include "interfaceitem.hpp"
#include <list>
#include <tcb/span.hpp>
#include <span>
#include "eventflags.hpp"
#include "argument.hpp"
#include "typeinfo.hpp"
@ -54,7 +54,7 @@ class AbstractEvent : public InterfaceItem
inline EventFlags flags() const { return m_flags; }
virtual tcb::span<const TypeInfo> argumentTypeInfo() const = 0;
virtual std::span<const TypeInfo> argumentTypeInfo() const = 0;
void connect(std::shared_ptr<AbstractEventHandler> handler);
bool disconnect(const std::shared_ptr<AbstractEventHandler>& handler);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2022 Reinder Feenstra
* Copyright (C) 2019-2022,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -28,7 +28,7 @@
#include <vector>
#include <variant>
#include <stdexcept>
#include <tcb/span.hpp>
#include <span>
#include "argument.hpp"
#include "typeinfo.hpp"
@ -113,7 +113,7 @@ class AbstractMethod : public InterfaceItem
inline MethodFlags flags() const { return m_flags; }
virtual tcb::span<const TypeInfo> argumentTypeInfo() const = 0;
virtual std::span<const TypeInfo> argumentTypeInfo() const = 0;
virtual TypeInfo resultTypeInfo() const = 0;
virtual Result call(const Arguments& args) = 0;
};

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021,2023 Reinder Feenstra
* Copyright (C) 2021,2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -24,7 +24,7 @@
#define TRAINTASTIC_SERVER_CORE_ABSTRACTVECTORPROPERTY_HPP
#include "baseproperty.hpp"
#include <tcb/span.hpp>
#include <span>
#include "objectptr.hpp"
class AbstractVectorProperty : public BaseProperty
@ -52,7 +52,7 @@ class AbstractVectorProperty : public BaseProperty
virtual void setObject(size_t index, const ObjectPtr& value) = 0;
virtual void loadJSON(const nlohmann::json& values) = 0;
virtual void loadObjects(tcb::span<ObjectPtr> values) = 0;
virtual void loadObjects(std::span<ObjectPtr> values) = 0;
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2024 Reinder Feenstra
* Copyright (C) 2019-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -30,7 +30,7 @@
#include "property.hpp"
#include "unitproperty.hpp"
#include "vectorproperty.hpp"
#include <tcb/span.hpp>
#include <span>
struct Attributes
{
@ -59,7 +59,7 @@ struct Attributes
}
template<class T>
static inline void addAliases(Property<T>& property, tcb::span<const T> keys, tcb::span<const std::string> values)
static inline void addAliases(Property<T>& property, std::span<const T> keys, std::span<const std::string> values)
{
assert(keys.size() == values.size());
property.addAttribute(AttributeName::AliasKeys, keys);
@ -67,7 +67,7 @@ struct Attributes
}
template<class T>
static inline void setAliases(Property<T>& property, tcb::span<const T> keys, tcb::span<const std::string> values)
static inline void setAliases(Property<T>& property, std::span<const T> keys, std::span<const std::string> values)
{
assert(keys.size() == values.size());
property.setAttribute(AttributeName::AliasKeys, keys);
@ -75,7 +75,7 @@ struct Attributes
}
template<class T, typename Unit>
static inline void addAliases(UnitProperty<T, Unit>& property, tcb::span<const T> keys, tcb::span<const std::string> values)
static inline void addAliases(UnitProperty<T, Unit>& property, std::span<const T> keys, std::span<const std::string> values)
{
assert(keys.size() == values.size());
property.addAttribute(AttributeName::AliasKeys, keys);
@ -88,7 +88,7 @@ struct Attributes
}
template<size_t N>
static inline void addClassList(InterfaceItem& item, tcb::span<const std::string_view, N> classList)
static inline void addClassList(InterfaceItem& item, std::span<const std::string_view, N> classList)
{
item.addAttribute(AttributeName::ClassList, classList);
}
@ -175,6 +175,19 @@ struct Attributes
property.setAttribute(AttributeName::Min, value);
}
template<class T, class Unit>
static inline T getMin(UnitProperty<T, Unit>& property)
{
static_assert(std::is_floating_point_v<T>);
return property.template getAttribute<T>(AttributeName::Min);
}
template<class T, class Unit>
static inline T getMin(UnitProperty<T, Unit>& property, Unit unit)
{
return convertUnit(getMin(property), property.unit(), unit);
}
template<class T, class Unit>
static inline void setMin(UnitProperty<T, Unit>& property, T value, Unit unit)
{
@ -189,6 +202,19 @@ struct Attributes
property.setAttribute(AttributeName::Max, value);
}
template<class T, class Unit>
static inline T getMax(UnitProperty<T, Unit>& property)
{
static_assert(std::is_floating_point_v<T>);
return property.template getAttribute<T>(AttributeName::Max);
}
template<class T, class Unit>
static inline T getMax(UnitProperty<T, Unit>& property, Unit unit)
{
return convertUnit(getMax(property), property.unit(), unit);
}
template<class T, class Unit>
static inline void setMax(UnitProperty<T, Unit>& property, T value, Unit unit)
{
@ -290,19 +316,19 @@ struct Attributes
}
template<class R, class T, size_t N>
static inline void addValues(Method<R(T)>& method, tcb::span<const T, N> values)
static inline void addValues(Method<R(T)>& method, std::span<const T, N> values)
{
method.addAttribute(AttributeName::Values, values);
}
template<typename T, size_t N>
static inline void addValues(Property<T>& property, tcb::span<const T, N> values)
static inline void addValues(Property<T>& property, std::span<const T, N> values)
{
property.addAttribute(AttributeName::Values, values);
}
template<typename T, typename Unit, size_t N>
static inline void addValues(UnitProperty<T, Unit>& property, tcb::span<const T, N> values)
static inline void addValues(UnitProperty<T, Unit>& property, std::span<const T, N> values)
{
property.addAttribute(AttributeName::Values, values);
}
@ -344,13 +370,13 @@ struct Attributes
}
template<typename T, size_t N>
static inline void setValues(Property<T>& property, tcb::span<const T, N> values)
static inline void setValues(Property<T>& property, std::span<const T, N> values)
{
property.setAttribute(AttributeName::Values, values);
}
template<typename T, typename Unit, size_t N>
static inline void setValues(UnitProperty<T, Unit>& property, tcb::span<const T, N> values)
static inline void setValues(UnitProperty<T, Unit>& property, std::span<const T, N> values)
{
property.setAttribute(AttributeName::Values, values);
}
@ -374,7 +400,7 @@ struct Attributes
}
template<class R, class T, size_t N>
static inline void setValues(Method<R(T)>& method, tcb::span<const T, N> values)
static inline void setValues(Method<R(T)>& method, std::span<const T, N> values)
{
method.setAttribute(AttributeName::Values, values);
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2023 Reinder Feenstra
* Copyright (C) 2021-2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -67,7 +67,7 @@ class Event : public AbstractEvent
{
}
tcb::span<const TypeInfo> argumentTypeInfo() const final
std::span<const TypeInfo> argumentTypeInfo() const final
{
return {typeInfoArray<Args...>};
}

Datei anzeigen

@ -48,6 +48,10 @@ class EventLoop
#ifdef TRAINTASTIC_TEST
threadId = std::this_thread::get_id();
#endif
if(ioContext.stopped())
{
ioContext.restart();
}
auto work = std::make_shared<boost::asio::io_context::work>(ioContext);
ioContext.run();
}

Datei anzeigen

@ -49,10 +49,7 @@ IdObject::IdObject(World& world, std::string_view _id) :
m_interfaceItems.add(id);
}
IdObject::~IdObject()
{
//assert(m_world.expired()); // is destroy() called ??
}
IdObject::~IdObject() = default;
void IdObject::destroying()
{

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2022 Reinder Feenstra
* Copyright (C) 2019-2022,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -54,7 +54,7 @@ class InterfaceItem
}
template<typename T, size_t N>
void addAttribute(AttributeName name, tcb::span<const T, N> values)
void addAttribute(AttributeName name, std::span<const T, N> values)
{
assert(m_attributes.find(name) == m_attributes.end());
m_attributes.emplace(name, std::make_unique<SpanAttribute<T, N>>(*this, name, values));
@ -63,7 +63,7 @@ class InterfaceItem
template<typename T, size_t N>
void addAttribute(AttributeName name, const std::array<T, N>& values)
{
addAttribute(name, tcb::span<const T, N>{values.data(), values.size()});
addAttribute(name, std::span<const T, N>{values.data(), values.size()});
}
template<typename T>
@ -88,7 +88,7 @@ class InterfaceItem
}
template<typename T, size_t N>
void setAttribute(AttributeName name, tcb::span<const T, N> values)
void setAttribute(AttributeName name, std::span<const T, N> values)
{
assert(m_attributes.find(name) != m_attributes.end());
static_cast<SpanAttribute<T, N>*>(m_attributes[name].get())->setValues(values);

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 Reinder Feenstra
* Copyright (C) 2019-2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -47,7 +47,7 @@ class Method<R(A...)> : public AbstractMethod
R operator()(A... args);
tcb::span<const TypeInfo> argumentTypeInfo() const final;
std::span<const TypeInfo> argumentTypeInfo() const final;
TypeInfo resultTypeInfo() const final;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2023 Reinder Feenstra
* Copyright (C) 2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -115,7 +115,7 @@ R Method<R(A...)>::operator()(A... args)
}
template<class R, class... A>
tcb::span<const TypeInfo> Method<R(A...)>::argumentTypeInfo() const
std::span<const TypeInfo> Method<R(A...)>::argumentTypeInfo() const
{
return {typeInfoArray<A...>};
}

Datei anzeigen

@ -42,7 +42,7 @@ class ObjectList : public AbstractObjectList
protected:
Items m_items;
std::unordered_map<Object*, boost::signals2::connection> m_propertyChanged;
std::unordered_map<Object*, boost::signals2::scoped_connection> m_propertyChanged;
std::vector<ObjectListTableModel<T>*> m_models;
void deleteMethodHandler(const std::shared_ptr<T>& object)
@ -121,12 +121,6 @@ class ObjectList : public AbstractObjectList
static_assert(std::is_base_of_v<Object, T>);
}
~ObjectList()
{
for(auto& it : m_propertyChanged)
it.second.disconnect();
}
inline const_iterator begin() const noexcept { return m_items.begin(); }
inline const_iterator end() const noexcept { return m_items.end(); }
inline const std::shared_ptr<T>& front() const noexcept { return m_items.front(); }

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021,2023 Reinder Feenstra
* Copyright (C) 2021,2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -159,7 +159,7 @@ class ObjectVectorProperty : public AbstractObjectVectorProperty
m_values = std::move(values);
}
void loadObjects(tcb::span<ObjectPtr> values) final;
void loadObjects(std::span<ObjectPtr> values) final;
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021,2023 Reinder Feenstra
* Copyright (C) 2021,2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -47,7 +47,7 @@ void ObjectVectorProperty<T>::setObject(size_t index, const ObjectPtr& value)
}
template<class T>
void ObjectVectorProperty<T>::loadObjects(tcb::span<ObjectPtr> values)
void ObjectVectorProperty<T>::loadObjects(std::span<ObjectPtr> values)
{
std::vector<std::shared_ptr<T>> objects;
objects.reserve(values.size());

Datei anzeigen

@ -40,8 +40,3 @@ SerialDeviceProperty::SerialDeviceProperty(Object* object, std::string_view name
static_cast<VectorRefAttribute<std::string>&>(*it->second).internalChanged();
});
}
SerialDeviceProperty::~SerialDeviceProperty()
{
m_serialPortListChanged.disconnect();
}

Datei anzeigen

@ -29,11 +29,11 @@
class SerialDeviceProperty final : public Property<std::string>
{
private:
boost::signals2::connection m_serialPortListChanged;
boost::signals2::scoped_connection m_serialPortListChanged;
public:
SerialDeviceProperty(Object* object, std::string_view name, const std::string& value, PropertyFlags flags);
~SerialDeviceProperty() final;
~SerialDeviceProperty() final = default;
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2020,2023-2024 Reinder Feenstra
* Copyright (C) 2019-2020,2023-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -23,15 +23,15 @@
#ifndef TRAINTASTIC_SERVER_CORE_SPANATTRIBUTE_HPP
#define TRAINTASTIC_SERVER_CORE_SPANATTRIBUTE_HPP
#include <tcb/span.hpp>
#include <span>
#include "abstractvaluesattribute.hpp"
#include "to.hpp"
template<typename T, size_t N = tcb::dynamic_extent>
template<typename T, size_t N = std::dynamic_extent>
class SpanAttribute : public AbstractValuesAttribute
{
public:
typedef tcb::span<const T, N> Span;
typedef std::span<const T, N> Span;
protected:
Span m_values;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2024 Reinder Feenstra
* Copyright (C) 2024-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -27,7 +27,7 @@
constexpr std::array<double, 1> aliasKeys = {{SpeedLimitProperty::noLimitValue}};
inline static const std::array<std::string, 1> aliasValues{{"$speed_limit_property:no_limit$"}};
static tcb::span<const double> getValues(SpeedUnit unit)
static std::span<const double> getValues(SpeedUnit unit)
{
switch(unit)
{
@ -97,7 +97,7 @@ void SpeedLimitProperty::addAttributes()
auto values = getValues(unit());
::Attributes::addMin(*this, values.front());
::Attributes::addValues(*this, values);
::Attributes::addAliases(*this, tcb::span<const double>(aliasKeys), tcb::span<const std::string>(aliasValues));
::Attributes::addAliases(*this, std::span<const double>(aliasKeys), std::span<const std::string>(aliasValues));
m_attributeUnit = m_unit;
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021,2023-2024 Reinder Feenstra
* Copyright (C) 2021,2023-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -332,7 +332,7 @@ class VectorProperty : public AbstractVectorProperty
throw conversion_error();
}
void loadObjects(tcb::span<ObjectPtr> /*values*/) final
void loadObjects(std::span<ObjectPtr> /*values*/) final
{
throw conversion_error();
}

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2023 Reinder Feenstra
* Copyright (C) 2019-2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -125,7 +125,7 @@ Decoder::Decoder(World& world, std::string_view _id) :
m_interfaceItems.add(interface);
Attributes::addEnabled(protocol, false);
Attributes::addValues(protocol, tcb::span<const DecoderProtocol>{});
Attributes::addValues(protocol, std::span<const DecoderProtocol>{});
Attributes::addVisible(protocol, false);
m_interfaceItems.add(protocol);
@ -151,7 +151,7 @@ Decoder::Decoder(World& world, std::string_view _id) :
Attributes::addDisplayName(speedSteps, DisplayName::Hardware::speedSteps);
Attributes::addEnabled(speedSteps, false);
Attributes::addValues(speedSteps, tcb::span<const uint8_t>{});
Attributes::addValues(speedSteps, std::span<const uint8_t>{});
Attributes::addVisible(speedSteps, false);
m_interfaceItems.add(speedSteps);
@ -193,10 +193,15 @@ void Decoder::loaded()
bool Decoder::hasFunction(uint32_t number) const
{
for(const auto& f : *functions)
if(f->number == number)
return true;
return false;
return std::any_of(functions->begin(), functions->end(),
[number](const auto& f)
{
return f->number == number;
});
//for(const auto& f : *functions)
// if(f->number == number)
// return true;
//return false;
}
std::shared_ptr<const DecoderFunction> Decoder::getFunction(uint32_t number) const
@ -376,7 +381,7 @@ void Decoder::protocolChanged()
checkSpeedSteps();
}
else
Attributes::setValues(speedSteps, tcb::span<const uint8_t>{});
Attributes::setValues(speedSteps, std::span<const uint8_t>{});
}
else
{
@ -390,7 +395,7 @@ bool Decoder::checkProtocol()
{
const auto protocols = protocol.getSpanAttribute<DecoderProtocol>(AttributeName::Values).values();
assert(!protocols.empty());
if(auto it = std::find(protocols.begin(), protocols.end(), protocol); it == protocols.end())
if(const auto it = std::find(protocols.begin(), protocols.end(), protocol); it == protocols.end())
{
protocol = protocols.front();
return true;
@ -413,7 +418,7 @@ bool Decoder::checkAddress()
bool Decoder::checkSpeedSteps()
{
const auto values = speedSteps.getSpanAttribute<uint8_t>(AttributeName::Values).values();
if(auto it = std::find(values.begin(), values.end(), speedSteps); it == values.end())
if(const auto it = std::find(values.begin(), values.end(), speedSteps); it == values.end())
{
speedSteps = values.back();
return true;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2023 Reinder Feenstra
* Copyright (C) 2021-2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -65,7 +65,7 @@ std::pair<uint16_t, uint16_t> DecoderController::decoderAddressMinMax(DecoderPro
return noAddressMinMax;
}
tcb::span<const uint8_t> DecoderController::decoderSpeedSteps(DecoderProtocol protocol) const
std::span<const uint8_t> DecoderController::decoderSpeedSteps(DecoderProtocol protocol) const
{
static constexpr std::array<uint8_t, 3> dccSpeedSteps{{14, 28, 128}};
static constexpr std::array<uint8_t, 3> motorolaSpeedSteps{{14, 27, 28}};

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2023 Reinder Feenstra
* Copyright (C) 2021-2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -26,7 +26,7 @@
#include <cstdint>
#include <vector>
#include <memory>
#include <tcb/span.hpp>
#include <span>
#include "../../core/objectproperty.hpp"
#ifdef interface
@ -70,7 +70,7 @@ class DecoderController
//! \brief Get supported protocols
//! \return Supported protocols, may not be empty and must be constant for the instance!
virtual tcb::span<const DecoderProtocol> decoderProtocols() const = 0;
virtual std::span<const DecoderProtocol> decoderProtocols() const = 0;
//! \brief Get address range for given protocol
//! \param[in] protocol The decoder protocol
@ -80,7 +80,7 @@ class DecoderController
//! \brief Get speed step options for given protocol
//! \param[in] protocol The decoder protocol
//! \return Speed step options for the given protocol
virtual tcb::span<const uint8_t> decoderSpeedSteps(DecoderProtocol protocol) const;
virtual std::span<const uint8_t> decoderSpeedSteps(DecoderProtocol protocol) const;
[[nodiscard]] bool addDecoder(Decoder& decoder);
[[nodiscard]] bool removeDecoder(Decoder& decoder);

Datei anzeigen

@ -44,7 +44,7 @@ DecoderFunctions::DecoderFunctions(Object& _parent, std::string_view parentPrope
}
auto function = std::make_shared<DecoderFunction>(decoder, number);
function->name = "F" + std::to_string(number);
function->name = std::to_string(number).insert(0, 1, 'F');
function->number = number;
if(number == 0) // F0 is (almost) always the light function
function->function = DecoderFunctionFunction::Light;

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2019-2021,2023 Reinder Feenstra
* Copyright (C) 2019-2021,2023,2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -49,6 +49,11 @@ class DecoderFunctions : public SubObject
inline const_iterator begin() const { return items.begin(); }
inline const_iterator end() const { return items.end(); }
inline bool empty() const
{
return items.empty();
}
};
#endif

Datei anzeigen

@ -3,7 +3,7 @@
*
* This file is part of the traintastic source code.
*
* Copyright (C) 2021-2024 Reinder Feenstra
* Copyright (C) 2021-2025 Reinder Feenstra
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -127,10 +127,10 @@ DCCEXInterface::DCCEXInterface(World& world, std::string_view _id)
updateVisible();
}
tcb::span<const DecoderProtocol> DCCEXInterface::decoderProtocols() const
std::span<const DecoderProtocol> DCCEXInterface::decoderProtocols() const
{
static constexpr std::array<DecoderProtocol, 2> protocols{DecoderProtocol::DCCShort, DecoderProtocol::DCCLong};
return tcb::span<const DecoderProtocol>{protocols.data(), protocols.size()};
return std::span<const DecoderProtocol>{protocols.data(), protocols.size()};
}
std::pair<uint16_t, uint16_t> DCCEXInterface::decoderAddressMinMax(DecoderProtocol protocol) const
@ -140,13 +140,13 @@ std::pair<uint16_t, uint16_t> DCCEXInterface::decoderAddressMinMax(DecoderProtoc
return DecoderController::decoderAddressMinMax(protocol);
}
tcb::span<const uint8_t> DCCEXInterface::decoderSpeedSteps(DecoderProtocol protocol) const
std::span<const uint8_t> DCCEXInterface::decoderSpeedSteps(DecoderProtocol protocol) const
{
(void)protocol; // silence unused warning for release build
assert(protocol == DecoderProtocol::DCCShort || protocol == DecoderProtocol::DCCLong);
const auto& speedStepValues = DCCEX::Settings::speedStepValues;
// find value in array so we can create a span, using a span of a variable won't work due to the compare with prevous value in the attribute setter
if(auto it = std::find(speedStepValues.begin(), speedStepValues.end(), dccex->speedSteps); it != speedStepValues.end()) /*[[likely]]/*/
if(const auto it = std::find(speedStepValues.begin(), speedStepValues.end(), dccex->speedSteps); it != speedStepValues.end()) /*[[likely]]/*/ // NOLINT(readability-qualified-auto) windows requires const auto
return {&(*it), 1};
assert(false);
return {};
@ -158,7 +158,7 @@ void DCCEXInterface::decoderChanged(const Decoder& decoder, DecoderChangeFlags c
m_kernel->decoderChanged(decoder, changes, functionNumber);
}
std::pair<uint32_t, uint32_t> DCCEXInterface::inputAddressMinMax(uint32_t) const
std::pair<uint32_t, uint32_t> DCCEXInterface::inputAddressMinMax(uint32_t /*channel*/) const
{
return {DCCEX::Kernel::idMin, DCCEX::Kernel::idMax};
}
@ -169,7 +169,7 @@ void DCCEXInterface::inputSimulateChange(uint32_t channel, uint32_t address, Sim
m_kernel->simulateInputChange(address, action);
}
tcb::span<const OutputChannel> DCCEXInterface::outputChannels() const
std::span<const OutputChannel> DCCEXInterface::outputChannels() const
{
static const auto values = makeArray(OutputChannel::Accessory, OutputChannel::Turnout, OutputChannel::Output, OutputChannel::DCCext);
return values;
@ -373,7 +373,7 @@ void DCCEXInterface::check() const
checkDecoder(*decoder);
}
void DCCEXInterface::checkDecoder(const Decoder& decoder) const
void DCCEXInterface::checkDecoder(const Decoder& decoder)
{
for(const auto& function : *decoder.functions)
if(function->number > DCCEX::Config::functionNumberMax)

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden Mehr anzeigen