Compare commits
372 Commits
Author | SHA1 | Date |
---|---|---|
gfgit | 130b4312c4 | |
Filippo Gentile | 1aa23f281a | |
Filippo Gentile | 9f14ee1c64 | |
Filippo Gentile | 42c486f5cf | |
Luca Pellegrini | 21a54888f2 | |
Luca Pellegrini | 05c45381d4 | |
Filippo Gentile | 87ec3e53ce | |
Filippo Gentile | 7ac337a181 | |
Filippo Gentile | 4e6f49dd44 | |
Filippo Gentile | efd60f9ce6 | |
Filippo Gentile | 48faf57a93 | |
Filippo Gentile | 90ca41cb55 | |
Filippo Gentile | 9abb3b014c | |
Filippo Gentile | 6323a87ff0 | |
Filippo Gentile | 9bf0027fe3 | |
Filippo Gentile | 6923642a53 | |
Filippo Gentile | 5d643196e0 | |
Filippo Gentile | 62ee86b8b5 | |
Filippo Gentile | 585476240f | |
Filippo Gentile | a4393aec43 | |
Filippo Gentile | 02f4938396 | |
Filippo Gentile | 49c7f60c94 | |
Filippo Gentile | 7fb7271745 | |
Filippo Gentile | 1cbc5e38a2 | |
Filippo Gentile | f60f9ed11d | |
Filippo Gentile | 1b65e413de | |
Filippo Gentile | 3847853913 | |
Filippo Gentile | 8c61554a5d | |
Filippo Gentile | 4e622d0841 | |
Filippo Gentile | 0149217499 | |
Filippo Gentile | e5fc856c10 | |
Filippo Gentile | ecf934bf86 | |
Filippo Gentile | 5f165502f8 | |
Filippo Gentile | 232f4e1dcc | |
Filippo Gentile | 576a79e065 | |
Filippo Gentile | c61fac42b7 | |
Filippo Gentile | 1618cc47ad | |
Filippo Gentile | 6dacc44880 | |
Filippo Gentile | 3b969aafce | |
Filippo Gentile | 77dcb5d0de | |
Filippo Gentile | 9383f4a922 | |
Filippo Gentile | a352eafdbb | |
Filippo Gentile | b2a8e04faf | |
Filippo Gentile | 66442b13cb | |
Filippo Gentile | 706d9dadb4 | |
Filippo Gentile | de54030785 | |
Filippo Gentile | 0c04121867 | |
Filippo Gentile | 017005e1a9 | |
Filippo Gentile | 8667ea96fe | |
Filippo Gentile | 3672395be5 | |
Filippo Gentile | a53c8948fe | |
Filippo Gentile | 0273501500 | |
gfgit | b951cedbc9 | |
Filippo Gentile | f352555266 | |
Filippo Gentile | f0ca606bf2 | |
Filippo Gentile | af337e3065 | |
Filippo Gentile | 214f08dc3e | |
Filippo Gentile | fca21eeae6 | |
Filippo Gentile | cb20814a69 | |
Filippo Gentile | 43658c227a | |
Filippo Gentile | 3df21ef200 | |
Filippo Gentile | 5e94d9b603 | |
Filippo Gentile | a3db250463 | |
Filippo Gentile | 98cd6f2702 | |
Filippo Gentile | 58523ed724 | |
Filippo Gentile | 04a148b9ba | |
gfgit | cfc705a009 | |
Filippo Gentile | b2bbf975f8 | |
Filippo Gentile | 79e609b720 | |
Filippo Gentile | 78bf2166f9 | |
Filippo Gentile | 8583684780 | |
Filippo Gentile | e83d8f601e | |
Filippo Gentile | 8e2f7c545b | |
Filippo Gentile | 4360e4b5b1 | |
Filippo Gentile | 140d076651 | |
Filippo Gentile | 7728133a95 | |
Filippo Gentile | 1438c4b118 | |
Filippo Gentile | 774d4497c5 | |
Filippo Gentile | eb7b773fc4 | |
Filippo Gentile | 42a468a1b5 | |
Filippo Gentile | 1bc031cae6 | |
Filippo Gentile | 0a5d02b862 | |
Filippo Gentile | d161b87859 | |
Filippo Gentile | fede5a826d | |
Filippo Gentile | f6148cf0ba | |
Filippo Gentile | f626d9ff8d | |
Filippo Gentile | 90f6669614 | |
Filippo Gentile | 76c774554e | |
Filippo Gentile | 0b34512f2f | |
Filippo Gentile | 9727d68493 | |
Filippo Gentile | 6bb011d96b | |
Filippo Gentile | b7e1f7284a | |
Filippo Gentile | dfc856aabe | |
Filippo Gentile | 042644c00a | |
Filippo Gentile | 0497d6442f | |
Filippo Gentile | 0c1d9db3bf | |
Filippo Gentile | cbebaf5204 | |
Filippo Gentile | 6a3281b4f1 | |
Filippo Gentile | 5608f0bc28 | |
Filippo Gentile | 36bfe821f1 | |
Filippo Gentile | b1f202c1cd | |
Filippo Gentile | 38e1248f60 | |
Filippo Gentile | b8483827ae | |
Filippo Gentile | 2a3496f0bc | |
Filippo Gentile | 63335f7613 | |
Filippo Gentile | 7a3a34a1ac | |
Filippo Gentile | 816a0830d9 | |
Filippo Gentile | 1c7d288632 | |
Filippo Gentile | 3ef0a142ef | |
Filippo Gentile | 783683b232 | |
Filippo Gentile | 573dfac662 | |
Filippo Gentile | 82c154fca2 | |
Filippo Gentile | d185c0016e | |
Filippo Gentile | 48d7180cd5 | |
Filippo Gentile | d3d6e099b6 | |
Filippo Gentile | 4c25de9133 | |
Filippo Gentile | f1fc42dd9b | |
Filippo Gentile | 1816b6292c | |
Filippo Gentile | c04d77d004 | |
Filippo Gentile | e536d1897f | |
Filippo Gentile | ba97509c2b | |
Filippo Gentile | 1aa479afcd | |
Filippo Gentile | 0f0aa2c94e | |
Filippo Gentile | 80630fbb2a | |
gfgit | b48372faa7 | |
Filippo Gentile | e0e22c8830 | |
Filippo Gentile | 11637625f8 | |
Filippo Gentile | a005f0f49e | |
Filippo Gentile | 1c4d27351d | |
Filippo Gentile | 7fe4e130ea | |
Filippo Gentile | 50a2f94233 | |
Filippo Gentile | b533c5dfe6 | |
Filippo Gentile | 3ad2ab563f | |
Filippo Gentile | 681220732c | |
Filippo Gentile | 5ccf083911 | |
Filippo Gentile | 95c1633103 | |
Filippo Gentile | 4dd467212c | |
Filippo Gentile | 97cd886109 | |
Filippo Gentile | d25897424e | |
Filippo Gentile | 3427248703 | |
Filippo Gentile | 7e12edba26 | |
Filippo Gentile | e749e82eb4 | |
Filippo Gentile | a622a49bdc | |
Filippo Gentile | 5313a0f287 | |
Filippo Gentile | d28c2b868b | |
Filippo Gentile | 83e60d26a1 | |
Filippo Gentile | 8d5c280663 | |
Filippo Gentile | 4e5463173e | |
Filippo Gentile | 6bde7076ca | |
Filippo Gentile | ec7897806f | |
Filippo Gentile | cc0770501d | |
gfgit | d407eb98f0 | |
Filippo Gentile | bdd6be0886 | |
Filippo Gentile | 2d3336a14f | |
Filippo Gentile | 147b532395 | |
Filippo Gentile | f5279788ce | |
Filippo Gentile | 539f07472f | |
Filippo Gentile | ea67bc93a8 | |
Filippo Gentile | 04c0b5cdfd | |
Filippo Gentile | 53421d4507 | |
Filippo Gentile | a2a487fad3 | |
Filippo Gentile | ed06754520 | |
Filippo Gentile | 34b277e6f4 | |
Filippo Gentile | fe23e91071 | |
Filippo Gentile | aceaf41475 | |
Filippo Gentile | 69318af831 | |
Filippo Gentile | c02c523433 | |
Filippo Gentile | ce1d6329d3 | |
Filippo Gentile | b36e260ab4 | |
Filippo Gentile | 163dd176bc | |
Filippo Gentile | 1c26d4f771 | |
Filippo Gentile | 98eec5abeb | |
Filippo Gentile | 85da6c49f2 | |
Filippo Gentile | d85cfe3d62 | |
Filippo Gentile | c4e24b954c | |
Filippo Gentile | d095abacd5 | |
Filippo Gentile | a3eb525e84 | |
Filippo Gentile | d1df52524a | |
Filippo Gentile | 967a7aefd4 | |
Filippo Gentile | 2688c075c7 | |
Filippo Gentile | bb4983f9eb | |
Filippo Gentile | 1f89e8e0e6 | |
Filippo Gentile | 5d7975a71e | |
Filippo Gentile | a6af65e93e | |
Filippo Gentile | add51736d4 | |
Filippo Gentile | 0d3e07050b | |
Filippo Gentile | 04f7123cf2 | |
Filippo Gentile | adeca33348 | |
Filippo Gentile | 0ebc219cf7 | |
Filippo Gentile | 682bb816d9 | |
Filippo Gentile | 2a8922c34b | |
Filippo Gentile | 2358f4296a | |
Filippo Gentile | 1e1478ebd0 | |
Filippo Gentile | 13ae3f5450 | |
Filippo Gentile | 5869fa3f3f | |
Filippo Gentile | 613088b68d | |
Filippo Gentile | 6b15cd6402 | |
Filippo Gentile | 3a9451a55c | |
Filippo Gentile | 2f58a64f2a | |
Filippo Gentile | 851520ef86 | |
Filippo Gentile | bd1f4efbdb | |
Filippo Gentile | ce30ac0edc | |
Filippo Gentile | 1c0646eb1a | |
Filippo Gentile | db70886108 | |
Filippo Gentile | 3d25dc8171 | |
Filippo Gentile | d9bfe81cfd | |
Filippo Gentile | cf40959137 | |
Filippo Gentile | 09ae6d932f | |
Filippo Gentile | eb6c94e59b | |
Filippo Gentile | ce689d5250 | |
Filippo Gentile | 06e72ff1e7 | |
Filippo Gentile | f4878cafb7 | |
Filippo Gentile | 3b532959a0 | |
Filippo Gentile | d4c22f3d94 | |
Filippo Gentile | 55ede00bd5 | |
Filippo Gentile | 1cc32a1913 | |
Filippo Gentile | 63e94bfdab | |
Filippo Gentile | eaf8773004 | |
Filippo Gentile | 35b62322b2 | |
Filippo Gentile | 9b88e8cc1c | |
Filippo Gentile | 472618ca80 | |
Filippo Gentile | 5881f9135e | |
Filippo Gentile | 91fd9abf1f | |
Filippo Gentile | a6cae27f8b | |
Filippo Gentile | 36df87b012 | |
Filippo Gentile | e83d1d73f0 | |
Filippo Gentile | 5979af91df | |
Filippo Gentile | 5eaabb9174 | |
Filippo Gentile | de2f8dd414 | |
Filippo Gentile | 8d05b55732 | |
Filippo Gentile | 01fe4d938a | |
Filippo Gentile | b142d3f537 | |
Filippo Gentile | 0226e08b0f | |
Filippo Gentile | 3d9d1e9b4b | |
Filippo Gentile | 159d046773 | |
Filippo Gentile | 9c595279b2 | |
Filippo Gentile | 1cd0b19536 | |
Filippo Gentile | 9e541c7986 | |
Filippo Gentile | 892bc9a6cf | |
Filippo Gentile | 73e5e6e179 | |
Filippo Gentile | 5e56dc4b39 | |
Filippo Gentile | 71f1e32c92 | |
Filippo Gentile | 40aff7d14a | |
Filippo Gentile | 4d88c236e0 | |
Filippo Gentile | f46fe790d3 | |
Filippo Gentile | 9e56c4f55e | |
Filippo Gentile | 06e929d1eb | |
Filippo Gentile | 9562a667eb | |
Filippo Gentile | f0ed0bcaf6 | |
Filippo Gentile | ff475f3eb8 | |
Filippo Gentile | 3c27da07bb | |
Filippo Gentile | 67ee46b9fb | |
Filippo Gentile | 0f76aea14e | |
Filippo Gentile | 4c2da6c816 | |
Filippo Gentile | ec52f3f833 | |
Filippo Gentile | 9b566b2e8a | |
Filippo Gentile | f5e2ce1ff4 | |
Filippo Gentile | 10734e0a4c | |
Filippo Gentile | db73ff693d | |
Filippo Gentile | 824cb2100b | |
Filippo Gentile | e45b6d0eab | |
Filippo Gentile | a361918694 | |
Filippo Gentile | 7ae34b3160 | |
Filippo Gentile | 2b263968c5 | |
Filippo Gentile | 63e5a33240 | |
Filippo Gentile | 51a6325797 | |
Filippo Gentile | 1648900cde | |
Filippo Gentile | 9e7f7f2a91 | |
Filippo Gentile | 42f780a017 | |
Filippo Gentile | 1aee704e6c | |
Filippo Gentile | e901df125b | |
Filippo Gentile | 4a2dcac484 | |
gfgit | 4dded56bf1 | |
Filippo Gentile | 08e15fed81 | |
Filippo Gentile | c7d222fcf7 | |
Filippo Gentile | 6d9d3ebe80 | |
Filippo Gentile | 2f7ccfc97c | |
Filippo Gentile | 02dcf9edf9 | |
Filippo Gentile | 55cbb8aaa1 | |
Filippo Gentile | 5a400cf736 | |
Filippo Gentile | 00245da29b | |
Filippo Gentile | 7308c49f2d | |
Filippo Gentile | 18c58ab3f1 | |
Filippo Gentile | e3bce19a14 | |
Filippo Gentile | 6c6b5e97b4 | |
Filippo Gentile | d5c39a1492 | |
Filippo Gentile | 59db245b7c | |
Filippo Gentile | d3754d0122 | |
Filippo Gentile | 5163dc7d66 | |
Filippo Gentile | 8e2a5bc979 | |
Filippo Gentile | 52c289000d | |
Filippo Gentile | 74088c8602 | |
Filippo Gentile | 432053e59b | |
Filippo Gentile | 788e2e05dc | |
Filippo Gentile | d9ea922cf5 | |
Filippo Gentile | aef7cfd1ee | |
Filippo Gentile | 09aaadaff0 | |
gfgit | eaa0f0d372 | |
Filippo Gentile | 8a25c3f390 | |
Filippo Gentile | c7a472e309 | |
Filippo Gentile | d177ab3f6a | |
Filippo Gentile | d8da91af97 | |
Filippo Gentile | 28e8c89216 | |
Filippo Gentile | c2cc58f439 | |
Filippo Gentile | 36bd2d6a68 | |
Filippo Gentile | e99ede2470 | |
Filippo Gentile | f3b8ea166b | |
Filippo Gentile | 20c931276f | |
Filippo Gentile | c2f2671277 | |
Filippo Gentile | c20bab48ef | |
Filippo Gentile | fbb3c241cb | |
Filippo Gentile | 964a8df501 | |
Filippo Gentile | d8b1e1ab3d | |
Filippo Gentile | 3fe400d126 | |
Filippo Gentile | 9f5cd87a98 | |
Filippo Gentile | 15c5da8802 | |
Filippo Gentile | a884a4340a | |
Filippo Gentile | f22d3e656f | |
Filippo Gentile | cc23a26f96 | |
Filippo Gentile | 1fd61d179c | |
Filippo Gentile | b4fbc5c661 | |
Filippo Gentile | a186023ad0 | |
Filippo Gentile | 657407cda1 | |
Filippo Gentile | 12587f733e | |
Filippo Gentile | 098f1838a8 | |
Filippo Gentile | 063d4f02bc | |
Filippo Gentile | 79eb82f758 | |
Filippo Gentile | 7352e0ca05 | |
Filippo Gentile | 2cd423aff1 | |
Filippo Gentile | 7e18451f2f | |
Filippo Gentile | a30b5642b5 | |
Filippo Gentile | d43cafbc3a | |
Filippo Gentile | d0e1e65553 | |
gfgit | 9fb9ff6e1a | |
Filippo Gentile | 0a5cd5cd96 | |
Filippo Gentile | 5fae5bc678 | |
Filippo Gentile | 96f9a959d0 | |
Filippo Gentile | a72c161cca | |
Filippo Gentile | 6488fcdcd8 | |
Filippo Gentile | 635042b360 | |
Filippo Gentile | cce4b327be | |
Filippo Gentile | 97519d311a | |
Filippo Gentile | 4b30b2af89 | |
Filippo Gentile | 7242fbe0ee | |
Filippo Gentile | ef9ce4c950 | |
Filippo Gentile | 8ff1d35ac5 | |
Filippo Gentile | afdcbc4ad4 | |
Filippo Gentile | bd248ca2b9 | |
Filippo Gentile | 27d74dce1e | |
Filippo Gentile | 0d9103c67b | |
Filippo Gentile | 1a0ee27a76 | |
Filippo Gentile | 9879623f03 | |
Filippo Gentile | 368e0d3f7f | |
Filippo Gentile | 1760d23ccc | |
Filippo Gentile | a202702f31 | |
Filippo Gentile | ba2c55ab70 | |
Filippo Gentile | a6e0e46c81 | |
Filippo Gentile | b5bcbafec8 | |
Filippo Gentile | 4b91b76e50 | |
Filippo Gentile | 1fb67335a7 | |
Filippo Gentile | fd428e0361 | |
Filippo Gentile | 39b773a118 | |
Filippo Gentile | 191766ae62 | |
Filippo Gentile | 2c123543f9 | |
Filippo Gentile | c8a580d14b | |
Filippo Gentile | f7a38e8bce | |
Filippo Gentile | 5208e05c59 | |
Filippo Gentile | 8d84dd6046 | |
Filippo Gentile | 7cb17b7601 | |
Filippo Gentile | 2b191d152c | |
Filippo Gentile | 984f706c0f | |
Filippo Gentile | b84c2c80ae |
|
@ -0,0 +1,55 @@
|
|||
---
|
||||
BasedOnStyle: Microsoft
|
||||
AlignEscapedNewlines: Left
|
||||
IndentWidth: 4
|
||||
|
||||
ColumnLimit: 100
|
||||
|
||||
# We want a space between the type and the star for pointer types.
|
||||
PointerBindsToType: false
|
||||
|
||||
# We want to break before the operators, but not before a '='.
|
||||
BreakBeforeBinaryOperators: NonAssignment
|
||||
|
||||
# Braces are usually attached, but not after functions or class declarations.
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterCaseLabel: true
|
||||
AfterControlStatement: true
|
||||
AfterEnum: true
|
||||
AfterFunction: true
|
||||
BeforeLambdaBody: true
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: true
|
||||
AfterUnion: false
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: true
|
||||
|
||||
# Indent width for line continuations.
|
||||
ContinuationIndentWidth: 2
|
||||
|
||||
# Allow indentation for preprocessing directives (if/ifdef/endif). https://reviews.llvm.org/rL312125
|
||||
IndentPPDirectives: AfterHash
|
||||
|
||||
# Do not indent public/private/protected
|
||||
IndentAccessModifiers: false
|
||||
|
||||
# This is needed because IndentAccessModifiers doesn't seem to work
|
||||
AccessModifierOffset: -4
|
||||
|
||||
BreakConstructorInitializers: AfterColon
|
||||
PackConstructorInitializers: Never
|
||||
|
||||
# Horizontally align arguments after an open bracket.
|
||||
AlignAfterOpenBracket: true
|
||||
|
||||
SortIncludes: false
|
||||
|
||||
InsertNewlineAtEOF: true
|
||||
|
||||
AlignConsecutiveMacros: AcrossEmptyLines
|
||||
AlignConsecutiveAssignments: AcrossEmptyLines
|
|
@ -0,0 +1,5 @@
|
|||
# Use this file when blaming
|
||||
# git-blame --ignore-revs-file .gitignore-blame
|
||||
|
||||
# Big clang-format refactor
|
||||
9f14ee1c64b3b9d399078918fc20ccce7bc4a7d9
|
10
BUILDING.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
ModelRailroadTimetablePlanner uses CMake build system.
|
||||
|
||||
CMake Minimun Version: **3.5**
|
||||
CMake Minimum Version: **3.5**
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
@ -31,7 +31,7 @@ CMake Minimun Version: **3.5**
|
|||
|
||||
- Install Qt 5:
|
||||
>`sudo apt install qtbase5-dev libqt5svg5-dev qttools5-dev qttools5-dev-tools`
|
||||
|
||||
|
||||
- Install SQLite 3
|
||||
>`sudo apt install libsqlite3-dev`
|
||||
|
||||
|
@ -40,7 +40,7 @@ CMake Minimun Version: **3.5**
|
|||
|
||||
- Install zlib
|
||||
> NOTE: automatically installed if installing libzip
|
||||
|
||||
|
||||
>`sudo apt install zlib1g-dev`
|
||||
|
||||
|
||||
|
@ -75,7 +75,7 @@ import libraries `*.dll.a`.
|
|||
To manually create an import library from a `*.dll` and associated `*.def` file, go to library directory and run:
|
||||
> `dlltool --dllname sqlite3.dll --def sqlite3.def --output-lib sqlite3.dll.a`
|
||||
|
||||
For more informations see [DLL Import Library Tool](https://www.willus.com/mingw/colinp/win32/tools/dlltool.html)
|
||||
For more information see [DLL Import Library Tool](https://www.willus.com/mingw/colinp/win32/tools/dlltool.html)
|
||||
|
||||
## Compile
|
||||
|
||||
|
@ -105,7 +105,7 @@ For more informations see [DLL Import Library Tool](https://www.willus.com/mingw
|
|||
|
||||
- Run
|
||||
> `mrtplanner`
|
||||
|
||||
|
||||
> NOTE: the location depends on where you installed the program
|
||||
> Look at `CMAKE_INSTALL_PREFIX` variable
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
cmake_minimum_required(VERSION 3.17)
|
||||
include(CMakeDependentOption)
|
||||
|
||||
project(ModelRailroadTimetablePlanner VERSION 6.0.0 LANGUAGES CXX)
|
||||
project(ModelRailroadTimetablePlanner VERSION 6.2.1 LANGUAGES CXX)
|
||||
|
||||
option(UPDATE_TS "Update translations" OFF)
|
||||
option(UPDATE_TS_KEEP_OBSOLETE "Keep obsolete entries when updating translations" ON)
|
||||
|
@ -24,7 +24,19 @@ set(PROJECT_HOMEPAGE_URL "https://github.com/gfgit/ModelRailroadTimetablePlanner
|
|||
set(APP_HELP_URL ${PROJECT_HOMEPAGE_URL})
|
||||
set(APP_UPDATE_URL ${PROJECT_HOMEPAGE_URL})
|
||||
|
||||
set(PROJECT_DESCRIPTION "${APP_DISPLAY_NAME} lets you create and manage model railway sessions")
|
||||
string(CONCAT PROJECT_DESCRIPTION "${APP_DISPLAY_NAME} is a cross-platform C++\n"
|
||||
"application with Qt GUI for model railway timetable scheduling.\n\n"
|
||||
"It lets you create and manage model railway sessions\n"
|
||||
"and provides all documents useful for driving and dispatching\n"
|
||||
"trains on big layouts like in FREMO Meetings.\n\n"
|
||||
"Main features:\n"
|
||||
" * Railway timetable graph per each line in SVG, PDF or printed\n"
|
||||
" * Group jobs (trains) in work shift\n"
|
||||
" * Export booklets in ODT (LibreOffice Writer) for shifts and stations\n"
|
||||
" * Importation of rollingstock pieces from\n"
|
||||
" other sessions or ODS Spreadsheet")
|
||||
|
||||
set(PROJECT_DESCRIPTION_SHORT "Timetables for model railroads")
|
||||
|
||||
set(APP_ICON ${CMAKE_SOURCE_DIR}/files/icons/icon.ico)
|
||||
|
||||
|
@ -39,7 +51,6 @@ option(CONFIG_GLOBAL_TRY_CATCH "Global try/catch at main()" OFF)
|
|||
option(CONFIG_NO_DEBUG_CALL_TRACE "Disable scope call trace messages" OFF)
|
||||
option(CONFIG_PRINT_DBG_MSG "Debug messages (some)" ON)
|
||||
option(CONFIG_ENABLE_BACKGROUND_MANAGER "Enable background task manager" ON)
|
||||
cmake_dependent_option(CONFIG_ENABLE_RS_CHECKER "Enable rollingstock checker" ON "CONFIG_ENABLE_BACKGROUND_MANAGER" OFF)
|
||||
cmake_dependent_option(CONFIG_SEARCHBOX_MODE_ASYNC "Use thread to search for jobs" ON "CONFIG_ENABLE_BACKGROUND_MANAGER" OFF)
|
||||
option(CONFIG_ENABLE_AUTO_TIME_RECALC "Automatic recalculation of travel times based on rollingstock speed, experimental" OFF)
|
||||
option(CONFIG_ENABLE_USER_QUERY "Enable SQL console" OFF)
|
||||
|
@ -60,10 +71,6 @@ if(CONFIG_ENABLE_BACKGROUND_MANAGER)
|
|||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DENABLE_BACKGROUND_MANAGER)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ENABLE_RS_CHECKER)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DENABLE_RS_CHECKER)
|
||||
endif()
|
||||
|
||||
if(CONFIG_SEARCHBOX_MODE_ASYNC)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DSEARCHBOX_MODE_ASYNC)
|
||||
endif()
|
||||
|
@ -115,29 +122,7 @@ if(BUILD_DOXYGEN)
|
|||
find_package(Doxygen)
|
||||
endif()
|
||||
|
||||
|
||||
#Locate windeployqt
|
||||
if(WIN32 AND RUN_WINDEPLOYQT AND NOT WINDEPLOYQT_EXE)
|
||||
set(WINDEPLOYQT_EXE_TMP NOTFOUND)
|
||||
message("Searching windeployqt executable")
|
||||
if(QT_QMAKE_EXECUTABLE)
|
||||
get_filename_component(WINDEPLOYQT_DIR ${QT_QMAKE_EXECUTABLE} DIRECTORY)
|
||||
set(WINDEPLOYQT_EXE_TMP "${WINDEPLOYQT_DIR}/windeployqt.exe")
|
||||
endif()
|
||||
if(NOT EXISTS ${WINDEPLOYQT_EXE_TMP} AND Qt5_DIR)
|
||||
get_filename_component(WINDEPLOYQT_EXE_TMP "${Qt5_DIR}/../../../bin/windeployqt.exe" REALPATH)
|
||||
endif()
|
||||
|
||||
if(EXISTS ${WINDEPLOYQT_EXE_TMP})
|
||||
message("Found ${WINDEPLOYQT_EXE_TMP}")
|
||||
else()
|
||||
message("windeployqt NOT FOUND")
|
||||
set(WINDEPLOYQT_EXE_TMP NOTFOUND)
|
||||
endif()
|
||||
set(WINDEPLOYQT_EXE ${WINDEPLOYQT_EXE_TMP} CACHE FILEPATH "windeployqt executable file path.")
|
||||
unset(WINDEPLOYQT_EXE_TMP)
|
||||
unset(WINDEPLOYQT_DIR)
|
||||
endif()
|
||||
include(LocateWinDeployQt)
|
||||
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# HOW TO CONTRIBUTE
|
||||
|
||||
I'm New to Git and Github so I don't know yet how to manage repositories and contributions.
|
||||
I'm New to Git and GitHub so I don't know yet how to manage repositories and contributions.
|
||||
|
||||
The model should be creating pull requests with topic branches and merging on master or `master` or `develop`.
|
||||
|
||||
|
@ -28,7 +28,7 @@ For more information see [Qt Documentation](https://doc.qt.io/qt-5/linguist-over
|
|||
1. Create a file named `traintimetable_*.ts` in translation folder.
|
||||
Replace placeholder with language code (i.e. `it`, `de`, `fr`, etc).
|
||||
|
||||
2. Make file known to Cmake by adding it to
|
||||
2. Make file known to CMake by adding it to
|
||||
[`src/translations/CMakeLists.txt`](src/translations/CMakeLists.txt).
|
||||
Add the file name with path in `TRAINTIMETABLE_TS_FILES` variabile.
|
||||
|
||||
|
@ -36,7 +36,7 @@ Then follow next paragraph.
|
|||
|
||||
### Update translations to match new UI elements
|
||||
|
||||
3. Run `lupdate` to fill with text to be traslated.
|
||||
3. Run `lupdate` to fill with text to be translated.
|
||||
This is done by enabling `UPDATE_TS` option in CMake and
|
||||
running `RELEASE_TRANSLATIONS` target.
|
||||
|
||||
|
|
12
README.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
[Versione in italiano](README_it.md)
|
||||
|
||||
Formely **TrainTimetable**
|
||||
Formerly **TrainTimetable**
|
||||
|
||||
A cross-platform C++ application with Qt GUI for model railway timetable scheduling.
|
||||
*Currently tested on Windows and Ubuntu.*
|
||||
|
@ -11,7 +11,7 @@ By Filippo Gentile
|
|||
|
||||
## Screenshots
|
||||
|
||||
![Screenshot of ModelRailroadTimetablePlanner](Screenshot.png "ModelRailroadTimetablePlanner Screenshot")
|
||||
![Screenshot of ModelRailroadTimetablePlanner](screenshots/Screenshot.png "ModelRailroadTimetablePlanner Screenshot")
|
||||
|
||||
## Goals
|
||||
|
||||
|
@ -27,15 +27,15 @@ for driving and dispatching trains on big layouts like in FREMO Meetings.
|
|||
- Available UI languages: English, Italian
|
||||
- Railway timetable graph per each line in SVG, PDF or printed
|
||||
- Group jobs (trains) in work shift
|
||||
- Export booklets in ODT (Libreoffice Writer) for shifts and stations
|
||||
- Export booklets in ODT (LibreOffice Writer) for shifts and stations
|
||||
- Importation of rollingstock pieces from other sessions or ODS Spreadsheet
|
||||
|
||||
## Project history
|
||||
The development started as a small hobby project back in 2016,
|
||||
in collaboration with italian FREMO organizations.
|
||||
It was rewritten from scratch due to core instsbilities and limitations.
|
||||
in collaboration with Italian FREMO organizations.
|
||||
It was rewritten from scratch due to core instabilities and limitations.
|
||||
Since then it has grown unexpectedly.
|
||||
So I'd like it to become a comunity project!
|
||||
So I'd like it to become a community project!
|
||||
|
||||
|
||||
## Project motto
|
||||
|
|
12
README_it.md
|
@ -2,7 +2,7 @@
|
|||
|
||||
[English version](README.md)
|
||||
|
||||
Precedenteme noto come **TrainTimetable**
|
||||
Precedentemente noto come **TrainTimetable**
|
||||
|
||||
Un programma multipiattaforma C++ con GUI Qt (interfaccia grafica) per creare orari di treni per plastici ferroviari.
|
||||
*Attualmente testato su Windows e Ubuntu.*
|
||||
|
@ -11,7 +11,7 @@ By Filippo Gentile
|
|||
|
||||
## Screenshots
|
||||
|
||||
![Screenshot di ModelRailroadTimetablePlanner](Screenshot_it.png "ModelRailroadTimetablePlanner Screenshot")
|
||||
![Screenshot di ModelRailroadTimetablePlanner](screenshots/Screenshot_it.png "ModelRailroadTimetablePlanner Screenshot")
|
||||
|
||||
## Obiettivi
|
||||
|
||||
|
@ -21,17 +21,17 @@ suggeriscano all'utente azioni utili al fine di
|
|||
velocizzare la creazione di tabelle orarie.
|
||||
|
||||
Il programma si prefigge di produrre tutta la documentazione
|
||||
per condurre e gestire i treni su grandi tracciati come nei FREMO Meetings.
|
||||
per condurre e gestire i treni su grandi tracciati come nei FREMO Meeting.
|
||||
|
||||
## Principali funzioni
|
||||
- Interfaccia disponibile in: Inglese, Italiano
|
||||
- Grafico orario ferroviario per ogni linea in SVG, PDF o stampato
|
||||
- Raggruppa servizi (treni) in turni lavorativi
|
||||
- Esporta libretti in ODT (Libreoffice Writer) per turni e stazioni
|
||||
- Raggruppa servizi (treni) in turni lavorativi
|
||||
- Esporta libretti in ODT (LibreOffice Writer) per turni e stazioni
|
||||
- Importazione del materiale rotabile da altre sessioni o fogli di calcolo ODS Spreadsheet
|
||||
|
||||
## Storia del progetto
|
||||
Lo sviluppo è partito come piccolo progetto hobbystico nel 2016,
|
||||
Lo sviluppo è partito come piccolo progetto hobbistico nel 2016,
|
||||
in collaborazione con organizzazioni FREMO italiane.
|
||||
È stato completamente riscritto a causa di instabilità e limitazioni nella struttura interna.
|
||||
Da allora è cresciuto inaspettatamente.
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Small helper for DLL file
|
||||
|
||||
function(get_dll_library_from_import_library LIB_VAR OUT_VAR)
|
||||
# Locate DLL file
|
||||
# Sometimes we link to import libraries '*.dll.a' or '*.lib'
|
||||
# When installing we need the real '*.dll' file
|
||||
# Try to locate it in same directory or in library path
|
||||
|
||||
get_filename_component(TEMP_EXT ${LIB_VAR} EXT)
|
||||
|
||||
if(${TEMP_EXT} MATCHES ".dll.a" OR ${TEMP_EXT} MATCHES ".lib")
|
||||
# Get filename without extension and then add '.dll'
|
||||
get_filename_component(TEMP_NAME ${LIB_VAR} NAME_WE)
|
||||
set(TEMP_NAME "${TEMP_NAME}.dll")
|
||||
|
||||
# Get file path
|
||||
get_filename_component(TEMP_PATH ${LIB_VAR} DIRECTORY)
|
||||
|
||||
# Try new file in same path
|
||||
set(TEMP_PATH "${TEMP_PATH}/${TEMP_NAME}")
|
||||
|
||||
if(NOT EXISTS ${TEMP_PATH})
|
||||
# Doesn't exist, try to find it in other directory, same name
|
||||
# Searche also in CMAKE_LIBRARY_PATH which is not used by default in find_file(...)
|
||||
find_file(TEMP_PATH_2 NAMES ${TEMP_NAME} PATHS ${CMAKE_LIBRARY_PATH})
|
||||
set(TEMP_PATH ${TEMP_PATH_2})
|
||||
|
||||
# find_file caches the variable but this causes problems
|
||||
# with subsequent calls reading old value instead of finding a new file
|
||||
unset(TEMP_PATH_2 CACHE)
|
||||
unset(TEMP_PATH_2)
|
||||
endif()
|
||||
|
||||
unset(TEMP_NAME)
|
||||
|
||||
elseif(${TEMP_EXT} MATCHES ".dll")
|
||||
# Library is already a *.dll, use it directly
|
||||
set(TEMP_PATH ${LIB_VAR})
|
||||
endif()
|
||||
|
||||
set("${OUT_VAR}" ${TEMP_PATH} PARENT_SCOPE)
|
||||
|
||||
unset(TEMP_PATH)
|
||||
unset(TEMP_EXT)
|
||||
endfunction()
|
|
@ -0,0 +1,37 @@
|
|||
# Locate windeployqt
|
||||
|
||||
if (NOT TARGET windeployqt_exe)
|
||||
add_executable(windeployqt_exe IMPORTED)
|
||||
|
||||
# Default exe name
|
||||
set(WINDEPLOYQT_EXE_NAME "windeployqt.exe")
|
||||
|
||||
if(WINDEPLOYQT_EXE_DIR)
|
||||
# If we have explicitly set directory use it
|
||||
set(WINDEPLOYQT_EXE_TMP "${WINDEPLOYQT_EXE_DIR}/${WINDEPLOYQT_EXE_NAME}")
|
||||
endif()
|
||||
if((NOT EXISTS ${WINDEPLOYQT_EXE_TMP}) AND QT_QMAKE_EXECUTABLE)
|
||||
# If we have QMake, it should be in same folder
|
||||
get_filename_component(WINDEPLOYQT_EXE_DIR ${QT_QMAKE_EXECUTABLE} DIRECTORY)
|
||||
set(WINDEPLOYQT_EXE_TMP "${WINDEPLOYQT_EXE_DIR}/${WINDEPLOYQT_EXE_NAME}")
|
||||
endif()
|
||||
if((NOT EXISTS ${WINDEPLOYQT_EXE_TMP}) AND Qt5_DIR)
|
||||
# If we have Qt5_DIR, go up and select 'bin' folder
|
||||
get_filename_component(WINDEPLOYQT_EXE_DIR "${Qt5_DIR}/../../../bin" REALPATH)
|
||||
set(WINDEPLOYQT_EXE_TMP "${WINDEPLOYQT_EXE_DIR}/${WINDEPLOYQT_EXE_NAME}")
|
||||
endif()
|
||||
|
||||
if(EXISTS ${WINDEPLOYQT_EXE_TMP})
|
||||
message("Found ${WINDEPLOYQT_EXE_TMP}")
|
||||
else()
|
||||
message("windeployqt NOT FOUND")
|
||||
set(WINDEPLOYQT_EXE_TMP NOTFOUND)
|
||||
endif()
|
||||
|
||||
set_target_properties(windeployqt_exe PROPERTIES
|
||||
IMPORTED_LOCATION ${WINDEPLOYQT_EXE_TMP}
|
||||
)
|
||||
|
||||
unset(WINDEPLOYQT_EXE_TMP)
|
||||
unset(WINDEPLOYQT_EXE_DIR)
|
||||
endif()
|
|
@ -59,6 +59,10 @@ set(CPACK_DEB_COMPONENT_INSTALL OFF)
|
|||
# list dependencies
|
||||
set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS YES)
|
||||
|
||||
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION_SHORT})
|
||||
set(CPACK_DEBIAN_PACKAGE_DESCRIPTION ${PROJECT_DESCRIPTION})
|
||||
|
||||
set(CPACK_PROJECT_CONFIG_FILE ${CMAKE_SOURCE_DIR}/packaging/CMakeCPackOptions.cmake)
|
||||
|
||||
include(CPack)
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="100mm"
|
||||
height="100mm"
|
||||
viewBox="0 0 100 100"
|
||||
version="1.1"
|
||||
id="svg5"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs2" />
|
||||
<g
|
||||
id="layer1">
|
||||
<path
|
||||
style="fill:none;stroke:#00aa00;stroke-width:18.4;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
|
||||
d="M 25,85 V 35 C 25.100727,6.7404564 75.224095,7.2276899 75,35 v 38"
|
||||
id="path876" />
|
||||
<path
|
||||
style="fill:#00aa00;fill-opacity:1;stroke:none;stroke-width:0.264583px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
d="M 53,70 H 97 L 75,96 Z"
|
||||
id="path2659" />
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 829 B |
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 37 KiB |
Before Width: | Height: | Size: 140 KiB After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 79 KiB |
|
@ -1,50 +1,7 @@
|
|||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
function(get_dll_library_from_import_library LIB_VAR OUT_VAR)
|
||||
# Locate DLL file
|
||||
# Sometimes we link to import libraries '*.dll.a' or '*.lib'
|
||||
# When installing we need the real '*.dll' file
|
||||
# Try to locate it in same directory or in library path
|
||||
|
||||
get_filename_component(TEMP_EXT ${LIB_VAR} EXT)
|
||||
|
||||
if(${TEMP_EXT} MATCHES ".dll.a" OR ${TEMP_EXT} MATCHES ".lib")
|
||||
# Get filename without extension and then add '.dll'
|
||||
get_filename_component(TEMP_NAME ${LIB_VAR} NAME_WE)
|
||||
set(TEMP_NAME "${TEMP_NAME}.dll")
|
||||
|
||||
# Get file path
|
||||
get_filename_component(TEMP_PATH ${LIB_VAR} DIRECTORY)
|
||||
|
||||
# Try new file in same path
|
||||
set(TEMP_PATH "${TEMP_PATH}/${TEMP_NAME}")
|
||||
|
||||
if(NOT EXISTS ${TEMP_PATH})
|
||||
# Doesn't exist, try to find it in other directory, same name
|
||||
# Searche also in CMAKE_LIBRARY_PATH which is not used by default in find_file(...)
|
||||
find_file(TEMP_PATH_2 NAMES ${TEMP_NAME} PATHS ${CMAKE_LIBRARY_PATH})
|
||||
set(TEMP_PATH ${TEMP_PATH_2})
|
||||
|
||||
# find_file caches the variable but this causes problems
|
||||
# with subsequent calls reading old value instead of finding a new file
|
||||
unset(TEMP_PATH_2 CACHE)
|
||||
unset(TEMP_PATH_2)
|
||||
endif()
|
||||
|
||||
unset(TEMP_NAME)
|
||||
|
||||
elseif(${TEMP_EXT} MATCHES ".dll")
|
||||
# Library is already a *.dll, use it directly
|
||||
set(TEMP_PATH ${LIB_VAR})
|
||||
endif()
|
||||
|
||||
set("${OUT_VAR}" ${TEMP_PATH} PARENT_SCOPE)
|
||||
|
||||
unset(TEMP_PATH)
|
||||
unset(TEMP_EXT)
|
||||
endfunction()
|
||||
|
||||
include(DLL_Utils)
|
||||
|
||||
#Set Win32 resources
|
||||
if (WIN32)
|
||||
|
@ -183,78 +140,6 @@ if (WIN32)
|
|||
)
|
||||
endif()
|
||||
|
||||
## Enable Crashpad if found
|
||||
#if (GoogleCrashpad_FOUND)
|
||||
# set(OLIVE_DEFINITIONS ${OLIVE_DEFINITIONS} USE_CRASHPAD)
|
||||
|
||||
# target_include_directories(
|
||||
# ${OLIVE_TARGET}
|
||||
# PRIVATE
|
||||
# ${CRASHPAD_INCLUDE_DIRS}
|
||||
# )
|
||||
|
||||
# target_link_libraries(
|
||||
# ${OLIVE_TARGET}
|
||||
# PRIVATE
|
||||
# ${CRASHPAD_LIBRARIES}
|
||||
# )
|
||||
|
||||
# set(OLIVE_CRASH_TARGET "olive-crashhandler")
|
||||
|
||||
# set(OLIVE_CRASH_SOURCES
|
||||
# dialog/crashhandler/crashhandler.h
|
||||
# dialog/crashhandler/crashhandler.cpp
|
||||
# dialog/crashhandler/crashhandlermain.cpp
|
||||
# )
|
||||
|
||||
# if (WIN32)
|
||||
# add_executable(
|
||||
# ${OLIVE_CRASH_TARGET}
|
||||
# WIN32
|
||||
# ${OLIVE_CRASH_SOURCES}
|
||||
# )
|
||||
# else()
|
||||
# add_executable(
|
||||
# ${OLIVE_CRASH_TARGET}
|
||||
# ${OLIVE_CRASH_SOURCES}
|
||||
# )
|
||||
# endif()
|
||||
|
||||
# target_include_directories(
|
||||
# ${OLIVE_CRASH_TARGET}
|
||||
# PRIVATE
|
||||
# ${CRASHPAD_INCLUDE_DIRS}
|
||||
# )
|
||||
|
||||
# target_link_libraries(
|
||||
# ${OLIVE_CRASH_TARGET}
|
||||
# PRIVATE
|
||||
# Qt5::Core
|
||||
# Qt5::Gui
|
||||
# Qt5::Widgets
|
||||
# Qt5::Network
|
||||
# ${CRASHPAD_LIBRARIES}
|
||||
# )
|
||||
|
||||
# set(CRASHPAD_HANDLER "crashpad_handler${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
# set(MINIDUMP_STACKWALK "minidump_stackwalk${CMAKE_EXECUTABLE_SUFFIX}")
|
||||
|
||||
# if(UNIX AND NOT APPLE)
|
||||
# install(TARGETS ${OLIVE_CRASH_TARGET} RUNTIME DESTINATION bin)
|
||||
# install(PROGRAMS ${CRASHPAD_LIBRARY_DIRS}/${CRASHPAD_HANDLER} DESTINATION bin)
|
||||
# install(PROGRAMS ${BREAKPAD_BIN_DIR}/${MINIDUMP_STACKWALK} DESTINATION bin)
|
||||
# endif()
|
||||
|
||||
# if(APPLE)
|
||||
# # Move crash handler executables inside Mac app bundle
|
||||
# add_custom_command(TARGET ${OLIVE_CRASH_TARGET} POST_BUILD
|
||||
# COMMAND ${CMAKE_COMMAND} -E copy_if_different ${OLIVE_CRASH_TARGET} $<TARGET_FILE_DIR:${OLIVE_TARGET}>
|
||||
# COMMAND ${CMAKE_COMMAND} -E copy_if_different ${CRASHPAD_LIBRARY_DIRS}/${CRASHPAD_HANDLER} $<TARGET_FILE_DIR:${OLIVE_TARGET}>
|
||||
# COMMAND ${CMAKE_COMMAND} -E copy_if_different ${BREAKPAD_BIN_DIR}/${MINIDUMP_STACKWALK} $<TARGET_FILE_DIR:${OLIVE_TARGET}>
|
||||
# )
|
||||
# endif()
|
||||
#endif()
|
||||
|
||||
# Set compiler definitions
|
||||
target_compile_definitions(${MR_TIMETABLE_PLANNER_TARGET} PRIVATE ${MR_TIMETABLE_PLANNER_DEFINITIONS})
|
||||
|
||||
|
@ -315,10 +200,13 @@ endforeach()
|
|||
# Copy executable
|
||||
install(TARGETS ${MR_TIMETABLE_PLANNER_TARGET})
|
||||
|
||||
# Copy SVG icon
|
||||
# Copy SVG icons
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/files/icons/lightning/lightning.svg
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}/icons)
|
||||
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/files/icons/reverse_direction/reverse_direction.svg
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}/icons)
|
||||
|
||||
# For each .ts file install corrensponding .qm file
|
||||
foreach(TS_FILE ${MR_TIMETABLE_PLANNER_TS_FILES})
|
||||
get_filename_component(QM_FILE_NAME ${TS_FILE} NAME_WLE)
|
||||
|
@ -340,21 +228,21 @@ if(WIN32)
|
|||
install(PROGRAMS ${LibZip_LIBRARY_TO_INSTALL} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
if(RUN_WINDEPLOYQT)
|
||||
if(NOT WINDEPLOYQT_EXE)
|
||||
message(FATAL_ERROR "In order to run windeployqt you must first set th exe path in WINDEPLOYQT_EXE")
|
||||
if(NOT TARGET windeployqt_exe)
|
||||
message(FATAL_ERROR "In order to run windeployqt you must first set the exe path in WINDEPLOYQT_EXE_DIR")
|
||||
endif()
|
||||
|
||||
# Use [[...]] to delay variable expansion
|
||||
install(CODE "
|
||||
message(STATUS \"Running windeployqt ${WINDEPLOYQT_EXE} ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}\")
|
||||
execute_process(COMMAND ${WINDEPLOYQT_EXE} ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}
|
||||
message(STATUS \"Running windeployqt ${windeployqt_exe} ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}\")
|
||||
execute_process(COMMAND ${windeployqt_exe} ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}
|
||||
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}
|
||||
OUTPUT_VARIABLE WINDEPLOYQT_EXE_RESULT
|
||||
ERROR_VARIABLE WINDEPLOYQT_EXE_RESULT)
|
||||
|
||||
message(STATUS \"${WINDEPLOYQT_EXE_RESULT}\")
|
||||
|
||||
message(STATUS \"windeployqt Done.\")
|
||||
message(STATUS \"${windeployqt_exe} Done.\")
|
||||
")
|
||||
endif()
|
||||
endif()
|
||||
|
|
171
src/app/main.cpp
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include <QApplication>
|
||||
#include "app/session.h"
|
||||
|
@ -27,23 +46,29 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
|
|||
QMutexLocker lock(&logMutex);
|
||||
|
||||
QString str;
|
||||
//const QString fmt = QStringLiteral("%1: %2 (%3:%4, %5)\n");
|
||||
// const QString fmt = QStringLiteral("%1: %2 (%3:%4, %5)\n");
|
||||
static const QString fmt = QStringLiteral("%1: %2\n");
|
||||
switch (type) {
|
||||
switch (type)
|
||||
{
|
||||
case QtDebugMsg:
|
||||
str = fmt.arg(QStringLiteral("Debug")).arg(msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
str = fmt.arg(QStringLiteral("Debug"),
|
||||
msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
str = fmt.arg(QStringLiteral("Info")).arg(msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
str = fmt.arg(QStringLiteral("Info"),
|
||||
msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
str = fmt.arg(QStringLiteral("Warning")).arg(msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
str = fmt.arg(QStringLiteral("Warning"),
|
||||
msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
str = fmt.arg(QStringLiteral("Critical")).arg(msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
str = fmt.arg(QStringLiteral("Critical"),
|
||||
msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
str = fmt.arg(QStringLiteral("Fatal")).arg(msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
str = fmt.arg(QStringLiteral("Fatal"),
|
||||
msg); //.arg(context.file).arg(context.line).arg(context.function);
|
||||
}
|
||||
|
||||
QTextStream s(gLogFile());
|
||||
|
@ -54,114 +79,124 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
|
|||
|
||||
void setupLogger()
|
||||
{
|
||||
//const QString path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
// const QString path = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
QString path = MeetingSession::appDataPath;
|
||||
if(qApp->arguments().contains("--test"))
|
||||
path = qApp->applicationDirPath(); //If testing use exe folder instead of AppData: see MeetingSession
|
||||
if (qApp->arguments().contains("--test"))
|
||||
path = qApp->applicationDirPath(); // If testing use exe folder instead of AppData:
|
||||
// see MeetingSession
|
||||
|
||||
QFile *logFile = gLogFile();
|
||||
|
||||
logFile->setFileName(path + QStringLiteral("/logs/mrtp_log.log"));
|
||||
logFile->open(QFile::WriteOnly | QFile::Append | QFile::Text);
|
||||
if(!logFile->isOpen()) //FIXME: if logFile gets too big, ask user to truncate it
|
||||
if (!logFile->isOpen()) // FIXME: if logFile gets too big, ask user to truncate it
|
||||
{
|
||||
QDir dir(path);
|
||||
dir.mkdir("logs");
|
||||
logFile->open(QFile::WriteOnly | QFile::Append | QFile::Text);
|
||||
}
|
||||
if(logFile->isOpen())
|
||||
if (logFile->isOpen())
|
||||
{
|
||||
defaultHandler = qInstallMessageHandler(myMessageOutput);
|
||||
}
|
||||
else {
|
||||
qDebug() << "Cannot open Log file:" << logFile->fileName() << "Error:" << logFile->errorString();
|
||||
else
|
||||
{
|
||||
qDebug() << "Cannot open Log file:" << logFile->fileName()
|
||||
<< "Error:" << logFile->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
#ifdef GLOBAL_TRY_CATCH
|
||||
try{
|
||||
try
|
||||
{
|
||||
#endif
|
||||
|
||||
QApplication app(argc, argv);
|
||||
QApplication::setOrganizationName(AppCompany);
|
||||
//QApplication::setApplicationName(AppProduct);
|
||||
QApplication::setApplicationDisplayName(AppDisplayName);
|
||||
QApplication::setApplicationVersion(AppVersion);
|
||||
QApplication app(argc, argv);
|
||||
QApplication::setOrganizationName(AppCompany);
|
||||
// QApplication::setApplicationName(AppProduct);
|
||||
QApplication::setApplicationDisplayName(AppDisplayName);
|
||||
QApplication::setApplicationVersion(AppVersion);
|
||||
|
||||
MeetingSession::locateAppdata();
|
||||
MeetingSession::locateAppdata();
|
||||
|
||||
setupLogger();
|
||||
setupLogger();
|
||||
|
||||
qDebug() << QApplication::applicationDisplayName()
|
||||
<< "Version:" << QApplication::applicationVersion()
|
||||
<< "Built:" << AppBuildDate
|
||||
<< "Website: " << AppProjectWebSite;
|
||||
qDebug() << "Qt:" << QT_VERSION_STR;
|
||||
qDebug() << "Sqlite:" << sqlite3_libversion() << " DB Format: V" << FormatVersion;
|
||||
qDebug() << QDateTime::currentDateTime().toString("dd/MM/yyyy HH:mm");
|
||||
qDebug() << QApplication::applicationDisplayName()
|
||||
<< "Version:" << QApplication::applicationVersion() << "Built:" << AppBuildDate
|
||||
<< "Website: " << AppProjectWebSite;
|
||||
qDebug() << "Qt:" << QT_VERSION_STR;
|
||||
qDebug() << "Sqlite:" << sqlite3_libversion() << " DB Format: V" << FormatVersion;
|
||||
qDebug() << QDateTime::currentDateTime().toString("dd/MM/yyyy HH:mm");
|
||||
|
||||
//Check SQLite thread safety
|
||||
int val = sqlite3_threadsafe();
|
||||
if(val != 1)
|
||||
{
|
||||
//Not thread safe
|
||||
qWarning() << "SQLite Library was not compiled with SQLITE_THREADSAFE=1. This may cause crashes of this application.";
|
||||
}
|
||||
|
||||
MeetingSession meetingSession;
|
||||
utils::language::loadTranslationsFromSettings();
|
||||
|
||||
MainWindow w;
|
||||
w.showNormal();
|
||||
w.resize(800, 600);
|
||||
w.showMaximized();
|
||||
|
||||
if(argc > 1) //FIXME: better handling if there are extra arguments
|
||||
{
|
||||
QString fileName = app.arguments().at(1);
|
||||
qDebug() << "Trying to load:" << fileName;
|
||||
if(QFile(fileName).exists())
|
||||
// Check SQLite thread safety
|
||||
int val = sqlite3_threadsafe();
|
||||
if (val != 1)
|
||||
{
|
||||
w.loadFile(app.arguments().at(1));
|
||||
// Not thread safe
|
||||
qWarning() << "SQLite Library was not compiled with SQLITE_THREADSAFE=1. This may "
|
||||
"cause crashes of this application.";
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Running...";
|
||||
MeetingSession meetingSession;
|
||||
utils::language::loadTranslationsFromSettings();
|
||||
|
||||
int ret = app.exec();
|
||||
QThreadPool::globalInstance()->waitForDone(1000);
|
||||
DB_Error err = Session->closeDB();
|
||||
MainWindow w;
|
||||
w.showNormal();
|
||||
w.resize(800, 600);
|
||||
w.showMaximized();
|
||||
|
||||
if(err == DB_Error::DbBusyWhenClosing || QThreadPool::globalInstance()->activeThreadCount() > 0)
|
||||
{
|
||||
qWarning() << "Error: Application closing while threadpool still running or database busy!";
|
||||
QThreadPool::globalInstance()->waitForDone(10000);
|
||||
Session->closeDB();
|
||||
}
|
||||
if (argc > 1) // FIXME: better handling if there are extra arguments
|
||||
{
|
||||
QString fileName = app.arguments().at(1);
|
||||
qDebug() << "Trying to load:" << fileName;
|
||||
if (QFile(fileName).exists())
|
||||
{
|
||||
w.loadFile(app.arguments().at(1));
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
qDebug() << "Running...";
|
||||
|
||||
int ret = app.exec();
|
||||
QThreadPool::globalInstance()->waitForDone(1000);
|
||||
DB_Error err = Session->closeDB();
|
||||
|
||||
if (err == DB_Error::DbBusyWhenClosing
|
||||
|| QThreadPool::globalInstance()->activeThreadCount() > 0)
|
||||
{
|
||||
qWarning()
|
||||
<< "Error: Application closing while threadpool still running or database busy!";
|
||||
QThreadPool::globalInstance()->waitForDone(10000);
|
||||
Session->closeDB();
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
#ifdef GLOBAL_TRY_CATCH
|
||||
}catch(const char* str)
|
||||
}
|
||||
catch (const char *str)
|
||||
{
|
||||
qDebug() << "Exception:" << str;
|
||||
throw;
|
||||
}catch(const std::string& str)
|
||||
}
|
||||
catch (const std::string &str)
|
||||
{
|
||||
qDebug() << "Exception:" << str.c_str();
|
||||
throw;
|
||||
}catch(sqlite3pp::database_error& e)
|
||||
}
|
||||
catch (sqlite3pp::database_error &e)
|
||||
{
|
||||
qDebug() << "Exception:" << e.what();
|
||||
throw;
|
||||
}catch(std::exception& e)
|
||||
}
|
||||
catch (std::exception &e)
|
||||
{
|
||||
qDebug() << "Exception:" << e.what();
|
||||
throw;
|
||||
}catch(...)
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
qDebug() << "Caught generic exception";
|
||||
throw;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "mainwindow.h"
|
||||
#include "ui_mainwindow.h"
|
||||
|
||||
|
@ -5,7 +24,6 @@
|
|||
|
||||
#include "viewmanager/viewmanager.h"
|
||||
|
||||
|
||||
#include "jobs/jobeditor/jobpatheditor.h"
|
||||
#include <QDockWidget>
|
||||
|
||||
|
@ -30,10 +48,10 @@
|
|||
|
||||
#include "db_metadata/meetinginformationdialog.h"
|
||||
|
||||
#include "printing/printwizard.h"
|
||||
#include "printing/wizard/printwizard.h"
|
||||
|
||||
#ifdef ENABLE_USER_QUERY
|
||||
#include "sqlconsole/sqlconsole.h"
|
||||
# include "sqlconsole/sqlconsole.h"
|
||||
#endif
|
||||
|
||||
#include <QActionGroup>
|
||||
|
@ -42,12 +60,11 @@
|
|||
#include "searchbox/searchresultmodel.h"
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
#include "backgroundmanager/backgroundmanager.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
#include "rollingstock/rs_checker/rserrorswidget.h"
|
||||
#endif
|
||||
# include "backgroundmanager/backgroundmanager.h"
|
||||
# include "backgroundmanager/backgroundresultpanel.h"
|
||||
# include "jobs/jobs_checker/crossing/jobcrossingchecker.h"
|
||||
# include "rollingstock/rs_checker/rscheckermanager.h"
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#include "propertiesdialog.h"
|
||||
#include "info.h"
|
||||
|
@ -68,10 +85,9 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
QMainWindow(parent),
|
||||
ui(new Ui::MainWindow),
|
||||
jobEditor(nullptr),
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
rsErrorsWidget(nullptr),
|
||||
rsErrDock(nullptr),
|
||||
#endif
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
resPanelDock(nullptr),
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
view(nullptr),
|
||||
jobDock(nullptr),
|
||||
searchEdit(nullptr),
|
||||
|
@ -83,63 +99,72 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
ui->setupUi(this);
|
||||
ui->actionAbout->setText(tr("About %1").arg(qApp->applicationDisplayName()));
|
||||
|
||||
auto viewMgr = Session->getViewManager();
|
||||
auto viewMgr = Session->getViewManager();
|
||||
viewMgr->m_mainWidget = this;
|
||||
|
||||
auto graphMgr = viewMgr->getLineGraphMgr();
|
||||
auto graphMgr = viewMgr->getLineGraphMgr();
|
||||
connect(graphMgr, &LineGraphManager::jobSelected, this, &MainWindow::onJobSelected);
|
||||
|
||||
//view = graphMgr->getView();
|
||||
// view = graphMgr->getView();
|
||||
view = new LineGraphWidget(this);
|
||||
|
||||
//Welcome label
|
||||
// Welcome label
|
||||
welcomeLabel = new QLabel(this);
|
||||
welcomeLabel->setTextFormat(Qt::RichText);
|
||||
welcomeLabel->setAlignment(Qt::AlignCenter);
|
||||
welcomeLabel->setFont(QFont("Arial", 15));
|
||||
welcomeLabel->setObjectName("WelcomeLabel");
|
||||
|
||||
//JobPathEditor dock
|
||||
jobEditor = new JobPathEditor(this);
|
||||
// JobPathEditor dock
|
||||
jobEditor = new JobPathEditor(this);
|
||||
viewMgr->jobEditor = jobEditor;
|
||||
jobDock = new QDockWidget(tr("Job Editor"), this);
|
||||
jobDock = new QDockWidget(tr("Job Editor"), this);
|
||||
jobDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea);
|
||||
jobDock->setWidget(jobEditor);
|
||||
jobDock->installEventFilter(this); //NOTE: see MainWindow::eventFilter() below
|
||||
jobDock->installEventFilter(this); // NOTE: see MainWindow::eventFilter() below
|
||||
|
||||
addDockWidget(Qt::RightDockWidgetArea, jobDock);
|
||||
ui->menuView->addAction(jobDock->toggleViewAction());
|
||||
connect(jobDock->toggleViewAction(), &QAction::triggered, jobEditor, &JobPathEditor::show);
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
//RS Errors dock
|
||||
rsErrorsWidget = new RsErrorsWidget(this);
|
||||
rsErrDock = new QDockWidget(rsErrorsWidget->windowTitle(), this);
|
||||
rsErrDock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
|
||||
rsErrDock->setWidget(rsErrorsWidget);
|
||||
rsErrDock->installEventFilter(this); //NOTE: see eventFilter() below
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
// Background Errors dock
|
||||
BackgroundResultPanel *resPanel = new BackgroundResultPanel(this);
|
||||
resPanelDock = new QDockWidget(tr("Errors"), this);
|
||||
resPanelDock->setAllowedAreas(Qt::TopDockWidgetArea | Qt::BottomDockWidgetArea);
|
||||
resPanelDock->setWidget(resPanel);
|
||||
resPanelDock->installEventFilter(this); // NOTE: see eventFilter() below
|
||||
|
||||
addDockWidget(Qt::BottomDockWidgetArea, rsErrDock);
|
||||
ui->menuView->addAction(rsErrDock->toggleViewAction());
|
||||
ui->mainToolBar->addAction(rsErrDock->toggleViewAction());
|
||||
#endif
|
||||
addDockWidget(Qt::BottomDockWidgetArea, resPanelDock);
|
||||
ui->menuView->addAction(resPanelDock->toggleViewAction());
|
||||
ui->mainToolBar->addAction(resPanelDock->toggleViewAction());
|
||||
|
||||
//Allow JobPathEditor to use all vertical space when RsErrorWidget dock is at bottom
|
||||
// Add checkers FIXME: move to session?
|
||||
JobCrossingChecker *jobCrossingChecker = new JobCrossingChecker(Session->m_Db, this);
|
||||
Session->getBackgroundManager()->addChecker(jobCrossingChecker);
|
||||
|
||||
RsCheckerManager *rsChecker = new RsCheckerManager(Session->m_Db, this);
|
||||
Session->getBackgroundManager()->addChecker(rsChecker);
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
// Allow JobPathEditor to use all vertical space when RsErrorWidget dock is at bottom
|
||||
setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
|
||||
setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
|
||||
|
||||
//Search Box
|
||||
// Search Box
|
||||
SearchResultModel *searchModel = new SearchResultModel(Session->m_Db, this);
|
||||
searchEdit = new CustomCompletionLineEdit(searchModel, this);
|
||||
searchEdit = new CustomCompletionLineEdit(searchModel, this);
|
||||
searchEdit->setMinimumWidth(300);
|
||||
searchEdit->setMinimumHeight(25);
|
||||
searchEdit->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
|
||||
searchEdit->setPlaceholderText(tr("Find"));
|
||||
searchEdit->setClearButtonEnabled(true);
|
||||
connect(searchEdit, &CustomCompletionLineEdit::completionDone, this, &MainWindow::onJobSearchItemSelected);
|
||||
connect(searchModel, &SearchResultModel::resultsReady, this, &MainWindow::onJobSearchResultsReady);
|
||||
connect(searchEdit, &CustomCompletionLineEdit::completionDone, this,
|
||||
&MainWindow::onJobSearchItemSelected);
|
||||
connect(searchModel, &SearchResultModel::resultsReady, this,
|
||||
&MainWindow::onJobSearchResultsReady);
|
||||
|
||||
QWidget* spacer = new QWidget();
|
||||
QWidget *spacer = new QWidget();
|
||||
spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
ui->mainToolBar->addWidget(spacer);
|
||||
ui->mainToolBar->addWidget(searchEdit);
|
||||
|
@ -148,12 +173,11 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
setCentralWidgetMode(CentralWidgetMode::StartPageMode);
|
||||
|
||||
QMenu *recentFilesMenu = new QMenu(this);
|
||||
for(int i = 0; i < MaxRecentFiles; i++)
|
||||
for (int i = 0; i < MaxRecentFiles; i++)
|
||||
{
|
||||
recentFileActs[i] = new QAction(this);
|
||||
recentFileActs[i]->setVisible(false);
|
||||
connect(recentFileActs[i], &QAction::triggered,
|
||||
this, &MainWindow::onOpenRecent);
|
||||
connect(recentFileActs[i], &QAction::triggered, this, &MainWindow::onOpenRecent);
|
||||
|
||||
recentFilesMenu->addAction(recentFileActs[i]);
|
||||
}
|
||||
|
@ -162,7 +186,7 @@ MainWindow::MainWindow(QWidget *parent) :
|
|||
|
||||
ui->actionOpen_Recent->setMenu(recentFilesMenu);
|
||||
|
||||
//Listen to changes to display welcomeLabel or view
|
||||
// Listen to changes to display welcomeLabel or view
|
||||
connect(Session, &MeetingSession::segmentAdded, this, &MainWindow::checkLineNumber);
|
||||
connect(Session, &MeetingSession::segmentRemoved, this, &MainWindow::checkLineNumber);
|
||||
connect(Session, &MeetingSession::lineAdded, this, &MainWindow::checkLineNumber);
|
||||
|
@ -216,11 +240,13 @@ void MainWindow::setup_actions()
|
|||
connect(ui->actionProperties, &QAction::triggered, this, &MainWindow::onProperties);
|
||||
|
||||
connect(ui->actionStations, &QAction::triggered, this, &MainWindow::onStationManager);
|
||||
connect(ui->actionRollingstockManager, &QAction::triggered, this, &MainWindow::onRollingStockManager);
|
||||
connect(ui->actionRollingstockManager, &QAction::triggered, this,
|
||||
&MainWindow::onRollingStockManager);
|
||||
connect(ui->actionJob_Shifts, &QAction::triggered, this, &MainWindow::onShiftManager);
|
||||
connect(ui->action_JobsMgr, &QAction::triggered, this, &MainWindow::onJobsManager);
|
||||
connect(ui->actionRS_Session_Viewer, &QAction::triggered, this, &MainWindow::onSessionRSViewer);
|
||||
connect(ui->actionMeeting_Information, &QAction::triggered, this, &MainWindow::onMeetingInformation);
|
||||
connect(ui->actionMeeting_Information, &QAction::triggered, this,
|
||||
&MainWindow::onMeetingInformation);
|
||||
|
||||
connect(ui->actionAddJob, &QAction::triggered, this, &MainWindow::onAddJob);
|
||||
connect(ui->actionRemoveJob, &QAction::triggered, this, &MainWindow::onRemoveJob);
|
||||
|
@ -239,16 +265,22 @@ void MainWindow::setup_actions()
|
|||
|
||||
connect(ui->actionExit, &QAction::triggered, this, &MainWindow::close);
|
||||
|
||||
ui->actionNext_Job_Segment->setToolTip(tr("Hold shift and click to go to <b>last</b> job stop."));
|
||||
ui->actionPrev_Job_Segment->setToolTip(tr("Hold shift and click to go to <b>first</b> job stop."));
|
||||
connect(ui->actionNext_Job_Segment, &QAction::triggered, this, []()
|
||||
ui->actionNext_Job_Segment->setToolTip(
|
||||
tr("Hold shift and click to go to <b>last</b> job stop."));
|
||||
ui->actionPrev_Job_Segment->setToolTip(
|
||||
tr("Hold shift and click to go to <b>first</b> job stop."));
|
||||
connect(ui->actionNext_Job_Segment, &QAction::triggered, this,
|
||||
[]()
|
||||
{
|
||||
bool shiftPressed = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
|
||||
bool shiftPressed =
|
||||
QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
|
||||
Session->getViewManager()->requestJobShowPrevNextSegment(false, shiftPressed);
|
||||
});
|
||||
connect(ui->actionPrev_Job_Segment, &QAction::triggered, this, []()
|
||||
connect(ui->actionPrev_Job_Segment, &QAction::triggered, this,
|
||||
[]()
|
||||
{
|
||||
bool shiftPressed = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
|
||||
bool shiftPressed =
|
||||
QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
|
||||
Session->getViewManager()->requestJobShowPrevNextSegment(true, shiftPressed);
|
||||
});
|
||||
}
|
||||
|
@ -260,16 +292,14 @@ void MainWindow::about()
|
|||
msgBox->setWindowTitle(tr("About %1").arg(qApp->applicationDisplayName()));
|
||||
|
||||
const QString translatedText =
|
||||
tr(
|
||||
"<h3>%1</h3>"
|
||||
"<p>This program makes it easier to deal with timetables and trains.</p>"
|
||||
"<p>Version: <b>%2</b></p>"
|
||||
"<p>Built: %3</p>"
|
||||
"<p>Website: <a href='%4'>%4</a></p>")
|
||||
.arg(qApp->applicationDisplayName())
|
||||
.arg(qApp->applicationVersion())
|
||||
.arg(QDate::fromString(AppBuildDate, QLatin1String("MMM dd yyyy")).toString("dd/MM/yyyy"))
|
||||
.arg(AppProjectWebSite);
|
||||
tr("<h3>%1</h3>"
|
||||
"<p>This program makes it easier to deal with timetables and trains.</p>"
|
||||
"<p>Version: <b>%2</b></p>"
|
||||
"<p>Built: %3</p>"
|
||||
"<p>Website: <a href='%4'>%4</a></p>")
|
||||
.arg(qApp->applicationDisplayName(), qApp->applicationVersion(),
|
||||
QDate::fromString(AppBuildDate, QLatin1String("MMM dd yyyy")).toString("dd/MM/yyyy"),
|
||||
AppProjectWebSite);
|
||||
|
||||
msgBox->setTextFormat(Qt::RichText);
|
||||
msgBox->setText(translatedText);
|
||||
|
@ -282,19 +312,18 @@ void MainWindow::onOpen()
|
|||
DEBUG_ENTRY;
|
||||
|
||||
#ifdef SEARCHBOX_MODE_ASYNC
|
||||
Session->getBackgroundManager()->abortTrivialTasks();
|
||||
emit Session->getBackgroundManager()->abortTrivialTasks();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
if(Session->getBackgroundManager()->isRunning())
|
||||
if (Session->getBackgroundManager()->isRunning())
|
||||
{
|
||||
int ret = QMessageBox::warning(this,
|
||||
tr("Backgroung Task"),
|
||||
tr("Background task for checking rollingstock errors is still running.\n"
|
||||
"Do you want to cancel it?"),
|
||||
QMessageBox::Yes, QMessageBox::No,
|
||||
QMessageBox::Yes);
|
||||
if(ret == QMessageBox::Yes)
|
||||
int ret = QMessageBox::warning(
|
||||
this, tr("Backgroung Task"),
|
||||
tr("Background task for checking rollingstock errors is still running.\n"
|
||||
"Do you want to cancel it?"),
|
||||
QMessageBox::Yes, QMessageBox::No, QMessageBox::Yes);
|
||||
if (ret == QMessageBox::Yes)
|
||||
Session->getBackgroundManager()->abortAllTasks();
|
||||
else
|
||||
return;
|
||||
|
@ -312,22 +341,21 @@ void MainWindow::onOpen()
|
|||
filters << FileFormats::tr(FileFormats::allFiles);
|
||||
dlg->setNameFilters(filters);
|
||||
|
||||
if(dlg->exec() != QDialog::Accepted || !dlg)
|
||||
if (dlg->exec() != QDialog::Accepted || !dlg)
|
||||
return;
|
||||
|
||||
QString fileName = dlg->selectedUrls().value(0).toLocalFile();
|
||||
|
||||
if(fileName.isEmpty())
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
RecentDirStore::setPath(directory_key::session, fileName);
|
||||
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
|
||||
if(!QThreadPool::globalInstance()->waitForDone(2000))
|
||||
if (!QThreadPool::globalInstance()->waitForDone(2000))
|
||||
{
|
||||
QMessageBox::warning(this,
|
||||
tr("Background Tasks"),
|
||||
QMessageBox::warning(this, tr("Background Tasks"),
|
||||
tr("Some background tasks are still running.\n"
|
||||
"The file was not opened. Try again."));
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
@ -339,10 +367,10 @@ void MainWindow::onOpen()
|
|||
loadFile(fileName);
|
||||
}
|
||||
|
||||
void MainWindow::loadFile(const QString& fileName)
|
||||
void MainWindow::loadFile(const QString &fileName)
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
if(fileName.isEmpty())
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
qDebug() << "Loading:" << fileName;
|
||||
|
@ -353,86 +381,91 @@ void MainWindow::loadFile(const QString& fileName)
|
|||
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if(err == DB_Error::FormatTooOld)
|
||||
if (err == DB_Error::FormatTooOld)
|
||||
{
|
||||
int but = QMessageBox::warning(this, tr("Version is old"),
|
||||
tr("This file was created by an older version of %1.\n"
|
||||
"Opening it without conversion might not work and even crash the application.\n"
|
||||
"Do you want to open it anyway?").arg(qApp->applicationDisplayName()),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if(but == QMessageBox::Yes)
|
||||
int but = QMessageBox::warning(
|
||||
this, tr("Version is old"),
|
||||
tr("This file was created by an older version of %1.\n"
|
||||
"Opening it without conversion might not work and even crash the application.\n"
|
||||
"Do you want to open it anyway?")
|
||||
.arg(qApp->applicationDisplayName()),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (but == QMessageBox::Yes)
|
||||
err = Session->openDB(fileName, true);
|
||||
}
|
||||
else if(err == DB_Error::FormatTooNew)
|
||||
else if (err == DB_Error::FormatTooNew)
|
||||
{
|
||||
if(err == DB_Error::FormatTooOld)
|
||||
if (err == DB_Error::FormatTooOld)
|
||||
{
|
||||
int but = QMessageBox::warning(this, tr("Version is too new"),
|
||||
tr("This file was created by a newer version of %1.\n"
|
||||
"You should update the application first. Opening this file might not work or even crash.\n"
|
||||
"Do you want to open it anyway?").arg(qApp->applicationDisplayName()),
|
||||
"You should update the application first. Opening "
|
||||
"this file might not work or even crash.\n"
|
||||
"Do you want to open it anyway?")
|
||||
.arg(qApp->applicationDisplayName()),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if(but == QMessageBox::Yes)
|
||||
if (but == QMessageBox::Yes)
|
||||
err = Session->openDB(fileName, true);
|
||||
}
|
||||
}
|
||||
|
||||
if(err == DB_Error::DbBusyWhenClosing)
|
||||
if (err == DB_Error::DbBusyWhenClosing)
|
||||
showCloseWarning();
|
||||
|
||||
if(err != DB_Error::NoError)
|
||||
if (err != DB_Error::NoError)
|
||||
return;
|
||||
|
||||
setCurrentFile(fileName);
|
||||
|
||||
//Fake we are coming from Start Page
|
||||
//Otherwise we cannot show the first line
|
||||
// Fake we are coming from Start Page
|
||||
// Otherwise we cannot show the first line
|
||||
m_mode = CentralWidgetMode::StartPageMode;
|
||||
checkLineNumber();
|
||||
|
||||
|
||||
if(!Session->checkImportRSTablesEmpty())
|
||||
if (!Session->checkImportRSTablesEmpty())
|
||||
{
|
||||
//Probably the application crashed before finishing RS importation
|
||||
//Give user choice to resume it or discard
|
||||
// Probably the application crashed before finishing RS importation
|
||||
// Give user choice to resume it or discard
|
||||
|
||||
OwningQPointer<QMessageBox> msgBox = new QMessageBox(
|
||||
QMessageBox::Warning,
|
||||
tr("RS Import"),
|
||||
tr("There is some rollingstock import data left in this file. "
|
||||
"Probably the application has crashed!<br>"
|
||||
"Before deleting it would you like to resume importation?<br>"
|
||||
"<i>(Sorry for the crash, would you like to contact me and share information about it?)</i>"),
|
||||
QMessageBox::NoButton, this);
|
||||
OwningQPointer<QMessageBox> msgBox =
|
||||
new QMessageBox(QMessageBox::Warning, tr("RS Import"),
|
||||
tr("There is some rollingstock import data left in this file. "
|
||||
"Probably the application has crashed!<br>"
|
||||
"Before deleting it would you like to resume importation?<br>"
|
||||
"<i>(Sorry for the crash, would you like to contact me and share "
|
||||
"information about it?)</i>"),
|
||||
QMessageBox::NoButton, this);
|
||||
auto resumeBut = msgBox->addButton(tr("Resume importation"), QMessageBox::YesRole);
|
||||
msgBox->addButton(tr("Just delete it"), QMessageBox::NoRole);
|
||||
msgBox->setDefaultButton(resumeBut);
|
||||
msgBox->setTextFormat(Qt::RichText);
|
||||
|
||||
msgBox->exec();
|
||||
if(!msgBox)
|
||||
if (!msgBox)
|
||||
return;
|
||||
|
||||
if(msgBox->clickedButton() == resumeBut)
|
||||
if (msgBox->clickedButton() == resumeBut)
|
||||
{
|
||||
Session->getViewManager()->resumeRSImportation();
|
||||
}else{
|
||||
}
|
||||
else
|
||||
{
|
||||
Session->clearImportRSTables();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::setCurrentFile(const QString& fileName)
|
||||
void MainWindow::setCurrentFile(const QString &fileName)
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
if(fileName.isEmpty())
|
||||
if (fileName.isEmpty())
|
||||
{
|
||||
setWindowFilePath(QString()); //Reset title bar
|
||||
setWindowFilePath(QString()); // Reset title bar
|
||||
return;
|
||||
}
|
||||
|
||||
//Qt automatically takes care of showing stripped filename in window title
|
||||
// Qt automatically takes care of showing stripped filename in window title
|
||||
setWindowFilePath(fileName);
|
||||
|
||||
QStringList files = AppSettings.getRecentFiles();
|
||||
|
@ -449,22 +482,23 @@ void MainWindow::setCurrentFile(const QString& fileName)
|
|||
QString MainWindow::strippedName(const QString &fullFileName, bool *ok)
|
||||
{
|
||||
QFileInfo fi(fullFileName);
|
||||
if(ok) *ok = fi.exists();
|
||||
if (ok)
|
||||
*ok = fi.exists();
|
||||
return fi.fileName();
|
||||
}
|
||||
|
||||
void MainWindow::updateRecentFileActions()
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
QStringList files = AppSettings.getRecentFiles();
|
||||
QStringList files = AppSettings.getRecentFiles();
|
||||
|
||||
int numRecentFiles = qMin(files.size(), int(MaxRecentFiles));
|
||||
|
||||
for (int i = 0; i < numRecentFiles; i++)
|
||||
{
|
||||
bool ok = true;
|
||||
bool ok = true;
|
||||
QString name = strippedName(files[i], &ok);
|
||||
if(name.isEmpty() || !ok)
|
||||
if (name.isEmpty() || !ok)
|
||||
{
|
||||
files.removeAt(i);
|
||||
i--;
|
||||
|
@ -488,8 +522,8 @@ void MainWindow::updateRecentFileActions()
|
|||
void MainWindow::onOpenRecent()
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
QAction *act = qobject_cast<QAction*>(sender());
|
||||
if(!act)
|
||||
QAction *act = qobject_cast<QAction *>(sender());
|
||||
if (!act)
|
||||
return;
|
||||
|
||||
loadFile(act->data().toString());
|
||||
|
@ -503,21 +537,20 @@ void MainWindow::onNew()
|
|||
emit Session->getBackgroundManager()->abortTrivialTasks();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
if(Session->getBackgroundManager()->isRunning())
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
if (Session->getBackgroundManager()->isRunning())
|
||||
{
|
||||
int ret = QMessageBox::warning(this,
|
||||
tr("Backgroung Task"),
|
||||
tr("Background task for checking rollingstock errors is still running.\n"
|
||||
"Do you want to cancel it?"),
|
||||
QMessageBox::Yes, QMessageBox::No,
|
||||
QMessageBox::Yes);
|
||||
if(ret == QMessageBox::Yes)
|
||||
int ret = QMessageBox::warning(
|
||||
this, tr("Backgroung Task"),
|
||||
tr("Background task for checking rollingstock errors is still running.\n"
|
||||
"Do you want to cancel it?"),
|
||||
QMessageBox::Yes, QMessageBox::No, QMessageBox::Yes);
|
||||
if (ret == QMessageBox::Yes)
|
||||
Session->getBackgroundManager()->abortAllTasks();
|
||||
else
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
OwningQPointer<QFileDialog> dlg = new QFileDialog(this, tr("Create new Session"));
|
||||
dlg->setFileMode(QFileDialog::AnyFile);
|
||||
|
@ -530,22 +563,21 @@ void MainWindow::onNew()
|
|||
filters << FileFormats::tr(FileFormats::allFiles);
|
||||
dlg->setNameFilters(filters);
|
||||
|
||||
if(dlg->exec() != QDialog::Accepted || !dlg)
|
||||
if (dlg->exec() != QDialog::Accepted || !dlg)
|
||||
return;
|
||||
|
||||
QString fileName = dlg->selectedUrls().value(0).toLocalFile();
|
||||
|
||||
if(fileName.isEmpty())
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
RecentDirStore::setPath(directory_key::session, fileName);
|
||||
|
||||
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
|
||||
|
||||
if(!QThreadPool::globalInstance()->waitForDone(2000))
|
||||
if (!QThreadPool::globalInstance()->waitForDone(2000))
|
||||
{
|
||||
QMessageBox::warning(this,
|
||||
tr("Background Tasks"),
|
||||
QMessageBox::warning(this, tr("Background Tasks"),
|
||||
tr("Some background tasks are still running.\n"
|
||||
"The new file was not created. Try again."));
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
@ -553,17 +585,17 @@ void MainWindow::onNew()
|
|||
}
|
||||
|
||||
QFile f(fileName);
|
||||
if(f.exists())
|
||||
if (f.exists())
|
||||
f.remove();
|
||||
|
||||
DB_Error err = Session->createNewDB(fileName);
|
||||
|
||||
QApplication::restoreOverrideCursor();
|
||||
|
||||
if(err == DB_Error::DbBusyWhenClosing)
|
||||
if (err == DB_Error::DbBusyWhenClosing)
|
||||
showCloseWarning();
|
||||
|
||||
if(err != DB_Error::NoError)
|
||||
if (err != DB_Error::NoError)
|
||||
return;
|
||||
|
||||
setCurrentFile(fileName);
|
||||
|
@ -572,7 +604,7 @@ void MainWindow::onNew()
|
|||
|
||||
void MainWindow::onSave()
|
||||
{
|
||||
if(!Session->getViewManager()->closeEditors())
|
||||
if (!Session->getViewManager()->closeEditors())
|
||||
return;
|
||||
|
||||
Session->releaseAllSavepoints();
|
||||
|
@ -582,7 +614,7 @@ void MainWindow::onSaveCopyAs()
|
|||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
if(!Session->getViewManager()->closeEditors())
|
||||
if (!Session->getViewManager()->closeEditors())
|
||||
return;
|
||||
|
||||
OwningQPointer<QFileDialog> dlg = new QFileDialog(this, tr("Save Session Copy"));
|
||||
|
@ -596,29 +628,30 @@ void MainWindow::onSaveCopyAs()
|
|||
filters << FileFormats::tr(FileFormats::allFiles);
|
||||
dlg->setNameFilters(filters);
|
||||
|
||||
if(dlg->exec() != QDialog::Accepted || !dlg)
|
||||
if (dlg->exec() != QDialog::Accepted || !dlg)
|
||||
return;
|
||||
|
||||
QString fileName = dlg->selectedUrls().value(0).toLocalFile();
|
||||
|
||||
if(fileName.isEmpty())
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
RecentDirStore::setPath(directory_key::session, fileName);
|
||||
|
||||
QFile f(fileName);
|
||||
if(f.exists())
|
||||
if (f.exists())
|
||||
f.remove();
|
||||
|
||||
database backupDB(fileName.toUtf8(), SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
|
||||
|
||||
int rc = Session->m_Db.backup(backupDB, [](int pageCount, int remaining, int res)
|
||||
int rc = Session->m_Db.backup(backupDB,
|
||||
[](int pageCount, int remaining, int res)
|
||||
{
|
||||
Q_UNUSED(res)
|
||||
qDebug() << pageCount << "/" << remaining;
|
||||
});
|
||||
|
||||
if(rc != SQLITE_OK && rc != SQLITE_DONE)
|
||||
if (rc != SQLITE_OK && rc != SQLITE_DONE)
|
||||
{
|
||||
QString errMsg = Session->m_Db.error_msg();
|
||||
qDebug() << Session->m_Db.error_code() << errMsg;
|
||||
|
@ -628,7 +661,7 @@ void MainWindow::onSaveCopyAs()
|
|||
|
||||
void MainWindow::closeEvent(QCloseEvent *e)
|
||||
{
|
||||
if(closeSession())
|
||||
if (closeSession())
|
||||
e->accept();
|
||||
else
|
||||
e->ignore();
|
||||
|
@ -636,15 +669,14 @@ void MainWindow::closeEvent(QCloseEvent *e)
|
|||
|
||||
void MainWindow::showCloseWarning()
|
||||
{
|
||||
QMessageBox::warning(this,
|
||||
tr("Error while Closing"),
|
||||
QMessageBox::warning(this, tr("Error while Closing"),
|
||||
tr("There was an error while closing the database.\n"
|
||||
"Make sure there aren't any background tasks running and try again."));
|
||||
}
|
||||
|
||||
void MainWindow::stopCloseTimer()
|
||||
{
|
||||
if(closeTimerId)
|
||||
if (closeTimerId)
|
||||
{
|
||||
killTimer(closeTimerId);
|
||||
closeTimerId = 0;
|
||||
|
@ -658,9 +690,11 @@ void MainWindow::setCentralWidgetMode(MainWindow::CentralWidgetMode mode)
|
|||
case CentralWidgetMode::StartPageMode:
|
||||
{
|
||||
jobDock->hide();
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
rsErrDock->hide();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
resPanelDock->hide();
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
welcomeLabel->setText(tr("<p>Open a file: <b>File</b> > <b>Open</b></p>"
|
||||
"<p>Create new project: <b>File</b> > <b>New</b></p>"));
|
||||
statusBar()->showMessage(tr("Open file or create a new one"));
|
||||
|
@ -670,48 +704,52 @@ void MainWindow::setCentralWidgetMode(MainWindow::CentralWidgetMode mode)
|
|||
case CentralWidgetMode::NoLinesWarningMode:
|
||||
{
|
||||
jobDock->show();
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
rsErrDock->hide();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
resPanelDock->hide();
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
welcomeLabel->setText(
|
||||
tr("<p><b>There are no lines in this session</b></p>"
|
||||
"<p>"
|
||||
"<table align=\"center\">"
|
||||
"<tr>"
|
||||
"<td>Start by creating the railway layout for this session:</td>"
|
||||
"</tr>"
|
||||
"<tr>"
|
||||
"<td>"
|
||||
"<table>"
|
||||
"<tr>"
|
||||
"<td>1.</td>"
|
||||
"<td>Create stations (<b>Edit</b> > <b>Stations</b>)</td>"
|
||||
"</tr>"
|
||||
"<tr>"
|
||||
"<td>2.</td>"
|
||||
"<td>Create railway lines (<b>Edit</b> > <b>Stations</b> > <b>Lines Tab</b>)</td>"
|
||||
"</tr>"
|
||||
"<tr>"
|
||||
"<td>3.</td>"
|
||||
"<td>Add stations to railway lines</td>"
|
||||
"</tr>"
|
||||
"<tr>"
|
||||
"<td></td>"
|
||||
"<td>(<b>Edit</b> > <b>Stations</b> > <b>Lines Tab</b> > <b>Edit Line</b>)</td>"
|
||||
"</tr>"
|
||||
"</table>"
|
||||
"</td>"
|
||||
"</tr>"
|
||||
"</table>"
|
||||
"</p>"));
|
||||
tr("<p><b>There are no lines in this session</b></p>"
|
||||
"<p>"
|
||||
"<table align=\"center\">"
|
||||
"<tr>"
|
||||
"<td>Start by creating the railway layout for this session:</td>"
|
||||
"</tr>"
|
||||
"<tr>"
|
||||
"<td>"
|
||||
"<table>"
|
||||
"<tr>"
|
||||
"<td>1.</td>"
|
||||
"<td>Create stations (<b>Edit</b> > <b>Stations</b>)</td>"
|
||||
"</tr>"
|
||||
"<tr>"
|
||||
"<td>2.</td>"
|
||||
"<td>Create railway lines (<b>Edit</b> > <b>Stations</b> > <b>Lines Tab</b>)</td>"
|
||||
"</tr>"
|
||||
"<tr>"
|
||||
"<td>3.</td>"
|
||||
"<td>Add stations to railway lines</td>"
|
||||
"</tr>"
|
||||
"<tr>"
|
||||
"<td></td>"
|
||||
"<td>(<b>Edit</b> > <b>Stations</b> > <b>Lines Tab</b> > <b>Edit Line</b>)</td>"
|
||||
"</tr>"
|
||||
"</table>"
|
||||
"</td>"
|
||||
"</tr>"
|
||||
"</table>"
|
||||
"</p>"));
|
||||
break;
|
||||
}
|
||||
case CentralWidgetMode::ViewSessionMode:
|
||||
{
|
||||
jobDock->show();
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
rsErrDock->show();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
resPanelDock->show();
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
welcomeLabel->setText(QString());
|
||||
break;
|
||||
}
|
||||
|
@ -719,37 +757,39 @@ void MainWindow::setCentralWidgetMode(MainWindow::CentralWidgetMode mode)
|
|||
|
||||
enableDBActions(mode != CentralWidgetMode::StartPageMode);
|
||||
|
||||
if(mode == CentralWidgetMode::ViewSessionMode)
|
||||
if (mode == CentralWidgetMode::ViewSessionMode)
|
||||
{
|
||||
if(centralWidget() != view)
|
||||
if (centralWidget() != view)
|
||||
{
|
||||
takeCentralWidget(); //Remove ownership from welcomeLabel
|
||||
takeCentralWidget(); // Remove ownership from welcomeLabel
|
||||
setCentralWidget(view);
|
||||
view->show();
|
||||
welcomeLabel->hide();
|
||||
}
|
||||
|
||||
//Enable Job Creation
|
||||
// Enable Job Creation
|
||||
ui->actionAddJob->setEnabled(true);
|
||||
ui->actionAddJob->setToolTip(tr("Add train job"));
|
||||
|
||||
//Update actions based on Job selection
|
||||
JobStopEntry selectedJob = Session->getViewManager()->getLineGraphMgr()->getCurrentSelectedJob();
|
||||
// Update actions based on Job selection
|
||||
JobStopEntry selectedJob =
|
||||
Session->getViewManager()->getLineGraphMgr()->getCurrentSelectedJob();
|
||||
onJobSelected(selectedJob.jobId);
|
||||
}
|
||||
else
|
||||
{
|
||||
if(centralWidget() != welcomeLabel)
|
||||
if (centralWidget() != welcomeLabel)
|
||||
{
|
||||
takeCentralWidget(); //Remove ownership from LineGraphWidget
|
||||
takeCentralWidget(); // Remove ownership from LineGraphWidget
|
||||
setCentralWidget(welcomeLabel);
|
||||
view->hide();
|
||||
welcomeLabel->show();
|
||||
}
|
||||
|
||||
//If there aren't lines prevent from creating jobs
|
||||
// If there aren't lines prevent from creating jobs
|
||||
ui->actionAddJob->setEnabled(false);
|
||||
ui->actionAddJob->setToolTip(tr("You must create at least one railway segment before adding job to this session"));
|
||||
ui->actionAddJob->setToolTip(
|
||||
tr("You must create at least one railway segment before adding job to this session"));
|
||||
ui->actionRemoveJob->setEnabled(false);
|
||||
}
|
||||
|
||||
|
@ -770,8 +810,8 @@ void MainWindow::onProperties()
|
|||
void MainWindow::onMeetingInformation()
|
||||
{
|
||||
OwningQPointer<MeetingInformationDialog> dlg = new MeetingInformationDialog(this);
|
||||
int ret = dlg->exec();
|
||||
if(dlg && ret == QDialog::Accepted)
|
||||
int ret = dlg->exec();
|
||||
if (dlg && ret == QDialog::Accepted)
|
||||
dlg->saveData();
|
||||
}
|
||||
|
||||
|
@ -779,11 +819,11 @@ bool MainWindow::closeSession()
|
|||
{
|
||||
DB_Error err = Session->closeDB();
|
||||
|
||||
if(err == DB_Error::DbBusyWhenClosing)
|
||||
if (err == DB_Error::DbBusyWhenClosing)
|
||||
{
|
||||
if(closeTimerId)
|
||||
if (closeTimerId)
|
||||
{
|
||||
//We already tried again
|
||||
// We already tried again
|
||||
|
||||
stopCloseTimer();
|
||||
|
||||
|
@ -791,17 +831,19 @@ bool MainWindow::closeSession()
|
|||
return false;
|
||||
}
|
||||
|
||||
//Start a timer to try again
|
||||
// Start a timer to try again
|
||||
closeTimerId = startTimer(1500);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(err != DB_Error::NoError && err != DB_Error::DbNotOpen)
|
||||
stopCloseTimer();
|
||||
|
||||
if (err != DB_Error::NoError && err != DB_Error::DbNotOpen)
|
||||
return false;
|
||||
|
||||
setCentralWidgetMode(CentralWidgetMode::StartPageMode);
|
||||
|
||||
//Reset filePath to refresh title
|
||||
// Reset filePath to refresh title
|
||||
setCurrentFile(QString());
|
||||
|
||||
return true;
|
||||
|
@ -811,11 +853,11 @@ void MainWindow::enableDBActions(bool enable)
|
|||
{
|
||||
databaseActionGroup->setEnabled(enable);
|
||||
searchEdit->setEnabled(enable);
|
||||
if(!enable)
|
||||
if (!enable)
|
||||
jobEditor->setEnabled(false);
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
rsErrorsWidget->setEnabled(enable);
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
resPanelDock->widget()->setEnabled(enable);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -853,21 +895,21 @@ void MainWindow::onRemoveJob()
|
|||
void MainWindow::onPrint()
|
||||
{
|
||||
OwningQPointer<PrintWizard> wizard = new PrintWizard(Session->m_Db, this);
|
||||
wizard->setOutputType(Print::Native);
|
||||
wizard->setOutputType(Print::OutputType::Native);
|
||||
wizard->exec();
|
||||
}
|
||||
|
||||
void MainWindow::onPrintPDF()
|
||||
{
|
||||
OwningQPointer<PrintWizard> wizard = new PrintWizard(Session->m_Db, this);
|
||||
wizard->setOutputType(Print::Pdf);
|
||||
wizard->setOutputType(Print::OutputType::Pdf);
|
||||
wizard->exec();
|
||||
}
|
||||
|
||||
void MainWindow::onExportSvg()
|
||||
{
|
||||
OwningQPointer<PrintWizard> wizard = new PrintWizard(Session->m_Db, this);
|
||||
wizard->setOutputType(Print::Svg);
|
||||
wizard->setOutputType(Print::OutputType::Svg);
|
||||
wizard->exec();
|
||||
}
|
||||
|
||||
|
@ -893,30 +935,30 @@ void MainWindow::checkLineNumber()
|
|||
{
|
||||
RailwaySegmentHelper helper(Session->m_Db);
|
||||
|
||||
bool isLine = false;
|
||||
bool isLine = false;
|
||||
db_id graphObjId = 0;
|
||||
|
||||
if(!helper.findFirstLineOrSegment(graphObjId, isLine))
|
||||
if (!helper.findFirstLineOrSegment(graphObjId, isLine))
|
||||
graphObjId = 0;
|
||||
if(graphObjId && m_mode != CentralWidgetMode::ViewSessionMode)
|
||||
if (graphObjId && m_mode != CentralWidgetMode::ViewSessionMode)
|
||||
{
|
||||
//First line was added or newly opened file -> Session has at least one line
|
||||
// First line was added or newly opened file -> Session has at least one line
|
||||
setCentralWidgetMode(CentralWidgetMode::ViewSessionMode);
|
||||
|
||||
//Load first line or segment
|
||||
// Load first line or segment
|
||||
view->tryLoadGraph(graphObjId,
|
||||
isLine ? LineGraphType::RailwayLine : LineGraphType::RailwaySegment);
|
||||
}
|
||||
else if(graphObjId == 0 && m_mode != CentralWidgetMode::NoLinesWarningMode)
|
||||
else if (graphObjId == 0 && m_mode != CentralWidgetMode::NoLinesWarningMode)
|
||||
{
|
||||
//Last line removed -> Session has no line
|
||||
// Last line removed -> Session has no line
|
||||
setCentralWidgetMode(CentralWidgetMode::NoLinesWarningMode);
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::timerEvent(QTimerEvent *e)
|
||||
{
|
||||
if(e->timerId() == closeTimerId)
|
||||
if (e->timerId() == closeTimerId)
|
||||
{
|
||||
closeSession();
|
||||
return;
|
||||
|
@ -933,41 +975,35 @@ void MainWindow::onJobSelected(db_id jobId)
|
|||
ui->actionRemoveJob->setEnabled(selected);
|
||||
|
||||
QString removeJobTooltip;
|
||||
if(selected)
|
||||
if (selected)
|
||||
removeJobTooltip = tr("Remove selected Job");
|
||||
else
|
||||
removeJobTooltip = tr("First select a Job by double click on graph or type in search box");
|
||||
ui->actionRemoveJob->setToolTip(removeJobTooltip);
|
||||
}
|
||||
|
||||
//QT-BUG 69922: If user closes a floating dock widget, when shown again it cannot dock anymore
|
||||
//HACK: intercept dock close event and manually re-dock and hide so next time is shown it's docked
|
||||
//NOTE: calling directly 'QDockWidget::setFloating(false)' from inside 'eventFinter()' causes CRASH
|
||||
// so queue it. Cannot use 'QMetaObject::invokeMethod()' because it's not a slot.
|
||||
// QT-BUG 69922: If user closes a floating dock widget, when shown again it cannot dock anymore
|
||||
// HACK: intercept dock close event and manually re-dock and hide so next time is shown it's docked
|
||||
// NOTE: calling directly 'QDockWidget::setFloating(false)' from inside 'eventFinter()' causes CRASH
|
||||
// so queue it. Cannot use 'QMetaObject::invokeMethod()' because it's not a slot.
|
||||
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
|
||||
{
|
||||
if(watched == jobDock && event->type() == QEvent::Close)
|
||||
if (watched == jobDock && event->type() == QEvent::Close)
|
||||
{
|
||||
if(jobDock->isFloating())
|
||||
if (jobDock->isFloating())
|
||||
{
|
||||
QTimer::singleShot(0, jobDock, [this]()
|
||||
{
|
||||
jobDock->setFloating(false);
|
||||
});
|
||||
QTimer::singleShot(0, jobDock, [this]() { jobDock->setFloating(false); });
|
||||
}
|
||||
}
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
else if(watched == rsErrDock && event->type() == QEvent::Close)
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
else if (watched == resPanelDock && event->type() == QEvent::Close)
|
||||
{
|
||||
if(rsErrDock->isFloating())
|
||||
if (resPanelDock->isFloating())
|
||||
{
|
||||
QTimer::singleShot(0, rsErrDock, [this]()
|
||||
{
|
||||
rsErrDock->setFloating(false);
|
||||
});
|
||||
QTimer::singleShot(0, resPanelDock, [this]() { resPanelDock->setFloating(false); });
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
return QMainWindow::eventFilter(watched, event);
|
||||
}
|
||||
|
@ -981,10 +1017,10 @@ void MainWindow::onJobSearchItemSelected()
|
|||
{
|
||||
db_id jobId = 0;
|
||||
QString tmp;
|
||||
if(!searchEdit->getData(jobId, tmp))
|
||||
if (!searchEdit->getData(jobId, tmp))
|
||||
return;
|
||||
|
||||
searchEdit->clear(); //Clear text
|
||||
searchEdit->clear(); // Clear text
|
||||
Session->getViewManager()->requestJobSelection(jobId, true, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
|
@ -22,10 +41,6 @@ class QLabel;
|
|||
class QActionGroup;
|
||||
class CustomCompletionLineEdit;
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
class RsErrorsWidget;
|
||||
#endif
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -42,7 +57,7 @@ public:
|
|||
void loadFile(const QString &fileName);
|
||||
|
||||
bool closeSession();
|
||||
|
||||
|
||||
private slots:
|
||||
void onStationManager();
|
||||
|
||||
|
@ -112,10 +127,9 @@ private:
|
|||
|
||||
JobPathEditor *jobEditor;
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
RsErrorsWidget *rsErrorsWidget;
|
||||
QDockWidget *rsErrDock;
|
||||
#endif
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
QDockWidget *resPanelDock;
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
LineGraphWidget *view;
|
||||
QDockWidget *jobDock;
|
||||
|
@ -126,8 +140,11 @@ private:
|
|||
|
||||
QActionGroup *databaseActionGroup;
|
||||
|
||||
enum { MaxRecentFiles = 5 };
|
||||
QAction* recentFileActs[MaxRecentFiles];
|
||||
enum
|
||||
{
|
||||
MaxRecentFiles = 5
|
||||
};
|
||||
QAction *recentFileActs[MaxRecentFiles];
|
||||
|
||||
CentralWidgetMode m_mode;
|
||||
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "propertiesdialog.h"
|
||||
|
||||
#include "app/session.h"
|
||||
|
@ -28,7 +47,7 @@ PropertiesDialog::PropertiesDialog(QWidget *parent) :
|
|||
pathReadOnlyEdit->setPlaceholderText(tr("No opened file"));
|
||||
pathReadOnlyEdit->setReadOnly(true);
|
||||
|
||||
//TODO: make pretty and maybe add other informations like metadata versions
|
||||
// TODO: make pretty and maybe add other informations like metadata versions
|
||||
|
||||
setMinimumSize(200, 200);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PROPERTIESDIALOG_H
|
||||
#define PROPERTIESDIALOG_H
|
||||
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "app/scopedebug.h"
|
||||
|
||||
#ifndef NO_DEBUG_CALL_TRACE
|
||||
|
@ -9,19 +28,20 @@ Scope::Scope(const char *fn, const char *s, const char *e) :
|
|||
start(s),
|
||||
end(e)
|
||||
{
|
||||
qDebug().nospace().noquote()
|
||||
<< start << QByteArray(" ").repeated(stackLevel) << ">>> " << func << end;
|
||||
qDebug().nospace().noquote() << start << QByteArray(" ").repeated(stackLevel) << ">>> " << func
|
||||
<< end;
|
||||
stackLevel++;
|
||||
}
|
||||
|
||||
Scope::~Scope()
|
||||
{
|
||||
stackLevel--;
|
||||
qDebug().nospace().noquote()
|
||||
<< start << QByteArray(" ").repeated(stackLevel) << "<<< " << func << end;
|
||||
qDebug().nospace().noquote() << start << QByteArray(" ").repeated(stackLevel) << "<<< " << func
|
||||
<< end;
|
||||
}
|
||||
|
||||
ScopeTimer::ScopeTimer(const char *fn, const char *s, const char *e) :Scope(fn, s, e)
|
||||
ScopeTimer::ScopeTimer(const char *fn, const char *s, const char *e) :
|
||||
Scope(fn, s, e)
|
||||
{
|
||||
timer.start();
|
||||
}
|
||||
|
|
|
@ -1,57 +1,71 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SCOPEDEBUG_H
|
||||
#define SCOPEDEBUG_H
|
||||
|
||||
#define SHELL_RESET "\033[0m"
|
||||
|
||||
#define SHELL_RESET "\033[0m"
|
||||
|
||||
#define SHELL_RED "\033[031m"
|
||||
#define SHELL_GREEN "\033[032m"
|
||||
#define SHELL_YELLOW "\033[033m"
|
||||
#define SHELL_BLUE "\033[034m"
|
||||
|
||||
#define SHELL_RED "\033[031m"
|
||||
#define SHELL_GREEN "\033[032m"
|
||||
#define SHELL_YELLOW "\033[033m"
|
||||
#define SHELL_BLUE "\033[034m"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
//#define NO_DEBUG_CALL_TRACE
|
||||
// #define NO_DEBUG_CALL_TRACE
|
||||
|
||||
#ifndef NO_DEBUG_CALL_TRACE
|
||||
|
||||
class Scope
|
||||
{
|
||||
public:
|
||||
Scope(const char *fn, const char *s="", const char* e="");
|
||||
Scope(const char *fn, const char *s = "", const char *e = "");
|
||||
~Scope();
|
||||
|
||||
const char *func, *start, *end;
|
||||
};
|
||||
|
||||
|
||||
class ScopeTimer : Scope
|
||||
{
|
||||
public:
|
||||
ScopeTimer(const char *fn, const char *s="", const char* e="");
|
||||
ScopeTimer(const char *fn, const char *s = "", const char *e = "");
|
||||
~ScopeTimer();
|
||||
|
||||
QElapsedTimer timer;
|
||||
};
|
||||
|
||||
# define DEBUG_ENTRY_NAME(name) Scope DBG(name)
|
||||
# define DEBUG_ENTRY DEBUG_ENTRY_NAME(__PRETTY_FUNCTION__)
|
||||
# define DEBUG_COLOR_ENTRY(color) Scope DBG(__PRETTY_FUNCTION__, color, SHELL_RESET)
|
||||
# define DEBUG_IMPORTANT_ENTRY DEBUG_COLOR_ENTRY(SHELL_GREEN)
|
||||
|
||||
|
||||
# define DEBUG_ENTRY_NAME(name) Scope DBG(name)
|
||||
# define DEBUG_ENTRY DEBUG_ENTRY_NAME(__PRETTY_FUNCTION__)
|
||||
# define DEBUG_COLOR_ENTRY(color) Scope DBG(__PRETTY_FUNCTION__, color, SHELL_RESET)
|
||||
# define DEBUG_IMPORTANT_ENTRY DEBUG_COLOR_ENTRY(SHELL_GREEN)
|
||||
|
||||
# define DEBUG_TIME_ENTRY ScopeTimer DBG(__PRETTY_FUNCTION__)
|
||||
# define DEBUG_TIME_ENTRY ScopeTimer DBG(__PRETTY_FUNCTION__)
|
||||
|
||||
#else
|
||||
# define DEBUG_ENTRY_NAME(name)
|
||||
# define DEBUG_ENTRY
|
||||
# define DEBUG_COLOR_ENTRY(color)
|
||||
# define DEBUG_IMPORTANT_ENTRY
|
||||
# define DEBUG_TIME_ENTRY
|
||||
# define DEBUG_ENTRY_NAME(name)
|
||||
# define DEBUG_ENTRY
|
||||
# define DEBUG_COLOR_ENTRY(color)
|
||||
# define DEBUG_IMPORTANT_ENTRY
|
||||
# define DEBUG_TIME_ENTRY
|
||||
#endif // NO_DEBUG_CALLTRACE
|
||||
|
||||
#endif // SCOPEDEBUG_H
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "app/session.h"
|
||||
#include "info.h"
|
||||
|
||||
|
@ -10,22 +29,15 @@
|
|||
#include "db_metadata/metadatamanager.h"
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
#include "backgroundmanager/backgroundmanager.h"
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
#include "rollingstock/rs_checker/rscheckermanager.h"
|
||||
#endif // ENABLE_RS_CHECKER
|
||||
|
||||
# include "backgroundmanager/backgroundmanager.h"
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#include "utils/jobcategorystrings.h"
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QDir>
|
||||
|
||||
#include <QTranslator>
|
||||
|
||||
MeetingSession* MeetingSession::session;
|
||||
MeetingSession *MeetingSession::session;
|
||||
QString MeetingSession::appDataPath;
|
||||
const QLocale MeetingSession::embeddedLocale = QLocale(QLocale::English, QLocale::UnitedStates);
|
||||
|
||||
|
@ -42,13 +54,14 @@ MeetingSession::MeetingSession() :
|
|||
m_Db(nullptr),
|
||||
sheetExportTranslator(nullptr)
|
||||
{
|
||||
session = this; //Global singleton pointer
|
||||
session = this; // Global singleton pointer
|
||||
|
||||
QString settings_file;
|
||||
if(qApp->arguments().contains("test"))
|
||||
if (qApp->arguments().contains("test"))
|
||||
{
|
||||
//If testing use exe folder instead of AppData
|
||||
settings_file = QCoreApplication::applicationDirPath() + QStringLiteral("/mrtp_settings.ini");
|
||||
// If testing use exe folder instead of AppData
|
||||
settings_file =
|
||||
QCoreApplication::applicationDirPath() + QStringLiteral("/mrtp_settings.ini");
|
||||
}
|
||||
|
||||
loadSettings(settings_file);
|
||||
|
@ -64,7 +77,7 @@ MeetingSession::MeetingSession() :
|
|||
|
||||
MeetingSession::~MeetingSession()
|
||||
{
|
||||
//Delete sheet export translator
|
||||
// Delete sheet export translator
|
||||
setSheetExportTranslator(nullptr, sheetExportLocale);
|
||||
}
|
||||
|
||||
|
@ -78,29 +91,29 @@ DB_Error MeetingSession::openDB(const QString &str, bool ignoreVersion)
|
|||
DEBUG_ENTRY;
|
||||
|
||||
DB_Error err = closeDB();
|
||||
if(err != DB_Error::NoError && err != DB_Error::DbNotOpen)
|
||||
if (err != DB_Error::NoError && err != DB_Error::DbNotOpen)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
//try{
|
||||
if(m_Db.connect(str.toUtf8(), SQLITE_OPEN_READWRITE) != SQLITE_OK)
|
||||
// try{
|
||||
if (m_Db.connect(str.toUtf8(), SQLITE_OPEN_READWRITE) != SQLITE_OK)
|
||||
{
|
||||
//throw database_error(m_Db);
|
||||
// throw database_error(m_Db);
|
||||
qWarning() << "DB:" << m_Db.error_msg();
|
||||
return DB_Error::GenericError;
|
||||
}
|
||||
|
||||
if(!ignoreVersion)
|
||||
if (!ignoreVersion)
|
||||
{
|
||||
qint64 version = 0;
|
||||
switch (metaDataMgr->getInt64(version, MetaDataKey::FormatVersionKey))
|
||||
{
|
||||
case MetaDataKey::Result::ValueFound:
|
||||
{
|
||||
if(version < FormatVersion)
|
||||
if (version < FormatVersion)
|
||||
return DB_Error::FormatTooOld;
|
||||
else if(version > FormatVersion)
|
||||
else if (version > FormatVersion)
|
||||
return DB_Error::FormatTooNew;
|
||||
break;
|
||||
}
|
||||
|
@ -111,45 +124,43 @@ DB_Error MeetingSession::openDB(const QString &str, bool ignoreVersion)
|
|||
|
||||
fileName = str;
|
||||
|
||||
//Enable foreign keys to ensure no invalid operation is allowed
|
||||
// Enable foreign keys to ensure no invalid operation is allowed
|
||||
m_Db.enable_foreign_keys(true);
|
||||
m_Db.enable_extended_result_codes(true);
|
||||
|
||||
// }catch(const char *msg)
|
||||
// {
|
||||
// QMessageBox::warning(nullptr,
|
||||
// QObject::tr("Error"),
|
||||
// QObject::tr("Error while opening file:\n%1\n'%2'")
|
||||
// .arg(str)
|
||||
// .arg(msg));
|
||||
// throw;
|
||||
// return false;
|
||||
// }
|
||||
// catch(std::exception& e)
|
||||
// {
|
||||
// QMessageBox::warning(nullptr,
|
||||
// QObject::tr("Error"),
|
||||
// QObject::tr("Error while opening file:\n%1\n'%2'")
|
||||
// .arg(str)
|
||||
// .arg(e.what()));
|
||||
// throw;
|
||||
// return false;
|
||||
// }
|
||||
// catch(...)
|
||||
// {
|
||||
// QMessageBox::warning(nullptr,
|
||||
// QObject::tr("Error"),
|
||||
// QObject::tr("Unknown error while opening file:\n%1").arg(str));
|
||||
// throw;
|
||||
// return false;
|
||||
// }
|
||||
// }catch(const char *msg)
|
||||
// {
|
||||
// QMessageBox::warning(nullptr,
|
||||
// QObject::tr("Error"),
|
||||
// QObject::tr("Error while opening file:\n%1\n'%2'")
|
||||
// .arg(str)
|
||||
// .arg(msg));
|
||||
// throw;
|
||||
// return false;
|
||||
// }
|
||||
// catch(std::exception& e)
|
||||
// {
|
||||
// QMessageBox::warning(nullptr,
|
||||
// QObject::tr("Error"),
|
||||
// QObject::tr("Error while opening file:\n%1\n'%2'")
|
||||
// .arg(str)
|
||||
// .arg(e.what()));
|
||||
// throw;
|
||||
// return false;
|
||||
// }
|
||||
// catch(...)
|
||||
// {
|
||||
// QMessageBox::warning(nullptr,
|
||||
// QObject::tr("Error"),
|
||||
// QObject::tr("Unknown error while opening file:\n%1").arg(str));
|
||||
// throw;
|
||||
// return false;
|
||||
// }
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
if(settings.getCheckRSWhenOpeningDB())
|
||||
backgroundManager->getRsChecker()->startWorker();
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
backgroundManager->handleSessionLoaded();
|
||||
#endif
|
||||
|
||||
|
||||
return DB_Error::NoError;
|
||||
}
|
||||
|
||||
|
@ -157,42 +168,42 @@ DB_Error MeetingSession::closeDB()
|
|||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
if(!m_Db.db())
|
||||
if (!m_Db.db())
|
||||
return DB_Error::DbNotOpen;
|
||||
|
||||
#ifdef SEARCHBOX_MODE_ASYNC
|
||||
backgroundManager->abortTrivialTasks();
|
||||
emit backgroundManager->abortTrivialTasks();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
backgroundManager->abortAllTasks();
|
||||
#endif
|
||||
|
||||
if(!viewManager->closeEditors())
|
||||
return DB_Error::EditorsStillOpened; //User wants to continue editing
|
||||
if (!viewManager->closeEditors())
|
||||
return DB_Error::EditorsStillOpened; // User wants to continue editing
|
||||
|
||||
//Close all graphs
|
||||
// Close all graphs
|
||||
viewManager->clearAllLineGraphs();
|
||||
|
||||
releaseAllSavepoints();
|
||||
|
||||
//Calls sqlite3_close(), not forcing closing db like sqlite3_close_v2
|
||||
//So in case the database is still used by some background task (returns SQLITE_BUSY)
|
||||
//we abort closing and return. It's like nevere having closed, database is 100% working
|
||||
// Calls sqlite3_close(), not forcing closing db like sqlite3_close_v2
|
||||
// So in case the database is still used by some background task (returns SQLITE_BUSY)
|
||||
// we abort closing and return. It's like nevere having closed, database is 100% working
|
||||
int rc = m_Db.disconnect();
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Err: closing db" << m_Db.error_code() << m_Db.error_msg();
|
||||
|
||||
if(rc == SQLITE_BUSY)
|
||||
if (rc == SQLITE_BUSY)
|
||||
{
|
||||
return DB_Error::DbBusyWhenClosing;
|
||||
}
|
||||
//return false;
|
||||
// return false;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
backgroundManager->getRsChecker()->clearModel();
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
backgroundManager->clearResults();
|
||||
#endif
|
||||
|
||||
fileName.clear();
|
||||
|
@ -200,28 +211,36 @@ DB_Error MeetingSession::closeDB()
|
|||
return DB_Error::NoError;
|
||||
}
|
||||
|
||||
DB_Error MeetingSession::createNewDB(const QString& file)
|
||||
DB_Error MeetingSession::createNewDB(const QString &file)
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
DB_Error err = closeDB();
|
||||
if(err != DB_Error::NoError && err != DB_Error::DbNotOpen)
|
||||
if (err != DB_Error::NoError && err != DB_Error::DbNotOpen)
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
int result = SQLITE_OK;
|
||||
#define CHECK(code) if((code) != SQLITE_OK) qWarning() << __LINE__ << (code) << m_Db.error_code() << m_Db.error_msg()
|
||||
#define CHECK(code) \
|
||||
if ((code) != SQLITE_OK) \
|
||||
qWarning() << __LINE__ << (code) << m_Db.error_code() << m_Db.error_msg()
|
||||
|
||||
result = m_Db.connect(file.toUtf8(), SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
|
||||
CHECK(result);
|
||||
|
||||
//See 'openDB()'
|
||||
// See 'openDB()'
|
||||
m_Db.enable_foreign_keys(true);
|
||||
m_Db.enable_extended_result_codes(true);
|
||||
|
||||
fileName = file;
|
||||
|
||||
//Tables
|
||||
/* NOTE: SQLite Extended Error codes as of version 3.35.0
|
||||
* If a foreign key is explicitly created with "ON DELETE RESTRICT",
|
||||
* it will trigger SQLITE_CONSTRAINT_TRIGGER error,
|
||||
* if not explicitly set, it will trigger SQLITE_CONSTRAINT_FOREIGNKEY error.
|
||||
*/
|
||||
|
||||
// Tables
|
||||
result = m_Db.execute("CREATE TABLE rs_models ("
|
||||
"id INTEGER,"
|
||||
"name TEXT,"
|
||||
|
@ -262,81 +281,87 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
"CHECK(length(name)>0) )");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE station_tracks ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"station_id INTEGER NOT NULL,"
|
||||
"pos INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"track_length_cm INTEGER NOT NULL,"
|
||||
"platf_length_cm INTEGET NOT NULL,"
|
||||
"freight_length_cm INTEGER NOT NULL,"
|
||||
"max_axes INTEGER NOT NULL,"
|
||||
"color_rgb INTEGER,"
|
||||
"name TEXT NOT NULL,"
|
||||
"CHECK("
|
||||
" length(name)>0 AND max_axes>=2 AND track_length_cm>0"
|
||||
" AND (platf_length_cm BETWEEN 0 AND track_length_cm)"
|
||||
" AND (freight_length_cm BETWEEN 0 AND track_length_cm)"
|
||||
"),"
|
||||
"UNIQUE(station_id, pos),"
|
||||
"UNIQUE(station_id, name),"
|
||||
"FOREIGN KEY (station_id) REFERENCES stations(id) ON UPDATE CASCADE ON DELETE CASCADE )");
|
||||
result = m_Db.execute(
|
||||
"CREATE TABLE station_tracks ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"station_id INTEGER NOT NULL,"
|
||||
"pos INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"track_length_cm INTEGER NOT NULL,"
|
||||
"platf_length_cm INTEGET NOT NULL,"
|
||||
"freight_length_cm INTEGER NOT NULL,"
|
||||
"max_axes INTEGER NOT NULL,"
|
||||
"color_rgb INTEGER,"
|
||||
"name TEXT NOT NULL,"
|
||||
"CHECK("
|
||||
" length(name)>0 AND max_axes>=2 AND track_length_cm>0"
|
||||
" AND (platf_length_cm BETWEEN 0 AND track_length_cm)"
|
||||
" AND (freight_length_cm BETWEEN 0 AND track_length_cm)"
|
||||
"),"
|
||||
"UNIQUE(station_id, pos),"
|
||||
"UNIQUE(station_id, name),"
|
||||
"FOREIGN KEY (station_id) REFERENCES stations(id) ON UPDATE CASCADE ON DELETE CASCADE )");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE station_gates ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"station_id INTEGER NOT NULL,"
|
||||
"out_track_count INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"def_in_platf_id INTEGER,"
|
||||
"name TEXT NOT NULL,"
|
||||
"side INTEGER NOT NULL,"
|
||||
"CHECK("
|
||||
" out_track_count>0 AND (type&(1<<0) OR type&(1<<1))"
|
||||
" AND (length(name)=1 AND name BETWEEN 'A' AND 'Z')"
|
||||
")," //NOTE: see utils::GateType
|
||||
"FOREIGN KEY (station_id) REFERENCES stations(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"FOREIGN KEY(def_in_platf_id) REFERENCES station_tracks(id) ON UPDATE CASCADE ON DELETE SET NULL,"
|
||||
"UNIQUE(station_id,name) )");
|
||||
result = m_Db.execute(
|
||||
"CREATE TABLE station_gates ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"station_id INTEGER NOT NULL,"
|
||||
"out_track_count INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"def_in_platf_id INTEGER,"
|
||||
"name TEXT NOT NULL,"
|
||||
"side INTEGER NOT NULL,"
|
||||
"CHECK("
|
||||
" out_track_count>0 AND (type&(1<<0) OR type&(1<<1))"
|
||||
" AND (length(name)=1 AND name BETWEEN 'A' AND 'Z')"
|
||||
")," // NOTE: see utils::GateType
|
||||
"FOREIGN KEY (station_id) REFERENCES stations(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"FOREIGN KEY(def_in_platf_id) REFERENCES station_tracks(id) ON UPDATE CASCADE ON DELETE SET "
|
||||
"NULL,"
|
||||
"UNIQUE(station_id,name) )");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE station_gate_connections ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"track_id INTEGER NOT NULL,"
|
||||
"track_side INTEGER NOT NULL,"
|
||||
"gate_id INTEGER NOT NULL,"
|
||||
"gate_track INTEGER NOT NULL,"
|
||||
"UNIQUE(gate_id,track_id,track_side,gate_track),"
|
||||
"FOREIGN KEY (track_id) REFERENCES station_tracks(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"FOREIGN KEY (gate_id) REFERENCES station_gates(id) ON UPDATE CASCADE ON DELETE CASCADE )");
|
||||
result = m_Db.execute(
|
||||
"CREATE TABLE station_gate_connections ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"track_id INTEGER NOT NULL,"
|
||||
"track_side INTEGER NOT NULL,"
|
||||
"gate_id INTEGER NOT NULL,"
|
||||
"gate_track INTEGER NOT NULL,"
|
||||
"UNIQUE(gate_id,track_id,track_side,gate_track),"
|
||||
"FOREIGN KEY (track_id) REFERENCES station_tracks(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"FOREIGN KEY (gate_id) REFERENCES station_gates(id) ON UPDATE CASCADE ON DELETE CASCADE )");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE railway_segments ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"in_gate_id INTEGER NOT NULL,"
|
||||
"out_gate_id INTEGER NOT NULL,"
|
||||
"name TEXT,"
|
||||
"max_speed_kmh INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"distance_meters INTEGER NOT NULL,"
|
||||
"UNIQUE(in_gate_id),"
|
||||
"UNIQUE(out_gate_id),"
|
||||
"FOREIGN KEY(in_gate_id) REFERENCES station_gates(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"FOREIGN KEY(out_gate_id) REFERENCES station_gates(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"CHECK(in_gate_id<>out_gate_id AND"
|
||||
" max_speed_kmh>=10 AND"
|
||||
" distance_meters>=100 AND"
|
||||
" length(name)>0) )");
|
||||
result = m_Db.execute(
|
||||
"CREATE TABLE railway_segments ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"in_gate_id INTEGER NOT NULL,"
|
||||
"out_gate_id INTEGER NOT NULL,"
|
||||
"name TEXT,"
|
||||
"max_speed_kmh INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"distance_meters INTEGER NOT NULL,"
|
||||
"UNIQUE(in_gate_id),"
|
||||
"UNIQUE(out_gate_id),"
|
||||
"FOREIGN KEY(in_gate_id) REFERENCES station_gates(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"FOREIGN KEY(out_gate_id) REFERENCES station_gates(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"CHECK(in_gate_id<>out_gate_id AND"
|
||||
" max_speed_kmh>=10 AND"
|
||||
" distance_meters>=100 AND"
|
||||
" length(name)>0) )");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE railway_connections ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"seg_id INTEGER NOT NULL,"
|
||||
"in_track INTEGER NOT NULL,"
|
||||
"out_track INTEGER NOT NULL,"
|
||||
"UNIQUE(seg_id,in_track),"
|
||||
"UNIQUE(seg_id,out_track),"
|
||||
"FOREIGN KEY(seg_id) REFERENCES railway_segments(id) ON UPDATE CASCADE ON DELETE RESTRICT )");
|
||||
result = m_Db.execute(
|
||||
"CREATE TABLE railway_connections ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"seg_id INTEGER NOT NULL,"
|
||||
"in_track INTEGER NOT NULL,"
|
||||
"out_track INTEGER NOT NULL,"
|
||||
"UNIQUE(seg_id,in_track),"
|
||||
"UNIQUE(seg_id,out_track),"
|
||||
"FOREIGN KEY(seg_id) REFERENCES railway_segments(id) ON UPDATE CASCADE ON DELETE RESTRICT )");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE lines ("
|
||||
|
@ -345,17 +370,18 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
"start_meters INTEGER NOT NULL DEFAULT 0 )");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE line_segments ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"line_id INTEGER NOT NULL,"
|
||||
"seg_id INTEGER NOT NULL,"
|
||||
"direction INTEGER NOT NULL,"
|
||||
"pos INTEGER NOT NULL,"
|
||||
"FOREIGN KEY(line_id) REFERENCES lines(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"FOREIGN KEY(seg_id) REFERENCES railway_segments(id) ON UPDATE CASCADE ON DELETE RESTRICT,"
|
||||
"UNIQUE(line_id, seg_id)"
|
||||
"UNIQUE(line_id, pos)"
|
||||
"CHECK(pos<100) )"); //Allow up to 100 segments for each line
|
||||
result = m_Db.execute(
|
||||
"CREATE TABLE line_segments ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"line_id INTEGER NOT NULL,"
|
||||
"seg_id INTEGER NOT NULL,"
|
||||
"direction INTEGER NOT NULL,"
|
||||
"pos INTEGER NOT NULL,"
|
||||
"FOREIGN KEY(line_id) REFERENCES lines(id) ON UPDATE CASCADE ON DELETE CASCADE,"
|
||||
"FOREIGN KEY(seg_id) REFERENCES railway_segments(id) ON UPDATE CASCADE ON DELETE RESTRICT,"
|
||||
"UNIQUE(line_id, seg_id)"
|
||||
"UNIQUE(line_id, pos)"
|
||||
"CHECK(pos<100) )"); // Allow up to 100 segments for each line
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE jobshifts ("
|
||||
|
@ -363,34 +389,39 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
"name TEXT UNIQUE NOT NULL)");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE jobs ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"category INTEGER NOT NULL DEFAULT 0,"
|
||||
"shift_id INTEGER,"
|
||||
"FOREIGN KEY(shift_id) REFERENCES jobshifts(id) )");
|
||||
result = m_Db.execute(
|
||||
"CREATE TABLE jobs ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"category INTEGER NOT NULL DEFAULT 0,"
|
||||
"shift_id INTEGER,"
|
||||
"FOREIGN KEY(shift_id) REFERENCES jobshifts(id) ON UPDATE CASCADE ON DELETE RESTRICT)");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE stops ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"job_id INTEGER NOT NULL,"
|
||||
"station_id INTEGER,"
|
||||
"arrival INTEGER NOT NULL,"
|
||||
"departure INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"description TEXT,"
|
||||
result = m_Db.execute(
|
||||
"CREATE TABLE stops ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"job_id INTEGER NOT NULL,"
|
||||
"station_id INTEGER,"
|
||||
"arrival INTEGER NOT NULL,"
|
||||
"departure INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"description TEXT,"
|
||||
|
||||
"in_gate_conn INTEGER,"
|
||||
"out_gate_conn INTEGER,"
|
||||
"next_segment_conn_id INTEGER,"
|
||||
"in_gate_conn INTEGER,"
|
||||
"out_gate_conn INTEGER,"
|
||||
"next_segment_conn_id INTEGER,"
|
||||
|
||||
"CHECK(arrival<=departure),"
|
||||
"UNIQUE(job_id,arrival),"
|
||||
"UNIQUE(job_id,departure),"
|
||||
"FOREIGN KEY(job_id) REFERENCES jobs(id) ON DELETE RESTRICT ON UPDATE CASCADE,"
|
||||
"FOREIGN KEY(station_id) REFERENCES stations(id) ON DELETE RESTRICT ON UPDATE CASCADE,"
|
||||
"FOREIGN KEY(in_gate_conn) REFERENCES station_gate_connections(id) ON UPDATE CASCADE ON DELETE RESTRICT,"
|
||||
"FOREIGN KEY(out_gate_conn) REFERENCES station_gate_connections(id) ON UPDATE CASCADE ON DELETE RESTRICT,"
|
||||
"FOREIGN KEY(next_segment_conn_id) REFERENCES railway_connections(id) ON UPDATE CASCADE ON DELETE RESTRICT )");
|
||||
"CHECK(arrival<=departure),"
|
||||
"UNIQUE(job_id,arrival),"
|
||||
"UNIQUE(job_id,departure),"
|
||||
"FOREIGN KEY(job_id) REFERENCES jobs(id) ON DELETE RESTRICT ON UPDATE CASCADE,"
|
||||
"FOREIGN KEY(station_id) REFERENCES stations(id) ON DELETE RESTRICT ON UPDATE CASCADE,"
|
||||
"FOREIGN KEY(in_gate_conn) REFERENCES station_gate_connections(id) ON UPDATE CASCADE ON "
|
||||
"DELETE RESTRICT,"
|
||||
"FOREIGN KEY(out_gate_conn) REFERENCES station_gate_connections(id) ON UPDATE CASCADE ON "
|
||||
"DELETE RESTRICT,"
|
||||
"FOREIGN KEY(next_segment_conn_id) REFERENCES railway_connections(id) ON UPDATE CASCADE ON "
|
||||
"DELETE RESTRICT )");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE coupling ("
|
||||
|
@ -404,41 +435,47 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
"UNIQUE(stop_id,rs_id))");
|
||||
CHECK(result);
|
||||
|
||||
//Create also backup tables to save old stops and couplings before editing a job and restore them if user cancels the edits.
|
||||
//NOTE: the structure of the table must be the same, remember to update theese if updating stops or couplings
|
||||
// Create also backup tables to save old stops and couplings before editing a job and restore
|
||||
// them if user cancels the edits. NOTE: the structure of the table must be the same, remember
|
||||
// to update theese if updating stops or couplings
|
||||
|
||||
result = m_Db.execute("CREATE TABLE old_stops ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"job_id INTEGER NOT NULL,"
|
||||
"station_id INTEGER,"
|
||||
"arrival INTEGER NOT NULL,"
|
||||
"departure INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"description TEXT,"
|
||||
result = m_Db.execute(
|
||||
"CREATE TABLE old_stops ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"job_id INTEGER NOT NULL,"
|
||||
"station_id INTEGER,"
|
||||
"arrival INTEGER NOT NULL,"
|
||||
"departure INTEGER NOT NULL,"
|
||||
"type INTEGER NOT NULL,"
|
||||
"description TEXT,"
|
||||
|
||||
"in_gate_conn INTEGER,"
|
||||
"out_gate_conn INTEGER,"
|
||||
"next_segment_conn_id INTEGER,"
|
||||
"in_gate_conn INTEGER,"
|
||||
"out_gate_conn INTEGER,"
|
||||
"next_segment_conn_id INTEGER,"
|
||||
|
||||
"CHECK(arrival<=departure),"
|
||||
"UNIQUE(job_id,arrival),"
|
||||
"UNIQUE(job_id,departure),"
|
||||
"FOREIGN KEY(job_id) REFERENCES jobs(id) ON DELETE RESTRICT ON UPDATE CASCADE,"
|
||||
"FOREIGN KEY(station_id) REFERENCES stations(id) ON DELETE RESTRICT ON UPDATE CASCADE,"
|
||||
"FOREIGN KEY(in_gate_conn) REFERENCES station_gate_connections(id) ON UPDATE CASCADE ON DELETE RESTRICT,"
|
||||
"FOREIGN KEY(out_gate_conn) REFERENCES station_gate_connections(id) ON UPDATE CASCADE ON DELETE RESTRICT,"
|
||||
"FOREIGN KEY(next_segment_conn_id) REFERENCES railway_connections(id) ON UPDATE CASCADE ON DELETE RESTRICT )");
|
||||
"CHECK(arrival<=departure),"
|
||||
"UNIQUE(job_id,arrival),"
|
||||
"UNIQUE(job_id,departure),"
|
||||
"FOREIGN KEY(job_id) REFERENCES jobs(id) ON DELETE RESTRICT ON UPDATE CASCADE,"
|
||||
"FOREIGN KEY(station_id) REFERENCES stations(id) ON DELETE RESTRICT ON UPDATE CASCADE,"
|
||||
"FOREIGN KEY(in_gate_conn) REFERENCES station_gate_connections(id) ON UPDATE CASCADE ON "
|
||||
"DELETE RESTRICT,"
|
||||
"FOREIGN KEY(out_gate_conn) REFERENCES station_gate_connections(id) ON UPDATE CASCADE ON "
|
||||
"DELETE RESTRICT,"
|
||||
"FOREIGN KEY(next_segment_conn_id) REFERENCES railway_connections(id) ON UPDATE CASCADE ON "
|
||||
"DELETE RESTRICT )");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE old_coupling ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"stop_id INTEGER,"
|
||||
"rs_id INTEGER,"
|
||||
"operation INTEGER NOT NULL DEFAULT 0,"
|
||||
result =
|
||||
m_Db.execute("CREATE TABLE old_coupling ("
|
||||
"id INTEGER PRIMARY KEY,"
|
||||
"stop_id INTEGER,"
|
||||
"rs_id INTEGER,"
|
||||
"operation INTEGER NOT NULL DEFAULT 0,"
|
||||
|
||||
"FOREIGN KEY(stop_id) REFERENCES old_stops(id) ON DELETE CASCADE," //Old stops
|
||||
"FOREIGN KEY(rs_id) REFERENCES rs_list(id) ON DELETE RESTRICT,"
|
||||
"UNIQUE(stop_id,rs_id))");
|
||||
"FOREIGN KEY(stop_id) REFERENCES old_stops(id) ON DELETE CASCADE," // Old stops
|
||||
"FOREIGN KEY(rs_id) REFERENCES rs_list(id) ON DELETE RESTRICT,"
|
||||
"UNIQUE(stop_id,rs_id))");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE imported_rs_owners ("
|
||||
|
@ -449,7 +486,8 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
"match_existing_id INTEGER,"
|
||||
"sheet_idx INTEGER,"
|
||||
"PRIMARY KEY(id),"
|
||||
"FOREIGN KEY(match_existing_id) REFERENCES rs_owners(id) ON UPDATE RESTRICT ON DELETE RESTRICT)");
|
||||
"FOREIGN KEY(match_existing_id) REFERENCES rs_owners(id) ON UPDATE "
|
||||
"RESTRICT ON DELETE RESTRICT)");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE imported_rs_models ("
|
||||
|
@ -464,7 +502,8 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
"type INTEGER,"
|
||||
"sub_type INTEGER,"
|
||||
"PRIMARY KEY(id),"
|
||||
"FOREIGN KEY(match_existing_id) REFERENCES rs_models(id) ON UPDATE RESTRICT ON DELETE RESTRICT)");
|
||||
"FOREIGN KEY(match_existing_id) REFERENCES rs_models(id) ON UPDATE "
|
||||
"RESTRICT ON DELETE RESTRICT)");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE imported_rs_list ("
|
||||
|
@ -475,8 +514,10 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
"number INTEGER,"
|
||||
"new_number INTEGER,"
|
||||
"PRIMARY KEY(id),"
|
||||
"FOREIGN KEY(model_id) REFERENCES imported_rs_models(id) ON UPDATE RESTRICT ON DELETE RESTRICT,"
|
||||
"FOREIGN KEY(owner_id) REFERENCES imported_rs_owners(id) ON UPDATE RESTRICT ON DELETE RESTRICT)");
|
||||
"FOREIGN KEY(model_id) REFERENCES imported_rs_models(id) ON UPDATE "
|
||||
"RESTRICT ON DELETE RESTRICT,"
|
||||
"FOREIGN KEY(owner_id) REFERENCES imported_rs_owners(id) ON UPDATE "
|
||||
"RESTRICT ON DELETE RESTRICT)");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TABLE metadata ("
|
||||
|
@ -484,50 +525,56 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
"val BLOB)");
|
||||
CHECK(result);
|
||||
|
||||
//Triggers
|
||||
// Triggers
|
||||
|
||||
//Prevent multiple segments on same station gate
|
||||
result = m_Db.execute("CREATE TRIGGER multiple_gate_segments\n"
|
||||
"BEFORE INSERT ON railway_segments\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect same gate twice') FROM railway_segments WHERE in_gate_id=NEW.out_gate_id OR out_gate_id=NEW.in_gate_id;"
|
||||
"END");
|
||||
// Prevent multiple segments on same station gate
|
||||
result =
|
||||
m_Db.execute("CREATE TRIGGER multiple_gate_segments\n"
|
||||
"BEFORE INSERT ON railway_segments\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect same gate twice') FROM railway_segments "
|
||||
"WHERE in_gate_id=NEW.out_gate_id OR out_gate_id=NEW.in_gate_id;"
|
||||
"END");
|
||||
CHECK(result);
|
||||
result = m_Db.execute("CREATE TRIGGER multiple_gate_segments_update_in\n"
|
||||
"BEFORE UPDATE OF in_gate_id ON railway_segments\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect same gate twice') FROM railway_segments WHERE out_gate_id=NEW.in_gate_id;"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect same gate twice') FROM "
|
||||
"railway_segments WHERE out_gate_id=NEW.in_gate_id;"
|
||||
"END");
|
||||
CHECK(result);
|
||||
result = m_Db.execute("CREATE TRIGGER multiple_gate_segments_update_out\n"
|
||||
"BEFORE UPDATE OF out_gate_id ON railway_segments\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect same gate twice') FROM railway_segments WHERE in_gate_id=NEW.out_gate_id;"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect same gate twice') FROM "
|
||||
"railway_segments WHERE in_gate_id=NEW.out_gate_id;"
|
||||
"END");
|
||||
CHECK(result);
|
||||
|
||||
//Prevent connecting a track to a gate of a different station
|
||||
result = m_Db.execute("CREATE TRIGGER gate_conn_different_station\n"
|
||||
"BEFORE INSERT ON station_gate_connections\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect platform of a different station') FROM station_tracks t"
|
||||
" JOIN station_gates g ON g.id=NEW.gate_id"
|
||||
" WHERE t.id=NEW.track_id AND t.station_id<>g.station_id;"
|
||||
"END");
|
||||
// Prevent connecting a track to a gate of a different station
|
||||
result = m_Db.execute(
|
||||
"CREATE TRIGGER gate_conn_different_station\n"
|
||||
"BEFORE INSERT ON station_gate_connections\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect platform of a different station') FROM station_tracks t"
|
||||
" JOIN station_gates g ON g.id=NEW.gate_id"
|
||||
" WHERE t.id=NEW.track_id AND t.station_id<>g.station_id;"
|
||||
"END");
|
||||
CHECK(result);
|
||||
|
||||
result = m_Db.execute("CREATE TRIGGER gate_conn_different_station_update\n"
|
||||
"BEFORE UPDATE OF track_id,gate_id ON station_gate_connections\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect platform of a different station') FROM station_tracks t"
|
||||
" JOIN station_gates g ON g.id=NEW.gate_id"
|
||||
" WHERE t.id=NEW.track_id AND t.station_id<>g.station_id;"
|
||||
"END");
|
||||
result = m_Db.execute(
|
||||
"CREATE TRIGGER gate_conn_different_station_update\n"
|
||||
"BEFORE UPDATE OF track_id,gate_id ON station_gate_connections\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Cannot connect platform of a different station') FROM station_tracks t"
|
||||
" JOIN station_gates g ON g.id=NEW.gate_id"
|
||||
" WHERE t.id=NEW.track_id AND t.station_id<>g.station_id;"
|
||||
"END");
|
||||
CHECK(result);
|
||||
|
||||
//FIXME: Remote possibility of updating 'station_id' of track or gate.
|
||||
// FIXME: Remote possibility of updating 'station_id' of track or gate.
|
||||
|
||||
//Prevent connecting gate track out of bound
|
||||
// Prevent connecting gate track out of bound
|
||||
result = m_Db.execute("CREATE TRIGGER gate_conn_gate_track_bound\n"
|
||||
"BEFORE INSERT ON station_gate_connections\n"
|
||||
"BEGIN\n"
|
||||
|
@ -547,18 +594,20 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
result = m_Db.execute("CREATE TRIGGER gate_out_track_bound_update\n"
|
||||
"BEFORE UPDATE OF out_track_count ON station_gates\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Cannot remove gate tracks. Platforms connected.') FROM station_gate_connections c"
|
||||
"SELECT RAISE(ABORT, 'Cannot remove gate tracks. Platforms connected.') "
|
||||
"FROM station_gate_connections c"
|
||||
" WHERE c.gate_id=NEW.id AND NEW.out_track_count<=c.gate_track;"
|
||||
"END");
|
||||
CHECK(result);
|
||||
|
||||
//Prevent setting gate default track to a track which is not connected to it
|
||||
// Prevent setting gate default track to a track which is not connected to it
|
||||
result = m_Db.execute("CREATE TRIGGER gate_def_platf_not_connected\n"
|
||||
"BEFORE INSERT ON station_gates\n"
|
||||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Platform not connected to this gate') WHERE"
|
||||
" NEW.def_in_platf_id NOT NULL AND NOT EXISTS ("
|
||||
" SELECT 1 FROM station_gate_connections WHERE track_id=NEW.def_in_platf_id AND gate_id=NEW.id"
|
||||
" SELECT 1 FROM station_gate_connections WHERE "
|
||||
"track_id=NEW.def_in_platf_id AND gate_id=NEW.id"
|
||||
");"
|
||||
"END");
|
||||
CHECK(result);
|
||||
|
@ -568,13 +617,13 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
"BEGIN\n"
|
||||
"SELECT RAISE(ABORT, 'Platform not connected to this gate') WHERE"
|
||||
" NEW.def_in_platf_id NOT NULL AND NOT EXISTS ("
|
||||
" SELECT 1 FROM station_gate_connections WHERE track_id=NEW.def_in_platf_id AND gate_id=NEW.id"
|
||||
" SELECT 1 FROM station_gate_connections WHERE "
|
||||
"track_id=NEW.def_in_platf_id AND gate_id=NEW.id"
|
||||
");"
|
||||
"END");
|
||||
CHECK(result);
|
||||
|
||||
//FIXME: if setting default gate track but then delete track connection -> invalid state
|
||||
|
||||
// FIXME: if setting default gate track but then delete track connection -> invalid state
|
||||
|
||||
#undef CHECK
|
||||
|
||||
|
@ -589,25 +638,25 @@ DB_Error MeetingSession::createNewDB(const QString& file)
|
|||
* Theese tables are used during RS importation and are cleared when the process
|
||||
* completes or gets canceled by the user
|
||||
* If they are not empty it might be because the application crashed before clearing theese tables
|
||||
*/
|
||||
*/
|
||||
bool MeetingSession::checkImportRSTablesEmpty()
|
||||
{
|
||||
query q(m_Db, "SELECT COUNT(1) FROM imported_rs_list");
|
||||
q.step();
|
||||
int count = q.getRows().get<int>(0);
|
||||
if(count)
|
||||
if (count)
|
||||
return false;
|
||||
|
||||
q.prepare("SELECT COUNT(1) FROM imported_rs_models");
|
||||
q.step();
|
||||
count = q.getRows().get<int>(0);
|
||||
if(count)
|
||||
if (count)
|
||||
return false;
|
||||
|
||||
q.prepare("SELECT COUNT(1) FROM imported_rs_owners");
|
||||
q.step();
|
||||
count = q.getRows().get<int>(0);
|
||||
if(count)
|
||||
if (count)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
@ -615,15 +664,15 @@ bool MeetingSession::checkImportRSTablesEmpty()
|
|||
bool MeetingSession::clearImportRSTables()
|
||||
{
|
||||
command cmd(m_Db, "DELETE FROM imported_rs_list");
|
||||
if(cmd.execute() != SQLITE_OK)
|
||||
if (cmd.execute() != SQLITE_OK)
|
||||
return false;
|
||||
|
||||
cmd.prepare("DELETE FROM imported_rs_models");
|
||||
if(cmd.execute() != SQLITE_OK)
|
||||
if (cmd.execute() != SQLITE_OK)
|
||||
return false;
|
||||
|
||||
cmd.prepare("DELETE FROM imported_rs_owners");
|
||||
if(cmd.execute() != SQLITE_OK)
|
||||
if (cmd.execute() != SQLITE_OK)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
|
@ -631,15 +680,15 @@ bool MeetingSession::clearImportRSTables()
|
|||
|
||||
bool MeetingSession::setSavepoint(const QString &pointname)
|
||||
{
|
||||
if(!m_Db.db())
|
||||
if (!m_Db.db())
|
||||
return false;
|
||||
|
||||
if(savepointList.contains(pointname))
|
||||
if (savepointList.contains(pointname))
|
||||
return true;
|
||||
|
||||
QString sql = QStringLiteral("SAVEPOINT %1;");
|
||||
|
||||
if(m_Db.execute(sql.arg(pointname).toUtf8()) != SQLITE_OK)
|
||||
if (m_Db.execute(sql.arg(pointname).toUtf8()) != SQLITE_OK)
|
||||
{
|
||||
qDebug() << m_Db.error_msg();
|
||||
return false;
|
||||
|
@ -651,15 +700,15 @@ bool MeetingSession::setSavepoint(const QString &pointname)
|
|||
|
||||
bool MeetingSession::releaseSavepoint(const QString &pointname)
|
||||
{
|
||||
if(!m_Db.db())
|
||||
if (!m_Db.db())
|
||||
return false;
|
||||
|
||||
if(!savepointList.contains(pointname))
|
||||
if (!savepointList.contains(pointname))
|
||||
return true;
|
||||
|
||||
QString sql = QStringLiteral("RELEASE %1;");
|
||||
|
||||
if(m_Db.execute(sql.arg(pointname).toUtf8()) != SQLITE_OK)
|
||||
if (m_Db.execute(sql.arg(pointname).toUtf8()) != SQLITE_OK)
|
||||
{
|
||||
qDebug() << m_Db.error_msg();
|
||||
return false;
|
||||
|
@ -673,15 +722,15 @@ bool MeetingSession::releaseSavepoint(const QString &pointname)
|
|||
|
||||
bool MeetingSession::revertToSavepoint(const QString &pointname)
|
||||
{
|
||||
if(!m_Db.db())
|
||||
if (!m_Db.db())
|
||||
return false;
|
||||
|
||||
if(!savepointList.contains(pointname))
|
||||
if (!savepointList.contains(pointname))
|
||||
return false;
|
||||
|
||||
QString sql = QStringLiteral("ROLLBACK TO SAVEPOINT %1;");
|
||||
|
||||
if(m_Db.execute(sql.arg(pointname).toUtf8()) != SQLITE_OK)
|
||||
if (m_Db.execute(sql.arg(pointname).toUtf8()) != SQLITE_OK)
|
||||
{
|
||||
qDebug() << m_Db.error_msg();
|
||||
return false;
|
||||
|
@ -695,17 +744,17 @@ bool MeetingSession::revertToSavepoint(const QString &pointname)
|
|||
|
||||
bool MeetingSession::releaseAllSavepoints()
|
||||
{
|
||||
if(!m_Db.db())
|
||||
if (!m_Db.db())
|
||||
return false;
|
||||
|
||||
for(const QString& point : qAsConst(savepointList))
|
||||
for (const QString &point : qAsConst(savepointList))
|
||||
{
|
||||
if(!releaseSavepoint(point))
|
||||
if (!releaseSavepoint(point))
|
||||
return false;
|
||||
}
|
||||
|
||||
// When still in a transaction, commit that too
|
||||
if(sqlite3_get_autocommit(m_Db.db()) == 0)
|
||||
if (sqlite3_get_autocommit(m_Db.db()) == 0)
|
||||
m_Db.execute("COMMIT;");
|
||||
|
||||
return true;
|
||||
|
@ -713,9 +762,9 @@ bool MeetingSession::releaseAllSavepoints()
|
|||
|
||||
bool MeetingSession::revertAll()
|
||||
{
|
||||
for(const QString& point : qAsConst(savepointList))
|
||||
for (const QString &point : qAsConst(savepointList))
|
||||
{
|
||||
if(!revertToSavepoint(point))
|
||||
if (!revertToSavepoint(point))
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -723,59 +772,59 @@ bool MeetingSession::revertAll()
|
|||
|
||||
QColor MeetingSession::colorForCat(JobCategory cat)
|
||||
{
|
||||
QColor col = settings.getCategoryColor(int(cat)); //TODO: maybe session-specific
|
||||
if(col.isValid())
|
||||
QColor col = settings.getCategoryColor(int(cat)); // TODO: maybe session-specific
|
||||
if (col.isValid())
|
||||
return col;
|
||||
return QColor(Qt::gray); //Error
|
||||
return QColor(Qt::gray); // Error
|
||||
}
|
||||
|
||||
void MeetingSession::locateAppdata()
|
||||
{
|
||||
appDataPath = QStringLiteral("%1/%2/%3")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation))
|
||||
.arg(AppCompany, AppProductShort);
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation),
|
||||
AppCompany, AppProductShort);
|
||||
appDataPath = QDir::cleanPath(appDataPath);
|
||||
qDebug() << appDataPath;
|
||||
}
|
||||
|
||||
void MeetingSession::setSheetExportTranslator(QTranslator *translator, const QLocale &loc)
|
||||
{
|
||||
//NOTE: if Sheet Language is of Application Language then set a nullptr translator
|
||||
//If translator is valid use it
|
||||
//If translator is nullptr and language is default English locale, use hardcoded strings
|
||||
//If translator is nullptr and language is different from English, use default translations
|
||||
// NOTE: if Sheet Language is of Application Language then set a nullptr translator
|
||||
// If translator is valid use it
|
||||
// If translator is nullptr and language is default English locale, use hardcoded strings
|
||||
// If translator is nullptr and language is different from English, use default translations
|
||||
|
||||
if(sheetExportTranslator && sheetExportTranslator != translator)
|
||||
if (sheetExportTranslator && sheetExportTranslator != translator)
|
||||
delete sheetExportTranslator;
|
||||
sheetExportTranslator = translator;
|
||||
sheetExportLocale = loc;
|
||||
sheetExportLocale = loc;
|
||||
}
|
||||
|
||||
void MeetingSession::loadSettings(const QString& settings_file)
|
||||
void MeetingSession::loadSettings(const QString &settings_file)
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
if(settings_file.isEmpty())
|
||||
if (settings_file.isEmpty())
|
||||
settings.loadSettings(appDataPath + QStringLiteral("/mrtp_settings.ini"));
|
||||
else
|
||||
settings.loadSettings(settings_file);
|
||||
|
||||
hourOffset = settings.getHourOffset();
|
||||
stationOffset = settings.getStationOffset();
|
||||
horizOffset = settings.getHorizontalOffset();
|
||||
vertOffset = settings.getVerticalOffset();
|
||||
platformOffset = settings.getPlatformOffset();
|
||||
hourOffset = settings.getHourOffset();
|
||||
stationOffset = settings.getStationOffset();
|
||||
horizOffset = settings.getHorizontalOffset();
|
||||
vertOffset = settings.getVerticalOffset();
|
||||
platformOffset = settings.getPlatformOffset();
|
||||
|
||||
jobLineWidth = settings.getJobLineWidth();
|
||||
jobLineWidth = settings.getJobLineWidth();
|
||||
|
||||
originalAppLocale = settings.getLanguage();
|
||||
|
||||
//Default initialize to same value of application language
|
||||
// Default initialize to same value of application language
|
||||
setSheetExportTranslator(nullptr, originalAppLocale);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
BackgroundManager* MeetingSession::getBackgroundManager() const
|
||||
BackgroundManager *MeetingSession::getBackgroundManager() const
|
||||
{
|
||||
return backgroundManager.get();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MEETINGSESSION_H
|
||||
#define MEETINGSESSION_H
|
||||
|
||||
|
@ -14,7 +33,6 @@ using namespace sqlite3pp;
|
|||
|
||||
#include <settings/appsettings.h>
|
||||
|
||||
|
||||
class ViewManager;
|
||||
class MetaDataManager;
|
||||
|
||||
|
@ -46,53 +64,60 @@ class MeetingSession : public QObject
|
|||
Q_OBJECT
|
||||
|
||||
private:
|
||||
static MeetingSession* session;
|
||||
static MeetingSession *session;
|
||||
|
||||
public:
|
||||
MeetingSession();
|
||||
~MeetingSession();
|
||||
|
||||
static MeetingSession* Get();
|
||||
static MeetingSession *Get();
|
||||
|
||||
inline ViewManager* getViewManager() { return viewManager.get(); }
|
||||
inline ViewManager *getViewManager()
|
||||
{
|
||||
return viewManager.get();
|
||||
}
|
||||
|
||||
inline MetaDataManager* getMetaDataManager() { return metaDataMgr.get(); }
|
||||
inline MetaDataManager *getMetaDataManager()
|
||||
{
|
||||
return metaDataMgr.get();
|
||||
}
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
BackgroundManager *getBackgroundManager() const;
|
||||
#endif
|
||||
|
||||
signals:
|
||||
//Shifts
|
||||
// Shifts
|
||||
void shiftAdded(db_id shiftId);
|
||||
void shiftRemoved(db_id shiftId);
|
||||
void shiftNameChanged(db_id shiftId);
|
||||
|
||||
//A job was added/removed/modified belonging to this shift
|
||||
// A job was added/removed/modified belonging to this shift
|
||||
void shiftJobsChanged(db_id shiftId, db_id jobId);
|
||||
|
||||
//Rollingstock
|
||||
// Rollingstock
|
||||
void rollingstockRemoved(db_id rsId);
|
||||
void rollingStockPlanChanged(QSet<db_id> rsIds);
|
||||
void rollingStockModified(db_id rsId);
|
||||
|
||||
//Jobs
|
||||
// Jobs
|
||||
void jobAdded(db_id jobId);
|
||||
void jobChanged(db_id jobId, db_id oldJobId); //Updated id/category/stops
|
||||
void jobChanged(db_id jobId, db_id oldJobId); // Updated id/category/stops
|
||||
void jobRemoved(db_id jobId);
|
||||
|
||||
//Stations
|
||||
// Stations
|
||||
void stationNameChanged(db_id stationId);
|
||||
//TODO: separate job stop changes (time plan) from track changes (track plan)
|
||||
void stationPlanChanged(const QSet<db_id>& stationIds);
|
||||
void stationJobsPlanChanged(const QSet<db_id> &stationIds);
|
||||
void stationTrackPlanChanged(const QSet<db_id> &stationIds);
|
||||
void stationRemoved(db_id stationId);
|
||||
|
||||
//Segments
|
||||
// Segments
|
||||
void segmentAdded(db_id segmentId);
|
||||
void segmentNameChanged(db_id segmentId);
|
||||
void segmentStationsChanged(db_id segmentId);
|
||||
void segmentRemoved(db_id segmentId);
|
||||
|
||||
//Lines
|
||||
// Lines
|
||||
void lineAdded(db_id lineId);
|
||||
void lineNameChanged(db_id lineId);
|
||||
void lineSegmentsChanged(db_id lineId);
|
||||
|
@ -107,45 +132,48 @@ private:
|
|||
std::unique_ptr<BackgroundManager> backgroundManager;
|
||||
#endif
|
||||
|
||||
//Settings TODO: remove
|
||||
// Settings TODO: remove
|
||||
public:
|
||||
void loadSettings(const QString &settings_file);
|
||||
|
||||
MRTPSettings settings;
|
||||
|
||||
int hourOffset;
|
||||
int stationOffset;
|
||||
qreal platformOffset;
|
||||
int hourOffset;
|
||||
int stationOffset;
|
||||
qreal platformOffset;
|
||||
|
||||
int horizOffset;
|
||||
int vertOffset;
|
||||
int horizOffset;
|
||||
int vertOffset;
|
||||
|
||||
int jobLineWidth;
|
||||
|
||||
//Database
|
||||
// Database
|
||||
public:
|
||||
database m_Db;
|
||||
|
||||
//Job Categories:
|
||||
// Job Categories:
|
||||
public:
|
||||
QColor colorForCat(JobCategory cat);
|
||||
|
||||
//Savepoints TODO: seem unused
|
||||
// Savepoints TODO: seem unused
|
||||
public:
|
||||
inline bool getDBDirty() { return !savepointList.isEmpty(); }
|
||||
inline bool getDBDirty()
|
||||
{
|
||||
return !savepointList.isEmpty();
|
||||
}
|
||||
|
||||
bool setSavepoint(const QString& pointname = "RESTOREPOINT");
|
||||
bool releaseSavepoint(const QString& pointname = "RESTOREPOINT");
|
||||
bool revertToSavepoint(const QString& pointname = "RESTOREPOINT");
|
||||
bool setSavepoint(const QString &pointname = "RESTOREPOINT");
|
||||
bool releaseSavepoint(const QString &pointname = "RESTOREPOINT");
|
||||
bool revertToSavepoint(const QString &pointname = "RESTOREPOINT");
|
||||
bool releaseAllSavepoints();
|
||||
bool revertAll();
|
||||
|
||||
QStringList savepointList;
|
||||
|
||||
//DB
|
||||
// DB
|
||||
public:
|
||||
DB_Error createNewDB(const QString &file);
|
||||
DB_Error openDB(const QString& str, bool ignoreVersion);
|
||||
DB_Error openDB(const QString &str, bool ignoreVersion);
|
||||
DB_Error closeDB();
|
||||
|
||||
bool checkImportRSTablesEmpty();
|
||||
|
@ -153,7 +181,7 @@ public:
|
|||
|
||||
QString fileName;
|
||||
|
||||
//AppData
|
||||
// AppData
|
||||
public:
|
||||
static void locateAppdata();
|
||||
static QString appDataPath;
|
||||
|
@ -209,14 +237,25 @@ public:
|
|||
*/
|
||||
static const QLocale embeddedLocale;
|
||||
|
||||
void setSheetExportTranslator(QTranslator *translator, const QLocale& loc);
|
||||
void setSheetExportTranslator(QTranslator *translator, const QLocale &loc);
|
||||
|
||||
inline QTranslator *getSheetExportTranslator() const { return sheetExportTranslator; }
|
||||
inline QLocale getSheetExportLocale() const { return sheetExportLocale; }
|
||||
inline QLocale getAppLanguage() const { return originalAppLocale; }
|
||||
inline QTranslator *getSheetExportTranslator() const
|
||||
{
|
||||
return sheetExportTranslator;
|
||||
}
|
||||
|
||||
inline QLocale getSheetExportLocale() const
|
||||
{
|
||||
return sheetExportLocale;
|
||||
}
|
||||
|
||||
inline QLocale getAppLanguage() const
|
||||
{
|
||||
return originalAppLocale;
|
||||
}
|
||||
};
|
||||
|
||||
#define Session MeetingSession::Get()
|
||||
#define Session MeetingSession::Get()
|
||||
|
||||
#define AppSettings Session->settings
|
||||
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
|
||||
backgroundmanager/backgroundmanager.h
|
||||
backgroundmanager/backgroundmanager.cpp
|
||||
|
||||
backgroundmanager/backgroundresultpanel.cpp
|
||||
backgroundmanager/backgroundresultpanel.h
|
||||
|
||||
backgroundmanager/backgroundresultwidget.cpp
|
||||
backgroundmanager/backgroundresultwidget.h
|
||||
|
||||
|
||||
backgroundmanager/ibackgroundchecker.cpp
|
||||
backgroundmanager/ibackgroundchecker.h
|
||||
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
|
|
@ -1,50 +1,91 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backgroundmanager.h"
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
# include "backgroundmanager/ibackgroundchecker.h"
|
||||
|
||||
#include "app/session.h"
|
||||
|
||||
#include <QThreadPool>
|
||||
#include "rollingstock/rs_checker/rscheckermanager.h"
|
||||
|
||||
#include <QSet>
|
||||
# include <QThreadPool>
|
||||
# include <QSet>
|
||||
|
||||
BackgroundManager::BackgroundManager(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
rsChecker = new RsCheckerManager(this);
|
||||
#endif
|
||||
}
|
||||
|
||||
BackgroundManager::~BackgroundManager()
|
||||
{
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
delete rsChecker;
|
||||
rsChecker = nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void BackgroundManager::handleSessionLoaded()
|
||||
{
|
||||
for (IBackgroundChecker *mgr : qAsConst(checkers))
|
||||
mgr->startWorker();
|
||||
}
|
||||
|
||||
void BackgroundManager::abortAllTasks()
|
||||
{
|
||||
emit abortTrivialTasks();
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
rsChecker->abortTasks();
|
||||
#endif
|
||||
for (IBackgroundChecker *mgr : qAsConst(checkers))
|
||||
mgr->abortTasks();
|
||||
}
|
||||
|
||||
bool BackgroundManager::isRunning()
|
||||
{
|
||||
bool running = QThreadPool::globalInstance()->activeThreadCount() > 0;
|
||||
if(running)
|
||||
if (running)
|
||||
return true;
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
running |= rsChecker->isRunning();
|
||||
#endif
|
||||
for (IBackgroundChecker *mgr : qAsConst(checkers))
|
||||
{
|
||||
if (mgr->isRunning())
|
||||
return true;
|
||||
}
|
||||
|
||||
return running;
|
||||
return false;
|
||||
}
|
||||
|
||||
void BackgroundManager::addChecker(IBackgroundChecker *mgr)
|
||||
{
|
||||
checkers.append(mgr);
|
||||
|
||||
connect(mgr, &IBackgroundChecker::destroyed, this,
|
||||
[this](QObject *self) { removeChecker(static_cast<IBackgroundChecker *>(self)); });
|
||||
|
||||
emit checkerAdded(mgr);
|
||||
}
|
||||
|
||||
void BackgroundManager::removeChecker(IBackgroundChecker *mgr)
|
||||
{
|
||||
emit checkerRemoved(mgr);
|
||||
disconnect(mgr, nullptr, this, nullptr);
|
||||
checkers.removeOne(mgr);
|
||||
}
|
||||
|
||||
void BackgroundManager::clearResults()
|
||||
{
|
||||
for (IBackgroundChecker *mgr : qAsConst(checkers))
|
||||
{
|
||||
mgr->clearModel();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
|
|
@ -1,15 +1,33 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKGROUNDMANAGER_H
|
||||
#define BACKGROUNDMANAGER_H
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#include <QObject>
|
||||
# include <QObject>
|
||||
# include <QVector>
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
class RsCheckerManager;
|
||||
#endif
|
||||
class IBackgroundChecker;
|
||||
|
||||
//TODO: show a progress bar for all task like Qt Creator does
|
||||
// TODO: show a progress bar for all task like Qt Creator does
|
||||
class BackgroundManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -17,12 +35,14 @@ public:
|
|||
explicit BackgroundManager(QObject *parent = nullptr);
|
||||
~BackgroundManager() override;
|
||||
|
||||
void handleSessionLoaded();
|
||||
void abortAllTasks();
|
||||
bool isRunning();
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
inline RsCheckerManager *getRsChecker() const { return rsChecker; };
|
||||
#endif // ENABLE_RS_CHECKER
|
||||
void addChecker(IBackgroundChecker *mgr);
|
||||
void removeChecker(IBackgroundChecker *mgr);
|
||||
|
||||
void clearResults();
|
||||
|
||||
signals:
|
||||
/* abortTrivialTasks() signal
|
||||
|
@ -39,10 +59,12 @@ signals:
|
|||
*/
|
||||
void abortTrivialTasks();
|
||||
|
||||
void checkerAdded(IBackgroundChecker *mgr);
|
||||
void checkerRemoved(IBackgroundChecker *mgr);
|
||||
|
||||
private:
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
RsCheckerManager *rsChecker;
|
||||
#endif
|
||||
friend class BackgroundResultPanel;
|
||||
QVector<IBackgroundChecker *> checkers;
|
||||
};
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
# include "backgroundresultpanel.h"
|
||||
|
||||
# include "backgroundmanager/ibackgroundchecker.h"
|
||||
# include "backgroundresultwidget.h"
|
||||
|
||||
# include "app/session.h"
|
||||
# include "backgroundmanager.h"
|
||||
|
||||
BackgroundResultPanel::BackgroundResultPanel(QWidget *parent) :
|
||||
QTabWidget(parent)
|
||||
{
|
||||
auto bkMgr = Session->getBackgroundManager();
|
||||
connect(bkMgr, &BackgroundManager::checkerAdded, this, &BackgroundResultPanel::addChecker);
|
||||
connect(bkMgr, &BackgroundManager::checkerRemoved, this, &BackgroundResultPanel::removeChecker);
|
||||
|
||||
for (auto mgr : bkMgr->checkers)
|
||||
addChecker(mgr);
|
||||
}
|
||||
|
||||
void BackgroundResultPanel::addChecker(IBackgroundChecker *mgr)
|
||||
{
|
||||
BackgroundResultWidget *w = new BackgroundResultWidget(mgr, this);
|
||||
addTab(w, mgr->getName());
|
||||
}
|
||||
|
||||
void BackgroundResultPanel::removeChecker(IBackgroundChecker *mgr)
|
||||
{
|
||||
for (int i = 0; i < count(); i++)
|
||||
{
|
||||
BackgroundResultWidget *w = static_cast<BackgroundResultWidget *>(widget(i));
|
||||
if (w->mgr == mgr)
|
||||
{
|
||||
removeTab(i);
|
||||
delete w;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKGROUNDRESULTPANEL_H
|
||||
#define BACKGROUNDRESULTPANEL_H
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
# include <QTabWidget>
|
||||
|
||||
class IBackgroundChecker;
|
||||
|
||||
class BackgroundResultPanel : public QTabWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BackgroundResultPanel(QWidget *parent = nullptr);
|
||||
|
||||
private slots:
|
||||
void addChecker(IBackgroundChecker *mgr);
|
||||
void removeChecker(IBackgroundChecker *mgr);
|
||||
};
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#endif // BACKGROUNDRESULTPANEL_H
|
|
@ -0,0 +1,132 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
# include "ibackgroundchecker.h"
|
||||
# include "backgroundresultwidget.h"
|
||||
|
||||
# include <QTreeView>
|
||||
# include <QHeaderView>
|
||||
# include <QProgressBar>
|
||||
# include <QPushButton>
|
||||
|
||||
# include <QGridLayout>
|
||||
|
||||
# include <QMenu>
|
||||
# include <QAction>
|
||||
|
||||
# include <QTimerEvent>
|
||||
|
||||
BackgroundResultWidget::BackgroundResultWidget(IBackgroundChecker *mgr_, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
mgr(mgr_),
|
||||
timerId(0)
|
||||
{
|
||||
view = new QTreeView;
|
||||
view->setUniformRowHeights(true);
|
||||
view->setSelectionBehavior(QTreeView::SelectRows);
|
||||
|
||||
progressBar = new QProgressBar;
|
||||
startBut = new QPushButton(tr("Start"));
|
||||
stopBut = new QPushButton(tr("Stop"));
|
||||
|
||||
startBut->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
stopBut->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
|
||||
view->setModel(mgr_->getModel());
|
||||
view->header()->setStretchLastSection(true);
|
||||
view->setSelectionBehavior(QTreeView::SelectRows);
|
||||
|
||||
QGridLayout *grid = new QGridLayout(this);
|
||||
grid->addWidget(view, 0, 0, 1, 3);
|
||||
grid->addWidget(startBut, 1, 0, 1, 1);
|
||||
grid->addWidget(stopBut, 1, 1, 1, 1);
|
||||
grid->addWidget(progressBar, 1, 2, 1, 1);
|
||||
|
||||
connect(mgr, &IBackgroundChecker::progress, this, &BackgroundResultWidget::onTaskProgress);
|
||||
connect(mgr, &IBackgroundChecker::taskFinished, this, &BackgroundResultWidget::taskFinished);
|
||||
|
||||
connect(startBut, &QPushButton::clicked, this, &BackgroundResultWidget::startTask);
|
||||
connect(stopBut, &QPushButton::clicked, this, &BackgroundResultWidget::stopTask);
|
||||
|
||||
view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(view, &QTreeView::customContextMenuRequested, this,
|
||||
&BackgroundResultWidget::showContextMenu);
|
||||
|
||||
setWindowTitle(tr("Error Results"));
|
||||
|
||||
progressBar->hide();
|
||||
}
|
||||
|
||||
void BackgroundResultWidget::startTask()
|
||||
{
|
||||
progressBar->setValue(0);
|
||||
|
||||
if (mgr->startWorker())
|
||||
{
|
||||
if (timerId)
|
||||
{
|
||||
killTimer(timerId); // Stop progressBar from hiding in 1 second
|
||||
timerId = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundResultWidget::stopTask()
|
||||
{
|
||||
mgr->abortTasks();
|
||||
}
|
||||
|
||||
void BackgroundResultWidget::onTaskProgress(int val, int max)
|
||||
{
|
||||
progressBar->setMaximum(max);
|
||||
progressBar->setValue(val);
|
||||
progressBar->show();
|
||||
}
|
||||
|
||||
void BackgroundResultWidget::taskFinished()
|
||||
{
|
||||
progressBar->setValue(progressBar->maximum());
|
||||
|
||||
if (timerId)
|
||||
killTimer(timerId);
|
||||
timerId = startTimer(1000); // Hide progressBar after 1 second
|
||||
}
|
||||
|
||||
void BackgroundResultWidget::timerEvent(QTimerEvent *e)
|
||||
{
|
||||
if (e->timerId() == timerId)
|
||||
{
|
||||
killTimer(timerId);
|
||||
timerId = 0;
|
||||
progressBar->hide();
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundResultWidget::showContextMenu(const QPoint &pos)
|
||||
{
|
||||
QModelIndex idx = view->indexAt(pos);
|
||||
if (!idx.isValid())
|
||||
return;
|
||||
|
||||
mgr->showContextMenu(this, view->viewport()->mapToGlobal(pos), idx);
|
||||
}
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKGROUNDRESULTWIDGET_H
|
||||
#define BACKGROUNDRESULTWIDGET_H
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
# include <QWidget>
|
||||
|
||||
class QTreeView;
|
||||
class QProgressBar;
|
||||
class QPushButton;
|
||||
|
||||
class IBackgroundChecker;
|
||||
|
||||
class BackgroundResultWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit BackgroundResultWidget(IBackgroundChecker *mgr_, QWidget *parent = nullptr);
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *e) override;
|
||||
|
||||
private slots:
|
||||
void startTask();
|
||||
void stopTask();
|
||||
void onTaskProgress(int val, int max);
|
||||
void taskFinished();
|
||||
void showContextMenu(const QPoint &pos);
|
||||
|
||||
private:
|
||||
friend class BackgroundResultPanel;
|
||||
|
||||
IBackgroundChecker *mgr;
|
||||
QTreeView *view;
|
||||
QProgressBar *progressBar;
|
||||
QPushButton *startBut;
|
||||
QPushButton *stopBut;
|
||||
int timerId;
|
||||
};
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#endif // BACKGROUNDRESULTWIDGET_H
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
# include "ibackgroundchecker.h"
|
||||
|
||||
# include <QThreadPool>
|
||||
# include "utils/thread/iquittabletask.h"
|
||||
# include "utils/thread/taskprogressevent.h"
|
||||
|
||||
# include "sqlite3pp/sqlite3pp.h"
|
||||
|
||||
IBackgroundChecker::IBackgroundChecker(sqlite3pp::database &db, QObject *parent) :
|
||||
QObject(parent),
|
||||
mDb(db)
|
||||
{
|
||||
}
|
||||
|
||||
bool IBackgroundChecker::event(QEvent *e)
|
||||
{
|
||||
if (e->type() == TaskProgressEvent::_Type)
|
||||
{
|
||||
e->setAccepted(true);
|
||||
|
||||
TaskProgressEvent *ev = static_cast<TaskProgressEvent *>(e);
|
||||
emit progress(ev->progress, ev->progressMax);
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (e->type() == eventType)
|
||||
{
|
||||
e->setAccepted(true);
|
||||
|
||||
GenericTaskEvent *ev = static_cast<GenericTaskEvent *>(e);
|
||||
if (m_mainWorker && ev->task == m_mainWorker)
|
||||
{
|
||||
if (!m_mainWorker->wasStopped())
|
||||
{
|
||||
setErrors(ev, false);
|
||||
}
|
||||
|
||||
delete m_mainWorker;
|
||||
m_mainWorker = nullptr;
|
||||
|
||||
emit taskFinished();
|
||||
}
|
||||
else
|
||||
{
|
||||
int idx = m_workers.indexOf(ev->task);
|
||||
if (idx != -1)
|
||||
{
|
||||
m_workers.removeAt(idx);
|
||||
if (!ev->task->wasStopped())
|
||||
setErrors(ev, true);
|
||||
|
||||
delete ev->task;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return QObject::event(e);
|
||||
}
|
||||
|
||||
bool IBackgroundChecker::startWorker()
|
||||
{
|
||||
if (m_mainWorker)
|
||||
return false;
|
||||
|
||||
if (!mDb.db())
|
||||
return false;
|
||||
|
||||
m_mainWorker = createMainWorker();
|
||||
|
||||
QThreadPool::globalInstance()->start(m_mainWorker);
|
||||
|
||||
for (auto task = m_workers.begin(); task != m_workers.end(); task++)
|
||||
{
|
||||
if (QThreadPool::globalInstance()->tryTake(*task))
|
||||
{
|
||||
IQuittableTask *ptr = *task;
|
||||
m_workers.erase(task);
|
||||
delete ptr;
|
||||
}
|
||||
else
|
||||
{
|
||||
(*task)->stop();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void IBackgroundChecker::abortTasks()
|
||||
{
|
||||
if (m_mainWorker)
|
||||
{
|
||||
m_mainWorker->stop();
|
||||
}
|
||||
|
||||
for (IQuittableTask *task : qAsConst(m_workers))
|
||||
{
|
||||
task->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void IBackgroundChecker::sessionLoadedHandler()
|
||||
{
|
||||
// no-op
|
||||
}
|
||||
|
||||
void IBackgroundChecker::addSubTask(IQuittableTask *task)
|
||||
{
|
||||
m_workers.append(task);
|
||||
QThreadPool::globalInstance()->start(task);
|
||||
}
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IBACKGROUNDCHECKER_H
|
||||
#define IBACKGROUNDCHECKER_H
|
||||
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
# include <QObject>
|
||||
# include <QVector>
|
||||
|
||||
class QAbstractItemModel;
|
||||
class QModelIndex;
|
||||
class QWidget;
|
||||
class QPoint;
|
||||
|
||||
class IQuittableTask;
|
||||
|
||||
namespace sqlite3pp {
|
||||
class database;
|
||||
}
|
||||
|
||||
class IBackgroundChecker : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
IBackgroundChecker(sqlite3pp::database &db, QObject *parent = nullptr);
|
||||
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
bool startWorker();
|
||||
void abortTasks();
|
||||
|
||||
inline bool isRunning() const
|
||||
{
|
||||
return m_mainWorker || m_workers.size() > 0;
|
||||
}
|
||||
|
||||
inline QAbstractItemModel *getModel() const
|
||||
{
|
||||
return errorsModel;
|
||||
};
|
||||
|
||||
virtual QString getName() const = 0;
|
||||
virtual void clearModel() = 0;
|
||||
virtual void showContextMenu(QWidget *panel, const QPoint &globalPos,
|
||||
const QModelIndex &idx) const = 0;
|
||||
|
||||
virtual void sessionLoadedHandler();
|
||||
|
||||
signals:
|
||||
void progress(int val, int max);
|
||||
void taskFinished();
|
||||
|
||||
protected:
|
||||
void addSubTask(IQuittableTask *task);
|
||||
|
||||
virtual IQuittableTask *createMainWorker() = 0;
|
||||
virtual void setErrors(QEvent *e, bool merge) = 0;
|
||||
|
||||
protected:
|
||||
sqlite3pp::database &mDb;
|
||||
QAbstractItemModel *errorsModel = nullptr;
|
||||
int eventType = 0;
|
||||
|
||||
private:
|
||||
IQuittableTask *m_mainWorker = nullptr;
|
||||
QVector<IQuittableTask *> m_workers;
|
||||
};
|
||||
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
#endif // IBACKGROUNDCHECKER_H
|
|
@ -1,11 +1,29 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "imagemetadata.h"
|
||||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
namespace ImageMetaData
|
||||
{
|
||||
namespace ImageMetaData {
|
||||
|
||||
constexpr char sql_get_key_id[] = "SELECT rowid FROM metadata WHERE name=? AND val NOT NULL";
|
||||
|
||||
|
@ -16,7 +34,6 @@ ImageBlobDevice::ImageBlobDevice(sqlite3 *db, QObject *parent) :
|
|||
mDb(db),
|
||||
mBlob(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ImageBlobDevice::~ImageBlobDevice()
|
||||
|
@ -26,40 +43,40 @@ ImageBlobDevice::~ImageBlobDevice()
|
|||
|
||||
void ImageBlobDevice::setBlobInfo(const QByteArray &table, const QByteArray &column, qint64 rowId)
|
||||
{
|
||||
mRowId = rowId;
|
||||
mTable = table;
|
||||
mRowId = rowId;
|
||||
mTable = table;
|
||||
mColumn = column;
|
||||
}
|
||||
|
||||
bool ImageBlobDevice::reserveSizeAndReset(qint64 len)
|
||||
{
|
||||
//NOTE: this will discard any previous content
|
||||
// NOTE: this will discard any previous content
|
||||
|
||||
//Close previous BLOB handle
|
||||
if(mBlob)
|
||||
// Close previous BLOB handle
|
||||
if (mBlob)
|
||||
close();
|
||||
|
||||
//Create SQL statement
|
||||
QByteArray sql = "UPDATE " + mTable + " SET " + mColumn + "=? WHERE rowId=?";
|
||||
// Create SQL statement
|
||||
QByteArray sql = "UPDATE " + mTable + " SET " + mColumn + "=? WHERE rowId=?";
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(mDb, sql.constData(), sql.size(), &stmt, nullptr);
|
||||
if(rc != SQLITE_OK)
|
||||
int rc = sqlite3_prepare_v2(mDb, sql.constData(), sql.size(), &stmt, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "ImageBlobDevice::reserveSizeAndReset cannot prepare:" << sqlite3_errmsg(mDb);
|
||||
setErrorString(tr("Cannot query database"));
|
||||
return false;
|
||||
}
|
||||
|
||||
//Reserve BLOB memory
|
||||
// Reserve BLOB memory
|
||||
rc = sqlite3_bind_zeroblob64(stmt, 1, len);
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
}
|
||||
rc = sqlite3_bind_int64(stmt, 2, mRowId);
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
|
@ -69,30 +86,30 @@ bool ImageBlobDevice::reserveSizeAndReset(qint64 len)
|
|||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if(rc != SQLITE_OK && rc != SQLITE_DONE)
|
||||
if (rc != SQLITE_OK && rc != SQLITE_DONE)
|
||||
{
|
||||
qWarning() << "ImageBlobDevice::reserveSizeAndReset cannot step:" << sqlite3_errmsg(mDb);
|
||||
setErrorString(tr("Cannot create BLOB"));
|
||||
return false;
|
||||
}
|
||||
|
||||
//Open new BLOB handle
|
||||
// Open new BLOB handle
|
||||
return open(QIODevice::ReadWrite);
|
||||
}
|
||||
|
||||
bool ImageBlobDevice::open(QIODevice::OpenMode mode)
|
||||
{
|
||||
if(isOpen())
|
||||
if (isOpen())
|
||||
{
|
||||
qWarning().nospace() << "ImageBlobDevice::open Device already open "
|
||||
<< '(' << mTable << '.' << mColumn << ')';
|
||||
qWarning().nospace() << "ImageBlobDevice::open Device already open " << '(' << mTable << '.'
|
||||
<< mColumn << ')';
|
||||
return false;
|
||||
}
|
||||
|
||||
mode |= QIODevice::ReadOnly; //Always enable reading
|
||||
int rc = sqlite3_blob_open(mDb, "main", mTable.constData(), mColumn.constData(),
|
||||
mRowId, (mode & QIODevice::WriteOnly) != 0, &mBlob);
|
||||
if(rc != SQLITE_OK || !mBlob)
|
||||
mode |= QIODevice::ReadOnly; // Always enable reading
|
||||
int rc = sqlite3_blob_open(mDb, "main", mTable.constData(), mColumn.constData(), mRowId,
|
||||
(mode & QIODevice::WriteOnly) != 0, &mBlob);
|
||||
if (rc != SQLITE_OK || !mBlob)
|
||||
{
|
||||
mBlob = nullptr;
|
||||
setErrorString(sqlite3_errmsg(mDb));
|
||||
|
@ -108,7 +125,7 @@ bool ImageBlobDevice::open(QIODevice::OpenMode mode)
|
|||
|
||||
void ImageBlobDevice::close()
|
||||
{
|
||||
if(mBlob)
|
||||
if (mBlob)
|
||||
{
|
||||
sqlite3_blob_close(mBlob);
|
||||
mBlob = nullptr;
|
||||
|
@ -125,21 +142,21 @@ qint64 ImageBlobDevice::size() const
|
|||
|
||||
qint64 ImageBlobDevice::writeData(const char *data, qint64 len)
|
||||
{
|
||||
if(!mBlob)
|
||||
if (!mBlob)
|
||||
return -1;
|
||||
|
||||
int offset = int(pos());
|
||||
if(len + offset >= mSize)
|
||||
if (len + offset >= mSize)
|
||||
len = mSize - offset;
|
||||
|
||||
if(!len)
|
||||
if (!len)
|
||||
return -1;
|
||||
|
||||
int rc = sqlite3_blob_write(mBlob, data, int(len), offset);
|
||||
if(rc == SQLITE_OK)
|
||||
if (rc == SQLITE_OK)
|
||||
return len;
|
||||
|
||||
if(rc == SQLITE_READONLY)
|
||||
if (rc == SQLITE_READONLY)
|
||||
return -1;
|
||||
|
||||
setErrorString(sqlite3_errmsg(mDb));
|
||||
|
@ -148,45 +165,46 @@ qint64 ImageBlobDevice::writeData(const char *data, qint64 len)
|
|||
|
||||
qint64 ImageBlobDevice::readData(char *data, qint64 maxlen)
|
||||
{
|
||||
if(!mBlob)
|
||||
if (!mBlob)
|
||||
return -1;
|
||||
|
||||
int offset = int(pos());
|
||||
if(maxlen + offset >= mSize)
|
||||
if (maxlen + offset >= mSize)
|
||||
maxlen = mSize - offset;
|
||||
|
||||
if(!maxlen)
|
||||
if (!maxlen)
|
||||
return -1;
|
||||
|
||||
int rc = sqlite3_blob_read(mBlob, data, int(maxlen), offset);
|
||||
if(rc == SQLITE_OK)
|
||||
if (rc == SQLITE_OK)
|
||||
return maxlen;
|
||||
|
||||
setErrorString(sqlite3_errmsg(mDb));
|
||||
return -1;
|
||||
}
|
||||
|
||||
ImageBlobDevice* getImage(sqlite3pp::database& db, const MetaDataManager::Key &key)
|
||||
ImageBlobDevice *getImage(sqlite3pp::database &db, const MetaDataManager::Key &key)
|
||||
{
|
||||
if(!db.db())
|
||||
if (!db.db())
|
||||
return nullptr;
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(db.db(), sql_get_key_id, sizeof (sql_get_key_id) - 1, &stmt, nullptr);
|
||||
if(rc != SQLITE_OK)
|
||||
int rc =
|
||||
sqlite3_prepare_v2(db.db(), sql_get_key_id, sizeof(sql_get_key_id) - 1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
return nullptr;
|
||||
|
||||
rc = sqlite3_bind_text(stmt, 1, key.str, key.len, SQLITE_STATIC);
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
qint64 rowId = 0;
|
||||
if(rc != SQLITE_ROW)
|
||||
if (rc != SQLITE_ROW)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return nullptr;
|
||||
|
@ -195,7 +213,7 @@ ImageBlobDevice* getImage(sqlite3pp::database& db, const MetaDataManager::Key &k
|
|||
rowId = sqlite3_column_int64(stmt, 0);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if(!rowId)
|
||||
if (!rowId)
|
||||
return nullptr;
|
||||
|
||||
ImageBlobDevice *dev = new ImageBlobDevice(db.db());
|
||||
|
@ -203,11 +221,11 @@ ImageBlobDevice* getImage(sqlite3pp::database& db, const MetaDataManager::Key &k
|
|||
return dev;
|
||||
}
|
||||
|
||||
void setImage(sqlite3pp::database& db, const MetaDataManager::Key &key, const void *data, int size)
|
||||
void setImage(sqlite3pp::database &db, const MetaDataManager::Key &key, const void *data, int size)
|
||||
{
|
||||
sqlite3pp::command cmd(db, "REPLACE INTO metadata(name, val) VALUES(?, ?)");
|
||||
sqlite3_bind_text(cmd.stmt(), 1, key.str, key.len, SQLITE_STATIC);
|
||||
if(data)
|
||||
if (data)
|
||||
sqlite3_bind_blob(cmd.stmt(), 2, data, size, SQLITE_STATIC);
|
||||
else
|
||||
sqlite3_bind_null(cmd.stmt(), 2);
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IMAGEBLOBDEVICE_H
|
||||
#define IMAGEBLOBDEVICE_H
|
||||
|
||||
|
@ -8,10 +27,9 @@
|
|||
typedef struct sqlite3 sqlite3;
|
||||
typedef struct sqlite3_blob sqlite3_blob;
|
||||
|
||||
namespace ImageMetaData
|
||||
{
|
||||
namespace ImageMetaData {
|
||||
|
||||
//TODO: move to utils
|
||||
// TODO: move to utils
|
||||
class ImageBlobDevice : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -19,7 +37,7 @@ public:
|
|||
ImageBlobDevice(sqlite3 *db, QObject *parent = nullptr);
|
||||
~ImageBlobDevice() override;
|
||||
|
||||
void setBlobInfo(const QByteArray& table, const QByteArray& column, qint64 rowId);
|
||||
void setBlobInfo(const QByteArray &table, const QByteArray &column, qint64 rowId);
|
||||
|
||||
bool reserveSizeAndReset(qint64 len);
|
||||
|
||||
|
@ -42,8 +60,8 @@ private:
|
|||
QByteArray mColumn;
|
||||
};
|
||||
|
||||
ImageBlobDevice *getImage(sqlite3pp::database& db, const MetaDataManager::Key& key);
|
||||
void setImage(sqlite3pp::database& db, const MetaDataManager::Key &key, const void *data, int size);
|
||||
ImageBlobDevice *getImage(sqlite3pp::database &db, const MetaDataManager::Key &key);
|
||||
void setImage(sqlite3pp::database &db, const MetaDataManager::Key &key, const void *data, int size);
|
||||
|
||||
} // namespace ImageMetaData
|
||||
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "meetinginformationdialog.h"
|
||||
#include "ui_meetinginformationdialog.h"
|
||||
|
||||
|
@ -28,28 +47,34 @@ MeetingInformationDialog::MeetingInformationDialog(QWidget *parent) :
|
|||
ui->setupUi(this);
|
||||
|
||||
connect(ui->viewPictureBut, &QPushButton::clicked, this, &MeetingInformationDialog::showImage);
|
||||
connect(ui->importPictureBut, &QPushButton::clicked, this, &MeetingInformationDialog::importImage);
|
||||
connect(ui->removePictureBut, &QPushButton::clicked, this, &MeetingInformationDialog::removeImage);
|
||||
connect(ui->resetHeaderBut, &QPushButton::clicked, this, &MeetingInformationDialog::toggleHeader);
|
||||
connect(ui->resetFooterBut, &QPushButton::clicked, this, &MeetingInformationDialog::toggleFooter);
|
||||
connect(ui->startDate, &QDateEdit::dateChanged, this, &MeetingInformationDialog::updateMinumumDate);
|
||||
connect(ui->importPictureBut, &QPushButton::clicked, this,
|
||||
&MeetingInformationDialog::importImage);
|
||||
connect(ui->removePictureBut, &QPushButton::clicked, this,
|
||||
&MeetingInformationDialog::removeImage);
|
||||
connect(ui->resetHeaderBut, &QPushButton::clicked, this,
|
||||
&MeetingInformationDialog::toggleHeader);
|
||||
connect(ui->resetFooterBut, &QPushButton::clicked, this,
|
||||
&MeetingInformationDialog::toggleFooter);
|
||||
connect(ui->startDate, &QDateEdit::dateChanged, this,
|
||||
&MeetingInformationDialog::updateMinumumDate);
|
||||
|
||||
QSizePolicy sp = ui->headerEdit->sizePolicy();
|
||||
sp.setRetainSizeWhenHidden(true);
|
||||
ui->headerEdit->setSizePolicy(sp);
|
||||
ui->footerEdit->setSizePolicy(sp);
|
||||
|
||||
//Use similar font to the actual font used in sheet export
|
||||
// Use similar font to the actual font used in sheet export
|
||||
QFont font;
|
||||
font.setBold(true);
|
||||
font.setPointSize(18);
|
||||
ui->descrEdit->document()->setDefaultFont(font);
|
||||
|
||||
if(!loadData())
|
||||
if (!loadData())
|
||||
{
|
||||
QMessageBox::warning(this, tr("Database Error"),
|
||||
tr("This database doesn't support metadata.\n"
|
||||
"Make sure it was created by a recent version of the application and was not manipulated."));
|
||||
"Make sure it was created by a recent version of the application "
|
||||
"and was not manipulated."));
|
||||
setDisabled(true);
|
||||
}
|
||||
}
|
||||
|
@ -63,7 +88,7 @@ bool MeetingInformationDialog::loadData()
|
|||
{
|
||||
MetaDataManager *meta = Session->getMetaDataManager();
|
||||
|
||||
qint64 tmp = 0;
|
||||
qint64 tmp = 0;
|
||||
QDate date;
|
||||
|
||||
switch (meta->getInt64(tmp, MetaDataKey::MeetingStartDate))
|
||||
|
@ -74,7 +99,7 @@ bool MeetingInformationDialog::loadData()
|
|||
break;
|
||||
}
|
||||
case MetaDataKey::Result::NoMetaDataTable:
|
||||
return false; //Database has no well-formed metadata
|
||||
return false; // Database has no well-formed metadata
|
||||
default:
|
||||
date = QDate::currentDate();
|
||||
}
|
||||
|
@ -107,7 +132,7 @@ bool MeetingInformationDialog::loadData()
|
|||
text.clear();
|
||||
meta->getString(text, MetaDataKey::MeetingDescription);
|
||||
ui->descrEdit->setPlainText(text);
|
||||
//Align all text to center
|
||||
// Align all text to center
|
||||
QTextCursor c = ui->descrEdit->textCursor();
|
||||
c.select(QTextCursor::Document);
|
||||
QTextBlockFormat fmt;
|
||||
|
@ -126,11 +151,12 @@ bool MeetingInformationDialog::loadData()
|
|||
return true;
|
||||
}
|
||||
|
||||
void MeetingInformationDialog::setSheetText(QLineEdit *lineEdit, QPushButton *but, const QString& text, bool isNull)
|
||||
void MeetingInformationDialog::setSheetText(QLineEdit *lineEdit, QPushButton *but,
|
||||
const QString &text, bool isNull)
|
||||
{
|
||||
lineEdit->setVisible(!isNull);
|
||||
|
||||
if(isNull)
|
||||
if (isNull)
|
||||
{
|
||||
but->setText(tr("Set custom text"));
|
||||
lineEdit->setText(QString());
|
||||
|
@ -151,15 +177,18 @@ void MeetingInformationDialog::saveData()
|
|||
meta->setInt64(ui->showDatesBox->isChecked() ? 1 : 0, false, MetaDataKey::MeetingShowDates);
|
||||
|
||||
meta->setString(ui->locationEdit->text().simplified(), false, MetaDataKey::MeetingLocation);
|
||||
meta->setString(ui->associationEdit->text().simplified(), false, MetaDataKey::MeetingHostAssociation);
|
||||
meta->setString(ui->associationEdit->text().simplified(), false,
|
||||
MetaDataKey::MeetingHostAssociation);
|
||||
meta->setString(ui->descrEdit->toPlainText(), false, MetaDataKey::MeetingDescription);
|
||||
|
||||
meta->setString(ui->headerEdit->text().simplified(), headerIsNull, MetaDataKey::SheetHeaderText);
|
||||
meta->setString(ui->footerEdit->text().simplified(), footerIsNull, MetaDataKey::SheetFooterText);
|
||||
meta->setString(ui->headerEdit->text().simplified(), headerIsNull,
|
||||
MetaDataKey::SheetHeaderText);
|
||||
meta->setString(ui->footerEdit->text().simplified(), footerIsNull,
|
||||
MetaDataKey::SheetFooterText);
|
||||
|
||||
if(needsToSaveImg)
|
||||
if (needsToSaveImg)
|
||||
{
|
||||
if(img.isNull())
|
||||
if (img.isNull())
|
||||
{
|
||||
ImageMetaData::setImage(Session->m_Db, MetaDataKey::MeetingLogoPicture, nullptr, 0);
|
||||
}
|
||||
|
@ -170,10 +199,13 @@ void MeetingInformationDialog::saveData()
|
|||
buf.open(QIODevice::WriteOnly);
|
||||
|
||||
QImageWriter writer(&buf, "PNG");
|
||||
if(writer.canWrite() && writer.write(img))
|
||||
if (writer.canWrite() && writer.write(img))
|
||||
{
|
||||
ImageMetaData::setImage(Session->m_Db, MetaDataKey::MeetingLogoPicture, arr.data(),
|
||||
arr.size());
|
||||
}
|
||||
else
|
||||
{
|
||||
ImageMetaData::setImage(Session->m_Db, MetaDataKey::MeetingLogoPicture, arr.data(), arr.size());
|
||||
}else{
|
||||
qDebug() << "MeetingInformationDialog: error saving image," << writer.errorString();
|
||||
}
|
||||
}
|
||||
|
@ -184,25 +216,28 @@ void MeetingInformationDialog::showImage()
|
|||
{
|
||||
OwningQPointer<ImageViewer> dlg = new ImageViewer(this);
|
||||
|
||||
if(img.isNull() && !needsToSaveImg)
|
||||
if (img.isNull() && !needsToSaveImg)
|
||||
{
|
||||
std::unique_ptr<ImageMetaData::ImageBlobDevice> imageIO;
|
||||
imageIO.reset(ImageMetaData::getImage(Session->m_Db, MetaDataKey::MeetingLogoPicture));
|
||||
if(imageIO && imageIO->open(QIODevice::ReadOnly))
|
||||
if (imageIO && imageIO->open(QIODevice::ReadOnly))
|
||||
{
|
||||
QImageReader reader(imageIO.get());
|
||||
if(reader.canRead())
|
||||
if (reader.canRead())
|
||||
{
|
||||
img = reader.read(); //ERRORMSG: handle errors, show to user
|
||||
img = reader.read(); // ERRORMSG: handle errors, show to user
|
||||
}
|
||||
|
||||
if(img.isNull())
|
||||
if (img.isNull())
|
||||
{
|
||||
qDebug() << "MeetingInformationDialog: error loading image," << reader.errorString();
|
||||
qDebug() << "MeetingInformationDialog: error loading image,"
|
||||
<< reader.errorString();
|
||||
}
|
||||
|
||||
imageIO->close();
|
||||
}else{
|
||||
}
|
||||
else
|
||||
{
|
||||
qDebug() << "MeetingInformationDialog: error query image," << Session->m_Db.error_msg();
|
||||
}
|
||||
}
|
||||
|
@ -211,15 +246,15 @@ void MeetingInformationDialog::showImage()
|
|||
|
||||
dlg->exec();
|
||||
|
||||
if(!needsToSaveImg)
|
||||
img = QImage(); //Cleanup to free memory
|
||||
if (!needsToSaveImg)
|
||||
img = QImage(); // Cleanup to free memory
|
||||
}
|
||||
|
||||
void MeetingInformationDialog::importImage()
|
||||
{
|
||||
const QLatin1String meeting_image_key = QLatin1String("meeting_image_key");
|
||||
|
||||
OwningQPointer<QFileDialog> dlg = new QFileDialog(this, tr("Import image"));
|
||||
OwningQPointer<QFileDialog> dlg = new QFileDialog(this, tr("Import image"));
|
||||
dlg->setFileMode(QFileDialog::ExistingFile);
|
||||
dlg->setAcceptMode(QFileDialog::AcceptOpen);
|
||||
dlg->setDirectory(RecentDirStore::getDir(meeting_image_key, RecentDirStore::Images));
|
||||
|
@ -227,28 +262,28 @@ void MeetingInformationDialog::importImage()
|
|||
QList<QByteArray> mimes = QImageReader::supportedMimeTypes();
|
||||
QStringList filters;
|
||||
filters.reserve(mimes.size() + 1);
|
||||
for(const QByteArray &ba : mimes)
|
||||
for (const QByteArray &ba : mimes)
|
||||
filters.append(QString::fromUtf8(ba));
|
||||
|
||||
filters << "application/octet-stream"; // will show "All files (*)"
|
||||
|
||||
dlg->setMimeTypeFilters(filters);
|
||||
|
||||
if(dlg->exec() != QDialog::Accepted || !dlg)
|
||||
if (dlg->exec() != QDialog::Accepted || !dlg)
|
||||
return;
|
||||
|
||||
QString fileName = dlg->selectedUrls().value(0).toLocalFile();
|
||||
if(fileName.isEmpty())
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
RecentDirStore::setPath(meeting_image_key, fileName);
|
||||
|
||||
QImageReader reader(fileName);
|
||||
reader.setQuality(100);
|
||||
if(reader.canRead())
|
||||
if (reader.canRead())
|
||||
{
|
||||
QImage image = reader.read();
|
||||
if(image.isNull())
|
||||
if (image.isNull())
|
||||
{
|
||||
QMessageBox::warning(this, tr("Importing error"),
|
||||
tr("The image format is not supported or the file is corrupted."));
|
||||
|
@ -256,7 +291,7 @@ void MeetingInformationDialog::importImage()
|
|||
return;
|
||||
}
|
||||
|
||||
img = image;
|
||||
img = image;
|
||||
needsToSaveImg = true;
|
||||
}
|
||||
}
|
||||
|
@ -265,10 +300,10 @@ void MeetingInformationDialog::removeImage()
|
|||
{
|
||||
int ret = QMessageBox::question(this, tr("Remove image?"),
|
||||
tr("Are you sure to remove the image logo?"));
|
||||
if(ret != QMessageBox::Yes)
|
||||
if (ret != QMessageBox::Yes)
|
||||
return;
|
||||
|
||||
img = QImage(); //Cleanup to free memory
|
||||
img = QImage(); // Cleanup to free memory
|
||||
needsToSaveImg = true;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MEETINGINFORMATIONDIALOG_H
|
||||
#define MEETINGINFORMATIONDIALOG_H
|
||||
|
||||
|
|
|
@ -1,30 +1,49 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "metadatamanager.h"
|
||||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
|
||||
constexpr char sql_get_metadata[] = "SELECT val FROM metadata WHERE name=?";
|
||||
constexpr char sql_get_metadata[] = "SELECT val FROM metadata WHERE name=?";
|
||||
constexpr char sql_has_metadata_key[] = "SELECT 1 FROM metadata WHERE name=? AND val NOT NULL";
|
||||
constexpr char sql_set_metadata[] = "REPLACE INTO metadata(name, val) VALUES(?, ?)";
|
||||
constexpr char sql_set_metadata[] = "REPLACE INTO metadata(name, val) VALUES(?, ?)";
|
||||
|
||||
MetaDataManager::MetaDataManager(sqlite3pp::database &db) :
|
||||
mDb(db)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
MetaDataKey::Result MetaDataManager::hasKey(const MetaDataManager::Key &key)
|
||||
{
|
||||
MetaDataKey::Result result = MetaDataKey::Result::NoMetaDataTable;
|
||||
if(!mDb.db())
|
||||
if (!mDb.db())
|
||||
return result;
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(mDb.db(), sql_has_metadata_key, sizeof (sql_has_metadata_key) - 1, &stmt, nullptr);
|
||||
if(rc != SQLITE_OK)
|
||||
int rc = sqlite3_prepare_v2(mDb.db(), sql_has_metadata_key, sizeof(sql_has_metadata_key) - 1,
|
||||
&stmt, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
return result;
|
||||
|
||||
rc = sqlite3_bind_text(stmt, 1, key.str, key.len, SQLITE_STATIC);
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
|
@ -32,11 +51,11 @@ MetaDataKey::Result MetaDataManager::hasKey(const MetaDataManager::Key &key)
|
|||
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
if(rc == SQLITE_ROW)
|
||||
if (rc == SQLITE_ROW)
|
||||
{
|
||||
result = MetaDataKey::Result::ValueFound;
|
||||
}
|
||||
else if(rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
else if (rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
{
|
||||
result = MetaDataKey::Result::ValueNotFound;
|
||||
}
|
||||
|
@ -49,19 +68,20 @@ MetaDataKey::Result MetaDataManager::hasKey(const MetaDataManager::Key &key)
|
|||
return result;
|
||||
}
|
||||
|
||||
MetaDataKey::Result MetaDataManager::getInt64(qint64 &out, const Key& key)
|
||||
MetaDataKey::Result MetaDataManager::getInt64(qint64 &out, const Key &key)
|
||||
{
|
||||
MetaDataKey::Result result = MetaDataKey::Result::NoMetaDataTable;
|
||||
if(!mDb.db())
|
||||
if (!mDb.db())
|
||||
return result;
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(mDb.db(), sql_get_metadata, sizeof (sql_get_metadata) - 1, &stmt, nullptr);
|
||||
if(rc != SQLITE_OK)
|
||||
int rc =
|
||||
sqlite3_prepare_v2(mDb.db(), sql_get_metadata, sizeof(sql_get_metadata) - 1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
return result;
|
||||
|
||||
rc = sqlite3_bind_text(stmt, 1, key.str, key.len, SQLITE_STATIC);
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
|
@ -69,19 +89,19 @@ MetaDataKey::Result MetaDataManager::getInt64(qint64 &out, const Key& key)
|
|||
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
if(rc == SQLITE_ROW)
|
||||
if (rc == SQLITE_ROW)
|
||||
{
|
||||
if(sqlite3_column_type(stmt, 0) == SQLITE_NULL)
|
||||
if (sqlite3_column_type(stmt, 0) == SQLITE_NULL)
|
||||
{
|
||||
result = MetaDataKey::Result::ValueIsNull;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = MetaDataKey::Result::ValueFound;
|
||||
out = sqlite3_column_int64(stmt, 0);
|
||||
out = sqlite3_column_int64(stmt, 0);
|
||||
}
|
||||
}
|
||||
else if(rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
else if (rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
{
|
||||
result = MetaDataKey::Result::ValueNotFound;
|
||||
}
|
||||
|
@ -94,29 +114,30 @@ MetaDataKey::Result MetaDataManager::getInt64(qint64 &out, const Key& key)
|
|||
return result;
|
||||
}
|
||||
|
||||
MetaDataKey::Result MetaDataManager::setInt64(qint64 in, bool setToNull, const Key& key)
|
||||
MetaDataKey::Result MetaDataManager::setInt64(qint64 in, bool setToNull, const Key &key)
|
||||
{
|
||||
MetaDataKey::Result result = MetaDataKey::Result::NoMetaDataTable;
|
||||
if(!mDb.db())
|
||||
if (!mDb.db())
|
||||
return result;
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(mDb.db(), sql_set_metadata, sizeof (sql_set_metadata) - 1, &stmt, nullptr);
|
||||
if(rc != SQLITE_OK)
|
||||
int rc =
|
||||
sqlite3_prepare_v2(mDb.db(), sql_set_metadata, sizeof(sql_set_metadata) - 1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
return result;
|
||||
|
||||
rc = sqlite3_bind_text(stmt, 1, key.str, key.len, SQLITE_STATIC);
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
}
|
||||
|
||||
if(setToNull)
|
||||
if (setToNull)
|
||||
rc = sqlite3_bind_null(stmt, 2);
|
||||
else
|
||||
rc = sqlite3_bind_int64(stmt, 2, in);
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
|
@ -124,7 +145,7 @@ MetaDataKey::Result MetaDataManager::setInt64(qint64 in, bool setToNull, const K
|
|||
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
if(rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
if (rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
{
|
||||
result = MetaDataKey::Result::ValueFound;
|
||||
}
|
||||
|
@ -137,19 +158,20 @@ MetaDataKey::Result MetaDataManager::setInt64(qint64 in, bool setToNull, const K
|
|||
return result;
|
||||
}
|
||||
|
||||
MetaDataKey::Result MetaDataManager::getString(QString &out, const Key& key)
|
||||
MetaDataKey::Result MetaDataManager::getString(QString &out, const Key &key)
|
||||
{
|
||||
MetaDataKey::Result result = MetaDataKey::Result::NoMetaDataTable;
|
||||
if(!mDb.db())
|
||||
if (!mDb.db())
|
||||
return result;
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(mDb.db(), sql_get_metadata, sizeof (sql_get_metadata) - 1, &stmt, nullptr);
|
||||
if(rc != SQLITE_OK)
|
||||
int rc =
|
||||
sqlite3_prepare_v2(mDb.db(), sql_get_metadata, sizeof(sql_get_metadata) - 1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
return result;
|
||||
|
||||
rc = sqlite3_bind_text(stmt, 1, key.str, key.len, SQLITE_STATIC);
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
|
@ -157,21 +179,21 @@ MetaDataKey::Result MetaDataManager::getString(QString &out, const Key& key)
|
|||
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
if(rc == SQLITE_ROW)
|
||||
if (rc == SQLITE_ROW)
|
||||
{
|
||||
if(sqlite3_column_type(stmt, 0) == SQLITE_NULL)
|
||||
if (sqlite3_column_type(stmt, 0) == SQLITE_NULL)
|
||||
{
|
||||
result = MetaDataKey::Result::ValueIsNull;
|
||||
}
|
||||
else
|
||||
{
|
||||
result = MetaDataKey::Result::ValueFound;
|
||||
const int len = sqlite3_column_bytes(stmt, 0);
|
||||
const char *text = reinterpret_cast<char const*>(sqlite3_column_text(stmt, 0));
|
||||
out = QString::fromUtf8(text, len);
|
||||
result = MetaDataKey::Result::ValueFound;
|
||||
const int len = sqlite3_column_bytes(stmt, 0);
|
||||
const char *text = reinterpret_cast<char const *>(sqlite3_column_text(stmt, 0));
|
||||
out = QString::fromUtf8(text, len);
|
||||
}
|
||||
}
|
||||
else if(rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
else if (rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
{
|
||||
result = MetaDataKey::Result::ValueNotFound;
|
||||
}
|
||||
|
@ -184,19 +206,20 @@ MetaDataKey::Result MetaDataManager::getString(QString &out, const Key& key)
|
|||
return result;
|
||||
}
|
||||
|
||||
MetaDataKey::Result MetaDataManager::setString(const QString& in, bool setToNull, const Key& key)
|
||||
MetaDataKey::Result MetaDataManager::setString(const QString &in, bool setToNull, const Key &key)
|
||||
{
|
||||
MetaDataKey::Result result = MetaDataKey::Result::NoMetaDataTable;
|
||||
if(!mDb.db())
|
||||
if (!mDb.db())
|
||||
return result;
|
||||
|
||||
sqlite3_stmt *stmt = nullptr;
|
||||
int rc = sqlite3_prepare_v2(mDb.db(), sql_set_metadata, sizeof (sql_set_metadata) - 1, &stmt, nullptr);
|
||||
if(rc != SQLITE_OK)
|
||||
int rc =
|
||||
sqlite3_prepare_v2(mDb.db(), sql_set_metadata, sizeof(sql_set_metadata) - 1, &stmt, nullptr);
|
||||
if (rc != SQLITE_OK)
|
||||
return result;
|
||||
|
||||
rc = sqlite3_bind_text(stmt, 1, key.str, key.len, SQLITE_STATIC);
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
|
@ -204,13 +227,13 @@ MetaDataKey::Result MetaDataManager::setString(const QString& in, bool setToNull
|
|||
|
||||
QByteArray arr = in.toUtf8();
|
||||
|
||||
if(setToNull)
|
||||
if (setToNull)
|
||||
rc = sqlite3_bind_null(stmt, 2);
|
||||
else
|
||||
{
|
||||
rc = sqlite3_bind_text(stmt, 2, arr.data(), arr.size(), SQLITE_STATIC);
|
||||
}
|
||||
if(rc != SQLITE_OK)
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return result;
|
||||
|
@ -218,7 +241,7 @@ MetaDataKey::Result MetaDataManager::setString(const QString& in, bool setToNull
|
|||
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
if(rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
if (rc == SQLITE_OK || rc == SQLITE_DONE)
|
||||
{
|
||||
result = MetaDataKey::Result::ValueFound;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef METADATAMANAGER_H
|
||||
#define METADATAMANAGER_H
|
||||
|
||||
|
@ -8,42 +27,46 @@ namespace sqlite3pp {
|
|||
class database;
|
||||
}
|
||||
|
||||
namespace MetaDataKey
|
||||
{
|
||||
namespace MetaDataKey {
|
||||
enum Result
|
||||
{
|
||||
ValueFound = 0,
|
||||
ValueIsNull,
|
||||
ValueNotFound,
|
||||
NoMetaDataTable, //Format is too old, 'metadata' table is not present
|
||||
NoMetaDataTable, // Format is too old, 'metadata' table is not present
|
||||
|
||||
NResults
|
||||
};
|
||||
|
||||
//BEGING Key constants TODO: maybe make static or extern to avoid duplication
|
||||
// BEGING Key constants TODO: maybe make static or extern to avoid duplication
|
||||
|
||||
//Database
|
||||
constexpr char FormatVersionKey[] = "format_version"; //INTEGER: version, NOTE: FormatVersion is aleady used by info.h constants
|
||||
constexpr char ApplicationString[] = "application_str"; //STRING: application version string 'maj.min.patch'
|
||||
// Database
|
||||
constexpr char FormatVersionKey[] =
|
||||
"format_version"; // INTEGER: version, NOTE: FormatVersion is aleady used by info.h constants
|
||||
constexpr char ApplicationString[] =
|
||||
"application_str"; // STRING: application version string 'maj.min.patch'
|
||||
|
||||
//Meeting
|
||||
constexpr char MeetingShowDates[] = "meeting_show_dates"; //INTEGER: 1 shows dates, 0 hides them
|
||||
constexpr char MeetingStartDate[] = "meeting_start_date"; //INTEGER: Start date in Julian Day integer
|
||||
constexpr char MeetingEndDate[] = "meeting_end_date"; //INTEGER: End date in Juliand Day integer
|
||||
constexpr char MeetingLocation[] = "meeting_location"; //STRING: city name
|
||||
constexpr char MeetingDescription[] = "meeting_descr"; //STRING: brief description of the meeting
|
||||
constexpr char MeetingHostAssociation[] = "meeting_host"; //STRING: name of association that is hosting the meeting
|
||||
constexpr char MeetingLogoPicture[] = "meeting_logo"; //BLOB: PNG alpha image, usually hosting association logo
|
||||
// Meeting
|
||||
constexpr char MeetingShowDates[] = "meeting_show_dates"; // INTEGER: 1 shows dates, 0 hides them
|
||||
constexpr char MeetingStartDate[] =
|
||||
"meeting_start_date"; // INTEGER: Start date in Julian Day integer
|
||||
constexpr char MeetingEndDate[] = "meeting_end_date"; // INTEGER: End date in Juliand Day integer
|
||||
constexpr char MeetingLocation[] = "meeting_location"; // STRING: city name
|
||||
constexpr char MeetingDescription[] = "meeting_descr"; // STRING: brief description of the meeting
|
||||
constexpr char MeetingHostAssociation[] =
|
||||
"meeting_host"; // STRING: name of association that is hosting the meeting
|
||||
constexpr char MeetingLogoPicture[] =
|
||||
"meeting_logo"; // BLOB: PNG alpha image, usually hosting association logo
|
||||
|
||||
//ODT Export Sheet
|
||||
constexpr char SheetHeaderText[] = "sheet_header"; //STRING: sheet header text
|
||||
constexpr char SheetFooterText[] = "sheet_footer"; //STRING: sheet footer text
|
||||
// ODT Export Sheet
|
||||
constexpr char SheetHeaderText[] = "sheet_header"; // STRING: sheet header text
|
||||
constexpr char SheetFooterText[] = "sheet_footer"; // STRING: sheet footer text
|
||||
|
||||
//Jobs
|
||||
#define METADATA_MAKE_RS_KEY(category) ("job_default_stop_" ## #category)
|
||||
// Jobs
|
||||
#define METADATA_MAKE_RS_KEY(category) ("job_default_stop_"## #category)
|
||||
|
||||
//END Key constants
|
||||
}
|
||||
// END Key constants
|
||||
} // namespace MetaDataKey
|
||||
|
||||
class MetaDataManager
|
||||
{
|
||||
|
@ -52,16 +75,20 @@ public:
|
|||
|
||||
struct Key
|
||||
{
|
||||
template<int N>
|
||||
constexpr inline Key(const char (&val)[N]) :str(val), len(N - 1) {}
|
||||
template <int N>
|
||||
constexpr inline Key(const char (&val)[N]) :
|
||||
str(val),
|
||||
len(N - 1)
|
||||
{
|
||||
}
|
||||
|
||||
const char *str;
|
||||
const int len;
|
||||
};
|
||||
|
||||
MetaDataKey::Result hasKey(const Key& key);
|
||||
MetaDataKey::Result hasKey(const Key &key);
|
||||
|
||||
MetaDataKey::Result getInt64(qint64 &out, const Key& key);
|
||||
MetaDataKey::Result getInt64(qint64 &out, const Key &key);
|
||||
MetaDataKey::Result setInt64(qint64 in, bool setToNull, const Key &key);
|
||||
|
||||
MetaDataKey::Result getString(QString &out, const Key &key);
|
||||
|
|
|
@ -4,6 +4,5 @@ add_subdirectory(view)
|
|||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
graph/linegraphtypes.h
|
||||
graph/linegraphtypes.cpp
|
||||
PARENT_SCOPE
|
||||
graph/linegraphtypes.cpp PARENT_SCOPE
|
||||
)
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "linegraphtypes.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
@ -9,16 +28,14 @@ public:
|
|||
static const char *texts[];
|
||||
};
|
||||
|
||||
const char *LineGraphTypeNames::texts[] = {
|
||||
QT_TRANSLATE_NOOP("LineGraphTypeNames", "No Graph"),
|
||||
QT_TRANSLATE_NOOP("LineGraphTypeNames", "Station"),
|
||||
QT_TRANSLATE_NOOP("LineGraphTypeNames", "Segment"),
|
||||
QT_TRANSLATE_NOOP("LineGraphTypeNames", "Line")
|
||||
};
|
||||
const char *LineGraphTypeNames::texts[] = {QT_TRANSLATE_NOOP("LineGraphTypeNames", "No Graph"),
|
||||
QT_TRANSLATE_NOOP("LineGraphTypeNames", "Station"),
|
||||
QT_TRANSLATE_NOOP("LineGraphTypeNames", "Segment"),
|
||||
QT_TRANSLATE_NOOP("LineGraphTypeNames", "Line")};
|
||||
|
||||
QString utils::getLineGraphTypeName(LineGraphType type)
|
||||
{
|
||||
if(type >= LineGraphType::NTypes)
|
||||
if (type >= LineGraphType::NTypes)
|
||||
return QString();
|
||||
return LineGraphTypeNames::tr(LineGraphTypeNames::texts[int(type)]);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LINEGRAPHTYPES_H
|
||||
#define LINEGRAPHTYPES_H
|
||||
|
||||
|
@ -8,10 +27,10 @@
|
|||
*/
|
||||
enum class LineGraphType
|
||||
{
|
||||
NoGraph = 0, //!< No content displayed
|
||||
SingleStation, //!< Show a single station
|
||||
NoGraph = 0, //!< No content displayed
|
||||
SingleStation, //!< Show a single station
|
||||
RailwaySegment, //!< Show two adjacent stations and the segment in between
|
||||
RailwayLine, //!< Show a complete railway line (multiple adjacent segments)
|
||||
RailwayLine, //!< Show a complete railway line (multiple adjacent segments)
|
||||
NTypes
|
||||
};
|
||||
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "linegraphmanager.h"
|
||||
|
||||
#include "linegraphscene.h"
|
||||
|
@ -6,37 +25,66 @@
|
|||
#include "app/session.h"
|
||||
#include "viewmanager/viewmanager.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include "utils/worker_event_types.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
typedef LineGraphScene::PendingUpdate PendingUpdate;
|
||||
|
||||
LineGraphManager::LineGraphManager(QObject *parent) :
|
||||
QObject(parent),
|
||||
activeScene(nullptr),
|
||||
m_followJobOnGraphChange(false)
|
||||
m_followJobOnGraphChange(false),
|
||||
m_hasScheduledUpdate(false)
|
||||
{
|
||||
auto session = Session;
|
||||
//Stations
|
||||
connect(session, &MeetingSession::stationNameChanged, this, &LineGraphManager::onStationNameChanged);
|
||||
connect(session, &MeetingSession::stationPlanChanged, this, &LineGraphManager::onStationPlanChanged);
|
||||
// Stations
|
||||
connect(session, &MeetingSession::stationNameChanged, this,
|
||||
&LineGraphManager::onStationNameChanged);
|
||||
connect(session, &MeetingSession::stationJobsPlanChanged, this,
|
||||
&LineGraphManager::onStationJobPlanChanged);
|
||||
connect(session, &MeetingSession::stationTrackPlanChanged, this,
|
||||
&LineGraphManager::onStationTrackPlanChanged);
|
||||
connect(session, &MeetingSession::stationRemoved, this, &LineGraphManager::onStationRemoved);
|
||||
|
||||
//Segments
|
||||
connect(session, &MeetingSession::segmentNameChanged, this, &LineGraphManager::onSegmentNameChanged);
|
||||
connect(session, &MeetingSession::segmentStationsChanged, this, &LineGraphManager::onSegmentStationsChanged);
|
||||
// Segments
|
||||
connect(session, &MeetingSession::segmentNameChanged, this,
|
||||
&LineGraphManager::onSegmentNameChanged);
|
||||
connect(session, &MeetingSession::segmentStationsChanged, this,
|
||||
&LineGraphManager::onSegmentStationsChanged);
|
||||
connect(session, &MeetingSession::segmentRemoved, this, &LineGraphManager::onSegmentRemoved);
|
||||
|
||||
//Lines
|
||||
// Lines
|
||||
connect(session, &MeetingSession::lineNameChanged, this, &LineGraphManager::onLineNameChanged);
|
||||
connect(session, &MeetingSession::lineSegmentsChanged, this, &LineGraphManager::onLineSegmentsChanged);
|
||||
connect(session, &MeetingSession::lineSegmentsChanged, this,
|
||||
&LineGraphManager::onLineSegmentsChanged);
|
||||
connect(session, &MeetingSession::lineRemoved, this, &LineGraphManager::onLineRemoved);
|
||||
|
||||
//Jobs
|
||||
// Jobs
|
||||
connect(session, &MeetingSession::jobChanged, this, &LineGraphManager::onJobChanged);
|
||||
connect(session, &MeetingSession::jobRemoved, this, &LineGraphManager::onJobRemoved);
|
||||
|
||||
//Settings
|
||||
connect(&AppSettings, &MRTPSettings::jobGraphOptionsChanged, this, &LineGraphManager::updateGraphOptions);
|
||||
// Settings
|
||||
connect(&AppSettings, &MRTPSettings::jobGraphOptionsChanged, this,
|
||||
&LineGraphManager::updateGraphOptions);
|
||||
m_followJobOnGraphChange = AppSettings.getFollowSelectionOnGraphChange();
|
||||
}
|
||||
|
||||
bool LineGraphManager::event(QEvent *ev)
|
||||
{
|
||||
if (ev->type() == QEvent::Type(CustomEvents::LineGraphManagerUpdate))
|
||||
{
|
||||
ev->accept();
|
||||
processPendingUpdates();
|
||||
return true;
|
||||
}
|
||||
|
||||
return QObject::event(ev);
|
||||
}
|
||||
|
||||
void LineGraphManager::registerScene(LineGraphScene *scene)
|
||||
{
|
||||
Q_ASSERT(!scenes.contains(scene));
|
||||
|
@ -47,13 +95,13 @@ void LineGraphManager::registerScene(LineGraphScene *scene)
|
|||
connect(scene, &LineGraphScene::sceneActivated, this, &LineGraphManager::setActiveScene);
|
||||
connect(scene, &LineGraphScene::jobSelected, this, &LineGraphManager::onJobSelected);
|
||||
|
||||
if(m_followJobOnGraphChange)
|
||||
if (m_followJobOnGraphChange)
|
||||
connect(scene, &LineGraphScene::graphChanged, this, &LineGraphManager::onGraphChanged);
|
||||
|
||||
if(scenes.count() == 1)
|
||||
if (scenes.count() == 1)
|
||||
{
|
||||
//This is the first scene registered
|
||||
//activate it so we have an active scene even if user does't activate one
|
||||
// This is the first scene registered
|
||||
// activate it so we have an active scene even if user does't activate one
|
||||
setActiveScene(scene);
|
||||
}
|
||||
}
|
||||
|
@ -68,17 +116,17 @@ void LineGraphManager::unregisterScene(LineGraphScene *scene)
|
|||
disconnect(scene, &LineGraphScene::sceneActivated, this, &LineGraphManager::setActiveScene);
|
||||
disconnect(scene, &LineGraphScene::jobSelected, this, &LineGraphManager::onJobSelected);
|
||||
|
||||
if(m_followJobOnGraphChange)
|
||||
if (m_followJobOnGraphChange)
|
||||
disconnect(scene, &LineGraphScene::graphChanged, this, &LineGraphManager::onGraphChanged);
|
||||
|
||||
//Reset active scene if it is unregistered
|
||||
if(activeScene == scene)
|
||||
// Reset active scene if it is unregistered
|
||||
if (activeScene == scene)
|
||||
setActiveScene(nullptr);
|
||||
}
|
||||
|
||||
void LineGraphManager::clearAllGraphs()
|
||||
{
|
||||
for(LineGraphScene *scene : qAsConst(scenes))
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
scene->loadGraph(0, LineGraphType::NoGraph, true);
|
||||
}
|
||||
|
@ -86,9 +134,9 @@ void LineGraphManager::clearAllGraphs()
|
|||
|
||||
void LineGraphManager::clearGraphsOfObject(db_id objectId, LineGraphType type)
|
||||
{
|
||||
for(LineGraphScene *scene : qAsConst(scenes))
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if(scene->getGraphObjectId() == objectId && scene->getGraphType() == type)
|
||||
if (scene->getGraphObjectId() == objectId && scene->getGraphType() == type)
|
||||
scene->loadGraph(0, LineGraphType::NoGraph);
|
||||
}
|
||||
}
|
||||
|
@ -96,35 +144,92 @@ void LineGraphManager::clearGraphsOfObject(db_id objectId, LineGraphType type)
|
|||
JobStopEntry LineGraphManager::getCurrentSelectedJob() const
|
||||
{
|
||||
JobStopEntry selectedJob;
|
||||
if(activeScene)
|
||||
if (activeScene)
|
||||
selectedJob = activeScene->getSelectedJob();
|
||||
return selectedJob;
|
||||
}
|
||||
|
||||
void LineGraphManager::setActiveScene(LineGraphScene *scene)
|
||||
void LineGraphManager::scheduleUpdate()
|
||||
{
|
||||
if(scene)
|
||||
if (m_hasScheduledUpdate)
|
||||
return; // Already scheduled
|
||||
|
||||
// Mark as scheduled and post event to ourself
|
||||
m_hasScheduledUpdate = true;
|
||||
QCoreApplication::postEvent(
|
||||
this, new QEvent(QEvent::Type(CustomEvents::LineGraphManagerUpdate)), Qt::HighEventPriority);
|
||||
}
|
||||
|
||||
void LineGraphManager::processPendingUpdates()
|
||||
{
|
||||
constexpr int MAX_UPDATE_TIME_MS = 1000;
|
||||
|
||||
// Clear update flag before updating in case one operation triggers update
|
||||
m_hasScheduledUpdate = false;
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if(activeScene == scene)
|
||||
if (timer.elapsed() > MAX_UPDATE_TIME_MS)
|
||||
{
|
||||
// It's taking to long, schedule a second update batch to finish
|
||||
scheduleUpdate();
|
||||
break;
|
||||
}
|
||||
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::NothingToDo))
|
||||
continue; // Skip
|
||||
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::FullReload))
|
||||
{
|
||||
scene->reload();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::ReloadJobs))
|
||||
{
|
||||
scene->reloadJobs();
|
||||
}
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::ReloadStationNames))
|
||||
{
|
||||
scene->updateStationNames();
|
||||
}
|
||||
|
||||
// Manually cleare pending update and trigger redraw
|
||||
scene->pendingUpdate = PendingUpdate::NothingToDo;
|
||||
emit scene->redrawGraph();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LineGraphManager::setActiveScene(IGraphScene *scene)
|
||||
{
|
||||
LineGraphScene *lineScene = qobject_cast<LineGraphScene *>(scene);
|
||||
|
||||
if (lineScene)
|
||||
{
|
||||
if (activeScene == lineScene)
|
||||
return;
|
||||
|
||||
//NOTE: Only registere scenes can become active
|
||||
//Otherwise we cannot track if scene got destroyed and reset active scene.
|
||||
if(!scenes.contains(scene))
|
||||
// NOTE: Only registere scenes can become active
|
||||
// Otherwise we cannot track if scene got destroyed and reset active scene.
|
||||
if (!scenes.contains(lineScene))
|
||||
return;
|
||||
}
|
||||
else if(!scenes.isEmpty())
|
||||
else if (!scenes.isEmpty())
|
||||
{
|
||||
//Activate first registered scene because previous one was unregistered
|
||||
scene = scenes.first();
|
||||
// Activate first registered scene because previous one was unregistered
|
||||
lineScene = scenes.first();
|
||||
}
|
||||
|
||||
activeScene = scene;
|
||||
activeScene = lineScene;
|
||||
emit activeSceneChanged(activeScene);
|
||||
|
||||
//Triegger selection update or clear it
|
||||
// Triegger selection update or clear it
|
||||
JobStopEntry selectedJob;
|
||||
if(activeScene)
|
||||
if (activeScene)
|
||||
{
|
||||
selectedJob = activeScene->getSelectedJob();
|
||||
}
|
||||
|
@ -138,60 +243,62 @@ void LineGraphManager::onSceneDestroyed(QObject *obj)
|
|||
unregisterScene(scene);
|
||||
}
|
||||
|
||||
void LineGraphManager::onGraphChanged(int graphType_, db_id graphObjId, LineGraphScene *scene)
|
||||
void LineGraphManager::onGraphChanged(int /*graphType_*/, db_id graphObjId, LineGraphScene *scene)
|
||||
{
|
||||
if(!m_followJobOnGraphChange || !scenes.contains(scene))
|
||||
if (!m_followJobOnGraphChange || !scenes.contains(scene))
|
||||
{
|
||||
qWarning() << "LineGraphManager: should not receive graph change for scene" << scene;
|
||||
return;
|
||||
}
|
||||
|
||||
if(!graphObjId || scene->getGraphType() == LineGraphType::NoGraph)
|
||||
return; //No graph selected
|
||||
if (!graphObjId || scene->getGraphType() == LineGraphType::NoGraph)
|
||||
return; // No graph selected
|
||||
|
||||
//Graph has changed, ensure selected job is still visible (if possible)
|
||||
// Graph has changed, ensure selected job is still visible (if possible)
|
||||
JobStopEntry selectedJob = scene->getSelectedJob();
|
||||
if(!selectedJob.jobId)
|
||||
return; //No job selected, nothing to do
|
||||
if (!selectedJob.jobId)
|
||||
return; // No job selected, nothing to do
|
||||
|
||||
LineGraphSelectionHelper helper(Session->m_Db);
|
||||
|
||||
LineGraphSelectionHelper::SegmentInfo info;
|
||||
if(!helper.tryFindJobStopInGraph(scene, selectedJob.jobId, info))
|
||||
return; //Cannot find job in current graph, give up
|
||||
if (!helper.tryFindJobStopInGraph(scene, selectedJob.jobId, info))
|
||||
return; // Cannot find job in current graph, give up
|
||||
|
||||
//Ensure job is visible
|
||||
scene->requestShowZone(info.firstStationId, info.segmentId, info.arrivalAndStart, info.departure);
|
||||
// Ensure job is visible
|
||||
scene->requestShowZone(info.firstStationId, info.segmentId, info.arrivalAndStart,
|
||||
info.departure);
|
||||
}
|
||||
|
||||
void LineGraphManager::onJobSelected(db_id jobId, int category, db_id stopId)
|
||||
{
|
||||
JobCategory cat = JobCategory(category);
|
||||
if(lastSelectedJob.jobId == jobId && lastSelectedJob.category == cat && lastSelectedJob.stopId == stopId)
|
||||
return; //Selection did not change
|
||||
if (lastSelectedJob.jobId == jobId && lastSelectedJob.category == cat
|
||||
&& lastSelectedJob.stopId == stopId)
|
||||
return; // Selection did not change
|
||||
|
||||
lastSelectedJob.jobId = jobId;
|
||||
lastSelectedJob.jobId = jobId;
|
||||
lastSelectedJob.category = cat;
|
||||
lastSelectedJob.stopId = stopId;
|
||||
lastSelectedJob.stopId = stopId;
|
||||
|
||||
if(jobId)
|
||||
if (jobId)
|
||||
Session->getViewManager()->requestJobEditor(jobId);
|
||||
else
|
||||
Session->getViewManager()->requestClearJob();
|
||||
|
||||
if(AppSettings.getSyncSelectionOnAllGraphs())
|
||||
if (AppSettings.getSyncSelectionOnAllGraphs())
|
||||
{
|
||||
//Sync selection among all registered scenes
|
||||
for(LineGraphScene *scene : qAsConst(scenes))
|
||||
// Sync selection among all registered scenes
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
scene->setSelectedJob(lastSelectedJob);
|
||||
}
|
||||
}
|
||||
|
||||
if(activeScene)
|
||||
if (activeScene)
|
||||
{
|
||||
const JobStopEntry selectedJob = activeScene->getSelectedJob();
|
||||
if(selectedJob.jobId == lastSelectedJob.jobId)
|
||||
if (selectedJob.jobId == lastSelectedJob.jobId)
|
||||
{
|
||||
emit jobSelected(lastSelectedJob.jobId, int(lastSelectedJob.category),
|
||||
lastSelectedJob.stopId);
|
||||
|
@ -201,143 +308,301 @@ void LineGraphManager::onJobSelected(db_id jobId, int category, db_id stopId)
|
|||
|
||||
void LineGraphManager::onStationNameChanged(db_id stationId)
|
||||
{
|
||||
onStationPlanChanged({stationId}); //FIXME: update only labels
|
||||
}
|
||||
bool found = false;
|
||||
|
||||
void LineGraphManager::onStationPlanChanged(const QSet<db_id>& stationIds)
|
||||
{
|
||||
//FIXME: speed up with threads???
|
||||
for(LineGraphScene *scene : qAsConst(scenes))
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
for(db_id stationId : stationIds)
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::FullReload))
|
||||
continue; // Already flagged
|
||||
|
||||
if (scene->stations.contains(stationId))
|
||||
{
|
||||
if(scene->stations.contains(stationId))
|
||||
{
|
||||
scene->reload();
|
||||
break;
|
||||
}
|
||||
scene->pendingUpdate.setFlag(PendingUpdate::ReloadStationNames);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
void LineGraphManager::onStationJobPlanChanged(const QSet<db_id> &stationIds)
|
||||
{
|
||||
onStationPlanChanged_internal(stationIds, int(PendingUpdate::ReloadJobs));
|
||||
}
|
||||
|
||||
void LineGraphManager::onStationTrackPlanChanged(const QSet<db_id> &stationIds)
|
||||
{
|
||||
onStationPlanChanged_internal(stationIds, int(PendingUpdate::FullReload));
|
||||
}
|
||||
|
||||
void LineGraphManager::onStationRemoved(db_id stationId)
|
||||
{
|
||||
// A station can be removed only when not connected and no jobs pass through it.
|
||||
// So there is no need to update other scenes because no line will contain
|
||||
// The removed station
|
||||
clearGraphsOfObject(stationId, LineGraphType::SingleStation);
|
||||
}
|
||||
|
||||
void LineGraphManager::onSegmentNameChanged(db_id segmentId)
|
||||
{
|
||||
onSegmentStationsChanged(segmentId); //FIXME: update only labels
|
||||
QString segName;
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::FullReload))
|
||||
continue; // Already flagged
|
||||
|
||||
if (scene->graphType == LineGraphType::RailwaySegment && scene->graphObjectId == segmentId)
|
||||
{
|
||||
if (segName.isEmpty())
|
||||
{
|
||||
// Fetch new name and cache it
|
||||
sqlite3pp::query q(scene->mDb, "SELECT name FROM railway_segments WHERE id=?");
|
||||
q.bind(1, segmentId);
|
||||
if (q.step() != SQLITE_ROW)
|
||||
{
|
||||
qWarning() << "Graph: invalid segment ID" << segmentId;
|
||||
return;
|
||||
}
|
||||
|
||||
// Store segment name
|
||||
segName = q.getRows().get<QString>(0);
|
||||
}
|
||||
|
||||
scene->graphObjectName = segName;
|
||||
emit scene->graphChanged(int(scene->graphType), scene->graphObjectId, scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LineGraphManager::onSegmentStationsChanged(db_id segmentId)
|
||||
{
|
||||
//FIXME: speed up with threads???
|
||||
for(LineGraphScene *scene : qAsConst(scenes))
|
||||
bool found = false;
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if(scene->graphType == LineGraphType::RailwaySegment && scene->graphObjectId == segmentId)
|
||||
scene->reload();
|
||||
else if(scene->graphType == LineGraphType::RailwayLine)
|
||||
scene->reload(); //FIXME: only if inside this line
|
||||
//Single stations are not affected
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::FullReload))
|
||||
continue; // Already flagged
|
||||
|
||||
if (scene->graphType == LineGraphType::RailwayLine)
|
||||
{
|
||||
for (const auto &stPos : qAsConst(scene->stationPositions))
|
||||
{
|
||||
if (stPos.segmentId == segmentId)
|
||||
{
|
||||
scene->pendingUpdate.setFlag(PendingUpdate::FullReload);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (scene->graphType == LineGraphType::RailwaySegment
|
||||
&& scene->graphObjectId == segmentId)
|
||||
{
|
||||
scene->pendingUpdate.setFlag(PendingUpdate::FullReload);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
void LineGraphManager::onSegmentRemoved(db_id segmentId)
|
||||
{
|
||||
// A segment can be removed only when is not on any line
|
||||
// And when no jobs pass through it.
|
||||
// So there is no need to update other line scenes because no line will contain
|
||||
// The removed segment
|
||||
clearGraphsOfObject(segmentId, LineGraphType::RailwaySegment);
|
||||
}
|
||||
|
||||
void LineGraphManager::onLineNameChanged(db_id lineId)
|
||||
{
|
||||
onLineSegmentsChanged(lineId); //FIXME: update only labels
|
||||
QString lineName;
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::FullReload))
|
||||
continue; // Already flagged
|
||||
|
||||
if (scene->graphType == LineGraphType::RailwayLine && scene->graphObjectId == lineId)
|
||||
{
|
||||
if (lineName.isEmpty())
|
||||
{
|
||||
// Fetch new name and cache it
|
||||
sqlite3pp::query q(scene->mDb, "SELECT name FROM lines WHERE id=?");
|
||||
q.bind(1, lineId);
|
||||
if (q.step() != SQLITE_ROW)
|
||||
{
|
||||
qWarning() << "Graph: invalid line ID" << lineId;
|
||||
return;
|
||||
}
|
||||
|
||||
// Store line name
|
||||
lineName = q.getRows().get<QString>(0);
|
||||
}
|
||||
|
||||
scene->graphObjectName = lineName;
|
||||
emit scene->graphChanged(int(scene->graphType), scene->graphObjectId, scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LineGraphManager::onLineSegmentsChanged(db_id lineId)
|
||||
{
|
||||
//FIXME: speed up with threads???
|
||||
for(LineGraphScene *scene : qAsConst(scenes))
|
||||
bool found = false;
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if(scene->graphType == LineGraphType::RailwayLine && scene->graphObjectId == lineId)
|
||||
scene->reload();
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::FullReload))
|
||||
continue; // Already flagged
|
||||
|
||||
if (scene->graphType == LineGraphType::RailwayLine && scene->graphObjectId == lineId)
|
||||
{
|
||||
scene->pendingUpdate.setFlag(PendingUpdate::FullReload);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
void LineGraphManager::onLineRemoved(db_id lineId)
|
||||
{
|
||||
// Lines do not affect segments and stations
|
||||
// So no other scene needs updating
|
||||
clearGraphsOfObject(lineId, LineGraphType::RailwayLine);
|
||||
}
|
||||
|
||||
void LineGraphManager::onJobChanged(db_id jobId, db_id oldJobId)
|
||||
{
|
||||
//If job changed ID or category, update all scenes which had it selected
|
||||
// If job changed ID or category, update selection on all scenes which had it selected
|
||||
// There is no need to reload jobs because it is already done.
|
||||
// In fact when a job changes ID, all station interested by this job get informed, and scenes
|
||||
// reloaded
|
||||
|
||||
JobStopEntry selectedJob;
|
||||
selectedJob.jobId = jobId;
|
||||
|
||||
LineGraphScene::updateJobSelection(Session->m_Db, selectedJob);
|
||||
|
||||
if(!selectedJob.jobId)
|
||||
return; //Invalid job ID
|
||||
if (!selectedJob.jobId)
|
||||
return; // Invalid job ID
|
||||
|
||||
if(activeScene)
|
||||
if (activeScene && AppSettings.getSyncSelectionOnAllGraphs())
|
||||
{
|
||||
// Update active scene before others in case selection is synced
|
||||
// This way all scenes get updated selection
|
||||
JobStopEntry oldSelectedJob = activeScene->getSelectedJob();
|
||||
if(oldSelectedJob.jobId == oldJobId)
|
||||
if (oldSelectedJob.jobId == oldJobId)
|
||||
{
|
||||
activeScene->setSelectedJob(selectedJob);
|
||||
activeScene->reloadJobs();
|
||||
}
|
||||
}
|
||||
|
||||
for(LineGraphScene *scene : qAsConst(scenes))
|
||||
else
|
||||
{
|
||||
JobStopEntry oldSelectedJob = scene->getSelectedJob();
|
||||
if(oldSelectedJob.jobId == oldJobId)
|
||||
// Manually update all scenes
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
scene->setSelectedJob(selectedJob);
|
||||
JobStopEntry oldSelectedJob = scene->getSelectedJob();
|
||||
if (oldSelectedJob.jobId == oldJobId)
|
||||
{
|
||||
scene->setSelectedJob(selectedJob);
|
||||
}
|
||||
}
|
||||
|
||||
if(scene->getSelectedJob().jobId == selectedJob.jobId)
|
||||
scene->reloadJobs();
|
||||
}
|
||||
}
|
||||
|
||||
void LineGraphManager::onJobRemoved(db_id jobId)
|
||||
{
|
||||
// We already catch normal job removal with other signals
|
||||
if (jobId)
|
||||
return;
|
||||
|
||||
// If jobId is zero, it means all jobs have been deleted
|
||||
// Reload all scenes
|
||||
|
||||
bool found = false;
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::FullReload)
|
||||
|| scene->pendingUpdate.testFlag(PendingUpdate(PendingUpdate::ReloadJobs)))
|
||||
continue; // Already flagged
|
||||
|
||||
scene->pendingUpdate.setFlag(PendingUpdate(PendingUpdate::ReloadJobs));
|
||||
found = true;
|
||||
}
|
||||
|
||||
if (found)
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
||||
void LineGraphManager::updateGraphOptions()
|
||||
{
|
||||
//TODO: maybe get rid of theese variables in MeetingSession and always use AppSettings?
|
||||
int hourOffset = AppSettings.getHourOffset();
|
||||
Session->hourOffset = hourOffset;
|
||||
// TODO: maybe get rid of theese variables in MeetingSession and always use AppSettings?
|
||||
int hourOffset = AppSettings.getHourOffset();
|
||||
Session->hourOffset = hourOffset;
|
||||
|
||||
int horizOffset = AppSettings.getHorizontalOffset();
|
||||
Session->horizOffset = horizOffset;
|
||||
int horizOffset = AppSettings.getHorizontalOffset();
|
||||
Session->horizOffset = horizOffset;
|
||||
|
||||
int vertOffset = AppSettings.getVerticalOffset();
|
||||
Session->vertOffset = vertOffset;
|
||||
int vertOffset = AppSettings.getVerticalOffset();
|
||||
Session->vertOffset = vertOffset;
|
||||
|
||||
Session->stationOffset = AppSettings.getStationOffset();
|
||||
Session->stationOffset = AppSettings.getStationOffset();
|
||||
Session->platformOffset = AppSettings.getPlatformOffset();
|
||||
|
||||
Session->jobLineWidth = AppSettings.getJobLineWidth();
|
||||
Session->jobLineWidth = AppSettings.getJobLineWidth();
|
||||
|
||||
//Reload all graphs
|
||||
for(LineGraphScene *scene : qAsConst(scenes))
|
||||
// Reload all graphs
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
scene->reload();
|
||||
}
|
||||
|
||||
const bool oldVal = m_followJobOnGraphChange;
|
||||
const bool oldVal = m_followJobOnGraphChange;
|
||||
m_followJobOnGraphChange = AppSettings.getFollowSelectionOnGraphChange();
|
||||
|
||||
if(m_followJobOnGraphChange != oldVal)
|
||||
if (m_followJobOnGraphChange != oldVal)
|
||||
{
|
||||
//Update connections
|
||||
for(LineGraphScene *scene : qAsConst(scenes))
|
||||
// Update connections
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if(m_followJobOnGraphChange)
|
||||
connect(scene, &LineGraphScene::graphChanged, this, &LineGraphManager::onGraphChanged);
|
||||
if (m_followJobOnGraphChange)
|
||||
connect(scene, &LineGraphScene::graphChanged, this,
|
||||
&LineGraphManager::onGraphChanged);
|
||||
else
|
||||
disconnect(scene, &LineGraphScene::graphChanged, this, &LineGraphManager::onGraphChanged);
|
||||
disconnect(scene, &LineGraphScene::graphChanged, this,
|
||||
&LineGraphManager::onGraphChanged);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LineGraphManager::onStationPlanChanged_internal(const QSet<db_id> &stationIds, int flag)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::FullReload)
|
||||
|| scene->pendingUpdate.testFlag(PendingUpdate(flag)))
|
||||
continue; // Already flagged
|
||||
|
||||
for (db_id stationId : stationIds)
|
||||
{
|
||||
if (scene->stations.contains(stationId))
|
||||
{
|
||||
scene->pendingUpdate.setFlag(PendingUpdate(flag));
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (found)
|
||||
scheduleUpdate();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LINEGRAPHMANAGER_H
|
||||
#define LINEGRAPHMANAGER_H
|
||||
|
||||
|
@ -8,6 +27,7 @@
|
|||
#include "utils/types.h"
|
||||
#include "graph/linegraphtypes.h"
|
||||
|
||||
class IGraphScene;
|
||||
class LineGraphScene;
|
||||
|
||||
/*!
|
||||
|
@ -25,6 +45,13 @@ class LineGraphManager : public QObject
|
|||
public:
|
||||
explicit LineGraphManager(QObject *parent = nullptr);
|
||||
|
||||
/*!
|
||||
* \brief react to line graph update events
|
||||
*
|
||||
* \sa processPendingUpdates()
|
||||
*/
|
||||
bool event(QEvent *ev) override;
|
||||
|
||||
/*!
|
||||
* \brief subscribe scene to notifications
|
||||
*
|
||||
|
@ -55,7 +82,10 @@ public:
|
|||
* \brief get active scene
|
||||
* \return Scene instance or nullptr if no scene is active
|
||||
*/
|
||||
inline LineGraphScene *getActiveScene() const { return activeScene; }
|
||||
inline LineGraphScene *getActiveScene() const
|
||||
{
|
||||
return activeScene;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief get current selected job
|
||||
|
@ -66,6 +96,27 @@ public:
|
|||
*/
|
||||
JobStopEntry getCurrentSelectedJob() const;
|
||||
|
||||
/*!
|
||||
* \brief schedule async update
|
||||
*
|
||||
* If an update is already schedule, does nothing.
|
||||
* Otherwise posts an event to self so it will update
|
||||
* when Qt event loop is not busy
|
||||
*
|
||||
* \sa processPendingUpdates()
|
||||
*/
|
||||
void scheduleUpdate();
|
||||
|
||||
/*!
|
||||
* \brief process pending updates
|
||||
*
|
||||
* Updates all scenes marked for update
|
||||
*
|
||||
* \sa scheduleUpdate()
|
||||
* \sa event()
|
||||
*/
|
||||
void processPendingUpdates();
|
||||
|
||||
signals:
|
||||
/*!
|
||||
* \brief Active scene is changed
|
||||
|
@ -100,40 +151,46 @@ public slots:
|
|||
* \sa activeSceneChanged()
|
||||
* \sa LineGraphScene::activateScene()
|
||||
*/
|
||||
void setActiveScene(LineGraphScene *scene);
|
||||
void setActiveScene(IGraphScene *scene);
|
||||
|
||||
private slots:
|
||||
//Scenes
|
||||
// Scenes
|
||||
void onSceneDestroyed(QObject *obj);
|
||||
void onGraphChanged(int graphType_, db_id graphObjId, LineGraphScene *scene);
|
||||
void onJobSelected(db_id jobId, int category, db_id stopId);
|
||||
|
||||
//Stations
|
||||
// Stations
|
||||
void onStationNameChanged(db_id stationId);
|
||||
void onStationPlanChanged(const QSet<db_id> &stationIds);
|
||||
void onStationJobPlanChanged(const QSet<db_id> &stationIds);
|
||||
void onStationTrackPlanChanged(const QSet<db_id> &stationIds);
|
||||
void onStationRemoved(db_id stationId);
|
||||
|
||||
//Segments
|
||||
// Segments
|
||||
void onSegmentNameChanged(db_id segmentId);
|
||||
void onSegmentStationsChanged(db_id segmentId);
|
||||
void onSegmentRemoved(db_id segmentId);
|
||||
|
||||
//Lines
|
||||
// Lines
|
||||
void onLineNameChanged(db_id lineId);
|
||||
void onLineSegmentsChanged(db_id lineId);
|
||||
void onLineRemoved(db_id lineId);
|
||||
|
||||
//Jobs
|
||||
// Jobs
|
||||
void onJobChanged(db_id jobId, db_id oldJobId);
|
||||
void onJobRemoved(db_id jobId);
|
||||
|
||||
//Settings
|
||||
// Settings
|
||||
void updateGraphOptions();
|
||||
|
||||
private:
|
||||
void onStationPlanChanged_internal(const QSet<db_id> &stationIds, int flag);
|
||||
|
||||
private:
|
||||
QVector<LineGraphScene *> scenes;
|
||||
LineGraphScene *activeScene;
|
||||
JobStopEntry lastSelectedJob;
|
||||
bool m_followJobOnGraphChange;
|
||||
bool m_hasScheduledUpdate;
|
||||
};
|
||||
|
||||
#endif // LINEGRAPHMANAGER_H
|
||||
|
|
|
@ -1,12 +1,31 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LINEGRAPHSCENE_H
|
||||
#define LINEGRAPHSCENE_H
|
||||
|
||||
#include <QObject>
|
||||
#include "utils/scene/igraphscene.h"
|
||||
|
||||
#include <QVector>
|
||||
#include <QHash>
|
||||
|
||||
#include <QPointF>
|
||||
#include <QSize>
|
||||
|
||||
#include "utils/types.h"
|
||||
|
||||
|
@ -21,17 +40,36 @@ class database;
|
|||
/*!
|
||||
* \brief Class to store line information
|
||||
*
|
||||
* Stores information to draw railway content in a LineGraphView
|
||||
* Reimplement IGraphScene for railway line graph
|
||||
* Stores information to draw railway contents in a LineGraphView
|
||||
*
|
||||
* \sa LineGraphManager
|
||||
* \sa LineGraphView
|
||||
*/
|
||||
class LineGraphScene : public QObject
|
||||
class LineGraphScene : public IGraphScene
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/*!
|
||||
* \brief Enum to describe pending update needed
|
||||
*
|
||||
* FIXME: allow to specify segments to update
|
||||
*/
|
||||
enum class PendingUpdate
|
||||
{
|
||||
NothingToDo = 0x0, //!< No content needs updating
|
||||
ReloadJobs = 0x1, //!< Only Jobs need to be reloaded
|
||||
ReloadStationNames = 0x2, //!< Only Station Names but not Station Plan has changed
|
||||
FullReload = 0x4 //!< Do a full reload
|
||||
};
|
||||
Q_DECLARE_FLAGS(PendingUpdateFlags, PendingUpdate)
|
||||
|
||||
LineGraphScene(sqlite3pp::database &db, QObject *parent = nullptr);
|
||||
|
||||
void renderContents(QPainter *painter, const QRectF &sceneRect) override;
|
||||
void renderHeader(QPainter *painter, const QRectF &sceneRect, Qt::Orientation orient,
|
||||
double scroll) override;
|
||||
|
||||
/*!
|
||||
* \brief Load graph contents
|
||||
*
|
||||
|
@ -57,13 +95,21 @@ public:
|
|||
*/
|
||||
bool reloadJobs();
|
||||
|
||||
/*!
|
||||
* \brief update header size
|
||||
*
|
||||
* Updates header size from settings valuse.
|
||||
* \sa MRTPSettings
|
||||
*/
|
||||
void updateHeaderSize();
|
||||
|
||||
/*!
|
||||
* \brief Get job at graph position
|
||||
*
|
||||
* \param pos Point in scene coordinates
|
||||
* \param tolerance A tolerance if mouse doesn't exactly click on job item
|
||||
*/
|
||||
JobStopEntry getJobAt(const QPointF& pos, const double tolerance);
|
||||
JobStopEntry getJobAt(const QPointF &pos, const double tolerance);
|
||||
|
||||
inline LineGraphType getGraphType() const
|
||||
{
|
||||
|
@ -80,11 +126,6 @@ public:
|
|||
return graphObjectName;
|
||||
}
|
||||
|
||||
inline QSize getContentSize() const
|
||||
{
|
||||
return contentSize;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief get selected job
|
||||
*
|
||||
|
@ -105,17 +146,32 @@ public:
|
|||
void setSelectedJob(JobStopEntry stop, bool sendChange = true);
|
||||
|
||||
/*!
|
||||
* \brief activate scene
|
||||
* \param self a pointer to LineGraphScene instance
|
||||
* \brief getDrawSelection
|
||||
* \return true if selection is drawn
|
||||
*
|
||||
* For scenes registered on a LineGraphManager, this tells
|
||||
* our instance is now the active one and will therefore receive
|
||||
* user requests to show items
|
||||
* When true and a Job is selected, it gets a semi transparent aurea
|
||||
* around it's line to make it more visible
|
||||
*
|
||||
* \sa LineGraphManager::setActiveScene()
|
||||
* \sa sceneActivated()
|
||||
* \sa setDrawSelection()
|
||||
*/
|
||||
inline void activateScene() { emit sceneActivated(this); }
|
||||
inline bool getDrawSelection()
|
||||
{
|
||||
return m_drawSelection;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief setDrawSelection
|
||||
* \param val true if needs to draw selection
|
||||
*
|
||||
* Sets scene option. You must manually redraw graph
|
||||
* by calling \ref renderContents() or emitting \ref redrawGraph()
|
||||
*
|
||||
* \sa getDrawSelection()
|
||||
*/
|
||||
inline void setDrawSelection(bool val)
|
||||
{
|
||||
m_drawSelection = val;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief requestShowZone
|
||||
|
@ -135,7 +191,6 @@ public:
|
|||
|
||||
signals:
|
||||
void graphChanged(int type, db_id objectId, LineGraphScene *self);
|
||||
void redrawGraph();
|
||||
|
||||
/*!
|
||||
* \brief job selected
|
||||
|
@ -149,21 +204,6 @@ signals:
|
|||
*/
|
||||
void jobSelected(db_id jobId, int category, db_id stopId);
|
||||
|
||||
/*!
|
||||
* \brief Signal for activation
|
||||
* \param self a pointer to LineGraphScene instance
|
||||
*
|
||||
* \sa activateScene()
|
||||
*/
|
||||
void sceneActivated(LineGraphScene *self);
|
||||
|
||||
/*!
|
||||
* \brief request LineGraphView to show this rect
|
||||
*
|
||||
* The view will ensure this rect is visible
|
||||
*/
|
||||
void requestShowRect(const QRectF& rect);
|
||||
|
||||
public slots:
|
||||
/*!
|
||||
* \brief Reload everything
|
||||
|
@ -174,13 +214,12 @@ public slots:
|
|||
void reload();
|
||||
|
||||
private:
|
||||
|
||||
/*!
|
||||
* \brief Graph of the job while is moving
|
||||
*
|
||||
* Contains informations to draw job line between two adjacent stations
|
||||
*/
|
||||
typedef struct
|
||||
struct JobSegmentGraph
|
||||
{
|
||||
db_id jobId;
|
||||
JobCategory category;
|
||||
|
@ -192,14 +231,14 @@ private:
|
|||
db_id toStopId;
|
||||
db_id toPlatfId;
|
||||
QPointF toArrival;
|
||||
} JobSegmentGraph;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Station entry on scene
|
||||
*
|
||||
* Represents a station item placeholder in an ordered list of scene
|
||||
*/
|
||||
typedef struct
|
||||
struct StationPosEntry
|
||||
{
|
||||
db_id stationId;
|
||||
db_id segmentId;
|
||||
|
@ -210,7 +249,7 @@ private:
|
|||
* Stores job graph of the next segment
|
||||
* Which means jobs departing from this staation and going to next one
|
||||
*/
|
||||
} StationPosEntry;
|
||||
};
|
||||
|
||||
private:
|
||||
/*!
|
||||
|
@ -231,7 +270,7 @@ private:
|
|||
* \brief Recalculate and store content size
|
||||
*
|
||||
* Stores cached calculated size of the graph.
|
||||
* It depends on settings like station offset
|
||||
* It depends on settings like station offset.
|
||||
* \sa MRTPSettings
|
||||
*/
|
||||
void recalcContentSize();
|
||||
|
@ -244,6 +283,14 @@ private:
|
|||
*/
|
||||
bool loadStation(StationGraphObject &st, QString &outFullName);
|
||||
|
||||
/*!
|
||||
* \brief updateStationNames
|
||||
*
|
||||
* Update names of already loaded stations
|
||||
* If graph type is SingleStation, graph name will be update too
|
||||
*/
|
||||
bool updateStationNames();
|
||||
|
||||
/*!
|
||||
* \brief Load all stations in a railway line
|
||||
*
|
||||
|
@ -266,7 +313,8 @@ private:
|
|||
* Load job segments and stores them in 'from' station
|
||||
* \sa loadStationJobStops()
|
||||
*/
|
||||
bool loadSegmentJobs(StationPosEntry &stPos, const StationGraphObject &fromSt, const StationGraphObject &toSt);
|
||||
bool loadSegmentJobs(StationPosEntry &stPos, const StationGraphObject &fromSt,
|
||||
const StationGraphObject &toSt);
|
||||
|
||||
/*!
|
||||
* \brief Update job selection category
|
||||
|
@ -282,7 +330,7 @@ private:
|
|||
friend class BackgroundHelper;
|
||||
friend class LineGraphManager;
|
||||
|
||||
sqlite3pp::database& mDb;
|
||||
sqlite3pp::database &mDb;
|
||||
|
||||
/*!
|
||||
* \brief Graph Object ID
|
||||
|
@ -308,20 +356,16 @@ private:
|
|||
QVector<StationPosEntry> stationPositions;
|
||||
QHash<db_id, StationGraphObject> stations;
|
||||
|
||||
/*!
|
||||
* \brief Graph size
|
||||
*
|
||||
* Caches calculated size of the graph.
|
||||
* So it doesn't need to be recalculated for scrollbars and printing
|
||||
*/
|
||||
QSize contentSize;
|
||||
|
||||
/*!
|
||||
* \brief Job selection
|
||||
*
|
||||
* Caches selected job item ID
|
||||
*/
|
||||
JobStopEntry selectedJob;
|
||||
|
||||
bool m_drawSelection;
|
||||
|
||||
PendingUpdateFlags pendingUpdate;
|
||||
};
|
||||
|
||||
#endif // LINEGRAPHSCENE_H
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "linegraphselectionhelper.h"
|
||||
|
||||
#include "linegraphscene.h"
|
||||
|
@ -8,10 +27,10 @@ using namespace sqlite3pp;
|
|||
LineGraphSelectionHelper::LineGraphSelectionHelper(sqlite3pp::database &db) :
|
||||
mDb(db)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::tryFindJobStopInGraph(LineGraphScene *scene, db_id jobId, SegmentInfo &info)
|
||||
bool LineGraphSelectionHelper::tryFindJobStopInGraph(LineGraphScene *scene, db_id jobId,
|
||||
SegmentInfo &info)
|
||||
{
|
||||
query q(mDb);
|
||||
|
||||
|
@ -53,7 +72,7 @@ bool LineGraphSelectionHelper::tryFindJobStopInGraph(LineGraphScene *scene, db_i
|
|||
case LineGraphType::NoGraph:
|
||||
case LineGraphType::NTypes:
|
||||
{
|
||||
//We need to load a new graph, give up
|
||||
// We need to load a new graph, give up
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -61,18 +80,18 @@ bool LineGraphSelectionHelper::tryFindJobStopInGraph(LineGraphScene *scene, db_i
|
|||
q.bind(1, jobId);
|
||||
q.bind(2, scene->getGraphObjectId());
|
||||
|
||||
if(q.step() != SQLITE_ROW || q.getRows().column_type(0) == SQLITE_NULL)
|
||||
return false; //We didn't find a stop in current graph, give up
|
||||
if (q.step() != SQLITE_ROW || q.getRows().column_type(0) == SQLITE_NULL)
|
||||
return false; // We didn't find a stop in current graph, give up
|
||||
|
||||
//Get stop info
|
||||
auto stop = q.getRows();
|
||||
info.firstStopId = stop.get<db_id>(0);
|
||||
info.segmentId = stop.get<db_id>(1);
|
||||
// Get stop info
|
||||
auto stop = q.getRows();
|
||||
info.firstStopId = stop.get<db_id>(0);
|
||||
info.segmentId = stop.get<db_id>(1);
|
||||
info.arrivalAndStart = stop.get<QTime>(2);
|
||||
info.departure = stop.get<QTime>(3);
|
||||
info.departure = stop.get<QTime>(3);
|
||||
|
||||
//If graph is SingleStation we already know the station ID
|
||||
if(scene->getGraphType() == LineGraphType::SingleStation)
|
||||
// If graph is SingleStation we already know the station ID
|
||||
if (scene->getGraphType() == LineGraphType::SingleStation)
|
||||
info.firstStationId = scene->getGraphObjectId();
|
||||
else
|
||||
info.firstStationId = stop.get<db_id>(4);
|
||||
|
@ -80,96 +99,98 @@ bool LineGraphSelectionHelper::tryFindJobStopInGraph(LineGraphScene *scene, db_i
|
|||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::tryFindJobStopsAfter(db_id jobId, SegmentInfo& info)
|
||||
bool LineGraphSelectionHelper::tryFindJobStopsAfter(db_id jobId, SegmentInfo &info)
|
||||
{
|
||||
query q(mDb, "SELECT stops.id, MIN(stops.arrival), stops.departure, stops.station_id, c.seg_id"
|
||||
" FROM stops"
|
||||
" LEFT JOIN railway_connections c ON c.id=stops.next_segment_conn_id"
|
||||
" WHERE stops.job_id=? AND stops.arrival>=?");
|
||||
|
||||
//Find first job stop after or equal to startTime
|
||||
// Find first job stop after or equal to startTime
|
||||
q.bind(1, jobId);
|
||||
q.bind(2, info.arrivalAndStart);
|
||||
if(q.step() != SQLITE_ROW || q.getRows().column_type(0) == SQLITE_NULL)
|
||||
if (q.step() != SQLITE_ROW || q.getRows().column_type(0) == SQLITE_NULL)
|
||||
return false;
|
||||
|
||||
//Get first stop info
|
||||
auto stop = q.getRows();
|
||||
info.firstStopId = stop.get<db_id>(0);
|
||||
// Get first stop info
|
||||
auto stop = q.getRows();
|
||||
info.firstStopId = stop.get<db_id>(0);
|
||||
info.arrivalAndStart = stop.get<QTime>(1);
|
||||
info.departure = stop.get<QTime>(2);
|
||||
info.firstStationId = stop.get<db_id>(3);
|
||||
info.segmentId = stop.get<db_id>(4);
|
||||
info.departure = stop.get<QTime>(2);
|
||||
info.firstStationId = stop.get<db_id>(3);
|
||||
info.segmentId = stop.get<db_id>(4);
|
||||
q.reset();
|
||||
|
||||
//Try get a second stop after the first departure
|
||||
//NOTE: minimum 60 seconds of travel between 2 consecutive stops
|
||||
// Try get a second stop after the first departure
|
||||
// NOTE: minimum 60 seconds of travel between 2 consecutive stops
|
||||
q.bind(1, jobId);
|
||||
q.bind(2, info.departure.addSecs(60));
|
||||
if(q.step() != SQLITE_ROW)
|
||||
if (q.step() != SQLITE_ROW)
|
||||
{
|
||||
//We found only 1 stop, return that
|
||||
// We found only 1 stop, return that
|
||||
info.secondStationId = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Get first stop info
|
||||
// Get first stop info
|
||||
stop = q.getRows();
|
||||
//db_id secondStopId = stop.get<db_id>(0);
|
||||
//QTime secondArrival = stop.get<QTime>(1);
|
||||
info.departure = stop.get<QTime>(2); //Overwrite departure
|
||||
// db_id secondStopId = stop.get<db_id>(0);
|
||||
// QTime secondArrival = stop.get<QTime>(1);
|
||||
info.departure = stop.get<QTime>(2); // Overwrite departure
|
||||
info.secondStationId = stop.get<db_id>(3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::tryFindNewGraphForJob(db_id jobId, SegmentInfo& info,
|
||||
db_id &outGraphObjId, LineGraphType &outGraphType)
|
||||
bool LineGraphSelectionHelper::tryFindNewGraphForJob(db_id jobId, SegmentInfo &info,
|
||||
db_id &outGraphObjId,
|
||||
LineGraphType &outGraphType)
|
||||
{
|
||||
if(!tryFindJobStopsAfter(jobId, info))
|
||||
return false; //No stops found
|
||||
if (!tryFindJobStopsAfter(jobId, info))
|
||||
return false; // No stops found
|
||||
|
||||
if(!info.secondStationId || !info.segmentId)
|
||||
if (!info.secondStationId || !info.segmentId)
|
||||
{
|
||||
//We found only 1 stop, select first station
|
||||
// We found only 1 stop, select first station
|
||||
outGraphObjId = info.firstStationId;
|
||||
outGraphType = LineGraphType::SingleStation;
|
||||
outGraphType = LineGraphType::SingleStation;
|
||||
return true;
|
||||
}
|
||||
|
||||
//Try to find a railway line which contains this segment
|
||||
//FIXME: better criteria to choose a line (name?, number of segments?, prompt user?)
|
||||
// Try to find a railway line which contains this segment
|
||||
// FIXME: better criteria to choose a line (name?, number of segments?, prompt user?)
|
||||
query q(mDb, "SELECT ls.line_id"
|
||||
" FROM line_segments ls"
|
||||
" WHERE ls.seg_id=?");
|
||||
q.bind(1, info.segmentId);
|
||||
|
||||
if(q.step() == SQLITE_ROW)
|
||||
if (q.step() == SQLITE_ROW)
|
||||
{
|
||||
//Found a line
|
||||
// Found a line
|
||||
outGraphObjId = q.getRows().get<db_id>(0);
|
||||
outGraphType = LineGraphType::RailwayLine;
|
||||
outGraphType = LineGraphType::RailwayLine;
|
||||
return true;
|
||||
}
|
||||
|
||||
//No lines found, use the railway segment
|
||||
// No lines found, use the railway segment
|
||||
outGraphObjId = info.segmentId;
|
||||
outGraphType = LineGraphType::RailwaySegment;
|
||||
outGraphType = LineGraphType::RailwaySegment;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::requestJobSelection(LineGraphScene *scene, db_id jobId, bool select, bool ensureVisible)
|
||||
bool LineGraphSelectionHelper::requestJobSelection(LineGraphScene *scene, db_id jobId, bool select,
|
||||
bool ensureVisible)
|
||||
{
|
||||
//Check jobId is valid and get category
|
||||
// Check jobId is valid and get category
|
||||
|
||||
query q(mDb, "SELECT category FROM jobs WHERE id=?");
|
||||
q.bind(1, jobId);
|
||||
if(q.step() != SQLITE_ROW)
|
||||
return false; //Job doen't exist
|
||||
if (q.step() != SQLITE_ROW)
|
||||
return false; // Job doen't exist
|
||||
|
||||
JobStopEntry selectedJob;
|
||||
selectedJob.jobId = jobId;
|
||||
selectedJob.jobId = jobId;
|
||||
selectedJob.category = JobCategory(q.getRows().get<int>(0));
|
||||
|
||||
SegmentInfo info;
|
||||
|
@ -177,65 +198,66 @@ bool LineGraphSelectionHelper::requestJobSelection(LineGraphScene *scene, db_id
|
|||
// Try to select earliest stop of this job in current graph, if any
|
||||
const bool found = tryFindJobStopInGraph(scene, selectedJob.jobId, info);
|
||||
|
||||
if(!found)
|
||||
if (!found)
|
||||
{
|
||||
//Find a NEW line graph or segment or station with this job
|
||||
// Find a NEW line graph or segment or station with this job
|
||||
|
||||
//Find first 2 stops of the job
|
||||
// Find first 2 stops of the job
|
||||
|
||||
db_id graphObjId = 0;
|
||||
db_id graphObjId = 0;
|
||||
LineGraphType graphType = LineGraphType::NoGraph;
|
||||
|
||||
if(!tryFindNewGraphForJob(selectedJob.jobId, info, graphObjId, graphType))
|
||||
if (!tryFindNewGraphForJob(selectedJob.jobId, info, graphObjId, graphType))
|
||||
{
|
||||
//Could not find a suitable graph, abort
|
||||
// Could not find a suitable graph, abort
|
||||
return false;
|
||||
}
|
||||
|
||||
//NOTE: clear selection to avoid LineGraphManager trying to follow selection
|
||||
//do not emit change because selection might be synced between all scenes
|
||||
//and because it's restored soon after
|
||||
// NOTE: clear selection to avoid LineGraphManager trying to follow selection
|
||||
// do not emit change because selection might be synced between all scenes
|
||||
// and because it's restored soon after
|
||||
const JobStopEntry oldSelection = scene->getSelectedJob();
|
||||
scene->setSelectedJob(JobStopEntry{}, false); //Clear selection
|
||||
scene->setSelectedJob(JobStopEntry{}, false); // Clear selection
|
||||
|
||||
//Select the graph
|
||||
// Select the graph
|
||||
scene->loadGraph(graphObjId, graphType);
|
||||
|
||||
//Restore previous selection
|
||||
// Restore previous selection
|
||||
scene->setSelectedJob(oldSelection, false);
|
||||
}
|
||||
|
||||
//Extract the info
|
||||
// Extract the info
|
||||
selectedJob.stopId = info.firstStopId;
|
||||
|
||||
if(!selectedJob.stopId)
|
||||
return false; //No stop found, abort
|
||||
if (!selectedJob.stopId)
|
||||
return false; // No stop found, abort
|
||||
|
||||
//Select job
|
||||
if(select)
|
||||
// Select job
|
||||
if (select)
|
||||
scene->setSelectedJob(selectedJob);
|
||||
|
||||
if(ensureVisible)
|
||||
if (ensureVisible)
|
||||
{
|
||||
return scene->requestShowZone(info.firstStationId, info.segmentId,
|
||||
info.arrivalAndStart, info.departure);
|
||||
return scene->requestShowZone(info.firstStationId, info.segmentId, info.arrivalAndStart,
|
||||
info.departure);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::requestCurrentJobPrevSegmentVisible(LineGraphScene *scene, bool goToStart)
|
||||
bool LineGraphSelectionHelper::requestCurrentJobPrevSegmentVisible(LineGraphScene *scene,
|
||||
bool goToStart)
|
||||
{
|
||||
JobStopEntry selectedJob = scene->getSelectedJob();
|
||||
if(!selectedJob.jobId)
|
||||
return false; //No job selected, nothing to do
|
||||
if (!selectedJob.jobId)
|
||||
return false; // No job selected, nothing to do
|
||||
|
||||
query q(mDb);
|
||||
|
||||
SegmentInfo info;
|
||||
if(selectedJob.stopId && !goToStart)
|
||||
if (selectedJob.stopId && !goToStart)
|
||||
{
|
||||
//Start from current stop and get previous stop
|
||||
// Start from current stop and get previous stop
|
||||
q.prepare("SELECT s2.job_id, s1.id, MAX(s1.arrival), s1.station_id, c.seg_id,"
|
||||
"s2.departure, s2.station_id"
|
||||
" FROM stops s2"
|
||||
|
@ -244,71 +266,72 @@ bool LineGraphSelectionHelper::requestCurrentJobPrevSegmentVisible(LineGraphScen
|
|||
" WHERE s2.id=? AND s1.arrival < s2.arrival");
|
||||
q.bind(1, selectedJob.stopId);
|
||||
|
||||
if(q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
if (q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
{
|
||||
auto stop = q.getRows();
|
||||
auto stop = q.getRows();
|
||||
db_id jobId = stop.get<db_id>(0);
|
||||
if(jobId == selectedJob.jobId)
|
||||
if (jobId == selectedJob.jobId)
|
||||
{
|
||||
//Found stop and belongs to requested job
|
||||
info.firstStopId = stop.get<db_id>(1);
|
||||
// Found stop and belongs to requested job
|
||||
info.firstStopId = stop.get<db_id>(1);
|
||||
info.arrivalAndStart = stop.get<QTime>(2);
|
||||
info.firstStationId = stop.get<db_id>(3);
|
||||
info.segmentId = stop.get<db_id>(4);
|
||||
info.departure = stop.get<QTime>(5);
|
||||
info.firstStationId = stop.get<db_id>(3);
|
||||
info.segmentId = stop.get<db_id>(4);
|
||||
info.departure = stop.get<QTime>(5);
|
||||
info.secondStationId = stop.get<db_id>(6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!info.firstStopId)
|
||||
if (!info.firstStopId)
|
||||
{
|
||||
//goToStart or failed to get previous stop so go to start anyway
|
||||
// goToStart or failed to get previous stop so go to start anyway
|
||||
|
||||
if(!tryFindJobStopsAfter(selectedJob.jobId, info))
|
||||
return false; //No stops found, give up
|
||||
if (!tryFindJobStopsAfter(selectedJob.jobId, info))
|
||||
return false; // No stops found, give up
|
||||
}
|
||||
|
||||
db_id graphObjId = 0;
|
||||
db_id graphObjId = 0;
|
||||
LineGraphType graphType = LineGraphType::NoGraph;
|
||||
|
||||
if(!tryFindNewGraphForJob(selectedJob.jobId, info, graphObjId, graphType))
|
||||
if (!tryFindNewGraphForJob(selectedJob.jobId, info, graphObjId, graphType))
|
||||
{
|
||||
//Could not find a suitable graph, abort
|
||||
// Could not find a suitable graph, abort
|
||||
return false;
|
||||
}
|
||||
|
||||
//NOTE: clear selection to avoid LineGraphManager trying to follow selection
|
||||
//do not emit change because selection might be synced between all scenes
|
||||
//and because it's restored soon after
|
||||
scene->setSelectedJob(JobStopEntry{}, false); //Clear selection
|
||||
// NOTE: clear selection to avoid LineGraphManager trying to follow selection
|
||||
// do not emit change because selection might be synced between all scenes
|
||||
// and because it's restored soon after
|
||||
scene->setSelectedJob(JobStopEntry{}, false); // Clear selection
|
||||
|
||||
//Select the graph
|
||||
// Select the graph
|
||||
scene->loadGraph(graphObjId, graphType);
|
||||
|
||||
//Restore selection
|
||||
// Restore selection
|
||||
selectedJob.stopId = info.firstStopId;
|
||||
scene->setSelectedJob(selectedJob); //This time emit
|
||||
scene->setSelectedJob(selectedJob); // This time emit
|
||||
|
||||
return scene->requestShowZone(info.firstStationId, info.segmentId,
|
||||
info.arrivalAndStart, info.departure);
|
||||
return scene->requestShowZone(info.firstStationId, info.segmentId, info.arrivalAndStart,
|
||||
info.departure);
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::requestCurrentJobNextSegmentVisible(LineGraphScene *scene, bool goToEnd)
|
||||
bool LineGraphSelectionHelper::requestCurrentJobNextSegmentVisible(LineGraphScene *scene,
|
||||
bool goToEnd)
|
||||
{
|
||||
//TODO: maybe go to first segment AFTER last segment in current view.
|
||||
//So it may not be 'next' but maybe 2 or 3 segments after current.
|
||||
// TODO: maybe go to first segment AFTER last segment in current view.
|
||||
// So it may not be 'next' but maybe 2 or 3 segments after current.
|
||||
|
||||
JobStopEntry selectedJob = scene->getSelectedJob();
|
||||
if(!selectedJob.jobId)
|
||||
return false; //No job selected, nothing to do
|
||||
if (!selectedJob.jobId)
|
||||
return false; // No job selected, nothing to do
|
||||
|
||||
query q(mDb);
|
||||
|
||||
SegmentInfo info;
|
||||
if(selectedJob.stopId && !goToEnd)
|
||||
if (selectedJob.stopId && !goToEnd)
|
||||
{
|
||||
//Start from current stop and get next stop
|
||||
// Start from current stop and get next stop
|
||||
q.prepare("SELECT s2.job_id, s1.id, MIN(s1.arrival), s1.departure, s1.station_id, c.seg_id"
|
||||
" FROM stops s2"
|
||||
" JOIN stops s1 ON s1.job_id=s2.job_id"
|
||||
|
@ -316,64 +339,64 @@ bool LineGraphSelectionHelper::requestCurrentJobNextSegmentVisible(LineGraphScen
|
|||
" WHERE s2.id=? AND s1.arrival > s2.arrival");
|
||||
q.bind(1, selectedJob.stopId);
|
||||
|
||||
if(q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
if (q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
{
|
||||
auto stop = q.getRows();
|
||||
auto stop = q.getRows();
|
||||
db_id jobId = stop.get<db_id>(0);
|
||||
if(jobId == selectedJob.jobId)
|
||||
if (jobId == selectedJob.jobId)
|
||||
{
|
||||
//Found stop and belongs to requested job
|
||||
info.firstStopId = stop.get<db_id>(1);
|
||||
// Found stop and belongs to requested job
|
||||
info.firstStopId = stop.get<db_id>(1);
|
||||
info.arrivalAndStart = stop.get<QTime>(2);
|
||||
info.departure = stop.get<QTime>(3);
|
||||
info.firstStationId = stop.get<db_id>(4);
|
||||
info.segmentId = stop.get<db_id>(5);
|
||||
info.departure = stop.get<QTime>(3);
|
||||
info.firstStationId = stop.get<db_id>(4);
|
||||
info.segmentId = stop.get<db_id>(5);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!info.firstStopId)
|
||||
if (!info.firstStopId)
|
||||
{
|
||||
//goToEnd or failed to get next stop so go to end anyway
|
||||
// goToEnd or failed to get next stop so go to end anyway
|
||||
|
||||
//Select last station, no segment
|
||||
// Select last station, no segment
|
||||
q.prepare("SELECT s1.id, MAX(s1.arrival), s1.departure, s1.station_id"
|
||||
" FROM stops s1"
|
||||
" WHERE s1.job_id=?");
|
||||
q.bind(1, selectedJob.jobId);
|
||||
|
||||
if(q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
if (q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
{
|
||||
//Found stop and belongs to requested job
|
||||
auto stop = q.getRows();
|
||||
info.firstStopId = stop.get<db_id>(0);
|
||||
// Found stop and belongs to requested job
|
||||
auto stop = q.getRows();
|
||||
info.firstStopId = stop.get<db_id>(0);
|
||||
info.arrivalAndStart = stop.get<QTime>(1);
|
||||
info.departure = stop.get<QTime>(2);
|
||||
info.firstStationId = stop.get<db_id>(3);
|
||||
info.departure = stop.get<QTime>(2);
|
||||
info.firstStationId = stop.get<db_id>(3);
|
||||
}
|
||||
}
|
||||
|
||||
db_id graphObjId = 0;
|
||||
db_id graphObjId = 0;
|
||||
LineGraphType graphType = LineGraphType::NoGraph;
|
||||
|
||||
if(!tryFindNewGraphForJob(selectedJob.jobId, info, graphObjId, graphType))
|
||||
if (!tryFindNewGraphForJob(selectedJob.jobId, info, graphObjId, graphType))
|
||||
{
|
||||
//Could not find a suitable graph, abort
|
||||
// Could not find a suitable graph, abort
|
||||
return false;
|
||||
}
|
||||
|
||||
//NOTE: clear selection to avoid LineGraphManager trying to follow selection
|
||||
//do not emit change because selection might be synced between all scenes
|
||||
//and because it's restored soon after
|
||||
scene->setSelectedJob(JobStopEntry{}, false); //Clear selection
|
||||
// NOTE: clear selection to avoid LineGraphManager trying to follow selection
|
||||
// do not emit change because selection might be synced between all scenes
|
||||
// and because it's restored soon after
|
||||
scene->setSelectedJob(JobStopEntry{}, false); // Clear selection
|
||||
|
||||
//Select the graph
|
||||
// Select the graph
|
||||
scene->loadGraph(graphObjId, graphType);
|
||||
|
||||
//Restore selection
|
||||
// Restore selection
|
||||
selectedJob.stopId = info.firstStopId;
|
||||
scene->setSelectedJob(selectedJob); //This time emit
|
||||
scene->setSelectedJob(selectedJob); // This time emit
|
||||
|
||||
return scene->requestShowZone(info.firstStationId, info.segmentId,
|
||||
info.arrivalAndStart, info.departure);
|
||||
return scene->requestShowZone(info.firstStationId, info.segmentId, info.arrivalAndStart,
|
||||
info.departure);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LINEGRAPHSELECTIONHELPER_H
|
||||
#define LINEGRAPHSELECTIONHELPER_H
|
||||
|
||||
|
@ -14,16 +33,15 @@ class database;
|
|||
class LineGraphSelectionHelper
|
||||
{
|
||||
public:
|
||||
|
||||
/*!
|
||||
* \brief The SegmentInfo struct
|
||||
*/
|
||||
struct SegmentInfo
|
||||
{
|
||||
db_id segmentId = 0;
|
||||
db_id firstStationId = 0;
|
||||
db_id segmentId = 0;
|
||||
db_id firstStationId = 0;
|
||||
db_id secondStationId = 0;
|
||||
db_id firstStopId = 0;
|
||||
db_id firstStopId = 0;
|
||||
|
||||
/*!
|
||||
* \brief arrival and start
|
||||
|
@ -37,7 +55,7 @@ public:
|
|||
|
||||
LineGraphSelectionHelper(sqlite3pp::database &db);
|
||||
|
||||
//Low level API
|
||||
// Low level API
|
||||
|
||||
/*!
|
||||
* \brief find job in current graph
|
||||
|
@ -50,7 +68,7 @@ public:
|
|||
* If scene has NoGraph or does not contain requested job then returns false
|
||||
* SegmentInfo::secondStId is always left empty
|
||||
*/
|
||||
bool tryFindJobStopInGraph(LineGraphScene *scene, db_id jobId, SegmentInfo& info);
|
||||
bool tryFindJobStopInGraph(LineGraphScene *scene, db_id jobId, SegmentInfo &info);
|
||||
|
||||
/*!
|
||||
* \brief find 2 job stops after requested hour
|
||||
|
@ -74,10 +92,11 @@ public:
|
|||
* Try to find a railway line containing the job. If not found try with a railway segment.
|
||||
* If neither line nor segment are found try with first stop station.
|
||||
*/
|
||||
bool tryFindNewGraphForJob(db_id jobId, SegmentInfo &info, db_id &outGraphObjId, LineGraphType &outGraphType);
|
||||
bool tryFindNewGraphForJob(db_id jobId, SegmentInfo &info, db_id &outGraphObjId,
|
||||
LineGraphType &outGraphType);
|
||||
|
||||
public:
|
||||
//High level API
|
||||
// High level API
|
||||
|
||||
/*!
|
||||
* \brief request job selection
|
||||
|
|
|
@ -1,6 +1,24 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "stationgraphobject.h"
|
||||
|
||||
StationGraphObject::StationGraphObject()
|
||||
{
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STATIONGRAPHOBJECT_H
|
||||
#define STATIONGRAPHOBJECT_H
|
||||
|
||||
|
|
|
@ -2,20 +2,19 @@ set(MR_TIMETABLE_PLANNER_SOURCES
|
|||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
|
||||
graph/view/backgroundhelper.h
|
||||
graph/view/hourpanel.h
|
||||
graph/view/linegraphtoolbar.h
|
||||
graph/view/linegraphview.h
|
||||
graph/view/linegraphwidget.h
|
||||
graph/view/linegraphselectionwidget.h
|
||||
graph/view/stationlabelsheader.h
|
||||
|
||||
graph/view/backgroundhelper.cpp
|
||||
graph/view/hourpanel.cpp
|
||||
|
||||
graph/view/linegraphtoolbar.h
|
||||
graph/view/linegraphtoolbar.cpp
|
||||
|
||||
graph/view/linegraphview.h
|
||||
graph/view/linegraphview.cpp
|
||||
|
||||
graph/view/linegraphwidget.h
|
||||
graph/view/linegraphwidget.cpp
|
||||
|
||||
graph/view/linegraphselectionwidget.h
|
||||
graph/view/linegraphselectionwidget.cpp
|
||||
graph/view/stationlabelsheader.cpp
|
||||
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
|
|
@ -1,40 +1,39 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "backgroundhelper.h"
|
||||
|
||||
#include "app/session.h"
|
||||
|
||||
#include "graph/model/linegraphscene.h"
|
||||
#include "graph/model/linegraphscene.h"
|
||||
#include "utils/jobcategorystrings.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include "utils/font_utils.h"
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
/*!
|
||||
* \brief Set font point size
|
||||
* \param font
|
||||
* \param points the value of font size to set
|
||||
* \param p the QPainter which will draw
|
||||
*
|
||||
* This function is needed because each QPaintDevice has different resolution (DPI)
|
||||
* The default value is 72 dots per inch (DPI)
|
||||
* But for example QPdfWriter default is 1200,
|
||||
* QPrinter default is 300 an QWidget depends on the screen,
|
||||
* so it depends on Operating System display settings.
|
||||
*
|
||||
* To avoid differences between screen contents and printed output we
|
||||
* rescale font sizes as it would be if DPI was 72
|
||||
*/
|
||||
inline void setFontPointSizeDPI(QFont &font, int val, QPainter *p)
|
||||
void BackgroundHelper::drawHourPanel(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
const qreal pointSize = val * 72.0 / qreal(p->device()->logicalDpiY());
|
||||
font.setPointSizeF(pointSize);
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawHourPanel(QPainter *painter, const QRectF& rect, double verticalScroll)
|
||||
{
|
||||
//TODO: settings
|
||||
// TODO: settings
|
||||
QFont hourTextFont;
|
||||
setFontPointSizeDPI(hourTextFont, 15, painter);
|
||||
|
||||
|
@ -46,24 +45,24 @@ void BackgroundHelper::drawHourPanel(QPainter *painter, const QRectF& rect, doub
|
|||
painter->setFont(hourTextFont);
|
||||
painter->setPen(hourTextPen);
|
||||
|
||||
//qDebug() << "Drawing hours..." << rect << scroll;
|
||||
// qDebug() << "Drawing hours..." << rect << scroll;
|
||||
const QString fmt(QStringLiteral("%1:00"));
|
||||
|
||||
const qreal top = verticalScroll - vertOffset;
|
||||
const qreal top = rect.top() - vertOffset;
|
||||
const qreal bottom = rect.bottom();
|
||||
|
||||
int h = qFloor(top / hourOffset);
|
||||
if(h < 0)
|
||||
int h = qFloor(top / hourOffset);
|
||||
if (h < 0)
|
||||
h = 0;
|
||||
|
||||
QRectF labelRect = rect;
|
||||
labelRect.setWidth(labelRect.width() * 0.9);
|
||||
labelRect.setHeight(hourOffset);
|
||||
labelRect.moveTop(h * hourOffset - verticalScroll + vertOffset - hourOffset / 2);
|
||||
labelRect.moveTop(h * hourOffset + vertOffset - hourOffset / 2);
|
||||
|
||||
for(; h <= 24 && labelRect.top() <= bottom; h++)
|
||||
for (; h <= 24 && labelRect.top() <= bottom; h++)
|
||||
{
|
||||
//qDebug() << "Y:" << y << fmt.arg(h);
|
||||
// qDebug() << "Y:" << y << fmt.arg(h);
|
||||
painter->drawText(labelRect, fmt.arg(h), QTextOption(Qt::AlignVCenter | Qt::AlignRight));
|
||||
labelRect.moveTop(labelRect.top() + hourOffset);
|
||||
}
|
||||
|
@ -72,45 +71,50 @@ void BackgroundHelper::drawHourPanel(QPainter *painter, const QRectF& rect, doub
|
|||
void BackgroundHelper::drawBackgroundHourLines(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
const double horizOffset = Session->horizOffset;
|
||||
const double vertOffset = Session->vertOffset;
|
||||
const double hourOffset = Session->hourOffset;
|
||||
const double vertOffset = Session->vertOffset;
|
||||
const double hourOffset = Session->hourOffset;
|
||||
|
||||
QPen hourLinePen(AppSettings.getHourLineColor(), AppSettings.getHourLineWidth());
|
||||
|
||||
const qreal x1 = qMax(qreal(horizOffset), rect.left());
|
||||
const qreal x2 = rect.right();
|
||||
const qreal t = qMax(rect.top(), vertOffset);
|
||||
const qreal b = rect.bottom();
|
||||
const qreal t = qMax(rect.top(), vertOffset);
|
||||
const qreal b = rect.bottom();
|
||||
|
||||
|
||||
if(x1 > x2 || b < vertOffset || t > b)
|
||||
if (x1 > x2 || b < vertOffset || t > b)
|
||||
return;
|
||||
|
||||
qreal f = std::remainder(t - vertOffset, hourOffset);
|
||||
int firstH = qCeil((t - vertOffset) / hourOffset);
|
||||
int lastH = qFloor((b - vertOffset) / hourOffset);
|
||||
|
||||
if(f < 0)
|
||||
f += hourOffset;
|
||||
qreal f1 = qFuzzyIsNull(f) ? vertOffset : qMax(t - f + hourOffset, vertOffset);
|
||||
if (firstH > 24 || lastH < 0)
|
||||
return;
|
||||
|
||||
if (firstH < 0)
|
||||
firstH = 0;
|
||||
if (lastH > 24)
|
||||
lastH = 24;
|
||||
|
||||
const qreal l = std::remainder(b - vertOffset, hourOffset);
|
||||
const qreal l1 = b - l;
|
||||
const int n = lastH - firstH + 1;
|
||||
if (n <= 0)
|
||||
return;
|
||||
|
||||
std::size_t n = std::size_t((l1 - f1)/hourOffset) + 1;
|
||||
qreal y = vertOffset + firstH * hourOffset;
|
||||
|
||||
QLineF *arr = new QLineF[n];
|
||||
for(std::size_t i = 0; i < n; i++)
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
arr[i] = QLineF(x1, f1, x2, f1);
|
||||
f1 += hourOffset;
|
||||
arr[i] = QLineF(x1, y, x2, y);
|
||||
y += hourOffset;
|
||||
}
|
||||
|
||||
painter->setPen(hourLinePen);
|
||||
painter->drawLines(arr, int(n));
|
||||
delete [] arr;
|
||||
painter->drawLines(arr, n);
|
||||
delete[] arr;
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawStationHeader(QPainter *painter, LineGraphScene *scene, const QRectF &rect, double horizontalScroll)
|
||||
void BackgroundHelper::drawStationHeader(QPainter *painter, LineGraphScene *scene,
|
||||
const QRectF &rect)
|
||||
{
|
||||
QFont stationFont;
|
||||
stationFont.setBold(true);
|
||||
|
@ -128,23 +132,23 @@ void BackgroundHelper::drawStationHeader(QPainter *painter, LineGraphScene *scen
|
|||
QPen nonElectricPlatfPen(Qt::black);
|
||||
|
||||
const qreal platformOffset = Session->platformOffset;
|
||||
const int stationOffset = Session->stationOffset;
|
||||
const int stationOffset = Session->stationOffset;
|
||||
|
||||
//On left go back by half station offset to center station label
|
||||
//and center platform label by going a back of half platformOffset
|
||||
const int leftOffset = -stationOffset/2 - platformOffset /2;
|
||||
// On left go back by half station offset to center station label
|
||||
// and center platform label by going a back of half platformOffset
|
||||
const int leftOffset = -stationOffset / 2 - platformOffset / 2;
|
||||
|
||||
const double margin = stationOffset * 0.1;
|
||||
const double margin = stationOffset * 0.1;
|
||||
|
||||
QRectF r = rect;
|
||||
QRectF r = rect;
|
||||
|
||||
for(auto st : qAsConst(scene->stations))
|
||||
for (auto st : qAsConst(scene->stations))
|
||||
{
|
||||
const double left = st.xPos + leftOffset - horizontalScroll;
|
||||
const double left = st.xPos + leftOffset;
|
||||
const double right = left + st.platforms.count() * platformOffset + stationOffset;
|
||||
|
||||
if(right <= 0 || left >= r.width())
|
||||
continue; //Skip station, it's not visible
|
||||
if (right < r.left() || left >= r.right())
|
||||
continue; // Skip station, it's not visible
|
||||
|
||||
QRectF labelRect = r;
|
||||
labelRect.setLeft(left + margin);
|
||||
|
@ -156,20 +160,20 @@ void BackgroundHelper::drawStationHeader(QPainter *painter, LineGraphScene *scen
|
|||
painter->drawText(labelRect, Qt::AlignVCenter | Qt::AlignCenter, st.stationName);
|
||||
|
||||
labelRect = r;
|
||||
labelRect.setTop(r.top() + r.height() * 2/3);
|
||||
labelRect.setTop(r.top() + r.height() * 2 / 3);
|
||||
|
||||
//Go to start of station (first platform)
|
||||
//We need to compensate the half stationOffset used to center station label
|
||||
double xPos = left + stationOffset/2;
|
||||
// Go to start of station (first platform)
|
||||
// We need to compensate the half stationOffset used to center station label
|
||||
double xPos = left + stationOffset / 2;
|
||||
labelRect.setWidth(platformOffset);
|
||||
for(const StationGraphObject::PlatformGraph& platf : qAsConst(st.platforms))
|
||||
for (const StationGraphObject::PlatformGraph &platf : qAsConst(st.platforms))
|
||||
{
|
||||
if(platf.platformType.testFlag(utils::StationTrackType::Electrified))
|
||||
if (platf.platformType.testFlag(utils::StationTrackType::Electrified))
|
||||
painter->setPen(electricPlatfPen);
|
||||
else
|
||||
painter->setPen(nonElectricPlatfPen);
|
||||
|
||||
if(platf.platformType.testFlag(utils::StationTrackType::Through))
|
||||
if (platf.platformType.testFlag(utils::StationTrackType::Through))
|
||||
painter->setFont(platfBoldFont);
|
||||
else
|
||||
painter->setFont(platfNormalFont);
|
||||
|
@ -187,33 +191,33 @@ void BackgroundHelper::drawStations(QPainter *painter, LineGraphScene *scene, co
|
|||
{
|
||||
const QRgb white = qRgb(255, 255, 255);
|
||||
|
||||
//const int horizOffset = Session->horizOffset;
|
||||
// const int horizOffset = Session->horizOffset;
|
||||
const int vertOffset = Session->vertOffset;
|
||||
//const int stationOffset = Session->stationOffset;
|
||||
const double platfOffset = Session->platformOffset;
|
||||
const int lastY = vertOffset + Session->hourOffset * 24 + 10;
|
||||
// const int stationOffset = Session->stationOffset;
|
||||
const double platfOffset = Session->platformOffset;
|
||||
const int lastY = vertOffset + Session->hourOffset * 24 + 10;
|
||||
|
||||
const int width = AppSettings.getPlatformLineWidth();
|
||||
const int width = AppSettings.getPlatformLineWidth();
|
||||
const QColor mainPlatfColor = AppSettings.getMainPlatfColor();
|
||||
|
||||
QPen platfPen (mainPlatfColor, width);
|
||||
QPen platfPen(mainPlatfColor, width);
|
||||
|
||||
QPointF top(0, vertOffset);
|
||||
QPointF bottom(0, lastY);
|
||||
|
||||
for(const StationGraphObject &st : qAsConst(scene->stations))
|
||||
for (const StationGraphObject &st : qAsConst(scene->stations))
|
||||
{
|
||||
const double left = st.xPos;
|
||||
const double left = st.xPos;
|
||||
const double right = left + st.platforms.count() * platfOffset;
|
||||
|
||||
if(left > rect.right() || right < rect.left())
|
||||
continue; //Skip station, it's not visible
|
||||
if (left > rect.right() || right < rect.left())
|
||||
continue; // Skip station, it's not visible
|
||||
|
||||
top.rx() = bottom.rx() = st.xPos;
|
||||
|
||||
for(const StationGraphObject::PlatformGraph& platf : st.platforms)
|
||||
for (const StationGraphObject::PlatformGraph &platf : st.platforms)
|
||||
{
|
||||
if(platf.color == white)
|
||||
if (platf.color == white)
|
||||
platfPen.setColor(mainPlatfColor);
|
||||
else
|
||||
platfPen.setColor(platf.color);
|
||||
|
@ -228,9 +232,11 @@ void BackgroundHelper::drawStations(QPainter *painter, LineGraphScene *scene, co
|
|||
}
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawJobStops(QPainter *painter, LineGraphScene *scene, const QRectF &rect, bool drawSelection)
|
||||
void BackgroundHelper::drawJobStops(QPainter *painter, LineGraphScene *scene, const QRectF &rect,
|
||||
bool drawSelection)
|
||||
{
|
||||
const double platfOffset = Session->platformOffset;
|
||||
const double platfOffset = Session->platformOffset;
|
||||
const double stationOffset = Session->stationOffset;
|
||||
|
||||
QFont jobNameFont;
|
||||
setFontPointSizeDPI(jobNameFont, 20, painter);
|
||||
|
@ -244,7 +250,7 @@ void BackgroundHelper::drawJobStops(QPainter *painter, LineGraphScene *scene, co
|
|||
QPen selectedJobPen;
|
||||
|
||||
const JobStopEntry selectedJob = scene->getSelectedJob();
|
||||
if(drawSelection && selectedJob.jobId)
|
||||
if (drawSelection && selectedJob.jobId)
|
||||
{
|
||||
selectedJobPen.setWidthF(jobPen.widthF() * SelectedJobWidthFactor);
|
||||
selectedJobPen.setCapStyle(Qt::RoundCap);
|
||||
|
@ -261,44 +267,48 @@ void BackgroundHelper::drawJobStops(QPainter *painter, LineGraphScene *scene, co
|
|||
JobCategory lastJobCategory = JobCategory::NCategories;
|
||||
QTextOption textOption(Qt::AlignTop | Qt::AlignLeft);
|
||||
|
||||
for(const StationGraphObject &st : qAsConst(scene->stations))
|
||||
for (const StationGraphObject &st : qAsConst(scene->stations))
|
||||
{
|
||||
const double left = st.xPos;
|
||||
const double left = st.xPos;
|
||||
const double right = left + st.platforms.count() * platfOffset;
|
||||
|
||||
if(left > rect.right() || right < rect.left())
|
||||
continue; //Skip station, it's not visible
|
||||
// Set a maximum right edge to Job labels
|
||||
// This allows to determine if they have to be drawn
|
||||
const double maxJobLabelX = right + stationOffset;
|
||||
|
||||
if (left > rect.right() || maxJobLabelX < rect.left())
|
||||
continue; // Skip station, it's not visible
|
||||
|
||||
top.rx() = bottom.rx() = st.xPos;
|
||||
|
||||
for(const StationGraphObject::PlatformGraph& platf : st.platforms)
|
||||
for (const StationGraphObject::PlatformGraph &platf : st.platforms)
|
||||
{
|
||||
for(const StationGraphObject::JobStopGraph& jobStop : platf.jobStops)
|
||||
for (const StationGraphObject::JobStopGraph &jobStop : platf.jobStops)
|
||||
{
|
||||
//NOTE: departure comes AFTER arrival in time, opposite than job segment
|
||||
if(jobStop.arrivalY > rect.bottom() || jobStop.departureY < rect.top())
|
||||
continue; //Skip, job not visible
|
||||
// NOTE: departure comes AFTER arrival in time, opposite than job segment
|
||||
if (jobStop.arrivalY > rect.bottom() || jobStop.departureY < rect.top())
|
||||
continue; // Skip, job not visible
|
||||
|
||||
top.setY(jobStop.arrivalY);
|
||||
bottom.setY(jobStop.departureY);
|
||||
|
||||
const bool nullStopDuration = qFuzzyCompare(top.y(), bottom.y());
|
||||
|
||||
if(drawSelection && selectedJob.jobId == jobStop.stop.jobId)
|
||||
if (drawSelection && selectedJob.jobId == jobStop.stop.jobId)
|
||||
{
|
||||
//Draw selection around segment
|
||||
// Draw selection around segment
|
||||
painter->setPen(selectedJobPen);
|
||||
|
||||
if(nullStopDuration)
|
||||
if (nullStopDuration)
|
||||
painter->drawPoint(top);
|
||||
else
|
||||
painter->drawLine(top, bottom);
|
||||
|
||||
//Reset pen
|
||||
// Reset pen
|
||||
painter->setPen(jobPen);
|
||||
}
|
||||
|
||||
if(lastJobCategory != jobStop.stop.category)
|
||||
if (lastJobCategory != jobStop.stop.category)
|
||||
{
|
||||
QColor color = Session->colorForCat(jobStop.stop.category);
|
||||
jobPen.setColor(color);
|
||||
|
@ -306,15 +316,20 @@ void BackgroundHelper::drawJobStops(QPainter *painter, LineGraphScene *scene, co
|
|||
lastJobCategory = jobStop.stop.category;
|
||||
}
|
||||
|
||||
if(nullStopDuration)
|
||||
if (nullStopDuration)
|
||||
painter->drawPoint(top);
|
||||
else
|
||||
painter->drawLine(top, bottom);
|
||||
|
||||
if(jobStop.drawLabel)
|
||||
if (jobStop.drawLabel)
|
||||
{
|
||||
const QString jobName = JobCategoryName::jobName(jobStop.stop.jobId, jobStop.stop.category);
|
||||
QRectF r(top.x() + platfOffset / 2, top.y(), platfOffset * 4, 25);
|
||||
const QString jobName =
|
||||
JobCategoryName::jobName(jobStop.stop.jobId, jobStop.stop.category);
|
||||
|
||||
// Put label a bit to the left in respect to the stop arrival point
|
||||
// Calculate width so it doesn't go after maxJobLabelX
|
||||
const qreal topWithMargin = top.x() + platfOffset / 2;
|
||||
QRectF r(topWithMargin, top.y(), maxJobLabelX - topWithMargin, 25);
|
||||
painter->drawText(r, jobName, textOption);
|
||||
}
|
||||
}
|
||||
|
@ -325,7 +340,8 @@ void BackgroundHelper::drawJobStops(QPainter *painter, LineGraphScene *scene, co
|
|||
}
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawJobSegments(QPainter *painter, LineGraphScene *scene, const QRectF &rect, bool drawSelection)
|
||||
void BackgroundHelper::drawJobSegments(QPainter *painter, LineGraphScene *scene, const QRectF &rect,
|
||||
bool drawSelection)
|
||||
{
|
||||
const double stationOffset = Session->stationOffset;
|
||||
|
||||
|
@ -344,7 +360,7 @@ void BackgroundHelper::drawJobSegments(QPainter *painter, LineGraphScene *scene,
|
|||
QPen selectedJobPen;
|
||||
|
||||
const JobStopEntry selectedJob = scene->getSelectedJob();
|
||||
if(drawSelection && selectedJob.jobId)
|
||||
if (drawSelection && selectedJob.jobId)
|
||||
{
|
||||
selectedJobPen.setWidthF(jobPen.widthF() * SelectedJobWidthFactor);
|
||||
selectedJobPen.setCapStyle(Qt::RoundCap);
|
||||
|
@ -358,47 +374,47 @@ void BackgroundHelper::drawJobSegments(QPainter *painter, LineGraphScene *scene,
|
|||
JobCategory lastJobCategory = JobCategory::NCategories;
|
||||
QTextOption textOption(Qt::AlignCenter);
|
||||
|
||||
//Iterate until one but last
|
||||
//This way we can always acces next station
|
||||
for(int i = 0; i < scene->stationPositions.size() - 1; i++)
|
||||
// Iterate until one but last
|
||||
// This way we can always acces next station
|
||||
for (int i = 0; i < scene->stationPositions.size() - 1; i++)
|
||||
{
|
||||
const LineGraphScene::StationPosEntry& stPos = scene->stationPositions.at(i);
|
||||
const LineGraphScene::StationPosEntry &stPos = scene->stationPositions.at(i);
|
||||
|
||||
const double left = stPos.xPos;
|
||||
double right = 0;
|
||||
const double left = stPos.xPos;
|
||||
double right = 0;
|
||||
|
||||
if(i < scene->stationPositions.size() - 2)
|
||||
if (i < scene->stationPositions.size() - 2)
|
||||
{
|
||||
const LineGraphScene::StationPosEntry& afterNextPos = scene->stationPositions.at(i + 2);
|
||||
right = afterNextPos.xPos - stationOffset;
|
||||
const LineGraphScene::StationPosEntry &afterNextPos = scene->stationPositions.at(i + 2);
|
||||
right = afterNextPos.xPos - stationOffset;
|
||||
}
|
||||
else
|
||||
{
|
||||
right = rect.right(); //Last station, use all space on right side
|
||||
right = rect.right(); // Last station, use all space on right side
|
||||
}
|
||||
|
||||
if(left > rect.right() || right < rect.left())
|
||||
continue; //Skip station, it's not visible
|
||||
if (left > rect.right() || right < rect.left())
|
||||
continue; // Skip station, it's not visible
|
||||
|
||||
for(const LineGraphScene::JobSegmentGraph& job : stPos.nextSegmentJobGraphs)
|
||||
for (const LineGraphScene::JobSegmentGraph &job : stPos.nextSegmentJobGraphs)
|
||||
{
|
||||
//NOTE: departure comes BEFORE arrival in time, opposite than job stop
|
||||
if(job.fromDeparture.y() > rect.bottom() || job.toArrival.y() < rect.top())
|
||||
continue; //Skip, job not visible
|
||||
// NOTE: departure comes BEFORE arrival in time, opposite than job stop
|
||||
if (job.fromDeparture.y() > rect.bottom() || job.toArrival.y() < rect.top())
|
||||
continue; // Skip, job not visible
|
||||
|
||||
const QLineF line(job.fromDeparture, job.toArrival);
|
||||
|
||||
if(drawSelection && selectedJob.jobId == job.jobId)
|
||||
if (drawSelection && selectedJob.jobId == job.jobId)
|
||||
{
|
||||
//Draw selection around segment
|
||||
// Draw selection around segment
|
||||
painter->setPen(selectedJobPen);
|
||||
painter->drawLine(line);
|
||||
|
||||
//Reset pen
|
||||
// Reset pen
|
||||
painter->setPen(jobPen);
|
||||
}
|
||||
|
||||
if(lastJobCategory != job.category)
|
||||
if (lastJobCategory != job.category)
|
||||
{
|
||||
QColor color = Session->colorForCat(job.category);
|
||||
jobPen.setColor(color);
|
||||
|
@ -410,35 +426,35 @@ void BackgroundHelper::drawJobSegments(QPainter *painter, LineGraphScene *scene,
|
|||
|
||||
const QString jobName = JobCategoryName::jobName(job.jobId, job.category);
|
||||
|
||||
//Save old transformation to reset it after drawing text
|
||||
// Save old transformation to reset it after drawing text
|
||||
const QTransform oldTransf = painter->transform();
|
||||
|
||||
//Move to line center, it will be rotation pivot
|
||||
// Move to line center, it will be rotation pivot
|
||||
painter->translate(line.center());
|
||||
|
||||
//Rotate by line angle
|
||||
// Rotate by line angle
|
||||
qreal angle = line.angle();
|
||||
if(job.fromDeparture.x() > job.toArrival.x())
|
||||
angle += 180.0; //Prevent flipping text
|
||||
if (job.fromDeparture.x() > job.toArrival.x())
|
||||
angle += 180.0; // Prevent flipping text
|
||||
|
||||
painter->rotate(-angle); //minus because QPainter wants clockwise angle
|
||||
painter->rotate(-angle); // minus because QPainter wants clockwise angle
|
||||
|
||||
const double lineLength = line.length();
|
||||
QRectF textRect(-lineLength / 2, -30, lineLength, 25);
|
||||
|
||||
//Try to avoid overlapping text of crossing jobs, move text towards arrival
|
||||
if(job.toArrival.x() > job.fromDeparture.x())
|
||||
// Try to avoid overlapping text of crossing jobs, move text towards arrival
|
||||
if (job.toArrival.x() > job.fromDeparture.x())
|
||||
textRect.moveLeft(textRect.left() + lineLength / 5);
|
||||
else
|
||||
textRect.moveLeft(textRect.left() - lineLength / 5);
|
||||
|
||||
textRect = painter->boundingRect(textRect, jobName, textOption);
|
||||
|
||||
//Draw a semi transparent background to ease text reading
|
||||
// Draw a semi transparent background to ease text reading
|
||||
painter->fillRect(textRect, textBackground);
|
||||
painter->drawText(textRect, jobName, QTextOption(Qt::AlignCenter));
|
||||
painter->drawText(textRect, jobName, textOption);
|
||||
|
||||
//Reset to old transformation
|
||||
// Reset to old transformation
|
||||
painter->setTransform(oldTransf);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKGROUNDHELPER_H
|
||||
#define BACKGROUNDHELPER_H
|
||||
|
||||
|
@ -8,32 +27,32 @@ class QPainter;
|
|||
class LineGraphScene;
|
||||
|
||||
/*!
|
||||
* \brief Helper class to render LineGraphView contents
|
||||
* \brief Helper class to render LineGraphScene contents
|
||||
*
|
||||
* Contains static helper functions to draw each part of the view
|
||||
*
|
||||
* \sa LineGraphView
|
||||
* \sa HourPanel
|
||||
* \sa StationLabelsHeader
|
||||
* \sa LineGraphScene
|
||||
*/
|
||||
class BackgroundHelper
|
||||
{
|
||||
public:
|
||||
static void drawHourPanel(QPainter *painter, const QRectF& rect, double verticalScroll);
|
||||
static void drawHourPanel(QPainter *painter, const QRectF &rect);
|
||||
|
||||
static void drawBackgroundHourLines(QPainter *painter, const QRectF& rect);
|
||||
static void drawBackgroundHourLines(QPainter *painter, const QRectF &rect);
|
||||
|
||||
static void drawStationHeader(QPainter *painter, LineGraphScene *scene, const QRectF& rect, double horizontalScroll);
|
||||
static void drawStationHeader(QPainter *painter, LineGraphScene *scene, const QRectF &rect);
|
||||
|
||||
static void drawStations(QPainter *painter, LineGraphScene *scene, const QRectF& rect);
|
||||
static void drawStations(QPainter *painter, LineGraphScene *scene, const QRectF &rect);
|
||||
|
||||
static void drawJobStops(QPainter *painter, LineGraphScene *scene, const QRectF& rect, bool drawSelection);
|
||||
static void drawJobStops(QPainter *painter, LineGraphScene *scene, const QRectF &rect,
|
||||
bool drawSelection);
|
||||
|
||||
static void drawJobSegments(QPainter *painter, LineGraphScene *scene, const QRectF &rect, bool drawSelection);
|
||||
static void drawJobSegments(QPainter *painter, LineGraphScene *scene, const QRectF &rect,
|
||||
bool drawSelection);
|
||||
|
||||
public:
|
||||
static constexpr double SelectedJobWidthFactor = 3.0;
|
||||
static constexpr int SelectedJobAlphaFactor = 127;
|
||||
static constexpr int SelectedJobAlphaFactor = 127;
|
||||
};
|
||||
|
||||
#endif // BACKGROUNDHELPER_H
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
#include "hourpanel.h"
|
||||
|
||||
#include "app/session.h"
|
||||
|
||||
#include "backgroundhelper.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
HourPanel::HourPanel(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
verticalScroll(0),
|
||||
mZoom(100)
|
||||
{
|
||||
connect(&AppSettings, &MRTPSettings::jobGraphOptionsChanged, this, qOverload<>(&HourPanel::update));
|
||||
}
|
||||
|
||||
void HourPanel::setScroll(int value)
|
||||
{
|
||||
verticalScroll = value;
|
||||
update();
|
||||
}
|
||||
|
||||
void HourPanel::setZoom(int val)
|
||||
{
|
||||
mZoom = val;
|
||||
update();
|
||||
}
|
||||
|
||||
void HourPanel::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter painter(this);
|
||||
QColor c(255, 255, 255, 220);
|
||||
painter.fillRect(rect(), c);
|
||||
|
||||
const double scaleFactor = mZoom / 100.0;
|
||||
|
||||
const double sceneScroll = verticalScroll / scaleFactor;
|
||||
QRectF sceneRect = rect();
|
||||
sceneRect.setSize(sceneRect.size() / scaleFactor);
|
||||
|
||||
painter.scale(scaleFactor, scaleFactor);
|
||||
|
||||
BackgroundHelper::drawHourPanel(&painter, sceneRect, sceneScroll);
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
#ifndef HOURPANEL_H
|
||||
#define HOURPANEL_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
/*!
|
||||
* \brief Helper widget to draw hour labels
|
||||
*
|
||||
* Draws hour labels on the view vertical header
|
||||
*
|
||||
* \sa LineGraphScene
|
||||
* \sa LineGraphView
|
||||
* \sa BackgroundHelper
|
||||
*/
|
||||
class HourPanel : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit HourPanel(QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void setScroll(int value);
|
||||
void setZoom(int val);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *);
|
||||
|
||||
private:
|
||||
int verticalScroll;
|
||||
int mZoom;
|
||||
};
|
||||
|
||||
#endif // HOURPANEL_H
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "linegraphselectionwidget.h"
|
||||
|
||||
#include <QComboBox>
|
||||
|
@ -30,20 +49,22 @@ LineGraphSelectionWidget::LineGraphSelectionWidget(QWidget *parent) :
|
|||
|
||||
QStringList items;
|
||||
items.reserve(int(LineGraphType::NTypes));
|
||||
for(int i = 0; i < int(LineGraphType::NTypes); i++)
|
||||
for (int i = 0; i < int(LineGraphType::NTypes); i++)
|
||||
items.append(utils::getLineGraphTypeName(LineGraphType(i)));
|
||||
graphTypeCombo->addItems(items);
|
||||
graphTypeCombo->setCurrentIndex(0);
|
||||
|
||||
connect(graphTypeCombo, qOverload<int>(&QComboBox::activated), this, &LineGraphSelectionWidget::onTypeComboActivated);
|
||||
connect(objectCombo, &CustomCompletionLineEdit::completionDone, this, &LineGraphSelectionWidget::onCompletionDone);
|
||||
connect(graphTypeCombo, qOverload<int>(&QComboBox::activated), this,
|
||||
&LineGraphSelectionWidget::onTypeComboActivated);
|
||||
connect(objectCombo, &CustomCompletionLineEdit::completionDone, this,
|
||||
&LineGraphSelectionWidget::onCompletionDone);
|
||||
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
|
||||
}
|
||||
|
||||
LineGraphSelectionWidget::~LineGraphSelectionWidget()
|
||||
{
|
||||
if(matchModel)
|
||||
if (matchModel)
|
||||
{
|
||||
objectCombo->setModel(nullptr);
|
||||
delete matchModel;
|
||||
|
@ -58,7 +79,7 @@ LineGraphType LineGraphSelectionWidget::getGraphType() const
|
|||
|
||||
void LineGraphSelectionWidget::setGraphType(LineGraphType type)
|
||||
{
|
||||
if(getGraphType() == type)
|
||||
if (getGraphType() == type)
|
||||
return;
|
||||
|
||||
graphTypeCombo->setCurrentIndex(int(type));
|
||||
|
@ -79,16 +100,16 @@ const QString &LineGraphSelectionWidget::getObjectName() const
|
|||
|
||||
void LineGraphSelectionWidget::setObjectId(db_id objectId, const QString &name)
|
||||
{
|
||||
if(m_graphType == LineGraphType::NoGraph)
|
||||
return; //Object ID must be null
|
||||
if (m_graphType == LineGraphType::NoGraph)
|
||||
return; // Object ID must be null
|
||||
|
||||
m_name = name;
|
||||
if(!objectId)
|
||||
if (!objectId)
|
||||
m_name.clear();
|
||||
|
||||
objectCombo->setData(objectId, name);
|
||||
|
||||
if(m_objectId != objectId)
|
||||
if (m_objectId != objectId)
|
||||
emit graphChanged(int(m_graphType), m_objectId);
|
||||
}
|
||||
|
||||
|
@ -100,7 +121,7 @@ void LineGraphSelectionWidget::onTypeComboActivated(int index)
|
|||
|
||||
void LineGraphSelectionWidget::onCompletionDone()
|
||||
{
|
||||
if(!objectCombo->getData(m_objectId, m_name))
|
||||
if (!objectCombo->getData(m_objectId, m_name))
|
||||
return;
|
||||
|
||||
emit graphChanged(int(m_graphType), m_objectId);
|
||||
|
@ -108,17 +129,17 @@ void LineGraphSelectionWidget::onCompletionDone()
|
|||
|
||||
void LineGraphSelectionWidget::setupModel(LineGraphType type)
|
||||
{
|
||||
if(type != m_graphType)
|
||||
if (type != m_graphType)
|
||||
{
|
||||
//Clear old model
|
||||
if(matchModel)
|
||||
// Clear old model
|
||||
if (matchModel)
|
||||
{
|
||||
objectCombo->setModel(nullptr);
|
||||
delete matchModel;
|
||||
matchModel = nullptr;
|
||||
}
|
||||
|
||||
//Manually clear line edit
|
||||
// Manually clear line edit
|
||||
m_objectId = 0;
|
||||
m_name.clear();
|
||||
objectCombo->setData(m_objectId, m_name);
|
||||
|
@ -128,7 +149,7 @@ void LineGraphSelectionWidget::setupModel(LineGraphType type)
|
|||
case LineGraphType::NoGraph:
|
||||
default:
|
||||
{
|
||||
//Prevent recursion on loadGraph() calling back to us
|
||||
// Prevent recursion on loadGraph() calling back to us
|
||||
type = LineGraphType::NoGraph;
|
||||
break;
|
||||
}
|
||||
|
@ -149,14 +170,13 @@ void LineGraphSelectionWidget::setupModel(LineGraphType type)
|
|||
case LineGraphType::RailwayLine:
|
||||
{
|
||||
LinesMatchModel *m = new LinesMatchModel(Session->m_Db, true, this);
|
||||
matchModel = m;
|
||||
matchModel = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(matchModel)
|
||||
if (matchModel)
|
||||
objectCombo->setModel(matchModel);
|
||||
}
|
||||
m_graphType = type;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LINEGRAPHSELECTIONWIDGET_H
|
||||
#define LINEGRAPHSELECTIONWIDGET_H
|
||||
|
||||
|
@ -32,7 +51,7 @@ public:
|
|||
|
||||
db_id getObjectId() const;
|
||||
const QString &getObjectName() const;
|
||||
void setObjectId(db_id objectId, const QString& name);
|
||||
void setObjectId(db_id objectId, const QString &name);
|
||||
|
||||
void setName(const QString &newName);
|
||||
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "linegraphtoolbar.h"
|
||||
|
||||
#include "graph/model/linegraphscene.h"
|
||||
|
@ -30,7 +49,8 @@ LineGraphToolbar::LineGraphToolbar(QWidget *parent) :
|
|||
lay->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
selectionWidget = new LineGraphSelectionWidget;
|
||||
connect(selectionWidget, &LineGraphSelectionWidget::graphChanged, this, &LineGraphToolbar::onWidgetGraphChanged);
|
||||
connect(selectionWidget, &LineGraphSelectionWidget::graphChanged, this,
|
||||
&LineGraphToolbar::onWidgetGraphChanged);
|
||||
lay->addWidget(selectionWidget);
|
||||
|
||||
redrawBut = new QPushButton(tr("Redraw"));
|
||||
|
@ -51,57 +71,59 @@ LineGraphToolbar::LineGraphToolbar(QWidget *parent) :
|
|||
zoomSpinBox->setRange(25, 400);
|
||||
zoomSpinBox->setValue(mZoom);
|
||||
zoomSpinBox->setSuffix(QChar('%'));
|
||||
connect(zoomSpinBox, qOverload<int>(&QSpinBox::valueChanged), this, &LineGraphToolbar::updateZoomLevel);
|
||||
connect(zoomSpinBox, qOverload<int>(&QSpinBox::valueChanged), this,
|
||||
&LineGraphToolbar::updateZoomLevel);
|
||||
lay->addWidget(zoomSpinBox);
|
||||
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
|
||||
|
||||
//Accept focus events by click
|
||||
// Accept focus events by click
|
||||
setFocusPolicy(Qt::ClickFocus);
|
||||
|
||||
//Install event filter to catch focus events on children widgets
|
||||
for(QObject *child : selectionWidget->children())
|
||||
// Install event filter to catch focus events on children widgets
|
||||
for (QObject *child : selectionWidget->children())
|
||||
{
|
||||
if(child->isWidgetType())
|
||||
if (child->isWidgetType())
|
||||
child->installEventFilter(this);
|
||||
}
|
||||
|
||||
//Install event filter on Zoom Slider to catch double click
|
||||
// Install event filter on Zoom Slider to catch double click
|
||||
zoomSlider->installEventFilter(this);
|
||||
}
|
||||
|
||||
LineGraphToolbar::~LineGraphToolbar()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void LineGraphToolbar::setScene(LineGraphScene *scene)
|
||||
{
|
||||
if(m_scene)
|
||||
if (m_scene)
|
||||
{
|
||||
disconnect(m_scene, &LineGraphScene::graphChanged, this, &LineGraphToolbar::onSceneGraphChanged);
|
||||
disconnect(m_scene, &LineGraphScene::graphChanged, this,
|
||||
&LineGraphToolbar::onSceneGraphChanged);
|
||||
disconnect(m_scene, &QObject::destroyed, this, &LineGraphToolbar::onSceneDestroyed);
|
||||
}
|
||||
m_scene = scene;
|
||||
if(m_scene)
|
||||
if (m_scene)
|
||||
{
|
||||
connect(m_scene, &LineGraphScene::graphChanged, this, &LineGraphToolbar::onSceneGraphChanged);
|
||||
connect(m_scene, &LineGraphScene::graphChanged, this,
|
||||
&LineGraphToolbar::onSceneGraphChanged);
|
||||
connect(m_scene, &QObject::destroyed, this, &LineGraphToolbar::onSceneDestroyed);
|
||||
}
|
||||
}
|
||||
|
||||
bool LineGraphToolbar::eventFilter(QObject *watched, QEvent *ev)
|
||||
{
|
||||
if(ev->type() == QEvent::FocusIn)
|
||||
if (ev->type() == QEvent::FocusIn)
|
||||
{
|
||||
//If any of our child widgets receives focus, activate our scene
|
||||
if(m_scene)
|
||||
// If any of our child widgets receives focus, activate our scene
|
||||
if (m_scene)
|
||||
m_scene->activateScene();
|
||||
}
|
||||
|
||||
if(watched == zoomSlider && ev->type() == QEvent::MouseButtonDblClick)
|
||||
if (watched == zoomSlider && ev->type() == QEvent::MouseButtonDblClick)
|
||||
{
|
||||
//Zoom Slider was double clicked, reset zoom level to 100
|
||||
// Zoom Slider was double clicked, reset zoom level to 100
|
||||
updateZoomLevel(100);
|
||||
}
|
||||
|
||||
|
@ -111,14 +133,14 @@ bool LineGraphToolbar::eventFilter(QObject *watched, QEvent *ev)
|
|||
void LineGraphToolbar::resetToolbarToScene()
|
||||
{
|
||||
LineGraphType type = LineGraphType::NoGraph;
|
||||
db_id objectId = 0;
|
||||
db_id objectId = 0;
|
||||
QString name;
|
||||
|
||||
if(m_scene)
|
||||
if (m_scene)
|
||||
{
|
||||
type = m_scene->getGraphType();
|
||||
type = m_scene->getGraphType();
|
||||
objectId = m_scene->getGraphObjectId();
|
||||
name = m_scene->getGraphObjectName();
|
||||
name = m_scene->getGraphObjectName();
|
||||
}
|
||||
|
||||
selectionWidget->setGraphType(type);
|
||||
|
@ -127,7 +149,7 @@ void LineGraphToolbar::resetToolbarToScene()
|
|||
|
||||
void LineGraphToolbar::updateZoomLevel(int zoom)
|
||||
{
|
||||
if(mZoom == zoom)
|
||||
if (mZoom == zoom)
|
||||
return;
|
||||
|
||||
mZoom = zoom;
|
||||
|
@ -141,13 +163,13 @@ void LineGraphToolbar::updateZoomLevel(int zoom)
|
|||
void LineGraphToolbar::onWidgetGraphChanged(int type, db_id objectId)
|
||||
{
|
||||
LineGraphType graphType = LineGraphType(type);
|
||||
if(graphType == LineGraphType::NoGraph)
|
||||
if (graphType == LineGraphType::NoGraph)
|
||||
objectId = 0;
|
||||
|
||||
if(graphType != LineGraphType::NoGraph && !objectId)
|
||||
return; //User is still selecting an object
|
||||
if (graphType != LineGraphType::NoGraph && !objectId)
|
||||
return; // User is still selecting an object
|
||||
|
||||
if(m_scene)
|
||||
if (m_scene)
|
||||
m_scene->loadGraph(objectId, graphType);
|
||||
}
|
||||
|
||||
|
@ -156,7 +178,7 @@ void LineGraphToolbar::onSceneGraphChanged(int type, db_id objectId)
|
|||
selectionWidget->setGraphType(LineGraphType(type));
|
||||
|
||||
QString name;
|
||||
if(m_scene && m_scene->getGraphObjectId() == objectId)
|
||||
if (m_scene && m_scene->getGraphObjectId() == objectId)
|
||||
name = m_scene->getGraphObjectName();
|
||||
selectionWidget->setObjectId(objectId, name);
|
||||
}
|
||||
|
@ -164,12 +186,12 @@ void LineGraphToolbar::onSceneGraphChanged(int type, db_id objectId)
|
|||
void LineGraphToolbar::onSceneDestroyed()
|
||||
{
|
||||
m_scene = nullptr;
|
||||
resetToolbarToScene(); //Clear UI
|
||||
resetToolbarToScene(); // Clear UI
|
||||
}
|
||||
|
||||
void LineGraphToolbar::focusInEvent(QFocusEvent *e)
|
||||
{
|
||||
if(m_scene)
|
||||
if (m_scene)
|
||||
m_scene->activateScene();
|
||||
|
||||
QWidget::focusInEvent(e);
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LINEGRAPHTOOLBAR_H
|
||||
#define LINEGRAPHTOOLBAR_H
|
||||
|
||||
|
@ -26,11 +45,13 @@ public:
|
|||
void setScene(LineGraphScene *scene);
|
||||
|
||||
/*!
|
||||
* \brief listen to focus events
|
||||
* \brief listen to focus events and to slider double click
|
||||
*
|
||||
* If a children widget gets focus events we do not know it
|
||||
* So we install an event filter on every children and listen
|
||||
*
|
||||
* When \ref zoomSlider gets double clicked, reset zoom to 100%
|
||||
*
|
||||
* \sa focusInEvent()
|
||||
*/
|
||||
bool eventFilter(QObject *watched, QEvent *ev) override;
|
||||
|
|
|
@ -1,156 +1,58 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "linegraphview.h"
|
||||
|
||||
#include "graph/model/linegraphscene.h"
|
||||
|
||||
#include "backgroundhelper.h"
|
||||
#include "stationlabelsheader.h"
|
||||
#include "hourpanel.h"
|
||||
|
||||
#include "utils/jobcategorystrings.h"
|
||||
|
||||
#include "app/session.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPaintEvent>
|
||||
|
||||
#include <QScrollBar>
|
||||
|
||||
#include <QToolTip>
|
||||
#include <QHelpEvent>
|
||||
|
||||
LineGraphView::LineGraphView(QWidget *parent) :
|
||||
QAbstractScrollArea(parent),
|
||||
m_scene(nullptr),
|
||||
mZoom(100)
|
||||
BasicGraphView(parent)
|
||||
{
|
||||
QPalette pal = palette();
|
||||
pal.setColor(backgroundRole(), Qt::white);
|
||||
setPalette(pal);
|
||||
|
||||
horizontalScrollBar()->setSingleStep(20);
|
||||
verticalScrollBar()->setSingleStep(20);
|
||||
|
||||
hourPanel = new HourPanel(this);
|
||||
stationHeader = new StationLabelsHeader(this);
|
||||
|
||||
connect(verticalScrollBar(), &QScrollBar::valueChanged, hourPanel, &HourPanel::setScroll);
|
||||
connect(horizontalScrollBar(), &QScrollBar::valueChanged, stationHeader, &StationLabelsHeader::setScroll);
|
||||
connect(&AppSettings, &MRTPSettings::jobGraphOptionsChanged, this, &LineGraphView::resizeHeaders);
|
||||
|
||||
resizeHeaders();
|
||||
}
|
||||
|
||||
LineGraphScene *LineGraphView::scene() const
|
||||
{
|
||||
return m_scene;
|
||||
}
|
||||
|
||||
void LineGraphView::setScene(LineGraphScene *newScene)
|
||||
{
|
||||
stationHeader->setScene(newScene);
|
||||
|
||||
if(m_scene)
|
||||
{
|
||||
disconnect(m_scene, &LineGraphScene::redrawGraph, this, &LineGraphView::redrawGraph);
|
||||
disconnect(m_scene, &LineGraphScene::requestShowRect, this, &LineGraphView::ensureRectVisible);
|
||||
disconnect(m_scene, &QObject::destroyed, this, &LineGraphView::onSceneDestroyed);
|
||||
}
|
||||
m_scene = newScene;
|
||||
if(m_scene)
|
||||
{
|
||||
connect(m_scene, &LineGraphScene::redrawGraph, this, &LineGraphView::redrawGraph);
|
||||
connect(m_scene, &LineGraphScene::requestShowRect, this, &LineGraphView::ensureRectVisible);
|
||||
connect(m_scene, &QObject::destroyed, this, &LineGraphView::onSceneDestroyed);
|
||||
}
|
||||
redrawGraph();
|
||||
}
|
||||
|
||||
void LineGraphView::redrawGraph()
|
||||
{
|
||||
updateScrollBars();
|
||||
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
void LineGraphView::ensureRectVisible(const QRectF &r)
|
||||
{
|
||||
QRect r2 = r.toRect();
|
||||
const QPoint c = r2.center();
|
||||
ensureVisible(c.x(), c.y(), r2.width() / 2, r2.height() / 2);
|
||||
}
|
||||
|
||||
void LineGraphView::setZoomLevel(int zoom)
|
||||
{
|
||||
if(mZoom == zoom)
|
||||
return;
|
||||
|
||||
auto hbar = horizontalScrollBar();
|
||||
auto vbar = verticalScrollBar();
|
||||
double horizScroll = hbar->value();
|
||||
double vertScroll = vbar->value();
|
||||
|
||||
//Bound values
|
||||
zoom = qBound(25, zoom, 400);
|
||||
|
||||
//Reposition scrollbars
|
||||
horizScroll *= zoom/double(mZoom);
|
||||
vertScroll *= zoom/double(mZoom);
|
||||
|
||||
mZoom = zoom;
|
||||
hourPanel->setZoom(mZoom);
|
||||
stationHeader->setZoom(mZoom);
|
||||
|
||||
emit zoomLevelChanged(mZoom);
|
||||
|
||||
updateScrollBars();
|
||||
resizeHeaders();
|
||||
|
||||
//Try set values
|
||||
hbar->setValue(horizScroll);
|
||||
vbar->setValue(vertScroll);
|
||||
|
||||
viewport()->update();
|
||||
}
|
||||
|
||||
bool LineGraphView::event(QEvent *e)
|
||||
{
|
||||
if (e->type() == QEvent::StyleChange || e->type() == QEvent::LayoutRequest)
|
||||
{
|
||||
updateScrollBars();
|
||||
resizeHeaders();
|
||||
}
|
||||
|
||||
return QAbstractScrollArea::event(e);
|
||||
}
|
||||
|
||||
bool LineGraphView::viewportEvent(QEvent *e)
|
||||
{
|
||||
if(e->type() == QEvent::ToolTip && m_scene && m_scene->getGraphType() != LineGraphType::NoGraph)
|
||||
LineGraphScene *lineScene = qobject_cast<LineGraphScene *>(scene());
|
||||
|
||||
if (e->type() == QEvent::ToolTip && lineScene
|
||||
&& lineScene->getGraphType() != LineGraphType::NoGraph)
|
||||
{
|
||||
QHelpEvent *ev = static_cast<QHelpEvent *>(e);
|
||||
QHelpEvent *ev = static_cast<QHelpEvent *>(e);
|
||||
|
||||
const QPoint origin(-horizontalScrollBar()->value(), -verticalScrollBar()->value());
|
||||
const QPointF scenePos = mapToScene(ev->pos());
|
||||
|
||||
QPointF pos = ev->pos();
|
||||
const QRect vp = viewport()->rect();
|
||||
JobStopEntry job = lineScene->getJobAt(scenePos, Session->platformOffset / 2);
|
||||
|
||||
//Map to viewport
|
||||
pos -= vp.topLeft();
|
||||
if(pos.x() < 0 || pos.y() < 0)
|
||||
return false;
|
||||
|
||||
//Map to scene
|
||||
pos -= origin;
|
||||
pos /= mZoom / 100.0;
|
||||
|
||||
JobStopEntry job = m_scene->getJobAt(pos, Session->platformOffset / 2);
|
||||
|
||||
if(job.jobId)
|
||||
if (job.jobId)
|
||||
{
|
||||
QToolTip::showText(ev->globalPos(),
|
||||
JobCategoryName::jobName(job.jobId, job.category),
|
||||
QToolTip::showText(ev->globalPos(), JobCategoryName::jobName(job.jobId, job.category),
|
||||
viewport());
|
||||
}else{
|
||||
}
|
||||
else
|
||||
{
|
||||
QToolTip::hideText();
|
||||
}
|
||||
|
||||
|
@ -160,46 +62,6 @@ bool LineGraphView::viewportEvent(QEvent *e)
|
|||
return QAbstractScrollArea::viewportEvent(e);
|
||||
}
|
||||
|
||||
void LineGraphView::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
const double scaleFactor = mZoom / 100.0;
|
||||
const QPoint origin(-horizontalScrollBar()->value(), -verticalScrollBar()->value());
|
||||
|
||||
QRectF exposedRect = e->rect();
|
||||
const QRect vp = viewport()->rect();
|
||||
|
||||
//Map to viewport
|
||||
exposedRect.moveTopLeft(exposedRect.topLeft() - vp.topLeft());
|
||||
exposedRect = exposedRect.intersected(vp);
|
||||
|
||||
//Map to scene
|
||||
exposedRect.moveTopLeft(exposedRect.topLeft() - origin);
|
||||
|
||||
//Set zoom
|
||||
const QRectF sceneRect(exposedRect.topLeft() / scaleFactor, exposedRect.size() / scaleFactor);
|
||||
|
||||
QPainter painter(viewport());
|
||||
|
||||
//Scroll contents
|
||||
painter.translate(origin);
|
||||
painter.scale(scaleFactor, scaleFactor);
|
||||
|
||||
BackgroundHelper::drawBackgroundHourLines(&painter, sceneRect);
|
||||
|
||||
if(!m_scene || m_scene->getGraphType() == LineGraphType::NoGraph)
|
||||
return; //Nothing to draw
|
||||
|
||||
BackgroundHelper::drawStations(&painter, m_scene, sceneRect);
|
||||
BackgroundHelper::drawJobStops(&painter, m_scene, sceneRect, true);
|
||||
BackgroundHelper::drawJobSegments(&painter, m_scene, sceneRect, true);
|
||||
}
|
||||
|
||||
void LineGraphView::resizeEvent(QResizeEvent *)
|
||||
{
|
||||
updateScrollBars();
|
||||
resizeHeaders();
|
||||
}
|
||||
|
||||
void LineGraphView::mousePressEvent(QMouseEvent *e)
|
||||
{
|
||||
emit syncToolbarToScene();
|
||||
|
@ -208,121 +70,13 @@ void LineGraphView::mousePressEvent(QMouseEvent *e)
|
|||
|
||||
void LineGraphView::mouseDoubleClickEvent(QMouseEvent *e)
|
||||
{
|
||||
if(!m_scene || m_scene->getGraphType() == LineGraphType::NoGraph)
|
||||
return; //Nothing to select
|
||||
LineGraphScene *lineScene = qobject_cast<LineGraphScene *>(scene());
|
||||
|
||||
const QPoint origin(-horizontalScrollBar()->value(), -verticalScrollBar()->value());
|
||||
if (!lineScene || lineScene->getGraphType() == LineGraphType::NoGraph)
|
||||
return; // Nothing to select
|
||||
|
||||
QPointF pos = e->pos();
|
||||
const QRect vp = viewport()->rect();
|
||||
const QPointF scenePos = mapToScene(e->pos());
|
||||
|
||||
//Map to viewport
|
||||
pos -= vp.topLeft();
|
||||
if(pos.x() < 0 || pos.y() < 0)
|
||||
return;
|
||||
|
||||
//Map to scene
|
||||
pos -= origin;
|
||||
pos /= mZoom / 100.0;
|
||||
|
||||
JobStopEntry job = m_scene->getJobAt(pos, Session->platformOffset / 2);
|
||||
m_scene->setSelectedJob(job);
|
||||
}
|
||||
|
||||
void LineGraphView::focusInEvent(QFocusEvent *e)
|
||||
{
|
||||
if(m_scene)
|
||||
m_scene->activateScene();
|
||||
|
||||
QAbstractScrollArea::focusInEvent(e);
|
||||
}
|
||||
|
||||
void LineGraphView::onSceneDestroyed()
|
||||
{
|
||||
m_scene = nullptr;
|
||||
redrawGraph();
|
||||
}
|
||||
|
||||
void LineGraphView::resizeHeaders()
|
||||
{
|
||||
const double horizOffset = (Session->horizOffset - 5.0) * mZoom / 100.0;
|
||||
const double vertOffset = (Session->vertOffset - 5.0) * mZoom / 100.0;
|
||||
|
||||
const QRect vg = viewport()->geometry();
|
||||
|
||||
hourPanel->move(vg.topLeft());
|
||||
hourPanel->resize(qRound(horizOffset), vg.height());
|
||||
hourPanel->setScroll(verticalScrollBar()->value());
|
||||
|
||||
stationHeader->move(vg.topLeft());
|
||||
stationHeader->resize(vg.width(), qRound(vertOffset));
|
||||
stationHeader->setScroll(horizontalScrollBar()->value());
|
||||
}
|
||||
|
||||
void LineGraphView::updateScrollBars()
|
||||
{
|
||||
if(!m_scene)
|
||||
return;
|
||||
|
||||
QSize contentSize = m_scene->getContentSize();
|
||||
if(contentSize.isEmpty())
|
||||
return;
|
||||
|
||||
//Scale contents
|
||||
contentSize *= mZoom / 100.0;
|
||||
|
||||
QSize p = viewport()->size();
|
||||
QSize m = maximumViewportSize();
|
||||
|
||||
if(m.expandedTo(contentSize) == m)
|
||||
p = m; //No scrollbars needed
|
||||
|
||||
auto hbar = horizontalScrollBar();
|
||||
hbar->setRange(0, contentSize.width() - p.width());
|
||||
hbar->setPageStep(p.width());
|
||||
|
||||
auto vbar = verticalScrollBar();
|
||||
vbar->setRange(0, contentSize.height() - p.height());
|
||||
vbar->setPageStep(p.height());
|
||||
}
|
||||
|
||||
void LineGraphView::ensureVisible(double x, double y, double xmargin, double ymargin)
|
||||
{
|
||||
auto hbar = horizontalScrollBar();
|
||||
auto vbar = verticalScrollBar();
|
||||
auto vp = viewport();
|
||||
|
||||
const double scaleFactor = mZoom / 100.0;
|
||||
|
||||
const double horizOffset = (hourPanel->width() + 5) * scaleFactor;
|
||||
const double vertOffset = (stationHeader->height() + 5) * scaleFactor;
|
||||
|
||||
x *= scaleFactor;
|
||||
y *= scaleFactor;
|
||||
xmargin *= scaleFactor;
|
||||
ymargin *= scaleFactor;
|
||||
|
||||
int logicalX = x;
|
||||
|
||||
double val = hbar->value();
|
||||
if (logicalX - xmargin < hbar->value() + horizOffset)
|
||||
{
|
||||
val = qMax(0.0, logicalX - xmargin - horizOffset);
|
||||
}
|
||||
else if (logicalX > hbar->value() + vp->width() - xmargin)
|
||||
{
|
||||
val = qMin(logicalX - vp->width() + xmargin, double(hbar->maximum()));
|
||||
}
|
||||
hbar->setValue(qRound(val));
|
||||
|
||||
val = vbar->value();
|
||||
if (y - ymargin < vbar->value() + vertOffset)
|
||||
{
|
||||
val = qMax(0.0, y - ymargin - vertOffset);
|
||||
}
|
||||
else if (y > vbar->value() + vp->height() - ymargin)
|
||||
{
|
||||
val = qMin(y - vp->height() + ymargin, double(vbar->maximum()));
|
||||
}
|
||||
vbar->setValue(qRound(val));
|
||||
JobStopEntry job = lineScene->getJobAt(scenePos, Session->platformOffset / 2);
|
||||
lineScene->setSelectedJob(job);
|
||||
}
|
||||
|
|
|
@ -1,47 +1,44 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LINEGRAPHVIEW_H
|
||||
#define LINEGRAPHVIEW_H
|
||||
|
||||
#include <QAbstractScrollArea>
|
||||
|
||||
class LineGraphScene;
|
||||
|
||||
class StationLabelsHeader;
|
||||
class HourPanel;
|
||||
#include "utils/scene/basicgraphview.h"
|
||||
|
||||
/*!
|
||||
* \brief Widget to display a LineGraphScene
|
||||
* \brief BasicGraphView subclass to display a LineGraphScene
|
||||
*
|
||||
* A scrollable widget which renders LineGraphScene contents
|
||||
* Moving the mouse cursor on the contents, tooltips will show
|
||||
* A custom view to render LineGraphScene contents.
|
||||
* Moving the mouse cursor on the contents, tooltips will be shown.
|
||||
* Double clicking on a Job selects it.
|
||||
*
|
||||
* \sa LineGraphScene
|
||||
* \sa LineGraphWidget
|
||||
* \sa BackgroundHelper
|
||||
*/
|
||||
class LineGraphView : public QAbstractScrollArea
|
||||
class LineGraphView : public BasicGraphView
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LineGraphView(QWidget *parent = nullptr);
|
||||
|
||||
LineGraphScene *scene() const;
|
||||
void setScene(LineGraphScene *newScene);
|
||||
|
||||
/*!
|
||||
* \brief ensure point is visible
|
||||
* \badcode
|
||||
* @badcode
|
||||
*
|
||||
* Scrolls the contents of the scroll area so that the point (\a x, \a y) is visible
|
||||
* inside the region of the viewport with margins specified in pixels by \a xmargin and
|
||||
* \a ymargin. If the specified point cannot be reached, the contents are scrolled to
|
||||
* the nearest valid position. The default value for both margins is 50 pixels.
|
||||
*
|
||||
* Vertical and horizontal headers are excluded from the visible zone
|
||||
*/
|
||||
void ensureVisible(double x, double y, double xmargin, double ymargin);
|
||||
|
||||
inline int getZoomLevel() const { return mZoom; }
|
||||
|
||||
signals:
|
||||
/*!
|
||||
* \brief Sync toolbar on click
|
||||
|
@ -50,34 +47,7 @@ signals:
|
|||
*/
|
||||
void syncToolbarToScene();
|
||||
|
||||
void zoomLevelChanged(int zoom);
|
||||
|
||||
public slots:
|
||||
/*!
|
||||
* \brief Triggers graph redrawing
|
||||
*
|
||||
* \sa updateScrollBars()
|
||||
*/
|
||||
void redrawGraph();
|
||||
|
||||
/*!
|
||||
* \brief ensure a rect is visible in the viewport
|
||||
*
|
||||
* \sa ensureVisible()
|
||||
*/
|
||||
void ensureRectVisible(const QRectF& r);
|
||||
|
||||
void setZoomLevel(int zoom);
|
||||
|
||||
protected:
|
||||
/*!
|
||||
* \brief React to layout and style change
|
||||
*
|
||||
* On layout and style change update scroll bars
|
||||
* \sa updateScrollBars()
|
||||
*/
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
/*!
|
||||
* \brief Show Tooltips
|
||||
*
|
||||
|
@ -86,9 +56,6 @@ protected:
|
|||
*/
|
||||
bool viewportEvent(QEvent *e) override;
|
||||
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
void resizeEvent(QResizeEvent *) override;
|
||||
|
||||
/*!
|
||||
* \brief Sync toolbar on click
|
||||
*
|
||||
|
@ -103,35 +70,6 @@ protected:
|
|||
* \sa LineGraphScene::getJobAt()
|
||||
*/
|
||||
void mouseDoubleClickEvent(QMouseEvent *e) override;
|
||||
|
||||
/*!
|
||||
* \brief Activate view
|
||||
*
|
||||
* This view (and its scene) is now active
|
||||
* It will receive requests to show items
|
||||
*
|
||||
* \sa LineGraphScene::activateScene()
|
||||
* \sa LineGraphManager::setActiveScene()
|
||||
*/
|
||||
void focusInEvent(QFocusEvent *e) override;
|
||||
|
||||
private slots:
|
||||
void onSceneDestroyed();
|
||||
void resizeHeaders();
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Update scrollbar size
|
||||
*/
|
||||
void updateScrollBars();
|
||||
|
||||
private:
|
||||
StationLabelsHeader *stationHeader;
|
||||
HourPanel *hourPanel;
|
||||
|
||||
LineGraphScene *m_scene;
|
||||
|
||||
int mZoom;
|
||||
};
|
||||
|
||||
#endif // LINEGRAPHVIEW_H
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "linegraphwidget.h"
|
||||
|
||||
#include "graph/model/linegraphscene.h"
|
||||
|
@ -17,7 +36,7 @@ LineGraphWidget::LineGraphWidget(QWidget *parent) :
|
|||
{
|
||||
QVBoxLayout *lay = new QVBoxLayout(this);
|
||||
|
||||
toolBar = new LineGraphToolbar(this);
|
||||
toolBar = new LineGraphToolbar(this);
|
||||
|
||||
lay->addWidget(toolBar);
|
||||
|
||||
|
@ -26,12 +45,13 @@ LineGraphWidget::LineGraphWidget(QWidget *parent) :
|
|||
|
||||
m_scene = new LineGraphScene(Session->m_Db, this);
|
||||
|
||||
//Subscribe to notifications and to session managment
|
||||
// Subscribe to notifications and to session managment
|
||||
Session->getViewManager()->getLineGraphMgr()->registerScene(m_scene);
|
||||
view->setScene(m_scene);
|
||||
toolBar->setScene(m_scene);
|
||||
|
||||
connect(view, &LineGraphView::syncToolbarToScene, toolBar, &LineGraphToolbar::resetToolbarToScene);
|
||||
connect(view, &LineGraphView::syncToolbarToScene, toolBar,
|
||||
&LineGraphToolbar::resetToolbarToScene);
|
||||
connect(toolBar, &LineGraphToolbar::requestRedraw, m_scene, &LineGraphScene::reload);
|
||||
|
||||
connect(toolBar, &LineGraphToolbar::requestZoom, view, &LineGraphView::setZoomLevel);
|
||||
|
@ -40,7 +60,7 @@ LineGraphWidget::LineGraphWidget(QWidget *parent) :
|
|||
|
||||
bool LineGraphWidget::tryLoadGraph(db_id graphObjId, LineGraphType type)
|
||||
{
|
||||
if(!m_scene)
|
||||
if (!m_scene)
|
||||
return false;
|
||||
|
||||
return m_scene->loadGraph(graphObjId, type);
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef LINEGRAPHWIDGET_H
|
||||
#define LINEGRAPHWIDGET_H
|
||||
|
||||
|
@ -27,9 +46,20 @@ class LineGraphWidget : public QWidget
|
|||
public:
|
||||
explicit LineGraphWidget(QWidget *parent = nullptr);
|
||||
|
||||
inline LineGraphScene *getScene() const { return m_scene; }
|
||||
inline LineGraphView *getView() const { return view; }
|
||||
inline LineGraphToolbar *getToolbar() const { return toolBar; }
|
||||
inline LineGraphScene *getScene() const
|
||||
{
|
||||
return m_scene;
|
||||
}
|
||||
|
||||
inline LineGraphView *getView() const
|
||||
{
|
||||
return view;
|
||||
}
|
||||
|
||||
inline LineGraphToolbar *getToolbar() const
|
||||
{
|
||||
return toolBar;
|
||||
}
|
||||
|
||||
bool tryLoadGraph(db_id graphObjId, LineGraphType type);
|
||||
|
||||
|
|
|
@ -1,78 +0,0 @@
|
|||
#include "stationlabelsheader.h"
|
||||
|
||||
#include "graph/model/linegraphscene.h"
|
||||
|
||||
#include "backgroundhelper.h"
|
||||
|
||||
#include "app/session.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <QPaintEvent>
|
||||
|
||||
StationLabelsHeader::StationLabelsHeader(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
m_scene(nullptr),
|
||||
horizontalScroll(0),
|
||||
mZoom(100)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void StationLabelsHeader::setScroll(int value)
|
||||
{
|
||||
horizontalScroll = value;
|
||||
update();
|
||||
}
|
||||
|
||||
void StationLabelsHeader::setZoom(int val)
|
||||
{
|
||||
mZoom = val;
|
||||
update();
|
||||
}
|
||||
|
||||
void StationLabelsHeader::onSceneDestroyed()
|
||||
{
|
||||
m_scene = nullptr;
|
||||
update();
|
||||
}
|
||||
|
||||
void StationLabelsHeader::paintEvent(QPaintEvent *e)
|
||||
{
|
||||
if(!m_scene || m_scene->getGraphType() == LineGraphType::NoGraph)
|
||||
return; //Nothing to draw
|
||||
|
||||
QPainter painter(this);
|
||||
QColor c(255, 255, 255, 220);
|
||||
painter.fillRect(rect(), c);
|
||||
|
||||
const double scaleFactor = mZoom / 100.0;
|
||||
|
||||
const double sceneScroll = horizontalScroll / scaleFactor;
|
||||
QRectF sceneRect(QPointF(e->rect().topLeft()) / scaleFactor,
|
||||
QSizeF(e->rect().size()) / scaleFactor);
|
||||
|
||||
painter.scale(scaleFactor, scaleFactor);
|
||||
|
||||
BackgroundHelper::drawStationHeader(&painter, m_scene, sceneRect, sceneScroll);
|
||||
}
|
||||
|
||||
LineGraphScene *StationLabelsHeader::scene() const
|
||||
{
|
||||
return m_scene;
|
||||
}
|
||||
|
||||
void StationLabelsHeader::setScene(LineGraphScene *newScene)
|
||||
{
|
||||
if(m_scene)
|
||||
{
|
||||
disconnect(m_scene, &LineGraphScene::redrawGraph, this, qOverload<>(&StationLabelsHeader::update));
|
||||
disconnect(m_scene, &QObject::destroyed, this, &StationLabelsHeader::onSceneDestroyed);
|
||||
}
|
||||
m_scene = newScene;
|
||||
if(m_scene)
|
||||
{
|
||||
connect(m_scene, &LineGraphScene::redrawGraph, this, qOverload<>(&StationLabelsHeader::update));
|
||||
connect(m_scene, &QObject::destroyed, this, &StationLabelsHeader::onSceneDestroyed);
|
||||
}
|
||||
update();
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
#ifndef STATIONLABELSHEADER_H
|
||||
#define STATIONLABELSHEADER_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class LineGraphScene;
|
||||
|
||||
/*!
|
||||
* \brief Helper widget to draw station labels
|
||||
*
|
||||
* Draws station labels (with platform numbers below) on
|
||||
* the view horizontal header
|
||||
*
|
||||
* \sa LineGraphScene
|
||||
* \sa LineGraphView
|
||||
* \sa BackgroundHelper
|
||||
*/
|
||||
class StationLabelsHeader : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit StationLabelsHeader(QWidget *parent = nullptr);
|
||||
|
||||
LineGraphScene *scene() const;
|
||||
void setScene(LineGraphScene *newScene);
|
||||
|
||||
public slots:
|
||||
void setScroll(int value);
|
||||
void setZoom(int val);
|
||||
|
||||
private slots:
|
||||
void onSceneDestroyed();
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *e) override;
|
||||
|
||||
private:
|
||||
LineGraphScene *m_scene;
|
||||
int horizontalScroll;
|
||||
int mZoom;
|
||||
};
|
||||
|
||||
#endif // STATIONLABELSHEADER_H
|
|
@ -1,4 +1,5 @@
|
|||
add_subdirectory(jobeditor)
|
||||
add_subdirectory(jobs_checker)
|
||||
add_subdirectory(jobsmanager)
|
||||
|
||||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "editstopdialog.h"
|
||||
#include "ui_editstopdialog.h"
|
||||
|
||||
|
@ -11,14 +30,15 @@
|
|||
#include "rscoupledialog.h"
|
||||
#include "model/rscouplinginterface.h"
|
||||
|
||||
#include "model/rsproxymodel.h"
|
||||
#include "model/stopcouplingmodel.h"
|
||||
#include "model/trainassetmodel.h"
|
||||
#include "model/jobpassingsmodel.h"
|
||||
#include "jobs/jobsmanager/model/jobshelper.h"
|
||||
#include "jobs/jobsmanager/model/jobmatchmodel.h"
|
||||
|
||||
#include "utils/delegates/sql/modelpageswitcher.h"
|
||||
#include "utils/delegates/sql/customcompletionlineedit.h"
|
||||
#include "utils/delegates/sql/chooseitemdlg.h"
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
|
@ -28,7 +48,6 @@
|
|||
|
||||
#include "app/scopedebug.h"
|
||||
|
||||
|
||||
EditStopDialog::EditStopDialog(StopModel *m, QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::EditStopDialog),
|
||||
|
@ -37,10 +56,13 @@ EditStopDialog::EditStopDialog(StopModel *m, QWidget *parent) :
|
|||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
//Stop
|
||||
helper = new StopEditingHelper(Session->m_Db, stopModel,
|
||||
ui->outGateTrackSpin, ui->arrivalTimeEdit, ui->departureTimeEdit,
|
||||
this);
|
||||
// Stop
|
||||
helper = new StopEditingHelper(Session->m_Db, stopModel, ui->outGateTrackSpin,
|
||||
ui->arrivalTimeEdit, ui->departureTimeEdit, this);
|
||||
connect(helper, &StopEditingHelper::nextSegmentChosen, this,
|
||||
&EditStopDialog::updateAdditionalNotes);
|
||||
connect(helper, &StopEditingHelper::stationTrackChosen, this,
|
||||
&EditStopDialog::updateAdditionalNotes);
|
||||
|
||||
CustomCompletionLineEdit *mStationEdit = helper->getStationEdit();
|
||||
CustomCompletionLineEdit *mStTrackEdit = helper->getStTrackEdit();
|
||||
|
@ -50,48 +72,53 @@ EditStopDialog::EditStopDialog(StopModel *m, QWidget *parent) :
|
|||
ui->curStopLay->setWidget(2, QFormLayout::FieldRole, mStTrackEdit);
|
||||
ui->curStopLay->setWidget(3, QFormLayout::FieldRole, mOutGateEdit);
|
||||
|
||||
//Coupling
|
||||
couplingMgr = new RSCouplingInterface(Session->m_Db, this);
|
||||
// Coupling
|
||||
couplingMgr = new RSCouplingInterface(Session->m_Db, this);
|
||||
|
||||
coupledModel = new StopCouplingModel(Session->m_Db, this);
|
||||
auto ps = new ModelPageSwitcher(true, this);
|
||||
auto ps = new ModelPageSwitcher(true, this);
|
||||
ps->setModel(coupledModel);
|
||||
ui->coupledView->setModel(coupledModel);
|
||||
ui->coupledLayout->insertWidget(1, ps);
|
||||
|
||||
uncoupledModel = new StopCouplingModel(Session->m_Db, this);
|
||||
ps = new ModelPageSwitcher(true, this);
|
||||
ps = new ModelPageSwitcher(true, this);
|
||||
ps->setModel(uncoupledModel);
|
||||
ui->uncoupledView->setModel(uncoupledModel);
|
||||
ui->uncoupledLayout->insertWidget(1, ps);
|
||||
|
||||
connect(ui->importJobRsBut, &QPushButton::clicked, this, &EditStopDialog::importJobRS);
|
||||
connect(ui->editCoupledBut, &QPushButton::clicked, this, &EditStopDialog::editCoupled);
|
||||
connect(ui->editUncoupledBut, &QPushButton::clicked, this, &EditStopDialog::editUncoupled);
|
||||
|
||||
ui->coupledView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
ui->uncoupledView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->coupledView, &QAbstractItemView::customContextMenuRequested, this, &EditStopDialog::couplingCustomContextMenuRequested);
|
||||
connect(ui->uncoupledView, &QAbstractItemView::customContextMenuRequested, this, &EditStopDialog::couplingCustomContextMenuRequested);
|
||||
connect(ui->coupledView, &QAbstractItemView::customContextMenuRequested, this,
|
||||
&EditStopDialog::couplingCustomContextMenuRequested);
|
||||
connect(ui->uncoupledView, &QAbstractItemView::customContextMenuRequested, this,
|
||||
&EditStopDialog::couplingCustomContextMenuRequested);
|
||||
|
||||
//Setup train asset models
|
||||
// Setup train asset models
|
||||
trainAssetModelBefore = new TrainAssetModel(Session->m_Db, this);
|
||||
ps = new ModelPageSwitcher(true, this);
|
||||
ps = new ModelPageSwitcher(true, this);
|
||||
ps->setModel(trainAssetModelBefore);
|
||||
ui->assetBeforeView->setModel(trainAssetModelBefore);
|
||||
ui->trainAssetGridLayout->addWidget(ps, 2, 0);
|
||||
|
||||
trainAssetModelAfter = new TrainAssetModel(Session->m_Db, this);
|
||||
ps = new ModelPageSwitcher(true, this);
|
||||
ps = new ModelPageSwitcher(true, this);
|
||||
ps->setModel(trainAssetModelAfter);
|
||||
ui->assetAfterView->setModel(trainAssetModelAfter);
|
||||
ui->trainAssetGridLayout->addWidget(ps, 2, 1);
|
||||
|
||||
ui->assetBeforeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
ui->assetAfterView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->assetBeforeView, &QAbstractItemView::customContextMenuRequested, this, &EditStopDialog::couplingCustomContextMenuRequested);
|
||||
connect(ui->assetAfterView, &QAbstractItemView::customContextMenuRequested, this, &EditStopDialog::couplingCustomContextMenuRequested);
|
||||
connect(ui->assetBeforeView, &QAbstractItemView::customContextMenuRequested, this,
|
||||
&EditStopDialog::couplingCustomContextMenuRequested);
|
||||
connect(ui->assetAfterView, &QAbstractItemView::customContextMenuRequested, this,
|
||||
&EditStopDialog::couplingCustomContextMenuRequested);
|
||||
|
||||
//Setup Crossings/Passings
|
||||
// Setup Crossings/Passings
|
||||
passingsModel = new JobPassingsModel(this);
|
||||
ui->passingsView->setModel(passingsModel);
|
||||
|
||||
|
@ -100,11 +127,16 @@ EditStopDialog::EditStopDialog(StopModel *m, QWidget *parent) :
|
|||
|
||||
connect(ui->calcPassingsBut, &QPushButton::clicked, this, &EditStopDialog::calcPassings);
|
||||
|
||||
//BIG TODO: temporarily disable option to Cancel dialog
|
||||
//This is because at the moment it doesn't seem Coupling are canceled
|
||||
//So you get a mixed state: Arrival/Departure/Descriptio ecc changes are canceled but Coupling changes are still applied
|
||||
// BIG TODO: temporarily disable option to Cancel dialog
|
||||
// This is because at the moment it doesn't seem Coupling are canceled
|
||||
// So you get a mixed state: Arrival/Departure/Descriptio ecc changes are canceled but Coupling
|
||||
// changes are still applied
|
||||
ui->buttonBox->setStandardButtons(QDialogButtonBox::Ok);
|
||||
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)
|
||||
->setToolTip(tr("Press SHIFT modifier and click to save changes"
|
||||
" without recalculating travel times."));
|
||||
|
||||
connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
|
||||
|
@ -123,15 +155,15 @@ void EditStopDialog::clearUi()
|
|||
{
|
||||
helper->stopOutTrackTimer();
|
||||
|
||||
stopIdx = QModelIndex();
|
||||
stopIdx = QModelIndex();
|
||||
|
||||
m_jobId = 0;
|
||||
m_jobId = 0;
|
||||
m_jobCat = JobCategory::FREIGHT;
|
||||
|
||||
trainAssetModelBefore->setStop(0, QTime(), TrainAssetModel::BeforeStop);
|
||||
trainAssetModelAfter->setStop(0, QTime(), TrainAssetModel::AfterStop);
|
||||
|
||||
//TODO: clear UI properly
|
||||
// TODO: clear UI properly
|
||||
}
|
||||
|
||||
void EditStopDialog::showBeforeAsset(bool val)
|
||||
|
@ -146,78 +178,79 @@ void EditStopDialog::showAfterAsset(bool val)
|
|||
ui->assetAfterLabel->setVisible(val);
|
||||
}
|
||||
|
||||
void EditStopDialog::setStop(const QModelIndex& idx)
|
||||
void EditStopDialog::setStop(const QModelIndex &idx)
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
if(!idx.isValid())
|
||||
if (!idx.isValid())
|
||||
{
|
||||
clearUi();
|
||||
return;
|
||||
}
|
||||
|
||||
m_jobId = stopModel->getJobId();
|
||||
m_jobCat = stopModel->getCategory();
|
||||
m_jobId = stopModel->getJobId();
|
||||
m_jobCat = stopModel->getCategory();
|
||||
|
||||
stopIdx = idx;
|
||||
stopIdx = idx;
|
||||
|
||||
const StopItem& curStop = stopModel->getItemAt(idx.row());
|
||||
const StopItem &curStop = stopModel->getItemAt(idx.row());
|
||||
StopItem prevStop;
|
||||
if(idx.row() == 0)
|
||||
prevStop = StopItem(); //First stop has no previous stop
|
||||
if (idx.row() == 0)
|
||||
prevStop = StopItem(); // First stop has no previous stop
|
||||
else
|
||||
prevStop = stopModel->getItemAt(idx.row() - 1);
|
||||
|
||||
helper->setStop(curStop, prevStop);
|
||||
|
||||
//Setup Train Asset
|
||||
// Setup Train Asset
|
||||
trainAssetModelBefore->setStop(m_jobId, curStop.arrival, TrainAssetModel::BeforeStop);
|
||||
trainAssetModelAfter->setStop(m_jobId, curStop.arrival, TrainAssetModel::AfterStop);
|
||||
|
||||
//Hide train asset before stop on First stop
|
||||
// Hide train asset before stop on First stop
|
||||
showBeforeAsset(curStop.type != StopType::First);
|
||||
|
||||
//Hide train asset after stop on Last stop
|
||||
// Hide train asset after stop on Last stop
|
||||
showAfterAsset(curStop.type != StopType::Last);
|
||||
|
||||
//Coupling operations
|
||||
// Coupling operations
|
||||
coupledModel->setStop(curStop.stopId, RsOp::Coupled);
|
||||
uncoupledModel->setStop(curStop.stopId, RsOp::Uncoupled);
|
||||
|
||||
//Update UI
|
||||
// Update UI
|
||||
updateInfo();
|
||||
|
||||
//Calc passings
|
||||
// Calc passings
|
||||
calcPassings();
|
||||
|
||||
//Update Title
|
||||
// Update Title
|
||||
const QString jobName = JobCategoryName::jobName(m_jobId, m_jobCat);
|
||||
setWindowTitle(jobName);
|
||||
}
|
||||
|
||||
void EditStopDialog::updateInfo()
|
||||
{
|
||||
const StopItem& curStop = helper->getCurItem();
|
||||
const StopItem& prevStop = helper->getPrevItem();
|
||||
const StopItem &curStop = helper->getCurItem();
|
||||
const StopItem &prevStop = helper->getPrevItem();
|
||||
|
||||
const QString inGateStr = helper->getGateString(curStop.fromGate.gateId,
|
||||
prevStop.nextSegment.reversed);
|
||||
const QString inGateStr =
|
||||
helper->getGateString(curStop.fromGate.gateId, prevStop.nextSegment.reversed);
|
||||
ui->inGateEdit->setText(inGateStr);
|
||||
|
||||
if(curStop.type == StopType::First)
|
||||
if (curStop.type == StopType::First)
|
||||
{
|
||||
//Hide box of previous stop
|
||||
// Hide box of previous stop
|
||||
ui->prevStopBox->setVisible(false);
|
||||
ui->curStopBox->setTitle(tr("First Stop"));
|
||||
}
|
||||
else
|
||||
{
|
||||
//Show box of previous stop
|
||||
// Show box of previous stop
|
||||
ui->prevStopBox->setVisible(true);
|
||||
ui->curStopBox->setTitle(curStop.type == StopType::Last ? tr("Last Stop") : tr("Current Stop"));
|
||||
ui->curStopBox->setTitle(curStop.type == StopType::Last ? tr("Last Stop")
|
||||
: tr("Current Stop"));
|
||||
|
||||
QString prevStName;
|
||||
if(prevStop.stationId)
|
||||
if (prevStop.stationId)
|
||||
{
|
||||
query q(Session->m_Db, "SELECT name FROM stations WHERE id=?");
|
||||
q.bind(1, prevStop.stationId);
|
||||
|
@ -226,50 +259,101 @@ void EditStopDialog::updateInfo()
|
|||
}
|
||||
ui->prevStEdit->setText(prevStName);
|
||||
|
||||
const QString outGateStr = helper->getGateString(prevStop.toGate.gateId,
|
||||
prevStop.nextSegment.reversed);
|
||||
const QString outGateStr =
|
||||
helper->getGateString(prevStop.toGate.gateId, prevStop.nextSegment.reversed);
|
||||
ui->prevOutGateEdit->setText(outGateStr);
|
||||
}
|
||||
|
||||
const QString descr = stopModel->getDescription(curStop);
|
||||
ui->descriptionEdit->setPlainText(descr);
|
||||
|
||||
if(curStop.type == StopType::Transit)
|
||||
if (curStop.type == StopType::Transit)
|
||||
{
|
||||
//On transit you cannot couple/uncouple rollingstock
|
||||
// On transit you cannot couple/uncouple rollingstock
|
||||
ui->editCoupledBut->setEnabled(false);
|
||||
ui->editUncoupledBut->setEnabled(false);
|
||||
}
|
||||
|
||||
couplingMgr->loadCouplings(stopModel, curStop.stopId, m_jobId, curStop.arrival);
|
||||
|
||||
updateAdditionalNotes();
|
||||
}
|
||||
|
||||
void EditStopDialog::saveDataToModel()
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
const StopItem& curStop = helper->getCurItem();
|
||||
const StopItem& prevStop = helper->getPrevItem();
|
||||
const StopItem &curStop = helper->getCurItem();
|
||||
const StopItem &prevStop = helper->getPrevItem();
|
||||
|
||||
if(ui->descriptionEdit->document()->isModified())
|
||||
if (ui->descriptionEdit->document()->isModified())
|
||||
{
|
||||
stopModel->setDescription(stopIdx,
|
||||
ui->descriptionEdit->toPlainText());
|
||||
stopModel->setDescription(stopIdx, ui->descriptionEdit->toPlainText());
|
||||
}
|
||||
|
||||
stopModel->setStopInfo(stopIdx, curStop, prevStop.nextSegment);
|
||||
bool avoidTimeRecalc = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
|
||||
stopModel->setStopInfo(stopIdx, curStop, prevStop.nextSegment, avoidTimeRecalc);
|
||||
}
|
||||
|
||||
void EditStopDialog::importJobRS()
|
||||
{
|
||||
const StopItem &curStop = helper->getCurItem();
|
||||
if (!curStop.stationId)
|
||||
{
|
||||
QMessageBox::warning(this, tr("Import Error"),
|
||||
tr("In order to import rollingstock from other<br>"
|
||||
"Jobs stopping in the <b>same station</b> as current one, "
|
||||
"you must first set a valid station for this stop."));
|
||||
return;
|
||||
}
|
||||
|
||||
JobMatchModel jobsMatch(Session->m_Db);
|
||||
jobsMatch.setDefaultId(JobMatchModel::StopId);
|
||||
jobsMatch.setFilter(m_jobId, curStop.stationId, curStop.departure);
|
||||
|
||||
QString stName = helper->getStationEdit()->text();
|
||||
|
||||
OwningQPointer<ChooseItemDlg> dlg = new ChooseItemDlg(&jobsMatch, this);
|
||||
dlg->setDescription(
|
||||
tr("Please choose a Job among the ones stopping at <b>%1</b> before <b>%2</b>.<br>"
|
||||
"All rollingstock uncoupled by selected Job at current station<br>"
|
||||
"will be coupled to current Job.")
|
||||
.arg(stName, curStop.departure.toString("HH:mm")));
|
||||
dlg->setPlaceholder(tr("Job number without category"));
|
||||
|
||||
// Select model
|
||||
int ret = dlg->exec();
|
||||
if (ret != QDialog::Accepted || !dlg)
|
||||
return;
|
||||
|
||||
db_id otherJobStopId = dlg->getItemId();
|
||||
if (!otherJobStopId)
|
||||
return;
|
||||
|
||||
// Import rollingstock
|
||||
int count = couplingMgr->importRSFromJob(otherJobStopId);
|
||||
|
||||
// Refresh views
|
||||
coupledModel->refreshData(true);
|
||||
trainAssetModelAfter->refreshData(true);
|
||||
|
||||
// Tell user it's completed
|
||||
QMessageBox::information(
|
||||
this, tr("Importation Finished"),
|
||||
tr("<b>%1</b> rollingstock items were successfully imported").arg(count));
|
||||
}
|
||||
|
||||
void EditStopDialog::editCoupled()
|
||||
{
|
||||
const StopItem& curStop = helper->getCurItem();
|
||||
const StopItem &curStop = helper->getCurItem();
|
||||
|
||||
coupledModel->clearCache();
|
||||
trainAssetModelAfter->clearCache();
|
||||
|
||||
OwningQPointer<RSCoupleDialog> dlg = new RSCoupleDialog(couplingMgr, RsOp::Coupled, this);
|
||||
dlg->setWindowTitle(tr("Couple"));
|
||||
dlg->loadProxyModels(Session->m_Db, m_jobId, curStop.stopId, curStop.stationId, curStop.arrival);
|
||||
dlg->loadProxyModels(Session->m_Db, m_jobId, curStop.stopId, curStop.stationId,
|
||||
curStop.arrival);
|
||||
|
||||
dlg->exec();
|
||||
|
||||
|
@ -277,17 +361,17 @@ void EditStopDialog::editCoupled()
|
|||
trainAssetModelAfter->refreshData(true);
|
||||
}
|
||||
|
||||
|
||||
void EditStopDialog::editUncoupled()
|
||||
{
|
||||
const StopItem& curStop = helper->getCurItem();
|
||||
const StopItem &curStop = helper->getCurItem();
|
||||
|
||||
uncoupledModel->clearCache();
|
||||
trainAssetModelAfter->clearCache();
|
||||
|
||||
OwningQPointer<RSCoupleDialog> dlg = new RSCoupleDialog(couplingMgr, RsOp::Uncoupled, this);
|
||||
dlg->setWindowTitle(tr("Uncouple"));
|
||||
dlg->loadProxyModels(Session->m_Db, m_jobId, curStop.stopId, curStop.stationId, curStop.arrival);
|
||||
dlg->loadProxyModels(Session->m_Db, m_jobId, curStop.stopId, curStop.stationId,
|
||||
curStop.arrival);
|
||||
|
||||
dlg->exec();
|
||||
|
||||
|
@ -305,20 +389,21 @@ void EditStopDialog::calcPassings()
|
|||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
const StopItem& curStop = helper->getCurItem();
|
||||
const StopItem &curStop = helper->getCurItem();
|
||||
|
||||
JobStopDirectionHelper dirHelper(Session->m_Db);
|
||||
utils::Side myDirection = dirHelper.getStopOutSide(curStop.stopId);
|
||||
|
||||
query q(Session->m_Db, "SELECT s.id, s.job_id, jobs.category, s.arrival, s.departure,"
|
||||
"t1.name,t2.name"
|
||||
" FROM stops s"
|
||||
" JOIN jobs ON jobs.id=s.job_id"
|
||||
" LEFT JOIN station_gate_connections g1 ON g1.id=s.in_gate_conn"
|
||||
" LEFT JOIN station_gate_connections g2 ON g2.id=s.out_gate_conn"
|
||||
" LEFT JOIN station_tracks t1 ON t1.id=g1.track_id"
|
||||
" LEFT JOIN station_tracks t2 ON t2.id=g2.track_id"
|
||||
" WHERE s.station_id=? AND s.departure >=? AND s.arrival<=? AND s.job_id <> ?");
|
||||
query q(Session->m_Db,
|
||||
"SELECT s.id, s.job_id, jobs.category, s.arrival, s.departure,"
|
||||
"t1.name,t2.name"
|
||||
" FROM stops s"
|
||||
" JOIN jobs ON jobs.id=s.job_id"
|
||||
" LEFT JOIN station_gate_connections g1 ON g1.id=s.in_gate_conn"
|
||||
" LEFT JOIN station_gate_connections g2 ON g2.id=s.out_gate_conn"
|
||||
" LEFT JOIN station_tracks t1 ON t1.id=g1.track_id"
|
||||
" LEFT JOIN station_tracks t2 ON t2.id=g2.track_id"
|
||||
" WHERE s.station_id=? AND s.departure >=? AND s.arrival<=? AND s.job_id <> ?");
|
||||
|
||||
q.bind(1, curStop.stationId);
|
||||
q.bind(2, curStop.arrival);
|
||||
|
@ -327,27 +412,27 @@ void EditStopDialog::calcPassings()
|
|||
|
||||
QVector<JobPassingsModel::Entry> passings, crossings;
|
||||
|
||||
for(auto r : q)
|
||||
for (auto r : q)
|
||||
{
|
||||
JobPassingsModel::Entry e;
|
||||
|
||||
db_id otherStopId = r.get<db_id>(0);
|
||||
e.jobId = r.get<db_id>(1);
|
||||
e.category = JobCategory(r.get<int>(2));
|
||||
e.arrival = r.get<QTime>(3);
|
||||
e.departure = r.get<QTime>(4);
|
||||
e.platform = r.get<int>(5);
|
||||
e.jobId = r.get<db_id>(1);
|
||||
e.category = JobCategory(r.get<int>(2));
|
||||
e.arrival = r.get<QTime>(3);
|
||||
e.departure = r.get<QTime>(4);
|
||||
e.platform = r.get<int>(5);
|
||||
|
||||
e.platform = r.get<QString>(6);
|
||||
if(e.platform.isEmpty())
|
||||
e.platform = r.get<QString>(7); //Use out gate to get track name
|
||||
e.platform = r.get<QString>(6);
|
||||
if (e.platform.isEmpty())
|
||||
e.platform = r.get<QString>(7); // Use out gate to get track name
|
||||
|
||||
utils::Side otherDir = dirHelper.getStopOutSide(otherStopId);
|
||||
|
||||
if(myDirection == otherDir)
|
||||
passings.append(e); //Same direction -> Passing
|
||||
if (myDirection == otherDir)
|
||||
passings.append(e); // Same direction -> Passing
|
||||
else
|
||||
crossings.append(e); //Opposite direction -> Crossing
|
||||
crossings.append(e); // Opposite direction -> Crossing
|
||||
}
|
||||
|
||||
q.reset();
|
||||
|
@ -359,20 +444,21 @@ void EditStopDialog::calcPassings()
|
|||
ui->crossingsView->resizeColumnsToContents();
|
||||
}
|
||||
|
||||
void EditStopDialog::couplingCustomContextMenuRequested(const QPoint& pos)
|
||||
void EditStopDialog::couplingCustomContextMenuRequested(const QPoint &pos)
|
||||
{
|
||||
OwningQPointer<QMenu> menu = new QMenu(this);
|
||||
QAction *act = menu->addAction(tr("Refresh"));
|
||||
QAction *act = menu->addAction(tr("Refresh"));
|
||||
|
||||
//HACK: could be ui->coupledView or ui->uncoupledView or ui->assetBeforeView or ui->assetAfterView
|
||||
// HACK: could be ui->coupledView or ui->uncoupledView or ui->assetBeforeView or
|
||||
// ui->assetAfterView
|
||||
QAbstractItemView *view = qobject_cast<QAbstractItemView *>(sender());
|
||||
if(!view)
|
||||
return; //Error: not called by the view?
|
||||
if (!view)
|
||||
return; // Error: not called by the view?
|
||||
|
||||
if(menu->exec(view->viewport()->mapToGlobal(pos)) != act)
|
||||
return; //User didn't select 'Refresh' action
|
||||
if (menu->exec(view->viewport()->mapToGlobal(pos)) != act)
|
||||
return; // User didn't select 'Refresh' action
|
||||
|
||||
//Refresh data
|
||||
// Refresh data
|
||||
coupledModel->refreshData(true);
|
||||
uncoupledModel->refreshData(true);
|
||||
trainAssetModelBefore->refreshData(true);
|
||||
|
@ -381,7 +467,7 @@ void EditStopDialog::couplingCustomContextMenuRequested(const QPoint& pos)
|
|||
|
||||
int EditStopDialog::getTrainSpeedKmH(bool afterStop)
|
||||
{
|
||||
const StopItem& curStop = helper->getCurItem();
|
||||
const StopItem &curStop = helper->getCurItem();
|
||||
|
||||
query q(Session->m_Db, "SELECT MIN(rs_models.max_speed), rs_id FROM("
|
||||
"SELECT coupling.rs_id AS rs_id, MAX(stops.arrival)"
|
||||
|
@ -392,11 +478,11 @@ int EditStopDialog::getTrainSpeedKmH(bool afterStop)
|
|||
" HAVING coupling.operation=1)"
|
||||
" JOIN rs_list ON rs_list.id=rs_id"
|
||||
" JOIN rs_models ON rs_models.id=rs_list.model_id");
|
||||
q.bind(1, m_jobId); //TODO: maybe move to model
|
||||
q.bind(1, m_jobId); // TODO: maybe move to model
|
||||
|
||||
//HACK: 1 minute is the min interval between stops,
|
||||
//by adding 1 minute we include the current stop but leave out the next one
|
||||
if(afterStop)
|
||||
// HACK: 1 minute is the min interval between stops,
|
||||
// by adding 1 minute we include the current stop but leave out the next one
|
||||
if (afterStop)
|
||||
q.bind(2, curStop.arrival.addSecs(60));
|
||||
else
|
||||
q.bind(2, curStop.arrival);
|
||||
|
@ -405,9 +491,62 @@ int EditStopDialog::getTrainSpeedKmH(bool afterStop)
|
|||
return q.getRows().get<int>(0);
|
||||
}
|
||||
|
||||
void EditStopDialog::updateAdditionalNotes()
|
||||
{
|
||||
const StopItem &curStop = helper->getCurItem();
|
||||
|
||||
QString msg;
|
||||
|
||||
// Check direction
|
||||
if (curStop.fromGate.gateConnId && curStop.toGate.gateConnId && curStop.type != StopType::First
|
||||
&& curStop.type != StopType::Last)
|
||||
{
|
||||
// Ignore First and Last stop (sometimes they have fake in/out gates set which might trigger
|
||||
// this message) Both entry and exit path are set, check direction
|
||||
if (curStop.fromGate.stationTrackSide == curStop.toGate.stationTrackSide)
|
||||
{
|
||||
// Train leaves station track from same side of entrance
|
||||
msg = tr("Train reverses direction.");
|
||||
}
|
||||
}
|
||||
|
||||
// Check line traction
|
||||
if (curStop.type != StopType::Last && curStop.nextSegment.segmentId)
|
||||
{
|
||||
// Last has no next segment so do not show traction type
|
||||
|
||||
bool nextSegmentElectrified = stopModel->isRailwayElectrifiedAfterRow(stopIdx.row());
|
||||
bool prevSegmentElectrified = !nextSegmentElectrified; // Trigger change on First stop
|
||||
|
||||
if (curStop.type != StopType::First && stopIdx.row() >= 0)
|
||||
{
|
||||
// Get real previous railway type
|
||||
prevSegmentElectrified = stopModel->isRailwayElectrifiedAfterRow(stopIdx.row() - 1);
|
||||
}
|
||||
|
||||
if (!msg.isEmpty())
|
||||
msg.append("\n\n"); // Separate from previous message
|
||||
|
||||
if (nextSegmentElectrified)
|
||||
msg.append(tr("Electric traction is ALLOWED."));
|
||||
else
|
||||
msg.append(tr("Electric traction is NOT ALLOWED."));
|
||||
|
||||
if (nextSegmentElectrified != prevSegmentElectrified && curStop.type != StopType::First)
|
||||
{
|
||||
// Railway type changed
|
||||
msg.append('\n');
|
||||
msg.append(tr("(Different traction then previous line!)"));
|
||||
}
|
||||
}
|
||||
|
||||
ui->additionalNotesLabel->setText(msg);
|
||||
ui->additionalNotesBox->setVisible(!msg.isEmpty());
|
||||
}
|
||||
|
||||
void EditStopDialog::setReadOnly(bool value)
|
||||
{
|
||||
readOnly = value;
|
||||
readOnly = value;
|
||||
|
||||
CustomCompletionLineEdit *mStationEdit = helper->getStationEdit();
|
||||
CustomCompletionLineEdit *mStTrackEdit = helper->getStTrackEdit();
|
||||
|
@ -428,73 +567,78 @@ void EditStopDialog::setReadOnly(bool value)
|
|||
|
||||
void EditStopDialog::done(int val)
|
||||
{
|
||||
if(val == QDialog::Accepted)
|
||||
if (val == QDialog::Accepted)
|
||||
{
|
||||
if(stopIdx.row() < stopModel->rowCount() - 2)
|
||||
if (stopIdx.row() < stopModel->rowCount() - 2)
|
||||
{
|
||||
//We are not last stop
|
||||
// We are not last stop
|
||||
|
||||
//Check if train has at least one engine after this stop
|
||||
//But not if we are Last stop (size - 1 - AddHere)
|
||||
//because the train doesn't have to leave the station
|
||||
// Check if train has at least one engine after this stop
|
||||
// But not if we are Last stop (size - 1 - AddHere)
|
||||
// because the train doesn't have to leave the station
|
||||
bool electricOnNonElectrifiedLine = false;
|
||||
if(!couplingMgr->hasEngineAfterStop(&electricOnNonElectrifiedLine) || electricOnNonElectrifiedLine)
|
||||
if (!couplingMgr->hasEngineAfterStop(&electricOnNonElectrifiedLine)
|
||||
|| electricOnNonElectrifiedLine)
|
||||
{
|
||||
int ret = QMessageBox::warning(this,
|
||||
tr("No Engine Left"),
|
||||
electricOnNonElectrifiedLine ?
|
||||
tr("It seems you have uncoupled all job engines except for electric ones "
|
||||
"but the line is not electrified\n"
|
||||
"(The train isn't able to move)\n"
|
||||
"Do you want to couple a non electric engine?") :
|
||||
tr("It seems you have uncoupled all job engines\n"
|
||||
"(The train isn't able to move)\n"
|
||||
"Do you want to couple an engine?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
int ret = QMessageBox::warning(
|
||||
this, tr("No Engine Left"),
|
||||
electricOnNonElectrifiedLine
|
||||
? tr("It seems you have uncoupled all job engines except for electric ones "
|
||||
"but the line is not electrified\n"
|
||||
"(The train isn't able to move)\n"
|
||||
"Do you want to couple a non electric engine?")
|
||||
: tr("It seems you have uncoupled all job engines\n"
|
||||
"(The train isn't able to move)\n"
|
||||
"Do you want to couple an engine?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if(ret == QMessageBox::Yes)
|
||||
if (ret == QMessageBox::Yes)
|
||||
{
|
||||
return; //Second chance to edit couplings
|
||||
return; // Second chance to edit couplings
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ENABLE_AUTO_TIME_RECALC
|
||||
if(originalSpeedAfterStop != newSpeedAfterStop)
|
||||
if (originalSpeedAfterStop != newSpeedAfterStop)
|
||||
{
|
||||
int speedBefore = originalSpeedAfterStop;
|
||||
int speedAfter = newSpeedAfterStop;
|
||||
int speedBefore = originalSpeedAfterStop;
|
||||
int speedAfter = newSpeedAfterStop;
|
||||
|
||||
LinesModel *linesModel = stopModel->getLinesModel();
|
||||
db_id lineId = curLine ? curLine : stopIdx.data(NEXT_LINE_ROLE).toLongLong();
|
||||
db_id lineId = curLine ? curLine : stopIdx.data(NEXT_LINE_ROLE).toLongLong();
|
||||
int lineSpeed = linesModel->getLineSpeed(lineId);
|
||||
if(speedBefore == 0)
|
||||
if (speedBefore == 0)
|
||||
{
|
||||
//If speed is null (likely because there weren't RS coupled before)
|
||||
//Fall back to line max speed
|
||||
// If speed is null (likely because there weren't RS coupled before)
|
||||
// Fall back to line max speed
|
||||
speedBefore = lineSpeed;
|
||||
}
|
||||
if(speedAfter == 0)
|
||||
if (speedAfter == 0)
|
||||
{
|
||||
//If speed is null (likely because there isn't RS coupled after this stop)
|
||||
//Fall back to line max speed
|
||||
// If speed is null (likely because there isn't RS coupled after this stop)
|
||||
// Fall back to line max speed
|
||||
speedAfter = lineSpeed;
|
||||
}
|
||||
int ret = QMessageBox::question(this,
|
||||
tr("Train Speed Changed"),
|
||||
tr("Train speed after this stop has changed from a value of %1 km/h to <b>%2 km/h</b>\n"
|
||||
"Do you want to rebase travel times to this new speed?\n"
|
||||
"NOTE: this doesn't affect stop times but you will lose manual adjustments to travel times")
|
||||
.arg(speedBefore).arg(speedAfter),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
|
||||
int ret = QMessageBox::question(
|
||||
this, tr("Train Speed Changed"),
|
||||
tr("Train speed after this stop has changed from a value of %1 km/h to <b>%2 "
|
||||
"km/h</b><br>"
|
||||
"Do you want to rebase travel times to this new speed?<br>"
|
||||
"NOTE: this doesn't affect stop times but you will lose manual adjustments to "
|
||||
"travel times")
|
||||
.arg(speedBefore)
|
||||
.arg(speedAfter),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Yes);
|
||||
|
||||
if(ret == QMessageBox::Cancel)
|
||||
if (ret == QMessageBox::Cancel)
|
||||
{
|
||||
return; //Second chance to edit couplings
|
||||
return; // Second chance to edit couplings
|
||||
}
|
||||
|
||||
if(ret == QMessageBox::Yes)
|
||||
if (ret == QMessageBox::Yes)
|
||||
{
|
||||
stopModel->rebaseTimesToSpeed(stopIdx.row(), ui->arrivalTimeEdit->time(), ui->departureTimeEdit->time());
|
||||
stopModel->rebaseTimesToSpeed(stopIdx.row(), ui->arrivalTimeEdit->time(),
|
||||
ui->departureTimeEdit->time());
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef EDITSTOPDIALOG2_H
|
||||
#define EDITSTOPDIALOG2_H
|
||||
|
||||
|
@ -9,7 +28,7 @@
|
|||
class StopEditingHelper;
|
||||
|
||||
class StopModel;
|
||||
class StopItem;
|
||||
struct StopItem;
|
||||
|
||||
class TrainAssetModel;
|
||||
class RSCouplingInterface;
|
||||
|
@ -42,6 +61,7 @@ public slots:
|
|||
void done(int val) override;
|
||||
|
||||
private slots:
|
||||
void importJobRS();
|
||||
void editCoupled();
|
||||
void editUncoupled();
|
||||
|
||||
|
@ -49,6 +69,8 @@ private slots:
|
|||
|
||||
void couplingCustomContextMenuRequested(const QPoint &pos);
|
||||
|
||||
void updateAdditionalNotes();
|
||||
|
||||
private:
|
||||
void saveDataToModel();
|
||||
|
||||
|
@ -73,11 +95,11 @@ private:
|
|||
StopCouplingModel *coupledModel;
|
||||
StopCouplingModel *uncoupledModel;
|
||||
|
||||
TrainAssetModel *trainAssetModelBefore;
|
||||
TrainAssetModel *trainAssetModelAfter;
|
||||
TrainAssetModel *trainAssetModelBefore;
|
||||
TrainAssetModel *trainAssetModelAfter;
|
||||
|
||||
JobPassingsModel *passingsModel;
|
||||
JobPassingsModel *crossingsModel;
|
||||
JobPassingsModel *passingsModel;
|
||||
JobPassingsModel *crossingsModel;
|
||||
|
||||
bool readOnly;
|
||||
};
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
<item row="0" column="0">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>1</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="stopTab">
|
||||
<attribute name="title">
|
||||
|
@ -220,6 +220,25 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QGroupBox" name="additionalNotesBox">
|
||||
<property name="title">
|
||||
<string>Additional Notes</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="additionalNotesLabel">
|
||||
<property name="text">
|
||||
<string>TextLabel</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="rsTab">
|
||||
|
@ -227,40 +246,6 @@
|
|||
<string>Rollingstock</string>
|
||||
</attribute>
|
||||
<layout class="QGridLayout" name="rsTabLay">
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="coupledBox">
|
||||
<property name="title">
|
||||
<string>Coupled</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="coupledLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QListView" name="coupledView"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="editCoupledBut">
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QGroupBox" name="uncoupledBox">
|
||||
<property name="title">
|
||||
|
@ -295,7 +280,7 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<item row="2" column="0" colspan="2">
|
||||
<widget class="QGroupBox" name="rollingstockBox">
|
||||
<property name="title">
|
||||
<string>Job Asset</string>
|
||||
|
@ -306,7 +291,6 @@
|
|||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
|
@ -341,7 +325,6 @@
|
|||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<weight>50</weight>
|
||||
<bold>false</bold>
|
||||
</font>
|
||||
</property>
|
||||
|
@ -352,7 +335,6 @@
|
|||
<property name="font">
|
||||
<font>
|
||||
<pointsize>10</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
|
@ -364,6 +346,47 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QGroupBox" name="coupledBox">
|
||||
<property name="title">
|
||||
<string>Coupled</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="coupledLayout">
|
||||
<property name="spacing">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="leftMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>3</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QListView" name="coupledView"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="editCoupledBut">
|
||||
<property name="text">
|
||||
<string>Edit</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="importJobRsBut">
|
||||
<property name="text">
|
||||
<string>Import From Job</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="infoTab">
|
||||
|
@ -406,7 +429,7 @@
|
|||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>837</width>
|
||||
<height>589</height>
|
||||
<height>581</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="infoScrollLay">
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "jobpatheditor.h"
|
||||
#include "ui_jobpatheditor.h"
|
||||
|
||||
|
@ -6,18 +25,12 @@
|
|||
|
||||
#include "viewmanager/viewmanager.h"
|
||||
|
||||
#include <QFileDialog>
|
||||
#include "utils/files/recentdirstore.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include "utils/owningqpointer.h"
|
||||
|
||||
#include <QCloseEvent>
|
||||
|
||||
#include "model/stopmodel.h"
|
||||
#include "stopdelegate.h"
|
||||
#include "jobs/jobsmanager/model/jobshelper.h"
|
||||
|
||||
#include "model/nextprevrsjobsmodel.h"
|
||||
|
||||
#include "jobs/jobeditor/editstopdialog.h"
|
||||
|
||||
#include "utils/jobcategorystrings.h"
|
||||
|
@ -33,6 +46,14 @@
|
|||
#include "utils/delegates/sql/customcompletionlineedit.h"
|
||||
#include "shifts/shiftcombomodel.h"
|
||||
|
||||
#include "utils/owningqpointer.h"
|
||||
#include <QMenu>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include "utils/files/recentdirstore.h"
|
||||
|
||||
#include <QCloseEvent>
|
||||
|
||||
JobPathEditor::JobPathEditor(QWidget *parent) :
|
||||
QDialog(parent),
|
||||
ui(new Ui::JobPathEditor),
|
||||
|
@ -46,7 +67,7 @@ JobPathEditor::JobPathEditor(QWidget *parent) :
|
|||
|
||||
QStringList catNames;
|
||||
catNames.reserve(int(JobCategory::NCategories));
|
||||
for(int cat = 0; cat < int(JobCategory::NCategories); cat++)
|
||||
for (int cat = 0; cat < int(JobCategory::NCategories); cat++)
|
||||
{
|
||||
catNames.append(JobCategoryName::fullName(JobCategory(cat)));
|
||||
}
|
||||
|
@ -56,24 +77,42 @@ JobPathEditor::JobPathEditor(QWidget *parent) :
|
|||
ui->categoryCombo->setCurrentIndex(-1);
|
||||
|
||||
ShiftComboModel *shiftComboModel = new ShiftComboModel(Session->m_Db, this);
|
||||
shiftCustomCombo = new CustomCompletionLineEdit(shiftComboModel);
|
||||
ui->verticalLayout->insertWidget(ui->verticalLayout->indexOf(ui->categoryCombo) + 1, shiftCustomCombo);
|
||||
shiftCombo = new CustomCompletionLineEdit(shiftComboModel);
|
||||
|
||||
// Get Catecory combo position and insert shift below
|
||||
int categoryRow = 0;
|
||||
QFormLayout::ItemRole unusedRole;
|
||||
ui->formLayout->getWidgetPosition(ui->categoryCombo, &categoryRow, &unusedRole);
|
||||
ui->formLayout->insertRow(categoryRow + 1, tr("Shift:"), shiftCombo);
|
||||
|
||||
// Stops
|
||||
stopModel = new StopModel(Session->m_Db, this);
|
||||
connect(shiftCustomCombo, &CustomCompletionLineEdit::dataIdChanged, stopModel, &StopModel::setNewShiftId);
|
||||
ui->view->setModel(stopModel);
|
||||
connect(shiftCombo, &CustomCompletionLineEdit::dataIdChanged, stopModel,
|
||||
&StopModel::setNewShiftId);
|
||||
ui->stopsView->setModel(stopModel);
|
||||
|
||||
delegate = new StopDelegate(Session->m_Db, this);
|
||||
ui->view->setItemDelegateForColumn(0, delegate);
|
||||
ui->stopsView->setItemDelegateForColumn(0, delegate);
|
||||
|
||||
ui->view->setResizeMode(QListView::Adjust);
|
||||
ui->view->setMovement(QListView::Static);
|
||||
ui->view->setSelectionMode(QListView::ContiguousSelection);
|
||||
ui->stopsView->setResizeMode(QListView::Adjust);
|
||||
ui->stopsView->setMovement(QListView::Static);
|
||||
ui->stopsView->setSelectionMode(QListView::ContiguousSelection);
|
||||
|
||||
connect(ui->categoryCombo, static_cast<void(QComboBox::*)(int)>(&QComboBox::activated), stopModel, &StopModel::setCategory);
|
||||
// Next/Prev Jobs
|
||||
prevJobsModel = new NextPrevRSJobsModel(Session->m_Db, this);
|
||||
prevJobsModel->setMode(NextPrevRSJobsModel::PrevJobs);
|
||||
ui->prevJobsView->setModel(prevJobsModel);
|
||||
|
||||
nextJobsModel = new NextPrevRSJobsModel(Session->m_Db, this);
|
||||
nextJobsModel->setMode(NextPrevRSJobsModel::NextJobs);
|
||||
ui->nextJobsView->setModel(nextJobsModel);
|
||||
|
||||
connect(ui->categoryCombo, static_cast<void (QComboBox::*)(int)>(&QComboBox::activated),
|
||||
stopModel, &StopModel::setCategory);
|
||||
connect(stopModel, &StopModel::categoryChanged, this, &JobPathEditor::onCategoryChanged);
|
||||
|
||||
connect(ui->jobIdSpin, static_cast<void(QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &JobPathEditor::startJobNumberTimer);
|
||||
connect(ui->jobIdSpin, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this,
|
||||
&JobPathEditor::startJobNumberTimer);
|
||||
connect(stopModel, &StopModel::jobIdChanged, this, &JobPathEditor::onJobIdChanged);
|
||||
|
||||
connect(stopModel, &StopModel::edited, this, &JobPathEditor::setEdited);
|
||||
|
@ -83,9 +122,18 @@ JobPathEditor::JobPathEditor(QWidget *parent) :
|
|||
|
||||
connect(ui->sheetBut, &QPushButton::clicked, this, &JobPathEditor::onSaveSheet);
|
||||
|
||||
ui->view->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->view, &QListView::customContextMenuRequested, this, &JobPathEditor::showContextMenu);
|
||||
connect(ui->view, &QListView::clicked, this, &JobPathEditor::onIndexClicked);
|
||||
ui->stopsView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->stopsView, &QListView::customContextMenuRequested, this,
|
||||
&JobPathEditor::showStopsContextMenu);
|
||||
connect(ui->stopsView, &QListView::clicked, this, &JobPathEditor::onStopIndexClicked);
|
||||
|
||||
ui->prevJobsView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->prevJobsView, &QListView::customContextMenuRequested, this,
|
||||
&JobPathEditor::showJobContextMenu);
|
||||
|
||||
ui->nextJobsView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->nextJobsView, &QListView::customContextMenuRequested, this,
|
||||
&JobPathEditor::showJobContextMenu);
|
||||
|
||||
connect(Session, &MeetingSession::jobRemoved, this, &JobPathEditor::onJobRemoved);
|
||||
connect(&AppSettings, &MRTPSettings::jobColorsChanged, this, &JobPathEditor::updateSpinColor);
|
||||
|
@ -104,16 +152,16 @@ JobPathEditor::~JobPathEditor()
|
|||
|
||||
bool JobPathEditor::setJob(db_id jobId)
|
||||
{
|
||||
if(!canSetJob)
|
||||
return false; //We are busy - (Avoid nested loop calls from inside 'saveChanges()')
|
||||
if (!canSetJob)
|
||||
return false; // We are busy - (Avoid nested loop calls from inside 'saveChanges()')
|
||||
|
||||
if(!isClear && stopModel->getJobId() == jobId)
|
||||
return true; //Fake return, we already set this job
|
||||
if (!isClear && stopModel->getJobId() == jobId)
|
||||
return true; // Fake return, we already set this job
|
||||
|
||||
if(isEdited())
|
||||
if (isEdited())
|
||||
{
|
||||
if(!maybeSave())
|
||||
return false; //User still wants to edit the current job
|
||||
if (!maybeSave())
|
||||
return false; // User still wants to edit the current job
|
||||
}
|
||||
|
||||
return setJob_internal(jobId);
|
||||
|
@ -123,36 +171,52 @@ bool JobPathEditor::setJob_internal(db_id jobId)
|
|||
{
|
||||
DEBUG_IMPORTANT_ENTRY;
|
||||
|
||||
if(!canSetJob)
|
||||
return false; //We are busy - (Avoid nested loop calls from inside 'saveChanges()')
|
||||
if (!canSetJob)
|
||||
return false; // We are busy - (Avoid nested loop calls from inside 'saveChanges()')
|
||||
|
||||
if(!isClear && stopModel->getJobId() == jobId)
|
||||
return true; //Fake return, we already set this job
|
||||
if (!isClear && stopModel->getJobId() == jobId)
|
||||
return true; // Fake return, we already set this job
|
||||
|
||||
isClear = false;
|
||||
|
||||
stopJobNumberTimer();
|
||||
|
||||
stopModel->loadJobStops(jobId); //Load from database
|
||||
// Load from database
|
||||
if (!stopModel->loadJobStops(jobId))
|
||||
{
|
||||
// Error: job could not be loaded, maybe invalid jobId
|
||||
clearJob();
|
||||
setEnabled(false);
|
||||
|
||||
//If read-only hide 'AddHere' row (last one)
|
||||
ui->view->setRowHidden(stopModel->rowCount() - 1, m_readOnly);
|
||||
QMessageBox::warning(this, tr("Error Loading Job"),
|
||||
tr("<b>Job %1</b> could not be loaded.<br>"
|
||||
"Maybe it's number was changed or maybe it doesn't exist at all.")
|
||||
.arg(jobId));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// If read-only hide 'AddHere' row (last one)
|
||||
ui->stopsView->setRowHidden(stopModel->rowCount() - 1, m_readOnly);
|
||||
|
||||
prevJobsModel->setJobId(jobId);
|
||||
nextJobsModel->setJobId(jobId);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JobPathEditor::startJobNumberTimer()
|
||||
{
|
||||
//Give user a small time to scroll values in ID QSpinBox
|
||||
//This will skip eventual non available IDs (already existent)
|
||||
//On timeout check ID and reset to old value if not available
|
||||
// Give user a small time to scroll values in ID QSpinBox
|
||||
// This will skip eventual non available IDs (already existent)
|
||||
// On timeout check ID and reset to old value if not available
|
||||
stopJobNumberTimer();
|
||||
jobNumberTimerId = startTimer(700);
|
||||
}
|
||||
|
||||
void JobPathEditor::stopJobNumberTimer()
|
||||
{
|
||||
if(jobNumberTimerId)
|
||||
if (jobNumberTimerId)
|
||||
{
|
||||
killTimer(jobNumberTimerId);
|
||||
jobNumberTimerId = 0;
|
||||
|
@ -161,15 +225,16 @@ void JobPathEditor::stopJobNumberTimer()
|
|||
|
||||
void JobPathEditor::checkJobNumberValid()
|
||||
{
|
||||
//Kill timer
|
||||
// Kill timer
|
||||
stopJobNumberTimer();
|
||||
|
||||
db_id jobId = ui->jobIdSpin->value();
|
||||
if(!stopModel->setNewJobId(jobId))
|
||||
if (!stopModel->setNewJobId(jobId))
|
||||
{
|
||||
QMessageBox::warning(this, tr("Invalid"),
|
||||
tr("Job number <b>%1</b> is already exists.<br>"
|
||||
"Please choose a different number.").arg(jobId));
|
||||
"Please choose a different number.")
|
||||
.arg(jobId));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -188,43 +253,43 @@ bool JobPathEditor::createNewJob(db_id *out)
|
|||
* or if user doesn't add at least 2 stops to the job
|
||||
*/
|
||||
|
||||
if(out)
|
||||
if (out)
|
||||
*out = 0;
|
||||
|
||||
if(!clearJob())
|
||||
return false; //Busy JobPathEditor
|
||||
if (!clearJob())
|
||||
return false; // Busy JobPathEditor
|
||||
|
||||
db_id jobId = 0;
|
||||
if(!JobsHelper::createNewJob(Session->m_Db, jobId) || jobId == 0)
|
||||
if (!JobsHelper::createNewJob(Session->m_Db, jobId) || jobId == 0)
|
||||
{
|
||||
return false; //An error occurred in database, abort
|
||||
return false; // An error occurred in database, abort
|
||||
}
|
||||
|
||||
if(!setJob_internal(jobId))
|
||||
if (!setJob_internal(jobId))
|
||||
{
|
||||
//If we fail opening JobPathEditor remove the job
|
||||
// If we fail opening JobPathEditor remove the job
|
||||
JobsHelper::removeJob(Session->m_Db, jobId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if(out)
|
||||
if (out)
|
||||
*out = jobId;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JobPathEditor::showContextMenu(const QPoint& pos)
|
||||
void JobPathEditor::showStopsContextMenu(const QPoint &pos)
|
||||
{
|
||||
QModelIndex index = ui->view->indexAt(pos);
|
||||
if(!index.isValid() || index.row()>= stopModel->rowCount() || stopModel->isAddHere(index))
|
||||
QModelIndex index = ui->stopsView->indexAt(pos);
|
||||
if (!index.isValid() || index.row() >= stopModel->rowCount() || stopModel->isAddHere(index))
|
||||
return;
|
||||
|
||||
OwningQPointer<QMenu> menu = new QMenu(this);
|
||||
QAction *toggleTransitAct = menu->addAction(tr("Toggle transit"));
|
||||
QAction *setToTransitAct = menu->addAction(tr("Set transit"));
|
||||
QAction *unsetTransit = menu->addAction(tr("Unset transit"));
|
||||
QAction *toggleTransitAct = menu->addAction(tr("Toggle transit"));
|
||||
QAction *setToTransitAct = menu->addAction(tr("Set transit"));
|
||||
QAction *unsetTransit = menu->addAction(tr("Unset transit"));
|
||||
menu->insertSeparator(unsetTransit);
|
||||
QAction *editStopAct = menu->addAction(tr("Edit stop"));
|
||||
QAction *editStopAct = menu->addAction(tr("Edit stop"));
|
||||
QAction *showStationSVG = menu->addAction(tr("Station SVG Plan"));
|
||||
menu->insertSeparator(editStopAct);
|
||||
QAction *removeStopAct = menu->addAction(tr("Remove"));
|
||||
|
@ -234,25 +299,25 @@ void JobPathEditor::showContextMenu(const QPoint& pos)
|
|||
unsetTransit->setEnabled(!m_readOnly);
|
||||
removeStopAct->setEnabled(!m_readOnly);
|
||||
|
||||
const db_id stationId = stopModel->getItemStationAt(index.row());
|
||||
showStationSVG->setEnabled(stationId != 0); //Enable only if station is set
|
||||
const StopItem stop = stopModel->getItemAt(index.row());
|
||||
showStationSVG->setEnabled(stop.stationId != 0); // Enable only if station is set
|
||||
|
||||
QAction *act = menu->exec(ui->view->viewport()->mapToGlobal(pos));
|
||||
QAction *act = menu->exec(ui->stopsView->viewport()->mapToGlobal(pos));
|
||||
|
||||
QItemSelectionModel *sm = ui->view->selectionModel();
|
||||
QItemSelectionModel *sm = ui->stopsView->selectionModel();
|
||||
|
||||
QItemSelectionRange range;
|
||||
QItemSelection s = ui->view->selectionModel()->selection();
|
||||
if(s.count() > 0)
|
||||
QItemSelection s = ui->stopsView->selectionModel()->selection();
|
||||
if (s.count() > 0)
|
||||
{
|
||||
//Take the first range only
|
||||
range = s.at(0); //Save range for later
|
||||
// Take the first range only
|
||||
range = s.at(0); // Save range for later
|
||||
}
|
||||
|
||||
//Select only 1 index
|
||||
// Select only 1 index
|
||||
sm->select(index, QItemSelectionModel::ClearAndSelect);
|
||||
|
||||
if(act == editStopAct)
|
||||
if (act == editStopAct)
|
||||
{
|
||||
OwningQPointer<EditStopDialog> dlg = new EditStopDialog(stopModel, this);
|
||||
dlg->setReadOnly(m_readOnly);
|
||||
|
@ -261,86 +326,140 @@ void JobPathEditor::showContextMenu(const QPoint& pos)
|
|||
return;
|
||||
}
|
||||
|
||||
if(act == showStationSVG)
|
||||
if (act == showStationSVG)
|
||||
{
|
||||
Session->getViewManager()->requestStSVGPlan(stationId);
|
||||
Session->getViewManager()->requestStSVGPlan(stop.stationId, true, stop.arrival);
|
||||
}
|
||||
|
||||
if(m_readOnly)
|
||||
if (m_readOnly)
|
||||
return;
|
||||
|
||||
if(range.isValid())
|
||||
if (range.isValid())
|
||||
{
|
||||
StopType type = StopType::ToggleType;
|
||||
bool useRange = true;
|
||||
if(act == toggleTransitAct)
|
||||
if (act == toggleTransitAct)
|
||||
type = StopType::ToggleType;
|
||||
else if(act == setToTransitAct)
|
||||
else if (act == setToTransitAct)
|
||||
type = StopType::Transit;
|
||||
else if(act == unsetTransit)
|
||||
else if (act == unsetTransit)
|
||||
type = StopType::Normal;
|
||||
else
|
||||
useRange = false;
|
||||
|
||||
if(useRange)
|
||||
if (useRange)
|
||||
{
|
||||
stopModel->setStopTypeRange(range.top(), range.bottom(), type);
|
||||
//Select only the range we changed (unselect possible other indexes)
|
||||
sm->select(QItemSelection(range.topLeft(), range.bottomRight()), QItemSelectionModel::ClearAndSelect);
|
||||
// Select only the range we changed (unselect possible other indexes)
|
||||
sm->select(QItemSelection(range.topLeft(), range.bottomRight()),
|
||||
QItemSelectionModel::ClearAndSelect);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(act == removeStopAct)
|
||||
if (act == removeStopAct)
|
||||
{
|
||||
stopModel->removeStop(index);
|
||||
}
|
||||
}
|
||||
|
||||
void JobPathEditor::showJobContextMenu(const QPoint &pos)
|
||||
{
|
||||
QTableView *jobView = qobject_cast<QTableView *>(sender());
|
||||
NextPrevRSJobsModel *jobModel = nextJobsModel;
|
||||
|
||||
if (jobView == ui->prevJobsView)
|
||||
jobModel = prevJobsModel;
|
||||
|
||||
QModelIndex index = jobView->indexAt(pos);
|
||||
|
||||
NextPrevRSJobsModel::Item item = jobModel->getItemAtRow(index.row());
|
||||
|
||||
OwningQPointer<QMenu> menu = new QMenu(this);
|
||||
QAction *goToStop = menu->addAction(tr("Go to Stop"));
|
||||
QAction *goToJob = menu->addAction(tr("Show Job"));
|
||||
QAction *showRSPlan = menu->addAction(tr("Show RS Plan"));
|
||||
menu->addSeparator();
|
||||
QAction *refreshViews = menu->addAction(tr("Refresh"));
|
||||
|
||||
// Enable only if RS is not going to depot
|
||||
goToStop->setEnabled(index.isValid());
|
||||
goToJob->setEnabled(index.isValid() && item.otherJobId != 0);
|
||||
showRSPlan->setEnabled(index.isValid());
|
||||
|
||||
QAction *act = menu->exec(jobView->viewport()->mapToGlobal(pos));
|
||||
|
||||
if (act == goToStop)
|
||||
{
|
||||
selectStop(item.stopId);
|
||||
}
|
||||
else if (act == goToJob)
|
||||
{
|
||||
if (isEdited()) // Prevent selecting other job before saving
|
||||
{
|
||||
if (!maybeSave())
|
||||
return;
|
||||
}
|
||||
Session->getViewManager()->requestJobSelection(item.otherJobId, true, true);
|
||||
}
|
||||
else if (act == showRSPlan)
|
||||
{
|
||||
Session->getViewManager()->requestRSInfo(item.rsId);
|
||||
}
|
||||
else if (act == refreshViews)
|
||||
{
|
||||
prevJobsModel->refreshData();
|
||||
nextJobsModel->refreshData();
|
||||
}
|
||||
}
|
||||
|
||||
bool JobPathEditor::clearJob()
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
if(!canSetJob)
|
||||
if (!canSetJob)
|
||||
return false;
|
||||
|
||||
if(isEdited())
|
||||
if (isEdited())
|
||||
{
|
||||
if(!maybeSave())
|
||||
if (!maybeSave())
|
||||
return false;
|
||||
}
|
||||
|
||||
isClear = true;
|
||||
|
||||
//Reset color
|
||||
// Reset color
|
||||
ui->jobIdSpin->setPalette(QPalette());
|
||||
|
||||
stopModel->clearJob();
|
||||
|
||||
stopJobNumberTimer();
|
||||
|
||||
prevJobsModel->clearData();
|
||||
nextJobsModel->clearData();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void JobPathEditor::done(int res)
|
||||
{
|
||||
if(res == Accepted)
|
||||
if (res == Accepted)
|
||||
{
|
||||
//Accepted: save changes
|
||||
if(!saveChanges())
|
||||
return; //Give user a second chance to edit job
|
||||
// Accepted: save changes
|
||||
if (!saveChanges())
|
||||
return; // Give user a second chance to edit job
|
||||
}
|
||||
else
|
||||
{
|
||||
//Rejected: discard changes
|
||||
// Rejected: discard changes
|
||||
discardChanges();
|
||||
}
|
||||
|
||||
//NOTE: if we call QDialog::done() the dialog is closed and QDockWidget remains open but empty
|
||||
// NOTE: if we call QDialog::done() the dialog is closed and QDockWidget remains open but empty
|
||||
setResult(res);
|
||||
if(res == QDialog::Accepted)
|
||||
if (res == QDialog::Accepted)
|
||||
emit accepted();
|
||||
else if(res == QDialog::Rejected)
|
||||
else if (res == QDialog::Rejected)
|
||||
emit rejected();
|
||||
emit finished(res);
|
||||
}
|
||||
|
@ -349,7 +468,7 @@ bool JobPathEditor::saveChanges()
|
|||
{
|
||||
DEBUG_IMPORTANT_ENTRY;
|
||||
|
||||
if(!canSetJob)
|
||||
if (!canSetJob)
|
||||
return false;
|
||||
canSetJob = false;
|
||||
|
||||
|
@ -360,15 +479,14 @@ bool JobPathEditor::saveChanges()
|
|||
stopModel->removeLastIfEmpty();
|
||||
stopModel->uncoupleStillCoupledAtLastStop();
|
||||
|
||||
if(stopModel->rowCount() < 3) //At least 2 stops + AddHere
|
||||
if (stopModel->rowCount() < 3) // At least 2 stops + AddHere
|
||||
{
|
||||
int res = QMessageBox::warning(this,
|
||||
tr("Error"),
|
||||
int res = QMessageBox::warning(this, tr("Error"),
|
||||
tr("You must register at least 2 stops.\n"
|
||||
"Do you want to delete this job?"),
|
||||
QMessageBox::Yes | QMessageBox::No);
|
||||
|
||||
if(res == QMessageBox::Yes)
|
||||
if (res == QMessageBox::Yes)
|
||||
{
|
||||
qDebug() << "User wants to delete job:" << stopModel->getJobId();
|
||||
stopModel->commitChanges();
|
||||
|
@ -380,19 +498,18 @@ bool JobPathEditor::saveChanges()
|
|||
}
|
||||
canSetJob = true;
|
||||
|
||||
return false; //Give user a second chance
|
||||
return false; // Give user a second chance
|
||||
}
|
||||
|
||||
//Re-check shift because user may have added stops so this job could last longer!
|
||||
if(stopModel->getNewShiftId())
|
||||
// Re-check shift because user may have added stops so this job could last longer!
|
||||
if (stopModel->getNewShiftId())
|
||||
{
|
||||
auto times = stopModel->getFirstLastTimes();
|
||||
|
||||
ShiftBusyModel model(Session->m_Db);
|
||||
model.loadData(stopModel->getNewShiftId(),
|
||||
stopModel->getJobId(),
|
||||
times.first, times.second);
|
||||
if(model.hasConcurrentJobs())
|
||||
model.loadData(stopModel->getNewShiftId(), stopModel->getJobId(), times.first,
|
||||
times.second);
|
||||
if (model.hasConcurrentJobs())
|
||||
{
|
||||
OwningQPointer<ShiftBusyDlg> dlg = new ShiftBusyDlg(this);
|
||||
dlg->setModel(&model);
|
||||
|
@ -405,20 +522,14 @@ bool JobPathEditor::saveChanges()
|
|||
}
|
||||
}
|
||||
|
||||
//Store before they are cleared on commitChanges()
|
||||
const auto stationsToUpdate = stopModel->getStationsToUpdate();
|
||||
const auto rsToUpdate = stopModel->getRsToUpdate();
|
||||
|
||||
stopModel->commitChanges();
|
||||
|
||||
//Update views
|
||||
emit Session->rollingStockPlanChanged(rsToUpdate);
|
||||
db_id newJobId = stopModel->getJobId();
|
||||
prevJobsModel->setJobId(newJobId);
|
||||
nextJobsModel->setJobId(newJobId);
|
||||
|
||||
//Update station views
|
||||
emit Session->stationPlanChanged(stationsToUpdate);
|
||||
|
||||
//When updating the path selection gets cleared so we restore it
|
||||
Session->getViewManager()->requestJobSelection(stopModel->getJobId(), true, true);
|
||||
// When updating the path selection gets cleared so we restore it
|
||||
Session->getViewManager()->requestJobSelection(newJobId, true, true);
|
||||
|
||||
canSetJob = true;
|
||||
return true;
|
||||
|
@ -428,46 +539,35 @@ void JobPathEditor::discardChanges()
|
|||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
if(!canSetJob)
|
||||
if (!canSetJob)
|
||||
return;
|
||||
|
||||
canSetJob = false;
|
||||
|
||||
closeStopEditor(); //Close before rolling savepoint
|
||||
closeStopEditor(); // Close before rolling savepoint
|
||||
|
||||
stopJobNumberTimer();
|
||||
|
||||
//Save them before reverting changes
|
||||
QSet<db_id> rsToUpdate = stopModel->getRsToUpdate();
|
||||
QSet<db_id> stationsToUpdate = stopModel->getStationsToUpdate();
|
||||
stopModel->revertChanges(); // Re-load old job from db
|
||||
|
||||
stopModel->revertChanges(); //Re-load old job from db
|
||||
|
||||
//After re-load but before possible 'clearJob()' (Below)
|
||||
//Because this hides 'AddHere' so after 'loadJobStops()'
|
||||
//Before 'clearJob()' because sets isEdited = false and
|
||||
// After re-load but before possible 'clearJob()' (Below)
|
||||
// Because this hides 'AddHere' so after 'loadJobStops()'
|
||||
// Before 'clearJob()' because sets isEdited = false and
|
||||
//'maybeSave()' doesn't be called in an infinite loop
|
||||
|
||||
canSetJob = true;
|
||||
|
||||
if(stopModel->rowCount() < 3) //At least 2 stops + AddHere
|
||||
if (stopModel->rowCount() < 3) // At least 2 stops + AddHere
|
||||
{
|
||||
//User discarded an invalid job so we delete it
|
||||
//This usually happens when you create a new job but then you change your mind and press 'Discard'
|
||||
// User discarded an invalid job so we delete it
|
||||
// This usually happens when you create a new job but then you change your mind and press
|
||||
// 'Discard'
|
||||
qDebug() << "User wants to delete job:" << stopModel->getJobId();
|
||||
stopModel->commitChanges();
|
||||
JobsHelper::removeJob(Session->m_Db, stopModel->getJobId());
|
||||
clearJob();
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
//After possible job deletion update views
|
||||
|
||||
//Update RS views
|
||||
emit Session->rollingStockPlanChanged(rsToUpdate);
|
||||
|
||||
//Update station views
|
||||
emit Session->stationPlanChanged(stationsToUpdate);
|
||||
}
|
||||
|
||||
db_id JobPathEditor::currentJobId() const
|
||||
|
@ -478,11 +578,9 @@ db_id JobPathEditor::currentJobId() const
|
|||
bool JobPathEditor::maybeSave()
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
QMessageBox::StandardButton ret = QMessageBox::question(this,
|
||||
tr("Save?"),
|
||||
tr("Do you want to save changes to job %1")
|
||||
.arg(stopModel->getJobId()),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
||||
QMessageBox::StandardButton ret = QMessageBox::question(
|
||||
this, tr("Save?"), tr("Do you want to save changes to job %1").arg(stopModel->getJobId()),
|
||||
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
|
||||
switch (ret)
|
||||
{
|
||||
case QMessageBox::Yes:
|
||||
|
@ -498,7 +596,7 @@ bool JobPathEditor::maybeSave()
|
|||
|
||||
void JobPathEditor::updateSpinColor()
|
||||
{
|
||||
if(!isClear)
|
||||
if (!isClear)
|
||||
{
|
||||
QColor col = Session->colorForCat(stopModel->getCategory());
|
||||
setSpinColor(col);
|
||||
|
@ -507,7 +605,7 @@ void JobPathEditor::updateSpinColor()
|
|||
|
||||
void JobPathEditor::timerEvent(QTimerEvent *e)
|
||||
{
|
||||
if(e->timerId() == jobNumberTimerId)
|
||||
if (e->timerId() == jobNumberTimerId)
|
||||
{
|
||||
checkJobNumberValid();
|
||||
return;
|
||||
|
@ -518,10 +616,10 @@ void JobPathEditor::timerEvent(QTimerEvent *e)
|
|||
|
||||
void JobPathEditor::onJobRemoved(db_id jobId)
|
||||
{
|
||||
//If the job shown is about to be removed clear JobPathEditor
|
||||
if(stopModel->getJobId() == jobId)
|
||||
// If the job shown is about to be removed clear JobPathEditor
|
||||
if (stopModel->getJobId() == jobId)
|
||||
{
|
||||
if(clearJob())
|
||||
if (clearJob())
|
||||
setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
@ -531,7 +629,7 @@ void JobPathEditor::onJobIdChanged(db_id jobId)
|
|||
ui->jobIdSpin->setValue(int(jobId));
|
||||
}
|
||||
|
||||
void JobPathEditor::setSpinColor(const QColor& col)
|
||||
void JobPathEditor::setSpinColor(const QColor &col)
|
||||
{
|
||||
QPalette pal = ui->jobIdSpin->palette();
|
||||
pal.setColor(QPalette::Text, col);
|
||||
|
@ -546,17 +644,15 @@ void JobPathEditor::onCategoryChanged(int newCat)
|
|||
|
||||
void JobPathEditor::onJobShiftChanged(db_id shiftId)
|
||||
{
|
||||
shiftCustomCombo->setData(shiftId);
|
||||
shiftCombo->setData(shiftId);
|
||||
|
||||
if(shiftId)
|
||||
if (shiftId)
|
||||
{
|
||||
auto times = stopModel->getFirstLastTimes();
|
||||
|
||||
ShiftBusyModel model(Session->m_Db);
|
||||
model.loadData(shiftId,
|
||||
stopModel->getJobId(),
|
||||
times.first, times.second);
|
||||
if(model.hasConcurrentJobs())
|
||||
model.loadData(shiftId, stopModel->getJobId(), times.first, times.second);
|
||||
if (model.hasConcurrentJobs())
|
||||
{
|
||||
OwningQPointer<ShiftBusyDlg> dlg = new ShiftBusyDlg(this);
|
||||
dlg->setModel(&model);
|
||||
|
@ -571,8 +667,7 @@ void JobPathEditor::onJobShiftChanged(db_id shiftId)
|
|||
|
||||
void JobPathEditor::onShiftError()
|
||||
{
|
||||
QMessageBox::warning(this,
|
||||
tr("Empty Job"),
|
||||
QMessageBox::warning(this, tr("Empty Job"),
|
||||
tr("Before setting a shift you should add stops to this job"),
|
||||
QMessageBox::Ok);
|
||||
}
|
||||
|
@ -590,29 +685,29 @@ void JobPathEditor::setEdited(bool val)
|
|||
|
||||
void JobPathEditor::setReadOnly(bool readOnly)
|
||||
{
|
||||
if(m_readOnly == readOnly)
|
||||
if (m_readOnly == readOnly)
|
||||
return;
|
||||
|
||||
m_readOnly = readOnly;
|
||||
|
||||
ui->jobIdSpin->setReadOnly(m_readOnly);
|
||||
ui->categoryCombo->setDisabled(m_readOnly);
|
||||
shiftCustomCombo->setDisabled(m_readOnly);
|
||||
shiftCombo->setDisabled(m_readOnly);
|
||||
|
||||
ui->buttonBox->setVisible(!m_readOnly);
|
||||
|
||||
//If read-only hide 'AddHere' row (last one)
|
||||
// If read-only hide 'AddHere' row (last one)
|
||||
int size = stopModel->rowCount();
|
||||
if(size > 0)
|
||||
ui->view->setRowHidden(size - 1, m_readOnly);
|
||||
if (size > 0)
|
||||
ui->stopsView->setRowHidden(size - 1, m_readOnly);
|
||||
|
||||
if(m_readOnly)
|
||||
if (m_readOnly)
|
||||
{
|
||||
ui->view->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
ui->stopsView->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
}
|
||||
else
|
||||
{
|
||||
ui->view->setEditTriggers(QAbstractItemView::DoubleClicked);
|
||||
ui->stopsView->setEditTriggers(QAbstractItemView::DoubleClicked);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -620,7 +715,7 @@ void JobPathEditor::onSaveSheet()
|
|||
{
|
||||
const QLatin1String job_sheet_key = QLatin1String("job_sheet_dir");
|
||||
|
||||
OwningQPointer<QFileDialog> dlg = new QFileDialog(this, tr("Save Job Sheet"));
|
||||
OwningQPointer<QFileDialog> dlg = new QFileDialog(this, tr("Save Job Sheet"));
|
||||
dlg->setFileMode(QFileDialog::AnyFile);
|
||||
dlg->setAcceptMode(QFileDialog::AcceptSave);
|
||||
dlg->setDirectory(RecentDirStore::getDir(job_sheet_key, RecentDirStore::Documents));
|
||||
|
@ -630,12 +725,12 @@ void JobPathEditor::onSaveSheet()
|
|||
filters << FileFormats::tr(FileFormats::odtFormat);
|
||||
dlg->setNameFilters(filters);
|
||||
|
||||
if(dlg->exec() != QDialog::Accepted || !dlg)
|
||||
if (dlg->exec() != QDialog::Accepted || !dlg)
|
||||
return;
|
||||
|
||||
QString fileName = dlg->selectedUrls().value(0).toLocalFile();
|
||||
|
||||
if(fileName.isEmpty())
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
RecentDirStore::setPath(job_sheet_key, fileName);
|
||||
|
@ -647,14 +742,14 @@ void JobPathEditor::onSaveSheet()
|
|||
utils::OpenFileInFolderDlg::askUser(tr("Job Sheet Saved"), fileName, this);
|
||||
}
|
||||
|
||||
void JobPathEditor::onIndexClicked(const QModelIndex& index)
|
||||
void JobPathEditor::onStopIndexClicked(const QModelIndex &index)
|
||||
{
|
||||
DEBUG_ENTRY;
|
||||
|
||||
if(m_readOnly)
|
||||
if (m_readOnly)
|
||||
return;
|
||||
|
||||
if(stopModel->isAddHere(index))
|
||||
if (stopModel->isAddHere(index))
|
||||
{
|
||||
qDebug() << index << "AddHere";
|
||||
|
||||
|
@ -662,30 +757,30 @@ void JobPathEditor::onIndexClicked(const QModelIndex& index)
|
|||
|
||||
int row = index.row();
|
||||
|
||||
if(row > 0)
|
||||
if (row > 0)
|
||||
{
|
||||
//idx - 1 is former Last Stop (now it became a normal Stop)
|
||||
//idx is new Last Stop (former AddHere)
|
||||
//idx + 1 is the new AddHere
|
||||
// idx - 1 is former Last Stop (now it became a normal Stop)
|
||||
// idx is new Last Stop (former AddHere)
|
||||
// idx + 1 is the new AddHere
|
||||
|
||||
//Edit former Last Stop
|
||||
// Edit former Last Stop
|
||||
QModelIndex prev = stopModel->index(row - 1, 0);
|
||||
ui->view->setCurrentIndex(prev);
|
||||
ui->view->scrollTo(prev);
|
||||
ui->view->edit(prev);
|
||||
ui->stopsView->setCurrentIndex(prev);
|
||||
ui->stopsView->scrollTo(prev);
|
||||
ui->stopsView->edit(prev);
|
||||
|
||||
//Tell editor to popup lines combo
|
||||
//QAbstractItemView::edit doesn't let you pass additional arguments
|
||||
//So we work around by emitting a signal
|
||||
//See 'StopDelegate::createEditor()'
|
||||
// Tell editor to popup lines combo
|
||||
// QAbstractItemView::edit doesn't let you pass additional arguments
|
||||
// So we work around by emitting a signal
|
||||
// See 'StopDelegate::createEditor()'
|
||||
emit delegate->popupEditorSegmentCombo();
|
||||
}
|
||||
else
|
||||
{
|
||||
QModelIndex lastStop = stopModel->index(row, 0);
|
||||
ui->view->setCurrentIndex(lastStop);
|
||||
ui->view->scrollTo(lastStop);
|
||||
ui->view->edit(lastStop);
|
||||
ui->stopsView->setCurrentIndex(lastStop);
|
||||
ui->stopsView->scrollTo(lastStop);
|
||||
ui->stopsView->edit(lastStop);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -697,9 +792,9 @@ bool JobPathEditor::getCanSetJob() const
|
|||
|
||||
void JobPathEditor::closeStopEditor()
|
||||
{
|
||||
QModelIndex idx = ui->view->currentIndex();
|
||||
QWidget *ed = ui->view->indexWidget(idx);
|
||||
if(ed == nullptr)
|
||||
QModelIndex idx = ui->stopsView->currentIndex();
|
||||
QWidget *ed = ui->stopsView->indexWidget(idx);
|
||||
if (ed == nullptr)
|
||||
return;
|
||||
emit delegate->commitData(ed);
|
||||
emit delegate->closeEditor(ed);
|
||||
|
@ -707,10 +802,10 @@ void JobPathEditor::closeStopEditor()
|
|||
|
||||
void JobPathEditor::closeEvent(QCloseEvent *e)
|
||||
{
|
||||
//TODO: prevent QDockWidget closing even if we ignore this event
|
||||
if(isEdited())
|
||||
// TODO: prevent QDockWidget closing even if we ignore this event
|
||||
if (isEdited())
|
||||
{
|
||||
if(maybeSave())
|
||||
if (maybeSave())
|
||||
e->accept();
|
||||
else
|
||||
e->ignore();
|
||||
|
@ -724,10 +819,10 @@ void JobPathEditor::closeEvent(QCloseEvent *e)
|
|||
void JobPathEditor::selectStop(db_id stopId)
|
||||
{
|
||||
int row = stopModel->getStopRow(stopId);
|
||||
if(row >= 0)
|
||||
if (row >= 0)
|
||||
{
|
||||
QModelIndex idx = stopModel->index(row, 0);
|
||||
ui->view->setCurrentIndex(idx);
|
||||
ui->view->scrollTo(idx, QListView::EnsureVisible);
|
||||
ui->stopsView->setCurrentIndex(idx);
|
||||
ui->stopsView->scrollTo(idx, QListView::EnsureVisible);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,18 +1,34 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef JOBPATHEDITOR_H
|
||||
#define JOBPATHEDITOR_H
|
||||
|
||||
#include <QDialog>
|
||||
|
||||
#include <QSet>
|
||||
|
||||
#include <QMenu>
|
||||
|
||||
#include "utils/types.h"
|
||||
|
||||
class StopDelegate;
|
||||
class CustomCompletionLineEdit;
|
||||
|
||||
class StopModel;
|
||||
class NextPrevRSJobsModel;
|
||||
|
||||
namespace Ui {
|
||||
class JobPathEditor;
|
||||
|
@ -32,7 +48,7 @@ class JobPathEditor : public QDialog
|
|||
|
||||
public:
|
||||
explicit JobPathEditor(QWidget *parent = nullptr);
|
||||
~JobPathEditor()override;
|
||||
~JobPathEditor() override;
|
||||
|
||||
bool setJob(db_id jobId);
|
||||
bool createNewJob(db_id *out = nullptr);
|
||||
|
@ -71,8 +87,11 @@ protected:
|
|||
private slots:
|
||||
void setEdited(bool val);
|
||||
|
||||
void showContextMenu(const QPoint &pos);
|
||||
void onIndexClicked(const QModelIndex &index);
|
||||
void showJobContextMenu(const QPoint &pos);
|
||||
void showStopsContextMenu(const QPoint &pos);
|
||||
|
||||
void onStopIndexClicked(const QModelIndex &index);
|
||||
|
||||
void onJobRemoved(db_id jobId);
|
||||
|
||||
void startJobNumberTimer();
|
||||
|
@ -93,17 +112,20 @@ private:
|
|||
private:
|
||||
Ui::JobPathEditor *ui;
|
||||
|
||||
CustomCompletionLineEdit *shiftCustomCombo;
|
||||
CustomCompletionLineEdit *shiftCombo;
|
||||
|
||||
StopModel *stopModel;
|
||||
StopDelegate *delegate;
|
||||
|
||||
NextPrevRSJobsModel *prevJobsModel;
|
||||
NextPrevRSJobsModel *nextJobsModel;
|
||||
|
||||
int jobNumberTimerId;
|
||||
|
||||
//TODO: there are too many bools
|
||||
// TODO: there are too many bools
|
||||
bool isClear;
|
||||
|
||||
bool canSetJob; //TODO: better name
|
||||
bool canSetJob; // TODO: better name
|
||||
bool m_readOnly;
|
||||
};
|
||||
|
||||
|
|
|
@ -6,35 +6,15 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>320</width>
|
||||
<height>603</height>
|
||||
<width>350</width>
|
||||
<height>600</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="bottomMargin">
|
||||
<number>9</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="jobIdSpin">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<weight>75</weight>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="4" column="1">
|
||||
<widget class="QComboBox" name="categoryCombo">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
|
@ -49,17 +29,93 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListView" name="view"/>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="categoryLabel">
|
||||
<property name="text">
|
||||
<string>Category:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="numberLabel">
|
||||
<property name="text">
|
||||
<string>Number:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QSpinBox" name="jobIdSpin">
|
||||
<property name="font">
|
||||
<font>
|
||||
<pointsize>12</pointsize>
|
||||
<bold>true</bold>
|
||||
</font>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>99999</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0" colspan="2">
|
||||
<widget class="QSplitter" name="splitter">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QVBoxLayout" name="splitterLay1">
|
||||
<item>
|
||||
<widget class="QLabel" name="stopsLabel">
|
||||
<property name="text">
|
||||
<string>Stops:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QListView" name="stopsView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="">
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<item>
|
||||
<widget class="QLabel" name="prevJobsLabel">
|
||||
<property name="text">
|
||||
<string>Prev Jobs:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableView" name="prevJobsView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<widget class="QWidget" name="layoutWidget">
|
||||
<layout class="QVBoxLayout" name="splitterLay3">
|
||||
<item>
|
||||
<widget class="QLabel" name="nextJobsLabel">
|
||||
<property name="text">
|
||||
<string>Next Jobs:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QTableView" name="nextJobsView"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QPushButton" name="sheetBut">
|
||||
<property name="text">
|
||||
<string>Save Sheet</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QDialogButtonBox" name="buttonBox">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
|
|
|
@ -1,22 +1,23 @@
|
|||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
|
||||
jobs/jobeditor/model/nextprevrsjobsmodel.h
|
||||
jobs/jobeditor/model/jobpassingsmodel.h
|
||||
jobs/jobeditor/model/rscouplinginterface.h
|
||||
jobs/jobeditor/model/rslistondemandmodel.h
|
||||
jobs/jobeditor/model/rslistondemandmodelresultevent.h
|
||||
jobs/jobeditor/model/rsproxymodel.h
|
||||
jobs/jobeditor/model/stopcouplingmodel.h
|
||||
jobs/jobeditor/model/trainassetmodel.h
|
||||
jobs/jobeditor/model/stopmodel.h
|
||||
|
||||
jobs/jobeditor/model/nextprevrsjobsmodel.cpp
|
||||
jobs/jobeditor/model/jobpassingsmodel.cpp
|
||||
jobs/jobeditor/model/rscouplinginterface.cpp
|
||||
jobs/jobeditor/model/rslistondemandmodel.cpp
|
||||
jobs/jobeditor/model/rslistondemandmodelresultevent.cpp
|
||||
jobs/jobeditor/model/rsproxymodel.cpp
|
||||
jobs/jobeditor/model/stopcouplingmodel.cpp
|
||||
jobs/jobeditor/model/trainassetmodel.cpp
|
||||
jobs/jobeditor/model/stopmodel.cpp
|
||||
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "jobpassingsmodel.h"
|
||||
|
||||
#include <QFont>
|
||||
|
@ -11,7 +30,7 @@ JobPassingsModel::JobPassingsModel(QObject *parent) :
|
|||
|
||||
QVariant JobPassingsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||
{
|
||||
switch (section)
|
||||
{
|
||||
|
@ -52,7 +71,7 @@ QVariant JobPassingsModel::data(const QModelIndex &idx, int role) const
|
|||
if (!idx.isValid() || idx.row() >= m_data.size() || idx.column() >= NCols)
|
||||
return QVariant();
|
||||
|
||||
const Entry& e = m_data.at(idx.row());
|
||||
const Entry &e = m_data.at(idx.row());
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
|
@ -78,7 +97,7 @@ QVariant JobPassingsModel::data(const QModelIndex &idx, int role) const
|
|||
{
|
||||
QFont f;
|
||||
f.setPointSize(10);
|
||||
if(idx.column() == JobNameCol)
|
||||
if (idx.column() == JobNameCol)
|
||||
f.setBold(true);
|
||||
return f;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef JOBPASSINGSMODEL_H
|
||||
#define JOBPASSINGSMODEL_H
|
||||
|
||||
|
@ -14,22 +33,23 @@ class JobPassingsModel : public QAbstractTableModel
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
typedef enum {
|
||||
enum Columns
|
||||
{
|
||||
JobNameCol = 0,
|
||||
ArrivalCol,
|
||||
DepartureCol,
|
||||
PlatformCol,
|
||||
NCols
|
||||
} Columns;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
struct Entry
|
||||
{
|
||||
db_id jobId;
|
||||
QTime arrival;
|
||||
QTime departure;
|
||||
QString platform;
|
||||
JobCategory category;
|
||||
} Entry;
|
||||
};
|
||||
|
||||
explicit JobPassingsModel(QObject *parent = nullptr);
|
||||
|
||||
|
@ -41,7 +61,7 @@ public:
|
|||
|
||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void setJobs(const QVector<Entry>& vec);
|
||||
void setJobs(const QVector<Entry> &vec);
|
||||
|
||||
private:
|
||||
QVector<Entry> m_data;
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "nextprevrsjobsmodel.h"
|
||||
|
||||
#include "utils/jobcategorystrings.h"
|
||||
#include "utils/rs_utils.h"
|
||||
|
||||
#include <QFont>
|
||||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
|
||||
NextPrevRSJobsModel::NextPrevRSJobsModel(sqlite3pp::database &db, QObject *parent) :
|
||||
QAbstractTableModel(parent),
|
||||
mDb(db)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant NextPrevRSJobsModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||
{
|
||||
switch (section)
|
||||
{
|
||||
case JobIdCol:
|
||||
return tr("Job");
|
||||
case RsNameCol:
|
||||
return tr("RS item");
|
||||
case TimeCol:
|
||||
return tr("Time");
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return QAbstractTableModel::headerData(section, orientation, role);
|
||||
}
|
||||
|
||||
int NextPrevRSJobsModel::rowCount(const QModelIndex &p) const
|
||||
{
|
||||
return p.isValid() ? 0 : m_data.size();
|
||||
}
|
||||
|
||||
int NextPrevRSJobsModel::columnCount(const QModelIndex &p) const
|
||||
{
|
||||
return p.isValid() ? 0 : NCols;
|
||||
}
|
||||
|
||||
QVariant NextPrevRSJobsModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if (!idx.isValid() || idx.row() >= m_data.size() || idx.column() >= NCols)
|
||||
return QVariant();
|
||||
|
||||
const Item &item = m_data.at(idx.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
switch (idx.column())
|
||||
{
|
||||
case JobIdCol:
|
||||
{
|
||||
if (item.otherJobCat == JobCategory::NCategories)
|
||||
return tr("Depot"); // Rollingstock item taken from/released to depot
|
||||
return JobCategoryName::jobName(item.otherJobId, item.otherJobCat);
|
||||
}
|
||||
case RsNameCol:
|
||||
return item.rsName;
|
||||
case TimeCol:
|
||||
return item.opTime;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Qt::FontRole:
|
||||
{
|
||||
if (idx.column() == JobIdCol && item.otherJobCat == JobCategory::NCategories)
|
||||
{
|
||||
// Rollingstock item taken from/released to depot, distinguish font from job names
|
||||
QFont f;
|
||||
f.setItalic(true);
|
||||
return f;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void NextPrevRSJobsModel::refreshData()
|
||||
{
|
||||
// GROUP by rollingstock
|
||||
QByteArray sql =
|
||||
"SELECT c.id, c.rs_id, rs_list.number, rs_models.name, rs_models.suffix, rs_models.type, "
|
||||
"stops.%time0,"
|
||||
" %min(s1.%time0), stops.id, s1.job_id AS job_id, j1.category"
|
||||
" FROM stops"
|
||||
" JOIN coupling c ON c.stop_id=stops.id"
|
||||
" JOIN rs_list ON rs_list.id=c.rs_id"
|
||||
" JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" LEFT JOIN coupling c1 ON c1.rs_id=c.rs_id"
|
||||
" LEFT JOIN stops s1 ON s1.id=c1.stop_id"
|
||||
" LEFT JOIN jobs j1 ON j1.id=s1.job_id"
|
||||
" WHERE stops.job_id=?1 AND c.operation=%rem AND c1.operation=%add AND s1.%time0 %gt "
|
||||
"stops.%time1"
|
||||
" GROUP BY c.rs_id"
|
||||
" UNION ALL "
|
||||
"SELECT c.id, c.rs_id, rs_list.number, rs_models.name, rs_models.suffix, rs_models.type, "
|
||||
"stops.%time0,"
|
||||
" %max(s1.%time0), stops.id, NULL AS job_id, NULL"
|
||||
" FROM stops"
|
||||
" JOIN coupling c ON c.stop_id=stops.id"
|
||||
" JOIN rs_list ON rs_list.id=c.rs_id"
|
||||
" JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" LEFT JOIN coupling c1 ON c1.rs_id=c.rs_id"
|
||||
" LEFT JOIN stops s1 ON s1.id=c1.stop_id"
|
||||
" WHERE stops.job_id=?1 AND c.operation=%rem AND c1.operation=%add"
|
||||
" GROUP BY c.rs_id"
|
||||
" HAVING s1.%time1 %lt stops.%time0"
|
||||
" ORDER BY stops.%time0, job_id, rs_models.type, rs_models.name, rs_list.number"
|
||||
" LIMIT 100";
|
||||
|
||||
if (m_mode == NextJobs)
|
||||
{
|
||||
sql.replace("%time0", "arrival");
|
||||
sql.replace("%time1", "departure");
|
||||
sql.replace("%min", "MIN");
|
||||
sql.replace("%max", "MAX");
|
||||
sql.replace("%rem", "0");
|
||||
sql.replace("%add", "1");
|
||||
sql.replace("%gt", ">=");
|
||||
sql.replace("%lt", "<=");
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invert all
|
||||
sql.replace("%time0", "departure");
|
||||
sql.replace("%time1", "arrival");
|
||||
sql.replace("%min", "MAX");
|
||||
sql.replace("%max", "MIN");
|
||||
sql.replace("%rem", "1");
|
||||
sql.replace("%add", "0");
|
||||
sql.replace("%gt", "<=");
|
||||
sql.replace("%lt", ">=");
|
||||
}
|
||||
|
||||
/*
|
||||
//GROUP by next job_id (NOTE: added COUNT(1) column)
|
||||
QByteArray sql1 = "SELECT c.id, c.rs_id, c.operation, rs_models.name, rs_list.number,"
|
||||
" stops.arrival, MIN(s1.arrival), s1.job_id, c1.operation, COUNT(1)"
|
||||
" FROM stops"
|
||||
" JOIN coupling c ON c.stop_id=stops.id"
|
||||
" JOIN rs_list ON rs_list.id=c.rs_id"
|
||||
" JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" LEFT JOIN coupling c1 ON c1.rs_id=c.rs_id"
|
||||
" LEFT JOIN stops s1 ON s1.id=c1.stop_id"
|
||||
" WHERE stops.job_id=16816 AND c.operation=0 AND c1.operation=1"
|
||||
" AND s1.arrival >= stops.departure"
|
||||
" GROUP BY s1.job_id"
|
||||
" UNION ALL "
|
||||
"SELECT *, COUNT(1) FROM (" //TODO: better way than sub query?
|
||||
" SELECT c.id, c.rs_id, c.operation, rs_models.name, rs_list.number,"
|
||||
" stops.arrival, MAX(s1.arrival), NULL, c1.operation, COUNT(1)"
|
||||
" FROM stops"
|
||||
" JOIN coupling c ON c.stop_id=stops.id"
|
||||
" JOIN rs_list ON rs_list.id=c.rs_id"
|
||||
" JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" LEFT JOIN coupling c1 ON c1.rs_id=c.rs_id"
|
||||
" LEFT JOIN stops s1 ON s1.id=c1.stop_id"
|
||||
" WHERE stops.job_id=16816 AND c.operation=0 AND c1.operation=1"
|
||||
" GROUP BY c.rs_id"
|
||||
" HAVING s1.departure <= stops.arrival"
|
||||
")";
|
||||
*/
|
||||
|
||||
sqlite3pp::query q(mDb, sql);
|
||||
q.bind(1, m_jobId);
|
||||
|
||||
beginResetModel();
|
||||
m_data.clear();
|
||||
|
||||
for (auto row : q)
|
||||
{
|
||||
Item item;
|
||||
item.couplingId = row.get<db_id>(0);
|
||||
item.rsId = row.get<db_id>(1);
|
||||
|
||||
int number = row.get<int>(2);
|
||||
int modelNameLen = sqlite3_column_bytes(q.stmt(), 3);
|
||||
const char *modelName = reinterpret_cast<char const *>(sqlite3_column_text(q.stmt(), 3));
|
||||
|
||||
int modelSuffixLen = sqlite3_column_bytes(q.stmt(), 4);
|
||||
const char *modelSuffix = reinterpret_cast<char const *>(sqlite3_column_text(q.stmt(), 4));
|
||||
RsType rsType = RsType(row.get<int>(5));
|
||||
|
||||
item.rsName = rs_utils::formatNameRef(modelName, modelNameLen, number, modelSuffix,
|
||||
modelSuffixLen, rsType);
|
||||
|
||||
item.opTime = row.get<QTime>(6);
|
||||
// Ignore column 7, which is used just for MIN/MAX
|
||||
item.stopId = row.get<db_id>(8);
|
||||
item.otherJobId = row.get<db_id>(9);
|
||||
item.otherJobCat = JobCategory(row.get<int>(10));
|
||||
if (row.column_type(10) == SQLITE_NULL)
|
||||
item.otherJobCat = JobCategory::NCategories;
|
||||
|
||||
m_data.append(item);
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void NextPrevRSJobsModel::clearData()
|
||||
{
|
||||
beginResetModel();
|
||||
m_data.clear();
|
||||
m_data.squeeze();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
db_id NextPrevRSJobsModel::jobId() const
|
||||
{
|
||||
return m_jobId;
|
||||
}
|
||||
|
||||
void NextPrevRSJobsModel::setJobId(db_id newJobId)
|
||||
{
|
||||
m_jobId = newJobId;
|
||||
if (m_jobId)
|
||||
refreshData();
|
||||
else
|
||||
clearData();
|
||||
}
|
||||
|
||||
NextPrevRSJobsModel::Mode NextPrevRSJobsModel::mode() const
|
||||
{
|
||||
return m_mode;
|
||||
}
|
||||
|
||||
void NextPrevRSJobsModel::setMode(Mode newMode)
|
||||
{
|
||||
m_mode = newMode;
|
||||
if (m_jobId)
|
||||
refreshData();
|
||||
}
|
||||
|
||||
NextPrevRSJobsModel::Item NextPrevRSJobsModel::getItemAtRow(int row) const
|
||||
{
|
||||
return m_data.value(row, Item());
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef NEXTPREVRSJOBSMODEL_H
|
||||
#define NEXTPREVRSJOBSMODEL_H
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QVector>
|
||||
#include <QTime>
|
||||
|
||||
#include "utils/types.h"
|
||||
|
||||
namespace sqlite3pp {
|
||||
class database;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief The NextPrevRSJobsModel class
|
||||
*
|
||||
* This model shows Next/Prev Jobs of currently set Job
|
||||
*
|
||||
* Next/Prev jobs are calculated based on coupled RS plans.
|
||||
* RS which do not have previous/next job are showed with "Depot" string
|
||||
*/
|
||||
class NextPrevRSJobsModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Columns
|
||||
{
|
||||
JobIdCol = 0,
|
||||
RsNameCol,
|
||||
TimeCol,
|
||||
NCols
|
||||
};
|
||||
|
||||
enum Mode
|
||||
{
|
||||
PrevJobs = 0,
|
||||
NextJobs
|
||||
};
|
||||
|
||||
struct Item
|
||||
{
|
||||
db_id otherJobId = 0;
|
||||
db_id couplingId = 0;
|
||||
db_id rsId = 0;
|
||||
db_id stopId = 0;
|
||||
|
||||
QString rsName;
|
||||
QTime opTime;
|
||||
|
||||
JobCategory otherJobCat = JobCategory::NCategories;
|
||||
};
|
||||
|
||||
NextPrevRSJobsModel(sqlite3pp::database &db, QObject *parent = nullptr);
|
||||
|
||||
// Header:
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const override;
|
||||
|
||||
// Basic functionality:
|
||||
int rowCount(const QModelIndex &p = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &p = QModelIndex()) const override;
|
||||
|
||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void refreshData();
|
||||
void clearData();
|
||||
|
||||
db_id jobId() const;
|
||||
void setJobId(db_id newJobId);
|
||||
|
||||
Mode mode() const;
|
||||
void setMode(Mode newMode);
|
||||
|
||||
Item getItemAtRow(int row) const;
|
||||
|
||||
private:
|
||||
sqlite3pp::database &mDb;
|
||||
|
||||
db_id m_jobId = 0;
|
||||
QVector<Item> m_data;
|
||||
|
||||
Mode m_mode = PrevJobs;
|
||||
};
|
||||
|
||||
#endif // NEXTPREVRSJOBSMODEL_H
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "rscouplinginterface.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
|
@ -7,6 +26,9 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "stopmodel.h"
|
||||
#include "utils/rs_utils.h"
|
||||
|
||||
#include <QElapsedTimer>
|
||||
|
||||
RSCouplingInterface::RSCouplingInterface(database &db, QObject *parent) :
|
||||
QObject(parent),
|
||||
|
@ -17,16 +39,15 @@ RSCouplingInterface::RSCouplingInterface(database &db, QObject *parent) :
|
|||
" coupling(stop_id,rs_id,operation)"
|
||||
" VALUES(?, ?, ?)")
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RSCouplingInterface::loadCouplings(StopModel *model, db_id stopId, db_id jobId, QTime arr)
|
||||
{
|
||||
stopsModel = model;
|
||||
|
||||
m_stopId = stopId;
|
||||
m_jobId = jobId;
|
||||
arrival = arr;
|
||||
m_stopId = stopId;
|
||||
m_jobId = jobId;
|
||||
arrival = arr;
|
||||
|
||||
coupled.clear();
|
||||
uncoupled.clear();
|
||||
|
@ -34,12 +55,12 @@ void RSCouplingInterface::loadCouplings(StopModel *model, db_id stopId, db_id jo
|
|||
query q(mDb, "SELECT rs_id, operation FROM coupling WHERE stop_id=?");
|
||||
q.bind(1, m_stopId);
|
||||
|
||||
for(auto rs : q)
|
||||
for (auto rs : q)
|
||||
{
|
||||
db_id rsId = rs.get<db_id>(0);
|
||||
RsOp op = RsOp(rs.get<int>(1));
|
||||
RsOp op = RsOp(rs.get<int>(1));
|
||||
|
||||
if(op == RsOp::Coupled)
|
||||
if (op == RsOp::Coupled)
|
||||
coupled.append(rsId);
|
||||
else
|
||||
uncoupled.append(rsId);
|
||||
|
@ -48,20 +69,21 @@ void RSCouplingInterface::loadCouplings(StopModel *model, db_id stopId, db_id jo
|
|||
|
||||
bool RSCouplingInterface::contains(db_id rsId, RsOp op) const
|
||||
{
|
||||
if(op == RsOp::Coupled)
|
||||
if (op == RsOp::Coupled)
|
||||
return coupled.contains(rsId);
|
||||
else
|
||||
return uncoupled.contains(rsId);
|
||||
}
|
||||
|
||||
bool RSCouplingInterface::coupleRS(db_id rsId, const QString& rsName, bool on, bool checkTractionType)
|
||||
bool RSCouplingInterface::coupleRS(db_id rsId, const QString &rsName, bool on,
|
||||
bool checkTractionType)
|
||||
{
|
||||
stopsModel->startStopsEditing();
|
||||
stopsModel->markRsToUpdate(rsId);
|
||||
|
||||
if(on)
|
||||
if (on)
|
||||
{
|
||||
if(coupled.contains(rsId))
|
||||
if (coupled.contains(rsId))
|
||||
{
|
||||
qWarning() << "Error already checked:" << rsId;
|
||||
return true;
|
||||
|
@ -77,82 +99,79 @@ bool RSCouplingInterface::coupleRS(db_id rsId, const QString& rsName, bool on, b
|
|||
" AND stops.arrival<?");
|
||||
q_RS_lastOp.bind(1, rsId);
|
||||
q_RS_lastOp.bind(2, arrival);
|
||||
int ret = q_RS_lastOp.step();
|
||||
int ret = q_RS_lastOp.step();
|
||||
|
||||
bool isOccupied = false; //No Op means RS is turned off in a depot so it isn't occupied
|
||||
if(ret == SQLITE_ROW)
|
||||
bool isOccupied = false; // No Op means RS is turned off in a depot so it isn't occupied
|
||||
if (ret == SQLITE_ROW)
|
||||
{
|
||||
auto row = q_RS_lastOp.getRows();
|
||||
RsOp operation = RsOp(row.get<int>(1)); //Get last operation
|
||||
jobId = row.get<db_id>(2);
|
||||
isOccupied = (operation == RsOp::Coupled);
|
||||
auto row = q_RS_lastOp.getRows();
|
||||
RsOp operation = RsOp(row.get<int>(1)); // Get last operation
|
||||
jobId = row.get<db_id>(2);
|
||||
isOccupied = (operation == RsOp::Coupled);
|
||||
}
|
||||
|
||||
if(isOccupied)
|
||||
if (isOccupied)
|
||||
{
|
||||
if(jobId == m_jobId)
|
||||
if (jobId == m_jobId)
|
||||
{
|
||||
qWarning() << "Error while adding coupling op. Stop:" << m_stopId
|
||||
<< "Rs:" << rsId << "Already coupled by this job:" << m_jobId;
|
||||
qWarning() << "Error while adding coupling op. Stop:" << m_stopId << "Rs:" << rsId
|
||||
<< "Already coupled by this job:" << m_jobId;
|
||||
|
||||
QMessageBox::warning(qApp->activeWindow(),
|
||||
tr("Error"),
|
||||
QMessageBox::warning(qApp->activeWindow(), tr("Error"),
|
||||
tr("Error while adding coupling operation.\n"
|
||||
"Rollingstock %1 is already coupled by this job (%2)")
|
||||
.arg(rsName).arg(m_jobId),
|
||||
.arg(rsName)
|
||||
.arg(m_jobId),
|
||||
QMessageBox::Ok);
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
qWarning() << "Error while adding coupling op. Stop:" << m_stopId
|
||||
<< "Rs:" << rsId << "Occupied by this job:" << jobId;
|
||||
qWarning() << "Error while adding coupling op. Stop:" << m_stopId << "Rs:" << rsId
|
||||
<< "Occupied by this job:" << jobId;
|
||||
|
||||
int but = QMessageBox::warning(qApp->activeWindow(),
|
||||
tr("Error"),
|
||||
tr("Error while adding coupling operation.\n"
|
||||
"Rollingstock %1 is already coupled to another job (%2)\n"
|
||||
"Do you still want to couple it?")
|
||||
.arg(rsName).arg(jobId),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
int but =
|
||||
QMessageBox::warning(qApp->activeWindow(), tr("Error"),
|
||||
tr("Error while adding coupling operation.\n"
|
||||
"Rollingstock %1 is already coupled to another job (%2)\n"
|
||||
"Do you still want to couple it?")
|
||||
.arg(rsName)
|
||||
.arg(jobId),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
|
||||
if(but == QMessageBox::No)
|
||||
return false; //Abort
|
||||
if (but == QMessageBox::No)
|
||||
return false; // Abort
|
||||
}
|
||||
}
|
||||
|
||||
if(checkTractionType)
|
||||
if (checkTractionType && !stopsModel->isRailwayElectrifiedAfterStop(m_stopId))
|
||||
{
|
||||
//Query RS type
|
||||
// Query RS type
|
||||
query q_getRSType(mDb, "SELECT rs_models.type,rs_models.sub_type"
|
||||
" FROM rs_list"
|
||||
" JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" WHERE rs_list.id=?");
|
||||
q_getRSType.bind(1, rsId);
|
||||
if(q_getRSType.step() != SQLITE_ROW)
|
||||
if (q_getRSType.step() != SQLITE_ROW)
|
||||
{
|
||||
qWarning() << "RS seems to not exist, ID:" << rsId;
|
||||
}
|
||||
|
||||
auto rs = q_getRSType.getRows();
|
||||
RsType type = RsType(rs.get<int>(0));
|
||||
auto rs = q_getRSType.getRows();
|
||||
RsType type = RsType(rs.get<int>(0));
|
||||
RsEngineSubType subType = RsEngineSubType(rs.get<int>(1));
|
||||
|
||||
if(type == RsType::Engine && subType == RsEngineSubType::Electric)
|
||||
if (type == RsType::Engine && subType == RsEngineSubType::Electric)
|
||||
{
|
||||
bool electrified = stopsModel->isRailwayElectrifiedAfterStop(m_stopId);
|
||||
if(!electrified)
|
||||
{
|
||||
int but = QMessageBox::warning(qApp->activeWindow(),
|
||||
tr("Warning"),
|
||||
tr("Rollingstock %1 is an Electric engine but the line is not electrified\n"
|
||||
"This engine will not be albe to move a train.\n"
|
||||
"Do you still want to couple it?")
|
||||
.arg(rsName),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if(but == QMessageBox::No)
|
||||
return false; //Abort
|
||||
}
|
||||
int but = QMessageBox::warning(
|
||||
qApp->activeWindow(), tr("Warning"),
|
||||
tr("Rollingstock %1 is an Electric engine but the line is not electrified\n"
|
||||
"This engine will not be albe to move a train.\n"
|
||||
"Do you still want to couple it?")
|
||||
.arg(rsName),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
if (but == QMessageBox::No)
|
||||
return false; // Cancel coupling operation
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -162,46 +181,45 @@ bool RSCouplingInterface::coupleRS(db_id rsId, const QString& rsName, bool on, b
|
|||
ret = q_addCoupling.execute();
|
||||
q_addCoupling.reset();
|
||||
|
||||
if(ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Error while adding coupling op. Stop:" << m_stopId
|
||||
<< "Rs:" << rsId << "Op: Coupled " << "Ret:" << ret
|
||||
<< mDb.error_msg();
|
||||
qWarning() << "Error while adding coupling op. Stop:" << m_stopId << "Rs:" << rsId
|
||||
<< "Op: Coupled "
|
||||
<< "Ret:" << ret << mDb.error_msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
coupled.append(rsId);
|
||||
|
||||
//Check if there is a next coupling operation in the same job
|
||||
// Check if there is a next coupling operation in the same job
|
||||
query q(mDb, "SELECT s2.id, s2.arrival, s2.station_id, stations.name"
|
||||
" FROM coupling"
|
||||
" JOIN stops s2 ON s2.id=coupling.stop_id"
|
||||
" JOIN stops s1 ON s1.id=?"
|
||||
" JOIN stations ON stations.id=s2.station_id"
|
||||
" WHERE coupling.rs_id=? AND coupling.operation=? AND s1.job_id=s2.job_id AND s1.arrival < s2.arrival");
|
||||
" WHERE coupling.rs_id=? AND coupling.operation=? AND s1.job_id=s2.job_id AND "
|
||||
"s1.arrival < s2.arrival");
|
||||
q.bind(1, m_stopId);
|
||||
q.bind(2, rsId);
|
||||
q.bind(3, int(RsOp::Coupled));
|
||||
|
||||
if(q.step() == SQLITE_ROW)
|
||||
if (q.step() == SQLITE_ROW)
|
||||
{
|
||||
auto r = q.getRows();
|
||||
db_id stopId = r.get<db_id>(0);
|
||||
QTime arr = r.get<QTime>(1);
|
||||
db_id stId = r.get<db_id>(2);
|
||||
auto r = q.getRows();
|
||||
db_id stopId = r.get<db_id>(0);
|
||||
QTime arr = r.get<QTime>(1);
|
||||
db_id stId = r.get<db_id>(2);
|
||||
QString stName = r.get<QString>(3);
|
||||
|
||||
qDebug() << "Found coupling, RS:" << rsId << "Stop:" << stopId << "St:" << stId << arr;
|
||||
|
||||
int but = QMessageBox::question(qApp->activeWindow(),
|
||||
tr("Delete coupling?"),
|
||||
tr("You couple %1 also in a next stop in %2 at %3.\n"
|
||||
"Do you want to remove the other coupling operation?")
|
||||
.arg(rsName)
|
||||
.arg(stName)
|
||||
.arg(arr.toString("HH:mm")),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if(but == QMessageBox::Yes)
|
||||
int but =
|
||||
QMessageBox::question(qApp->activeWindow(), tr("Delete coupling?"),
|
||||
tr("You couple %1 also in a next stop in %2 at %3.\n"
|
||||
"Do you want to remove the other coupling operation?")
|
||||
.arg(rsName, stName, arr.toString("HH:mm")),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if (but == QMessageBox::Yes)
|
||||
{
|
||||
qDebug() << "Deleting coupling";
|
||||
|
||||
|
@ -210,11 +228,11 @@ bool RSCouplingInterface::coupleRS(db_id rsId, const QString& rsName, bool on, b
|
|||
ret = q_deleteCoupling.execute();
|
||||
q_deleteCoupling.reset();
|
||||
|
||||
if(ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Error while deleting next coupling op. Stop:" << stopId
|
||||
<< "Rs:" << rsId << "Op: Uncoupled " << "Ret:" << ret
|
||||
<< mDb.error_msg();
|
||||
<< "Rs:" << rsId << "Op: Uncoupled "
|
||||
<< "Ret:" << ret << mDb.error_msg();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -226,7 +244,7 @@ bool RSCouplingInterface::coupleRS(db_id rsId, const QString& rsName, bool on, b
|
|||
else
|
||||
{
|
||||
int row = coupled.indexOf(rsId);
|
||||
if(row == -1)
|
||||
if (row == -1)
|
||||
return false;
|
||||
|
||||
q_deleteCoupling.bind(1, m_stopId);
|
||||
|
@ -234,46 +252,46 @@ bool RSCouplingInterface::coupleRS(db_id rsId, const QString& rsName, bool on, b
|
|||
int ret = q_deleteCoupling.execute();
|
||||
q_deleteCoupling.reset();
|
||||
|
||||
if(ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Error while deleting coupling op. Stop:" << m_stopId
|
||||
<< "Rs:" << rsId << "Op: Coupled " << "Ret:" << ret
|
||||
<< mDb.error_msg();
|
||||
qWarning() << "Error while deleting coupling op. Stop:" << m_stopId << "Rs:" << rsId
|
||||
<< "Op: Coupled "
|
||||
<< "Ret:" << ret << mDb.error_msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
coupled.removeAt(row);
|
||||
|
||||
//Check if there is a next uncoupling operation
|
||||
// Check if there is a next uncoupling operation
|
||||
query q(mDb, "SELECT s2.id, MIN(s2.arrival), s2.station_id, stations.name"
|
||||
" FROM coupling"
|
||||
" JOIN stops s2 ON s2.id=coupling.stop_id"
|
||||
" JOIN stops s1 ON s1.id=?"
|
||||
" JOIN stations ON stations.id=s2.station_id"
|
||||
" WHERE coupling.rs_id=? AND coupling.operation=? AND s2.arrival > s1.arrival AND s2.job_id=s1.job_id");
|
||||
" WHERE coupling.rs_id=? AND coupling.operation=? AND s2.arrival > s1.arrival "
|
||||
"AND s2.job_id=s1.job_id");
|
||||
q.bind(1, m_stopId);
|
||||
q.bind(2, rsId);
|
||||
q.bind(3, int(RsOp::Uncoupled));
|
||||
|
||||
if(q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
if (q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
{
|
||||
auto r = q.getRows();
|
||||
db_id stopId = r.get<db_id>(0);
|
||||
QTime arr = r.get<QTime>(1);
|
||||
db_id stId = r.get<db_id>(2);
|
||||
auto r = q.getRows();
|
||||
db_id stopId = r.get<db_id>(0);
|
||||
QTime arr = r.get<QTime>(1);
|
||||
db_id stId = r.get<db_id>(2);
|
||||
QString stName = r.get<QString>(3);
|
||||
|
||||
qDebug() << "Found uncoupling, RS:" << rsId << "Stop:" << stopId << "St:" << stId << arr;
|
||||
qDebug() << "Found uncoupling, RS:" << rsId << "Stop:" << stopId << "St:" << stId
|
||||
<< arr;
|
||||
|
||||
int but = QMessageBox::question(qApp->activeWindow(),
|
||||
tr("Delete uncoupling?"),
|
||||
tr("You don't couple %1 anymore.\n"
|
||||
"Do you want to remove also the uncoupling operation in %2 at %3?")
|
||||
.arg(rsName)
|
||||
.arg(stName)
|
||||
.arg(arr.toString("HH:mm")),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if(but == QMessageBox::Yes)
|
||||
int but = QMessageBox::question(
|
||||
qApp->activeWindow(), tr("Delete uncoupling?"),
|
||||
tr("You don't couple %1 anymore.\n"
|
||||
"Do you want to remove also the uncoupling operation in %2 at %3?")
|
||||
.arg(rsName, stName, arr.toString("HH:mm")),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if (but == QMessageBox::Yes)
|
||||
{
|
||||
qDebug() << "Deleting coupling";
|
||||
|
||||
|
@ -282,11 +300,11 @@ bool RSCouplingInterface::coupleRS(db_id rsId, const QString& rsName, bool on, b
|
|||
ret = q_deleteCoupling.execute();
|
||||
q_deleteCoupling.reset();
|
||||
|
||||
if(ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Error while deleting next uncoupling op. Stop:" << stopId
|
||||
<< "Rs:" << rsId << "Op: Uncoupled " << "Ret:" << ret
|
||||
<< mDb.error_msg();
|
||||
<< "Rs:" << rsId << "Op: Uncoupled "
|
||||
<< "Ret:" << ret << mDb.error_msg();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -299,14 +317,14 @@ bool RSCouplingInterface::coupleRS(db_id rsId, const QString& rsName, bool on, b
|
|||
return true;
|
||||
}
|
||||
|
||||
bool RSCouplingInterface::uncoupleRS(db_id rsId, const QString& rsName, bool on)
|
||||
bool RSCouplingInterface::uncoupleRS(db_id rsId, const QString &rsName, bool on)
|
||||
{
|
||||
stopsModel->startStopsEditing();
|
||||
stopsModel->markRsToUpdate(rsId);
|
||||
|
||||
if(on)
|
||||
if (on)
|
||||
{
|
||||
if(uncoupled.contains(rsId))
|
||||
if (uncoupled.contains(rsId))
|
||||
{
|
||||
qWarning() << "Error already checked:" << rsId;
|
||||
return true;
|
||||
|
@ -318,46 +336,46 @@ bool RSCouplingInterface::uncoupleRS(db_id rsId, const QString& rsName, bool on)
|
|||
int ret = q_addCoupling.execute();
|
||||
q_addCoupling.reset();
|
||||
|
||||
if(ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Error while adding coupling op. Stop:" << m_stopId
|
||||
<< "Rs:" << rsId << "Op: Uncoupled " << "Ret:" << ret
|
||||
<< mDb.error_msg();
|
||||
qWarning() << "Error while adding coupling op. Stop:" << m_stopId << "Rs:" << rsId
|
||||
<< "Op: Uncoupled "
|
||||
<< "Ret:" << ret << mDb.error_msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
uncoupled.append(rsId);
|
||||
|
||||
//Check if there is a next uncoupling operation
|
||||
// Check if there is a next uncoupling operation
|
||||
query q(mDb, "SELECT s2.id, MIN(s2.arrival), s2.station_id, stations.name"
|
||||
" FROM coupling"
|
||||
" JOIN stops s2 ON s2.id=coupling.stop_id"
|
||||
" JOIN stops s1 ON s1.id=?"
|
||||
" JOIN stations ON stations.id=s2.station_id"
|
||||
" WHERE coupling.rs_id=? AND coupling.operation=? AND s2.arrival > s1.arrival AND s2.job_id=s1.job_id");
|
||||
" WHERE coupling.rs_id=? AND coupling.operation=? AND s2.arrival > s1.arrival "
|
||||
"AND s2.job_id=s1.job_id");
|
||||
q.bind(1, m_stopId);
|
||||
q.bind(2, rsId);
|
||||
q.bind(3, int(RsOp::Uncoupled));
|
||||
|
||||
if(q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
if (q.step() == SQLITE_ROW && q.getRows().column_type(0) != SQLITE_NULL)
|
||||
{
|
||||
auto r = q.getRows();
|
||||
db_id stopId = r.get<db_id>(0);
|
||||
QTime arr = r.get<QTime>(1);
|
||||
db_id stId = r.get<db_id>(2);
|
||||
auto r = q.getRows();
|
||||
db_id stopId = r.get<db_id>(0);
|
||||
QTime arr = r.get<QTime>(1);
|
||||
db_id stId = r.get<db_id>(2);
|
||||
QString stName = r.get<QString>(3);
|
||||
|
||||
qDebug() << "Found uncoupling, RS:" << rsId << "Stop:" << stopId << "St:" << stId << arr;
|
||||
qDebug() << "Found uncoupling, RS:" << rsId << "Stop:" << stopId << "St:" << stId
|
||||
<< arr;
|
||||
|
||||
int but = QMessageBox::question(qApp->activeWindow(),
|
||||
tr("Delete uncoupling?"),
|
||||
tr("You uncouple %1 also in %2 at %3.\n"
|
||||
"Do you want to remove the other uncoupling operation?")
|
||||
.arg(rsName)
|
||||
.arg(stName)
|
||||
.arg(arr.toString("HH:mm")),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if(but == QMessageBox::Yes)
|
||||
int but =
|
||||
QMessageBox::question(qApp->activeWindow(), tr("Delete uncoupling?"),
|
||||
tr("You uncouple %1 also in %2 at %3.\n"
|
||||
"Do you want to remove the other uncoupling operation?")
|
||||
.arg(rsName, stName, arr.toString("HH:mm")),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes);
|
||||
if (but == QMessageBox::Yes)
|
||||
{
|
||||
qDebug() << "Deleting coupling";
|
||||
|
||||
|
@ -366,11 +384,11 @@ bool RSCouplingInterface::uncoupleRS(db_id rsId, const QString& rsName, bool on)
|
|||
ret = q_deleteCoupling.execute();
|
||||
q_deleteCoupling.reset();
|
||||
|
||||
if(ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Error while deleting next uncoupling op. Stop:" << stopId
|
||||
<< "Rs:" << rsId << "Op: Uncoupled " << "Ret:" << ret
|
||||
<< mDb.error_msg();
|
||||
<< "Rs:" << rsId << "Op: Uncoupled "
|
||||
<< "Ret:" << ret << mDb.error_msg();
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -382,7 +400,7 @@ bool RSCouplingInterface::uncoupleRS(db_id rsId, const QString& rsName, bool on)
|
|||
else
|
||||
{
|
||||
int row = uncoupled.indexOf(rsId);
|
||||
if(row == -1)
|
||||
if (row == -1)
|
||||
return false;
|
||||
|
||||
q_deleteCoupling.bind(1, m_stopId);
|
||||
|
@ -390,11 +408,11 @@ bool RSCouplingInterface::uncoupleRS(db_id rsId, const QString& rsName, bool on)
|
|||
int ret = q_deleteCoupling.execute();
|
||||
q_deleteCoupling.reset();
|
||||
|
||||
if(ret != SQLITE_OK)
|
||||
if (ret != SQLITE_OK)
|
||||
{
|
||||
qWarning() << "Error while deleting coupling op. Stop:" << m_stopId
|
||||
<< "Rs:" << rsId << "Op: Uncoupled " << "Ret:" << ret
|
||||
<< mDb.error_msg();
|
||||
qWarning() << "Error while deleting coupling op. Stop:" << m_stopId << "Rs:" << rsId
|
||||
<< "Op: Uncoupled "
|
||||
<< "Ret:" << ret << mDb.error_msg();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -404,6 +422,61 @@ bool RSCouplingInterface::uncoupleRS(db_id rsId, const QString& rsName, bool on)
|
|||
return true;
|
||||
}
|
||||
|
||||
int RSCouplingInterface::importRSFromJob(db_id otherStopId)
|
||||
{
|
||||
query q_getUncoupled(mDb, "SELECT coupling.rs_id, rs_list.number,"
|
||||
" rs_models.name, rs_models.suffix, rs_models.type"
|
||||
" FROM coupling"
|
||||
" JOIN rs_list ON rs_list.id=coupling.rs_id"
|
||||
" JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" WHERE coupling.stop_id=? AND coupling.operation=0");
|
||||
q_getUncoupled.bind(1, otherStopId);
|
||||
|
||||
int count = 0;
|
||||
bool lineElectrified = stopsModel->isRailwayElectrifiedAfterStop(m_stopId);
|
||||
|
||||
QElapsedTimer timer;
|
||||
timer.start();
|
||||
|
||||
for (auto rs : q_getUncoupled)
|
||||
{
|
||||
db_id rsId = rs.get<db_id>(0);
|
||||
|
||||
int number = rs.get<int>(1);
|
||||
int modelNameLen = sqlite3_column_bytes(q_getUncoupled.stmt(), 2);
|
||||
const char *modelName =
|
||||
reinterpret_cast<char const *>(sqlite3_column_text(q_getUncoupled.stmt(), 2));
|
||||
|
||||
int modelSuffixLen = sqlite3_column_bytes(q_getUncoupled.stmt(), 3);
|
||||
const char *modelSuffix =
|
||||
reinterpret_cast<char const *>(sqlite3_column_text(q_getUncoupled.stmt(), 3));
|
||||
RsType rsType = RsType(rs.get<int>(4));
|
||||
|
||||
QString rsName = rs_utils::formatNameRef(modelName, modelNameLen, number, modelSuffix,
|
||||
modelSuffixLen, rsType);
|
||||
|
||||
// TODO: optimize work
|
||||
if (coupleRS(rsId, rsName, true, !lineElectrified))
|
||||
count++;
|
||||
|
||||
if (timer.elapsed() > 10000)
|
||||
{
|
||||
// After 10 seconds, give opportunity to stop
|
||||
int ret = QMessageBox::question(
|
||||
qApp->activeWindow(), tr("Continue Importation?"),
|
||||
tr("Rollingstock importation is taking more time than expected.\n"
|
||||
"Do you want to continue?"));
|
||||
|
||||
if (ret == QMessageBox::No)
|
||||
return count; // Abort here
|
||||
|
||||
timer.restart(); // Count again
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
bool RSCouplingInterface::hasEngineAfterStop(bool *isElectricOnNonElectrifiedLine)
|
||||
{
|
||||
query q_hasEngine(mDb, "SELECT coupling.rs_id,MAX(rs_models.sub_type),MAX(stops.arrival)"
|
||||
|
@ -417,13 +490,14 @@ bool RSCouplingInterface::hasEngineAfterStop(bool *isElectricOnNonElectrifiedLin
|
|||
" LIMIT 1");
|
||||
q_hasEngine.bind(1, m_jobId);
|
||||
q_hasEngine.bind(2, arrival);
|
||||
if(q_hasEngine.step() != SQLITE_ROW)
|
||||
return false; //No engine
|
||||
if (q_hasEngine.step() != SQLITE_ROW)
|
||||
return false; // No engine
|
||||
|
||||
if(isElectricOnNonElectrifiedLine)
|
||||
if (isElectricOnNonElectrifiedLine)
|
||||
{
|
||||
RsEngineSubType subType = RsEngineSubType(q_hasEngine.getRows().get<int>(1));
|
||||
*isElectricOnNonElectrifiedLine = (subType == RsEngineSubType::Electric) && (!isRailwayElectrified());
|
||||
*isElectricOnNonElectrifiedLine =
|
||||
(subType == RsEngineSubType::Electric) && (!isRailwayElectrified());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RSCOUPLINGINTERFACE_H
|
||||
#define RSCOUPLINGINTERFACE_H
|
||||
|
||||
|
@ -25,6 +44,8 @@ public:
|
|||
bool coupleRS(db_id rsId, const QString &rsName, bool on, bool checkTractionType);
|
||||
bool uncoupleRS(db_id rsId, const QString &rsName, bool on);
|
||||
|
||||
int importRSFromJob(db_id otherStopId);
|
||||
|
||||
bool hasEngineAfterStop(bool *isElectricOnNonElectrifiedLine = nullptr);
|
||||
|
||||
bool isRailwayElectrified() const;
|
||||
|
|
|
@ -1,59 +1,52 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "rslistondemandmodel.h"
|
||||
#include "rslistondemandmodelresultevent.h"
|
||||
|
||||
#include "utils/delegates/sql/pageditemmodelhelper_impl.h"
|
||||
|
||||
#include <QFont>
|
||||
|
||||
RSListOnDemandModel::RSListOnDemandModel(sqlite3pp::database &db, QObject *parent) :
|
||||
IPagedItemModel(50, db, parent),
|
||||
cacheFirstRow(0),
|
||||
firstPendingRow(-BatchSize)
|
||||
BaseClass(100, db, parent)
|
||||
{
|
||||
sortColumn = Name;
|
||||
}
|
||||
|
||||
bool RSListOnDemandModel::event(QEvent *e)
|
||||
{
|
||||
if(e->type() == RSListOnDemandModelResultEvent::_Type)
|
||||
{
|
||||
RSListOnDemandModelResultEvent *ev = static_cast<RSListOnDemandModelResultEvent *>(e);
|
||||
ev->setAccepted(true);
|
||||
|
||||
handleResult(ev->items, ev->firstRow);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return QAbstractTableModel::event(e);
|
||||
}
|
||||
|
||||
int RSListOnDemandModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : curItemCount;
|
||||
}
|
||||
|
||||
int RSListOnDemandModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
return parent.isValid() ? 0 : NCols;
|
||||
}
|
||||
|
||||
QVariant RSListOnDemandModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
const int row = idx.row();
|
||||
if (!idx.isValid() || row >= curItemCount || idx.column() >= NCols)
|
||||
return QVariant();
|
||||
|
||||
//qDebug() << "Data:" << idx.row();
|
||||
// qDebug() << "Data:" << idx.row();
|
||||
|
||||
if(row < cacheFirstRow || row >= cacheFirstRow + cache.size())
|
||||
if (row < cacheFirstRow || row >= cacheFirstRow + cache.size())
|
||||
{
|
||||
//Fetch above or below current cache
|
||||
// Fetch above or below current cache
|
||||
const_cast<RSListOnDemandModel *>(this)->fetchRow(row);
|
||||
|
||||
//Temporarily return null
|
||||
// Temporarily return null
|
||||
return role == Qt::DisplayRole ? QVariant("...") : QVariant();
|
||||
}
|
||||
|
||||
const RSItem& item = cache.at(row - cacheFirstRow);
|
||||
const RSItem &item = cache.at(row - cacheFirstRow);
|
||||
|
||||
switch (role)
|
||||
{
|
||||
|
@ -70,9 +63,9 @@ QVariant RSListOnDemandModel::data(const QModelIndex &idx, int role) const
|
|||
}
|
||||
case Qt::FontRole:
|
||||
{
|
||||
if(item.type == RsType::Engine)
|
||||
if (item.type == RsType::Engine)
|
||||
{
|
||||
//Engines in bold
|
||||
// Engines in bold
|
||||
QFont f;
|
||||
f.setBold(true);
|
||||
return f;
|
||||
|
@ -83,111 +76,3 @@ QVariant RSListOnDemandModel::data(const QModelIndex &idx, int role) const
|
|||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
void RSListOnDemandModel::clearCache()
|
||||
{
|
||||
cache.clear();
|
||||
cache.squeeze();
|
||||
cacheFirstRow = 0;
|
||||
}
|
||||
|
||||
void RSListOnDemandModel::fetchRow(int row)
|
||||
{
|
||||
if(firstPendingRow != -BatchSize)
|
||||
return; //Currently fetching another batch, wait for it to finish first
|
||||
|
||||
if(row >= firstPendingRow && row < firstPendingRow + BatchSize)
|
||||
return; //Already fetching this batch
|
||||
|
||||
if(row >= cacheFirstRow && row < cacheFirstRow + cache.size())
|
||||
return; //Already cached
|
||||
|
||||
//TODO: abort fetching here
|
||||
|
||||
const int remainder = row % BatchSize;
|
||||
firstPendingRow = row - remainder;
|
||||
|
||||
QVariant val;
|
||||
int valRow = 0;
|
||||
// RSItem *item = nullptr;
|
||||
|
||||
// if(cache.size())
|
||||
// {
|
||||
// if(firstPendingRow >= cacheFirstRow + cache.size())
|
||||
// {
|
||||
// valRow = cacheFirstRow + cache.size();
|
||||
// item = &cache.last();
|
||||
// }
|
||||
// else if(firstPendingRow > (cacheFirstRow - firstPendingRow))
|
||||
// {
|
||||
// valRow = cacheFirstRow;
|
||||
// item = &cache.first();
|
||||
// }
|
||||
// }
|
||||
|
||||
/*switch (sortCol) TODO: use val in WHERE clause
|
||||
{
|
||||
case Name:
|
||||
{
|
||||
if(item)
|
||||
{
|
||||
val = item->name;
|
||||
}
|
||||
break;
|
||||
}
|
||||
//No data hint for TypeCol column
|
||||
}*/
|
||||
|
||||
//TODO: use a custom QRunnable
|
||||
// QMetaObject::invokeMethod(this, "internalFetch", Qt::QueuedConnection,
|
||||
// Q_ARG(int, firstPendingRow), Q_ARG(int, sortCol),
|
||||
// Q_ARG(int, valRow), Q_ARG(QVariant, val));
|
||||
internalFetch(firstPendingRow, sortColumn, val.isNull() ? 0 : valRow, val);
|
||||
}
|
||||
|
||||
void RSListOnDemandModel::handleResult(const QVector<RSItem>& items, int firstRow)
|
||||
{
|
||||
if(firstRow == cacheFirstRow + cache.size())
|
||||
{
|
||||
cache.append(items);
|
||||
if(cache.size() > ItemsPerPage)
|
||||
{
|
||||
const int extra = cache.size() - ItemsPerPage; //Round up to BatchSize
|
||||
const int remainder = extra % BatchSize;
|
||||
const int n = remainder ? extra + BatchSize - remainder : extra;
|
||||
cache.remove(0, n);
|
||||
cacheFirstRow += n;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if(firstRow + items.size() == cacheFirstRow)
|
||||
{
|
||||
QVector<RSItem> tmp = items;
|
||||
tmp.append(cache);
|
||||
cache = tmp;
|
||||
if(cache.size() > ItemsPerPage)
|
||||
{
|
||||
const int n = cache.size() - ItemsPerPage;
|
||||
cache.remove(ItemsPerPage, n);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
cache = items;
|
||||
}
|
||||
cacheFirstRow = firstRow;
|
||||
}
|
||||
|
||||
firstPendingRow = -BatchSize;
|
||||
|
||||
int lastRow = firstRow + items.count(); //Last row + 1 extra to re-trigger possible next batch
|
||||
if(lastRow >= curItemCount)
|
||||
lastRow = curItemCount -1; //Ok, there is no extra row so notify just our batch
|
||||
|
||||
if(firstRow > 0)
|
||||
firstRow--; //Try notify also the row before because there might be another batch waiting so re-trigger it
|
||||
QModelIndex firstIdx = index(firstRow, 0);
|
||||
QModelIndex lastIdx = index(lastRow, NCols - 1);
|
||||
emit dataChanged(firstIdx, lastIdx);
|
||||
}
|
||||
|
|
|
@ -1,59 +1,66 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RSLISTONDEMANDMODEL_H
|
||||
#define RSLISTONDEMANDMODEL_H
|
||||
|
||||
#include "utils/delegates/sql/pageditemmodel.h"
|
||||
#include "utils/delegates/sql/pageditemmodelhelper.h"
|
||||
|
||||
#include "utils/types.h"
|
||||
|
||||
#include <QVector>
|
||||
|
||||
struct RSListOnDemandModelItem
|
||||
{
|
||||
db_id rsId;
|
||||
QString name;
|
||||
RsType type;
|
||||
};
|
||||
|
||||
class RSListOnDemandModel : public IPagedItemModel
|
||||
class RSListOnDemandModel : public IPagedItemModelImpl<RSListOnDemandModel, RSListOnDemandModelItem>
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
BatchSize = 50
|
||||
};
|
||||
|
||||
enum { BatchSize = 25 };
|
||||
|
||||
typedef enum {
|
||||
enum Columns
|
||||
{
|
||||
Name = 0,
|
||||
NCols
|
||||
} Columns;
|
||||
};
|
||||
|
||||
typedef struct RSItem_
|
||||
{
|
||||
db_id rsId;
|
||||
QString name;
|
||||
RsType type;
|
||||
} RSItem;
|
||||
typedef RSListOnDemandModelItem RSItem;
|
||||
typedef IPagedItemModelImpl<RSListOnDemandModel, RSListOnDemandModelItem> BaseClass;
|
||||
|
||||
RSListOnDemandModel(sqlite3pp::database &db, QObject *parent = nullptr);
|
||||
|
||||
bool event(QEvent *e) override;
|
||||
|
||||
// QAbstractTableModel
|
||||
|
||||
// Basic functionality:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
// IPagedItemModel
|
||||
|
||||
// Cached rows management
|
||||
virtual void clearCache() override;
|
||||
|
||||
private:
|
||||
friend BaseClass;
|
||||
virtual void internalFetch(int first, int sortColumn, int valRow, const QVariant &val) = 0;
|
||||
void fetchRow(int row);
|
||||
void handleResult(const QVector<RSItem> &items, int firstRow);
|
||||
|
||||
private:
|
||||
QVector<RSItem> cache;
|
||||
int cacheFirstRow;
|
||||
int firstPendingRow;
|
||||
};
|
||||
|
||||
#endif // RSLISTONDEMANDMODEL_H
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
#include "rslistondemandmodelresultevent.h"
|
||||
|
||||
RSListOnDemandModelResultEvent::RSListOnDemandModelResultEvent() :
|
||||
QEvent(_Type)
|
||||
{
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#ifndef RSLISTONDEMANDMODELRESULTEVENT_H
|
||||
#define RSLISTONDEMANDMODELRESULTEVENT_H
|
||||
|
||||
#include "rslistondemandmodel.h"
|
||||
#include <QEvent>
|
||||
#include "utils/worker_event_types.h"
|
||||
|
||||
class RSListOnDemandModelResultEvent : public QEvent
|
||||
{
|
||||
public:
|
||||
static constexpr Type _Type = Type(CustomEvents::RsOnDemandListModelResult);
|
||||
|
||||
RSListOnDemandModelResultEvent();
|
||||
|
||||
QVector<RSListOnDemandModel::RSItem> items;
|
||||
int firstRow;
|
||||
};
|
||||
|
||||
#endif // RSLISTONDEMANDMODELRESULTEVENT_H
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "rsproxymodel.h"
|
||||
|
||||
#include "rscouplinginterface.h"
|
||||
|
@ -6,17 +25,12 @@
|
|||
|
||||
#include <QBrush>
|
||||
|
||||
|
||||
RSProxyModel::RSProxyModel(RSCouplingInterface *mgr,
|
||||
RsOp o,
|
||||
RsType type,
|
||||
QObject *parent) :
|
||||
QAbstractListModel (parent),
|
||||
RSProxyModel::RSProxyModel(RSCouplingInterface *mgr, RsOp o, RsType type, QObject *parent) :
|
||||
QAbstractListModel(parent),
|
||||
couplingMgr(mgr),
|
||||
op(o),
|
||||
targetType(type)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
int RSProxyModel::rowCount(const QModelIndex &parent) const
|
||||
|
@ -26,10 +40,10 @@ int RSProxyModel::rowCount(const QModelIndex &parent) const
|
|||
|
||||
QVariant RSProxyModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if(!idx.isValid() || idx.column() > 0 || idx.row() >= m_data.size())
|
||||
if (!idx.isValid() || idx.column() > 0 || idx.row() >= m_data.size())
|
||||
return QVariant();
|
||||
|
||||
const RsItem& item = m_data.at(idx.row());
|
||||
const RsItem &item = m_data.at(idx.row());
|
||||
|
||||
switch (role)
|
||||
{
|
||||
|
@ -39,92 +53,104 @@ QVariant RSProxyModel::data(const QModelIndex &idx, int role) const
|
|||
{
|
||||
return couplingMgr->contains(item.rsId, op) ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
case Qt::BackgroundRole: //NOTE SYNC: RSCoupleDialog
|
||||
case Qt::BackgroundRole: // NOTE SYNC: RSCoupleDialog
|
||||
{
|
||||
if(item.flag == ErrNotCoupledBefore || item.flag == ErrAlreadyCoupled)
|
||||
if (item.flag == ErrNotCoupledBefore || item.flag == ErrAlreadyCoupled)
|
||||
{
|
||||
//Error: already coupled or already uncoupled or not coupled at all before this stop
|
||||
return QBrush(qRgb(255, 86, 255)); //Solid light magenta #FF56FF
|
||||
// Error: already coupled or already uncoupled or not coupled at all before this stop
|
||||
return QBrush(qRgb(255, 86, 255)); // Solid light magenta #FF56FF
|
||||
}
|
||||
if(item.flag == WrongStation)
|
||||
if (item.flag == WrongStation)
|
||||
{
|
||||
//Error: RS is not in this station
|
||||
return QBrush(qRgb(255, 61, 67)); //Solid light red #FF3d43
|
||||
// Error: RS is not in this station
|
||||
return QBrush(qRgb(255, 61, 67)); // Solid light red #FF3d43
|
||||
}
|
||||
if(targetType == RsType::Engine && item.engineType == RsEngineSubType::Electric && !couplingMgr->isRailwayElectrified())
|
||||
if (targetType == RsType::Engine && item.engineType == RsEngineSubType::Electric
|
||||
&& !couplingMgr->isRailwayElectrified())
|
||||
{
|
||||
//Warn Electric traction not possible
|
||||
return QBrush(qRgb(0, 0, 255), Qt::FDiagPattern); //Blue
|
||||
// Warn Electric traction not possible
|
||||
return QBrush(qRgb(0, 0, 255), Qt::FDiagPattern); // Blue
|
||||
}
|
||||
if(item.flag == FirstUseOfRS)
|
||||
if (item.flag == FirstUseOfRS)
|
||||
{
|
||||
return QBrush(qRgb(0, 255, 255)); //Cyan
|
||||
return QBrush(qRgb(0, 255, 255)); // Cyan
|
||||
}
|
||||
if(item.flag == UnusedRS)
|
||||
if (item.flag == UnusedRS)
|
||||
{
|
||||
return QBrush(qRgb(0, 255, 0)); //Green
|
||||
return QBrush(qRgb(0, 255, 0)); // Green
|
||||
}
|
||||
break;
|
||||
}
|
||||
case Qt::ToolTipRole:
|
||||
{
|
||||
if(item.flag == ErrNotCoupledBefore)
|
||||
if (item.flag == ErrNotCoupledBefore)
|
||||
{
|
||||
//Error
|
||||
return tr("Rollingstock <b>%1</b> cannot be uncoupled here because it wasn't coupled to this job before this stop "
|
||||
// Error
|
||||
return tr("Rollingstock <b>%1</b> cannot be uncoupled here because it wasn't coupled "
|
||||
"to this job before this stop "
|
||||
"or because it was already uncoupled before this stop.<br>"
|
||||
"Please remove the tick").arg(item.rsName);
|
||||
"Please remove the tick")
|
||||
.arg(item.rsName);
|
||||
}
|
||||
if(item.flag == ErrAlreadyCoupled)
|
||||
if (item.flag == ErrAlreadyCoupled)
|
||||
{
|
||||
//Error
|
||||
if(item.jobId == couplingMgr->getJobId())
|
||||
// Error
|
||||
if (item.jobId == couplingMgr->getJobId())
|
||||
{
|
||||
return tr("Rollingstock <b>%1</b> cannot be coupled here because it was already coupled to this job before this stop<br>"
|
||||
"Please remove the tick").arg(item.rsName);
|
||||
}else{
|
||||
return tr("Rollingstock <b>%1</b> cannot be coupled here because it was already coupled before this stop<br>"
|
||||
return tr("Rollingstock <b>%1</b> cannot be coupled here because it was already "
|
||||
"coupled to this job before this stop<br>"
|
||||
"Please remove the tick")
|
||||
.arg(item.rsName);
|
||||
}
|
||||
else
|
||||
{
|
||||
return tr("Rollingstock <b>%1</b> cannot be coupled here because it was already "
|
||||
"coupled before this stop<br>"
|
||||
"to job <b>%2<b/><br>"
|
||||
"Please remove the tick")
|
||||
.arg(item.rsName)
|
||||
.arg(JobCategoryName::jobName(item.jobId, item.jobCat));
|
||||
.arg(item.rsName, JobCategoryName::jobName(item.jobId, item.jobCat));
|
||||
}
|
||||
}
|
||||
if(item.flag == WrongStation)
|
||||
if (item.flag == WrongStation)
|
||||
{
|
||||
//Error
|
||||
return tr("Rollingstock <b>%1</b> cannot be coupled here because it is not in this station.<br>"
|
||||
"Please remove the tick").arg(item.rsName);
|
||||
// Error
|
||||
return tr("Rollingstock <b>%1</b> cannot be coupled here because it is not in this "
|
||||
"station.<br>"
|
||||
"Please remove the tick")
|
||||
.arg(item.rsName);
|
||||
}
|
||||
if(targetType == RsType::Engine && item.engineType == RsEngineSubType::Electric && !couplingMgr->isRailwayElectrified())
|
||||
if (targetType == RsType::Engine && item.engineType == RsEngineSubType::Electric
|
||||
&& !couplingMgr->isRailwayElectrified())
|
||||
{
|
||||
//Warn Electric traction not possible
|
||||
return tr("Engine <b>%1</b> is electric but the line is not electrified!").arg(item.rsName);
|
||||
// Warn Electric traction not possible
|
||||
return tr("Engine <b>%1</b> is electric but the line is not electrified!")
|
||||
.arg(item.rsName);
|
||||
}
|
||||
if(item.flag == HasNextOperation)
|
||||
if (item.flag == HasNextOperation)
|
||||
{
|
||||
return tr("Rollingstock <b>%1</b> is coupled in this station also by <b>%2</b> at <b>%3</b>.")
|
||||
.arg(item.rsName)
|
||||
.arg(JobCategoryName::jobName(item.jobId, item.jobCat))
|
||||
.arg(item.time.toString("HH:mm"));
|
||||
return tr("Rollingstock <b>%1</b> is coupled in this station also by <b>%2</b> at "
|
||||
"<b>%3</b>.")
|
||||
.arg(item.rsName, JobCategoryName::jobName(item.jobId, item.jobCat),
|
||||
item.time.toString("HH:mm"));
|
||||
}
|
||||
if(item.flag == LastOperation)
|
||||
if (item.flag == LastOperation)
|
||||
{
|
||||
return tr("Rollingstock <b>%1</b> was left in this station by <b>%2</b> at <b>%3</b>.")
|
||||
.arg(item.rsName)
|
||||
.arg(JobCategoryName::jobName(item.jobId, item.jobCat))
|
||||
.arg(item.time.toString("HH:mm"));
|
||||
.arg(item.rsName, JobCategoryName::jobName(item.jobId, item.jobCat),
|
||||
item.time.toString("HH:mm"));
|
||||
}
|
||||
if(item.flag == FirstUseOfRS)
|
||||
if (item.flag == FirstUseOfRS)
|
||||
{
|
||||
if(op == RsOp::Coupled && couplingMgr->contains(item.rsId, RsOp::Coupled))
|
||||
if (op == RsOp::Coupled && couplingMgr->contains(item.rsId, RsOp::Coupled))
|
||||
return tr("This is the first use of this rollingstock <b>%1</b>").arg(item.rsName);
|
||||
return tr("This would be the first use of this rollingstock <b>%1</b>").arg(item.rsName);
|
||||
return tr("This would be the first use of this rollingstock <b>%1</b>")
|
||||
.arg(item.rsName);
|
||||
}
|
||||
if(item.flag == UnusedRS)
|
||||
if (item.flag == UnusedRS)
|
||||
{
|
||||
return tr("Rollingstock <b>%1</b> is never used in this session. You can couple it for the first time from any one station")
|
||||
.arg(item.rsName);
|
||||
return tr("Rollingstock <b>%1</b> is never used in this session. You can couple it for "
|
||||
"the first time from any one station")
|
||||
.arg(item.rsName);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -135,21 +161,23 @@ QVariant RSProxyModel::data(const QModelIndex &idx, int role) const
|
|||
|
||||
bool RSProxyModel::setData(const QModelIndex &idx, const QVariant &value, int role)
|
||||
{
|
||||
if(role != Qt::CheckStateRole || !idx.isValid() || idx.column() > 0 || idx.row() >= m_data.size())
|
||||
if (role != Qt::CheckStateRole || !idx.isValid() || idx.column() > 0
|
||||
|| idx.row() >= m_data.size())
|
||||
return false;
|
||||
|
||||
Qt::CheckState state = value.value<Qt::CheckState>();
|
||||
|
||||
const RsItem& item = m_data.at(idx.row());
|
||||
const RsItem &item = m_data.at(idx.row());
|
||||
|
||||
bool ret = false;
|
||||
bool ret = false;
|
||||
|
||||
if(op == RsOp::Coupled) //Check traction type only if we are dealing with RsType::Engine
|
||||
ret = couplingMgr->coupleRS(item.rsId, item.rsName, state == Qt::Checked, targetType == RsType::Engine);
|
||||
if (op == RsOp::Coupled) // Check traction type only if we are dealing with RsType::Engine
|
||||
ret = couplingMgr->coupleRS(item.rsId, item.rsName, state == Qt::Checked,
|
||||
targetType == RsType::Engine);
|
||||
else
|
||||
ret = couplingMgr->uncoupleRS(item.rsId, item.rsName, state == Qt::Checked);
|
||||
|
||||
if(ret)
|
||||
if (ret)
|
||||
emit dataChanged(idx, idx);
|
||||
|
||||
return ret;
|
||||
|
@ -157,11 +185,9 @@ bool RSProxyModel::setData(const QModelIndex &idx, const QVariant &value, int ro
|
|||
|
||||
Qt::ItemFlags RSProxyModel::flags(const QModelIndex &index) const
|
||||
{
|
||||
if(index.isValid())
|
||||
return Qt::ItemIsEnabled
|
||||
| Qt::ItemNeverHasChildren
|
||||
| Qt::ItemIsSelectable
|
||||
| Qt::ItemIsUserCheckable;
|
||||
if (index.isValid())
|
||||
return Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable
|
||||
| Qt::ItemIsUserCheckable;
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RSPROXYMODEL_H
|
||||
#define RSPROXYMODEL_H
|
||||
|
||||
|
@ -14,11 +33,7 @@ class RSProxyModel : public QAbstractListModel
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
RSProxyModel(RSCouplingInterface *mgr,
|
||||
RsOp o,
|
||||
RsType type,
|
||||
QObject *parent = nullptr);
|
||||
RSProxyModel(RSCouplingInterface *mgr, RsOp o, RsType type, QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
||||
|
@ -29,28 +44,28 @@ public:
|
|||
|
||||
enum RSItemFlg
|
||||
{
|
||||
NoFlag = 0,
|
||||
WrongStation = 1,
|
||||
LastOperation = 2,
|
||||
HasNextOperation = 3,
|
||||
FirstUseOfRS = 4,
|
||||
UnusedRS = 5,
|
||||
NoFlag = 0,
|
||||
WrongStation = 1,
|
||||
LastOperation = 2,
|
||||
HasNextOperation = 3,
|
||||
FirstUseOfRS = 4,
|
||||
UnusedRS = 5,
|
||||
ErrNotCoupledBefore = 6,
|
||||
ErrAlreadyCoupled = 7
|
||||
ErrAlreadyCoupled = 7
|
||||
};
|
||||
|
||||
struct RsItem
|
||||
{
|
||||
db_id rsId;
|
||||
db_id jobId; //Can be next job or previous job
|
||||
db_id jobId; // Can be next job or previous job
|
||||
QString rsName;
|
||||
int flag;
|
||||
QTime time; //Can be next or previous operation time
|
||||
QTime time; // Can be next or previous operation time
|
||||
JobCategory jobCat;
|
||||
RsEngineSubType engineType;
|
||||
};
|
||||
|
||||
void loadData(const QVector<RsItem>& items);
|
||||
void loadData(const QVector<RsItem> &items);
|
||||
|
||||
private:
|
||||
QVector<RsItem> m_data;
|
||||
|
|
|
@ -1,14 +1,31 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "stopcouplingmodel.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include "utils/delegates/sql/pageditemmodelhelper_impl.h"
|
||||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
using namespace sqlite3pp;
|
||||
|
||||
#include "rslistondemandmodelresultevent.h"
|
||||
#include "utils/rs_utils.h"
|
||||
|
||||
|
||||
StopCouplingModel::StopCouplingModel(sqlite3pp::database &db, QObject *parent) :
|
||||
RSListOnDemandModel(db, parent),
|
||||
m_stopId(0),
|
||||
|
@ -28,145 +45,66 @@ qint64 StopCouplingModel::recalcTotalItemCount()
|
|||
|
||||
void StopCouplingModel::setStop(db_id stopId, RsOp op)
|
||||
{
|
||||
m_stopId = stopId;
|
||||
m_stopId = stopId;
|
||||
m_operation = op;
|
||||
|
||||
refreshData(true);
|
||||
}
|
||||
|
||||
void StopCouplingModel::internalFetch(int first, int sortCol, int valRow, const QVariant& val)
|
||||
void StopCouplingModel::internalFetch(int first, int /*sortCol*/, int /*valRow*/,
|
||||
const QVariant & /*val*/)
|
||||
{
|
||||
query q(mDb);
|
||||
|
||||
int offset = first - valRow + curPage * ItemsPerPage;
|
||||
bool reverse = false;
|
||||
int offset = first + curPage * ItemsPerPage;
|
||||
|
||||
if(valRow > first)
|
||||
{
|
||||
offset = 0;
|
||||
reverse = true;
|
||||
}
|
||||
|
||||
//const char *whereCol;
|
||||
|
||||
QByteArray sql = "SELECT coupling.rs_id,rs_list.number,rs_models.name,rs_models.suffix,rs_models.type"
|
||||
" FROM coupling"
|
||||
" JOIN rs_list ON rs_list.id=coupling.rs_id"
|
||||
" LEFT JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" WHERE coupling.stop_id=?2 AND coupling.operation=?3"
|
||||
" ORDER BY rs_models.type,rs_models.name,rs_list.number,rs_models.suffix";
|
||||
// switch (sortCol)
|
||||
// {
|
||||
// case Name:
|
||||
// {
|
||||
// whereCol = "name"; //Order by 2 columns, no where clause
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// if(val.isValid())
|
||||
// {
|
||||
// sql += " WHERE ";
|
||||
// sql += whereCol;
|
||||
// if(reverse)
|
||||
// sql += "<?3";
|
||||
// else
|
||||
// sql += ">?3";
|
||||
// }
|
||||
|
||||
// sql += " ORDER BY ";
|
||||
// sql += whereCol;
|
||||
|
||||
// if(reverse)
|
||||
// sql += " DESC";
|
||||
QByteArray sql =
|
||||
"SELECT coupling.rs_id,rs_list.number,rs_models.name,rs_models.suffix,rs_models.type"
|
||||
" FROM coupling"
|
||||
" JOIN rs_list ON rs_list.id=coupling.rs_id"
|
||||
" LEFT JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" WHERE coupling.stop_id=?2 AND coupling.operation=?3"
|
||||
" ORDER BY rs_models.type,rs_models.name,rs_list.number,rs_models.suffix";
|
||||
|
||||
sql += " LIMIT ?1";
|
||||
if(offset)
|
||||
if (offset)
|
||||
sql += " OFFSET ?2";
|
||||
|
||||
q.prepare(sql);
|
||||
q.bind(1, BatchSize);
|
||||
q.bind(2, m_stopId);
|
||||
q.bind(3, int(m_operation));
|
||||
if(offset)
|
||||
if (offset)
|
||||
q.bind(2, offset);
|
||||
|
||||
if(val.isValid())
|
||||
{
|
||||
switch (sortCol)
|
||||
{
|
||||
case Name:
|
||||
{
|
||||
q.bind(3, val.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector<RSItem> vec(BatchSize);
|
||||
|
||||
auto it = q.begin();
|
||||
auto it = q.begin();
|
||||
const auto end = q.end();
|
||||
|
||||
if(reverse)
|
||||
int i = 0;
|
||||
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
int i = BatchSize - 1;
|
||||
auto r = *it;
|
||||
RSItem &item = vec[i];
|
||||
item.rsId = r.get<db_id>(0);
|
||||
|
||||
for(; it != end; ++it)
|
||||
{
|
||||
auto r = *it;
|
||||
RSItem &item = vec[i];
|
||||
item.rsId = r.get<db_id>(0);
|
||||
int number = r.get<int>(1);
|
||||
int modelNameLen = sqlite3_column_bytes(q.stmt(), 2);
|
||||
const char *modelName = reinterpret_cast<char const *>(sqlite3_column_text(q.stmt(), 2));
|
||||
|
||||
int number = r.get<int>(1);
|
||||
int modelNameLen = sqlite3_column_bytes(q.stmt(), 2);
|
||||
const char *modelName = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 2));
|
||||
int modelSuffixLen = sqlite3_column_bytes(q.stmt(), 3);
|
||||
const char *modelSuffix = reinterpret_cast<char const *>(sqlite3_column_text(q.stmt(), 3));
|
||||
item.type = RsType(sqlite3_column_int(q.stmt(), 4));
|
||||
|
||||
int modelSuffixLen = sqlite3_column_bytes(q.stmt(), 3);
|
||||
const char *modelSuffix = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 3));
|
||||
item.type = RsType(sqlite3_column_int(q.stmt(), 4));
|
||||
|
||||
item.name = rs_utils::formatNameRef(modelName, modelNameLen,
|
||||
number,
|
||||
modelSuffix, modelSuffixLen,
|
||||
item.type);
|
||||
i--;
|
||||
}
|
||||
if(i > -1)
|
||||
vec.remove(0, i + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for(; it != end; ++it)
|
||||
{
|
||||
auto r = *it;
|
||||
RSItem &item = vec[i];
|
||||
item.rsId = r.get<db_id>(0);
|
||||
|
||||
int number = r.get<int>(1);
|
||||
int modelNameLen = sqlite3_column_bytes(q.stmt(), 2);
|
||||
const char *modelName = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 2));
|
||||
|
||||
int modelSuffixLen = sqlite3_column_bytes(q.stmt(), 3);
|
||||
const char *modelSuffix = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 3));
|
||||
item.type = RsType(sqlite3_column_int(q.stmt(), 4));
|
||||
|
||||
item.name = rs_utils::formatNameRef(modelName, modelNameLen,
|
||||
number,
|
||||
modelSuffix, modelSuffixLen,
|
||||
item.type);
|
||||
i++;
|
||||
}
|
||||
if(i < BatchSize)
|
||||
vec.remove(i, BatchSize - i);
|
||||
item.name = rs_utils::formatNameRef(modelName, modelNameLen, number, modelSuffix,
|
||||
modelSuffixLen, item.type);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i < BatchSize)
|
||||
vec.remove(i, BatchSize - i);
|
||||
|
||||
RSListOnDemandModelResultEvent *ev = new RSListOnDemandModelResultEvent;
|
||||
ev->items = vec;
|
||||
ev->firstRow = first;
|
||||
|
||||
qApp->postEvent(this, ev);
|
||||
postResult(vec, first);
|
||||
}
|
||||
|
|
|
@ -1,9 +1,27 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STOPCOUPLINGMODEL_H
|
||||
#define STOPCOUPLINGMODEL_H
|
||||
|
||||
#include "rslistondemandmodel.h"
|
||||
|
||||
|
||||
class StopCouplingModel : public RSListOnDemandModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef STOPMODEL_H
|
||||
#define STOPMODEL_H
|
||||
|
||||
|
@ -7,7 +26,7 @@
|
|||
#include <QVector>
|
||||
#include <QSet>
|
||||
|
||||
#include "utils/types.h"
|
||||
#include "stations/station_utils.h"
|
||||
|
||||
namespace sqlite3pp {
|
||||
class database;
|
||||
|
@ -17,9 +36,10 @@ struct StopItem
|
|||
{
|
||||
struct Gate
|
||||
{
|
||||
db_id gateConnId = 0;
|
||||
db_id gateId = 0;
|
||||
int trackNum = -1;
|
||||
db_id gateConnId = 0;
|
||||
db_id gateId = 0;
|
||||
int gateTrackNum = -1;
|
||||
utils::Side stationTrackSide = utils::Side::NSides;
|
||||
};
|
||||
|
||||
struct Segment
|
||||
|
@ -28,7 +48,7 @@ struct StopItem
|
|||
db_id segmentId = 0;
|
||||
int inTrackNum = -1;
|
||||
int outTrackNum = -1;
|
||||
bool reversed = false;
|
||||
bool reversed = false;
|
||||
};
|
||||
|
||||
db_id stopId = 0;
|
||||
|
@ -42,12 +62,11 @@ struct StopItem
|
|||
QTime arrival;
|
||||
QTime departure;
|
||||
|
||||
int addHere = 0;
|
||||
int addHere = 0;
|
||||
|
||||
StopType type = StopType::Normal;
|
||||
};
|
||||
|
||||
|
||||
/*!
|
||||
* \brief The StopModel class
|
||||
*
|
||||
|
@ -60,7 +79,7 @@ class StopModel : public QAbstractListModel
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
StopModel(sqlite3pp::database& db, QObject *parent = nullptr);
|
||||
StopModel(sqlite3pp::database &db, QObject *parent = nullptr);
|
||||
|
||||
// QAbstractListModel
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
@ -75,7 +94,7 @@ public:
|
|||
void setAutoUncoupleAtLast(bool value);
|
||||
|
||||
// Loading
|
||||
void loadJobStops(db_id jobId);
|
||||
bool loadJobStops(db_id jobId);
|
||||
void clearJob();
|
||||
|
||||
// Saving
|
||||
|
@ -85,7 +104,7 @@ public:
|
|||
|
||||
// Editing
|
||||
void addStop();
|
||||
void removeStop(const QModelIndex& idx);
|
||||
void removeStop(const QModelIndex &idx);
|
||||
void removeLastIfEmpty();
|
||||
|
||||
void uncoupleStillCoupledAtLastStop();
|
||||
|
@ -98,7 +117,8 @@ public:
|
|||
db_id getNewShiftId() const;
|
||||
|
||||
// Setters
|
||||
void setStopInfo(const QModelIndex& idx, StopItem newStop, StopItem::Segment prevSeg);
|
||||
void setStopInfo(const QModelIndex &idx, StopItem newStop, StopItem::Segment prevSeg,
|
||||
bool avoidTimeRecalc = false);
|
||||
|
||||
bool setStopTypeRange(int firstRow, int lastRow, StopType type);
|
||||
|
||||
|
@ -109,7 +129,7 @@ public:
|
|||
// Convinience
|
||||
int getStopRow(db_id stopId) const;
|
||||
|
||||
bool isAddHere(const QModelIndex& idx);
|
||||
bool isAddHere(const QModelIndex &idx);
|
||||
|
||||
std::pair<QTime, QTime> getFirstLastTimes() const;
|
||||
|
||||
|
@ -119,10 +139,18 @@ public:
|
|||
bool isRailwayElectrifiedAfterStop(db_id stopId) const;
|
||||
bool isRailwayElectrifiedAfterRow(int row) const;
|
||||
|
||||
inline StopItem getItemAt(int row) const { return stops.at(row); }
|
||||
inline StopType getItemTypeAt(int row) const { return stops.at(row).type; }
|
||||
inline db_id getItemStationAt(int row) const { return stops.at(row).stationId; }
|
||||
|
||||
inline StopItem getItemAt(int row) const
|
||||
{
|
||||
return stops.at(row);
|
||||
}
|
||||
inline StopType getItemTypeAt(int row) const
|
||||
{
|
||||
return stops.at(row).type;
|
||||
}
|
||||
inline db_id getItemStationAt(int row) const
|
||||
{
|
||||
return stops.at(row).stationId;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_AUTO_TIME_RECALC
|
||||
void rebaseTimesToSpeed(int firstIdx, QTime firstArr, QTime firstDep);
|
||||
|
@ -130,11 +158,10 @@ public:
|
|||
|
||||
bool trySelectTrackForStop(StopItem &item);
|
||||
|
||||
bool trySetTrackConnections(StopItem &item, db_id trackId,
|
||||
QString *outErr);
|
||||
bool trySetTrackConnections(StopItem &item, db_id trackId, QString *outErr);
|
||||
|
||||
bool trySelectNextSegment(StopItem &item, db_id segmentId, int suggestedOutGateTrk,
|
||||
db_id nextStationId, db_id &out_gateId);
|
||||
db_id nextStationId, db_id &out_gateId, db_id &out_suggestedTrackId);
|
||||
|
||||
signals:
|
||||
void edited(bool val);
|
||||
|
@ -142,7 +169,7 @@ signals:
|
|||
void categoryChanged(int newCat);
|
||||
void jobIdChanged(db_id jobId);
|
||||
void jobShiftChanged(db_id shiftId);
|
||||
void errorSetShiftWithoutStops(); //TODO: find better way to show errors
|
||||
void errorSetShiftWithoutStops(); // TODO: find better way to show errors
|
||||
|
||||
public slots:
|
||||
void setCategory(int value);
|
||||
|
@ -162,25 +189,29 @@ private:
|
|||
db_id createStop(db_id jobId, const QTime &arr, const QTime &dep, StopType type);
|
||||
void deleteStop(db_id stopId);
|
||||
|
||||
bool updateCurrentInGate(StopItem& curStop, const StopItem::Segment& prevSeg);
|
||||
bool updateStopTime(StopItem& item, int row, bool propagate, const QTime &oldArr, const QTime &oldDep);
|
||||
bool updateCurrentInGate(StopItem &curStop, const StopItem::Segment &prevSeg);
|
||||
bool updateStopTime(StopItem &item, int row, bool propagate, const QTime &oldArr,
|
||||
const QTime &oldDep);
|
||||
|
||||
int calcTravelTime(db_id segmentId);
|
||||
int defaultStopTimeSec();
|
||||
|
||||
void shiftStopsBy24hoursFrom(const QTime& startTime);
|
||||
void shiftStopsBy24hoursFrom(const QTime &startTime);
|
||||
|
||||
friend class RSCouplingInterface;
|
||||
bool startInfoEditing();
|
||||
bool startStopsEditing();
|
||||
bool endStopsEditing();
|
||||
inline void markRsToUpdate(db_id rsId) { rsToUpdate.insert(rsId); }
|
||||
inline void markRsToUpdate(db_id rsId)
|
||||
{
|
||||
rsToUpdate.insert(rsId);
|
||||
}
|
||||
|
||||
private:
|
||||
//To simulate acceleration/braking we add 4 km to distance
|
||||
// To simulate acceleration/braking we add 4 km to distance
|
||||
static constexpr double accelerationDistMeters = 4000.0;
|
||||
|
||||
sqlite3pp::database& mDb;
|
||||
sqlite3pp::database &mDb;
|
||||
|
||||
QVector<StopItem> stops;
|
||||
|
||||
|
@ -198,8 +229,8 @@ private:
|
|||
|
||||
enum EditState
|
||||
{
|
||||
NotEditing = 0,
|
||||
InfoEditing = 1,
|
||||
NotEditing = 0,
|
||||
InfoEditing = 1,
|
||||
StopsEditing = 2
|
||||
};
|
||||
|
||||
|
|
|
@ -1,16 +1,34 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "trainassetmodel.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include "utils/delegates/sql/pageditemmodelhelper_impl.h"
|
||||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
using namespace sqlite3pp;
|
||||
|
||||
#include "rslistondemandmodelresultevent.h"
|
||||
#include "utils/rs_utils.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
TrainAssetModel::TrainAssetModel(database& db, QObject *parent) :
|
||||
TrainAssetModel::TrainAssetModel(database &db, QObject *parent) :
|
||||
RSListOnDemandModel(db, parent),
|
||||
m_jobId(0),
|
||||
m_mode(BeforeStop)
|
||||
|
@ -27,37 +45,33 @@ qint64 TrainAssetModel::recalcTotalItemCount()
|
|||
" GROUP BY coupling.rs_id"
|
||||
" HAVING coupling.operation=1)");
|
||||
q.bind(1, m_jobId);
|
||||
//HACK: 1 minute is the min interval between stops,
|
||||
//by adding 1 minute we include the current stop but leave out the next one
|
||||
if(m_mode == AfterStop)
|
||||
// HACK: 1 minute is the min interval between stops,
|
||||
// by adding 1 minute we include the current stop but leave out the next one
|
||||
if (m_mode == AfterStop)
|
||||
q.bind(2, m_arrival.addSecs(60));
|
||||
else
|
||||
q.bind(2, m_arrival);
|
||||
int ret = q.step();
|
||||
if(ret != SQLITE_ROW)
|
||||
if (ret != SQLITE_ROW)
|
||||
qWarning() << "TrainAssetModel: " << mDb.error_msg() << mDb.error_code();
|
||||
|
||||
const qint64 count = q.getRows().get<int>(0);
|
||||
return count;
|
||||
}
|
||||
|
||||
void TrainAssetModel::internalFetch(int first, int sortCol, int valRow, const QVariant &val)
|
||||
void TrainAssetModel::internalFetch(int first, int sortCol, int /*valRow*/,
|
||||
const QVariant & /*val*/)
|
||||
{
|
||||
query q(mDb);
|
||||
|
||||
int offset = first - valRow + curPage * ItemsPerPage;
|
||||
bool reverse = false;
|
||||
int offset = first + curPage * ItemsPerPage;
|
||||
|
||||
if(valRow > first)
|
||||
{
|
||||
offset = 0;
|
||||
reverse = true;
|
||||
}
|
||||
|
||||
//const char *whereCol;
|
||||
// const char *whereCol;
|
||||
|
||||
QByteArray sql = "SELECT sub.rs_id,sub.number,sub.name,sub.suffix,sub.type FROM("
|
||||
"SELECT coupling.rs_id,rs_list.number,rs_models.name,rs_models.suffix,rs_models.type,MAX(stops.arrival)"
|
||||
"SELECT "
|
||||
"coupling.rs_id,rs_list.number,rs_models.name,rs_models.suffix,rs_models.type,"
|
||||
"MAX(stops.arrival)"
|
||||
" FROM stops"
|
||||
" JOIN coupling ON coupling.stop_id=stops.id"
|
||||
" JOIN rs_list ON rs_list.id=rs_id"
|
||||
|
@ -92,106 +106,57 @@ void TrainAssetModel::internalFetch(int first, int sortCol, int valRow, const QV
|
|||
// sql += " DESC";
|
||||
|
||||
sql += " LIMIT ?1";
|
||||
if(offset)
|
||||
if (offset)
|
||||
sql += " OFFSET ?2";
|
||||
|
||||
q.prepare(sql);
|
||||
q.bind(1, BatchSize);
|
||||
q.bind(3, m_jobId);
|
||||
//HACK: 1 minute is the min interval between stops,
|
||||
//by adding 1 minute we include the current stop but leave out the next one
|
||||
if(m_mode == AfterStop)
|
||||
// HACK: 1 minute is the min interval between stops,
|
||||
// by adding 1 minute we include the current stop but leave out the next one
|
||||
if (m_mode == AfterStop)
|
||||
q.bind(4, m_arrival.addSecs(60));
|
||||
else
|
||||
q.bind(4, m_arrival);
|
||||
if(offset)
|
||||
if (offset)
|
||||
q.bind(2, offset);
|
||||
|
||||
if(val.isValid())
|
||||
{
|
||||
switch (sortCol)
|
||||
{
|
||||
case Name:
|
||||
{
|
||||
q.bind(3, val.toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector<RSItem> vec(BatchSize);
|
||||
|
||||
auto it = q.begin();
|
||||
auto it = q.begin();
|
||||
const auto end = q.end();
|
||||
|
||||
if(reverse)
|
||||
int i = 0;
|
||||
for (; it != end; ++it)
|
||||
{
|
||||
int i = BatchSize - 1;
|
||||
auto r = *it;
|
||||
RSItem &item = vec[i];
|
||||
item.rsId = r.get<db_id>(0);
|
||||
|
||||
for(; it != end; ++it)
|
||||
{
|
||||
auto r = *it;
|
||||
RSItem &item = vec[i];
|
||||
item.rsId = r.get<db_id>(0);
|
||||
int number = r.get<int>(1);
|
||||
int modelNameLen = sqlite3_column_bytes(q.stmt(), 2);
|
||||
const char *modelName = reinterpret_cast<char const *>(sqlite3_column_text(q.stmt(), 2));
|
||||
|
||||
int number = r.get<int>(1);
|
||||
int modelNameLen = sqlite3_column_bytes(q.stmt(), 2);
|
||||
const char *modelName = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 2));
|
||||
int modelSuffixLen = sqlite3_column_bytes(q.stmt(), 3);
|
||||
const char *modelSuffix = reinterpret_cast<char const *>(sqlite3_column_text(q.stmt(), 3));
|
||||
item.type = RsType(sqlite3_column_int(q.stmt(), 4));
|
||||
|
||||
int modelSuffixLen = sqlite3_column_bytes(q.stmt(), 3);
|
||||
const char *modelSuffix = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 3));
|
||||
item.type = RsType(sqlite3_column_int(q.stmt(), 4));
|
||||
|
||||
item.name = rs_utils::formatNameRef(modelName, modelNameLen,
|
||||
number,
|
||||
modelSuffix, modelSuffixLen,
|
||||
item.type);
|
||||
i--;
|
||||
}
|
||||
if(i > -1)
|
||||
vec.remove(0, i + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
int i = 0;
|
||||
|
||||
for(; it != end; ++it)
|
||||
{
|
||||
auto r = *it;
|
||||
RSItem &item = vec[i];
|
||||
item.rsId = r.get<db_id>(0);
|
||||
|
||||
int number = r.get<int>(1);
|
||||
int modelNameLen = sqlite3_column_bytes(q.stmt(), 2);
|
||||
const char *modelName = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 2));
|
||||
|
||||
int modelSuffixLen = sqlite3_column_bytes(q.stmt(), 3);
|
||||
const char *modelSuffix = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 3));
|
||||
item.type = RsType(sqlite3_column_int(q.stmt(), 4));
|
||||
|
||||
item.name = rs_utils::formatNameRef(modelName, modelNameLen,
|
||||
number,
|
||||
modelSuffix, modelSuffixLen,
|
||||
item.type);
|
||||
i++;
|
||||
}
|
||||
if(i < BatchSize)
|
||||
vec.remove(i, BatchSize - i);
|
||||
item.name = rs_utils::formatNameRef(modelName, modelNameLen, number, modelSuffix,
|
||||
modelSuffixLen, item.type);
|
||||
i++;
|
||||
}
|
||||
|
||||
if (i < BatchSize)
|
||||
vec.remove(i, BatchSize - i);
|
||||
|
||||
RSListOnDemandModelResultEvent *ev = new RSListOnDemandModelResultEvent;
|
||||
ev->items = vec;
|
||||
ev->firstRow = first;
|
||||
|
||||
qApp->postEvent(this, ev);
|
||||
postResult(vec, first);
|
||||
}
|
||||
|
||||
void TrainAssetModel::setStop(db_id jobId, QTime arrival, Mode mode)
|
||||
{
|
||||
m_jobId = jobId;
|
||||
m_jobId = jobId;
|
||||
m_arrival = arrival;
|
||||
m_mode = mode;
|
||||
m_mode = mode;
|
||||
|
||||
refreshData(true);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef TRAINASSETMODEL_H
|
||||
#define TRAINASSETMODEL_H
|
||||
|
||||
|
@ -8,12 +27,13 @@ class TrainAssetModel : public RSListOnDemandModel
|
|||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
typedef enum {
|
||||
enum Mode
|
||||
{
|
||||
BeforeStop,
|
||||
AfterStop
|
||||
} Mode;
|
||||
};
|
||||
|
||||
TrainAssetModel(sqlite3pp::database& db, QObject *parent = nullptr);
|
||||
TrainAssetModel(sqlite3pp::database &db, QObject *parent = nullptr);
|
||||
|
||||
// TrainAssetModel
|
||||
void setStop(db_id jobId, QTime arrival, Mode mode);
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "rscoupledialog.h"
|
||||
|
||||
#include <QLabel>
|
||||
|
@ -14,15 +33,18 @@
|
|||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
|
||||
#include "app/session.h"
|
||||
|
||||
RSCoupleDialog::RSCoupleDialog(RSCouplingInterface *mgr, RsOp o, QWidget *parent) :
|
||||
QDialog (parent),
|
||||
QDialog(parent),
|
||||
couplingMgr(mgr),
|
||||
legend(nullptr),
|
||||
m_showLegend(false),
|
||||
op(o)
|
||||
{
|
||||
engModel = new RSProxyModel(couplingMgr, op, RsType::Engine, this);
|
||||
coachModel = new RSProxyModel(couplingMgr, op, RsType::Coach, this);
|
||||
freightModel = new RSProxyModel(couplingMgr, op, RsType::FreightWagon, this);
|
||||
engModel = new RSProxyModel(couplingMgr, op, RsType::Engine, this);
|
||||
coachModel = new RSProxyModel(couplingMgr, op, RsType::Coach, this);
|
||||
freightModel = new RSProxyModel(couplingMgr, op, RsType::FreightWagon, this);
|
||||
|
||||
QGridLayout *lay = new QGridLayout(this);
|
||||
|
||||
|
@ -54,32 +76,39 @@ RSCoupleDialog::RSCoupleDialog(RSCouplingInterface *mgr, RsOp o, QWidget *parent
|
|||
lay->addWidget(freightLabel, 0, 2);
|
||||
lay->addWidget(freightView, 1, 2);
|
||||
|
||||
showHideLegendBut = new QPushButton;
|
||||
lay->addWidget(showHideLegendBut, 2, 0, 1, 1);
|
||||
|
||||
legend = new QFrame;
|
||||
lay->addWidget(legend, 3, 0, 1, 3);
|
||||
lay->addWidget(legend, 2, 0, 1, 3);
|
||||
legend->hide();
|
||||
|
||||
QDialogButtonBox *box = new QDialogButtonBox(QDialogButtonBox::Ok); //TODO: implement also cancel
|
||||
QHBoxLayout *buttonLay = new QHBoxLayout;
|
||||
lay->addLayout(buttonLay, 3, 0, 1, 3);
|
||||
|
||||
showHideLegendBut = new QPushButton;
|
||||
buttonLay->addWidget(showHideLegendBut);
|
||||
|
||||
QDialogButtonBox *box =
|
||||
new QDialogButtonBox(QDialogButtonBox::Ok); // TODO: implement also cancel
|
||||
connect(box, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||
connect(box, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
lay->addWidget(box, 4, 0, 1, 3);
|
||||
buttonLay->addWidget(box);
|
||||
|
||||
connect(showHideLegendBut, &QPushButton::clicked, this, &RSCoupleDialog::toggleLegend);
|
||||
updateButText();
|
||||
|
||||
setMinimumSize(400, 300);
|
||||
setWindowFlag(Qt::WindowMaximizeButtonHint);
|
||||
|
||||
setLegendVisible(AppSettings.getShowCouplingLegend());
|
||||
}
|
||||
|
||||
void RSCoupleDialog::loadProxyModels(sqlite3pp::database& db, db_id jobId, db_id stopId, db_id stationId, const QTime& arrival)
|
||||
void RSCoupleDialog::loadProxyModels(sqlite3pp::database &db, db_id jobId, db_id stopId,
|
||||
db_id stationId, const QTime &arrival)
|
||||
{
|
||||
QVector<RSProxyModel::RsItem> engines, freight, coaches;
|
||||
|
||||
sqlite3pp::query q(db);
|
||||
|
||||
if(op == RsOp::Coupled)
|
||||
if (op == RsOp::Coupled)
|
||||
{
|
||||
/* Show Couple-able RS:
|
||||
* - RS free in this station
|
||||
|
@ -90,59 +119,68 @@ void RSCoupleDialog::loadProxyModels(sqlite3pp::database& db, db_id jobId, db_id
|
|||
* - Possible wrong operations to let the user remove them
|
||||
*/
|
||||
|
||||
q.prepare("SELECT MAX(sub.p), sub.rs_id, rs_list.number, rs_models.name, rs_models.suffix, rs_models.type, rs_models.sub_type, sub.arr, sub.job_id, jobs.category FROM ("
|
||||
q.prepare(
|
||||
"SELECT MAX(sub.p), sub.rs_id, rs_list.number, rs_models.name, rs_models.suffix, "
|
||||
"rs_models.type, rs_models.sub_type, sub.arr, sub.job_id, jobs.category FROM ("
|
||||
|
||||
//Select possible wrong operations to let user remove (un-check) them
|
||||
" SELECT 1 AS p, coupling.rs_id AS rs_id, NULL AS arr, NULL AS job_id FROM coupling WHERE coupling.stop_id=?3 AND coupling.operation=1"
|
||||
" UNION ALL"
|
||||
// Select possible wrong operations to let user remove (un-check) them
|
||||
" SELECT 1 AS p, coupling.rs_id AS rs_id, NULL AS arr, NULL AS job_id FROM coupling "
|
||||
"WHERE coupling.stop_id=?3 AND coupling.operation=1"
|
||||
" UNION ALL"
|
||||
|
||||
//Select RS uncoupled before our arrival (included RS uncoupled at exact same time) (except uncoupled by us)
|
||||
" SELECT 2 AS p, coupling.rs_id AS rs_id, MAX(stops.arrival) AS arr, stops.job_id AS job_id"
|
||||
" FROM stops"
|
||||
" JOIN coupling ON coupling.stop_id=stops.id"
|
||||
" WHERE stops.station_id=?1 AND stops.arrival <= ?2 AND stops.id<>?3"
|
||||
" GROUP BY coupling.rs_id"
|
||||
" HAVING coupling.operation=0"
|
||||
" UNION ALL"
|
||||
// Select RS uncoupled before our arrival (included RS uncoupled at exact same time)
|
||||
// (except uncoupled by us)
|
||||
" SELECT 2 AS p, coupling.rs_id AS rs_id, MAX(stops.arrival) AS arr, stops.job_id AS "
|
||||
"job_id"
|
||||
" FROM stops"
|
||||
" JOIN coupling ON coupling.stop_id=stops.id"
|
||||
" WHERE stops.station_id=?1 AND stops.arrival <= ?2 AND stops.id<>?3"
|
||||
" GROUP BY coupling.rs_id"
|
||||
" HAVING coupling.operation=0"
|
||||
" UNION ALL"
|
||||
|
||||
//Select RS coupled after our arrival (excluded RS coupled at exact same time)
|
||||
" SELECT 3 AS p, coupling.rs_id, MIN(stops.arrival) AS arr, stops.job_id AS job_id"
|
||||
" FROM coupling"
|
||||
" JOIN stops ON stops.id=coupling.stop_id"
|
||||
" WHERE stops.station_id=?1 AND stops.arrival > ?2"
|
||||
" GROUP BY coupling.rs_id"
|
||||
" HAVING coupling.operation=1"
|
||||
" UNION ALL"
|
||||
// Select RS coupled after our arrival (excluded RS coupled at exact same time)
|
||||
" SELECT 3 AS p, coupling.rs_id, MIN(stops.arrival) AS arr, stops.job_id AS job_id"
|
||||
" FROM coupling"
|
||||
" JOIN stops ON stops.id=coupling.stop_id"
|
||||
" WHERE stops.station_id=?1 AND stops.arrival > ?2"
|
||||
" GROUP BY coupling.rs_id"
|
||||
" HAVING coupling.operation=1"
|
||||
" UNION ALL"
|
||||
|
||||
//Select coupled RS for first time
|
||||
" SELECT 4 AS p, rs_list.id AS rs_id, NULL AS arr, NULL AS job_id"
|
||||
" FROM rs_list"
|
||||
" WHERE NOT EXISTS ("
|
||||
" SELECT coupling.rs_id FROM coupling"
|
||||
" JOIN stops ON stops.id=coupling.stop_id WHERE coupling.rs_id=rs_list.id AND stops.arrival<?2)"
|
||||
" UNION ALL"
|
||||
// Select coupled RS for first time
|
||||
" SELECT 4 AS p, rs_list.id AS rs_id, NULL AS arr, NULL AS job_id"
|
||||
" FROM rs_list"
|
||||
" WHERE NOT EXISTS ("
|
||||
" SELECT coupling.rs_id FROM coupling"
|
||||
" JOIN stops ON stops.id=coupling.stop_id WHERE coupling.rs_id=rs_list.id AND "
|
||||
"stops.arrival<?2)"
|
||||
" UNION ALL"
|
||||
|
||||
//Select unused RS (RS without any operation)
|
||||
" SELECT 5 AS p, rs_list.id AS rs_id, NULL AS arr, NULL AS job_id"
|
||||
" FROM rs_list"
|
||||
" WHERE NOT EXISTS (SELECT coupling.rs_id FROM coupling WHERE coupling.rs_id=rs_list.id)"
|
||||
" UNION ALL"
|
||||
// Select unused RS (RS without any operation)
|
||||
" SELECT 5 AS p, rs_list.id AS rs_id, NULL AS arr, NULL AS job_id"
|
||||
" FROM rs_list"
|
||||
" WHERE NOT EXISTS (SELECT coupling.rs_id FROM coupling WHERE coupling.rs_id=rs_list.id)"
|
||||
" UNION ALL"
|
||||
|
||||
//Select RS coupled before our arrival (already coupled by this job or occupied by other job)
|
||||
" SELECT 7 AS p, c1.rs_id, MAX(s1.arrival) AS arr, s1.job_id AS job_id"
|
||||
" FROM coupling c1"
|
||||
" JOIN coupling c2 ON c2.rs_id=c1.rs_id"
|
||||
" JOIN stops s1 ON s1.id=c2.stop_id"
|
||||
" WHERE c1.stop_id=?3 AND c1.operation=1 AND s1.arrival<?2"
|
||||
" GROUP BY c1.rs_id"
|
||||
" HAVING c2.operation=1"
|
||||
" ) AS sub"
|
||||
// Select RS coupled before our arrival (already coupled by this job or occupied by other
|
||||
// job)
|
||||
" SELECT 7 AS p, c1.rs_id, MAX(s1.arrival) AS arr, s1.job_id AS job_id"
|
||||
" FROM coupling c1"
|
||||
" JOIN coupling c2 ON c2.rs_id=c1.rs_id"
|
||||
" JOIN stops s1 ON s1.id=c2.stop_id"
|
||||
" WHERE c1.stop_id=?3 AND c1.operation=1 AND s1.arrival<?2"
|
||||
" GROUP BY c1.rs_id"
|
||||
" HAVING c2.operation=1"
|
||||
" ) AS sub"
|
||||
|
||||
" JOIN rs_list ON rs_list.id=sub.rs_id" //FIXME: it seems it is better to join in the subquery directly, also avoids some LEFT in joins
|
||||
" LEFT JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" LEFT JOIN jobs ON jobs.id=sub.job_id"
|
||||
" GROUP BY sub.rs_id"
|
||||
" ORDER BY rs_models.name, rs_list.number");
|
||||
" JOIN rs_list ON rs_list.id=sub.rs_id" // FIXME: it seems it is better to join in the
|
||||
// subquery directly, also avoids some LEFT in
|
||||
// joins
|
||||
" LEFT JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" LEFT JOIN jobs ON jobs.id=sub.job_id"
|
||||
" GROUP BY sub.rs_id"
|
||||
" ORDER BY rs_models.name, rs_list.number");
|
||||
|
||||
q.bind(1, stationId);
|
||||
q.bind(2, arrival);
|
||||
|
@ -157,65 +195,67 @@ void RSCoupleDialog::loadProxyModels(sqlite3pp::database& db, db_id jobId, db_id
|
|||
* - Show possible wrong operations to let user remove them
|
||||
*/
|
||||
|
||||
q.prepare("SELECT MAX(sub.p), sub.rs_id, rs_list.number, rs_models.name, rs_models.suffix, rs_models.type, rs_models.sub_type, sub.arr, NULL AS job_id, NULL AS category FROM("
|
||||
"SELECT 8 AS p, coupling.rs_id AS rs_id, MAX(stops.arrival) AS arr"
|
||||
" FROM stops"
|
||||
" JOIN coupling ON coupling.stop_id=stops.id"
|
||||
" WHERE stops.arrival<?2"
|
||||
" GROUP BY coupling.rs_id"
|
||||
" HAVING coupling.operation=1 AND stops.job_id=?1"
|
||||
" UNION ALL"
|
||||
q.prepare(
|
||||
"SELECT MAX(sub.p), sub.rs_id, rs_list.number, rs_models.name, rs_models.suffix, "
|
||||
"rs_models.type, rs_models.sub_type, sub.arr, NULL AS job_id, NULL AS category FROM("
|
||||
"SELECT 8 AS p, coupling.rs_id AS rs_id, MAX(stops.arrival) AS arr"
|
||||
" FROM stops"
|
||||
" JOIN coupling ON coupling.stop_id=stops.id"
|
||||
" WHERE stops.arrival<?2"
|
||||
" GROUP BY coupling.rs_id"
|
||||
" HAVING coupling.operation=1 AND stops.job_id=?1"
|
||||
" UNION ALL"
|
||||
|
||||
//Select possible wrong operations to let user remove them
|
||||
" SELECT 6 AS p, coupling.rs_id AS rs_id, NULL AS arr FROM coupling WHERE coupling.stop_id=?3 AND coupling.operation=0"
|
||||
") AS sub"
|
||||
" JOIN rs_list ON rs_list.id=sub.rs_id" //FIXME: it seems it is better to join in the subquery directly, also avoids some LEFT in joins
|
||||
" LEFT JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" GROUP BY sub.rs_id"
|
||||
" ORDER BY rs_models.name, rs_list.number");
|
||||
// Select possible wrong operations to let user remove them
|
||||
" SELECT 6 AS p, coupling.rs_id AS rs_id, NULL AS arr FROM coupling WHERE "
|
||||
"coupling.stop_id=?3 AND coupling.operation=0"
|
||||
") AS sub"
|
||||
" JOIN rs_list ON rs_list.id=sub.rs_id" // FIXME: it seems it is better to join in the
|
||||
// subquery directly, also avoids some LEFT in
|
||||
// joins
|
||||
" LEFT JOIN rs_models ON rs_models.id=rs_list.model_id"
|
||||
" GROUP BY sub.rs_id"
|
||||
" ORDER BY rs_models.name, rs_list.number");
|
||||
|
||||
q.bind(1, jobId);
|
||||
q.bind(2, arrival);
|
||||
q.bind(3, stopId);
|
||||
}
|
||||
|
||||
for(auto rs : q)
|
||||
for (auto rs : q)
|
||||
{
|
||||
/*Priority flag:
|
||||
* 1: The RS is not free in this station, it's shown to let the user remove the operation
|
||||
* 2: The RS is free and has no following operation in this station
|
||||
* (It could have wrong operation in other station that should be fixed by user,
|
||||
* as shown in RsErrorWidget)
|
||||
* 3: The RS is free but a job will couple it in the future so you should bring it back here before that time
|
||||
* 4: The RS is used for first time
|
||||
* 5: The RS is unsed
|
||||
* 6: RS was not coupled before this stop and you are trying to uncouple it
|
||||
* 7: Normal RS uncouple-able, used to win against 6
|
||||
*/
|
||||
* 3: The RS is free but a job will couple it in the future so you should bring it back here
|
||||
* before that time 4: The RS is used for first time 5: The RS is unsed 6: RS was not
|
||||
* coupled before this stop and you are trying to uncouple it 7: Normal RS uncouple-able,
|
||||
* used to win against 6
|
||||
*/
|
||||
|
||||
RSProxyModel::RsItem item;
|
||||
item.flag = rs.get<int>(0);
|
||||
item.rsId = rs.get<db_id>(1);
|
||||
item.flag = rs.get<int>(0);
|
||||
item.rsId = rs.get<db_id>(1);
|
||||
|
||||
int number = rs.get<int>(2);
|
||||
int modelNameLen = sqlite3_column_bytes(q.stmt(), 3);
|
||||
const char *modelName = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 3));
|
||||
int number = rs.get<int>(2);
|
||||
int modelNameLen = sqlite3_column_bytes(q.stmt(), 3);
|
||||
const char *modelName = reinterpret_cast<char const *>(sqlite3_column_text(q.stmt(), 3));
|
||||
|
||||
int modelSuffixLen = sqlite3_column_bytes(q.stmt(), 4);
|
||||
const char *modelSuffix = reinterpret_cast<char const*>(sqlite3_column_text(q.stmt(), 4));
|
||||
RsType type = RsType(rs.get<int>(5));
|
||||
int modelSuffixLen = sqlite3_column_bytes(q.stmt(), 4);
|
||||
const char *modelSuffix = reinterpret_cast<char const *>(sqlite3_column_text(q.stmt(), 4));
|
||||
RsType type = RsType(rs.get<int>(5));
|
||||
RsEngineSubType subType = RsEngineSubType(rs.get<int>(6));
|
||||
|
||||
item.rsName = rs_utils::formatNameRef(modelName, modelNameLen,
|
||||
number,
|
||||
modelSuffix, modelSuffixLen,
|
||||
type);
|
||||
item.rsName = rs_utils::formatNameRef(modelName, modelNameLen, number, modelSuffix,
|
||||
modelSuffixLen, type);
|
||||
|
||||
item.engineType = RsEngineSubType::Invalid;
|
||||
|
||||
item.time = rs.get<QTime>(7);
|
||||
item.jobId = rs.get<db_id>(8);
|
||||
item.jobCat = JobCategory(rs.get<int>(9));
|
||||
item.time = rs.get<QTime>(7);
|
||||
item.jobId = rs.get<db_id>(8);
|
||||
item.jobCat = JobCategory(rs.get<int>(9));
|
||||
|
||||
switch (type)
|
||||
{
|
||||
|
@ -245,32 +285,72 @@ void RSCoupleDialog::loadProxyModels(sqlite3pp::database& db, db_id jobId, db_id
|
|||
coachModel->loadData(coaches);
|
||||
}
|
||||
|
||||
void RSCoupleDialog::done(int ret)
|
||||
{
|
||||
// Save legend state
|
||||
AppSettings.setShowCouplingLegend(m_showLegend);
|
||||
|
||||
QDialog::done(ret);
|
||||
}
|
||||
|
||||
void RSCoupleDialog::toggleLegend()
|
||||
{
|
||||
if(legend->isVisible())
|
||||
setLegendVisible(!m_showLegend);
|
||||
}
|
||||
|
||||
void RSCoupleDialog::updateButText()
|
||||
{
|
||||
showHideLegendBut->setText(m_showLegend ? tr("Hide legend") : tr("Show legend"));
|
||||
}
|
||||
|
||||
void RSCoupleDialog::setLegendVisible(bool val)
|
||||
{
|
||||
m_showLegend = val;
|
||||
|
||||
if (legend->isVisible() && !m_showLegend)
|
||||
{
|
||||
legend->hide();
|
||||
}else{
|
||||
}
|
||||
else if (m_showLegend && !legend->isVisible())
|
||||
{
|
||||
legend->show();
|
||||
if(!legend->layout())
|
||||
if (!legend->layout())
|
||||
{
|
||||
double fontPt = font().pointSizeF() * 1.2;
|
||||
|
||||
QVBoxLayout *legendLay = new QVBoxLayout(legend);
|
||||
QLabel *label = new QLabel(tr("<p style=\"font-size:13pt\">"
|
||||
"<span style=\"background-color:#FF56FF\">___</span> The item isn't coupled before or already coupled.<br>"
|
||||
"<span style=\"background-color:#FF3d43\">___</span> The item isn't in this station.<br>"
|
||||
"<span style=\"color:#0000FF;background-color:#FFFFFF\">\\\\\\\\</span> Railway line doesn't allow electric traction.<br>"
|
||||
"<span style=\"background-color:#00FFFF\">___</span> First use of this item.<br>"
|
||||
"<span style=\"background-color:#00FF00\">___</span> This item is never used in this session.</p>"));
|
||||
QLabel *label = new QLabel(
|
||||
tr("<style>\n"
|
||||
"table, td {"
|
||||
"border: 1px solid black;"
|
||||
"border-collapse:collapse;"
|
||||
"padding:5px;"
|
||||
" }"
|
||||
"</style>"
|
||||
"<table style=\"font-size:%1pt;padding:10pt\"><tr>"
|
||||
"<td><span style=\"background-color:#FFFFFF\">___</span> This item is free in "
|
||||
"current station.</td>"
|
||||
"<td><span style=\"background-color:#FF3d43\">___</span> The item isn't in this "
|
||||
"station.</td>"
|
||||
"</tr><tr>"
|
||||
"<td><span style=\"background-color:#00FFFF\">___</span> First use of this "
|
||||
"item.</td>"
|
||||
"<td><span style=\"background-color:#FF56FF\">___</span> The item isn't coupled "
|
||||
"before or already coupled.</td>"
|
||||
"</tr><tr>"
|
||||
"<td><span style=\"background-color:#00FF00\">___</span> This item is never used "
|
||||
"in this session.</td>"
|
||||
"<td><span style=\"color:#0000FF;background-color:#FFFFFF\">\\\\\\\\</span> "
|
||||
"Railway line doesn't allow electric traction.</td>"
|
||||
"</tr></table>")
|
||||
.arg(fontPt));
|
||||
label->setTextFormat(Qt::RichText);
|
||||
label->setWordWrap(true);
|
||||
legendLay->addWidget(label);
|
||||
legendLay->setContentsMargins(QMargins());
|
||||
legend->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
|
||||
}
|
||||
}
|
||||
updateButText();
|
||||
adjustSize();
|
||||
}
|
||||
|
||||
void RSCoupleDialog::updateButText()
|
||||
{
|
||||
showHideLegendBut->setText(legend->isVisible() ? tr("Hide legend") : tr("Show legend"));
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef RSCOUPLEDIALOG_H
|
||||
#define RSCOUPLEDIALOG_H
|
||||
|
||||
|
@ -13,20 +32,25 @@ namespace sqlite3pp {
|
|||
class database;
|
||||
}
|
||||
|
||||
//FIXME: on-demand load and filter
|
||||
// FIXME: on-demand load and filter
|
||||
class RSCoupleDialog : public QDialog
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
RSCoupleDialog(RSCouplingInterface *mgr, RsOp o, QWidget *parent = nullptr);
|
||||
|
||||
void loadProxyModels(sqlite3pp::database &db, db_id jobId, db_id stopId, db_id stationId, const QTime &arrival);
|
||||
void loadProxyModels(sqlite3pp::database &db, db_id jobId, db_id stopId, db_id stationId,
|
||||
const QTime &arrival);
|
||||
|
||||
protected:
|
||||
void done(int ret) override;
|
||||
|
||||
private slots:
|
||||
void toggleLegend();
|
||||
|
||||
private:
|
||||
void updateButText();
|
||||
void setLegendVisible(bool val);
|
||||
|
||||
private:
|
||||
RSCouplingInterface *couplingMgr;
|
||||
|
@ -37,6 +61,7 @@ private:
|
|||
|
||||
QPushButton *showHideLegendBut;
|
||||
QWidget *legend;
|
||||
bool m_showLegend;
|
||||
|
||||
RsOp op;
|
||||
};
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "shiftbusydialog.h"
|
||||
#include "shiftbusymodel.h"
|
||||
|
||||
|
@ -15,7 +34,7 @@ ShiftBusyDlg::ShiftBusyDlg(QWidget *parent) :
|
|||
{
|
||||
QVBoxLayout *lay = new QVBoxLayout(this);
|
||||
|
||||
m_label = new QLabel;
|
||||
m_label = new QLabel;
|
||||
lay->addWidget(m_label);
|
||||
|
||||
view = new QTableView;
|
||||
|
@ -38,8 +57,7 @@ void ShiftBusyDlg::setModel(ShiftBusyModel *m)
|
|||
m_label->setText(tr("Cannot set shift <b>%1</b> to job <b>%2</b>.<br>"
|
||||
"The selected shift is busy:<br>"
|
||||
"From: %3 To: %4")
|
||||
.arg(model->getShiftName())
|
||||
.arg(model->getJobName())
|
||||
.arg(model->getStart().toString("HH:mm"))
|
||||
.arg(model->getEnd().toString("HH:mm")));
|
||||
.arg(model->getShiftName(), model->getJobName(),
|
||||
model->getStart().toString("HH:mm"),
|
||||
model->getEnd().toString("HH:mm")));
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHIFTBUSYBOX_H
|
||||
#define SHIFTBUSYBOX_H
|
||||
|
||||
|
@ -17,6 +36,7 @@ public:
|
|||
explicit ShiftBusyDlg(QWidget *parent = nullptr);
|
||||
|
||||
void setModel(ShiftBusyModel *m);
|
||||
|
||||
private:
|
||||
QLabel *m_label;
|
||||
QTableView *view;
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "shiftbusymodel.h"
|
||||
|
||||
#include "utils/jobcategorystrings.h"
|
||||
|
@ -16,9 +35,10 @@ ShiftBusyModel::ShiftBusyModel(sqlite3pp::database &db, QObject *parent) :
|
|||
|
||||
QVariant ShiftBusyModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
|
||||
{
|
||||
switch (section) {
|
||||
switch (section)
|
||||
{
|
||||
case JobCol:
|
||||
return tr("Job");
|
||||
case Start:
|
||||
|
@ -47,7 +67,7 @@ QVariant ShiftBusyModel::data(const QModelIndex &idx, int role) const
|
|||
if (!idx.isValid() || row >= m_data.size() || idx.column() >= NCols)
|
||||
return QVariant();
|
||||
|
||||
const JobInfo& info = m_data.at(row);
|
||||
const JobInfo &info = m_data.at(row);
|
||||
|
||||
switch (role)
|
||||
{
|
||||
|
@ -75,13 +95,13 @@ void ShiftBusyModel::loadData(db_id shiftId, db_id jobId, const QTime &start, co
|
|||
m_data.clear();
|
||||
|
||||
m_shiftId = shiftId;
|
||||
m_jobId = jobId;
|
||||
m_start = start;
|
||||
m_end = end;
|
||||
m_jobId = jobId;
|
||||
m_start = start;
|
||||
m_end = end;
|
||||
|
||||
query q(mDb, "SELECT name FROM jobshifts WHERE id=?");
|
||||
q.bind(1, m_shiftId);
|
||||
if(q.step() != SQLITE_ROW)
|
||||
if (q.step() != SQLITE_ROW)
|
||||
{
|
||||
endResetModel();
|
||||
return;
|
||||
|
@ -99,7 +119,7 @@ void ShiftBusyModel::loadData(db_id shiftId, db_id jobId, const QTime &start, co
|
|||
q.bind(2, m_start);
|
||||
q.bind(3, m_end);
|
||||
q.step();
|
||||
int count = q.getRows().get<int>(0) - 1; //Do not count ourself
|
||||
int count = q.getRows().get<int>(0) - 1; // Do not count ourself
|
||||
m_data.reserve(count);
|
||||
|
||||
q.prepare("SELECT jobs.id, jobs.category,"
|
||||
|
@ -114,20 +134,20 @@ void ShiftBusyModel::loadData(db_id shiftId, db_id jobId, const QTime &start, co
|
|||
q.bind(2, m_start);
|
||||
q.bind(3, m_end);
|
||||
|
||||
for(auto j : q)
|
||||
for (auto j : q)
|
||||
{
|
||||
JobInfo info;
|
||||
info.jobId = j.get<db_id>(0);
|
||||
info.jobId = j.get<db_id>(0);
|
||||
info.jobCat = JobCategory(j.get<int>(1));
|
||||
|
||||
if(info.jobId == m_jobId)
|
||||
if (info.jobId == m_jobId)
|
||||
{
|
||||
m_jobCat = info.jobCat;
|
||||
continue;
|
||||
}
|
||||
|
||||
info.start = j.get<QTime>(3);
|
||||
info.end = j.get<QTime>(4);
|
||||
info.end = j.get<QTime>(4);
|
||||
|
||||
m_data.append(info);
|
||||
}
|
||||
|
|
|
@ -1,3 +1,22 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef SHIFTBUSYMODEL_H
|
||||
#define SHIFTBUSYMODEL_H
|
||||
|
||||
|
@ -11,32 +30,33 @@ namespace sqlite3pp {
|
|||
class database;
|
||||
}
|
||||
|
||||
//TODO: move to shifts subdir
|
||||
// TODO: move to shifts subdir
|
||||
class ShiftBusyModel : public QAbstractTableModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
typedef enum {
|
||||
enum Columns
|
||||
{
|
||||
JobCol = 0,
|
||||
Start,
|
||||
End,
|
||||
NCols
|
||||
} Columns;
|
||||
};
|
||||
|
||||
typedef struct
|
||||
struct JobInfo
|
||||
{
|
||||
db_id jobId;
|
||||
QTime start;
|
||||
QTime end;
|
||||
JobCategory jobCat;
|
||||
} JobInfo;
|
||||
};
|
||||
|
||||
ShiftBusyModel(sqlite3pp::database &db, QObject *parent = nullptr);
|
||||
|
||||
// Header:
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation,
|
||||
int role = Qt::DisplayRole) const override;
|
||||
|
||||
// Basic functionality:
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
@ -44,14 +64,26 @@ public:
|
|||
|
||||
QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
void loadData(db_id shiftId, db_id jobId, const QTime& start, const QTime& end);
|
||||
void loadData(db_id shiftId, db_id jobId, const QTime &start, const QTime &end);
|
||||
|
||||
inline bool hasConcurrentJobs() const { return m_data.size(); }
|
||||
inline bool hasConcurrentJobs() const
|
||||
{
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
inline QTime getStart() const { return m_start; }
|
||||
inline QTime getEnd() const { return m_end; }
|
||||
inline QTime getStart() const
|
||||
{
|
||||
return m_start;
|
||||
}
|
||||
inline QTime getEnd() const
|
||||
{
|
||||
return m_end;
|
||||
}
|
||||
|
||||
inline QString getShiftName() const { return m_shiftName; }
|
||||
inline QString getShiftName() const
|
||||
{
|
||||
return m_shiftName;
|
||||
}
|
||||
QString getJobName() const;
|
||||
|
||||
private:
|
||||
|
|
|
@ -1,34 +1,51 @@
|
|||
/*
|
||||
* ModelRailroadTimetablePlanner
|
||||
* Copyright 2016-2023, Filippo Gentile
|
||||
*
|
||||
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "stopdelegate.h"
|
||||
|
||||
#include "stopeditor.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include "app/session.h"
|
||||
#include "model/stopmodel.h"
|
||||
|
||||
#include "app/scopedebug.h"
|
||||
#include <QPainter>
|
||||
#include <QSvgRenderer>
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
using namespace sqlite3pp;
|
||||
|
||||
#include "app/scopedebug.h"
|
||||
#include <QDebug>
|
||||
|
||||
StopDelegate::StopDelegate(sqlite3pp::database &db, QObject *parent) :
|
||||
QStyledItemDelegate(parent),
|
||||
mDb(db)
|
||||
{
|
||||
renderer = new QSvgRenderer(this);
|
||||
loadIcon(QCoreApplication::instance()->applicationDirPath() + QStringLiteral("/icons/lightning.svg"));
|
||||
refreshPixmaps();
|
||||
}
|
||||
|
||||
void StopDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QRect rect = option.rect.adjusted(5, 5, -5, -5);
|
||||
QRect rect = option.rect.adjusted(5, 5, -5, -5);
|
||||
|
||||
const StopModel *model = static_cast<const StopModel *>(index.model());
|
||||
const StopItem item = model->getItemAt(index.row());
|
||||
const bool isTransit = item.type == StopType::Transit;
|
||||
const StopItem item = model->getItemAt(index.row());
|
||||
const bool isTransit = item.type == StopType::Transit;
|
||||
|
||||
query q(mDb, "SELECT name FROM stations WHERE id=?");
|
||||
q.bind(1, item.stationId);
|
||||
|
@ -39,7 +56,7 @@ void StopDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|||
painter->save();
|
||||
painter->setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
//Draw bottom border
|
||||
// Draw bottom border
|
||||
painter->setPen(QPen(Qt::black, 1));
|
||||
painter->drawLine(option.rect.bottomLeft(), option.rect.bottomRight());
|
||||
|
||||
|
@ -56,107 +73,117 @@ void StopDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|||
|
||||
painter->setBrush(option.palette.text());
|
||||
|
||||
const double top = rect.top();
|
||||
const double bottom = rect.bottom();
|
||||
const double left = rect.left();
|
||||
const double width = rect.width();
|
||||
const double height = rect.height();
|
||||
const double top = rect.top();
|
||||
const double bottom = rect.bottom();
|
||||
const double left = rect.left();
|
||||
const double width = rect.width();
|
||||
const double height = rect.height();
|
||||
|
||||
const double stHeight = top + (isTransit ? 0.0 : height * 0.1);
|
||||
const double timeHeight = top + height * 0.4;
|
||||
const double lineHeight = top + height * 0.65;
|
||||
const double stHeight = top + (isTransit ? 0.0 : height * 0.1);
|
||||
const double timeHeight = top + height * 0.4;
|
||||
const double lineHeight = top + height * 0.65;
|
||||
|
||||
const double arrX = left + width * (isTransit ? 0.4 : 0.2);
|
||||
const double depX = left + width * 0.6;
|
||||
const double lineX = left + (isTransit ? width * 0.1 : 0.0);
|
||||
const double arrX = left + width * (isTransit ? 0.4 : 0.2);
|
||||
const double depX = left + width * 0.6;
|
||||
const double transitLineX = left + width * 0.2;
|
||||
|
||||
|
||||
if(item.addHere == 0)
|
||||
if (item.addHere == 0)
|
||||
{
|
||||
//Draw item
|
||||
//Station name
|
||||
painter->drawText(QRectF(left, stHeight, width, bottom - stHeight),
|
||||
station,
|
||||
// Draw item
|
||||
// Station name
|
||||
painter->drawText(QRectF(left, stHeight, width, bottom - stHeight), station,
|
||||
QTextOption(Qt::AlignHCenter));
|
||||
|
||||
if(item.type != StopType::First)
|
||||
if (item.type != StopType::First)
|
||||
{
|
||||
//Arrival
|
||||
// Arrival
|
||||
painter->drawText(QRectF(arrX, timeHeight, width, bottom - timeHeight),
|
||||
item.arrival.toString("HH:mm"));
|
||||
}
|
||||
|
||||
if(item.type == StopType::First || item.type == StopType::Normal) //Last, Transit don't have a separate departure
|
||||
if (item.type == StopType::First
|
||||
|| item.type == StopType::Normal) // Last, Transit don't have a separate departure
|
||||
{
|
||||
//Departure
|
||||
// Departure
|
||||
painter->drawText(QRectF(depX, timeHeight, width, bottom - timeHeight),
|
||||
item.departure.toString("HH:mm"));
|
||||
}
|
||||
|
||||
if(item.type != StopType::Last && item.nextSegment.segmentId)
|
||||
// Check direction
|
||||
if (item.fromGate.gateConnId && item.toGate.gateConnId && item.type != StopType::First
|
||||
&& item.type != StopType::Last)
|
||||
{
|
||||
//Last has no next segment so do not draw lightning
|
||||
// Ignore First and Last stop (sometimes they have fake in/out gates set which might
|
||||
// trigger this message) Both entry and exit path are set, check direction
|
||||
if (item.fromGate.stationTrackSide == item.toGate.stationTrackSide)
|
||||
{
|
||||
// Train leaves station track from same side of entrance, draw reverse icon
|
||||
QPointF iconTopLeft(left, bottom - PixHeight);
|
||||
painter->drawPixmap(iconTopLeft, m_reverseDirPix);
|
||||
}
|
||||
}
|
||||
|
||||
if (item.type != StopType::Last && item.nextSegment.segmentId)
|
||||
{
|
||||
// Last has no next segment so do not draw lightning
|
||||
|
||||
bool nextSegmentElectrified = model->isRailwayElectrifiedAfterRow(index.row());
|
||||
bool prevSegmentElectrified = !nextSegmentElectrified; //Trigger change on First stop
|
||||
bool prevSegmentElectrified = !nextSegmentElectrified; // Trigger change on First stop
|
||||
|
||||
if(item.type != StopType::First && index.row() >= 0)
|
||||
if (item.type != StopType::First && index.row() >= 0)
|
||||
{
|
||||
//Get real previous railway type
|
||||
// Get real previous railway type
|
||||
prevSegmentElectrified = model->isRailwayElectrifiedAfterRow(index.row() - 1);
|
||||
}
|
||||
|
||||
if(nextSegmentElectrified != prevSegmentElectrified)
|
||||
if (nextSegmentElectrified != prevSegmentElectrified)
|
||||
{
|
||||
//Railway type changed, draw a lightning
|
||||
QSizeF s = QSizeF(renderer->defaultSize()).scaled(width, height / 2, Qt::KeepAspectRatio);
|
||||
QRectF lightningRect(left, top + height / 4, s.width(), s.height());
|
||||
renderer->render(painter, lightningRect);
|
||||
// Railway type changed, draw a lightning
|
||||
QPointF lightningTopLeft(left, top + qMin(5.0, height * 0.1));
|
||||
painter->drawPixmap(lightningTopLeft, m_lightningPix);
|
||||
|
||||
if(!nextSegmentElectrified)
|
||||
if (!nextSegmentElectrified)
|
||||
{
|
||||
//Next railway is not electrified, cross the lightning
|
||||
//Then keep red pen to draw next segment name
|
||||
// Next railway is not electrified, cross the lightning
|
||||
// Then keep red pen to draw next segment name
|
||||
painter->setPen(QPen(Qt::red, 4));
|
||||
painter->drawLine(lightningRect.topLeft(), lightningRect.bottomRight());
|
||||
painter->drawLine(lightningTopLeft,
|
||||
lightningTopLeft + QPointF(PixWidth, PixHeight));
|
||||
}
|
||||
}
|
||||
|
||||
//Draw next segment name
|
||||
// Draw next segment name
|
||||
q.prepare("SELECT name FROM railway_segments WHERE id=?");
|
||||
q.bind(1, item.nextSegment.segmentId);
|
||||
q.step();
|
||||
auto r = q.getRows();
|
||||
auto r = q.getRows();
|
||||
const QString segName = r.get<QString>(0);
|
||||
q.reset();
|
||||
|
||||
const double lineRightX = left + width * 0.8;
|
||||
painter->drawText(QRectF(lineX, lineHeight, lineRightX - left, bottom - lineHeight),
|
||||
tr("Seg: %1").arg(segName),
|
||||
QTextOption(Qt::AlignHCenter));
|
||||
painter->drawText(
|
||||
QRectF(transitLineX, lineHeight, lineRightX - left, bottom - lineHeight),
|
||||
tr("Seg: %1").arg(segName), QTextOption(Qt::AlignHCenter));
|
||||
|
||||
if(item.toGate.trackNum != 0)
|
||||
if (item.toGate.gateTrackNum != 0)
|
||||
{
|
||||
painter->setPen(QPen(Qt::red, 4));
|
||||
painter->drawText(QRectF(lineRightX, lineHeight, left + width - lineRightX, bottom - lineHeight),
|
||||
QString::number(item.toGate.trackNum),
|
||||
QTextOption(Qt::AlignHCenter));
|
||||
painter->drawText(
|
||||
QRectF(lineRightX, lineHeight, left + width - lineRightX, bottom - lineHeight),
|
||||
QString::number(item.toGate.gateTrackNum), QTextOption(Qt::AlignHCenter));
|
||||
}
|
||||
}
|
||||
|
||||
if(isTransit)
|
||||
if (isTransit)
|
||||
{
|
||||
const double transitLinePos = rect.left() + rect.width() * 0.1;
|
||||
painter->setPen(QPen(Qt::red, 5));
|
||||
// Draw a vertical -0- to tell this is a transit
|
||||
painter->setPen(QPen(Qt::red, 4));
|
||||
painter->setBrush(Qt::red);
|
||||
painter->drawLine(QLineF(transitLinePos, rect.top(),
|
||||
transitLinePos, rect.bottom()));
|
||||
painter->drawLine(QLineF(transitLineX, rect.top(), transitLineX, rect.bottom()));
|
||||
|
||||
painter->drawEllipse(QRectF(transitLinePos - 12 / 2,
|
||||
rect.top() + rect.height() * 0.4,
|
||||
12, 12));
|
||||
painter->drawEllipse(
|
||||
QRectF(transitLineX - 12 / 2, rect.top() + rect.height() * 0.4, 12, 12));
|
||||
}
|
||||
|
||||
}
|
||||
else if (item.addHere == 1)
|
||||
{
|
||||
|
@ -170,30 +197,29 @@ void StopDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
|
|||
painter->restore();
|
||||
}
|
||||
|
||||
QSize StopDelegate::sizeHint(const QStyleOptionViewItem &/*option*/,
|
||||
QSize StopDelegate::sizeHint(const QStyleOptionViewItem & /*option*/,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
int w = 200;
|
||||
int h = 100;
|
||||
int w = 200;
|
||||
int h = NormalStopHeight;
|
||||
const StopModel *model = static_cast<const StopModel *>(index.model());
|
||||
if(index.row() < 0 || index.row() >= model->rowCount())
|
||||
return QSize(w, 30);
|
||||
if (index.row() < 0 || index.row() >= model->rowCount())
|
||||
return QSize(w, AddHereHeight);
|
||||
|
||||
const StopItem& item = model->getItemAt(index.row());
|
||||
if(item.type == StopType::Transit)
|
||||
h = 80;
|
||||
if(item.addHere != 0)
|
||||
h = 30;
|
||||
const StopItem &item = model->getItemAt(index.row());
|
||||
if (item.type == StopType::Transit)
|
||||
h = TransitStopHeight;
|
||||
if (item.addHere != 0)
|
||||
h = AddHereHeight;
|
||||
return QSize(w, h);
|
||||
}
|
||||
|
||||
QWidget *StopDelegate::createEditor(QWidget *parent,
|
||||
const QStyleOptionViewItem &/*option*/,
|
||||
QWidget *StopDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem & /*option*/,
|
||||
const QModelIndex &index) const
|
||||
|
||||
{
|
||||
StopModel *model = const_cast<StopModel*>(static_cast<const StopModel *>(index.model()));
|
||||
if(model->isAddHere(index))
|
||||
StopModel *model = const_cast<StopModel *>(static_cast<const StopModel *>(index.model()));
|
||||
if (model->isAddHere(index))
|
||||
{
|
||||
qDebug() << index << "is AddHere";
|
||||
return nullptr;
|
||||
|
@ -201,35 +227,34 @@ QWidget *StopDelegate::createEditor(QWidget *parent,
|
|||
|
||||
StopEditor *editor = new StopEditor(mDb, model, parent);
|
||||
editor->setAutoFillBackground(true);
|
||||
editor->setEnabled(false); //Mark it
|
||||
editor->setEnabled(false); // Mark it
|
||||
|
||||
//Prevent JobPathEditor context menu in table view during stop editing
|
||||
// Prevent JobPathEditor context menu in table view during stop editing
|
||||
editor->setContextMenuPolicy(Qt::PreventContextMenu);
|
||||
|
||||
//See 'StopEditor::popupLinesCombo'
|
||||
// See 'StopEditor::popupLinesCombo'
|
||||
connect(this, &StopDelegate::popupEditorSegmentCombo, editor, &StopEditor::popupSegmentCombo);
|
||||
connect(editor, &StopEditor::nextSegmentChosen, this, &StopDelegate::onEditorSegmentChosen);
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
void StopDelegate::setEditorData(QWidget *editor,
|
||||
const QModelIndex &index) const
|
||||
void StopDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
|
||||
{
|
||||
StopEditor *ed = static_cast<StopEditor*>(editor);
|
||||
if(ed->isEnabled()) //We already set data
|
||||
StopEditor *ed = static_cast<StopEditor *>(editor);
|
||||
if (ed->isEnabled()) // We already set data
|
||||
return;
|
||||
ed->setEnabled(true); //Mark it
|
||||
ed->setEnabled(true); // Mark it
|
||||
|
||||
const StopModel *model = static_cast<const StopModel *>(index.model());
|
||||
|
||||
const StopItem item = model->getItemAt(index.row());
|
||||
const StopItem item = model->getItemAt(index.row());
|
||||
StopItem prev;
|
||||
|
||||
int r = index.row();
|
||||
if(r > 0)
|
||||
if (r > 0)
|
||||
{
|
||||
//Current stop is not First, get also previous one
|
||||
// Current stop is not First, get also previous one
|
||||
prev = model->getItemAt(r - 1);
|
||||
}
|
||||
|
||||
|
@ -242,27 +267,68 @@ void StopDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
|
|||
DEBUG_IMPORTANT_ENTRY;
|
||||
|
||||
qDebug() << "End editing: stop" << index.row();
|
||||
StopEditor *ed = static_cast<StopEditor*>(editor);
|
||||
StopEditor *ed = static_cast<StopEditor *>(editor);
|
||||
StopModel *stopModel = static_cast<StopModel *>(model);
|
||||
|
||||
stopModel->setStopInfo(index, ed->getCurItem(), ed->getPrevItem().nextSegment);
|
||||
bool avoidTimeRecalc = QGuiApplication::keyboardModifiers().testFlag(Qt::ShiftModifier);
|
||||
|
||||
stopModel->setStopInfo(index, ed->getCurItem(), ed->getPrevItem().nextSegment, avoidTimeRecalc);
|
||||
}
|
||||
|
||||
void StopDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const
|
||||
void StopDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
|
||||
const QModelIndex & /*index*/) const
|
||||
{
|
||||
editor->setGeometry(option.rect);
|
||||
}
|
||||
|
||||
void StopDelegate::loadIcon(const QString &fileName)
|
||||
{
|
||||
renderer->load(fileName);
|
||||
}
|
||||
|
||||
void StopDelegate::onEditorSegmentChosen(StopEditor *editor)
|
||||
{
|
||||
if(editor->closeOnSegmentChosen())
|
||||
if (editor->closeOnSegmentChosen())
|
||||
{
|
||||
emit commitData(editor);
|
||||
emit closeEditor(editor, StopDelegate::EditNextItem);
|
||||
}
|
||||
}
|
||||
|
||||
void StopDelegate::refreshPixmaps()
|
||||
{
|
||||
const QString iconPath =
|
||||
QCoreApplication::instance()->applicationDirPath() + QStringLiteral("/icons");
|
||||
|
||||
// Square pixmaps
|
||||
m_lightningPix = QPixmap(PixWidth, PixHeight);
|
||||
m_reverseDirPix = QPixmap(PixWidth, PixHeight);
|
||||
|
||||
m_lightningPix.fill(Qt::transparent);
|
||||
m_reverseDirPix.fill(Qt::transparent);
|
||||
|
||||
QSvgRenderer mSvg;
|
||||
QPainter painter;
|
||||
QRectF iconRect;
|
||||
|
||||
// Cache Lightning
|
||||
mSvg.load(iconPath + QStringLiteral("/lightning.svg"));
|
||||
|
||||
// Scale SVG to fit requested size
|
||||
iconRect.setSize(mSvg.defaultSize().scaled(PixWidth, PixHeight, Qt::KeepAspectRatio));
|
||||
|
||||
// Center on pixmap
|
||||
iconRect.moveTop((PixHeight - iconRect.height()) / 2);
|
||||
iconRect.moveLeft((PixWidth - iconRect.width()) / 2);
|
||||
painter.begin(&m_lightningPix);
|
||||
mSvg.render(&painter, iconRect);
|
||||
painter.end();
|
||||
|
||||
// Cache Reverse Direction
|
||||
mSvg.load(iconPath + QStringLiteral("/reverse_direction.svg"));
|
||||
|
||||
// Scale SVG to fit requested size
|
||||
iconRect.setSize(mSvg.defaultSize().scaled(PixWidth, PixHeight, Qt::KeepAspectRatio));
|
||||
|
||||
// Center on pixmap
|
||||
iconRect.moveTop((PixHeight - iconRect.height()) / 2);
|
||||
iconRect.moveLeft((PixWidth - iconRect.width()) / 2);
|
||||
painter.begin(&m_reverseDirPix);
|
||||
mSvg.render(&painter, iconRect);
|
||||
painter.end();
|
||||
}
|
||||
|
|