Compare commits
1193 Commits
old_layout
...
master
Author | SHA1 | Date |
---|---|---|
|
130b4312c4 | |
|
1aa23f281a | |
|
9f14ee1c64 | |
|
42c486f5cf | |
|
21a54888f2 | |
|
05c45381d4 | |
|
87ec3e53ce | |
|
7ac337a181 | |
|
4e6f49dd44 | |
|
efd60f9ce6 | |
|
48faf57a93 | |
|
90ca41cb55 | |
|
9abb3b014c | |
|
6323a87ff0 | |
|
9bf0027fe3 | |
|
6923642a53 | |
|
5d643196e0 | |
|
62ee86b8b5 | |
|
585476240f | |
|
a4393aec43 | |
|
02f4938396 | |
|
49c7f60c94 | |
|
7fb7271745 | |
|
1cbc5e38a2 | |
|
f60f9ed11d | |
|
1b65e413de | |
|
3847853913 | |
|
8c61554a5d | |
|
4e622d0841 | |
|
0149217499 | |
|
e5fc856c10 | |
|
ecf934bf86 | |
|
5f165502f8 | |
|
232f4e1dcc | |
|
576a79e065 | |
|
c61fac42b7 | |
|
1618cc47ad | |
|
6dacc44880 | |
|
3b969aafce | |
|
77dcb5d0de | |
|
9383f4a922 | |
|
a352eafdbb | |
|
b2a8e04faf | |
|
66442b13cb | |
|
706d9dadb4 | |
|
de54030785 | |
|
0c04121867 | |
|
017005e1a9 | |
|
8667ea96fe | |
|
3672395be5 | |
|
a53c8948fe | |
|
0273501500 | |
|
b951cedbc9 | |
|
f352555266 | |
|
f0ca606bf2 | |
|
af337e3065 | |
|
214f08dc3e | |
|
fca21eeae6 | |
|
cb20814a69 | |
|
43658c227a | |
|
3df21ef200 | |
|
5e94d9b603 | |
|
a3db250463 | |
|
98cd6f2702 | |
|
58523ed724 | |
|
04a148b9ba | |
|
cfc705a009 | |
|
b2bbf975f8 | |
|
79e609b720 | |
|
78bf2166f9 | |
|
8583684780 | |
|
e83d8f601e | |
|
8e2f7c545b | |
|
4360e4b5b1 | |
|
140d076651 | |
|
7728133a95 | |
|
1438c4b118 | |
|
774d4497c5 | |
|
eb7b773fc4 | |
|
42a468a1b5 | |
|
1bc031cae6 | |
|
0a5d02b862 | |
|
d161b87859 | |
|
fede5a826d | |
|
f6148cf0ba | |
|
f626d9ff8d | |
|
90f6669614 | |
|
76c774554e | |
|
0b34512f2f | |
|
9727d68493 | |
|
6bb011d96b | |
|
b7e1f7284a | |
|
dfc856aabe | |
|
042644c00a | |
|
0497d6442f | |
|
0c1d9db3bf | |
|
cbebaf5204 | |
|
6a3281b4f1 | |
|
5608f0bc28 | |
|
36bfe821f1 | |
|
b1f202c1cd | |
|
38e1248f60 | |
|
b8483827ae | |
|
2a3496f0bc | |
|
63335f7613 | |
|
7a3a34a1ac | |
|
816a0830d9 | |
|
1c7d288632 | |
|
3ef0a142ef | |
|
783683b232 | |
|
573dfac662 | |
|
82c154fca2 | |
|
d185c0016e | |
|
48d7180cd5 | |
|
d3d6e099b6 | |
|
4c25de9133 | |
|
f1fc42dd9b | |
|
1816b6292c | |
|
c04d77d004 | |
|
e536d1897f | |
|
ba97509c2b | |
|
1aa479afcd | |
|
0f0aa2c94e | |
|
80630fbb2a | |
|
b48372faa7 | |
|
e0e22c8830 | |
|
11637625f8 | |
|
a005f0f49e | |
|
1c4d27351d | |
|
7fe4e130ea | |
|
50a2f94233 | |
|
b533c5dfe6 | |
|
3ad2ab563f | |
|
681220732c | |
|
5ccf083911 | |
|
95c1633103 | |
|
4dd467212c | |
|
97cd886109 | |
|
d25897424e | |
|
3427248703 | |
|
7e12edba26 | |
|
e749e82eb4 | |
|
a622a49bdc | |
|
5313a0f287 | |
|
d28c2b868b | |
|
83e60d26a1 | |
|
8d5c280663 | |
|
4e5463173e | |
|
6bde7076ca | |
|
ec7897806f | |
|
cc0770501d | |
|
d407eb98f0 | |
|
bdd6be0886 | |
|
2d3336a14f | |
|
147b532395 | |
|
f5279788ce | |
|
539f07472f | |
|
ea67bc93a8 | |
|
04c0b5cdfd | |
|
53421d4507 | |
|
a2a487fad3 | |
|
ed06754520 | |
|
34b277e6f4 | |
|
fe23e91071 | |
|
aceaf41475 | |
|
69318af831 | |
|
c02c523433 | |
|
ce1d6329d3 | |
|
b36e260ab4 | |
|
163dd176bc | |
|
1c26d4f771 | |
|
98eec5abeb | |
|
85da6c49f2 | |
|
d85cfe3d62 | |
|
c4e24b954c | |
|
d095abacd5 | |
|
a3eb525e84 | |
|
d1df52524a | |
|
967a7aefd4 | |
|
2688c075c7 | |
|
bb4983f9eb | |
|
1f89e8e0e6 | |
|
5d7975a71e | |
|
a6af65e93e | |
|
add51736d4 | |
|
0d3e07050b | |
|
04f7123cf2 | |
|
adeca33348 | |
|
0ebc219cf7 | |
|
682bb816d9 | |
|
2a8922c34b | |
|
2358f4296a | |
|
1e1478ebd0 | |
|
13ae3f5450 | |
|
5869fa3f3f | |
|
613088b68d | |
|
6b15cd6402 | |
|
3a9451a55c | |
|
2f58a64f2a | |
|
851520ef86 | |
|
bd1f4efbdb | |
|
ce30ac0edc | |
|
1c0646eb1a | |
|
db70886108 | |
|
3d25dc8171 | |
|
d9bfe81cfd | |
|
cf40959137 | |
|
09ae6d932f | |
|
eb6c94e59b | |
|
ce689d5250 | |
|
06e72ff1e7 | |
|
f4878cafb7 | |
|
3b532959a0 | |
|
d4c22f3d94 | |
|
55ede00bd5 | |
|
1cc32a1913 | |
|
63e94bfdab | |
|
eaf8773004 | |
|
35b62322b2 | |
|
9b88e8cc1c | |
|
472618ca80 | |
|
5881f9135e | |
|
91fd9abf1f | |
|
a6cae27f8b | |
|
36df87b012 | |
|
e83d1d73f0 | |
|
5979af91df | |
|
5eaabb9174 | |
|
de2f8dd414 | |
|
8d05b55732 | |
|
01fe4d938a | |
|
b142d3f537 | |
|
0226e08b0f | |
|
3d9d1e9b4b | |
|
159d046773 | |
|
9c595279b2 | |
|
1cd0b19536 | |
|
9e541c7986 | |
|
892bc9a6cf | |
|
73e5e6e179 | |
|
5e56dc4b39 | |
|
71f1e32c92 | |
|
40aff7d14a | |
|
4d88c236e0 | |
|
f46fe790d3 | |
|
9e56c4f55e | |
|
06e929d1eb | |
|
9562a667eb | |
|
f0ed0bcaf6 | |
|
ff475f3eb8 | |
|
3c27da07bb | |
|
67ee46b9fb | |
|
0f76aea14e | |
|
4c2da6c816 | |
|
ec52f3f833 | |
|
9b566b2e8a | |
|
f5e2ce1ff4 | |
|
10734e0a4c | |
|
db73ff693d | |
|
824cb2100b | |
|
e45b6d0eab | |
|
a361918694 | |
|
7ae34b3160 | |
|
2b263968c5 | |
|
63e5a33240 | |
|
51a6325797 | |
|
1648900cde | |
|
9e7f7f2a91 | |
|
42f780a017 | |
|
1aee704e6c | |
|
e901df125b | |
|
4a2dcac484 | |
|
4dded56bf1 | |
|
08e15fed81 | |
|
c7d222fcf7 | |
|
6d9d3ebe80 | |
|
2f7ccfc97c | |
|
02dcf9edf9 | |
|
55cbb8aaa1 | |
|
5a400cf736 | |
|
00245da29b | |
|
7308c49f2d | |
|
18c58ab3f1 | |
|
e3bce19a14 | |
|
6c6b5e97b4 | |
|
d5c39a1492 | |
|
59db245b7c | |
|
d3754d0122 | |
|
5163dc7d66 | |
|
8e2a5bc979 | |
|
52c289000d | |
|
74088c8602 | |
|
432053e59b | |
|
788e2e05dc | |
|
d9ea922cf5 | |
|
aef7cfd1ee | |
|
09aaadaff0 | |
|
eaa0f0d372 | |
|
8a25c3f390 | |
|
c7a472e309 | |
|
d177ab3f6a | |
|
d8da91af97 | |
|
28e8c89216 | |
|
c2cc58f439 | |
|
36bd2d6a68 | |
|
e99ede2470 | |
|
f3b8ea166b | |
|
20c931276f | |
|
c2f2671277 | |
|
c20bab48ef | |
|
fbb3c241cb | |
|
964a8df501 | |
|
d8b1e1ab3d | |
|
3fe400d126 | |
|
9f5cd87a98 | |
|
15c5da8802 | |
|
a884a4340a | |
|
f22d3e656f | |
|
cc23a26f96 | |
|
1fd61d179c | |
|
b4fbc5c661 | |
|
a186023ad0 | |
|
657407cda1 | |
|
12587f733e | |
|
098f1838a8 | |
|
063d4f02bc | |
|
79eb82f758 | |
|
7352e0ca05 | |
|
2cd423aff1 | |
|
7e18451f2f | |
|
a30b5642b5 | |
|
d43cafbc3a | |
|
d0e1e65553 | |
|
9fb9ff6e1a | |
|
0a5cd5cd96 | |
|
5fae5bc678 | |
|
96f9a959d0 | |
|
a72c161cca | |
|
6488fcdcd8 | |
|
635042b360 | |
|
cce4b327be | |
|
97519d311a | |
|
4b30b2af89 | |
|
7242fbe0ee | |
|
ef9ce4c950 | |
|
8ff1d35ac5 | |
|
afdcbc4ad4 | |
|
bd248ca2b9 | |
|
27d74dce1e | |
|
0d9103c67b | |
|
734e8244e2 | |
|
ea764842ad | |
|
f4dd194853 | |
|
915d4376c3 | |
|
06d345c4c7 | |
|
a149d82a53 | |
|
3fce3f145d | |
|
0e2e253222 | |
|
f85c462fff | |
|
8584ae1c82 | |
|
8a1233a90b | |
|
6dd5ac5331 | |
|
f2fd243deb | |
|
a97ad5753b | |
|
d93ee3e1b9 | |
|
823be54714 | |
|
cfa5e66c94 | |
|
5060518140 | |
|
eb97bcab68 | |
|
4abfff007c | |
|
061602469f | |
|
13aec23cc6 | |
|
c4063b3a8b | |
|
94029a19ee | |
|
fb8fbf838e | |
|
e63fda2235 | |
|
c7cdc60c0c | |
|
01e290850c | |
|
e519a69b4f | |
|
babe30d30d | |
|
861c7affde | |
|
f6f558f325 | |
|
e868e4e20c | |
|
198608fee6 | |
|
efa6213ace | |
|
cd016c9d47 | |
|
31733992d3 | |
|
5ca7d0fbfe | |
|
1a0ee27a76 | |
|
9879623f03 | |
|
368e0d3f7f | |
|
1760d23ccc | |
|
a202702f31 | |
|
ba2c55ab70 | |
|
a6e0e46c81 | |
|
b5bcbafec8 | |
|
4b91b76e50 | |
|
1fb67335a7 | |
|
fd428e0361 | |
|
39b773a118 | |
|
191766ae62 | |
|
2c123543f9 | |
|
c8a580d14b | |
|
f7a38e8bce | |
|
5208e05c59 | |
|
8d84dd6046 | |
|
7cb17b7601 | |
|
2b191d152c | |
|
984f706c0f | |
|
b84c2c80ae | |
|
2ec77863de | |
|
5d05c48de8 | |
|
a95a866c95 | |
|
b9befd4105 | |
|
1a9acfd01d | |
|
70e6d07e27 | |
|
c85a213370 | |
|
0921eff096 | |
|
1cea37d58a | |
|
1af024ac42 | |
|
25ba2db404 | |
|
0f5c3c5f60 | |
|
b5c7e26ef4 | |
|
d2e510e487 | |
|
853a7ed2eb | |
|
34fc6e876a | |
|
92262ca5a9 | |
|
0391922b36 | |
|
1525ffcab5 | |
|
51003a74dc | |
|
c6289bd8a4 | |
|
188385bf46 | |
|
b8b95b7d19 | |
|
ad5a3efe02 | |
|
9c9dd1ef47 | |
|
7307961be5 | |
|
8f0bf71e63 | |
|
4ab4393c1f | |
|
d799e313f2 | |
|
96327de241 | |
|
6713df39f9 | |
|
c2c77c3d43 | |
|
585cd69141 | |
|
c8c3e7bcd9 | |
|
94129131a6 | |
|
ee4a37e72f | |
|
82743ec76f | |
|
8adf8a1337 | |
|
7c6d183e4d | |
|
17e536565f | |
|
096c463f8c | |
|
56c2d87194 | |
|
d5cbc47219 | |
|
9397c8ad3b | |
|
9321624201 | |
|
1127f1c004 | |
|
52cedb8f69 | |
|
5e785e2a80 | |
|
62ed7db377 | |
|
1ee66fb438 | |
|
88123d3da5 | |
|
f95be9d9d9 | |
|
21d473ffbb | |
|
c18f1b809c | |
|
7804d31fb3 | |
|
e9e7cd90f4 | |
|
0ba9cc02ac | |
|
afcb93aaf9 | |
|
0bf16b2481 | |
|
a3f1b4086c | |
|
a1b9d83110 | |
|
95ad2d4a0d | |
|
a4691a904d | |
|
1f1649138a | |
|
c2d11f64a5 | |
|
a78ebbc826 | |
|
c92a415a59 | |
|
ff00ae9dd0 | |
|
4a1f36feec | |
|
a207a97053 | |
|
1718479bab | |
|
4631802ed8 | |
|
4192e71466 | |
|
c4779d7a77 | |
|
cb7a778cab | |
|
83356a0a67 | |
|
18be558673 | |
|
9cf172dcf4 | |
|
4e1aa236aa | |
|
7fc79c63ea | |
|
4610fa9edf | |
|
2fd7705b82 | |
|
e0cd632da8 | |
|
fca049f179 | |
|
ba2a67c043 | |
|
18c7557fa0 | |
|
58e5501620 | |
|
7e2048b331 | |
|
c5bb89e862 | |
|
cb78c456d8 | |
|
816e3082b5 | |
|
af5d3c7351 | |
|
989557033e | |
|
0dd0b9caab | |
|
60d9205c57 | |
|
dbb370a526 | |
|
8c97372fb6 | |
|
62a6b218fe | |
|
89b1d13203 | |
|
f112c4a350 | |
|
4c7265a2d8 | |
|
06d8afd9a0 | |
|
b665770522 | |
|
6d5a6a4af7 | |
|
999b21b204 | |
|
7aec8834b9 | |
|
d18f271dbe | |
|
3d03d53d79 | |
|
792f5aa76b | |
|
1819327660 | |
|
360fc4d960 | |
|
84c922962a | |
|
7fa2f0bb3e | |
|
47acae35c3 | |
|
2142bf7b59 | |
|
d2e8168e6a | |
|
ac33fff6df | |
|
3e366d5d78 | |
|
31339c81c8 | |
|
2dab6b8fa4 | |
|
6a00ecb9b2 | |
|
53196f6a28 | |
|
d08a8cdbed | |
|
ee9d566f02 | |
|
ffdc3aeeef | |
|
f43ddc52a8 | |
|
18e672c8c5 | |
|
d33324d704 | |
|
e96a2df910 | |
|
3b90bee8f7 | |
|
e60e2eacc8 | |
|
60e1c77d1f | |
|
66b15c33e1 | |
|
47278767f5 | |
|
b32cb4a5f6 | |
|
18c1a596c8 | |
|
5bd3f4d664 | |
|
9297f4d205 | |
|
6f84774d7f | |
|
c79eaeb66d | |
|
48d91c87e2 | |
|
195c4c6186 | |
|
063f0840ff | |
|
251194323d | |
|
3a50406c60 | |
|
184433331b | |
|
4c5150dc1e | |
|
7b15248a89 | |
|
2119ae07b4 | |
|
5edc489c47 | |
|
a00b5dec9d | |
|
480029309a | |
|
3e81dbf4a9 | |
|
f818046ea1 | |
|
d615542367 | |
|
1e8ac30101 | |
|
e16b639657 | |
|
ff74c05165 | |
|
3011a0a831 | |
|
d6d9d1db0c | |
|
b22cbd9128 | |
|
0a30c2d3c4 | |
|
e74cf4b73a | |
|
3ecd70c1b1 | |
|
3f83a581f1 | |
|
7556b1bbbc | |
|
d9396b90bd | |
|
274c03ae10 | |
|
a395fe8c0e | |
|
790cdec50e | |
|
69bcedbb0d | |
|
47abd018d1 | |
|
a2eab58e78 | |
|
b4dd98864b | |
|
1a26cd8b28 | |
|
49ba6d7437 | |
|
1225dd38ce | |
|
83bd0e314b | |
|
5d2f3e06c0 | |
|
bcccdf57b4 | |
|
217c1c6952 | |
|
64984b1c2b | |
|
42b6aa629d | |
|
6580fe5c31 | |
|
0aedffc089 | |
|
4fc2fa150b | |
|
ffd831feaa | |
|
73fbec395e | |
|
bbd0966487 | |
|
13d4d9f845 | |
|
389e66d51e | |
|
eea39e9204 | |
|
3955129f7a | |
|
215dc2881c | |
|
b43db5bbae | |
|
0ffa6e9fe4 | |
|
6ccb9c32ef | |
|
7cb0c944cf | |
|
00dd49a54d | |
|
aa127229c9 | |
|
d63648ba0b | |
|
1a4c9b40dd | |
|
40267968c7 | |
|
1ee033b165 | |
|
814dfb4aab | |
|
2b93b5597f | |
|
ec78cdfde7 | |
|
9c6c01b1db | |
|
e45ac59bd3 | |
|
54f19c494b | |
|
6b045e9dee | |
|
a3b3625b1c | |
|
845fa5eb8e | |
|
989b423b2d | |
|
9fbfc59d15 | |
|
c88fbd4135 | |
|
7b26f40fd6 | |
|
c8057c3b4e | |
|
4ae39ea8da | |
|
d5ea9ebc3d | |
|
488945abd9 | |
|
b43fbe07f0 | |
|
222089931a | |
|
40c9816b60 | |
|
ec71cf956d | |
|
779a5914b6 | |
|
635ca16499 | |
|
a45e0712d2 | |
|
3025d29467 | |
|
b5d4a9acfd | |
|
0cca7a0838 | |
|
2c215ab362 | |
|
b0ca3bec24 | |
|
d73cec4a63 | |
|
8b11ea1aab | |
|
748fc8e0a5 | |
|
b6beb19f13 | |
|
7a67d5b446 | |
|
fd9ffe4d09 | |
|
06ee85eaca | |
|
020819dd46 | |
|
ab2773adb4 | |
|
deadbe8a7c | |
|
d0039d1760 | |
|
e46f35a9a1 | |
|
fd90fbc188 | |
|
6e3917824c | |
|
336c3c9f68 | |
|
acccf2df00 | |
|
5a2d10c885 | |
|
ac4b11e084 | |
|
dff063ac93 | |
|
59d14b1759 | |
|
3c0f4ec113 | |
|
daff553e1b | |
|
c94496c030 | |
|
2a64622489 | |
|
17246ba99f | |
|
0b0e1de6a4 | |
|
00d3cbca4c | |
|
079ef099ae | |
|
4e27294446 | |
|
e017f8e9d2 | |
|
192b6432a7 | |
|
13573d4a56 | |
|
911724e6c8 | |
|
3caa54f84f | |
|
9f63c1c570 | |
|
d004ae2527 | |
|
0a665385d8 | |
|
7ca5765873 | |
|
3db6cb896e | |
|
c7cba33a5f | |
|
a1b473399e | |
|
35ac28759a | |
|
aa223182ba | |
|
e01ce21ab4 | |
|
246533feff | |
|
2c5f84b922 | |
|
5a4a6bcc0b | |
|
7cea95eb58 | |
|
7deb58c377 | |
|
734be8b34c | |
|
759fe09e34 | |
|
75145c5e90 | |
|
80f1c1ba97 | |
|
4a2ec7ecfc | |
|
170e234e62 | |
|
06e04a142c | |
|
d38898ee8e | |
|
25abd16e23 | |
|
9395434918 | |
|
162f3acabc | |
|
aee947aa79 | |
|
ab40eda802 | |
|
1a070c628f | |
|
18aea4a02c | |
|
a757cc4e00 | |
|
e575f4bc6e | |
|
b8a1a94194 | |
|
baed3f75d1 | |
|
8c42d1222b | |
|
41361a35d7 | |
|
e7e1778d2e | |
|
b1561dd692 | |
|
e14fec00c5 | |
|
5c54358ddf | |
|
3b3ac6b140 | |
|
87d270d299 | |
|
3940fa0d81 | |
|
e8ff9800a1 | |
|
9edf582aa7 | |
|
ad78dae306 | |
|
ffc323c991 | |
|
b2b7568453 | |
|
e5d4712816 | |
|
e6968fd366 | |
|
638282f5ff | |
|
3c71578df4 | |
|
51f7e63528 | |
|
9787644278 | |
|
c7e9627bd4 | |
|
64b7390fb7 | |
|
99bddaeb11 | |
|
b713517a7b | |
|
7104c596f3 | |
|
7c6fe5c999 | |
|
e0a9428816 | |
|
f165928a57 | |
|
60725206f7 | |
|
9f5777cbbd | |
|
82c77f7dd1 | |
|
be74a2d53f | |
|
38c4f2ade4 | |
|
1981a876d0 | |
|
fa4ef443e2 | |
|
c61a8c5fbe | |
|
bc111d78f2 | |
|
328df9ddfa | |
|
4db438fcd1 | |
|
d701528fac | |
|
535aa3d802 | |
|
4466278a3a | |
|
42261fd663 | |
|
925b860f8c | |
|
9fd0b36ae8 | |
|
2b16295eba | |
|
5c187ad389 | |
|
46b68df652 | |
|
b4176b1ba4 | |
|
082f3e3715 | |
|
5a12eb7229 | |
|
8b88832140 | |
|
bdee972a47 | |
|
6901e906a2 | |
|
e8e0fa2adc | |
|
f898dbc3f4 | |
|
656edd5a74 | |
|
d098c70c0f | |
|
abfa3b7983 | |
|
a43baa3126 | |
|
eb1d798212 | |
|
7d140c0e31 | |
|
15adfd545a | |
|
6b8d815af5 | |
|
f939acb134 | |
|
32c00ee77b | |
|
3c24fc7d0e | |
|
549ccf43e3 | |
|
621da16076 | |
|
4173155df5 | |
|
5a579fd403 | |
|
d4f5eff5cf | |
|
0e4401da92 | |
|
0b24216db7 | |
|
9ae91f5633 | |
|
8111753315 | |
|
7ef5498f9d | |
|
54cd17276b | |
|
a57ebcb36a | |
|
058d0fc2ab | |
|
590cb2e335 | |
|
bb98e2a4f9 | |
|
4ffd245f94 | |
|
ba37234b63 | |
|
2f611ed1c7 | |
|
2d3db05020 | |
|
39678fbdc9 | |
|
9bcfd591a7 | |
|
112c9dd028 | |
|
219ec6db11 | |
|
707e4b4649 | |
|
10a01a9cc9 | |
|
8069a51400 | |
|
36ec641ded | |
|
fe3a84b8ed | |
|
8c3b5a2c9e | |
|
ac46bb0768 | |
|
70a0c53b31 | |
|
8b8bf0685f | |
|
eeaadcd748 | |
|
9d39b6f3c6 | |
|
f4ab5d9439 | |
|
7074e66216 | |
|
98ecc5561e | |
|
77ea3b6661 | |
|
e67061013c | |
|
b54512045e | |
|
5e1d19904e | |
|
3ae46826d1 | |
|
d7634f7851 | |
|
9db2b7fcde | |
|
dd2e3d0852 | |
|
c322ad5358 | |
|
be3e18da2a | |
|
0c58f82ea4 | |
|
a3e3c4ed5d | |
|
e51a2f0c4c | |
|
e012561efb | |
|
d2bbf4a320 | |
|
3f781e1be3 | |
|
9cd01a2262 | |
|
ed7a03f003 | |
|
355f76a7e7 | |
|
898a6e3ae3 | |
|
19a6b9f6b9 | |
|
50afaa744a | |
|
71d1dceaa8 | |
|
4f07094379 | |
|
29131938b1 | |
|
0e99960d86 | |
|
fa95d8fb5e | |
|
79acafde1b | |
|
4445d45126 | |
|
cf660faa37 | |
|
49f427d206 | |
|
7e678393d0 | |
|
5a049166e3 | |
|
cfacea0caa | |
|
0279fe0856 | |
|
35792d0ee5 | |
|
5896ca8cfe | |
|
832f2105ec | |
|
fa501cbd76 | |
|
2c262728ad | |
|
3db4379f84 | |
|
6c5380153f | |
|
b21b9298ea | |
|
5fbe29469a | |
|
4e9f81e2c4 | |
|
fb527db6af | |
|
db2323faba | |
|
8d2ca01dc0 | |
|
0bde072585 | |
|
fbd469e7e6 | |
|
b7be5a3446 | |
|
37f95fae3e | |
|
0c7d65f198 | |
|
f414477cb2 | |
|
4c050b4d7a | |
|
10588aecd5 | |
|
46312eec48 | |
|
8bc3fdadd1 | |
|
bf17a3d7ab | |
|
c2404ae81f | |
|
aba10c10ef | |
|
005e9cce6b | |
|
e74b5eec52 | |
|
c2835a7ae6 | |
|
f5fc4fdd09 | |
|
95b8f2417e | |
|
cf2f579c25 | |
|
577430cfcd | |
|
9c37bc4baf | |
|
1ca17b3e77 | |
|
dc72d6712a | |
|
b487b8b2b4 | |
|
36cb11f08b | |
|
0bdd72aff3 | |
|
341aac3c1c | |
|
1135393616 | |
|
a0080c7846 | |
|
d59f40de77 | |
|
9ca9d7f79d | |
|
b9b168bb10 | |
|
113d8071e9 | |
|
f2dbc66b2b | |
|
a921e8f9cf | |
|
e3c7b222a4 | |
|
717a96f0fa | |
|
d7c461143e | |
|
b9d65dc724 | |
|
74502cdcda | |
|
0bab5fcca1 | |
|
d2bc4a3296 | |
|
5f63abf811 | |
|
3c1e4c2a3d | |
|
bff1990e39 | |
|
a5afa855be | |
|
c5ef978757 | |
|
8262507040 | |
|
57abe1f810 | |
|
9dc679f68f | |
|
ddf0ea5642 | |
|
363a76d9c7 | |
|
cc08fd7822 | |
|
fbadf1cc7c | |
|
4699fc85c7 | |
|
76c7fc01cd | |
|
ec0d9499eb | |
|
b4e5de5b1e | |
|
56db0b40af | |
|
8325da8959 | |
|
49afab64d5 | |
|
2850ad58f9 | |
|
64fc188e65 | |
|
b44831f5d9 | |
|
8e5bb6ddf5 | |
|
5b55670f3b | |
|
d2c77a1230 | |
|
d2680abf97 | |
|
e76b632715 | |
|
eb21b0ded1 | |
|
a7929f5a55 | |
|
121d48092f | |
|
4d95550493 | |
|
d0faf43432 | |
|
be1a770087 | |
|
75adbf8c31 | |
|
62185c5018 | |
|
4ce6e9372c | |
|
94e8250745 | |
|
456cc1a544 | |
|
7f1a0bbdbc | |
|
28194fc0c9 | |
|
a6e7da2395 | |
|
e15a29283e | |
|
6918fb0b10 | |
|
3b8d4f1cc6 | |
|
6e954c4585 | |
|
433216d666 | |
|
04159bbca2 | |
|
d3c7d5057c | |
|
4903486b46 | |
|
294db0a153 | |
|
d034e015c3 | |
|
c6de565034 | |
|
23ec490220 | |
|
21d8822772 | |
|
1477520955 | |
|
179bce8779 | |
|
f6740539c9 | |
|
f4f2698176 | |
|
09e77c3fdd | |
|
5b80b89e4c | |
|
cf49d6101e | |
|
bfc6138725 | |
|
9a160df919 | |
|
1fbdb7c64c | |
|
944955751c | |
|
ac67f4f40f | |
|
e8c49130cf | |
|
404796d02e | |
|
ad00c6da7f | |
|
5ba6d1f1df | |
|
b38e98ff0a | |
|
b6d208f939 | |
|
39081f8a4f | |
|
864e9a158c | |
|
3efaeec029 | |
|
9e279119c6 | |
|
c4000267a3 | |
|
fe5fa9994e | |
|
26568cb6a8 | |
|
3bf45b00d8 | |
|
887be8275a | |
|
4ff710f305 | |
|
36c5c3c6c2 | |
|
c6e0027edf | |
|
cdfb447800 | |
|
77d1410b4e | |
|
95a5aa0391 | |
|
979c181c2a | |
|
35b86a6696 | |
|
76e78877df | |
|
451bd9c488 | |
|
3f4e8c28b5 | |
|
0ddafd2af8 | |
|
78a725c5b2 | |
|
26c3ea47c7 | |
|
2dee27b9c5 | |
|
ae3bb8b7ac | |
|
32f86532d7 | |
|
c8b1a7cd06 | |
|
41e373b43e | |
|
d0b7766e8d | |
|
6d0e50b5d5 | |
|
1bd9085de9 | |
|
9ea6856a25 | |
|
efc92a8dd0 | |
|
f8c3b5e43a | |
|
8ba629b328 | |
|
31fa2cef29 | |
|
d135b11a68 | |
|
bf5b5c7005 | |
|
3453383823 | |
|
271c9a4321 | |
|
15a2907a00 | |
|
64cc4d57e1 | |
|
51a8f10ae5 | |
|
5bbde8823c | |
|
083beccf23 | |
|
28950dc11e | |
|
0fd080d2a6 | |
|
a664790141 | |
|
1e1ab7f92b | |
|
c4c130d62f | |
|
47e91567c1 | |
|
537f53da7f | |
|
b2c0be9149 | |
|
a5be3f61b4 | |
|
9dfcaa4ec6 | |
|
2a452ae8cf | |
|
f53e4b3340 | |
|
bd497c89ff | |
|
9981b07f0d | |
|
b4fcd30bd8 | |
|
af9cc5d880 | |
|
a10e35f9c7 | |
|
32b4001f24 | |
|
5514ebcaca | |
|
40204e8b61 | |
|
3ade15071f | |
|
38078f4f3c | |
|
5bb553fa27 | |
|
ff2942698b | |
|
160b79fb8d | |
|
f9d72cc8d0 | |
|
a66c036650 | |
|
f748d3b0c8 | |
|
c072ca8fd5 | |
|
2266dbf6ec | |
|
06d1c0802a | |
|
4220cc2713 | |
|
fd3640afc6 | |
|
75d1f23297 | |
|
41ee0253f1 | |
|
21badd0ab6 | |
|
1a5e94a6ca | |
|
13a94f5412 | |
|
809d2d61ed | |
|
eb7ef4ba1c | |
|
c4058b010e | |
|
a4483a0f9c | |
|
efbd76133d | |
|
7a47f846ab | |
|
35b439e637 | |
|
5e625c15de | |
|
543f5b6443 | |
|
b404344c8d | |
|
f9c6663dbc | |
|
5ba51e5322 | |
|
0fe7cec356 | |
|
38ae8a82be | |
|
a7a76f781d | |
|
cb0d1d58db | |
|
348683948f | |
|
a1d007928a | |
|
bfe1b78e18 | |
|
5e8cd824ba | |
|
089344ce9a | |
|
d38a0841f1 | |
|
6cc00a79ab | |
|
d90b935bf8 | |
|
c1b6f922a1 | |
|
42065ea7d2 | |
|
14a713900f | |
|
e9e81f2985 | |
|
6f12a5ff6e | |
|
1f0f41dc50 | |
|
6e1fd56cd1 | |
|
a4e780b8a3 | |
|
8131efce0c | |
|
8ca690c439 | |
|
cce2e9b9e1 | |
|
53ac31185d | |
|
5133f9687f | |
|
a55465f3cf | |
|
5367813083 | |
|
f0c9c76532 | |
|
b85b83b6bd | |
|
755b9ebf97 | |
|
273320fb31 | |
|
e2907b82e1 | |
|
52c67dc4f5 | |
|
91704e2c5d | |
|
422cecb3e2 | |
|
71261bc350 | |
|
7aeee2ef9a | |
|
caac213bd1 | |
|
fbb663bb18 | |
|
01f3e6430f | |
|
e7276a766f | |
|
fd81054dd8 | |
|
4645940ba6 | |
|
3e47088350 | |
|
c701473618 | |
|
9ecc277d7f | |
|
f16d1c3d63 | |
|
06a3f913a1 | |
|
09de8b0229 | |
|
622a3fc27a | |
|
dacf9101ac | |
|
234a9f0448 | |
|
56975778ba | |
|
8522cf721d | |
|
40a0fa15db | |
|
ace27499ee | |
|
830d79e3ef | |
|
4abcb751f4 | |
|
7b251d3f45 | |
|
3bf0d06d54 | |
|
668769f558 | |
|
4e676eb9ea | |
|
c9c938f86a | |
|
486979a663 | |
|
e4543d10a6 | |
|
63ae9c959a | |
|
168b7e848d | |
|
a9176b9c3d | |
|
c424c7dadf | |
|
22e6cdf4dc | |
|
3be3da6b69 | |
|
283e174abd | |
|
ef33066620 | |
|
bf2bc9e07a | |
|
d5ff235f5d | |
|
6a67b21a42 | |
|
cb926b09b0 | |
|
3a24847e6e | |
|
46875b26c1 | |
|
b46104ac42 | |
|
0e879d2fe1 | |
|
8ac9583cb0 | |
|
df651b849c | |
|
ceea03ac65 | |
|
59967a139d | |
|
26b67781ba | |
|
5a50120db7 | |
|
229c99ddb6 | |
|
e03f41b660 | |
|
667097a809 | |
|
fe8304d06b | |
|
77d0771063 | |
|
9a1eb628e7 | |
|
89c0b8513d | |
|
05d521046b | |
|
15d2ccb18f | |
|
5c23c61f16 | |
|
c23bc1b85e | |
|
d2092b3cf0 | |
|
5c0faf5f1c | |
|
c770850473 | |
|
c429ba3e64 | |
|
4fe9d6a023 | |
|
10f9693e92 | |
|
7ea413e54b | |
|
7857f2c5cb | |
|
8272becd70 | |
|
d38236dd16 | |
|
fd5187e744 | |
|
6ef6378dca | |
|
1e2a299907 | |
|
70e9f167be | |
|
d8f000cf9d | |
|
72cc8c189d | |
|
d0c76d94f7 | |
|
196f9c2b3a | |
|
6629f13693 | |
|
7c49484568 | |
|
a007b7764d | |
|
382f56ed94 | |
|
9b1c9b878f |
|
@ -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,31 @@
|
|||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: "[BUG]"
|
||||
labels: bug
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. Windows 10]
|
||||
- Version [e.g. 5]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
|
@ -0,0 +1,20 @@
|
|||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: enhancement
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
|
@ -0,0 +1,7 @@
|
|||
Fixes #
|
||||
|
||||
## Proposed Changes
|
||||
|
||||
-
|
||||
-
|
||||
-
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
# CMake artifacts
|
||||
/build*/
|
||||
/_packages/
|
||||
CMakeUserPresets.json
|
||||
|
||||
# Doxygen
|
||||
/docs/
|
||||
|
@ -91,4 +93,4 @@ compile_commands.json
|
|||
# QtCreator local machine specific files for imported projects
|
||||
*creator.user*
|
||||
|
||||
*_qmlcache.qrc
|
||||
*_qmlcache.qrc
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
# Use this file when blaming
|
||||
# git-blame --ignore-revs-file .gitignore-blame
|
||||
|
||||
# Big clang-format refactor
|
||||
9f14ee1c64b3b9d399078918fc20ccce7bc4a7d9
|
|
@ -0,0 +1,120 @@
|
|||
# Building using CMake
|
||||
|
||||
ModelRailroadTimetablePlanner uses CMake build system.
|
||||
|
||||
CMake Minimum Version: **3.5**
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
1. **Qt**: [qt.io](https://www.qt.io)
|
||||
|
||||
Components:
|
||||
- Core
|
||||
- Gui
|
||||
- Widgets
|
||||
- Svg
|
||||
- PrintSupport
|
||||
- LinguistTools
|
||||
|
||||
2. **SQLite 3**: [sqlite.org](https://sqlite.org/index.html)
|
||||
|
||||
3. **libzip**: [libzip.org](https://libzip.org)
|
||||
|
||||
4. **zlib**: [zlib.net](https://www.zlib.net)
|
||||
|
||||
|
||||
## Ubuntu
|
||||
|
||||
- Install GCC and CMake
|
||||
>`sudo apt install build-essential cmake`
|
||||
|
||||
- Install Qt 5:
|
||||
>`sudo apt install qtbase5-dev libqt5svg5-dev qttools5-dev qttools5-dev-tools`
|
||||
|
||||
- Install SQLite 3
|
||||
>`sudo apt install libsqlite3-dev`
|
||||
|
||||
- Install libzip
|
||||
>`sudo apt install libzip-dev`
|
||||
|
||||
- Install zlib
|
||||
> NOTE: automatically installed if installing libzip
|
||||
|
||||
>`sudo apt install zlib1g-dev`
|
||||
|
||||
|
||||
## Windows
|
||||
|
||||
### Setup Qt
|
||||
> Set `QT5_DIR` CMake variable:
|
||||
> Set to the folder which contains `Qt5Config.cmake` file
|
||||
> Example: `C:\Qt\5.15.2\mingw81_64\lib\cmake\Qt5`
|
||||
|
||||
### Setup libzip
|
||||
If compiled from source `libzip` should generate CMake config package files
|
||||
This is the preferred method to include it:
|
||||
> Set `LibZip_DIR` CMake variable:
|
||||
> Set to the folder which contains `libzip-config.cmake` file
|
||||
> Example: `%LIBZIP_INSTALL_DIRECTORY%\lib\cmake\libzip`
|
||||
> (replace `%LIBZIP_INSTALL_DIRECTORY%` with the directory in which you installed `libzip`)
|
||||
|
||||
Alternative method:
|
||||
Include headers directory and libraries directory in CMake variables
|
||||
> Append `include` directory to `CMAKE_INCLUDE_PATH` or set `LibZip_INCLUDE_DIRS`
|
||||
> Append `lib` and `bin` directory to `CMAKE_LIBRARY_PATH`
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Windows CMake Doesn't Find DLL
|
||||
|
||||
MinGW can link directly to `*.dll` dynamic libraries but CMake is set to look for
|
||||
import libraries `*.dll.a`.
|
||||
|
||||
**SQLite 3** does not provide an import library so CMake will NOT find it.
|
||||
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 information see [DLL Import Library Tool](https://www.willus.com/mingw/colinp/win32/tools/dlltool.html)
|
||||
|
||||
## Compile
|
||||
|
||||
- Clone the repository
|
||||
> `git clone https://github.com/gfgit/ModelRailroadTimetablePlanner`
|
||||
|
||||
- Go to project directory
|
||||
> `cd ModelRailroadTimetablePlanner`
|
||||
|
||||
- Create directory for building the program
|
||||
> `mkdir build`
|
||||
> `cd build`
|
||||
|
||||
- Run CMake
|
||||
> `cmake ..`
|
||||
|
||||
> NOTE: you can use `cmake-gui` to configure CMake variables
|
||||
|
||||
- Build
|
||||
> `cmake --build .`
|
||||
|
||||
> NOTE: if you have problems locating `libzip` on Ubuntu
|
||||
> Set `LibZip_LIBRARIES=/usr/lib/x86_64-linux-gnu/libzip.so`
|
||||
|
||||
- Install
|
||||
> `cmake --build . -t install`
|
||||
|
||||
- Run
|
||||
> `mrtplanner`
|
||||
|
||||
> NOTE: the location depends on where you installed the program
|
||||
> Look at `CMAKE_INSTALL_PREFIX` variable
|
||||
|
||||
## Doxygen
|
||||
|
||||
Enable `BUILD_DOXYGEN` in CMake configuration to generate doxygen documentation.
|
||||
The output will go in `build/docs` directory.
|
||||
|
||||
|
||||
## NSIS Installer
|
||||
|
||||
> TODO: document `makensis.exe`
|
108
CMakeLists.txt
|
@ -1,7 +1,7 @@
|
|||
cmake_minimum_required(VERSION 3.5)
|
||||
cmake_minimum_required(VERSION 3.17)
|
||||
include(CMakeDependentOption)
|
||||
|
||||
project(TrainTimetable VERSION 5.25.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)
|
||||
|
@ -13,81 +13,74 @@ endif()
|
|||
|
||||
## Defines ##
|
||||
|
||||
set(DB_FORMAT_VERSION 7)
|
||||
set(DB_FORMAT_VERSION 8)
|
||||
|
||||
set(APP_PRODUCT_NAME "TrainTimeTable")
|
||||
set(APP_DISPLAY_NAME "Train Timetable")
|
||||
set(APP_PRODUCT_NAME "ModelRailroadTimetablePlanner")
|
||||
set(APP_DISPLAY_NAME "Model Railroad Timetable Planner")
|
||||
set(APP_COMPANY_NAME "Train Software")
|
||||
set(APP_COMPANY_NAME_LOWER "trainsoftware")
|
||||
|
||||
set(PROJECT_HOMEPAGE_URL "www.pollofrittomachebuono.altervista.org")
|
||||
set(APP_HELP_URL ${APP_ABOUT_URL})
|
||||
set(APP_UPDATE_URL ${APP_ABOUT_URL})
|
||||
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)
|
||||
|
||||
# Create main application target
|
||||
set(TRAINTIMETABLE_TARGET "train-timetable")
|
||||
set(MR_TIMETABLE_PLANNER_TARGET "mrtplanner")
|
||||
|
||||
## defines end ##
|
||||
|
||||
# NSIS Installer
|
||||
if(WIN32)
|
||||
configure_file(packaging/windows/NSIS/constants.nsh.in ${CMAKE_BINARY_DIR}/NSIS/constants.nsh @ONLY)
|
||||
configure_file(packaging/windows/NSIS/installer.nsi ${CMAKE_BINARY_DIR}/NSIS/installer.nsi COPYONLY)
|
||||
endif()
|
||||
|
||||
add_custom_target(NSIS
|
||||
DEPENDS ${TRAINTIMETABLE_TARGET}
|
||||
#COMMAND TODO add NSIS compiler
|
||||
SOURCES
|
||||
packaging/windows/NSIS/constants.nsh.in
|
||||
packaging/windows/NSIS/installer.nsi
|
||||
packaging/windows/resources.rc.in
|
||||
VERBATIM)
|
||||
|
||||
## CUSTOM CONFIGURATION ##
|
||||
|
||||
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)
|
||||
|
||||
if(CONFIG_GLOBAL_TRY_CATCH)
|
||||
set(TRAINTIMETABLE_DEFINITIONS ${TRAINTIMETABLE_DEFINITIONS} -DGLOBAL_TRY_CATCH)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DGLOBAL_TRY_CATCH)
|
||||
endif()
|
||||
|
||||
if(CONFIG_NO_DEBUG_CALL_TRACE)
|
||||
set(TRAINTIMETABLE_DEFINITIONS ${TRAINTIMETABLE_DEFINITIONS} -DNO_DEBUG_CALL_TRACE)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DNO_DEBUG_CALL_TRACE)
|
||||
endif()
|
||||
|
||||
if(CONFIG_PRINT_DBG_MSG)
|
||||
set(TRAINTIMETABLE_DEFINITIONS ${TRAINTIMETABLE_DEFINITIONS} -DPRINT_DBG_MSG)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DPRINT_DBG_MSG)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ENABLE_BACKGROUND_MANAGER)
|
||||
set(TRAINTIMETABLE_DEFINITIONS ${TRAINTIMETABLE_DEFINITIONS} -DENABLE_BACKGROUND_MANAGER)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ENABLE_RS_CHECKER)
|
||||
set(TRAINTIMETABLE_DEFINITIONS ${TRAINTIMETABLE_DEFINITIONS} -DENABLE_RS_CHECKER)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DENABLE_BACKGROUND_MANAGER)
|
||||
endif()
|
||||
|
||||
if(CONFIG_SEARCHBOX_MODE_ASYNC)
|
||||
set(TRAINTIMETABLE_DEFINITIONS ${TRAINTIMETABLE_DEFINITIONS} -DSEARCHBOX_MODE_ASYNC)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DSEARCHBOX_MODE_ASYNC)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ENABLE_AUTO_TIME_RECALC)
|
||||
set(TRAINTIMETABLE_DEFINITIONS ${TRAINTIMETABLE_DEFINITIONS} -DENABLE_AUTO_TIME_RECALC)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DENABLE_AUTO_TIME_RECALC)
|
||||
endif()
|
||||
|
||||
if(CONFIG_ENABLE_USER_QUERY)
|
||||
set(TRAINTIMETABLE_DEFINITIONS ${TRAINTIMETABLE_DEFINITIONS} -DENABLE_USER_QUERY)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DENABLE_USER_QUERY)
|
||||
endif()
|
||||
|
||||
## Config end ##
|
||||
|
@ -100,8 +93,9 @@ set(CMAKE_AUTOUIC ON)
|
|||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
|
||||
set(TRAINTIMETABLE_DEFINITIONS ${TRAINTIMETABLE_DEFINITIONS} -DAPPVERSION="${PROJECT_VERSION}" -DQT_DEPRECATED_WARNINGS)
|
||||
set(MR_TIMETABLE_PLANNER_DEFINITIONS ${MR_TIMETABLE_PLANNER_DEFINITIONS} -DAPPVERSION="${PROJECT_VERSION}" -DQT_DEPRECATED_WARNINGS)
|
||||
|
||||
# Include our custom FindXXX moudules in '/cmake' subdirectory
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
|
||||
find_package(Qt5 REQUIRED
|
||||
|
@ -115,44 +109,24 @@ find_package(Qt5 REQUIRED
|
|||
|
||||
find_package(SQLite3)
|
||||
find_package(ZLIB)
|
||||
find_package(ssplib)
|
||||
|
||||
# Locate libzip
|
||||
if(NOT ZIP_INCLUDE_DIR OR NOT ZIP_LIBRARY)
|
||||
set(ZIP_INCLUDE_DIR NOTFOUND CACHE PATH "Path to libzip include directory, contains 'zip.h' file.")
|
||||
set(ZIP_LIBRARY NOTFOUND CACHE FILEPATH "Path to libzip library (shared DLL usually libzip.dll).")
|
||||
message(FATAL_ERROR "libzip NOT FOUND (set ZIP_INCLUDE_DIR and ZIP_LIBRARY)")
|
||||
endif()
|
||||
# Prefer config files shipped with 'libzip'
|
||||
# If not found, it will fallback to out custom FindLibZip.cmake module
|
||||
# Located in '/cmake' subdirectory
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG true)
|
||||
find_package(LibZip)
|
||||
set(CMAKE_FIND_PACKAGE_PREFER_CONFIG false)
|
||||
|
||||
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)
|
||||
|
||||
add_subdirectory(src)
|
||||
add_subdirectory(packaging)
|
||||
|
||||
include(Packing)
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
Usage of branches:
|
||||
|
||||
master: stable code ready to be released
|
||||
|
||||
develop: unstable code or code that needs further testing
|
||||
|
||||
To develop new features please create new branches and finally merge to develop
|
|
@ -0,0 +1,47 @@
|
|||
# HOW TO CONTRIBUTE
|
||||
|
||||
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`.
|
||||
|
||||
|
||||
## Usage of branches
|
||||
|
||||
- `master`: stable code ready to be released
|
||||
|
||||
- `develop`: unstable code or code that needs further testing
|
||||
|
||||
To develop new features please create new branches and finally merge to develop
|
||||
|
||||
## Building
|
||||
|
||||
See [BUILDING.md](BUILDING.md)
|
||||
|
||||
## Translations
|
||||
|
||||
UI is localized with Qt Linguist.
|
||||
Translation files live in [`src/translations`](src/translations) folder.
|
||||
For more information see [Qt Documentation](https://doc.qt.io/qt-5/linguist-overview.html).
|
||||
|
||||
### Adding a new language
|
||||
|
||||
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
|
||||
[`src/translations/CMakeLists.txt`](src/translations/CMakeLists.txt).
|
||||
Add the file name with path in `TRAINTIMETABLE_TS_FILES` variabile.
|
||||
|
||||
Then follow next paragraph.
|
||||
|
||||
### Update translations to match new UI elements
|
||||
|
||||
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.
|
||||
|
||||
4. Use Qt Linguist or other software to translate text.
|
||||
|
||||
|
||||
## Do you have suggestions?
|
||||
If you think this workflow model is not efficient please let me know!
|
3
README
|
@ -1,3 +0,0 @@
|
|||
Train Timetable
|
||||
A C++ software with Qt GUI for model railway timetable scheduling
|
||||
By Filippo Gentile
|
|
@ -0,0 +1,48 @@
|
|||
# ModelRailroadTimetablePlanner
|
||||
|
||||
[Versione in italiano](README_it.md)
|
||||
|
||||
Formerly **TrainTimetable**
|
||||
|
||||
A cross-platform C++ application with Qt GUI for model railway timetable scheduling.
|
||||
*Currently tested on Windows and Ubuntu.*
|
||||
|
||||
By Filippo Gentile
|
||||
|
||||
## Screenshots
|
||||
|
||||
![Screenshot of ModelRailroadTimetablePlanner](screenshots/Screenshot.png "ModelRailroadTimetablePlanner Screenshot")
|
||||
|
||||
## Goals
|
||||
|
||||
The goal of the project is to implement
|
||||
automatic mechanism which prevent invalid
|
||||
operations and suggest user with useful action
|
||||
in order to speed up the creation of timetables.
|
||||
|
||||
The programs aims to provide all documents useful
|
||||
for driving and dispatching trains on big layouts like in FREMO Meetings.
|
||||
|
||||
## Main features
|
||||
- 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
|
||||
- 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 instabilities and limitations.
|
||||
Since then it has grown unexpectedly.
|
||||
So I'd like it to become a community project!
|
||||
|
||||
|
||||
## Project motto
|
||||
|
||||
Less time spent on the computer,
|
||||
more time to have fun on your railway layout!!!
|
||||
|
||||
## Contributing to the project
|
||||
|
||||
Please see [CONTRIBUTING.md](CONTRIBUTING.md) file.
|
|
@ -0,0 +1,48 @@
|
|||
# ModelRailroadTimetablePlanner
|
||||
|
||||
[English version](README.md)
|
||||
|
||||
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.*
|
||||
|
||||
By Filippo Gentile
|
||||
|
||||
## Screenshots
|
||||
|
||||
![Screenshot di ModelRailroadTimetablePlanner](screenshots/Screenshot_it.png "ModelRailroadTimetablePlanner Screenshot")
|
||||
|
||||
## Obiettivi
|
||||
|
||||
L'obiettivo del progetto è implementare meccanismi
|
||||
automatici che impediscano operazioni non valide e
|
||||
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 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
|
||||
- Importazione del materiale rotabile da altre sessioni o fogli di calcolo ODS Spreadsheet
|
||||
|
||||
## Storia del progetto
|
||||
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.
|
||||
Quindi mi piacerebbe che diventasse un progetto condiviso con la comunità!
|
||||
|
||||
|
||||
## Motto del progetto
|
||||
|
||||
Meno tempo passato al computer, più tempo
|
||||
per divertirsi sul proprio tracciato ferroviario!!!
|
||||
|
||||
## Partecipare al progetto
|
||||
|
||||
Per favore vedere il file [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
@ -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,68 @@
|
|||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
FindLibZip
|
||||
-----------
|
||||
|
||||
.. versionadded:: 3.14
|
||||
|
||||
Find the Zip libraries, v3
|
||||
|
||||
IMPORTED targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module defines the following :prop_tgt:`IMPORTED` target:
|
||||
|
||||
``LibZip::LibZip``
|
||||
|
||||
Result variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module will set the following variables if found:
|
||||
|
||||
``LibZip_INCLUDE_DIRS``
|
||||
where to find zip.h, etc.
|
||||
``LibZip_LIBRARIES``
|
||||
the libraries to link against to use SQLite3.
|
||||
``LibZip_VERSION``
|
||||
version of the LibZip library found
|
||||
``LibZip_FOUND``
|
||||
TRUE if found
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
# Look for the necessary header
|
||||
find_path(LibZip_INCLUDE_DIR NAMES zip.h)
|
||||
mark_as_advanced(LibZip_INCLUDE_DIR)
|
||||
|
||||
# Look for the necessary library
|
||||
find_library(LibZip_LIBRARY NAMES libzip)
|
||||
mark_as_advanced(LibZip_LIBRARY)
|
||||
|
||||
# Extract version information from the header file
|
||||
if(LibZip_INCLUDE_DIR)
|
||||
file(STRINGS ${LibZip_INCLUDE_DIR}/zipconf.h _ver_line
|
||||
REGEX "^#define LIBZIP_VERSION *\"[0-9]+\\.[0-9]+\\.[0-9]+\""
|
||||
LIMIT_COUNT 1)
|
||||
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+"
|
||||
LibZip_VERSION "${_ver_line}")
|
||||
unset(_ver_line)
|
||||
endif()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(LibZip
|
||||
REQUIRED_VARS LibZip_INCLUDE_DIR LibZip_LIBRARY
|
||||
VERSION_VAR LibZip_VERSION)
|
||||
|
||||
# Create the imported target
|
||||
if(LibZip_FOUND)
|
||||
set(LibZip_INCLUDE_DIRS ${LibZip_INCLUDE_DIR})
|
||||
set(LibZip_LIBRARIES ${LibZip_LIBRARY})
|
||||
if(NOT TARGET LibZip::LibZip)
|
||||
add_library(LibZip::LibZip UNKNOWN IMPORTED)
|
||||
set_target_properties(LibZip::LibZip PROPERTIES
|
||||
IMPORTED_LOCATION "${LibZip_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${LibZip_INCLUDE_DIR}")
|
||||
endif()
|
||||
endif()
|
|
@ -1,66 +0,0 @@
|
|||
# Distributed under the OSI-approved BSD 3-Clause License. See accompanying
|
||||
# file Copyright.txt or https://cmake.org/licensing for details.
|
||||
|
||||
#[=======================================================================[.rst:
|
||||
FindSQLite3
|
||||
-----------
|
||||
|
||||
Find the SQLite libraries, v3
|
||||
|
||||
IMPORTED targets
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module defines the following :prop_tgt:`IMPORTED` target:
|
||||
|
||||
``SQLite::SQLite3``
|
||||
|
||||
Result variables
|
||||
^^^^^^^^^^^^^^^^
|
||||
|
||||
This module will set the following variables if found:
|
||||
|
||||
``SQLite3_INCLUDE_DIRS``
|
||||
where to find sqlite3.h, etc.
|
||||
``SQLite3_LIBRARIES``
|
||||
the libraries to link against to use SQLite3.
|
||||
``SQLite3_VERSION``
|
||||
version of the SQLite3 library found
|
||||
``SQLite3_FOUND``
|
||||
TRUE if found
|
||||
|
||||
#]=======================================================================]
|
||||
|
||||
# Look for the necessary header
|
||||
find_path(SQLite3_INCLUDE_DIR NAMES sqlite3.h)
|
||||
mark_as_advanced(SQLite3_INCLUDE_DIR)
|
||||
|
||||
# Look for the necessary library
|
||||
find_library(SQLite3_LIBRARY NAMES sqlite3 sqlite)
|
||||
mark_as_advanced(SQLite3_LIBRARY)
|
||||
|
||||
# Extract version information from the header file
|
||||
if(SQLite3_INCLUDE_DIR)
|
||||
file(STRINGS ${SQLite3_INCLUDE_DIR}/sqlite3.h _ver_line
|
||||
REGEX "^#define SQLITE_VERSION *\"[0-9]+\\.[0-9]+\\.[0-9]+\""
|
||||
LIMIT_COUNT 1)
|
||||
string(REGEX MATCH "[0-9]+\\.[0-9]+\\.[0-9]+"
|
||||
SQLite3_VERSION "${_ver_line}")
|
||||
unset(_ver_line)
|
||||
endif()
|
||||
|
||||
include(${CMAKE_ROOT}/Modules/FindPackageHandleStandardArgs.cmake)
|
||||
find_package_handle_standard_args(SQLite3
|
||||
REQUIRED_VARS SQLite3_INCLUDE_DIR SQLite3_LIBRARY
|
||||
VERSION_VAR SQLite3_VERSION)
|
||||
|
||||
# Create the imported target
|
||||
if(SQLite3_FOUND)
|
||||
set(SQLite3_INCLUDE_DIRS ${SQLite3_INCLUDE_DIR})
|
||||
set(SQLite3_LIBRARIES ${SQLite3_LIBRARY})
|
||||
if(NOT TARGET SQLite::SQLite3)
|
||||
add_library(SQLite::SQLite3 UNKNOWN IMPORTED)
|
||||
set_target_properties(SQLite::SQLite3 PROPERTIES
|
||||
IMPORTED_LOCATION "${SQLite3_LIBRARY}"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${SQLite3_INCLUDE_DIR}")
|
||||
endif()
|
||||
endif()
|
|
@ -1,63 +0,0 @@
|
|||
# Searches for an installation of the zip library. On success, it sets the following variables:
|
||||
#
|
||||
# Zip_FOUND Set to true to indicate the zip library was found
|
||||
# Zip_INCLUDE_DIRS The directory containing the header file zip/zip.h
|
||||
# Zip_LIBRARIES The libraries needed to use the zip library
|
||||
#
|
||||
# To specify an additional directory to search, set Zip_ROOT.
|
||||
#
|
||||
# Author: Siddhartha Chaudhuri, 2009
|
||||
#
|
||||
|
||||
# Look for the header, first in the user-specified location and then in the system locations
|
||||
SET(Zip_INCLUDE_DOC "The directory containing the header file zip/zip.h")
|
||||
FIND_PATH(Zip_INCLUDE_DIRS NAMES zip/zip.h PATHS ${Zip_ROOT} ${Zip_ROOT}/include DOC ${Zip_INCLUDE_DOC} NO_DEFAULT_PATH)
|
||||
IF(NOT Zip_INCLUDE_DIRS) # now look in system locations
|
||||
FIND_PATH(Zip_INCLUDE_DIRS NAMES zip/zip.h DOC ${Zip_INCLUDE_DOC})
|
||||
ENDIF(NOT Zip_INCLUDE_DIRS)
|
||||
|
||||
SET(Zip_FOUND FALSE)
|
||||
|
||||
IF(Zip_INCLUDE_DIRS)
|
||||
SET(Zip_LIBRARY_DIRS ${Zip_INCLUDE_DIRS})
|
||||
|
||||
IF("${Zip_LIBRARY_DIRS}" MATCHES "/include$")
|
||||
# Strip off the trailing "/include" in the path.
|
||||
GET_FILENAME_COMPONENT(Zip_LIBRARY_DIRS ${Zip_LIBRARY_DIRS} PATH)
|
||||
ENDIF("${Zip_LIBRARY_DIRS}" MATCHES "/include$")
|
||||
|
||||
IF(EXISTS "${Zip_LIBRARY_DIRS}/lib")
|
||||
SET(Zip_LIBRARY_DIRS ${Zip_LIBRARY_DIRS}/lib)
|
||||
ENDIF(EXISTS "${Zip_LIBRARY_DIRS}/lib")
|
||||
|
||||
# Find Zip libraries
|
||||
FIND_LIBRARY(Zip_DEBUG_LIBRARY NAMES zipd zip_d libzipd libzip_d
|
||||
PATH_SUFFIXES Debug ${CMAKE_LIBRARY_ARCHITECTURE} ${CMAKE_LIBRARY_ARCHITECTURE}/Debug
|
||||
PATHS ${Zip_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
FIND_LIBRARY(Zip_RELEASE_LIBRARY NAMES zip libzip
|
||||
PATH_SUFFIXES Release ${CMAKE_LIBRARY_ARCHITECTURE} ${CMAKE_LIBRARY_ARCHITECTURE}/Release
|
||||
PATHS ${Zip_LIBRARY_DIRS} NO_DEFAULT_PATH)
|
||||
|
||||
SET(Zip_LIBRARIES )
|
||||
IF(Zip_DEBUG_LIBRARY AND Zip_RELEASE_LIBRARY)
|
||||
SET(Zip_LIBRARIES debug ${Zip_DEBUG_LIBRARY} optimized ${Zip_RELEASE_LIBRARY})
|
||||
ELSEIF(Zip_DEBUG_LIBRARY)
|
||||
SET(Zip_LIBRARIES ${Zip_DEBUG_LIBRARY})
|
||||
ELSEIF(Zip_RELEASE_LIBRARY)
|
||||
SET(Zip_LIBRARIES ${Zip_RELEASE_LIBRARY})
|
||||
ENDIF(Zip_DEBUG_LIBRARY AND Zip_RELEASE_LIBRARY)
|
||||
|
||||
IF(Zip_LIBRARIES)
|
||||
SET(Zip_FOUND TRUE)
|
||||
ENDIF(Zip_LIBRARIES)
|
||||
ENDIF(Zip_INCLUDE_DIRS)
|
||||
|
||||
IF(Zip_FOUND)
|
||||
IF(NOT Zip_FIND_QUIETLY)
|
||||
MESSAGE(STATUS "Found Zip: headers at ${Zip_INCLUDE_DIRS}, libraries at ${Zip_LIBRARY_DIRS}")
|
||||
ENDIF(NOT Zip_FIND_QUIETLY)
|
||||
ELSE(Zip_FOUND)
|
||||
IF(Zip_FIND_REQUIRED)
|
||||
MESSAGE(FATAL_ERROR "Zip library not found")
|
||||
ENDIF(Zip_FIND_REQUIRED)
|
||||
ENDIF(Zip_FOUND)
|
|
@ -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()
|
|
@ -0,0 +1,92 @@
|
|||
|
||||
|
||||
set(MY_PACKAGE_MAINTAINER_NAME "Filippo Gentile")
|
||||
set(MY_PACKAGE_MAINTAINER_EMAIL "gentilefilippo01@gmail.com")
|
||||
|
||||
|
||||
# these are cache variables, so they could be overwritten with -D,
|
||||
# ${namespace}-... could be your main project name, or company, or whatever
|
||||
set(CPACK_PACKAGE_NAME "${APP_COMPANY_NAME_LOWER}-${MR_TIMETABLE_PLANNER_TARGET}"
|
||||
CACHE STRING "The resulting package name"
|
||||
)
|
||||
# which is useful in case of packing only selected components instead of the whole thing
|
||||
set(
|
||||
CPACK_PACKAGE_DESCRIPTION_SUMMARY "${CMAKE_PROJECT_DESCRIPTION}"
|
||||
CACHE STRING "Package description for the package metadata"
|
||||
)
|
||||
set(CPACK_PACKAGE_VENDOR "${APP_COMPANY_NAME}")
|
||||
|
||||
set(CPACK_VERBATIM_VARIABLES YES)
|
||||
|
||||
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME})
|
||||
SET(CPACK_OUTPUT_FILE_PREFIX "${CMAKE_SOURCE_DIR}/_packages")
|
||||
set(CPACK_STRIP_FILES YES)
|
||||
|
||||
set(
|
||||
CPACK_INSTALL_DEFAULT_DIRECTORY_PERMISSIONS
|
||||
OWNER_READ OWNER_WRITE OWNER_EXECUTE
|
||||
GROUP_READ GROUP_EXECUTE
|
||||
WORLD_READ WORLD_EXECUTE
|
||||
)
|
||||
|
||||
# https://unix.stackexchange.com/a/11552/254512
|
||||
set(CPACK_PACKAGING_INSTALL_PREFIX "/opt/${MR_TIMETABLE_PLANNER_TARGET}")#/${CMAKE_PROJECT_VERSION}")
|
||||
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR})
|
||||
set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR})
|
||||
set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH})
|
||||
|
||||
set(CPACK_PACKAGE_CONTACT ${MY_PACKAGE_MAINTAINER_EMAIL})
|
||||
set(CPACK_DEBIAN_PACKAGE_MAINTAINER "${MY_PACKAGE_MAINTAINER_NAME} <${CPACK_PACKAGE_CONTACT}>")
|
||||
|
||||
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
|
||||
set(CPACK_RESOURCE_FILE_README "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
|
||||
|
||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY ${PROJECT_DESCRIPTION})
|
||||
|
||||
set(CPACK_PACKAGE_HOMEPAGE_URL ${PROJECT_HOMEPAGE_URL})
|
||||
|
||||
# package name for deb
|
||||
# if set, then instead of some-application-0.9.2-Linux.deb
|
||||
# you'll get some-application_0.9.2_amd64.deb (note the underscores too)
|
||||
set(CPACK_DEBIAN_FILE_NAME DEB-DEFAULT)
|
||||
# if you want every group to have its own package,
|
||||
# although the same happens if this is not sent (so it defaults to ONE_PER_GROUP)
|
||||
# and CPACK_DEB_COMPONENT_INSTALL is set to YES
|
||||
set(CPACK_COMPONENTS_GROUPING ALL_COMPONENTS_IN_ONE)#ONE_PER_GROUP)
|
||||
# without this you won't be able to pack only specified component
|
||||
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)
|
||||
|
||||
# optionally, you can add various meta information to the components defined in INSTALLs
|
||||
# cpack_add_component(some-application
|
||||
# DISPLAY_NAME "Some application"
|
||||
# DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}"
|
||||
# #GROUP group1
|
||||
# )
|
||||
# cpack_add_component(SomeLibrary
|
||||
# DISPLAY_NAME "Some library"
|
||||
# DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}"
|
||||
# #GROUP group1
|
||||
# )
|
||||
# cpack_add_component(AnotherLibrary
|
||||
# DISPLAY_NAME "Another library"
|
||||
# DESCRIPTION "${CPACK_PACKAGE_DESCRIPTION_SUMMARY}"
|
||||
# #GROUP group2
|
||||
# )
|
||||
# you can also put them into groups
|
||||
#cpack_add_component_group(group1)
|
||||
#cpack_add_component_group(group2)
|
||||
|
||||
# can be also set as -DCPACK_COMPONENTS_ALL="AnotherLibrary"
|
||||
#set(CPACK_COMPONENTS_ALL "AnotherLibrary")
|
||||
message(STATUS "Components to pack: ${CPACK_COMPONENTS_ALL}")
|
|
@ -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 |
|
@ -0,0 +1,3 @@
|
|||
# TODO: if/else for different package types
|
||||
|
||||
## CPack DEB Generator ##
|
|
@ -0,0 +1,19 @@
|
|||
# NSIS Installer
|
||||
|
||||
set(MAKENSIS_EXE "makensis")
|
||||
|
||||
if(WIN32)
|
||||
configure_file(windows/NSIS/constants.nsh.in ${CMAKE_BINARY_DIR}/NSIS/constants.nsh @ONLY)
|
||||
configure_file(windows/NSIS/installer.nsi ${CMAKE_BINARY_DIR}/NSIS/installer.nsi COPYONLY)
|
||||
endif()
|
||||
|
||||
add_custom_target(NSIS
|
||||
DEPENDS ${MR_TIMETABLE_PLANNER_TARGET} # First build the application
|
||||
COMMAND ${MAKENSIS_EXE} installer.nsi
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/NSIS
|
||||
COMMENT "Building NSIS Win32 installer..."
|
||||
SOURCES #Make this files visible in IDE editor
|
||||
windows/NSIS/constants.nsh.in
|
||||
windows/NSIS/installer.nsi
|
||||
windows/resources.rc.in
|
||||
VERBATIM)
|
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 5.4 KiB |
After Width: | Height: | Size: 751 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 22 KiB |
After Width: | Height: | Size: 2.9 KiB |
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
|
||||
<mime-type type="application/vnd.trainsoftware-mrtplanner-session-sqlite3">
|
||||
<comment>MRTPlanner Session database file</comment>
|
||||
<comment xml:lang="it">Sessione Database MRTPlanner</comment>
|
||||
<sub-class-of type="application/x-sqlite3"/>
|
||||
<glob pattern="*.ttt"/>
|
||||
<alias type="application/x-mrtplanner-session-sqlite3"/>
|
||||
</mime-type>
|
||||
</mime-info>
|
|
@ -0,0 +1,14 @@
|
|||
[Desktop Entry]
|
||||
Name=Model Railroad Timetable Planner
|
||||
Comment=Database application to create and manage model railway sessions
|
||||
Comment[it]=Applicazione database per creare e gestire sessioni di plastici ferroviari
|
||||
Keywords=Train;Trains;Railway;Database;Model;Timetable
|
||||
Keywords[it]=Treno;Treni;Ferrovia;Plastico;Database;Orario
|
||||
Exec=/opt/mrtplanner/bin/mrtplanner %f
|
||||
Icon=mrtplanner
|
||||
Terminal=false
|
||||
X-MultipleArgs=false
|
||||
Type=Application
|
||||
Categories=Development;Utility;Database;
|
||||
MimeType=application/x-mrtplanner-session-sqlite3
|
||||
StartupWMClass=mrtplanner
|
|
@ -1,7 +1,7 @@
|
|||
;Definitions
|
||||
|
||||
!define APP_NAME "@APP_DISPLAY_NAME@"
|
||||
!define APP_PRODUCT "@TRAINTIMETABLE_TARGET@"
|
||||
!define APP_PRODUCT "@MR_TIMETABLE_PLANNER_TARGET@"
|
||||
!define COMPANY_NAME "@APP_COMPANY_NAME@"
|
||||
!define DESCRIPTION "@PROJECT_DESCRIPTION@"
|
||||
|
||||
|
@ -19,10 +19,10 @@
|
|||
# This is the size (in kB) of all the files copied into "Program Files"
|
||||
!define INSTALLSIZE 8000
|
||||
|
||||
!define TRAIN_TIMETABLE_PATH "@CMAKE_BINARY_DIR@\debug\"
|
||||
!define TRAIN_TIMETABLE_EXTRA "@CMAKE_SOURCE_DIR@\files"
|
||||
!define TRAIN_TIMETABLE_EXE "@TRAINTIMETABLE_TARGET@.exe"
|
||||
!define TRAIN_TIMETABLE_SETTINGS "traintimetable_settings.ini"
|
||||
!define MR_TIMETABLE_PLANNER_PATH "@CMAKE_INSTALL_PREFIX@\@CMAKE_INSTALL_BINDIR@"
|
||||
!define MR_TIMETABLE_PLANNER_EXTRA "@CMAKE_SOURCE_DIR@\files"
|
||||
!define MR_TIMETABLE_PLANNER_EXE "@MR_TIMETABLE_PLANNER_TARGET@.exe"
|
||||
!define MR_TIMETABLE_PLANNER_SETTINGS "mrtp_settings.ini"
|
||||
|
||||
!define TRAIN_TIMETABLE_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt" ;TODO
|
||||
!define TRAIN_TIMETABLE_README "${NSISDIR}\Docs\Modern UI\License.txt" ;TODO
|
||||
!define MR_TIMETABLE_PLANNER_LICENSE "${NSISDIR}\Docs\Modern UI\License.txt" ;TODO
|
||||
!define MR_TIMETABLE_PLANNER_README "${NSISDIR}\Docs\Modern UI\License.txt" ;TODO
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
; Setup TrainTimetable and register variables for .ttt file associations
|
||||
; Setup ModelRailroadTimetablePlanner and register variables for .ttt file associations
|
||||
|
||||
;--------------------------------
|
||||
;Include Modern UI and FileFunc and LogicLib
|
||||
|
@ -27,10 +27,10 @@ Unicode True
|
|||
SetCompressor /SOLID /FINAL lzma
|
||||
|
||||
;Default installation folder
|
||||
InstallDir "$PROGRAMFILES64\${COMPANY_NAME}\${APP_NAME}" ; x86_64 64-bit
|
||||
InstallDir "$PROGRAMFILES64\${COMPANY_NAME}\${APP_PRODUCT}" ; x86_64 64-bit
|
||||
|
||||
;Get installation folder from registry if available
|
||||
InstallDirRegKey HKCU "Software\${COMPANY_NAME} ${APP_NAME}" ""
|
||||
InstallDirRegKey HKCU "Software\${COMPANY_NAME} ${APP_PRODUCT}" ""
|
||||
|
||||
;Request application privileges for Windows Vista
|
||||
RequestExecutionLevel admin ;Require admin rights on NT6+ (When UAC is turned on)
|
||||
|
@ -53,7 +53,7 @@ ManifestDPIAware True
|
|||
#!define MUI_WELCOMEPAGE_TEXT "$(welcome_text)"
|
||||
|
||||
!insertmacro MUI_PAGE_WELCOME
|
||||
!insertmacro MUI_PAGE_LICENSE ${TRAIN_TIMETABLE_LICENSE}
|
||||
!insertmacro MUI_PAGE_LICENSE "${MR_TIMETABLE_PLANNER_LICENSE}"
|
||||
!insertmacro MUI_PAGE_COMPONENTS
|
||||
!insertmacro MUI_PAGE_DIRECTORY
|
||||
!insertmacro MUI_PAGE_INSTFILES
|
||||
|
@ -61,10 +61,10 @@ ManifestDPIAware True
|
|||
!define MUI_FINISHPAGE_LINK "$(visit_site)"
|
||||
!define MUI_FINISHPAGE_LINK_LOCATION ${ABOUTURL}
|
||||
|
||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\${TRAIN_TIMETABLE_EXE}"
|
||||
!define MUI_FINISHPAGE_RUN "$INSTDIR\${MR_TIMETABLE_PLANNER_EXE}"
|
||||
!define MUI_FINISHPAGE_NOREBOOTSUPPORT
|
||||
|
||||
!define MUI_FINISHPAGE_SHOWREADME ${TRAIN_TIMETABLE_README}
|
||||
!define MUI_FINISHPAGE_SHOWREADME "${MR_TIMETABLE_PLANNER_README}"
|
||||
!define MUI_FINISHPAGE_SHOWREADME_TEXT "$(show_readme_label)"
|
||||
|
||||
!insertmacro MUI_PAGE_FINISH
|
||||
|
@ -97,7 +97,7 @@ LangString show_readme_label ${LANG_ITALIAN} "Mostra note di rilascio"
|
|||
LangString DESC_MainProgram ${LANG_ENGLISH} "Main application and settings files"
|
||||
LangString DESC_MainProgram ${LANG_ITALIAN} "Applicazione principale e file di configurazione"
|
||||
|
||||
LangString DESC_SM_Shortcut ${LANG_ENGLISH} "Create shortcuts to Start Menu. This makes easier to start Train Timetable"
|
||||
LangString DESC_SM_Shortcut ${LANG_ENGLISH} "Create shortcuts to Start Menu. This makes easier to start Model Railroad Timetable Planner"
|
||||
LangString DESC_SM_Shortcut ${LANG_ITALIAN} "Crea collegamenti al Menu Start. Questo rende pi${U+00FA} facile l'avvio dell'applicazione" #${U+00FA} = ù (U accentata minuscola)
|
||||
|
||||
LangString DESC_FileAss ${LANG_ENGLISH} "Setup file associations to display an icon for Train Timetable Session files and be able to open the application by just double clicking on the file"
|
||||
|
@ -151,32 +151,32 @@ Section "Application" main_program
|
|||
|
||||
SetOutPath $INSTDIR
|
||||
|
||||
File ${TRAIN_TIMETABLE_PATH}\${TRAIN_TIMETABLE_EXE}
|
||||
File ${TRAIN_TIMETABLE_EXTRA}\icons\icon.ico
|
||||
File ${MR_TIMETABLE_PLANNER_PATH}\${MR_TIMETABLE_PLANNER_EXE}
|
||||
File ${MR_TIMETABLE_PLANNER_EXTRA}\icons\icon.ico
|
||||
|
||||
File ${TRAIN_TIMETABLE_PATH}\*.dll
|
||||
File ${MR_TIMETABLE_PLANNER_PATH}\*.dll
|
||||
|
||||
SetOutPath $INSTDIR\platforms
|
||||
File ${TRAIN_TIMETABLE_PATH}\platforms\*.dll
|
||||
File ${MR_TIMETABLE_PLANNER_PATH}\platforms\*.dll
|
||||
|
||||
SetOutPath $INSTDIR\printsupport
|
||||
File ${TRAIN_TIMETABLE_PATH}\printsupport\*.dll
|
||||
File ${MR_TIMETABLE_PLANNER_PATH}\printsupport\*.dll
|
||||
|
||||
SetOutPath $INSTDIR\styles
|
||||
File ${TRAIN_TIMETABLE_PATH}\styles\*.dll
|
||||
File ${MR_TIMETABLE_PLANNER_PATH}\styles\*.dll
|
||||
|
||||
SetOutPath $INSTDIR\imageformats
|
||||
File ${TRAIN_TIMETABLE_PATH}\imageformats\*.dll
|
||||
File ${MR_TIMETABLE_PLANNER_PATH}\imageformats\*.dll
|
||||
|
||||
SetOutPath $INSTDIR\icons
|
||||
File ${TRAIN_TIMETABLE_EXTRA}\icons\lightning\lightning.svg
|
||||
File ${MR_TIMETABLE_PLANNER_EXTRA}\icons\lightning\lightning.svg
|
||||
|
||||
SetOutPath $INSTDIR\translations
|
||||
File ${TRAIN_TIMETABLE_PATH}\translations\*.qm
|
||||
File ${MR_TIMETABLE_PLANNER_PATH}\translations\*.qm
|
||||
|
||||
SetOutPath "$LOCALAPPDATA\${COMPANY_NAME}\${APP_NAME}"
|
||||
SetOutPath "$LOCALAPPDATA\${COMPANY_NAME}\${APP_PRODUCT}"
|
||||
; Create empty settings file
|
||||
FileOpen $0 $OUTDIR\${TRAIN_TIMETABLE_SETTINGS} w
|
||||
FileOpen $0 $OUTDIR\${MR_TIMETABLE_PLANNER_SETTINGS} w
|
||||
FileClose $0
|
||||
|
||||
; Set application language to the language chosen int the installer (default English)
|
||||
|
@ -189,35 +189,35 @@ Section "Application" main_program
|
|||
${Default}
|
||||
${Break}
|
||||
${EndSwitch}
|
||||
WriteINIStr $OUTDIR\${TRAIN_TIMETABLE_SETTINGS} "General" "language" $0
|
||||
WriteINIStr $OUTDIR\${MR_TIMETABLE_PLANNER_SETTINGS} "General" "language" $0
|
||||
|
||||
# Uninstaller - See function un.onInit and section "uninstall" for configuration
|
||||
WriteUninstaller "$INSTDIR\uninstall.exe"
|
||||
|
||||
# Registry information for add/remove programs
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "DisplayName" "${COMPANY_NAME} - ${APP_NAME} - ${DESCRIPTION}"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "InstallLocation" "$\"$INSTDIR$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "DisplayIcon" "$\"$INSTDIR\icon.ico$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "Publisher" "$\"${COMPANY_NAME}$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "HelpLink" "$\"${HELPURL}$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "URLUpdateInfo" "$\"${UPDATEURL}$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "URLInfoAbout" "$\"${ABOUTURL}$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "DisplayVersion" "$\"${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}$\""
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "VersionMajor" ${VERSIONMAJOR}
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "VersionMinor" ${VERSIONMINOR}
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "DisplayName" "${COMPANY_NAME} - ${APP_PRODUCT} - ${DESCRIPTION}"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "UninstallString" "$\"$INSTDIR\uninstall.exe$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "QuietUninstallString" "$\"$INSTDIR\uninstall.exe$\" /S"
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "InstallLocation" "$\"$INSTDIR$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "DisplayIcon" "$\"$INSTDIR\icon.ico$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "Publisher" "$\"${COMPANY_NAME}$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "HelpLink" "$\"${HELPURL}$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "URLUpdateInfo" "$\"${UPDATEURL}$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "URLInfoAbout" "$\"${ABOUTURL}$\""
|
||||
WriteRegStr HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "DisplayVersion" "$\"${VERSIONMAJOR}.${VERSIONMINOR}.${VERSIONBUILD}$\""
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "VersionMajor" ${VERSIONMAJOR}
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "VersionMinor" ${VERSIONMINOR}
|
||||
# There is no option for modifying or repairing the install
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "NoModify" 1
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "NoRepair" 1
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "NoModify" 1
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "NoRepair" 1
|
||||
# Set the INSTALLSIZE constant (!defined at the top of this script) so Add/Remove Programs can accurately report the size
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "EstimatedSize" ${INSTALLSIZE}
|
||||
WriteRegDWORD HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "EstimatedSize" ${INSTALLSIZE}
|
||||
|
||||
WriteRegStr HKCU "Software\${COMPANY_NAME} ${APP_NAME}" "" $INSTDIR
|
||||
WriteRegDWORD HKCU "Software\${COMPANY_NAME} ${APP_NAME}" "VersionMajor" "${VERSIONMAJOR}"
|
||||
WriteRegDWORD HKCU "Software\${COMPANY_NAME} ${APP_NAME}" "VersionMinor" "${VERSIONMINOR}"
|
||||
WriteRegDWORD HKCU "Software\${COMPANY_NAME} ${APP_NAME}" "VersionRevision" "77"
|
||||
WriteRegDWORD HKCU "Software\${COMPANY_NAME} ${APP_NAME}" "VersionBuild" "${VERSIONBUILD}"
|
||||
WriteRegStr HKCU "Software\${COMPANY_NAME} ${APP_PRODUCT}" "" $INSTDIR
|
||||
WriteRegDWORD HKCU "Software\${COMPANY_NAME} ${APP_PRODUCT}" "VersionMajor" "${VERSIONMAJOR}"
|
||||
WriteRegDWORD HKCU "Software\${COMPANY_NAME} ${APP_PRODUCT}" "VersionMinor" "${VERSIONMINOR}"
|
||||
WriteRegDWORD HKCU "Software\${COMPANY_NAME} ${APP_PRODUCT}" "VersionRevision" "77"
|
||||
WriteRegDWORD HKCU "Software\${COMPANY_NAME} ${APP_PRODUCT}" "VersionBuild" "${VERSIONBUILD}"
|
||||
|
||||
SectionEnd
|
||||
|
||||
|
@ -226,7 +226,7 @@ Section "Start Menu Shortcuts" sm_shorcuts
|
|||
# Start Menu
|
||||
SetShellVarContext current
|
||||
CreateDirectory "$SMPROGRAMS\${COMPANY_NAME}"
|
||||
CreateShortCut "$SMPROGRAMS\${COMPANY_NAME}\${APP_NAME}.lnk" "$INSTDIR\${TRAIN_TIMETABLE_EXE}" "" "$INSTDIR\icon.ico"
|
||||
CreateShortCut "$SMPROGRAMS\${COMPANY_NAME}\${APP_NAME}.lnk" "$INSTDIR\${MR_TIMETABLE_PLANNER_EXE}" "" "$INSTDIR\icon.ico"
|
||||
SectionEnd
|
||||
|
||||
; Open a section to register file type
|
||||
|
@ -234,11 +234,11 @@ Section "File associations" file_ass
|
|||
SetShellVarContext current
|
||||
SetOutPath $INSTDIR
|
||||
|
||||
WriteRegStr HKCU "Software\Classes\.ttt" "" "Train_Timetable.session"
|
||||
WriteRegStr HKCU "Software\Classes\.ttt" "" "MR_TIMETABLE_PLANNER.session"
|
||||
WriteRegStr HKCU "Software\Classes\.ttt" "PerceivedType" "document"
|
||||
WriteRegStr HKCU "Software\Classes\Train_Timetable.session" "" "Train Timetable Session File"
|
||||
WriteRegStr HKCU "Software\Classes\Train_Timetable.session\DefaultIcon" "" "$INSTDIR\${TRAIN_TIMETABLE_EXE},0"
|
||||
WriteRegStr HKCU "Software\Classes\Train_Timetable.session\shell\open\command" "" '$INSTDIR\${TRAIN_TIMETABLE_EXE} "%1"'
|
||||
WriteRegStr HKCU "Software\Classes\MR_TIMETABLE_PLANNER.session" "" "MRTPlanner Timetable Session File"
|
||||
WriteRegStr HKCU "Software\Classes\MR_TIMETABLE_PLANNER.session\DefaultIcon" "" "$INSTDIR\${MR_TIMETABLE_PLANNER_EXE},0"
|
||||
WriteRegStr HKCU "Software\Classes\MR_TIMETABLE_PLANNER.session\shell\open\command" "" '$INSTDIR\${MR_TIMETABLE_PLANNER_EXE} "%1"'
|
||||
|
||||
DetailPrint $INSTDIR
|
||||
|
||||
|
@ -294,7 +294,7 @@ Function .onInit
|
|||
|
||||
!insertmacro MUI_LANGDLL_DISPLAY
|
||||
|
||||
ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}" "UninstallString"
|
||||
ReadRegStr $0 HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}" "UninstallString"
|
||||
${If} $0 != ""
|
||||
${AndIf} ${Cmd} `MessageBox MB_YESNO|MB_ICONQUESTION "$(unist_previous_msg)" /SD IDYES IDYES`
|
||||
!insertmacro UninstallExisting $0 $0
|
||||
|
@ -326,7 +326,7 @@ Section un.main_program
|
|||
RMDir "$SMPROGRAMS\${COMPANY_NAME}"
|
||||
|
||||
# Remove files
|
||||
Delete $INSTDIR\${TRAIN_TIMETABLE_EXE}
|
||||
Delete $INSTDIR\${MR_TIMETABLE_PLANNER_EXE}
|
||||
Delete $INSTDIR\icon.ico
|
||||
|
||||
Delete $INSTDIR\icons\lightning.svg
|
||||
|
@ -337,11 +337,11 @@ Section un.main_program
|
|||
|
||||
# Ask user if they want to delete or keep log files. If they choose to keep them AppData folder is not removed
|
||||
MessageBox MB_YESNO "$(keep_logs_message)" IDYES delete_settings
|
||||
RMDir /r "$LOCALAPPDATA\${COMPANY_NAME}\${APP_NAME}\logs"
|
||||
RMDir /r "$LOCALAPPDATA\${COMPANY_NAME}\${APP_PRODUCT}\logs"
|
||||
|
||||
delete_settings:
|
||||
Delete "$LOCALAPPDATA\${COMPANY_NAME}\${APP_NAME}\${TRAIN_TIMETABLE_SETTINGS}"
|
||||
RMDir "$LOCALAPPDATA\${COMPANY_NAME}\${APP_NAME}"
|
||||
Delete "$LOCALAPPDATA\${COMPANY_NAME}\${APP_PRODUCT}\${MR_TIMETABLE_PLANNER_SETTINGS}"
|
||||
RMDir "$LOCALAPPDATA\${COMPANY_NAME}\${APP_PRODUCT}"
|
||||
RMDir "$LOCALAPPDATA\${COMPANY_NAME}"
|
||||
|
||||
Delete $INSTDIR\*.dll
|
||||
|
@ -361,8 +361,8 @@ delete_settings:
|
|||
RMDir /r $INSTDIR\imageformats
|
||||
|
||||
# Remove uninstaller information from the registry
|
||||
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_NAME}"
|
||||
DeleteRegKey HKCU "Software\${COMPANY_NAME} ${APP_NAME}"
|
||||
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\${COMPANY_NAME} ${APP_PRODUCT}"
|
||||
DeleteRegKey HKCU "Software\${COMPANY_NAME} ${APP_PRODUCT}"
|
||||
|
||||
; Unregister file associations in uninstall.exe
|
||||
!macro AssocDeleteFileExtAndProgId _hkey _dotext _pid
|
||||
|
@ -373,7 +373,7 @@ delete_settings:
|
|||
DeleteRegKey ${_hkey} "Software\Classes\${_pid}"
|
||||
!macroend
|
||||
|
||||
!insertmacro AssocDeleteFileExtAndProgId HKCU ".ttt" "Train_Timetable.session"
|
||||
!insertmacro AssocDeleteFileExtAndProgId HKCU ".ttt" "MR_TIMETABLE_PLANNER.session"
|
||||
|
||||
DetailPrint $INSTDIR
|
||||
DetailPrint $OUTDIR
|
||||
|
|
|
@ -23,7 +23,7 @@ VS_VERSION_INFO VERSIONINFO
|
|||
VALUE "FileDescription", "${APP_DISPLAY_NAME}\0"
|
||||
VALUE "FileVersion", "${PROJECT_VERSION}\0"
|
||||
VALUE "LegalCopyright", "${APP_COMPANY_NAME}\0"
|
||||
VALUE "OriginalFilename", "${TRAINTIMETABLE_TARGET}.exe\0"
|
||||
VALUE "OriginalFilename", "${MR_TIMETABLE_PLANNER_TARGET}.exe\0"
|
||||
VALUE "ProductName", "${APP_PRODUCT_NAME}\0"
|
||||
VALUE "ProductVersion", "${PROJECT_VERSION}\0"
|
||||
END
|
||||
|
|
After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 37 KiB |
After Width: | Height: | Size: 140 KiB |
After Width: | Height: | Size: 79 KiB |
|
@ -1,9 +1,13 @@
|
|||
|
||||
include(GNUInstallDirs)
|
||||
|
||||
include(DLL_Utils)
|
||||
|
||||
#Set Win32 resources
|
||||
if (WIN32)
|
||||
configure_file(../packaging/windows/resources.rc.in ${CMAKE_BINARY_DIR}/resources/resources.rc)
|
||||
set(TRAINTIMETABLE_RESOURCES
|
||||
${TRAINTIMETABLE_RESOURCES}
|
||||
set(MR_TIMETABLE_PLANNER_RESOURCES
|
||||
${MR_TIMETABLE_PLANNER_RESOURCES}
|
||||
${CMAKE_BINARY_DIR}/resources/resources.rc
|
||||
)
|
||||
endif()
|
||||
|
@ -13,7 +17,6 @@ add_subdirectory(backgroundmanager)
|
|||
add_subdirectory(db_metadata)
|
||||
add_subdirectory(graph)
|
||||
add_subdirectory(jobs)
|
||||
add_subdirectory(lines)
|
||||
add_subdirectory(odt_export)
|
||||
add_subdirectory(printing)
|
||||
add_subdirectory(rollingstock)
|
||||
|
@ -27,24 +30,24 @@ add_subdirectory(translations)
|
|||
add_subdirectory(utils)
|
||||
add_subdirectory(viewmanager)
|
||||
|
||||
# Set TrainTimetable info template file
|
||||
set(TRAINTIMETABLE_SOURCES
|
||||
${TRAINTIMETABLE_SOURCES}
|
||||
# Set ModelRailroadTimetablePlanner info template file
|
||||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
app/info.h.in
|
||||
)
|
||||
configure_file(app/info.h.in ${CMAKE_BINARY_DIR}/include/info.h)
|
||||
|
||||
# Add executable
|
||||
add_executable(${TRAINTIMETABLE_TARGET} WIN32
|
||||
${TRAINTIMETABLE_SOURCES}
|
||||
${TRAINTIMETABLE_UI_FILES}
|
||||
${TRAINTIMETABLE_RESOURCES}
|
||||
add_executable(${MR_TIMETABLE_PLANNER_TARGET} WIN32
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
${MR_TIMETABLE_PLANNER_UI_FILES}
|
||||
${MR_TIMETABLE_PLANNER_RESOURCES}
|
||||
)
|
||||
|
||||
# Set compiler options
|
||||
if(MSVC)
|
||||
target_compile_options(
|
||||
${TRAINTIMETABLE_TARGET}
|
||||
${MR_TIMETABLE_PLANNER_TARGET}
|
||||
PRIVATE
|
||||
/WX
|
||||
/wd4267
|
||||
|
@ -57,7 +60,7 @@ if(MSVC)
|
|||
)
|
||||
else()
|
||||
target_compile_options(
|
||||
${TRAINTIMETABLE_TARGET}
|
||||
${MR_TIMETABLE_PLANNER_TARGET}
|
||||
PRIVATE
|
||||
"$<$<CONFIG:RELEASE>:-O2>"
|
||||
#-Werror
|
||||
|
@ -72,7 +75,7 @@ endif()
|
|||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_options(
|
||||
${TRAINTIMETABLE_TARGET}
|
||||
${MR_TIMETABLE_PLANNER_TARGET}
|
||||
PRIVATE
|
||||
-rdynamic
|
||||
)
|
||||
|
@ -80,16 +83,15 @@ endif()
|
|||
|
||||
# Set include directories
|
||||
target_include_directories(
|
||||
${TRAINTIMETABLE_TARGET}
|
||||
${MR_TIMETABLE_PLANNER_TARGET}
|
||||
PRIVATE
|
||||
${SQLite3_INCLUDE_DIRS}
|
||||
${ZIP_INCLUDE_DIR}
|
||||
${CMAKE_BINARY_DIR}/include #For template files
|
||||
)
|
||||
|
||||
# Set link libraries
|
||||
target_link_libraries(
|
||||
${TRAINTIMETABLE_TARGET}
|
||||
${MR_TIMETABLE_PLANNER_TARGET}
|
||||
PRIVATE
|
||||
Qt5::Core
|
||||
Qt5::Gui
|
||||
|
@ -98,95 +100,48 @@ target_link_libraries(
|
|||
Qt5::PrintSupport
|
||||
${SQLite3_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
${ZIP_LIBRARY}
|
||||
ssplib::ssplib
|
||||
)
|
||||
|
||||
# Link LibZip
|
||||
if(TARGET libzip::zip)
|
||||
# LibZip was found with Config Package
|
||||
# Include directory setup is automatic
|
||||
|
||||
target_link_libraries(
|
||||
${MR_TIMETABLE_PLANNER_TARGET}
|
||||
PRIVATE
|
||||
libzip::zip
|
||||
)
|
||||
|
||||
get_target_property(LibZip_LIBRARY_TO_INSTALL libzip::zip LOCATION)
|
||||
else()
|
||||
# LibZip was found with our Find Module
|
||||
# Set include directories manually
|
||||
target_include_directories(
|
||||
${MR_TIMETABLE_PLANNER_TARGET}
|
||||
PRIVATE
|
||||
${LibZip_INCLUDE_DIRS}
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
${MR_TIMETABLE_PLANNER_TARGET}
|
||||
PRIVATE
|
||||
${LibZip_LIBRARIES}
|
||||
)
|
||||
get_dll_library_from_import_library(${LibZip_LIBRARIES} LibZip_LIBRARY_TO_INSTALL)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(
|
||||
${TRAINTIMETABLE_TARGET}
|
||||
${MR_TIMETABLE_PLANNER_TARGET}
|
||||
PRIVATE
|
||||
DbgHelp
|
||||
)
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
install(TARGETS ${TRAINTIMETABLE_TARGET} RUNTIME DESTINATION bin)
|
||||
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(${TRAINTIMETABLE_TARGET} PRIVATE ${TRAINTIMETABLE_DEFINITIONS})
|
||||
target_compile_definitions(${MR_TIMETABLE_PLANNER_TARGET} PRIVATE ${MR_TIMETABLE_PLANNER_DEFINITIONS})
|
||||
|
||||
## Doxygen documentation ##
|
||||
if(DOXYGEN_FOUND)
|
||||
|
@ -196,13 +151,13 @@ if(DOXYGEN_FOUND)
|
|||
set(DOXYGEN_EXTRACT_ALL "YES")
|
||||
set(DOXYGEN_EXTRACT_PRIVATE "YES")
|
||||
set(DOXYGEN_DOT_GRAPH_MAX_NODES 100)
|
||||
doxygen_add_docs(docs ALL ${TRAINTIMETABLE_SOURCES})
|
||||
doxygen_add_docs(docs ALL ${MR_TIMETABLE_PLANNER_SOURCES})
|
||||
endif()
|
||||
## Doxygen end ##
|
||||
|
||||
## Update/Release translations ##
|
||||
|
||||
#(Run this target before installing installing and every time you update translations)
|
||||
#(Run this target before installing and every time you update translations)
|
||||
add_custom_target(RELEASE_TRANSLATIONS ALL
|
||||
COMMENT "Running translations it_IT...")
|
||||
|
||||
|
@ -211,14 +166,14 @@ if(UPDATE_TS)
|
|||
if(UPDATE_TS_KEEP_OBSOLETE)
|
||||
add_custom_command(TARGET RELEASE_TRANSLATIONS
|
||||
POST_BUILD
|
||||
COMMAND ${Qt5_LUPDATE_EXECUTABLE} ARGS ${CMAKE_SOURCE_DIR}/src -ts ${TRAINTIMETABLE_TS_FILES}
|
||||
COMMAND ${Qt5_LUPDATE_EXECUTABLE} ARGS ${CMAKE_SOURCE_DIR}/src -ts ${MR_TIMETABLE_PLANNER_TS_FILES}
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src
|
||||
COMMENT "Updating translations"
|
||||
VERBATIM)
|
||||
else()
|
||||
add_custom_command(TARGET RELEASE_TRANSLATIONS
|
||||
POST_BUILD
|
||||
COMMAND ${Qt5_LUPDATE_EXECUTABLE} ARGS ${CMAKE_SOURCE_DIR}/src -ts ${TRAINTIMETABLE_TS_FILES} -no-obsolete
|
||||
COMMAND ${Qt5_LUPDATE_EXECUTABLE} ARGS ${CMAKE_SOURCE_DIR}/src -ts ${MR_TIMETABLE_PLANNER_TS_FILES} -no-obsolete
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/src
|
||||
COMMENT "Updating translations"
|
||||
VERBATIM)
|
||||
|
@ -227,7 +182,7 @@ if(UPDATE_TS)
|
|||
endif()
|
||||
|
||||
# For each .ts file release a .qm file
|
||||
foreach(TS_FILE ${TRAINTIMETABLE_TS_FILES})
|
||||
foreach(TS_FILE ${MR_TIMETABLE_PLANNER_TS_FILES})
|
||||
get_filename_component(QM_FILE_NAME ${TS_FILE} NAME_WLE)
|
||||
set(QM_FILE "${CMAKE_BINARY_DIR}/src/translations/${QM_FILE_NAME}.qm")
|
||||
message("Generating translation: ${QM_FILE}")
|
||||
|
@ -243,49 +198,87 @@ endforeach()
|
|||
## Install and Deploy ##
|
||||
|
||||
# Copy executable
|
||||
install(TARGETS ${TRAINTIMETABLE_TARGET}
|
||||
RUNTIME
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX})
|
||||
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_PREFIX}/icons)
|
||||
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 ${TRAINTIMETABLE_TS_FILES})
|
||||
foreach(TS_FILE ${MR_TIMETABLE_PLANNER_TS_FILES})
|
||||
get_filename_component(QM_FILE_NAME ${TS_FILE} NAME_WLE)
|
||||
install(FILES
|
||||
"${CMAKE_BINARY_DIR}/src/translations/${QM_FILE_NAME}.qm"
|
||||
DESTINATION ${CMAKE_INSTALL_PREFIX}/translations OPTIONAL)
|
||||
DESTINATION ${CMAKE_INSTALL_BINDIR}/translations OPTIONAL)
|
||||
endforeach()
|
||||
|
||||
if(WIN32)
|
||||
# Copy SQlite3 DLL
|
||||
install(PROGRAMS ${SQLite3_LIBRARIES} DESTINATION ${CMAKE_INSTALL_PREFIX})
|
||||
get_dll_library_from_import_library(${SQLite3_LIBRARIES} SQLite3_LIBRARY_TO_INSTALL)
|
||||
install(PROGRAMS ${SQLite3_LIBRARY_TO_INSTALL} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
# Copy ZLib DLL
|
||||
install(PROGRAMS ${ZLIB_LIBRARIES} DESTINATION ${CMAKE_INSTALL_PREFIX})
|
||||
get_dll_library_from_import_library(${ZLIB_LIBRARIES} ZLIB_LIBRARY_TO_INSTALL)
|
||||
install(PROGRAMS ${ZLIB_LIBRARY_TO_INSTALL} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
# Copy libzip DLL
|
||||
install(PROGRAMS ${ZIP_LIBRARY} DESTINATION ${CMAKE_INSTALL_PREFIX})
|
||||
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}\")
|
||||
execute_process(COMMAND ${WINDEPLOYQT_EXE} ${CMAKE_INSTALL_PREFIX}
|
||||
WORKING_DIRECTORY ${CMAKE_INSTALL_PREFIX}
|
||||
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_EXE_RESULT}\")
|
||||
|
||||
message(STATUS \"windeployqt Done.\")
|
||||
message(STATUS \"${windeployqt_exe} Done.\")
|
||||
")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
set(LINUX_ICON_DIR_BASE "/usr/share/icons/hicolor")
|
||||
|
||||
# Install PNG icons
|
||||
set(MY_ICON_SIZES "16;24;32;64;128;256;512")
|
||||
foreach(ICON_SIZE ${MY_ICON_SIZES})
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/packaging/linux/icons/train_${ICON_SIZE}.png
|
||||
DESTINATION ${LINUX_ICON_DIR_BASE}/${ICON_SIZE}x${ICON_SIZE}/apps/
|
||||
RENAME ${MR_TIMETABLE_PLANNER_TARGET}.png)
|
||||
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/packaging/linux/icons/train_${ICON_SIZE}.png
|
||||
DESTINATION ${LINUX_ICON_DIR_BASE}/${ICON_SIZE}x${ICON_SIZE}/mimetypes/
|
||||
RENAME application-x-${MR_TIMETABLE_PLANNER_TARGET}-session-sqlite3.png)
|
||||
endforeach()
|
||||
|
||||
# Install SVG icon
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/packaging/linux/icons/train.svg
|
||||
DESTINATION ${LINUX_ICON_DIR_BASE}/scalable/apps/
|
||||
RENAME ${MR_TIMETABLE_PLANNER_TARGET}.svg)
|
||||
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/packaging/linux/icons/train.svg
|
||||
DESTINATION ${LINUX_ICON_DIR_BASE}/scalable/mimetypes/
|
||||
RENAME application-vnd.${APP_COMPANY_NAME_LOWER}-${MR_TIMETABLE_PLANNER_TARGET}-session-sqlite3.svg)
|
||||
|
||||
# Install desktop file
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/packaging/linux/${MR_TIMETABLE_PLANNER_TARGET}.desktop
|
||||
DESTINATION /usr/share/applications/)
|
||||
|
||||
# Install mime type association
|
||||
#vnd.trainsoftware-mrtplanner.xml
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/packaging/linux/mime/vnd.${APP_COMPANY_NAME_LOWER}-${MR_TIMETABLE_PLANNER_TARGET}.xml
|
||||
DESTINATION /usr/share/mime/packages)
|
||||
endif(UNIX AND NOT APPLE)
|
||||
|
||||
## Install end ##
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
set(TRAINTIMETABLE_SOURCES
|
||||
${TRAINTIMETABLE_SOURCES}
|
||||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
app/main.cpp
|
||||
app/mainwindow.h
|
||||
app/mainwindow.cpp
|
||||
|
@ -12,8 +12,8 @@ set(TRAINTIMETABLE_SOURCES
|
|||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
set(TRAINTIMETABLE_UI_FILES
|
||||
${TRAINTIMETABLE_UI_FILES}
|
||||
set(MR_TIMETABLE_PLANNER_UI_FILES
|
||||
${MR_TIMETABLE_PLANNER_UI_FILES}
|
||||
app/mainwindow.ui
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
|
|
@ -6,7 +6,9 @@
|
|||
static const QString AppVersion = QStringLiteral("${PROJECT_VERSION}");
|
||||
static const QString AppCompany = QStringLiteral("${APP_COMPANY_NAME}");
|
||||
static const QString AppProduct = QStringLiteral("${APP_PRODUCT_NAME}");
|
||||
static const QString AppProductShort = QStringLiteral("${MR_TIMETABLE_PLANNER_TARGET}");
|
||||
static const QString AppDisplayName = QStringLiteral("${APP_DISPLAY_NAME}");
|
||||
static const QString AppProjectWebSite = QStringLiteral("${PROJECT_HOMEPAGE_URL}");
|
||||
|
||||
static const QString FormatVersionStr = QStringLiteral("${DB_FORMAT_VERSION}");
|
||||
|
||||
|
|
212
src/app/main.cpp
|
@ -1,19 +1,32 @@
|
|||
/*
|
||||
* 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"
|
||||
|
||||
#include <QTranslator>
|
||||
#include "utils/localization/languageutils.h"
|
||||
|
||||
#include <QTextStream>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <QSettings>
|
||||
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
|
||||
#include <QDateTime>
|
||||
|
||||
#include "info.h"
|
||||
|
@ -22,7 +35,7 @@
|
|||
|
||||
#include <QMutex>
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QDebug>
|
||||
|
||||
Q_GLOBAL_STATIC(QFile, gLogFile)
|
||||
static QtMessageHandler defaultHandler;
|
||||
|
@ -33,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());
|
||||
|
@ -58,143 +77,126 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
|
|||
defaultHandler(type, context, msg);
|
||||
}
|
||||
|
||||
void loadTranslations()
|
||||
{
|
||||
QLocale locale = AppSettings.getLanguage();
|
||||
|
||||
qDebug() << "Locale:" << locale << locale.uiLanguages();
|
||||
|
||||
QString path = qApp->applicationDirPath() + QStringLiteral("/translations");
|
||||
qDebug() << path;
|
||||
|
||||
QTranslator *translatorQt = new QTranslator(qApp);
|
||||
if(translatorQt->load(locale,
|
||||
QStringLiteral("qt"), QStringLiteral("_"),
|
||||
path))
|
||||
{
|
||||
qDebug() << "Loading Qt translations";
|
||||
qApp->installTranslator(translatorQt);
|
||||
}
|
||||
|
||||
QTranslator *translator = new QTranslator(qApp);
|
||||
if(translator->load(locale,
|
||||
QStringLiteral("traintimetable"), QStringLiteral("_"),
|
||||
path))
|
||||
{
|
||||
qDebug() << "Loading UI translations";
|
||||
qApp->installTranslator(translator);
|
||||
}
|
||||
}
|
||||
|
||||
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/traintimetable_log.log"));
|
||||
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;
|
||||
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;
|
||||
loadTranslations();
|
||||
|
||||
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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
|
@ -15,18 +34,13 @@ class database;
|
|||
|
||||
using namespace sqlite3pp;
|
||||
|
||||
class QGraphicsView;
|
||||
class QGraphicsScene;
|
||||
class LineGraphWidget;
|
||||
class JobPathEditor;
|
||||
class QDockWidget;
|
||||
class QLabel;
|
||||
class QActionGroup;
|
||||
class CustomCompletionLineEdit;
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
class RsErrorsWidget;
|
||||
#endif
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
@ -43,7 +57,7 @@ public:
|
|||
void loadFile(const QString &fileName);
|
||||
|
||||
bool closeSession();
|
||||
|
||||
|
||||
private slots:
|
||||
void onStationManager();
|
||||
|
||||
|
@ -80,6 +94,8 @@ private slots:
|
|||
void checkLineNumber();
|
||||
|
||||
protected:
|
||||
void timerEvent(QTimerEvent *e) override;
|
||||
|
||||
void closeEvent(QCloseEvent *e) override;
|
||||
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
|
@ -89,12 +105,13 @@ private slots:
|
|||
|
||||
void onSessionRSViewer();
|
||||
|
||||
void onJobSearchItemSelected(db_id jobId);
|
||||
void onJobSearchItemSelected();
|
||||
void onJobSearchResultsReady();
|
||||
|
||||
private:
|
||||
void setup_actions();
|
||||
void showCloseWarning();
|
||||
void stopCloseTimer();
|
||||
|
||||
enum class CentralWidgetMode
|
||||
{
|
||||
|
@ -110,25 +127,28 @@ private:
|
|||
|
||||
JobPathEditor *jobEditor;
|
||||
|
||||
#ifdef ENABLE_RS_CHECKER
|
||||
RsErrorsWidget *rsErrorsWidget;
|
||||
QDockWidget *rsErrDock;
|
||||
#endif
|
||||
#ifdef ENABLE_BACKGROUND_MANAGER
|
||||
QDockWidget *resPanelDock;
|
||||
#endif // ENABLE_BACKGROUND_MANAGER
|
||||
|
||||
QGraphicsView *view;
|
||||
LineGraphWidget *view;
|
||||
QDockWidget *jobDock;
|
||||
|
||||
CustomCompletionLineEdit *searchEdit;
|
||||
CustomCompletionLineEdit *lineComboSearch;
|
||||
|
||||
QLabel *welcomeLabel;
|
||||
|
||||
QActionGroup *databaseActionGroup;
|
||||
|
||||
enum { MaxRecentFiles = 5 };
|
||||
QAction* recentFileActs[MaxRecentFiles];
|
||||
enum
|
||||
{
|
||||
MaxRecentFiles = 5
|
||||
};
|
||||
QAction *recentFileActs[MaxRecentFiles];
|
||||
|
||||
CentralWidgetMode m_mode;
|
||||
|
||||
int closeTimerId;
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
|
|
|
@ -6,8 +6,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
<width>500</width>
|
||||
<height>400</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
|
@ -21,8 +21,8 @@
|
|||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>26</height>
|
||||
<width>500</width>
|
||||
<height>25</height>
|
||||
</rect>
|
||||
</property>
|
||||
<widget class="QMenu" name="menuFile">
|
||||
|
@ -122,7 +122,7 @@
|
|||
<string>Remove Job</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Remove selected train job</string>
|
||||
<string>Remove Job</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="actionStations">
|
||||
|
@ -205,7 +205,7 @@
|
|||
</action>
|
||||
<action name="actionNext_Job_Segment">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Next Job Segment</string>
|
||||
|
@ -213,7 +213,7 @@
|
|||
</action>
|
||||
<action name="actionPrev_Job_Segment">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Prev Job Segment</string>
|
||||
|
|
|
@ -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,8 +1,26 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
#include "utils/types.h"
|
||||
#include "utils/directiontype.h"
|
||||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
using namespace sqlite3pp;
|
||||
|
@ -15,9 +33,6 @@ using namespace sqlite3pp;
|
|||
|
||||
#include <settings/appsettings.h>
|
||||
|
||||
class LineStorage;
|
||||
class JobStorage;
|
||||
|
||||
class ViewManager;
|
||||
class MetaDataManager;
|
||||
|
||||
|
@ -25,6 +40,8 @@ class MetaDataManager;
|
|||
class BackgroundManager;
|
||||
#endif
|
||||
|
||||
class QTranslator;
|
||||
|
||||
enum class DB_Error
|
||||
{
|
||||
NoError = 0,
|
||||
|
@ -36,51 +53,75 @@ enum class DB_Error
|
|||
FormatTooNew
|
||||
};
|
||||
|
||||
//TODO: reorder functions
|
||||
/*!
|
||||
* \brief The MeetingSession class
|
||||
*
|
||||
* This class is a singleton.
|
||||
* It stores all informations about current loaded session and settings
|
||||
*/
|
||||
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 SYNC: wire them from models
|
||||
// Rollingstock
|
||||
void rollingstockRemoved(db_id rsId);
|
||||
void rollingStockPlanChanged(db_id rsId);
|
||||
void rollingStockPlanChanged(QSet<db_id> rsIds);
|
||||
void rollingStockModified(db_id rsId);
|
||||
|
||||
//Jobs
|
||||
void jobChanged(db_id jobId, db_id oldJobId); //Updated id/category/stops
|
||||
// Jobs
|
||||
void jobAdded(db_id jobId);
|
||||
void jobChanged(db_id jobId, db_id oldJobId); // Updated id/category/stops
|
||||
void jobRemoved(db_id jobId);
|
||||
|
||||
//TODO: old methods, remove them
|
||||
public:
|
||||
qreal getStationGraphPos(db_id lineId, db_id stId, int platf = 0);
|
||||
// Stations
|
||||
void stationNameChanged(db_id stationId);
|
||||
void stationJobsPlanChanged(const QSet<db_id> &stationIds);
|
||||
void stationTrackPlanChanged(const QSet<db_id> &stationIds);
|
||||
void stationRemoved(db_id stationId);
|
||||
|
||||
bool getPrevStop(db_id stopId, db_id &prevSt, db_id &lineId);
|
||||
bool getNextStop(db_id stopId, db_id &nextSt, db_id &lineId);
|
||||
Direction getStopDirection(db_id stopId, db_id stId);
|
||||
// Segments
|
||||
void segmentAdded(db_id segmentId);
|
||||
void segmentNameChanged(db_id segmentId);
|
||||
void segmentStationsChanged(db_id segmentId);
|
||||
void segmentRemoved(db_id segmentId);
|
||||
|
||||
// Lines
|
||||
void lineAdded(db_id lineId);
|
||||
void lineNameChanged(db_id lineId);
|
||||
void lineSegmentsChanged(db_id lineId);
|
||||
void lineRemoved(db_id lineId);
|
||||
|
||||
private:
|
||||
std::unique_ptr<ViewManager> viewManager;
|
||||
|
@ -91,71 +132,130 @@ private:
|
|||
std::unique_ptr<BackgroundManager> backgroundManager;
|
||||
#endif
|
||||
|
||||
public:
|
||||
LineStorage *mLineStorage;
|
||||
JobStorage *mJobStorage;
|
||||
|
||||
//Settings TODO: remove
|
||||
// Settings TODO: remove
|
||||
public:
|
||||
void loadSettings(const QString &settings_file);
|
||||
|
||||
TrainTimetableSettings settings;
|
||||
MRTPSettings settings;
|
||||
|
||||
int hourOffset;
|
||||
int stationOffset;
|
||||
qreal platformOffset;
|
||||
int hourOffset;
|
||||
int stationOffset;
|
||||
qreal platformOffset;
|
||||
|
||||
int horizOffset;
|
||||
int vertOffset;
|
||||
int horizOffset;
|
||||
int vertOffset;
|
||||
|
||||
int jobLineWidth;
|
||||
|
||||
//Queries TODO: remove
|
||||
// Database
|
||||
public:
|
||||
database m_Db;
|
||||
|
||||
query q_getPrevStop;
|
||||
query q_getNextStop;
|
||||
|
||||
query q_getKmDirection;
|
||||
|
||||
//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();
|
||||
bool clearImportRSTables();
|
||||
|
||||
void prepareQueryes();
|
||||
void finalizeStatements();
|
||||
QString fileName;
|
||||
|
||||
QString fileName; //TODO: re organize variables
|
||||
|
||||
//AppData
|
||||
// AppData
|
||||
public:
|
||||
static void locateAppdata();
|
||||
static QString appDataPath;
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief sheetExportTranslator
|
||||
*
|
||||
* Custom translator for Sheet Export
|
||||
* When it's valid it should be used before application translations
|
||||
* When it's \c nullptr, application translations should be used
|
||||
* Special case: when it's \c nullptr and \a sheetExportLocale is \c QLocale(QLocale::English)
|
||||
* which is default language, then use hardcoded string literals and bypass any translation.
|
||||
*
|
||||
* \sa sheetExportLocale
|
||||
* \sa setSheetExportTranslator()
|
||||
*/
|
||||
QTranslator *sheetExportTranslator;
|
||||
|
||||
/*!
|
||||
* \brief sheetExportLocale
|
||||
*
|
||||
* Locale for Sheet Export
|
||||
* \sa sheetExportTranslator
|
||||
*/
|
||||
QLocale sheetExportLocale;
|
||||
|
||||
/*!
|
||||
* \brief originalAppLocale
|
||||
* Store original language in which application was loaded
|
||||
* When user changes Application Language in settings a restart
|
||||
* is required to apply it but settings reports already new language.
|
||||
* So use this variable to get original settings value before restart.
|
||||
*/
|
||||
QLocale originalAppLocale;
|
||||
|
||||
/*!
|
||||
* \brief setSheetExportTranslator
|
||||
* \param translator
|
||||
* \param loc
|
||||
*
|
||||
* Set translator for Sheet Export
|
||||
* \sa sheetExportTranslator
|
||||
*/
|
||||
|
||||
public:
|
||||
/*!
|
||||
* \brief Embedded Locale
|
||||
*
|
||||
* This represents the QLocale of embedded strings (American English).
|
||||
* This is the default Application Language if no translations are loaded
|
||||
* If user choose this language no translations need to be loaded.
|
||||
*/
|
||||
static const QLocale embeddedLocale;
|
||||
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
#define Session MeetingSession::Get()
|
||||
#define Session MeetingSession::Get()
|
||||
|
||||
#define AppSettings Session->settings
|
||||
|
||||
|
|
|
@ -1,6 +1,18 @@
|
|||
set(TRAINTIMETABLE_SOURCES
|
||||
${TRAINTIMETABLE_SOURCES}
|
||||
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,5 +1,5 @@
|
|||
set(TRAINTIMETABLE_SOURCES
|
||||
${TRAINTIMETABLE_SOURCES}
|
||||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
db_metadata/imagemetadata.h
|
||||
db_metadata/imagemetadata.cpp
|
||||
db_metadata/meetinginformationdialog.h
|
||||
|
@ -9,8 +9,8 @@ set(TRAINTIMETABLE_SOURCES
|
|||
PARENT_SCOPE
|
||||
)
|
||||
|
||||
set(TRAINTIMETABLE_UI_FILES
|
||||
${TRAINTIMETABLE_UI_FILES}
|
||||
set(MR_TIMETABLE_PLANNER_UI_FILES
|
||||
${MR_TIMETABLE_PLANNER_UI_FILES}
|
||||
db_metadata/meetinginformationdialog.ui
|
||||
PARENT_SCOPE
|
||||
)
|
||||
|
|
|
@ -1,32 +1,115 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
namespace ImageMetaData
|
||||
{
|
||||
#include <QDebug>
|
||||
|
||||
namespace ImageMetaData {
|
||||
|
||||
constexpr char sql_get_key_id[] = "SELECT rowid FROM metadata WHERE name=? AND val NOT NULL";
|
||||
|
||||
ImageBlobDevice::ImageBlobDevice(sqlite3 *db, qint64 rowId, QObject *parent) :
|
||||
ImageBlobDevice::ImageBlobDevice(sqlite3 *db, QObject *parent) :
|
||||
QIODevice(parent),
|
||||
mRowId(rowId),
|
||||
mRowId(0),
|
||||
mSize(0),
|
||||
mDb(db),
|
||||
mBlob(nullptr)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
ImageBlobDevice::~ImageBlobDevice()
|
||||
{
|
||||
close();
|
||||
ImageBlobDevice::close();
|
||||
}
|
||||
|
||||
void ImageBlobDevice::setBlobInfo(const QByteArray &table, const QByteArray &column, qint64 rowId)
|
||||
{
|
||||
mRowId = rowId;
|
||||
mTable = table;
|
||||
mColumn = column;
|
||||
}
|
||||
|
||||
bool ImageBlobDevice::reserveSizeAndReset(qint64 len)
|
||||
{
|
||||
// NOTE: this will discard any previous content
|
||||
|
||||
// Close previous BLOB handle
|
||||
if (mBlob)
|
||||
close();
|
||||
|
||||
// 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)
|
||||
{
|
||||
qWarning() << "ImageBlobDevice::reserveSizeAndReset cannot prepare:" << sqlite3_errmsg(mDb);
|
||||
setErrorString(tr("Cannot query database"));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reserve BLOB memory
|
||||
rc = sqlite3_bind_zeroblob64(stmt, 1, len);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
}
|
||||
rc = sqlite3_bind_int64(stmt, 2, mRowId);
|
||||
if (rc != SQLITE_OK)
|
||||
{
|
||||
sqlite3_finalize(stmt);
|
||||
return false;
|
||||
}
|
||||
|
||||
rc = sqlite3_step(stmt);
|
||||
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
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
|
||||
return open(QIODevice::ReadWrite);
|
||||
}
|
||||
|
||||
bool ImageBlobDevice::open(QIODevice::OpenMode mode)
|
||||
{
|
||||
mode |= QIODevice::ReadOnly;
|
||||
int rc = sqlite3_blob_open(mDb, "main", "metadata", "val", mRowId, (mode & QIODevice::WriteOnly) != 0, &mBlob);
|
||||
if(rc != SQLITE_OK || !mBlob)
|
||||
if (isOpen())
|
||||
{
|
||||
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)
|
||||
{
|
||||
mBlob = nullptr;
|
||||
setErrorString(sqlite3_errmsg(mDb));
|
||||
|
@ -42,7 +125,7 @@ bool ImageBlobDevice::open(QIODevice::OpenMode mode)
|
|||
|
||||
void ImageBlobDevice::close()
|
||||
{
|
||||
if(mBlob)
|
||||
if (mBlob)
|
||||
{
|
||||
sqlite3_blob_close(mBlob);
|
||||
mBlob = nullptr;
|
||||
|
@ -59,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));
|
||||
|
@ -82,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;
|
||||
|
@ -129,17 +213,19 @@ ImageBlobDevice* getImage(sqlite3pp::database& db, const MetaDataManager::Key &k
|
|||
rowId = sqlite3_column_int64(stmt, 0);
|
||||
sqlite3_finalize(stmt);
|
||||
|
||||
if(!rowId)
|
||||
if (!rowId)
|
||||
return nullptr;
|
||||
|
||||
return new ImageBlobDevice(db.db(), rowId);
|
||||
ImageBlobDevice *dev = new ImageBlobDevice(db.db());
|
||||
dev->setBlobInfo("metadata", "val", rowId);
|
||||
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,16 +27,20 @@
|
|||
typedef struct sqlite3 sqlite3;
|
||||
typedef struct sqlite3_blob sqlite3_blob;
|
||||
|
||||
namespace ImageMetaData
|
||||
{
|
||||
namespace ImageMetaData {
|
||||
|
||||
// TODO: move to utils
|
||||
class ImageBlobDevice : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ImageBlobDevice(sqlite3 *db, qint64 rowId, QObject *parent = nullptr);
|
||||
ImageBlobDevice(sqlite3 *db, QObject *parent = nullptr);
|
||||
~ImageBlobDevice() override;
|
||||
|
||||
void setBlobInfo(const QByteArray &table, const QByteArray &column, qint64 rowId);
|
||||
|
||||
bool reserveSizeAndReset(qint64 len);
|
||||
|
||||
virtual bool open(OpenMode mode) override;
|
||||
virtual void close() override;
|
||||
|
||||
|
@ -32,10 +55,13 @@ private:
|
|||
qint64 mSize;
|
||||
sqlite3 *mDb;
|
||||
sqlite3_blob *mBlob;
|
||||
|
||||
QByteArray mTable;
|
||||
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"
|
||||
|
||||
|
@ -11,9 +30,10 @@
|
|||
#include <QBuffer>
|
||||
#include <QMessageBox>
|
||||
#include <QFileDialog>
|
||||
#include <QStandardPaths>
|
||||
#include "utils/files/recentdirstore.h"
|
||||
|
||||
#include "utils/imageviewer.h"
|
||||
#include "utils/delegates/imageviewer/imageviewer.h"
|
||||
#include "utils/owningqpointer.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
|
@ -27,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);
|
||||
}
|
||||
}
|
||||
|
@ -62,7 +88,7 @@ bool MeetingInformationDialog::loadData()
|
|||
{
|
||||
MetaDataManager *meta = Session->getMetaDataManager();
|
||||
|
||||
qint64 tmp = 0;
|
||||
qint64 tmp = 0;
|
||||
QDate date;
|
||||
|
||||
switch (meta->getInt64(tmp, MetaDataKey::MeetingStartDate))
|
||||
|
@ -73,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();
|
||||
}
|
||||
|
@ -106,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;
|
||||
|
@ -125,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());
|
||||
|
@ -150,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);
|
||||
}
|
||||
|
@ -169,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();
|
||||
}
|
||||
}
|
||||
|
@ -181,69 +214,76 @@ void MeetingInformationDialog::saveData()
|
|||
|
||||
void MeetingInformationDialog::showImage()
|
||||
{
|
||||
ImageViewer dlg(this);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
dlg.setImage(img);
|
||||
dlg->setImage(img);
|
||||
|
||||
dlg.exec();
|
||||
dlg->exec();
|
||||
|
||||
if(!needsToSaveImg)
|
||||
img = QImage(); //Cleanup to free memory
|
||||
if (!needsToSaveImg)
|
||||
img = QImage(); // Cleanup to free memory
|
||||
}
|
||||
|
||||
void MeetingInformationDialog::importImage()
|
||||
{
|
||||
QFileDialog dlg(this, tr("Import image"));
|
||||
dlg.setFileMode(QFileDialog::ExistingFile);
|
||||
dlg.setAcceptMode(QFileDialog::AcceptOpen);
|
||||
dlg.setDirectory(QStandardPaths::writableLocation(QStandardPaths::PicturesLocation));
|
||||
const QLatin1String meeting_image_key = QLatin1String("meeting_image_key");
|
||||
|
||||
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));
|
||||
|
||||
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);
|
||||
dlg->setMimeTypeFilters(filters);
|
||||
|
||||
if(dlg.exec() != QDialog::Accepted)
|
||||
if (dlg->exec() != QDialog::Accepted || !dlg)
|
||||
return;
|
||||
|
||||
QString fileName = dlg.selectedUrls().value(0).toLocalFile();
|
||||
if(fileName.isEmpty())
|
||||
QString fileName = dlg->selectedUrls().value(0).toLocalFile();
|
||||
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."));
|
||||
|
@ -251,7 +291,7 @@ void MeetingInformationDialog::importImage()
|
|||
return;
|
||||
}
|
||||
|
||||
img = image;
|
||||
img = image;
|
||||
needsToSaveImg = true;
|
||||
}
|
||||
}
|
||||
|
@ -260,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);
|
||||
|
|
|
@ -1,16 +1,8 @@
|
|||
set(TRAINTIMETABLE_SOURCES
|
||||
${TRAINTIMETABLE_SOURCES}
|
||||
graph/backgroundhelper.h
|
||||
graph/backgroundhelper.cpp
|
||||
graph/graphicsscene.h
|
||||
graph/graphicsscene.cpp
|
||||
graph/graphicsview.h
|
||||
graph/graphicsview.cpp
|
||||
graph/graphmanager.h
|
||||
graph/graphmanager.cpp
|
||||
graph/hourpane.h
|
||||
graph/hourpane.cpp
|
||||
graph/stationlayer.h
|
||||
graph/stationlayer.cpp
|
||||
PARENT_SCOPE
|
||||
add_subdirectory(model)
|
||||
add_subdirectory(view)
|
||||
|
||||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
graph/linegraphtypes.h
|
||||
graph/linegraphtypes.cpp PARENT_SCOPE
|
||||
)
|
||||
|
|
|
@ -1,175 +0,0 @@
|
|||
#include "backgroundhelper.h"
|
||||
|
||||
#include "app/scopedebug.h"
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QGraphicsLineItem>
|
||||
#include <QGraphicsSimpleTextItem>
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
#include "app/session.h"
|
||||
|
||||
BackgroundHelper::BackgroundHelper(QObject *parent) :
|
||||
QObject(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
BackgroundHelper::~BackgroundHelper()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void BackgroundHelper::setHourLinePen(const QPen& pen)
|
||||
{
|
||||
if(hourLinePen == pen)
|
||||
return;
|
||||
|
||||
hourLinePen = pen;
|
||||
emit updateGraph();
|
||||
}
|
||||
|
||||
void BackgroundHelper::setHourTextPen(const QPen &pen)
|
||||
{
|
||||
if(hourTextPen == pen)
|
||||
return;
|
||||
|
||||
hourTextPen = pen;
|
||||
emit updateGraph();
|
||||
}
|
||||
|
||||
void BackgroundHelper::setHourTextFont(const QFont& font)
|
||||
{
|
||||
if(hourTextFont == font)
|
||||
return;
|
||||
|
||||
hourTextFont = font;
|
||||
emit updateGraph();
|
||||
}
|
||||
|
||||
void BackgroundHelper::setHourOffset(qreal value)
|
||||
{
|
||||
hourOffset = value;
|
||||
emit updateGraph();
|
||||
}
|
||||
|
||||
void BackgroundHelper::setVertOffset(qreal value)
|
||||
{
|
||||
vertOffset = value;
|
||||
emit vertOffsetChanged();
|
||||
emit updateGraph();
|
||||
}
|
||||
|
||||
qreal BackgroundHelper::getVertOffset() const
|
||||
{
|
||||
return vertOffset;
|
||||
}
|
||||
|
||||
void BackgroundHelper::setHourHorizOffset(qreal value)
|
||||
{
|
||||
hourHorizOffset = value;
|
||||
emit horizHorizOffsetChanged();
|
||||
emit updateGraph();
|
||||
}
|
||||
|
||||
qreal BackgroundHelper::getHourHorizOffset() const
|
||||
{
|
||||
return hourHorizOffset;
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawBackgroundLines(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
const qreal x1 = qMax(qreal(hourHorizOffset), rect.left());
|
||||
const qreal x2 = rect.right();
|
||||
const qreal t = qMax(rect.top(), vertOffset);
|
||||
const qreal b = rect.bottom();
|
||||
|
||||
if(x1 > x2 || b < vertOffset || t > b)
|
||||
return;
|
||||
|
||||
qreal f = std::remainder(t - vertOffset, hourOffset);
|
||||
|
||||
if(f < 0)
|
||||
f += hourOffset;
|
||||
qreal f1 = qFuzzyIsNull(f) ? vertOffset : qMax(t - f + hourOffset, vertOffset);
|
||||
|
||||
|
||||
const qreal l = std::remainder(b - vertOffset, hourOffset);
|
||||
const qreal l1 = b - l;
|
||||
|
||||
std::size_t n = std::size_t((l1 - f1)/hourOffset) + 1;
|
||||
|
||||
QLineF *arr = new QLineF[n];
|
||||
for(std::size_t i = 0; i < n; i++)
|
||||
{
|
||||
arr[i] = QLineF(x1, f1, x2, f1);
|
||||
f1 += hourOffset;
|
||||
}
|
||||
|
||||
painter->setPen(hourLinePen);
|
||||
painter->drawLines(arr, int(n));
|
||||
delete [] arr;
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawForegroundHours(QPainter *painter, const QRectF &rect, int scroll)
|
||||
{
|
||||
painter->setFont(hourTextFont);
|
||||
painter->setPen(hourTextPen);
|
||||
|
||||
//qDebug() << "Drawing hours..." << rect << scroll;
|
||||
const QString fmt(QStringLiteral("%1:00"));
|
||||
|
||||
const qreal top = scroll;
|
||||
const qreal bottom = rect.bottom();
|
||||
|
||||
int h = qFloor(top / hourOffset);
|
||||
qreal y = h * hourOffset - scroll + vertOffset;
|
||||
|
||||
for(; h <= 24 && y <= bottom; h++)
|
||||
{
|
||||
//qDebug() << "Y:" << y << fmt.arg(h);
|
||||
painter->drawText(QPointF(5, y + 8), fmt.arg(h)); //y + 8 to center text vertically
|
||||
y += hourOffset;
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawForegroundStationLabels(QPainter *painter, const QRectF &rect, int hScroll, db_id lineId)
|
||||
{
|
||||
query q(Session->m_Db, "SELECT s.name,s.short_name,s.platforms,s.depot_platf FROM railways"
|
||||
" JOIN stations s ON s.id=railways.stationId"
|
||||
" WHERE railways.lineId=? ORDER BY railways.pos_meters ASC");
|
||||
q.bind(1, lineId);
|
||||
|
||||
QFont f;
|
||||
f.setBold(true);
|
||||
f.setPointSize(15);
|
||||
painter->setFont(f);
|
||||
painter->setPen(AppSettings.getStationTextColor());
|
||||
|
||||
const qreal platformOffset = Session->platformOffset;
|
||||
const int stationOffset = Session->stationOffset;
|
||||
|
||||
qreal x = Session->horizOffset;
|
||||
|
||||
QRectF r = rect;
|
||||
|
||||
for(auto station : q)
|
||||
{
|
||||
QString stName;
|
||||
if(station.column_bytes(1) == 0)
|
||||
stName = station.get<QString>(0); //Fallback to full name
|
||||
else
|
||||
stName = station.get<QString>(1);
|
||||
|
||||
int platf = station.get<int>(2);
|
||||
platf += station.get<int>(3);
|
||||
|
||||
r.setLeft(x - hScroll); //Eat width
|
||||
painter->drawText(r, Qt::AlignVCenter, stName);
|
||||
|
||||
x += stationOffset + platf * platformOffset;
|
||||
}
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
#ifndef BACKGROUNDHELPER_H
|
||||
#define BACKGROUNDHELPER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QPen>
|
||||
#include <QFont>
|
||||
|
||||
#include "utils/types.h"
|
||||
|
||||
class BackgroundHelper : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
BackgroundHelper(QObject *parent = nullptr);
|
||||
~BackgroundHelper();
|
||||
|
||||
void setHourLinePen(const QPen &pen);
|
||||
void setHourTextPen(const QPen &pen);
|
||||
void setHourTextFont(const QFont &font);
|
||||
|
||||
void setHourOffset(qreal value);
|
||||
|
||||
void setVertOffset(qreal value);
|
||||
qreal getVertOffset() const;
|
||||
|
||||
void setHourHorizOffset(qreal value);
|
||||
qreal getHourHorizOffset() const;
|
||||
|
||||
void drawBackgroundLines(QPainter *painter, const QRectF& rect);
|
||||
void drawForegroundHours(QPainter *painter, const QRectF& rect, int scroll);
|
||||
|
||||
void drawForegroundStationLabels(QPainter *painter, const QRectF& rect, int hScroll, db_id lineId);
|
||||
|
||||
signals:
|
||||
void updateGraph();
|
||||
void vertOffsetChanged();
|
||||
void horizHorizOffsetChanged();
|
||||
|
||||
private:
|
||||
qreal hourOffset;
|
||||
qreal vertOffset;
|
||||
qreal hourHorizOffset;
|
||||
|
||||
QFont hourTextFont;
|
||||
QPen hourTextPen;
|
||||
QPen hourLinePen;
|
||||
};
|
||||
|
||||
#endif // BACKGROUNDHELPER_H
|
|
@ -1,22 +0,0 @@
|
|||
#include "graphicsscene.h"
|
||||
|
||||
GraphicsScene::GraphicsScene(QObject *parent) : QGraphicsScene(parent)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void GraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *e)
|
||||
{
|
||||
/* This is needed to clear selection in some nasty cases:
|
||||
* 1 - select a job in Line1 (JobPathEditor opens...)
|
||||
* 2 - change to Line2 (but the selected job is not in Line2)
|
||||
* 3 - JobPathEditor is still open and you want to lose it
|
||||
* 4 - clicking in empty area won't emit 'selectionChanged()' because
|
||||
* the selection was already emty so we need to subclass
|
||||
* and manually emit 'selectionCleared()'
|
||||
*/
|
||||
QGraphicsScene::mousePressEvent(e);
|
||||
|
||||
if(selectedItems().isEmpty())
|
||||
emit selectionCleared();
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#ifndef GRAPHICSSCENE_H
|
||||
#define GRAPHICSSCENE_H
|
||||
|
||||
#include <QGraphicsScene>
|
||||
|
||||
class GraphicsScene : public QGraphicsScene
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GraphicsScene(QObject *parent = nullptr);
|
||||
|
||||
signals:
|
||||
void selectionCleared();
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *e);
|
||||
};
|
||||
|
||||
#endif // GRAPHICSSCENE_H
|
|
@ -1,77 +0,0 @@
|
|||
#include "graphicsview.h"
|
||||
|
||||
#include "graphmanager.h"
|
||||
#include "backgroundhelper.h"
|
||||
|
||||
#include "hourpane.h"
|
||||
#include "stationlayer.h"
|
||||
|
||||
#include <QScrollBar>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <QResizeEvent>
|
||||
#include <QMoveEvent>
|
||||
|
||||
GraphicsView::GraphicsView(GraphManager *mgr, QWidget *parent) :
|
||||
QGraphicsView(parent),
|
||||
helper(mgr->getBackGround())
|
||||
{
|
||||
//setRenderHint(QPainter::Antialiasing); It blurs background lines with big pen sizes
|
||||
setAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
|
||||
hourPane = new HourPane(helper, this);
|
||||
stationLayer = new StationLayer(mgr, this);
|
||||
updateHourHorizOffset();
|
||||
updateStationVertOffset();
|
||||
|
||||
connect(verticalScrollBar(), &QScrollBar::valueChanged, hourPane, &HourPane::setScroll);
|
||||
connect(horizontalScrollBar(), &QScrollBar::valueChanged, stationLayer, &StationLayer::setScroll);
|
||||
|
||||
connect(helper, &BackgroundHelper::horizHorizOffsetChanged, this, &GraphicsView::updateHourHorizOffset);
|
||||
connect(helper, &BackgroundHelper::vertOffsetChanged, this, &GraphicsView::updateStationVertOffset);
|
||||
connect(helper, &BackgroundHelper::updateGraph, this, static_cast<void(QGraphicsView::*)()>(&GraphicsView::update));
|
||||
}
|
||||
|
||||
void GraphicsView::drawBackground(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
helper->drawBackgroundLines(painter, rect);
|
||||
}
|
||||
|
||||
void GraphicsView::updateHourHorizOffset()
|
||||
{
|
||||
hourPane->resize(int(helper->getHourHorizOffset()) - 5, viewport()->height());
|
||||
}
|
||||
|
||||
void GraphicsView::updateStationVertOffset()
|
||||
{
|
||||
stationLayer->resize(viewport()->width(), int(helper->getVertOffset()) - 5);
|
||||
}
|
||||
|
||||
void GraphicsView::redrawStationNames()
|
||||
{
|
||||
stationLayer->update();
|
||||
}
|
||||
|
||||
bool GraphicsView::viewportEvent(QEvent *e)
|
||||
{
|
||||
switch (e->type())
|
||||
{
|
||||
case QEvent::Resize:
|
||||
{
|
||||
//qDebug() << e << "Resizing HourPane";
|
||||
//qDebug() << "View:" << rect() << "Viewport:" << viewport()->rect();
|
||||
hourPane->resize(hourPane->width(), viewport()->height());
|
||||
hourPane->setScroll(verticalScrollBar()->value());
|
||||
|
||||
stationLayer->resize(viewport()->width(), stationLayer->height());
|
||||
stationLayer->setScroll(horizontalScrollBar()->value());
|
||||
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QGraphicsView::viewportEvent(e);
|
||||
}
|
|
@ -1,32 +0,0 @@
|
|||
#ifndef GRAPHICSVIEW_H
|
||||
#define GRAPHICSVIEW_H
|
||||
|
||||
#include <QGraphicsView>
|
||||
|
||||
class HourPane;
|
||||
class StationLayer;
|
||||
class BackgroundHelper;
|
||||
class GraphManager;
|
||||
|
||||
class GraphicsView : public QGraphicsView
|
||||
{
|
||||
public:
|
||||
GraphicsView(GraphManager *mgr, QWidget *parent = nullptr);
|
||||
|
||||
void redrawStationNames();
|
||||
|
||||
public slots:
|
||||
void updateStationVertOffset();
|
||||
void updateHourHorizOffset();
|
||||
|
||||
protected:
|
||||
void drawBackground(QPainter *painter, const QRectF &rect) override;
|
||||
bool viewportEvent(QEvent *e) override;
|
||||
|
||||
private:
|
||||
BackgroundHelper *helper;
|
||||
HourPane *hourPane;
|
||||
StationLayer *stationLayer;
|
||||
};
|
||||
|
||||
#endif // GRAPHICSVIEW_H
|
|
@ -1,244 +0,0 @@
|
|||
#include "graphmanager.h"
|
||||
|
||||
#include "app/session.h"
|
||||
#include "viewmanager/viewmanager.h"
|
||||
|
||||
#include "backgroundhelper.h"
|
||||
#include "graphicsview.h"
|
||||
#include "graphicsscene.h"
|
||||
#include "utils/model_roles.h"
|
||||
|
||||
#include "lines/linestorage.h"
|
||||
|
||||
#include "app/scopedebug.h"
|
||||
|
||||
#include <QGraphicsSimpleTextItem>
|
||||
|
||||
GraphManager::GraphManager(QObject *parent) :
|
||||
QObject(parent),
|
||||
backGround(nullptr),
|
||||
curLineId(0),
|
||||
curJobId(0)
|
||||
{
|
||||
backGround = new BackgroundHelper(this);
|
||||
|
||||
lineStorage = Session->mLineStorage;
|
||||
|
||||
connect(&AppSettings, &TrainTimetableSettings::jobGraphOptionsChanged, this, &GraphManager::updateGraphOptions);
|
||||
updateGraphOptions();
|
||||
|
||||
view = new GraphicsView(this);
|
||||
view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
view->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
|
||||
connect(lineStorage, &LineStorage::lineNameChanged, this, &GraphManager::onLineNameChanged);
|
||||
connect(lineStorage, &LineStorage::lineStationsModified, this, &GraphManager::onLineModified);
|
||||
connect(lineStorage, &LineStorage::lineRemoved, this, &GraphManager::onLineRemoved);
|
||||
}
|
||||
|
||||
GraphManager::~GraphManager()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
bool GraphManager::setCurrentLine(db_id lineId)
|
||||
{
|
||||
if(curLineId == lineId)
|
||||
return true;
|
||||
|
||||
if(lineId < 0)
|
||||
lineId = 0;
|
||||
|
||||
LineStorage *lines = Session->mLineStorage;
|
||||
bool error = false;
|
||||
|
||||
GraphicsScene *scene = static_cast<GraphicsScene *>(view->scene());
|
||||
if(scene)
|
||||
{
|
||||
disconnect(scene, &QGraphicsScene::selectionChanged, this, &GraphManager::onSelectionChanged);
|
||||
disconnect(scene, &GraphicsScene::selectionCleared, this, &GraphManager::onSelectionCleared);
|
||||
scene->clearSelection();
|
||||
}
|
||||
view->setScene(nullptr);
|
||||
scene = nullptr;
|
||||
if(curLineId)
|
||||
lines->releaseLine(curLineId);
|
||||
|
||||
if(lineId > 0)
|
||||
{
|
||||
if(lines->increfLine(lineId) && (scene = static_cast<GraphicsScene *>(lineStorage->sceneForLine(lineId))))
|
||||
{
|
||||
connect(scene, &QGraphicsScene::selectionChanged, this, &GraphManager::onSelectionChanged);
|
||||
connect(scene, &GraphicsScene::selectionCleared, this, &GraphManager::onSelectionCleared);
|
||||
view->setScene(scene);
|
||||
view->centerOn(0.0, 0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
error = true;
|
||||
lineId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
view->redrawStationNames();
|
||||
curLineId = lineId;
|
||||
emit currentLineChanged(curLineId);
|
||||
return !error;
|
||||
}
|
||||
|
||||
void GraphManager::onSelectionChanged()
|
||||
{
|
||||
//TODO: single selection. Ctrl+click allow to select multiple items but only the first one is considerated
|
||||
QGraphicsScene *scene = view->scene();
|
||||
if(scene && !scene->selectedItems().isEmpty())
|
||||
{
|
||||
auto sel = scene->selectedItems();
|
||||
QGraphicsItem *item = sel.first();
|
||||
db_id jobId = item->data(JOB_ID_ROLE).toLongLong();
|
||||
|
||||
if(curJobId == jobId)
|
||||
return;
|
||||
|
||||
curJobId = jobId;
|
||||
|
||||
Session->getViewManager()->requestJobEditor(jobId);
|
||||
|
||||
emit jobSelected(jobId);
|
||||
}
|
||||
else
|
||||
{
|
||||
onSelectionCleared();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphManager::onSelectionCleared()
|
||||
{
|
||||
curJobId = 0;
|
||||
Session->getViewManager()->requestClearJob();
|
||||
emit jobSelected(0);
|
||||
}
|
||||
|
||||
BackgroundHelper *GraphManager::getBackGround() const
|
||||
{
|
||||
return backGround;
|
||||
}
|
||||
|
||||
JobSelection GraphManager::getSelectedJob()
|
||||
{
|
||||
if(!view)
|
||||
return {0, 0}; //NULL
|
||||
QGraphicsScene *scene = view->scene();
|
||||
if(!scene)
|
||||
return {0, 0};
|
||||
|
||||
auto selection = scene->selectedItems();
|
||||
if(selection.isEmpty())
|
||||
return {0, 0};
|
||||
|
||||
QGraphicsItem *item = selection.first();
|
||||
db_id jobId = item->data(JOB_ID_ROLE).toLongLong();
|
||||
db_id segmentId = item->data(SEGMENT_ROLE).toLongLong();
|
||||
|
||||
return {jobId, segmentId};
|
||||
}
|
||||
|
||||
void GraphManager::clearSelection()
|
||||
{
|
||||
QGraphicsScene *scene = view->scene();
|
||||
if(scene)
|
||||
{
|
||||
scene->clearSelection();
|
||||
}
|
||||
}
|
||||
|
||||
/* db_id GraphManager::showFirstLine()
|
||||
* Tries to select first line in Alphabetical order
|
||||
* If it succeds returs its lineId
|
||||
* Otherwise returns 0
|
||||
*/
|
||||
db_id GraphManager::getFirstLineId()
|
||||
{
|
||||
db_id firstLineId = 0;
|
||||
|
||||
if(Session->m_Db.db())
|
||||
{
|
||||
query q(Session->m_Db, "SELECT id,MIN(name) FROM lines");
|
||||
q.step();
|
||||
firstLineId = q.getRows().get<db_id>(0);
|
||||
}
|
||||
|
||||
return firstLineId;
|
||||
}
|
||||
|
||||
db_id GraphManager::getCurLineId() const
|
||||
{
|
||||
return curLineId;
|
||||
}
|
||||
|
||||
GraphicsView *GraphManager::getView() const
|
||||
{
|
||||
return view;
|
||||
}
|
||||
|
||||
void GraphManager::updateGraphOptions()
|
||||
{
|
||||
//TODO: maybe get rid of theese variables in MeetingSession and always use AppSettings?
|
||||
int hourOffset = AppSettings.getHourOffset();
|
||||
backGround->setHourOffset(hourOffset);
|
||||
Session->hourOffset = hourOffset;
|
||||
|
||||
int horizOffset = AppSettings.getHorizontalOffset();
|
||||
backGround->setHourHorizOffset(AppSettings.getHourLineOffset());
|
||||
Session->horizOffset = horizOffset;
|
||||
|
||||
int vertOffset = AppSettings.getVerticalOffset();
|
||||
backGround->setVertOffset(vertOffset);
|
||||
Session->vertOffset = vertOffset;
|
||||
|
||||
Session->stationOffset = AppSettings.getStationOffset();
|
||||
Session->platformOffset = AppSettings.getPlatformOffset();
|
||||
|
||||
Session->jobLineWidth = AppSettings.getJobLineWidth();
|
||||
|
||||
QPen hourLinePen;
|
||||
hourLinePen.setColor(AppSettings.getHourLineColor());
|
||||
hourLinePen.setWidth(AppSettings.getHourLineWidth());
|
||||
backGround->setHourLinePen(hourLinePen);
|
||||
|
||||
backGround->setHourTextPen(AppSettings.getHourTextColor());
|
||||
|
||||
QFont f;
|
||||
f.setPointSize(11);
|
||||
backGround->setHourTextFont(f);
|
||||
}
|
||||
|
||||
void GraphManager::onLineNameChanged(db_id lineId)
|
||||
{
|
||||
if(lineId == curLineId)
|
||||
{
|
||||
//Emit the signal again
|
||||
//So MainWindow->lineComboSearch (CustomCompletionLineEdit)
|
||||
//updates the text
|
||||
emit currentLineChanged(curLineId);
|
||||
}
|
||||
}
|
||||
|
||||
void GraphManager::onLineModified(db_id lineId)
|
||||
{
|
||||
if(lineId == curLineId)
|
||||
{
|
||||
view->updateStationVertOffset();
|
||||
view->redrawStationNames();
|
||||
}
|
||||
}
|
||||
|
||||
void GraphManager::onLineRemoved(db_id lineId)
|
||||
{
|
||||
if(curLineId == lineId)
|
||||
{
|
||||
//Current line is removed, show another line instead
|
||||
lineId = getFirstLineId();
|
||||
if(lineId)
|
||||
setCurrentLine(lineId);
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
#ifndef GRAPHMANAGER_H
|
||||
#define GRAPHMANAGER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include "utils/types.h"
|
||||
|
||||
class BackgroundHelper;
|
||||
class GraphicsView;
|
||||
class LineStorage;
|
||||
|
||||
typedef struct JobSelection
|
||||
{
|
||||
db_id jobId;
|
||||
db_id segmentId;
|
||||
} JobSelection;
|
||||
|
||||
class GraphManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit GraphManager(QObject *parent = nullptr);
|
||||
~GraphManager();
|
||||
|
||||
GraphicsView *getView() const;
|
||||
|
||||
db_id getCurLineId() const;
|
||||
|
||||
BackgroundHelper *getBackGround() const;
|
||||
|
||||
JobSelection getSelectedJob();
|
||||
void clearSelection();
|
||||
|
||||
db_id getFirstLineId();
|
||||
|
||||
signals:
|
||||
void currentLineChanged(db_id lineId);
|
||||
|
||||
void jobSelected(db_id jobId);
|
||||
|
||||
public slots:
|
||||
bool setCurrentLine(db_id lineId);
|
||||
void onSelectionChanged();
|
||||
void onSelectionCleared();
|
||||
|
||||
private slots:
|
||||
void onLineNameChanged(db_id lineId);
|
||||
void onLineModified(db_id lineId);
|
||||
void onLineRemoved(db_id lineId);
|
||||
|
||||
void updateGraphOptions();
|
||||
|
||||
public:
|
||||
LineStorage *lineStorage;
|
||||
|
||||
private:
|
||||
BackgroundHelper *backGround;
|
||||
GraphicsView *view;
|
||||
|
||||
db_id curLineId;
|
||||
|
||||
db_id curJobId;
|
||||
};
|
||||
|
||||
#endif // GRAPHMANAGER_H
|
|
@ -1,29 +0,0 @@
|
|||
#include "hourpane.h"
|
||||
|
||||
#include "backgroundhelper.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
HourPane::HourPane(BackgroundHelper *h, QWidget *parent) :
|
||||
QWidget (parent),
|
||||
helper(h),
|
||||
verticalScroll(0)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void HourPane::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter p(this);
|
||||
QColor c(255, 255, 255, 220);
|
||||
p.fillRect(rect(), c);
|
||||
helper->drawForegroundHours(&p, rect(), verticalScroll);
|
||||
}
|
||||
|
||||
void HourPane::setScroll(int value)
|
||||
{
|
||||
verticalScroll = value;
|
||||
update();
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
#ifndef HOURPANE_H
|
||||
#define HOURPANE_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class BackgroundHelper;
|
||||
|
||||
class HourPane : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
HourPane(BackgroundHelper *h, QWidget *parent);
|
||||
|
||||
public slots:
|
||||
void setScroll(int value);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *);
|
||||
|
||||
private:
|
||||
BackgroundHelper *helper;
|
||||
int verticalScroll;
|
||||
};
|
||||
|
||||
#endif // HOURPANE_H
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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>
|
||||
|
||||
class LineGraphTypeNames
|
||||
{
|
||||
Q_DECLARE_TR_FUNCTIONS(LineGraphTypeNames)
|
||||
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")};
|
||||
|
||||
QString utils::getLineGraphTypeName(LineGraphType type)
|
||||
{
|
||||
if (type >= LineGraphType::NTypes)
|
||||
return QString();
|
||||
return LineGraphTypeNames::tr(LineGraphTypeNames::texts[int(type)]);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
#include <QString>
|
||||
|
||||
/*!
|
||||
* \brief Enum to describe view content type
|
||||
*/
|
||||
enum class LineGraphType
|
||||
{
|
||||
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)
|
||||
NTypes
|
||||
};
|
||||
|
||||
namespace utils {
|
||||
|
||||
QString getLineGraphTypeName(LineGraphType type);
|
||||
|
||||
} // namespace utils
|
||||
|
||||
#endif // LINEGRAPHTYPES_H
|
|
@ -0,0 +1,13 @@
|
|||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
graph/model/linegraphmanager.h
|
||||
graph/model/linegraphscene.h
|
||||
graph/model/linegraphselectionhelper.h
|
||||
graph/model/stationgraphobject.h
|
||||
|
||||
graph/model/linegraphmanager.cpp
|
||||
graph/model/linegraphscene.cpp
|
||||
graph/model/linegraphselectionhelper.cpp
|
||||
graph/model/stationgraphobject.cpp
|
||||
PARENT_SCOPE
|
||||
)
|
|
@ -0,0 +1,608 @@
|
|||
/*
|
||||
* 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"
|
||||
#include "linegraphselectionhelper.h"
|
||||
|
||||
#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_hasScheduledUpdate(false)
|
||||
{
|
||||
auto session = Session;
|
||||
// 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);
|
||||
connect(session, &MeetingSession::segmentRemoved, this, &LineGraphManager::onSegmentRemoved);
|
||||
|
||||
// Lines
|
||||
connect(session, &MeetingSession::lineNameChanged, this, &LineGraphManager::onLineNameChanged);
|
||||
connect(session, &MeetingSession::lineSegmentsChanged, this,
|
||||
&LineGraphManager::onLineSegmentsChanged);
|
||||
connect(session, &MeetingSession::lineRemoved, this, &LineGraphManager::onLineRemoved);
|
||||
|
||||
// Jobs
|
||||
connect(session, &MeetingSession::jobChanged, this, &LineGraphManager::onJobChanged);
|
||||
connect(session, &MeetingSession::jobRemoved, this, &LineGraphManager::onJobRemoved);
|
||||
|
||||
// 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));
|
||||
|
||||
scenes.append(scene);
|
||||
|
||||
connect(scene, &LineGraphScene::destroyed, this, &LineGraphManager::onSceneDestroyed);
|
||||
connect(scene, &LineGraphScene::sceneActivated, this, &LineGraphManager::setActiveScene);
|
||||
connect(scene, &LineGraphScene::jobSelected, this, &LineGraphManager::onJobSelected);
|
||||
|
||||
if (m_followJobOnGraphChange)
|
||||
connect(scene, &LineGraphScene::graphChanged, this, &LineGraphManager::onGraphChanged);
|
||||
|
||||
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
|
||||
setActiveScene(scene);
|
||||
}
|
||||
}
|
||||
|
||||
void LineGraphManager::unregisterScene(LineGraphScene *scene)
|
||||
{
|
||||
Q_ASSERT(scenes.contains(scene));
|
||||
|
||||
scenes.removeOne(scene);
|
||||
|
||||
disconnect(scene, &LineGraphScene::destroyed, this, &LineGraphManager::onSceneDestroyed);
|
||||
disconnect(scene, &LineGraphScene::sceneActivated, this, &LineGraphManager::setActiveScene);
|
||||
disconnect(scene, &LineGraphScene::jobSelected, this, &LineGraphManager::onJobSelected);
|
||||
|
||||
if (m_followJobOnGraphChange)
|
||||
disconnect(scene, &LineGraphScene::graphChanged, this, &LineGraphManager::onGraphChanged);
|
||||
|
||||
// Reset active scene if it is unregistered
|
||||
if (activeScene == scene)
|
||||
setActiveScene(nullptr);
|
||||
}
|
||||
|
||||
void LineGraphManager::clearAllGraphs()
|
||||
{
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
scene->loadGraph(0, LineGraphType::NoGraph, true);
|
||||
}
|
||||
}
|
||||
|
||||
void LineGraphManager::clearGraphsOfObject(db_id objectId, LineGraphType type)
|
||||
{
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if (scene->getGraphObjectId() == objectId && scene->getGraphType() == type)
|
||||
scene->loadGraph(0, LineGraphType::NoGraph);
|
||||
}
|
||||
}
|
||||
|
||||
JobStopEntry LineGraphManager::getCurrentSelectedJob() const
|
||||
{
|
||||
JobStopEntry selectedJob;
|
||||
if (activeScene)
|
||||
selectedJob = activeScene->getSelectedJob();
|
||||
return selectedJob;
|
||||
}
|
||||
|
||||
void LineGraphManager::scheduleUpdate()
|
||||
{
|
||||
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 (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(lineScene))
|
||||
return;
|
||||
}
|
||||
else if (!scenes.isEmpty())
|
||||
{
|
||||
// Activate first registered scene because previous one was unregistered
|
||||
lineScene = scenes.first();
|
||||
}
|
||||
|
||||
activeScene = lineScene;
|
||||
emit activeSceneChanged(activeScene);
|
||||
|
||||
// Triegger selection update or clear it
|
||||
JobStopEntry selectedJob;
|
||||
if (activeScene)
|
||||
{
|
||||
selectedJob = activeScene->getSelectedJob();
|
||||
}
|
||||
|
||||
onJobSelected(selectedJob.jobId, int(selectedJob.category), selectedJob.stopId);
|
||||
}
|
||||
|
||||
void LineGraphManager::onSceneDestroyed(QObject *obj)
|
||||
{
|
||||
LineGraphScene *scene = static_cast<LineGraphScene *>(obj);
|
||||
unregisterScene(scene);
|
||||
}
|
||||
|
||||
void LineGraphManager::onGraphChanged(int /*graphType_*/, db_id graphObjId, LineGraphScene *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
|
||||
|
||||
// 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
|
||||
|
||||
LineGraphSelectionHelper helper(Session->m_Db);
|
||||
|
||||
LineGraphSelectionHelper::SegmentInfo info;
|
||||
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);
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
lastSelectedJob.jobId = jobId;
|
||||
lastSelectedJob.category = cat;
|
||||
lastSelectedJob.stopId = stopId;
|
||||
|
||||
if (jobId)
|
||||
Session->getViewManager()->requestJobEditor(jobId);
|
||||
else
|
||||
Session->getViewManager()->requestClearJob();
|
||||
|
||||
if (AppSettings.getSyncSelectionOnAllGraphs())
|
||||
{
|
||||
// Sync selection among all registered scenes
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
scene->setSelectedJob(lastSelectedJob);
|
||||
}
|
||||
}
|
||||
|
||||
if (activeScene)
|
||||
{
|
||||
const JobStopEntry selectedJob = activeScene->getSelectedJob();
|
||||
if (selectedJob.jobId == lastSelectedJob.jobId)
|
||||
{
|
||||
emit jobSelected(lastSelectedJob.jobId, int(lastSelectedJob.category),
|
||||
lastSelectedJob.stopId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LineGraphManager::onStationNameChanged(db_id stationId)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if (scene->pendingUpdate.testFlag(PendingUpdate::FullReload))
|
||||
continue; // Already flagged
|
||||
|
||||
if (scene->stations.contains(stationId))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
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 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 (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)
|
||||
{
|
||||
activeScene->setSelectedJob(selectedJob);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Manually update all scenes
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
JobStopEntry oldSelectedJob = scene->getSelectedJob();
|
||||
if (oldSelectedJob.jobId == oldJobId)
|
||||
{
|
||||
scene->setSelectedJob(selectedJob);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
int horizOffset = AppSettings.getHorizontalOffset();
|
||||
Session->horizOffset = horizOffset;
|
||||
|
||||
int vertOffset = AppSettings.getVerticalOffset();
|
||||
Session->vertOffset = vertOffset;
|
||||
|
||||
Session->stationOffset = AppSettings.getStationOffset();
|
||||
Session->platformOffset = AppSettings.getPlatformOffset();
|
||||
|
||||
Session->jobLineWidth = AppSettings.getJobLineWidth();
|
||||
|
||||
// Reload all graphs
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
scene->reload();
|
||||
}
|
||||
|
||||
const bool oldVal = m_followJobOnGraphChange;
|
||||
m_followJobOnGraphChange = AppSettings.getFollowSelectionOnGraphChange();
|
||||
|
||||
if (m_followJobOnGraphChange != oldVal)
|
||||
{
|
||||
// Update connections
|
||||
for (LineGraphScene *scene : qAsConst(scenes))
|
||||
{
|
||||
if (m_followJobOnGraphChange)
|
||||
connect(scene, &LineGraphScene::graphChanged, this,
|
||||
&LineGraphManager::onGraphChanged);
|
||||
else
|
||||
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();
|
||||
}
|
|
@ -0,0 +1,196 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include "utils/types.h"
|
||||
#include "graph/linegraphtypes.h"
|
||||
|
||||
class IGraphScene;
|
||||
class LineGraphScene;
|
||||
|
||||
/*!
|
||||
* \brief Class for managing LineGraphScene instances
|
||||
*
|
||||
* The manager listens for changes on railway plan and
|
||||
* decides which of the registered LineGraphScene needs
|
||||
* to be updated.
|
||||
*
|
||||
* \sa LineGraphScene
|
||||
*/
|
||||
class LineGraphManager : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
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
|
||||
*
|
||||
* The scene gets registered on this manager and will be refreshed
|
||||
* we railway layout changes.
|
||||
* The first scene registered is set as active
|
||||
*
|
||||
* \sa unregisterScene()
|
||||
* \sa setActiveScene()
|
||||
*/
|
||||
void registerScene(LineGraphScene *scene);
|
||||
|
||||
/*!
|
||||
* \brief unsubscribe scene from notifications
|
||||
*
|
||||
* The scene will not be refreshed by this manager anymore
|
||||
* If it was the active scene, active scene will be reset
|
||||
*
|
||||
* \sa registerScene()
|
||||
* \sa setActiveScene()
|
||||
*/
|
||||
void unregisterScene(LineGraphScene *scene);
|
||||
|
||||
void clearAllGraphs();
|
||||
void clearGraphsOfObject(db_id objectId, LineGraphType type);
|
||||
|
||||
/*!
|
||||
* \brief get active scene
|
||||
* \return Scene instance or nullptr if no scene is active
|
||||
*/
|
||||
inline LineGraphScene *getActiveScene() const
|
||||
{
|
||||
return activeScene;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief get current selected job
|
||||
*
|
||||
* If there is an active scene, return it's selection otherwise null JobStopEntry::jobId
|
||||
* \sa getActiveScene()
|
||||
* \sa LineGraphScene::getSelectedJob()
|
||||
*/
|
||||
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
|
||||
*/
|
||||
void activeSceneChanged(LineGraphScene *scene);
|
||||
|
||||
/*!
|
||||
* \brief jobSelected
|
||||
* \param jobId
|
||||
* \param category
|
||||
* \param stopId
|
||||
*
|
||||
* Emitted when \a active scene job selection changes or if active scene changes.
|
||||
* Not emitted for other scenes.
|
||||
*
|
||||
* \sa getActiveScene()
|
||||
* \sa getCurrentSelectedJob()
|
||||
*/
|
||||
void jobSelected(db_id jobId, int category, db_id stopId);
|
||||
|
||||
public slots:
|
||||
/*!
|
||||
* \brief sets active scene
|
||||
*
|
||||
* This scene instance will be the active one and will therefore receive
|
||||
* user requests to show items.
|
||||
* Scene must be registered on this manager first.
|
||||
* If scene parameter is nullptr (i.e. when resetting active scene) we try
|
||||
* to activate our first registered scene.
|
||||
*
|
||||
* \sa getActiveScene()
|
||||
* \sa activeSceneChanged()
|
||||
* \sa LineGraphScene::activateScene()
|
||||
*/
|
||||
void setActiveScene(IGraphScene *scene);
|
||||
|
||||
private slots:
|
||||
// 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
|
||||
void onStationNameChanged(db_id stationId);
|
||||
void onStationJobPlanChanged(const QSet<db_id> &stationIds);
|
||||
void onStationTrackPlanChanged(const QSet<db_id> &stationIds);
|
||||
void onStationRemoved(db_id stationId);
|
||||
|
||||
// Segments
|
||||
void onSegmentNameChanged(db_id segmentId);
|
||||
void onSegmentStationsChanged(db_id segmentId);
|
||||
void onSegmentRemoved(db_id segmentId);
|
||||
|
||||
// Lines
|
||||
void onLineNameChanged(db_id lineId);
|
||||
void onLineSegmentsChanged(db_id lineId);
|
||||
void onLineRemoved(db_id lineId);
|
||||
|
||||
// Jobs
|
||||
void onJobChanged(db_id jobId, db_id oldJobId);
|
||||
void onJobRemoved(db_id jobId);
|
||||
|
||||
// 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
|
|
@ -0,0 +1,965 @@
|
|||
/*
|
||||
* 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 "linegraphscene.h"
|
||||
|
||||
#include "graph/view/backgroundhelper.h"
|
||||
|
||||
#include "app/session.h"
|
||||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
// TODO: maybe move to utils?
|
||||
constexpr qreal MSEC_PER_HOUR = 1000 * 60 * 60;
|
||||
|
||||
static inline qreal timeToHourFraction(const QTime &t)
|
||||
{
|
||||
qreal ret = t.msecsSinceStartOfDay() / MSEC_PER_HOUR;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline double stationPlatformPosition(const StationGraphObject &st, const db_id platfId,
|
||||
const double platfOffset)
|
||||
{
|
||||
double x = st.xPos;
|
||||
for (const StationGraphObject::PlatformGraph &platf : st.platforms)
|
||||
{
|
||||
if (platf.platformId == platfId)
|
||||
return x;
|
||||
x += platfOffset;
|
||||
}
|
||||
|
||||
// Error: requested platform belongs to different station
|
||||
qWarning() << "Station:" << st.stationName << st.stationId << "No platf:" << platfId;
|
||||
return -1;
|
||||
}
|
||||
|
||||
LineGraphScene::LineGraphScene(sqlite3pp::database &db, QObject *parent) :
|
||||
IGraphScene(parent),
|
||||
mDb(db),
|
||||
graphObjectId(0),
|
||||
graphType(LineGraphType::NoGraph),
|
||||
m_drawSelection(true)
|
||||
{
|
||||
}
|
||||
|
||||
void LineGraphScene::renderContents(QPainter *painter, const QRectF &sceneRect)
|
||||
{
|
||||
BackgroundHelper::drawBackgroundHourLines(painter, sceneRect);
|
||||
|
||||
if (getGraphType() == LineGraphType::NoGraph)
|
||||
return; // Nothing to draw
|
||||
|
||||
BackgroundHelper::drawStations(painter, this, sceneRect);
|
||||
BackgroundHelper::drawJobStops(painter, this, sceneRect, m_drawSelection);
|
||||
BackgroundHelper::drawJobSegments(painter, this, sceneRect, m_drawSelection);
|
||||
}
|
||||
|
||||
void LineGraphScene::renderHeader(QPainter *painter, const QRectF &sceneRect,
|
||||
Qt::Orientation orient, double /*scroll*/)
|
||||
{
|
||||
if (orient == Qt::Horizontal)
|
||||
BackgroundHelper::drawStationHeader(painter, this, sceneRect);
|
||||
else
|
||||
BackgroundHelper::drawHourPanel(painter, sceneRect);
|
||||
}
|
||||
|
||||
void LineGraphScene::recalcContentSize()
|
||||
{
|
||||
m_cachedContentsSize = QSize();
|
||||
|
||||
if (graphType == LineGraphType::NoGraph)
|
||||
return; // Nothing to draw
|
||||
|
||||
if (stationPositions.isEmpty())
|
||||
return;
|
||||
|
||||
const auto entry = stationPositions.last();
|
||||
const int platfCount = stations.value(entry.stationId).platforms.count();
|
||||
|
||||
// Add an additional half station offset after last station
|
||||
// This gives extra space to center station label
|
||||
const double maxWidth =
|
||||
entry.xPos + platfCount * Session->platformOffset + Session->stationOffset / 2;
|
||||
const double lastY = Session->vertOffset + Session->hourOffset * 24 + 10;
|
||||
|
||||
m_cachedContentsSize = QSize(maxWidth, lastY);
|
||||
}
|
||||
|
||||
void LineGraphScene::reload()
|
||||
{
|
||||
loadGraph(graphObjectId, graphType, true);
|
||||
}
|
||||
|
||||
bool LineGraphScene::loadGraph(db_id objectId, LineGraphType type, bool force)
|
||||
{
|
||||
if (!force && objectId == graphObjectId && type == graphType)
|
||||
return true; // Already loaded
|
||||
|
||||
// Initial state is invalid
|
||||
graphType = LineGraphType::NoGraph;
|
||||
graphObjectId = 0;
|
||||
graphObjectName.clear();
|
||||
stations.clear();
|
||||
stationPositions.clear();
|
||||
m_cachedContentsSize = QSize();
|
||||
|
||||
if (type == LineGraphType::NoGraph)
|
||||
{
|
||||
// Nothing to load
|
||||
emit graphChanged(int(graphType), graphObjectId, this);
|
||||
emit redrawGraph();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mDb.db())
|
||||
{
|
||||
qWarning() << "Database not open on graph loading!";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (objectId <= 0)
|
||||
{
|
||||
qWarning() << "Invalid object ID on graph loading!";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Leave on left horizOffset plus half station offset to separate first station from HourPanel
|
||||
// and to give more space to station label.
|
||||
const double curPos = Session->horizOffset + Session->stationOffset / 2;
|
||||
|
||||
if (type == LineGraphType::SingleStation)
|
||||
{
|
||||
StationGraphObject st;
|
||||
st.stationId = objectId;
|
||||
if (!loadStation(st, graphObjectName))
|
||||
return false;
|
||||
|
||||
// Register a single station at start position
|
||||
st.xPos = curPos;
|
||||
stations.insert(st.stationId, st);
|
||||
stationPositions = {{st.stationId, 0, st.xPos, {}}};
|
||||
}
|
||||
else if (type == LineGraphType::RailwaySegment)
|
||||
{
|
||||
// TODO: maybe show also station gates
|
||||
StationGraphObject stA, stB;
|
||||
|
||||
sqlite3pp::query q(mDb, "SELECT s.in_gate_id,s.out_gate_id,s.name,s.max_speed_kmh,"
|
||||
"s.type,s.distance_meters,"
|
||||
"g1.station_id,g2.station_id"
|
||||
" FROM railway_segments s"
|
||||
" JOIN station_gates g1 ON g1.id=s.in_gate_id"
|
||||
" JOIN station_gates g2 ON g2.id=s.out_gate_id"
|
||||
" WHERE s.id=?");
|
||||
q.bind(1, objectId);
|
||||
if (q.step() != SQLITE_ROW)
|
||||
{
|
||||
qWarning() << "Graph: invalid segment ID" << objectId;
|
||||
return false;
|
||||
}
|
||||
|
||||
auto r = q.getRows();
|
||||
// TODO useful?
|
||||
// outFromGateId = r.get<db_id>(0);
|
||||
// outToGateId = r.get<db_id>(1);
|
||||
graphObjectName = r.get<QString>(2);
|
||||
// outSpeed = r.get<int>(3);
|
||||
// outType = utils::RailwaySegmentType(r.get<db_id>(4));
|
||||
// outDistance = r.get<int>(5);
|
||||
|
||||
stA.stationId = r.get<db_id>(6);
|
||||
stB.stationId = r.get<db_id>(7);
|
||||
|
||||
QString unusedStFullName;
|
||||
if (!loadStation(stA, unusedStFullName) || !loadStation(stB, unusedStFullName))
|
||||
return false;
|
||||
|
||||
stA.xPos = curPos;
|
||||
stB.xPos =
|
||||
stA.xPos + stA.platforms.count() * Session->platformOffset + Session->stationOffset;
|
||||
|
||||
stations.insert(stA.stationId, stA);
|
||||
stations.insert(stB.stationId, stB);
|
||||
|
||||
stationPositions = {{stA.stationId, objectId, stA.xPos, {}},
|
||||
{stB.stationId, 0, stB.xPos, {}}};
|
||||
}
|
||||
else if (type == LineGraphType::RailwayLine)
|
||||
{
|
||||
if (!loadFullLine(objectId))
|
||||
{
|
||||
stations.clear();
|
||||
stationPositions.clear();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
graphObjectId = objectId;
|
||||
graphType = type;
|
||||
|
||||
recalcContentSize();
|
||||
updateHeaderSize();
|
||||
|
||||
reloadJobs();
|
||||
|
||||
// Reset pending update
|
||||
pendingUpdate = PendingUpdate::NothingToDo;
|
||||
|
||||
emit graphChanged(int(graphType), graphObjectId, this);
|
||||
emit redrawGraph();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphScene::reloadJobs()
|
||||
{
|
||||
if (graphType == LineGraphType::NoGraph)
|
||||
return false;
|
||||
|
||||
// TODO: maybe only load visible
|
||||
|
||||
for (StationGraphObject &st : stations)
|
||||
{
|
||||
if (!loadStationJobStops(st))
|
||||
return false;
|
||||
}
|
||||
|
||||
// Save last station from previous iteration
|
||||
auto lastSt = stations.constEnd();
|
||||
|
||||
for (int i = 0; i < stationPositions.size(); i++)
|
||||
{
|
||||
StationPosEntry &stPos = stationPositions[i];
|
||||
if (!stPos.segmentId)
|
||||
continue; // No segment, skip
|
||||
|
||||
db_id fromStId = stPos.stationId;
|
||||
db_id toStId = 0;
|
||||
if (i <= stationPositions.size() - 1)
|
||||
toStId = stationPositions.at(i + 1).stationId;
|
||||
|
||||
if (!toStId)
|
||||
break; // No next station
|
||||
|
||||
auto fromSt = lastSt;
|
||||
if (fromSt == stations.constEnd() || fromSt->stationId != fromStId)
|
||||
{
|
||||
fromSt = stations.constFind(fromStId);
|
||||
if (fromSt == stations.constEnd())
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto toSt = stations.constFind(toStId);
|
||||
if (toSt == stations.constEnd())
|
||||
continue;
|
||||
|
||||
if (!loadSegmentJobs(stPos, fromSt.value(), toSt.value()))
|
||||
return false;
|
||||
|
||||
// Store last station
|
||||
lastSt = toSt;
|
||||
}
|
||||
|
||||
JobStopEntry newSelection = selectedJob;
|
||||
updateJobSelection(mDb, newSelection);
|
||||
setSelectedJob(newSelection);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LineGraphScene::updateHeaderSize()
|
||||
{
|
||||
QSizeF headerSize(Session->horizOffset, Session->vertOffset);
|
||||
if (headerSize != m_cachedHeaderSize)
|
||||
{
|
||||
m_cachedHeaderSize = headerSize;
|
||||
emit headersSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
JobStopEntry LineGraphScene::getJobStopAt(const StationGraphObject *prevSt,
|
||||
const StationGraphObject *nextSt, const QPointF &pos,
|
||||
const double tolerance)
|
||||
{
|
||||
const double platformOffset = Session->platformOffset;
|
||||
|
||||
JobStopEntry job;
|
||||
|
||||
// Find nearest station
|
||||
const StationGraphObject *nearestSt = nullptr;
|
||||
|
||||
double nextStDistance = 0;
|
||||
if (nextSt)
|
||||
{
|
||||
nextStDistance = qAbs(nextSt->xPos - pos.x());
|
||||
if (nextStDistance <= tolerance)
|
||||
{
|
||||
// Next station is a good candidate
|
||||
nearestSt = nextSt;
|
||||
}
|
||||
}
|
||||
|
||||
if (prevSt)
|
||||
{
|
||||
const double prevStRight = prevSt->xPos + prevSt->platforms.count() * platformOffset;
|
||||
if (pos.x() >= prevSt->xPos && pos.x() <= prevStRight)
|
||||
{
|
||||
// Requested pos is inside this station
|
||||
nearestSt = prevSt;
|
||||
}
|
||||
else if (pos.x() >= prevStRight)
|
||||
{
|
||||
// Requested position is between prevSt and nextSt, find nearest
|
||||
const double prevStDistance = pos.x() - prevStRight;
|
||||
if (prevStDistance <= tolerance && (!nearestSt || prevStDistance < nextStDistance))
|
||||
{
|
||||
nearestSt = prevSt;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!nearestSt)
|
||||
return job; // Both stations exceed tolerance, null selection
|
||||
|
||||
const StationGraphObject::PlatformGraph *prevPlatf = nullptr;
|
||||
const StationGraphObject::PlatformGraph *nextPlatf = nullptr;
|
||||
double prevPos = 0;
|
||||
double nextPos = 0;
|
||||
|
||||
double xPos = nearestSt->xPos;
|
||||
for (const StationGraphObject::PlatformGraph &platf : nearestSt->platforms)
|
||||
{
|
||||
if (xPos >= pos.x())
|
||||
{
|
||||
// We went past the requested position
|
||||
nextPlatf = &platf;
|
||||
nextPos = xPos;
|
||||
break;
|
||||
}
|
||||
|
||||
prevPlatf = &platf;
|
||||
prevPos = xPos;
|
||||
|
||||
xPos += platformOffset;
|
||||
}
|
||||
|
||||
// Find nearest platform
|
||||
const StationGraphObject::PlatformGraph *resultPlatf = nullptr;
|
||||
|
||||
const double prevDistance = qAbs(prevPos - pos.x());
|
||||
if (prevPlatf && prevDistance <= tolerance)
|
||||
{
|
||||
// Previous platform is a good candidate
|
||||
resultPlatf = prevPlatf;
|
||||
}
|
||||
|
||||
const double nextDistance = qAbs(nextPos - pos.x());
|
||||
if (nextPlatf && nextDistance <= tolerance)
|
||||
{
|
||||
// Next platform is a good candidate
|
||||
if (!resultPlatf || nextDistance < prevDistance)
|
||||
{
|
||||
// We are the nearest
|
||||
resultPlatf = nextPlatf;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resultPlatf)
|
||||
return job; // No match
|
||||
|
||||
for (const StationGraphObject::JobStopGraph &jobStop : resultPlatf->jobStops)
|
||||
{
|
||||
// NOTE: in stops arrival comes BEFORE departure
|
||||
if (jobStop.arrivalY <= pos.y() + tolerance && jobStop.departureY >= pos.y() - tolerance)
|
||||
{
|
||||
// Found match
|
||||
job = jobStop.stop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
JobStopEntry LineGraphScene::getJobAt(const QPointF &pos, const double tolerance)
|
||||
{
|
||||
JobStopEntry job;
|
||||
|
||||
if (stationPositions.isEmpty())
|
||||
return job;
|
||||
|
||||
db_id prevStId = 0;
|
||||
db_id nextStId = 0;
|
||||
|
||||
const StationPosEntry *entry = nullptr;
|
||||
|
||||
for (const StationPosEntry &stPos : qAsConst(stationPositions))
|
||||
{
|
||||
if (stPos.xPos <= pos.x())
|
||||
{
|
||||
prevStId = stPos.stationId;
|
||||
entry = &stPos;
|
||||
}
|
||||
|
||||
if (stPos.xPos >= pos.x())
|
||||
{
|
||||
// We went past the requested position
|
||||
nextStId = stPos.stationId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto prevSt = stations.constFind(prevStId);
|
||||
auto nextSt = stations.constFind(nextStId);
|
||||
|
||||
const StationGraphObject *prevStPtr = prevSt == stations.constEnd() ? nullptr : &prevSt.value();
|
||||
const StationGraphObject *nextStPtr = nextSt == stations.constEnd() ? nullptr : &nextSt.value();
|
||||
|
||||
if (!prevStPtr && !nextStPtr)
|
||||
return job; // Error
|
||||
|
||||
job = getJobStopAt(prevStPtr, nextStPtr, pos, tolerance);
|
||||
if (job.jobId)
|
||||
return job; // Found match
|
||||
|
||||
// Check job segments
|
||||
if (!entry)
|
||||
return job; // Error, no match
|
||||
|
||||
double prevSegDistance = -1;
|
||||
for (const JobSegmentGraph &segment : qAsConst(entry->nextSegmentJobGraphs))
|
||||
{
|
||||
// NOTE: in segments arrival comes AFTER departure
|
||||
const QRectF r = QRectF(segment.fromDeparture, segment.toArrival).normalized();
|
||||
if (r.contains(pos))
|
||||
{
|
||||
// Requested position is inside bounds, might be a match
|
||||
const double resultingY = r.top() + (pos.x() - r.left()) * r.height() / r.width();
|
||||
const double segDistance = qAbs(resultingY - pos.y());
|
||||
if (prevSegDistance < 0 || segDistance < prevSegDistance)
|
||||
{
|
||||
// We are a better match than previous, replace it
|
||||
// Use departure station ('from') because arrival station might be last one
|
||||
// So there might be no segments after arrival
|
||||
job.stopId = segment.fromStopId;
|
||||
job.jobId = segment.jobId;
|
||||
job.category = segment.category;
|
||||
|
||||
// Store new minimum distance
|
||||
prevSegDistance = segDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return job;
|
||||
}
|
||||
|
||||
bool LineGraphScene::loadStation(StationGraphObject &st, QString &outFullName)
|
||||
{
|
||||
sqlite3pp::query q(mDb);
|
||||
|
||||
q.prepare("SELECT name,short_name,type FROM stations WHERE id=?");
|
||||
q.bind(1, st.stationId);
|
||||
if (q.step() != SQLITE_ROW)
|
||||
{
|
||||
qWarning() << "Graph: invalid station ID" << st.stationId;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load station
|
||||
auto row = q.getRows();
|
||||
|
||||
outFullName = row.get<QString>(0);
|
||||
st.stationName = row.get<QString>(1);
|
||||
if (st.stationName.isEmpty())
|
||||
{
|
||||
// Empty short name, fallback to full name
|
||||
st.stationName = outFullName;
|
||||
}
|
||||
st.stationType = utils::StationType(row.get<int>(2));
|
||||
|
||||
// Load platforms
|
||||
const QRgb white = qRgb(255, 255, 255);
|
||||
q.prepare(
|
||||
"SELECT id, type, color_rgb, name FROM station_tracks WHERE station_id=? ORDER BY pos");
|
||||
q.bind(1, st.stationId);
|
||||
for (auto r : q)
|
||||
{
|
||||
StationGraphObject::PlatformGraph platf;
|
||||
platf.platformId = r.get<db_id>(0);
|
||||
platf.platformType = utils::StationTrackType(r.get<int>(1));
|
||||
if (r.column_type(2) == SQLITE_NULL) // NULL is white (#FFFFFF) -> default value
|
||||
platf.color = white;
|
||||
else
|
||||
platf.color = QRgb(r.get<int>(2));
|
||||
platf.platformName = r.get<QString>(3);
|
||||
st.platforms.append(platf);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphScene::updateStationNames()
|
||||
{
|
||||
sqlite3pp::query q(mDb);
|
||||
|
||||
q.prepare("SELECT name,short_name FROM stations WHERE id=?");
|
||||
|
||||
for (StationGraphObject &st : stations)
|
||||
{
|
||||
q.bind(1, st.stationId);
|
||||
if (q.step() != SQLITE_ROW)
|
||||
{
|
||||
qWarning() << "Graph: invalid station ID" << st.stationId;
|
||||
continue;
|
||||
}
|
||||
|
||||
st.stationName = q.getRows().get<QString>(1);
|
||||
QString fullName = q.getRows().get<QString>(0);
|
||||
if (st.stationName.isEmpty())
|
||||
{
|
||||
// Empty short name, fallback to full name
|
||||
st.stationName = fullName;
|
||||
}
|
||||
|
||||
if (graphObjectId == st.stationId && graphType == LineGraphType::SingleStation)
|
||||
{
|
||||
// If we are a station graph also update grah name
|
||||
graphObjectName = fullName;
|
||||
|
||||
// Notify views TODO: specify graph didn't really change, just name
|
||||
emit graphChanged(int(graphType), graphObjectId, this);
|
||||
}
|
||||
|
||||
q.reset();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphScene::loadFullLine(db_id lineId)
|
||||
{
|
||||
// TODO: maybe show also station gates
|
||||
// TODO: load only visible stations, other will be loaded when scrolling graph
|
||||
sqlite3pp::query q(mDb, "SELECT name FROM lines WHERE id=?");
|
||||
q.bind(1, lineId);
|
||||
if (q.step() != SQLITE_ROW)
|
||||
{
|
||||
qWarning() << "Graph: invalid line ID" << lineId;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Store line name
|
||||
graphObjectName = q.getRows().get<QString>(0);
|
||||
|
||||
// Get segments
|
||||
q.prepare("SELECT ls.id, ls.seg_id, ls.direction,"
|
||||
"seg.name, seg.max_speed_kmh, seg.type, seg.distance_meters,"
|
||||
"g1.station_id, g2.station_id"
|
||||
" FROM line_segments ls"
|
||||
" JOIN railway_segments seg ON seg.id=ls.seg_id"
|
||||
" JOIN station_gates g1 ON g1.id=seg.in_gate_id"
|
||||
" JOIN station_gates g2 ON g2.id=seg.out_gate_id"
|
||||
" WHERE ls.line_id=?"
|
||||
" ORDER BY ls.pos");
|
||||
q.bind(1, lineId);
|
||||
|
||||
db_id lastStationId = 0;
|
||||
double curPos = Session->horizOffset + Session->stationOffset / 2;
|
||||
|
||||
QString unusedStFullName;
|
||||
|
||||
for (auto seg : q)
|
||||
{
|
||||
db_id lineSegmentId = seg.get<db_id>(0);
|
||||
db_id railwaySegmentId = seg.get<db_id>(1);
|
||||
bool reversed = seg.get<int>(2) != 0;
|
||||
|
||||
// item.segmentName = seg.get<QString>(3);
|
||||
// item.maxSpeedKmH = seg.get<int>(4);
|
||||
// item.segmentType = utils::RailwaySegmentType(seg.get<int>(5));
|
||||
// item.distanceMeters = seg.get<int>(6);
|
||||
|
||||
// Store first segment end
|
||||
db_id fromStationId = seg.get<db_id>(7);
|
||||
|
||||
// Store also the other end of segment for last item
|
||||
db_id otherStationId = seg.get<db_id>(8);
|
||||
|
||||
if (reversed)
|
||||
{
|
||||
// Swap segments ends
|
||||
qSwap(fromStationId, otherStationId);
|
||||
}
|
||||
|
||||
if (!lastStationId)
|
||||
{
|
||||
// First line station
|
||||
StationGraphObject st;
|
||||
st.stationId = fromStationId;
|
||||
if (!loadStation(st, unusedStFullName))
|
||||
return false;
|
||||
|
||||
st.xPos = curPos;
|
||||
stations.insert(st.stationId, st);
|
||||
stationPositions.append({st.stationId, railwaySegmentId, st.xPos, {}});
|
||||
curPos += st.platforms.count() * Session->platformOffset + Session->stationOffset;
|
||||
}
|
||||
else if (fromStationId != lastStationId)
|
||||
{
|
||||
qWarning() << "Line segments are not adjacent, ID:" << lineSegmentId
|
||||
<< "LINE:" << lineId;
|
||||
return false;
|
||||
}
|
||||
|
||||
StationGraphObject stB;
|
||||
stB.stationId = otherStationId;
|
||||
if (!loadStation(stB, unusedStFullName))
|
||||
return false;
|
||||
|
||||
stB.xPos = curPos;
|
||||
stations.insert(stB.stationId, stB);
|
||||
|
||||
stationPositions.last().segmentId = railwaySegmentId;
|
||||
stationPositions.append({stB.stationId, 0, stB.xPos, {}});
|
||||
|
||||
curPos += stB.platforms.count() * Session->platformOffset + Session->stationOffset;
|
||||
lastStationId = stB.stationId;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphScene::loadStationJobStops(StationGraphObject &st)
|
||||
{
|
||||
// Reset previous job graphs
|
||||
for (StationGraphObject::PlatformGraph &platf : st.platforms)
|
||||
{
|
||||
platf.jobStops.clear();
|
||||
}
|
||||
|
||||
sqlite3pp::query q_prevSegment(
|
||||
mDb, "SELECT c.seg_id, MAX(stops.departure)"
|
||||
" FROM stops"
|
||||
" LEFT JOIN railway_connections c ON c.id=stops.next_segment_conn_id"
|
||||
" WHERE stops.job_id=? AND stops.departure<?");
|
||||
|
||||
sqlite3pp::query q(mDb,
|
||||
"SELECT stops.id, stops.job_id, jobs.category,"
|
||||
"stops.arrival, stops.departure,"
|
||||
"g_in.track_id, g_out.track_id,"
|
||||
"c.seg_id"
|
||||
" FROM stops"
|
||||
" JOIN jobs ON stops.job_id=jobs.id"
|
||||
" LEFT JOIN station_gate_connections g_in ON g_in.id=stops.in_gate_conn"
|
||||
" LEFT JOIN station_gate_connections g_out ON g_out.id=stops.out_gate_conn"
|
||||
" LEFT JOIN railway_connections c ON c.id=stops.next_segment_conn_id"
|
||||
" WHERE stops.station_id=?"
|
||||
" ORDER BY stops.arrival");
|
||||
q.bind(1, st.stationId);
|
||||
|
||||
const double vertOffset = Session->vertOffset;
|
||||
const double hourOffset = Session->hourOffset;
|
||||
|
||||
for (auto stop : q)
|
||||
{
|
||||
StationGraphObject::JobStopGraph jobStop;
|
||||
jobStop.stop.stopId = stop.get<db_id>(0);
|
||||
jobStop.stop.jobId = stop.get<db_id>(1);
|
||||
jobStop.stop.category = JobCategory(stop.get<int>(2));
|
||||
QTime arrival = stop.get<QTime>(3);
|
||||
QTime departure = stop.get<QTime>(4);
|
||||
db_id trackId = stop.get<db_id>(5);
|
||||
db_id outTrackId = stop.get<db_id>(6);
|
||||
db_id nextSegId = stop.get<db_id>(7);
|
||||
|
||||
if (trackId && outTrackId && trackId != outTrackId)
|
||||
{
|
||||
// Not last stop, neither first stop. Tracks must correspond
|
||||
qWarning() << "Stop:" << jobStop.stop.stopId << "Track not corresponding, using in";
|
||||
}
|
||||
else if (!trackId)
|
||||
{
|
||||
if (outTrackId)
|
||||
trackId = outTrackId; // First stop, use out gate connection
|
||||
else
|
||||
{
|
||||
qWarning() << "Stop:" << jobStop.stop.stopId << "Both in/out track NULL, skipping";
|
||||
continue; // Skip this stop
|
||||
}
|
||||
}
|
||||
|
||||
StationGraphObject::PlatformGraph *platf = nullptr;
|
||||
|
||||
// Find platform
|
||||
for (StationGraphObject::PlatformGraph &p : st.platforms)
|
||||
{
|
||||
if (p.platformId == trackId)
|
||||
{
|
||||
platf = &p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!platf)
|
||||
{
|
||||
// Requested platform is not in this station
|
||||
qWarning() << "Stop:" << jobStop.stop.stopId << "Track is not in this station";
|
||||
continue; // Skip this stop
|
||||
}
|
||||
|
||||
// Check if we need job label
|
||||
bool isSegmentVisible = false;
|
||||
if (graphType == LineGraphType::SingleStation)
|
||||
isSegmentVisible = true; // Skip checking, always draw label
|
||||
|
||||
if (!isSegmentVisible && nextSegId)
|
||||
{
|
||||
for (const StationPosEntry &stPos : qAsConst(stationPositions))
|
||||
{
|
||||
if (stPos.segmentId == nextSegId)
|
||||
{
|
||||
isSegmentVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isSegmentVisible)
|
||||
{
|
||||
// Check if previous segment is visible
|
||||
q_prevSegment.bind(1, jobStop.stop.jobId);
|
||||
q_prevSegment.bind(2, arrival);
|
||||
q_prevSegment.step();
|
||||
auto seg = q_prevSegment.getRows();
|
||||
if (seg.column_type(0) != SQLITE_NULL)
|
||||
{
|
||||
db_id prevSegId = seg.get<db_id>(0);
|
||||
for (const StationPosEntry &stPos : qAsConst(stationPositions))
|
||||
{
|
||||
if (stPos.segmentId == prevSegId)
|
||||
{
|
||||
isSegmentVisible = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
q_prevSegment.reset();
|
||||
}
|
||||
|
||||
// Draw only if neither segment is visible or when graph is SignleStation
|
||||
jobStop.drawLabel = !isSegmentVisible || graphType == LineGraphType::SingleStation;
|
||||
|
||||
// Calculate coordinates
|
||||
jobStop.arrivalY = vertOffset + timeToHourFraction(arrival) * hourOffset;
|
||||
jobStop.departureY = vertOffset + timeToHourFraction(departure) * hourOffset;
|
||||
|
||||
platf->jobStops.append(jobStop);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphScene::loadSegmentJobs(LineGraphScene::StationPosEntry &stPos,
|
||||
const StationGraphObject &fromSt,
|
||||
const StationGraphObject &toSt)
|
||||
{
|
||||
// Reset previous job segment graph
|
||||
stPos.nextSegmentJobGraphs.clear();
|
||||
|
||||
const double vertOffset = Session->vertOffset;
|
||||
const double hourOffset = Session->hourOffset;
|
||||
const double platfOffset = Session->platformOffset;
|
||||
|
||||
sqlite3pp::query q(
|
||||
mDb, "SELECT sub.*, jobs.category, g_out.track_id, g_in.track_id FROM ("
|
||||
" SELECT stops.id AS cur_stop_id, lead(stops.id, 1) OVER win AS next_stop_id,"
|
||||
" stops.station_id,"
|
||||
" stops.job_id,"
|
||||
" stops.departure, lead(stops.arrival, 1) OVER win AS next_stop_arrival,"
|
||||
" stops.out_gate_conn,"
|
||||
" lead(stops.in_gate_conn, 1) OVER win AS next_stop_g_in,"
|
||||
" seg_conn.seg_id"
|
||||
" FROM stops"
|
||||
" LEFT JOIN railway_connections seg_conn ON seg_conn.id=stops.next_segment_conn_id"
|
||||
" WINDOW win AS (PARTITION BY stops.job_id ORDER BY stops.arrival)"
|
||||
") AS sub"
|
||||
" JOIN station_gate_connections g_out ON g_out.id=sub.out_gate_conn"
|
||||
" JOIN station_gate_connections g_in ON g_in.id=sub.next_stop_g_in"
|
||||
" JOIN jobs ON jobs.id=sub.job_id"
|
||||
" WHERE sub.seg_id=?");
|
||||
|
||||
q.bind(1, stPos.segmentId);
|
||||
for (auto stop : q)
|
||||
{
|
||||
JobSegmentGraph job;
|
||||
job.fromStopId = stop.get<db_id>(0);
|
||||
job.toStopId = stop.get<db_id>(1);
|
||||
db_id stId = stop.get<db_id>(2);
|
||||
job.jobId = stop.get<db_id>(3);
|
||||
QTime departure = stop.get<QTime>(4);
|
||||
QTime arrival = stop.get<QTime>(5);
|
||||
// 6 - out gate connection
|
||||
// 7 - in gate connection
|
||||
// 8 - segment_id
|
||||
job.category = JobCategory(stop.get<int>(9));
|
||||
job.fromPlatfId = stop.get<db_id>(10);
|
||||
job.toPlatfId = stop.get<db_id>(11);
|
||||
|
||||
// NOTE: fromPlatfId and toPlatfId do not need to be reversed because represent correct
|
||||
// platforms Only stations might be reversed
|
||||
bool reverse = toSt.stationId == stId; // If job goes in opposite direction
|
||||
|
||||
// Calculate coordinates
|
||||
job.fromDeparture.rx() =
|
||||
stationPlatformPosition(reverse ? toSt : fromSt, job.fromPlatfId, platfOffset);
|
||||
job.fromDeparture.ry() = vertOffset + timeToHourFraction(departure) * hourOffset;
|
||||
|
||||
job.toArrival.rx() =
|
||||
stationPlatformPosition(reverse ? fromSt : toSt, job.toPlatfId, platfOffset);
|
||||
job.toArrival.ry() = vertOffset + timeToHourFraction(arrival) * hourOffset;
|
||||
|
||||
if (job.fromDeparture.x() < 0 || job.toArrival.x() < 0)
|
||||
continue; // Skip, couldn't find platform
|
||||
|
||||
stPos.nextSegmentJobGraphs.append(job);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void LineGraphScene::updateJobSelection(sqlite3pp::database &db, JobStopEntry &job)
|
||||
{
|
||||
if (!job.jobId)
|
||||
return;
|
||||
|
||||
query q(db);
|
||||
|
||||
if (job.stopId)
|
||||
{
|
||||
// Check if stop is valid
|
||||
q.prepare("SELECT job_id FROM stops WHERE id=?");
|
||||
q.bind(1, job.stopId);
|
||||
if (q.step() == SQLITE_ROW)
|
||||
{
|
||||
db_id jobId = q.getRows().get<db_id>(0);
|
||||
if (jobId != job.jobId)
|
||||
job.stopId = 0; // Stop doesn't belong to this job
|
||||
}
|
||||
else
|
||||
{
|
||||
// This stop doesn't exist anymore
|
||||
job.stopId = 0;
|
||||
}
|
||||
}
|
||||
|
||||
q.prepare("SELECT category FROM jobs WHERE id=?");
|
||||
q.bind(1, job.jobId);
|
||||
if (q.step() != SQLITE_ROW)
|
||||
{
|
||||
// Job doesn't exist anymore, clear selection
|
||||
job = JobStopEntry{};
|
||||
return;
|
||||
}
|
||||
|
||||
JobCategory newCategory = JobCategory(q.getRows().get<int>(0));
|
||||
|
||||
if (newCategory != job.category)
|
||||
{
|
||||
job.category = newCategory;
|
||||
}
|
||||
}
|
||||
|
||||
JobStopEntry LineGraphScene::getSelectedJob() const
|
||||
{
|
||||
return selectedJob;
|
||||
}
|
||||
|
||||
void LineGraphScene::setSelectedJob(JobStopEntry stop, bool sendChange)
|
||||
{
|
||||
const JobStopEntry oldJob = selectedJob;
|
||||
|
||||
selectedJob = stop;
|
||||
if (!selectedJob.jobId)
|
||||
{
|
||||
// Clear other members too
|
||||
selectedJob.stopId = 0;
|
||||
selectedJob.category = JobCategory::NCategories;
|
||||
}
|
||||
|
||||
if (sendChange
|
||||
&& (selectedJob.jobId != oldJob.jobId || selectedJob.category != oldJob.category))
|
||||
{
|
||||
emit redrawGraph();
|
||||
emit jobSelected(selectedJob.jobId, int(selectedJob.category), selectedJob.stopId);
|
||||
}
|
||||
}
|
||||
|
||||
bool LineGraphScene::requestShowZone(db_id stationId, db_id segmentId, QTime from, QTime to)
|
||||
{
|
||||
// TODO: when we will load incrementally, ensure relevant items are loaded
|
||||
|
||||
const double vertOffset = Session->vertOffset;
|
||||
const double hourOffset = Session->hourOffset;
|
||||
const double platfOffset = Session->platformOffset;
|
||||
|
||||
QRectF result;
|
||||
result.setTop(vertOffset + timeToHourFraction(from) * hourOffset);
|
||||
result.setBottom(vertOffset + timeToHourFraction(to) * hourOffset);
|
||||
|
||||
// NOTE: Initially left() is 0 which will always be less than any station position
|
||||
// So the first station must set it's position regardless of left() value
|
||||
bool leftEdgeSet = false;
|
||||
|
||||
for (const StationPosEntry &entry : qAsConst(stationPositions))
|
||||
{
|
||||
// Match the requested station or both station in the segment
|
||||
if (entry.stationId == stationId || entry.segmentId == segmentId)
|
||||
{
|
||||
auto st = stations.constFind(entry.stationId);
|
||||
if (st == stations.constEnd())
|
||||
continue;
|
||||
|
||||
if (result.left() > entry.xPos || !leftEdgeSet)
|
||||
{
|
||||
result.setLeft(entry.xPos);
|
||||
leftEdgeSet = true;
|
||||
}
|
||||
|
||||
const int platfCount = st->platforms.count();
|
||||
const double rightPos = entry.xPos + platfCount * platfOffset;
|
||||
|
||||
if (result.right() < rightPos)
|
||||
result.setRight(rightPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Set a margin around the selection so it douesn't end up at view edges
|
||||
const double margin = hourOffset / 4;
|
||||
result.adjust(-margin, -margin, margin, margin);
|
||||
|
||||
emit requestShowRect(result);
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,371 @@
|
|||
/*
|
||||
* 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 "utils/scene/igraphscene.h"
|
||||
|
||||
#include <QVector>
|
||||
#include <QHash>
|
||||
|
||||
#include <QPointF>
|
||||
|
||||
#include "utils/types.h"
|
||||
|
||||
#include "graph/linegraphtypes.h"
|
||||
|
||||
#include "stationgraphobject.h"
|
||||
|
||||
namespace sqlite3pp {
|
||||
class database;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief Class to store line information
|
||||
*
|
||||
* Reimplement IGraphScene for railway line graph
|
||||
* Stores information to draw railway contents in a LineGraphView
|
||||
*
|
||||
* \sa LineGraphManager
|
||||
* \sa LineGraphView
|
||||
*/
|
||||
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
|
||||
*
|
||||
* Loads stations and jobs
|
||||
*
|
||||
* \param objectId Graph object ID
|
||||
* \param type Graph type
|
||||
* \param force Force reloading if objectId and type are the same as current
|
||||
*
|
||||
* \sa loadStation()
|
||||
* \sa loadFullLine()
|
||||
* \sa reloadJobs()
|
||||
*/
|
||||
bool loadGraph(db_id objectId, LineGraphType type, bool force = false);
|
||||
|
||||
/*!
|
||||
* \brief Load graph jobs
|
||||
*
|
||||
* Reloads only jobs but not stations
|
||||
* It also updates current job selection
|
||||
* \sa loadGraph()
|
||||
* \sa updateJobSelection()
|
||||
*/
|
||||
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);
|
||||
|
||||
inline LineGraphType getGraphType() const
|
||||
{
|
||||
return graphType;
|
||||
}
|
||||
|
||||
inline db_id getGraphObjectId() const
|
||||
{
|
||||
return graphObjectId;
|
||||
}
|
||||
|
||||
inline QString getGraphObjectName() const
|
||||
{
|
||||
return graphObjectName;
|
||||
}
|
||||
|
||||
/*!
|
||||
* \brief get selected job
|
||||
*
|
||||
* Get selected job info
|
||||
* \sa setSelectedJob()
|
||||
*/
|
||||
JobStopEntry getSelectedJob() const;
|
||||
|
||||
/*!
|
||||
* \brief setSelectedJob
|
||||
* \param stop a specific stop or just a job ID and its category
|
||||
* \param sendChange emit redrawGraph() and jobSelected() signals if true
|
||||
*
|
||||
* Set job selection and schedule graph redraw
|
||||
* \sa jobSelected()
|
||||
* \sa getSelectedJob()
|
||||
*/
|
||||
void setSelectedJob(JobStopEntry stop, bool sendChange = true);
|
||||
|
||||
/*!
|
||||
* \brief getDrawSelection
|
||||
* \return true if selection is drawn
|
||||
*
|
||||
* When true and a Job is selected, it gets a semi transparent aurea
|
||||
* around it's line to make it more visible
|
||||
*
|
||||
* \sa setDrawSelection()
|
||||
*/
|
||||
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
|
||||
* \param stationId null if you want to select segment
|
||||
* \param segmentId null if you want to select station
|
||||
* \param from top of the zone
|
||||
* \param to bottom of the zone
|
||||
*
|
||||
* Calculates a zone in the scene which show the selected station or entire segment
|
||||
* (It depends on which is non-null)
|
||||
* The y values are calculated from the QTime arguments
|
||||
* Then it requests the view to show the area.
|
||||
*
|
||||
* \sa requestShowRect()
|
||||
*/
|
||||
bool requestShowZone(db_id stationId, db_id segmentId, QTime from, QTime to);
|
||||
|
||||
signals:
|
||||
void graphChanged(int type, db_id objectId, LineGraphScene *self);
|
||||
|
||||
/*!
|
||||
* \brief job selected
|
||||
* \param jobId ID of the job on 0 if selection cleared.
|
||||
* \param category job's category casted to int
|
||||
* \param stopId possible stop ID hint to select a specific section
|
||||
*
|
||||
* Selection changed: either user clicked on a job
|
||||
* or on an empty zone to clear selection
|
||||
* \sa setSelectedJob()
|
||||
*/
|
||||
void jobSelected(db_id jobId, int category, db_id stopId);
|
||||
|
||||
public slots:
|
||||
/*!
|
||||
* \brief Reload everything
|
||||
*
|
||||
* Reload entire contents
|
||||
* \sa loadSegmentJobs()
|
||||
*/
|
||||
void reload();
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Graph of the job while is moving
|
||||
*
|
||||
* Contains informations to draw job line between two adjacent stations
|
||||
*/
|
||||
struct JobSegmentGraph
|
||||
{
|
||||
db_id jobId;
|
||||
JobCategory category;
|
||||
|
||||
db_id fromStopId;
|
||||
db_id fromPlatfId;
|
||||
QPointF fromDeparture;
|
||||
|
||||
db_id toStopId;
|
||||
db_id toPlatfId;
|
||||
QPointF toArrival;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Station entry on scene
|
||||
*
|
||||
* Represents a station item placeholder in an ordered list of scene
|
||||
*/
|
||||
struct StationPosEntry
|
||||
{
|
||||
db_id stationId;
|
||||
db_id segmentId;
|
||||
double xPos;
|
||||
|
||||
QVector<JobSegmentGraph> nextSegmentJobGraphs;
|
||||
/*!<
|
||||
* Stores job graph of the next segment
|
||||
* Which means jobs departing from this staation and going to next one
|
||||
*/
|
||||
};
|
||||
|
||||
private:
|
||||
/*!
|
||||
* \brief Get job stop at graph position
|
||||
*
|
||||
* \param prevSt Station at position's left
|
||||
* \param nextSt Station at position's right
|
||||
* \param pos Point in scene coordinates
|
||||
* \param tolerance A tolerance if mouse doesn't exactly click on job item
|
||||
*
|
||||
* Check if a job stop in this station matches requested position
|
||||
* Otherwise return null selection
|
||||
*/
|
||||
JobStopEntry getJobStopAt(const StationGraphObject *prevSt, const StationGraphObject *nextSt,
|
||||
const QPointF &pos, const double tolerance);
|
||||
|
||||
/*!
|
||||
* \brief Recalculate and store content size
|
||||
*
|
||||
* Stores cached calculated size of the graph.
|
||||
* It depends on settings like station offset.
|
||||
* \sa MRTPSettings
|
||||
*/
|
||||
void recalcContentSize();
|
||||
|
||||
/*!
|
||||
* \brief Load station details
|
||||
*
|
||||
* Loads station name and tracks (color, attributes, names)
|
||||
* It does NOT load jobs, only tracks
|
||||
*/
|
||||
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
|
||||
*
|
||||
* Loads stations of all railway line segments
|
||||
* \sa loadStation()
|
||||
*/
|
||||
bool loadFullLine(db_id lineId);
|
||||
|
||||
/*!
|
||||
* \brief Load job stops in station
|
||||
*
|
||||
* Load jobs stops, only the part on station's track
|
||||
* \sa loadSegmentJobs()
|
||||
*/
|
||||
bool loadStationJobStops(StationGraphObject &st);
|
||||
|
||||
/*!
|
||||
* \brief Load job segments between 2 stations
|
||||
*
|
||||
* Load job segments and stores them in 'from' station
|
||||
* \sa loadStationJobStops()
|
||||
*/
|
||||
bool loadSegmentJobs(StationPosEntry &stPos, const StationGraphObject &fromSt,
|
||||
const StationGraphObject &toSt);
|
||||
|
||||
/*!
|
||||
* \brief Update job selection category
|
||||
*
|
||||
* Updates current selection.
|
||||
* If selected job got removed or changed ID (Number) selection is cleared
|
||||
* If selected stop got removed or doesn't belong to selected job it's cleared
|
||||
* Category of selected job gets updated if changed in the meantime
|
||||
*/
|
||||
static void updateJobSelection(sqlite3pp::database &db, JobStopEntry &job);
|
||||
|
||||
private:
|
||||
friend class BackgroundHelper;
|
||||
friend class LineGraphManager;
|
||||
|
||||
sqlite3pp::database &mDb;
|
||||
|
||||
/*!
|
||||
* \brief Graph Object ID
|
||||
*
|
||||
* Can be station, segment, line ID
|
||||
* Depending on graph type
|
||||
*/
|
||||
db_id graphObjectId;
|
||||
|
||||
/*!
|
||||
* \brief Type of Graph displayed by Scene
|
||||
*/
|
||||
LineGraphType graphType;
|
||||
|
||||
/*!
|
||||
* \brief Graph Object Name
|
||||
*
|
||||
* Can be station, segment, line name
|
||||
* Depending on graph type
|
||||
*/
|
||||
QString graphObjectName;
|
||||
|
||||
QVector<StationPosEntry> stationPositions;
|
||||
QHash<db_id, StationGraphObject> stations;
|
||||
|
||||
/*!
|
||||
* \brief Job selection
|
||||
*
|
||||
* Caches selected job item ID
|
||||
*/
|
||||
JobStopEntry selectedJob;
|
||||
|
||||
bool m_drawSelection;
|
||||
|
||||
PendingUpdateFlags pendingUpdate;
|
||||
};
|
||||
|
||||
#endif // LINEGRAPHSCENE_H
|
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
* 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"
|
||||
|
||||
#include <sqlite3pp/sqlite3pp.h>
|
||||
using namespace sqlite3pp;
|
||||
|
||||
LineGraphSelectionHelper::LineGraphSelectionHelper(sqlite3pp::database &db) :
|
||||
mDb(db)
|
||||
{
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::tryFindJobStopInGraph(LineGraphScene *scene, db_id jobId,
|
||||
SegmentInfo &info)
|
||||
{
|
||||
query q(mDb);
|
||||
|
||||
switch (scene->getGraphType())
|
||||
{
|
||||
case LineGraphType::SingleStation:
|
||||
{
|
||||
q.prepare("SELECT stops.id, c.seg_id, MIN(stops.arrival), stops.departure"
|
||||
" FROM stops"
|
||||
" LEFT JOIN railway_connections c ON c.id=stops.next_segment_conn_id"
|
||||
" WHERE stops.job_id=? AND stops.station_id=?");
|
||||
break;
|
||||
}
|
||||
case LineGraphType::RailwaySegment:
|
||||
{
|
||||
q.prepare("SELECT stops.id, c.seg_id, MIN(stops.arrival), stops.departure, stops.station_id"
|
||||
" FROM railway_segments seg"
|
||||
" JOIN station_gates g1 ON g1.id=seg.in_gate_id"
|
||||
" JOIN station_gates g2 ON g2.id=seg.out_gate_id"
|
||||
" JOIN stops ON stops.job_id=? AND"
|
||||
" (stops.station_id=g1.station_id OR stops.station_id=g2.station_id)"
|
||||
" LEFT JOIN railway_connections c ON c.id=stops.next_segment_conn_id"
|
||||
" WHERE seg.id=?");
|
||||
break;
|
||||
}
|
||||
case LineGraphType::RailwayLine:
|
||||
{
|
||||
q.prepare("SELECT stops.id, c.seg_id, MIN(stops.arrival), stops.departure, stops.station_id"
|
||||
" FROM line_segments"
|
||||
" JOIN railway_segments seg ON seg.id=line_segments.seg_id"
|
||||
" JOIN station_gates g1 ON g1.id=seg.in_gate_id"
|
||||
" JOIN station_gates g2 ON g2.id=seg.out_gate_id"
|
||||
" JOIN stops ON stops.job_id=? AND"
|
||||
" (stops.station_id=g1.station_id OR stops.station_id=g2.station_id)"
|
||||
" LEFT JOIN railway_connections c ON c.id=stops.next_segment_conn_id"
|
||||
" WHERE line_segments.line_id=?");
|
||||
break;
|
||||
}
|
||||
case LineGraphType::NoGraph:
|
||||
case LineGraphType::NTypes:
|
||||
{
|
||||
// We need to load a new graph, give up
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// 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);
|
||||
|
||||
// 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);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
q.bind(1, jobId);
|
||||
q.bind(2, info.arrivalAndStart);
|
||||
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);
|
||||
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);
|
||||
q.reset();
|
||||
|
||||
// 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)
|
||||
{
|
||||
// We found only 1 stop, return that
|
||||
info.secondStationId = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
// 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
|
||||
info.secondStationId = stop.get<db_id>(3);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::tryFindNewGraphForJob(db_id jobId, SegmentInfo &info,
|
||||
db_id &outGraphObjId,
|
||||
LineGraphType &outGraphType)
|
||||
{
|
||||
if (!tryFindJobStopsAfter(jobId, info))
|
||||
return false; // No stops found
|
||||
|
||||
if (!info.secondStationId || !info.segmentId)
|
||||
{
|
||||
// We found only 1 stop, select first station
|
||||
outGraphObjId = info.firstStationId;
|
||||
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?)
|
||||
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)
|
||||
{
|
||||
// Found a line
|
||||
outGraphObjId = q.getRows().get<db_id>(0);
|
||||
outGraphType = LineGraphType::RailwayLine;
|
||||
return true;
|
||||
}
|
||||
|
||||
// No lines found, use the railway segment
|
||||
outGraphObjId = info.segmentId;
|
||||
outGraphType = LineGraphType::RailwaySegment;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::requestJobSelection(LineGraphScene *scene, db_id jobId, bool select,
|
||||
bool ensureVisible)
|
||||
{
|
||||
// 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
|
||||
|
||||
JobStopEntry selectedJob;
|
||||
selectedJob.jobId = jobId;
|
||||
selectedJob.category = JobCategory(q.getRows().get<int>(0));
|
||||
|
||||
SegmentInfo info;
|
||||
|
||||
// Try to select earliest stop of this job in current graph, if any
|
||||
const bool found = tryFindJobStopInGraph(scene, selectedJob.jobId, info);
|
||||
|
||||
if (!found)
|
||||
{
|
||||
// Find a NEW line graph or segment or station with this job
|
||||
|
||||
// Find first 2 stops of the job
|
||||
|
||||
db_id graphObjId = 0;
|
||||
LineGraphType graphType = LineGraphType::NoGraph;
|
||||
|
||||
if (!tryFindNewGraphForJob(selectedJob.jobId, info, graphObjId, graphType))
|
||||
{
|
||||
// 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
|
||||
const JobStopEntry oldSelection = scene->getSelectedJob();
|
||||
scene->setSelectedJob(JobStopEntry{}, false); // Clear selection
|
||||
|
||||
// Select the graph
|
||||
scene->loadGraph(graphObjId, graphType);
|
||||
|
||||
// Restore previous selection
|
||||
scene->setSelectedJob(oldSelection, false);
|
||||
}
|
||||
|
||||
// Extract the info
|
||||
selectedJob.stopId = info.firstStopId;
|
||||
|
||||
if (!selectedJob.stopId)
|
||||
return false; // No stop found, abort
|
||||
|
||||
// Select job
|
||||
if (select)
|
||||
scene->setSelectedJob(selectedJob);
|
||||
|
||||
if (ensureVisible)
|
||||
{
|
||||
return scene->requestShowZone(info.firstStationId, info.segmentId, info.arrivalAndStart,
|
||||
info.departure);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LineGraphSelectionHelper::requestCurrentJobPrevSegmentVisible(LineGraphScene *scene,
|
||||
bool goToStart)
|
||||
{
|
||||
JobStopEntry selectedJob = scene->getSelectedJob();
|
||||
if (!selectedJob.jobId)
|
||||
return false; // No job selected, nothing to do
|
||||
|
||||
query q(mDb);
|
||||
|
||||
SegmentInfo info;
|
||||
if (selectedJob.stopId && !goToStart)
|
||||
{
|
||||
// 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"
|
||||
" JOIN stops s1 ON s1.job_id=s2.job_id"
|
||||
" LEFT JOIN railway_connections c ON c.id=s1.next_segment_conn_id"
|
||||
" 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)
|
||||
{
|
||||
auto stop = q.getRows();
|
||||
db_id jobId = stop.get<db_id>(0);
|
||||
if (jobId == selectedJob.jobId)
|
||||
{
|
||||
// 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.secondStationId = stop.get<db_id>(6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!info.firstStopId)
|
||||
{
|
||||
// goToStart or failed to get previous stop so go to start anyway
|
||||
|
||||
if (!tryFindJobStopsAfter(selectedJob.jobId, info))
|
||||
return false; // No stops found, give up
|
||||
}
|
||||
|
||||
db_id graphObjId = 0;
|
||||
LineGraphType graphType = LineGraphType::NoGraph;
|
||||
|
||||
if (!tryFindNewGraphForJob(selectedJob.jobId, info, graphObjId, graphType))
|
||||
{
|
||||
// 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
|
||||
|
||||
// Select the graph
|
||||
scene->loadGraph(graphObjId, graphType);
|
||||
|
||||
// Restore selection
|
||||
selectedJob.stopId = info.firstStopId;
|
||||
scene->setSelectedJob(selectedJob); // This time emit
|
||||
|
||||
return scene->requestShowZone(info.firstStationId, info.segmentId, info.arrivalAndStart,
|
||||
info.departure);
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
JobStopEntry selectedJob = scene->getSelectedJob();
|
||||
if (!selectedJob.jobId)
|
||||
return false; // No job selected, nothing to do
|
||||
|
||||
query q(mDb);
|
||||
|
||||
SegmentInfo info;
|
||||
if (selectedJob.stopId && !goToEnd)
|
||||
{
|
||||
// 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"
|
||||
" LEFT JOIN railway_connections c ON c.id=s1.next_segment_conn_id"
|
||||
" 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)
|
||||
{
|
||||
auto stop = q.getRows();
|
||||
db_id jobId = stop.get<db_id>(0);
|
||||
if (jobId == selectedJob.jobId)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!info.firstStopId)
|
||||
{
|
||||
// goToEnd or failed to get next stop so go to end anyway
|
||||
|
||||
// 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)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
db_id graphObjId = 0;
|
||||
LineGraphType graphType = LineGraphType::NoGraph;
|
||||
|
||||
if (!tryFindNewGraphForJob(selectedJob.jobId, info, graphObjId, graphType))
|
||||
{
|
||||
// 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
|
||||
|
||||
// Select the graph
|
||||
scene->loadGraph(graphObjId, graphType);
|
||||
|
||||
// Restore selection
|
||||
selectedJob.stopId = info.firstStopId;
|
||||
scene->setSelectedJob(selectedJob); // This time emit
|
||||
|
||||
return scene->requestShowZone(info.firstStationId, info.segmentId, info.arrivalAndStart,
|
||||
info.departure);
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
#include "utils/types.h"
|
||||
#include "graph/linegraphtypes.h"
|
||||
#include <QTime>
|
||||
|
||||
class LineGraphScene;
|
||||
|
||||
namespace sqlite3pp {
|
||||
class database;
|
||||
}
|
||||
|
||||
class LineGraphSelectionHelper
|
||||
{
|
||||
public:
|
||||
/*!
|
||||
* \brief The SegmentInfo struct
|
||||
*/
|
||||
struct SegmentInfo
|
||||
{
|
||||
db_id segmentId = 0;
|
||||
db_id firstStationId = 0;
|
||||
db_id secondStationId = 0;
|
||||
db_id firstStopId = 0;
|
||||
|
||||
/*!
|
||||
* \brief arrival and start
|
||||
*
|
||||
* Input: hour time limit after which stops are searched.
|
||||
* Output: first stop arrival
|
||||
*/
|
||||
QTime arrivalAndStart;
|
||||
QTime departure; //!< departure of first or second stop (if found)
|
||||
};
|
||||
|
||||
LineGraphSelectionHelper(sqlite3pp::database &db);
|
||||
|
||||
// Low level API
|
||||
|
||||
/*!
|
||||
* \brief find job in current graph
|
||||
* \param scene the graph scene
|
||||
* \param jobId job ID
|
||||
* \param info structure to get output result
|
||||
* \return true if found
|
||||
*
|
||||
* Try to find a job stop in current graph.
|
||||
* 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);
|
||||
|
||||
/*!
|
||||
* \brief find 2 job stops after requested hour
|
||||
* \param jobId job ID
|
||||
* \param info structure to get output result, and pass SegmentInfo::arrivalAndStart
|
||||
* \return true if found 1 or 2 stops
|
||||
*
|
||||
* Try to find 2 job stops after requested hour (SegmentInfo::arrivalAndStart).
|
||||
* If only 1 stop is found, SegmentInfo::secondStId is 0.
|
||||
*/
|
||||
bool tryFindJobStopsAfter(db_id jobId, SegmentInfo &info);
|
||||
|
||||
/*!
|
||||
* \brief try find a new graph for job
|
||||
* \param jobId job ID
|
||||
* \param info structure to get output result, and pass SegmentInfo::arrivalAndStart
|
||||
* \param outGraphObjId output object to load in scene
|
||||
* \param outGraphType graph scene type
|
||||
* \return false if not found
|
||||
*
|
||||
* 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);
|
||||
|
||||
public:
|
||||
// High level API
|
||||
|
||||
/*!
|
||||
* \brief request job selection
|
||||
* \param scene the graph scene
|
||||
* \param jobId jobId
|
||||
* \param select do you want to select the job?
|
||||
* \param ensureVisible do you want to make it visible on LineGraphView
|
||||
* \return true on success
|
||||
*
|
||||
* Try to select or make visible (or both) the requested job on current scene graph.
|
||||
* If the job is not found on current graph, it tries to find a new grah and loads it.
|
||||
*
|
||||
* \sa ViewManager::requestJobSelection()
|
||||
*/
|
||||
bool requestJobSelection(LineGraphScene *scene, db_id jobId, bool select, bool ensureVisible);
|
||||
|
||||
bool requestCurrentJobPrevSegmentVisible(LineGraphScene *scene, bool goToStart);
|
||||
bool requestCurrentJobNextSegmentVisible(LineGraphScene *scene, bool goToEnd);
|
||||
|
||||
private:
|
||||
sqlite3pp::database &mDb;
|
||||
};
|
||||
|
||||
#endif // LINEGRAPHSELECTIONHELPER_H
|
|
@ -0,0 +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()
|
||||
{
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* 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
|
||||
|
||||
#include <QVector>
|
||||
|
||||
#include "utils/types.h"
|
||||
|
||||
#include "stations/station_utils.h"
|
||||
#include <QRgb>
|
||||
|
||||
/*!
|
||||
* \brief Graph of a railway station
|
||||
*
|
||||
* Contains informations to draw station name, platforms and jobs
|
||||
*
|
||||
* \sa PlatformGraph
|
||||
*/
|
||||
class StationGraphObject
|
||||
{
|
||||
public:
|
||||
StationGraphObject();
|
||||
|
||||
db_id stationId;
|
||||
QString stationName;
|
||||
utils::StationType stationType;
|
||||
|
||||
/*!
|
||||
* \brief Graph of the job while is stopping
|
||||
*
|
||||
* Contains informations to draw job line on top of the PlatformGraph
|
||||
*/
|
||||
struct JobStopGraph
|
||||
{
|
||||
JobStopEntry stop;
|
||||
double arrivalY;
|
||||
double departureY;
|
||||
bool drawLabel;
|
||||
};
|
||||
|
||||
/*!
|
||||
* \brief Graph of a station track (platform)
|
||||
*
|
||||
* Contains informations to draw platform line and header name
|
||||
* \sa JobStopGraph
|
||||
*/
|
||||
struct PlatformGraph
|
||||
{
|
||||
db_id platformId;
|
||||
QString platformName;
|
||||
QRgb color;
|
||||
QFlags<utils::StationTrackType> platformType;
|
||||
QVector<JobStopGraph> jobStops;
|
||||
};
|
||||
|
||||
QVector<PlatformGraph> platforms;
|
||||
|
||||
double xPos;
|
||||
};
|
||||
|
||||
#endif // STATIONGRAPHOBJECT_H
|
|
@ -1,39 +0,0 @@
|
|||
#include "stationlayer.h"
|
||||
|
||||
#include <QPainter>
|
||||
|
||||
#include "graphmanager.h"
|
||||
#include "backgroundhelper.h"
|
||||
|
||||
#include "app/session.h"
|
||||
#include "lines/linestorage.h"
|
||||
|
||||
StationLayer::StationLayer(GraphManager *mgr, QWidget *parent) :
|
||||
QWidget(parent),
|
||||
graphMgr(mgr),
|
||||
horizontalScroll(0)
|
||||
{
|
||||
connect(Session->mLineStorage, &LineStorage::stationNameChanged,
|
||||
this, static_cast<void(QWidget::*)()>(&StationLayer::update));
|
||||
}
|
||||
|
||||
void StationLayer::setScroll(int value)
|
||||
{
|
||||
horizontalScroll = value;
|
||||
update();
|
||||
}
|
||||
|
||||
void StationLayer::paintEvent(QPaintEvent *)
|
||||
{
|
||||
QPainter p(this);
|
||||
QColor c(255, 255, 255, 220);
|
||||
p.fillRect(rect(), c);
|
||||
|
||||
db_id lineId = graphMgr->getCurLineId();
|
||||
if(lineId == 0)
|
||||
return; //No line selected
|
||||
|
||||
graphMgr->getBackGround()->drawForegroundStationLabels(&p, rect(),
|
||||
horizontalScroll,
|
||||
lineId);
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
#ifndef STATIONLAYER_H
|
||||
#define STATIONLAYER_H
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
class GraphManager;
|
||||
|
||||
class StationLayer : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
StationLayer(GraphManager *mgr, QWidget *parent = nullptr);
|
||||
|
||||
public slots:
|
||||
void setScroll(int value);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent *) override;
|
||||
|
||||
private:
|
||||
GraphManager *graphMgr;
|
||||
int horizontalScroll;
|
||||
};
|
||||
|
||||
#endif // STATIONLAYER_H
|
|
@ -0,0 +1,20 @@
|
|||
set(MR_TIMETABLE_PLANNER_SOURCES
|
||||
${MR_TIMETABLE_PLANNER_SOURCES}
|
||||
|
||||
graph/view/backgroundhelper.h
|
||||
graph/view/backgroundhelper.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
|
||||
|
||||
PARENT_SCOPE
|
||||
)
|
|
@ -0,0 +1,461 @@
|
|||
/*
|
||||
* 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 "utils/jobcategorystrings.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include "utils/font_utils.h"
|
||||
|
||||
#include <QtMath>
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
void BackgroundHelper::drawHourPanel(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
// TODO: settings
|
||||
QFont hourTextFont;
|
||||
setFontPointSizeDPI(hourTextFont, 15, painter);
|
||||
|
||||
QPen hourTextPen(AppSettings.getHourTextColor());
|
||||
|
||||
const int vertOffset = Session->vertOffset;
|
||||
const int hourOffset = Session->hourOffset;
|
||||
|
||||
painter->setFont(hourTextFont);
|
||||
painter->setPen(hourTextPen);
|
||||
|
||||
// qDebug() << "Drawing hours..." << rect << scroll;
|
||||
const QString fmt(QStringLiteral("%1:00"));
|
||||
|
||||
const qreal top = rect.top() - vertOffset;
|
||||
const qreal bottom = rect.bottom();
|
||||
|
||||
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 + vertOffset - hourOffset / 2);
|
||||
|
||||
for (; h <= 24 && labelRect.top() <= bottom; h++)
|
||||
{
|
||||
// qDebug() << "Y:" << y << fmt.arg(h);
|
||||
painter->drawText(labelRect, fmt.arg(h), QTextOption(Qt::AlignVCenter | Qt::AlignRight));
|
||||
labelRect.moveTop(labelRect.top() + hourOffset);
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawBackgroundHourLines(QPainter *painter, const QRectF &rect)
|
||||
{
|
||||
const double horizOffset = Session->horizOffset;
|
||||
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();
|
||||
|
||||
if (x1 > x2 || b < vertOffset || t > b)
|
||||
return;
|
||||
|
||||
int firstH = qCeil((t - vertOffset) / hourOffset);
|
||||
int lastH = qFloor((b - vertOffset) / hourOffset);
|
||||
|
||||
if (firstH > 24 || lastH < 0)
|
||||
return;
|
||||
|
||||
if (firstH < 0)
|
||||
firstH = 0;
|
||||
if (lastH > 24)
|
||||
lastH = 24;
|
||||
|
||||
const int n = lastH - firstH + 1;
|
||||
if (n <= 0)
|
||||
return;
|
||||
|
||||
qreal y = vertOffset + firstH * hourOffset;
|
||||
|
||||
QLineF *arr = new QLineF[n];
|
||||
for (int i = 0; i < n; i++)
|
||||
{
|
||||
arr[i] = QLineF(x1, y, x2, y);
|
||||
y += hourOffset;
|
||||
}
|
||||
|
||||
painter->setPen(hourLinePen);
|
||||
painter->drawLines(arr, n);
|
||||
delete[] arr;
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawStationHeader(QPainter *painter, LineGraphScene *scene,
|
||||
const QRectF &rect)
|
||||
{
|
||||
QFont stationFont;
|
||||
stationFont.setBold(true);
|
||||
setFontPointSizeDPI(stationFont, 25, painter);
|
||||
|
||||
QPen stationPen(AppSettings.getStationTextColor());
|
||||
|
||||
QFont platfBoldFont = stationFont;
|
||||
setFontPointSizeDPI(platfBoldFont, 16, painter);
|
||||
|
||||
QFont platfNormalFont = platfBoldFont;
|
||||
platfNormalFont.setBold(false);
|
||||
|
||||
QPen electricPlatfPen(Qt::blue);
|
||||
QPen nonElectricPlatfPen(Qt::black);
|
||||
|
||||
const qreal platformOffset = Session->platformOffset;
|
||||
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;
|
||||
|
||||
const double margin = stationOffset * 0.1;
|
||||
|
||||
QRectF r = rect;
|
||||
|
||||
for (auto st : qAsConst(scene->stations))
|
||||
{
|
||||
const double left = st.xPos + leftOffset;
|
||||
const double right = left + st.platforms.count() * platformOffset + stationOffset;
|
||||
|
||||
if (right < r.left() || left >= r.right())
|
||||
continue; // Skip station, it's not visible
|
||||
|
||||
QRectF labelRect = r;
|
||||
labelRect.setLeft(left + margin);
|
||||
labelRect.setRight(right - margin);
|
||||
labelRect.setBottom(r.bottom() - r.height() / 3);
|
||||
|
||||
painter->setPen(stationPen);
|
||||
painter->setFont(stationFont);
|
||||
painter->drawText(labelRect, Qt::AlignVCenter | Qt::AlignCenter, st.stationName);
|
||||
|
||||
labelRect = r;
|
||||
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;
|
||||
labelRect.setWidth(platformOffset);
|
||||
for (const StationGraphObject::PlatformGraph &platf : qAsConst(st.platforms))
|
||||
{
|
||||
if (platf.platformType.testFlag(utils::StationTrackType::Electrified))
|
||||
painter->setPen(electricPlatfPen);
|
||||
else
|
||||
painter->setPen(nonElectricPlatfPen);
|
||||
|
||||
if (platf.platformType.testFlag(utils::StationTrackType::Through))
|
||||
painter->setFont(platfBoldFont);
|
||||
else
|
||||
painter->setFont(platfNormalFont);
|
||||
|
||||
labelRect.moveLeft(xPos);
|
||||
|
||||
painter->drawText(labelRect, Qt::AlignCenter, platf.platformName);
|
||||
|
||||
xPos += platformOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawStations(QPainter *painter, LineGraphScene *scene, const QRectF &rect)
|
||||
{
|
||||
const QRgb white = qRgb(255, 255, 255);
|
||||
|
||||
// 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 width = AppSettings.getPlatformLineWidth();
|
||||
const QColor mainPlatfColor = AppSettings.getMainPlatfColor();
|
||||
|
||||
QPen platfPen(mainPlatfColor, width);
|
||||
|
||||
QPointF top(0, vertOffset);
|
||||
QPointF bottom(0, lastY);
|
||||
|
||||
for (const StationGraphObject &st : qAsConst(scene->stations))
|
||||
{
|
||||
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
|
||||
|
||||
top.rx() = bottom.rx() = st.xPos;
|
||||
|
||||
for (const StationGraphObject::PlatformGraph &platf : st.platforms)
|
||||
{
|
||||
if (platf.color == white)
|
||||
platfPen.setColor(mainPlatfColor);
|
||||
else
|
||||
platfPen.setColor(platf.color);
|
||||
|
||||
painter->setPen(platfPen);
|
||||
|
||||
painter->drawLine(top, bottom);
|
||||
|
||||
top.rx() += platfOffset;
|
||||
bottom.rx() += platfOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawJobStops(QPainter *painter, LineGraphScene *scene, const QRectF &rect,
|
||||
bool drawSelection)
|
||||
{
|
||||
const double platfOffset = Session->platformOffset;
|
||||
const double stationOffset = Session->stationOffset;
|
||||
|
||||
QFont jobNameFont;
|
||||
setFontPointSizeDPI(jobNameFont, 20, painter);
|
||||
painter->setFont(jobNameFont);
|
||||
|
||||
QPen jobPen;
|
||||
jobPen.setWidth(AppSettings.getJobLineWidth());
|
||||
jobPen.setCapStyle(Qt::RoundCap);
|
||||
jobPen.setJoinStyle(Qt::RoundJoin);
|
||||
|
||||
QPen selectedJobPen;
|
||||
|
||||
const JobStopEntry selectedJob = scene->getSelectedJob();
|
||||
if (drawSelection && selectedJob.jobId)
|
||||
{
|
||||
selectedJobPen.setWidthF(jobPen.widthF() * SelectedJobWidthFactor);
|
||||
selectedJobPen.setCapStyle(Qt::RoundCap);
|
||||
selectedJobPen.setJoinStyle(Qt::RoundJoin);
|
||||
|
||||
QColor color = Session->colorForCat(selectedJob.category);
|
||||
color.setAlpha(SelectedJobAlphaFactor);
|
||||
selectedJobPen.setColor(color);
|
||||
}
|
||||
|
||||
QPointF top;
|
||||
QPointF bottom;
|
||||
|
||||
JobCategory lastJobCategory = JobCategory::NCategories;
|
||||
QTextOption textOption(Qt::AlignTop | Qt::AlignLeft);
|
||||
|
||||
for (const StationGraphObject &st : qAsConst(scene->stations))
|
||||
{
|
||||
const double left = st.xPos;
|
||||
const double right = left + st.platforms.count() * platfOffset;
|
||||
|
||||
// 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::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
|
||||
|
||||
top.setY(jobStop.arrivalY);
|
||||
bottom.setY(jobStop.departureY);
|
||||
|
||||
const bool nullStopDuration = qFuzzyCompare(top.y(), bottom.y());
|
||||
|
||||
if (drawSelection && selectedJob.jobId == jobStop.stop.jobId)
|
||||
{
|
||||
// Draw selection around segment
|
||||
painter->setPen(selectedJobPen);
|
||||
|
||||
if (nullStopDuration)
|
||||
painter->drawPoint(top);
|
||||
else
|
||||
painter->drawLine(top, bottom);
|
||||
|
||||
// Reset pen
|
||||
painter->setPen(jobPen);
|
||||
}
|
||||
|
||||
if (lastJobCategory != jobStop.stop.category)
|
||||
{
|
||||
QColor color = Session->colorForCat(jobStop.stop.category);
|
||||
jobPen.setColor(color);
|
||||
painter->setPen(jobPen);
|
||||
lastJobCategory = jobStop.stop.category;
|
||||
}
|
||||
|
||||
if (nullStopDuration)
|
||||
painter->drawPoint(top);
|
||||
else
|
||||
painter->drawLine(top, bottom);
|
||||
|
||||
if (jobStop.drawLabel)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
top.rx() += platfOffset;
|
||||
bottom.rx() += platfOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BackgroundHelper::drawJobSegments(QPainter *painter, LineGraphScene *scene, const QRectF &rect,
|
||||
bool drawSelection)
|
||||
{
|
||||
const double stationOffset = Session->stationOffset;
|
||||
|
||||
QFont jobNameFont;
|
||||
setFontPointSizeDPI(jobNameFont, 20, painter);
|
||||
painter->setFont(jobNameFont);
|
||||
|
||||
QColor textBackground(Qt::white);
|
||||
textBackground.setAlpha(100);
|
||||
|
||||
QPen jobPen;
|
||||
jobPen.setWidth(AppSettings.getJobLineWidth());
|
||||
jobPen.setCapStyle(Qt::RoundCap);
|
||||
jobPen.setJoinStyle(Qt::RoundJoin);
|
||||
|
||||
QPen selectedJobPen;
|
||||
|
||||
const JobStopEntry selectedJob = scene->getSelectedJob();
|
||||
if (drawSelection && selectedJob.jobId)
|
||||
{
|
||||
selectedJobPen.setWidthF(jobPen.widthF() * SelectedJobWidthFactor);
|
||||
selectedJobPen.setCapStyle(Qt::RoundCap);
|
||||
selectedJobPen.setJoinStyle(Qt::RoundJoin);
|
||||
|
||||
QColor color = Session->colorForCat(selectedJob.category);
|
||||
color.setAlpha(SelectedJobAlphaFactor);
|
||||
selectedJobPen.setColor(color);
|
||||
}
|
||||
|
||||
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++)
|
||||
{
|
||||
const LineGraphScene::StationPosEntry &stPos = scene->stationPositions.at(i);
|
||||
|
||||
const double left = stPos.xPos;
|
||||
double right = 0;
|
||||
|
||||
if (i < scene->stationPositions.size() - 2)
|
||||
{
|
||||
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
|
||||
}
|
||||
|
||||
if (left > rect.right() || right < rect.left())
|
||||
continue; // Skip station, it's not visible
|
||||
|
||||
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
|
||||
|
||||
const QLineF line(job.fromDeparture, job.toArrival);
|
||||
|
||||
if (drawSelection && selectedJob.jobId == job.jobId)
|
||||
{
|
||||
// Draw selection around segment
|
||||
painter->setPen(selectedJobPen);
|
||||
painter->drawLine(line);
|
||||
|
||||
// Reset pen
|
||||
painter->setPen(jobPen);
|
||||
}
|
||||
|
||||
if (lastJobCategory != job.category)
|
||||
{
|
||||
QColor color = Session->colorForCat(job.category);
|
||||
jobPen.setColor(color);
|
||||
painter->setPen(jobPen);
|
||||
lastJobCategory = job.category;
|
||||
}
|
||||
|
||||
painter->drawLine(line);
|
||||
|
||||
const QString jobName = JobCategoryName::jobName(job.jobId, job.category);
|
||||
|
||||
// Save old transformation to reset it after drawing text
|
||||
const QTransform oldTransf = painter->transform();
|
||||
|
||||
// Move to line center, it will be rotation pivot
|
||||
painter->translate(line.center());
|
||||
|
||||
// Rotate by line angle
|
||||
qreal angle = line.angle();
|
||||
if (job.fromDeparture.x() > job.toArrival.x())
|
||||
angle += 180.0; // Prevent flipping text
|
||||
|
||||
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())
|
||||
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
|
||||
painter->fillRect(textRect, textBackground);
|
||||
painter->drawText(textRect, jobName, textOption);
|
||||
|
||||
// Reset to old transformation
|
||||
painter->setTransform(oldTransf);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef BACKGROUNDHELPER_H
|
||||
#define BACKGROUNDHELPER_H
|
||||
|
||||
#include <QRectF>
|
||||
|
||||
class QPainter;
|
||||
|
||||
class LineGraphScene;
|
||||
|
||||
/*!
|
||||
* \brief Helper class to render LineGraphScene contents
|
||||
*
|
||||
* Contains static helper functions to draw each part of the view
|
||||
*
|
||||
* \sa LineGraphScene
|
||||
*/
|
||||
class BackgroundHelper
|
||||
{
|
||||
public:
|
||||
static void drawHourPanel(QPainter *painter, const QRectF &rect);
|
||||
|
||||
static void drawBackgroundHourLines(QPainter *painter, const QRectF &rect);
|
||||
|
||||
static void drawStationHeader(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 drawJobSegments(QPainter *painter, LineGraphScene *scene, const QRectF &rect,
|
||||
bool drawSelection);
|
||||
|
||||
public:
|
||||
static constexpr double SelectedJobWidthFactor = 3.0;
|
||||
static constexpr int SelectedJobAlphaFactor = 127;
|
||||
};
|
||||
|
||||
#endif // BACKGROUNDHELPER_H
|
|
@ -0,0 +1,182 @@
|
|||
/*
|
||||
* 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>
|
||||
#include <QPushButton>
|
||||
|
||||
#include "utils/delegates/sql/customcompletionlineedit.h"
|
||||
|
||||
#include "stations/match_models/stationsmatchmodel.h"
|
||||
#include "stations/match_models/railwaysegmentmatchmodel.h"
|
||||
#include "stations/match_models/linesmatchmodel.h"
|
||||
|
||||
#include "app/session.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
||||
LineGraphSelectionWidget::LineGraphSelectionWidget(QWidget *parent) :
|
||||
QWidget(parent),
|
||||
matchModel(nullptr),
|
||||
m_objectId(0),
|
||||
m_graphType(LineGraphType::NoGraph)
|
||||
{
|
||||
QHBoxLayout *lay = new QHBoxLayout(this);
|
||||
lay->setContentsMargins(0, 0, 0, 0);
|
||||
|
||||
graphTypeCombo = new QComboBox;
|
||||
lay->addWidget(graphTypeCombo);
|
||||
|
||||
objectCombo = new CustomCompletionLineEdit(nullptr, this);
|
||||
lay->addWidget(objectCombo);
|
||||
|
||||
QStringList items;
|
||||
items.reserve(int(LineGraphType::NTypes));
|
||||
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);
|
||||
|
||||
setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Minimum);
|
||||
}
|
||||
|
||||
LineGraphSelectionWidget::~LineGraphSelectionWidget()
|
||||
{
|
||||
if (matchModel)
|
||||
{
|
||||
objectCombo->setModel(nullptr);
|
||||
delete matchModel;
|
||||
matchModel = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
LineGraphType LineGraphSelectionWidget::getGraphType() const
|
||||
{
|
||||
return LineGraphType(graphTypeCombo->currentIndex());
|
||||
}
|
||||
|
||||
void LineGraphSelectionWidget::setGraphType(LineGraphType type)
|
||||
{
|
||||
if (getGraphType() == type)
|
||||
return;
|
||||
|
||||
graphTypeCombo->setCurrentIndex(int(type));
|
||||
setupModel(type);
|
||||
|
||||
emit graphChanged(int(m_graphType), m_objectId);
|
||||
}
|
||||
|
||||
db_id LineGraphSelectionWidget::getObjectId() const
|
||||
{
|
||||
return m_objectId;
|
||||
}
|
||||
|
||||
const QString &LineGraphSelectionWidget::getObjectName() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
void LineGraphSelectionWidget::setObjectId(db_id objectId, const QString &name)
|
||||
{
|
||||
if (m_graphType == LineGraphType::NoGraph)
|
||||
return; // Object ID must be null
|
||||
|
||||
m_name = name;
|
||||
if (!objectId)
|
||||
m_name.clear();
|
||||
|
||||
objectCombo->setData(objectId, name);
|
||||
|
||||
if (m_objectId != objectId)
|
||||
emit graphChanged(int(m_graphType), m_objectId);
|
||||
}
|
||||
|
||||
void LineGraphSelectionWidget::onTypeComboActivated(int index)
|
||||
{
|
||||
setupModel(LineGraphType(index));
|
||||
emit graphChanged(int(m_graphType), m_objectId);
|
||||
}
|
||||
|
||||
void LineGraphSelectionWidget::onCompletionDone()
|
||||
{
|
||||
if (!objectCombo->getData(m_objectId, m_name))
|
||||
return;
|
||||
|
||||
emit graphChanged(int(m_graphType), m_objectId);
|
||||
}
|
||||
|
||||
void LineGraphSelectionWidget::setupModel(LineGraphType type)
|
||||
{
|
||||
if (type != m_graphType)
|
||||
{
|
||||
// Clear old model
|
||||
if (matchModel)
|
||||
{
|
||||
objectCombo->setModel(nullptr);
|
||||
delete matchModel;
|
||||
matchModel = nullptr;
|
||||
}
|
||||
|
||||
// Manually clear line edit
|
||||
m_objectId = 0;
|
||||
m_name.clear();
|
||||
objectCombo->setData(m_objectId, m_name);
|
||||
|
||||
switch (LineGraphType(type))
|
||||
{
|
||||
case LineGraphType::NoGraph:
|
||||
default:
|
||||
{
|
||||
// Prevent recursion on loadGraph() calling back to us
|
||||
type = LineGraphType::NoGraph;
|
||||
break;
|
||||
}
|
||||
case LineGraphType::SingleStation:
|
||||
{
|
||||
StationsMatchModel *m = new StationsMatchModel(Session->m_Db, this);
|
||||
m->setFilter(0);
|
||||
matchModel = m;
|
||||
break;
|
||||
}
|
||||
case LineGraphType::RailwaySegment:
|
||||
{
|
||||
RailwaySegmentMatchModel *m = new RailwaySegmentMatchModel(Session->m_Db, this);
|
||||
m->setFilter(0, 0, 0);
|
||||
matchModel = m;
|
||||
break;
|
||||
}
|
||||
case LineGraphType::RailwayLine:
|
||||
{
|
||||
LinesMatchModel *m = new LinesMatchModel(Session->m_Db, true, this);
|
||||
matchModel = m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (matchModel)
|
||||
objectCombo->setModel(matchModel);
|
||||
}
|
||||
m_graphType = type;
|
||||
}
|