Compare commits
354
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d130c55be5 | ||
|
|
0e2100f383 | ||
|
|
26f702b7a4 | ||
|
|
37bd700564 | ||
|
|
684e05109d | ||
|
|
676ae05335 | ||
|
|
a17b431669 | ||
|
|
493893d2e6 | ||
|
|
8976b5aedb | ||
|
|
6e7b09cf6f | ||
|
|
11501ff7bb | ||
|
|
59f0c025e5 | ||
|
|
cebc456bc1 | ||
|
|
2d1b12b5d8 | ||
|
|
bf3fecf5c0 | ||
|
|
5c012ba228 | ||
|
|
5644914fe3 | ||
|
|
b662e4daf9 | ||
|
|
1dd1e86355 | ||
|
|
52df1c8ae9 | ||
|
|
23e694afc2 | ||
|
|
217bb54bc0 | ||
|
|
ba99b74608 | ||
|
|
862d0dbe2b | ||
|
|
5554704174 | ||
|
|
14dde18e8a | ||
|
|
65ca0d8b5e | ||
|
|
23cf641d36 | ||
|
|
a32481de09 | ||
|
|
a3ff160086 | ||
|
|
42f6e83c2c | ||
|
|
855b0a6101 | ||
|
|
eb4d067365 | ||
|
|
8b3306732d | ||
|
|
5a96bd5d58 | ||
|
|
e4743d631d | ||
|
|
1a38b88229 | ||
|
|
444ac417bf | ||
|
|
72a30c1e30 | ||
|
|
0e22c5b029 | ||
|
|
167a826cbc | ||
|
|
c9411c04e1 | ||
|
|
1b29039a28 | ||
|
|
b54fd0caa5 | ||
|
|
3e15fb9281 | ||
|
|
5d2b5761a4 | ||
|
|
ac4f2e1aaa | ||
|
|
e7a062e531 | ||
|
|
3785f2c036 | ||
|
|
1589d8f17d | ||
|
|
a7a5782fc8 | ||
|
|
bb58062c6f | ||
|
|
6d3a06f90a | ||
|
|
62a017cf27 | ||
|
|
78ff6f7df9 | ||
|
|
4ea6ed119c | ||
|
|
4a3fb0bfe3 | ||
|
|
0d67b3a8c9 | ||
|
|
04899a7160 | ||
|
|
b877bc4af5 | ||
|
|
0e15524181 | ||
|
|
6177fd00fb | ||
|
|
6cc19e138d | ||
|
|
75f7f52230 | ||
|
|
b915bacc62 | ||
|
|
9992af628a | ||
|
|
1c77f995e2 | ||
|
|
9caa2573ca | ||
|
|
e771c1252f | ||
|
|
980dd26106 | ||
|
|
143b594a59 | ||
|
|
6c8e5d663a | ||
|
|
34b9cd10f3 | ||
|
|
cf358fe527 | ||
|
|
88902aa572 | ||
|
|
d7f58c558d | ||
|
|
87fe045bbe | ||
|
|
220529c74a | ||
|
|
6b8f2efabb | ||
|
|
95f7d78569 | ||
|
|
c7d1c250dd | ||
|
|
8436f113e3 | ||
|
|
f345ebc16f | ||
|
|
ce154f6a1d | ||
|
|
78253c60f4 | ||
|
|
1e07bf76ba | ||
|
|
985aa9787b | ||
|
|
ed81fccb1d | ||
|
|
43300efaa7 | ||
|
|
3f7beb3c7e | ||
|
|
7b090a26ca | ||
|
|
040bdd558f | ||
|
|
1bcc7e4855 | ||
|
|
ab1cf60643 | ||
|
|
3f8a7eccfd | ||
|
|
d8ec9e8d74 | ||
|
|
8345470273 | ||
|
|
593c221d23 | ||
|
|
b2e787a70b | ||
|
|
9c1ac7cbef | ||
|
|
36f077bce4 | ||
|
|
a410019553 | ||
|
|
b18844a202 | ||
|
|
4d6bef5fa2 | ||
|
|
97a9992ce1 | ||
|
|
168d03866e | ||
|
|
2b8f3f02c7 | ||
|
|
1e1a3e6f30 | ||
|
|
db8adbfe3b | ||
|
|
c664611b80 | ||
|
|
e748e82c84 | ||
|
|
4223ca2ff5 | ||
|
|
5381c65fee | ||
|
|
b6a97c39e5 | ||
|
|
9f0076a197 | ||
|
|
fd3e5e8380 | ||
|
|
881d44f4dd | ||
|
|
ed9fc51e7a | ||
|
|
cdb8c754e9 | ||
|
|
0997dcba79 | ||
|
|
f6292cc244 | ||
|
|
ef20ac3d95 | ||
|
|
ab29b7b2d5 | ||
|
|
7cd3e43959 | ||
|
|
4bf53db7fa | ||
|
|
5485a90385 | ||
|
|
29002f2a48 | ||
|
|
c3eda18234 | ||
|
|
6a8c31b735 | ||
|
|
3ec4208139 | ||
|
|
95071993f5 | ||
|
|
bdb2461737 | ||
|
|
a09979afdd | ||
|
|
2df80f90a7 | ||
|
|
40f767fc4d | ||
|
|
82356f23e5 | ||
|
|
472f537313 | ||
|
|
e8d07af320 | ||
|
|
cf62b17d58 | ||
|
|
220ca64679 | ||
|
|
a1914cb7d9 | ||
|
|
4e29022c06 | ||
|
|
501bd2b4b8 | ||
|
|
ce47b9236f | ||
|
|
b8e2ed5344 | ||
|
|
58a383d212 | ||
|
|
9e553e5d7a | ||
|
|
9a9a023b70 | ||
|
|
7d1074588c | ||
|
|
a3e44a5bd8 | ||
|
|
bd3cccbc15 | ||
|
|
b96dec41e4 | ||
|
|
3c058facd7 | ||
|
|
e156fbb5e4 | ||
|
|
d79d89c352 | ||
|
|
4ee52e2f52 | ||
|
|
c32c9031e7 | ||
|
|
9d1419a5b1 | ||
|
|
5d6807f256 | ||
|
|
619c7683d4 | ||
|
|
a318dbb64a | ||
|
|
7f9a59004a | ||
|
|
4cab91a948 | ||
|
|
6794412c29 | ||
|
|
f974625b0f | ||
|
|
15a961182a | ||
|
|
35bfe2be39 | ||
|
|
851cbd5cec | ||
|
|
c066c74d7f | ||
|
|
9e38583785 | ||
|
|
882f783bcb | ||
|
|
4de31193b6 | ||
|
|
82da99b41c | ||
|
|
f42bd0276c | ||
|
|
8e2acb6707 | ||
|
|
86f331341a | ||
|
|
778866b7cd | ||
|
|
80d73fc330 | ||
|
|
a03fa59e35 | ||
|
|
d911e95d05 | ||
|
|
7c2196bd6d | ||
|
|
5865239b07 | ||
|
|
a5e5560478 | ||
|
|
dba7759292 | ||
|
|
691bf21f3e | ||
|
|
9db7dd604c | ||
|
|
03c698d1eb | ||
|
|
7173785915 | ||
|
|
f23384bb68 | ||
|
|
c29ffee9d4 | ||
|
|
1fa7897671 | ||
|
|
60f7ea0d9a | ||
|
|
44fdf2ac91 | ||
|
|
92ad7d4686 | ||
|
|
be912de9d6 | ||
|
|
eb02c1f001 | ||
|
|
1cb7a2ea22 | ||
|
|
f217207675 | ||
|
|
1157e12e0f | ||
|
|
290d9c1f5d | ||
|
|
6aa040abb3 | ||
|
|
b21bdbf410 | ||
|
|
969e273904 | ||
|
|
710f7ad475 | ||
|
|
50c4a6fbc0 | ||
|
|
524f076c2a | ||
|
|
2ed0a944d1 | ||
|
|
db81f2de26 | ||
|
|
2a0192ce1d | ||
|
|
3aa0b04ebd | ||
|
|
67491249c2 | ||
|
|
8d1897d297 | ||
|
|
18ef659723 | ||
|
|
8cec9973e7 | ||
|
|
ba1d9e2c04 | ||
|
|
758ae0f53f | ||
|
|
283e89e3c9 | ||
|
|
6513a267e2 | ||
|
|
b656e1aaa9 | ||
|
|
e398f4903d | ||
|
|
afda2a82a8 | ||
|
|
c5c54cb268 | ||
|
|
1445fc659d | ||
|
|
c270b7ebe7 | ||
|
|
f528f9b609 | ||
|
|
8a26ac0a5d | ||
|
|
4e548318b5 | ||
|
|
0c1503d85c | ||
|
|
ad07345480 | ||
|
|
497baa804b | ||
|
|
a9c5b2181e | ||
|
|
851d47e114 | ||
|
|
eafbbbbbe9 | ||
|
|
115243d47c | ||
|
|
f74ff251a2 | ||
|
|
e5d8bfa432 | ||
|
|
c21083700a | ||
|
|
bb1447c858 | ||
|
|
8050bca545 | ||
|
|
1edc9934fa | ||
|
|
d5af8b9764 | ||
|
|
c143ff8a4f | ||
|
|
ec83f017be | ||
|
|
71636a42c4 | ||
|
|
d5c13033e2 | ||
|
|
01dce5fe52 | ||
|
|
45eb8ee689 | ||
|
|
079175a055 | ||
|
|
0ca0e9ac60 | ||
|
|
060792032b | ||
|
|
bf0b1a3ddd | ||
|
|
f15f342a47 | ||
|
|
793c9f8b81 | ||
|
|
4969e4ff9b | ||
|
|
f6c2ac04f9 | ||
|
|
92b7f26fc8 | ||
|
|
47985c5a88 | ||
|
|
d7c8c9e6bd | ||
|
|
7a6be7474e | ||
|
|
3d3c3b48fe | ||
|
|
8d456c7d1a | ||
|
|
ec75789fae | ||
|
|
c042faae9e | ||
|
|
f6cbb9021f | ||
|
|
d757420cb0 | ||
|
|
93020295ad | ||
|
|
3cc3129de0 | ||
|
|
6588fc7d3f | ||
|
|
950d669538 | ||
|
|
c599be63e3 | ||
|
|
22a3a8d5b1 | ||
|
|
418443c841 | ||
|
|
ffa25fb394 | ||
|
|
3bd40639a3 | ||
|
|
016371c076 | ||
|
|
d890b11c5f | ||
|
|
27fe03a03a | ||
|
|
85bae2e60a | ||
|
|
7a80c64b04 | ||
|
|
d17d397fe7 | ||
|
|
bd10a8db89 | ||
|
|
c7fa97c8bf | ||
|
|
e22dd98040 | ||
|
|
05095c6813 | ||
|
|
b9a807c92a | ||
|
|
5e0a20094e | ||
|
|
b1ff110439 | ||
|
|
8c824f87b4 | ||
|
|
32041db9a7 | ||
|
|
b0a281ddf4 | ||
|
|
fdf2bba69d | ||
|
|
9c1463546e | ||
|
|
734a5e5315 | ||
|
|
cac1dd809e | ||
|
|
72cbe318b6 | ||
|
|
19ab996be2 | ||
|
|
13c341c7c7 | ||
|
|
1632478122 | ||
|
|
a1185399d4 | ||
|
|
0fcfac2f26 | ||
|
|
4f09d01afb | ||
|
|
2a127de21f | ||
|
|
52d82529f4 | ||
|
|
6111fc86ab | ||
|
|
c8dc6f387f | ||
|
|
ec2f8f1691 | ||
|
|
81a85ad3dc | ||
|
|
f58ba53a1e | ||
|
|
38239bcebc | ||
|
|
4de8d75015 | ||
|
|
6e06e6793b | ||
|
|
f66c6a1bc0 | ||
|
|
ca9d89c2ff | ||
|
|
6c668cfbfa | ||
|
|
f48e645973 | ||
|
|
c3f3920380 | ||
|
|
5d1768c51c | ||
|
|
0a288a3bff | ||
|
|
00a37f1fd6 | ||
|
|
ccc31f8fff | ||
|
|
29fa8876fb | ||
|
|
989e5e32c8 | ||
|
|
135810bbb9 | ||
|
|
98d671a96d | ||
|
|
0740625839 | ||
|
|
ed28c9a58b | ||
|
|
c430b46289 | ||
|
|
a281671919 | ||
|
|
e66a627d8d | ||
|
|
8d3a033aa1 | ||
|
|
013511ab1d | ||
|
|
d7effabf17 | ||
|
|
e8d501773b | ||
|
|
d7f6ff1f9e | ||
|
|
5b429564dd | ||
|
|
022dcba077 | ||
|
|
d6b6ffce7c | ||
|
|
1881d68aef | ||
|
|
e6a295bb5b | ||
|
|
bafed3705f | ||
|
|
3bb6e89496 | ||
|
|
8453e104c5 | ||
|
|
0c1c1ceb85 | ||
|
|
4c9861a469 | ||
|
|
b774f4035c | ||
|
|
010f91928a | ||
|
|
4a143f09b3 | ||
|
|
f14faa04e5 | ||
|
|
cf6d77c030 | ||
|
|
514ded2739 | ||
|
|
6a2a0667ab | ||
|
|
ba4121728b | ||
|
|
e3e87f886e | ||
|
|
d176b5e0d7 |
No files matched your search
@@ -1,28 +0,0 @@
|
||||
BasedOnStyle: LLVM
|
||||
IndentWidth: 4
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
ColumnLimit: 120
|
||||
Language: Cpp
|
||||
Standard: c++20
|
||||
PointerAlignment: Left
|
||||
ReferenceAlignment: Left
|
||||
SortIncludes: true
|
||||
IncludeBlocks: Regroup
|
||||
BreakBeforeBraces: Custom
|
||||
BraceWrapping:
|
||||
AfterClass: true
|
||||
AfterControlStatement: true
|
||||
AfterFunction: true
|
||||
AfterNamespace: true
|
||||
AfterStruct: true
|
||||
BeforeCatch: true
|
||||
BeforeElse: true
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Empty
|
||||
NamespaceIndentation: None
|
||||
IndentCaseLabels: true
|
||||
AlignTrailingComments: true
|
||||
SeparateDefinitionBlocks: Always
|
||||
SpaceBeforeParens: ControlStatements
|
||||
Cpp11BracedListStyle: true
|
||||
@@ -1,5 +0,0 @@
|
||||
tmp/
|
||||
settings.local.json
|
||||
metadata/
|
||||
logs/
|
||||
redactors/local/
|
||||
@@ -1,9 +0,0 @@
|
||||
{
|
||||
"enabled": true,
|
||||
"telemetry": true,
|
||||
"checkpoints": {
|
||||
"primary": {
|
||||
"type": "git-branch"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
export STRIX_LLM="qwen3.5-397b-a17b"
|
||||
export SCW_SECRET_KEY="REPLACE_WITH_YOURS"
|
||||
|
||||
export COPILOT_PROVIDER_TYPE="openai"
|
||||
export COPILOT_PROVIDER_API_KEY="${SCW_SECRET_KEY}"
|
||||
export COPILOT_PROVIDER_BASE_URL="https://api.scaleway.ai/v1"
|
||||
export COPILOT_MODEL="${STRIX_LLM}"
|
||||
|
||||
export COPILOT_PROVIDER_MAX_PROMPT_TOKENS="32768"
|
||||
export COPILOT_PROVIDER_MAX_OUTPUT_TOKENS="8192"
|
||||
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
export ENTIRE_TOKEN_STORE_PATH=$HOME/.config/entire/tokens.json
|
||||
export ACCESSIBLE=1
|
||||
@@ -1,56 +0,0 @@
|
||||
# GitHub Copilot Repository Instructions: C++23 Modernization
|
||||
|
||||
## Context
|
||||
- Language: C++
|
||||
- Target standard: C++23
|
||||
- Goal: modernize legacy C++11/14/17 code safely and incrementally
|
||||
|
||||
## Primary Rule
|
||||
Preserve existing behavior unless functional changes are explicitly requested.
|
||||
|
||||
## Balanced Modernization Policy
|
||||
- Prefer modern C++23 constructs when they clearly improve safety, readability, or maintainability.
|
||||
- Keep refactors focused and reviewable; avoid broad rewrites unless requested.
|
||||
- Prefer standard library solutions over custom or legacy patterns.
|
||||
- Respect toolchain limitations, ABI-sensitive interfaces, and repository-local style.
|
||||
|
||||
## Preferred Modernizations
|
||||
- Use `auto` where the type is obvious from the initializer; avoid it when it hides important type information.
|
||||
- Prefer structured bindings over `std::tie` or manual pair/tuple access when clearer.
|
||||
- Replace raw owning pointers and manual `delete` with RAII, usually `std::unique_ptr`.
|
||||
- Use `std::make_unique` and `std::make_shared`; prefer `std::shared_ptr` only for real shared ownership.
|
||||
- Use `std::string_view` for read-only string parameters only when lifetime safety is clear.
|
||||
- Prefer `std::span` for non-owning contiguous ranges.
|
||||
- Use `std::mdspan` only when multidimensional access is central and the change improves clarity or safety.
|
||||
- Prefer `.contains()` over `find(...) != end()` where available and clearer.
|
||||
- Prefer standard algorithms and ranges over manual loops when readability improves.
|
||||
- Use `[[nodiscard]]` where ignoring the result is likely a bug.
|
||||
- Prefer early returns over deeply nested control flow.
|
||||
- Use `std::optional` monadic operations (`and_then`, `transform`, `or_else`) when they make the code simpler.
|
||||
- Prefer `constexpr`, `if constexpr`, concepts, and `requires` over legacy `std::enable_if` / SFINAE where practical.
|
||||
- Add `noexcept`, `override`, `final`, `= default`, and `= delete` where correct and useful.
|
||||
- Prefer `enum class` over unscoped enums where practical.
|
||||
- Prefer `std::print` / `std::println` for console output when supported by the project toolchain.
|
||||
- Prefer typed `std::chrono` durations and `std::jthread` where appropriate.
|
||||
|
||||
## Avoid
|
||||
- Do not modernize mechanically if readability becomes worse.
|
||||
- Do not introduce unsupported C++23 features.
|
||||
- Do not introduce dangling references, dangling `std::string_view`, or unclear ownership.
|
||||
- Do not silently change ABI, object lifetime, exception behavior, or performance characteristics.
|
||||
- Do not rewrite unrelated code.
|
||||
|
||||
## Expected Copilot Behavior
|
||||
When making migration suggestions:
|
||||
- produce small, focused updates
|
||||
- explain non-trivial changes briefly
|
||||
- call out risky refactors explicitly
|
||||
- note compatibility or toolchain caveats
|
||||
- leave optional follow-up improvements separate
|
||||
|
||||
## Preferred Output
|
||||
Provide:
|
||||
- updated code
|
||||
- short rationale
|
||||
- compatibility/toolchain caveats
|
||||
- optional next modernization steps
|
||||
@@ -1,61 +0,0 @@
|
||||
{
|
||||
"hooks": {
|
||||
"agentStop": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli agent-stop'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"errorOccurred": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli error-occurred'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"postToolUse": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli post-tool-use'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"preToolUse": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli pre-tool-use'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"sessionEnd": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli session-end'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"sessionStart": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli session-start'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"subagentStop": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli subagent-stop'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
],
|
||||
"userPromptSubmitted": [
|
||||
{
|
||||
"type": "command",
|
||||
"bash": "sh -c 'if ! command -v entire >/dev/null 2>&1; then exit 0; fi; exec entire hooks copilot-cli user-prompt-submitted'",
|
||||
"comment": "Entire CLI"
|
||||
}
|
||||
]
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -1,157 +0,0 @@
|
||||
# GitHub Copilot Instructions: Qt5/C++11 to Qt6/C++23 Migration
|
||||
|
||||
## Mission
|
||||
Migrate the legacy Qt5/C++11 desktop application to Qt6/C++23, preserve behavior unless change is strictly required, and validate the result completely in the repository’s Docker-based CI workflow.
|
||||
|
||||
## Operating Mode
|
||||
- Work autonomously from analysis through validation.
|
||||
- Do not stop after editing code.
|
||||
- Continue until configure, build, tests, and policy checks have been attempted in Docker.
|
||||
- Do not ask for confirmation during normal implementation.
|
||||
- Stop early only if required source material is missing, the task is impossible, or a destructive action would be required.
|
||||
|
||||
## Boundaries
|
||||
- Read the legacy project from `${PROJECT_DIR}`.
|
||||
- Write the migrated project to `qt6-after/qt6-migrated-benchmark`.
|
||||
- Do not modify the legacy source tree in place.
|
||||
- Limit changes to the target tree and only the CI/build/test files needed to support migration and validation.
|
||||
|
||||
## Main Migration Targets
|
||||
- Upgrade Qt5 to Qt6.
|
||||
- Upgrade C++11/14/17 code to C++23.
|
||||
- Keep Qt Core, Gui, and Widgets explicitly linked.
|
||||
- Replace `QRegExp` with `QRegularExpression`.
|
||||
- Replace `QDesktopWidget` and `QApplication::desktop()` with `QScreen`-based Qt6 APIs.
|
||||
- Remove legacy `QTextCodec` usage and adopt Qt6-safe text handling.
|
||||
- Replace old `SIGNAL`/`SLOT` syntax with function-pointer connect syntax.
|
||||
- Replace `0`/`NULL` pointer literals with `nullptr`.
|
||||
- Preserve `.ui` and `.qrc` integration.
|
||||
- Preserve behavior unless Qt6 compatibility requires adaptation.
|
||||
|
||||
## C++23 Modernization Rules
|
||||
- Preserve semantics first; modernize second.
|
||||
- Use `auto` when the type is obvious; avoid it when it hides important type information.
|
||||
- Prefer structured bindings where clearer.
|
||||
- Replace raw owning pointers/manual `delete` with RAII, usually `std::unique_ptr`.
|
||||
- Use `std::make_unique` / `std::make_shared`; use `std::shared_ptr` only for real shared ownership.
|
||||
- Use `std::string_view` only when lifetime safety is clear.
|
||||
- Prefer `std::span` for non-owning contiguous data; use `std::mdspan` only when multidimensional access clearly benefits.
|
||||
- Prefer `.contains()` over `find(...) != end()` where available.
|
||||
- Prefer algorithms and ranges over manual loops when readability improves.
|
||||
- Use `[[nodiscard]]` when ignoring a result is likely a bug.
|
||||
- Prefer early returns over deeply nested control flow.
|
||||
- Use `std::optional` monadic operations (`and_then`, `transform`, `or_else`) when clearer.
|
||||
- Prefer `constexpr`, `if constexpr`, concepts, and `requires` over legacy `std::enable_if`/SFINAE where practical.
|
||||
- Add `noexcept`, `override`, `final`, `= default`, and `= delete` where correct and useful.
|
||||
- Prefer `enum class` over unscoped enums where practical.
|
||||
- Prefer `std::print` / `std::println` when supported by the toolchain.
|
||||
- Prefer typed `std::chrono` durations and `std::jthread` where appropriate.
|
||||
|
||||
## Safety Rules
|
||||
- Do not change business logic unless explicitly requested or required for Qt6 compatibility.
|
||||
- Do not introduce unsupported Qt6/C++23 features.
|
||||
- Do not silently change ABI, ownership, lifetime, exception behavior, performance, UI behavior, or event ordering.
|
||||
- Do not worsen readability for the sake of modernization.
|
||||
- Avoid dangling references, dangling `std::string_view`, and ownership ambiguity.
|
||||
- Keep includes minimal and preserve repository-local formatting/style.
|
||||
|
||||
## Tests
|
||||
- For every code change inside a method, add at least three tests covering the changed behavior.
|
||||
- Place generated tests in `qt6-after/qt6-migrated-benchmark/tests`.
|
||||
- Use policy-compatible test filenames such as:
|
||||
- `test_<name>.cpp`
|
||||
- `<name>_test.cpp`
|
||||
- `<name>test.cpp`
|
||||
- `<name>gtest.cpp`
|
||||
- Update CMake so the migrated app and all tests build and run with CTest.
|
||||
- Ensure all tests are registered with CTest.
|
||||
|
||||
## Docker-First Validation
|
||||
Use `lifemaps/ci-cpp:latest` as the authoritative environment. Do not substitute host tools for missing container tools. Treat `ci/Dockerfile.ci` as the source of truth for CI dependencies.
|
||||
|
||||
### Required validation sequence
|
||||
1. Configure in Docker
|
||||
2. Build in Docker
|
||||
3. Run tests in Docker
|
||||
4. If tests pass, run `python3 ci/check_pr_policies.py` in Docker
|
||||
|
||||
### Recommended commands
|
||||
- Configure:
|
||||
`cmake -S qt6-after/qt6-migrated-benchmark -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug`
|
||||
- Build:
|
||||
`cmake --build build --parallel`
|
||||
- Test:
|
||||
`ctest --test-dir build --output-on-failure`
|
||||
- Policy:
|
||||
`python3 ci/check_pr_policies.py`
|
||||
|
||||
Adapt paths/flags only if repository inspection proves they must differ.
|
||||
|
||||
## Missing-Tool Remediation
|
||||
If Docker validation fails because a required tool/package is missing:
|
||||
- identify the exact missing dependency
|
||||
- update `Dockerfile.ci` minimally
|
||||
- rebuild `lifemaps/ci-cpp:latest`
|
||||
- rerun the full validation sequence
|
||||
|
||||
Continue until:
|
||||
- validation succeeds, or
|
||||
- the next blocker is a genuine project/code issue
|
||||
|
||||
## Failure Classification
|
||||
Environment failures:
|
||||
- command not found
|
||||
- missing compiler/toolchain utility
|
||||
- missing Qt dev package
|
||||
- missing CMake/Ninja/CTest support tool
|
||||
- missing dependency required by `ci/check_pr_policies.py`
|
||||
|
||||
Project failures:
|
||||
- compile errors
|
||||
- linker errors
|
||||
- failing tests
|
||||
- formatting/policy failures caused by migration changes
|
||||
|
||||
## Policy Awareness
|
||||
- `ci/check_pr_policies.py` checks formatting for changed C/C++ files, expects related tests, and verifies commit-signature conditions in its diff range.
|
||||
- Ensure Docker validation runs with mounted workspace and git metadata available.
|
||||
- Do not claim success unless the policy script passes.
|
||||
|
||||
## Execution Plan
|
||||
1. Inspect `${PROJECT_DIR}` and the target tree.
|
||||
2. Identify Qt5 and legacy C++ constructs to migrate.
|
||||
3. Migrate incrementally in `qt6-after/qt6-migrated-benchmark`.
|
||||
4. Add/update tests with each behavior-relevant change.
|
||||
5. Update CMake/test registration.
|
||||
6. Validate in Docker.
|
||||
7. If missing dependencies are found, update `Dockerfile.ci`, rebuild, and rerun.
|
||||
8. Repeat until success or a real project defect remains.
|
||||
|
||||
## Reporting
|
||||
Group change explanations by:
|
||||
- CMake / build-system migration
|
||||
- Qt API replacements
|
||||
- C++ modernization
|
||||
- UI/resource preservation
|
||||
- tests added
|
||||
- `Dockerfile.ci` / container updates
|
||||
|
||||
Also provide a concise changed-files summary.
|
||||
|
||||
## Completion Criteria
|
||||
Success requires:
|
||||
- migrated project exists under `qt6-after/qt6-migrated-benchmark`
|
||||
- configure succeeds in Docker
|
||||
- build succeeds in Docker
|
||||
- tests succeed in Docker
|
||||
- `python3 ci/check_pr_policies.py` succeeds in Docker after tests pass
|
||||
|
||||
## Final Response
|
||||
- State whether migration is fully validated or which blocker remains.
|
||||
- List the exact Docker commands used for image rebuild, configure, build, test, and policy check.
|
||||
- For every failed attempt, include:
|
||||
- exact command
|
||||
- key error
|
||||
- environment vs project classification
|
||||
- fix applied
|
||||
- End with a short summary of any remaining blocker.
|
||||
+10
-3
@@ -1,4 +1,11 @@
|
||||
.envrc
|
||||
.*~
|
||||
*~
|
||||
build-rti-full
|
||||
build-rti-off
|
||||
build-rti-off-notests
|
||||
build-*
|
||||
build_*
|
||||
CMakeFiles
|
||||
fix_qt6_gui_deps2.py
|
||||
fix_qt6_gui_deps.py
|
||||
fix_rti_conditionals.py
|
||||
fix_target_checks.py
|
||||
build
|
||||
Vendored
-90
@@ -1,90 +0,0 @@
|
||||
pipeline {
|
||||
agent {
|
||||
docker {
|
||||
image 'lifemaps/ci-cpp:latest'
|
||||
reuseNode true
|
||||
args '-u root:root'
|
||||
}
|
||||
}
|
||||
|
||||
options {
|
||||
timestamps()
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
environment {
|
||||
PROJECT_DIR = "qt6-after/qt6-migrated-benchmark"
|
||||
BUILD_DIR = "build"
|
||||
CHANGE_TARGET = "${env.CHANGE_TARGET ?: 'origin/main'}"
|
||||
QT_QPA_PLATFORM = "offscreen"
|
||||
LANG = "C.UTF-8"
|
||||
LC_ALL = "C.UTF-8"
|
||||
}
|
||||
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
steps {
|
||||
checkout scm
|
||||
sh 'git fetch --all --prune'
|
||||
sh 'git submodule update --init --recursive || true'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Tool Versions') {
|
||||
steps {
|
||||
sh '''
|
||||
python3 --version
|
||||
cmake --version
|
||||
ninja --version
|
||||
g++ --version
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Qt Diagnostics') {
|
||||
steps {
|
||||
sh '''
|
||||
echo "QT_QPA_PLATFORM=$QT_QPA_PLATFORM"
|
||||
echo "LANG=$LANG"
|
||||
echo "LC_ALL=$LC_ALL"
|
||||
dpkg -l | grep -E "qt6-base-dev|qtbase5-dev|qt6-tools" || true
|
||||
pkg-config --list-all | grep -i qt || true
|
||||
'''
|
||||
}
|
||||
}
|
||||
|
||||
stage('Policy Checks') {
|
||||
steps {
|
||||
sh 'python3 ci/check_pr_policies.py'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Configure') {
|
||||
steps {
|
||||
sh 'cmake -S ${PROJECT_DIR} -B ${BUILD_DIR} -G Ninja -DCMAKE_BUILD_TYPE=Debug'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Build') {
|
||||
steps {
|
||||
sh 'cmake --build ${BUILD_DIR} --parallel'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Test') {
|
||||
steps {
|
||||
sh 'QT_QPA_PLATFORM=offscreen LANG=C.UTF-8 LC_ALL=C.UTF-8 ctest --test-dir ${BUILD_DIR} --output-on-failure'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
if (getContext(hudson.FilePath)) {
|
||||
cleanWs deleteDirs: true, disableDeferredWipeout: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 lifemaps
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -1,30 +0,0 @@
|
||||
FROM ubuntu:26.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
python3 \
|
||||
clang-format \
|
||||
cmake \
|
||||
ninja-build \
|
||||
git \
|
||||
g++ \
|
||||
make \
|
||||
ca-certificates \
|
||||
openssh-client \
|
||||
pkg-config \
|
||||
qtbase5-dev \
|
||||
qt6-base-dev \
|
||||
qt6-base-dev-tools \
|
||||
qt6-tools-dev \
|
||||
qt6-tools-dev-tools && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Set Qt platform for headless testing
|
||||
ENV QT_QPA_PLATFORM=offscreen
|
||||
|
||||
# Configure git for containerized environments
|
||||
RUN git config --global --add safe.directory "*"
|
||||
|
||||
WORKDIR /workspace
|
||||
@@ -1,2 +0,0 @@
|
||||
torsten.wierschin@life-maps.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIO+c+9jJKrLpTk4pLg+p1Qo0CjCYzfKfEhXEu0MAlZpj
|
||||
nkana@outlook.de ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBR68AzU7UtY+NJPIRloEeIlQIZIP3xhvkWB8BediMeQ
|
||||
@@ -1,221 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(".").resolve()
|
||||
TEST_DIRS = [
|
||||
ROOT / "tests",
|
||||
ROOT / "test",
|
||||
ROOT / "qt6-after" / "qt6-migrated-benchmark" / "tests",
|
||||
]
|
||||
CPP_EXTENSIONS = {".c", ".cc", ".cpp", ".cxx", ".h", ".hh", ".hpp", ".hxx"}
|
||||
ALLOWED_SIGNERS_FILE = ROOT / "ci" / "allowed_signers"
|
||||
|
||||
TEST_PATTERNS = [
|
||||
"test_{name}.cpp",
|
||||
"test_{name}.h",
|
||||
"{name}_test.cpp",
|
||||
"{name}_test.h",
|
||||
"{name}test.cpp",
|
||||
"{name}test.h",
|
||||
"{name}gtest.cpp",
|
||||
"{name}gtest.h",
|
||||
]
|
||||
|
||||
|
||||
def run(cmd: str) -> str:
|
||||
return subprocess.check_output(cmd, shell=True, text=True).strip()
|
||||
|
||||
|
||||
def git_ref_exists(ref: str) -> bool:
|
||||
try:
|
||||
subprocess.check_call(
|
||||
f"git rev-parse --verify --quiet '{ref}'",
|
||||
shell=True,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
|
||||
def is_pull_request_build() -> bool:
|
||||
return bool(os.environ.get("CHANGE_ID"))
|
||||
|
||||
|
||||
def resolve_target_ref() -> str | None:
|
||||
raw = os.environ.get("CHANGE_TARGET", "").strip()
|
||||
|
||||
candidates = []
|
||||
if raw:
|
||||
candidates.append(raw)
|
||||
if not raw.startswith("origin/"):
|
||||
candidates.append(f"origin/{raw}")
|
||||
|
||||
candidates.extend(["origin/main", "main"])
|
||||
|
||||
seen = set()
|
||||
for ref in candidates:
|
||||
if ref and ref not in seen:
|
||||
seen.add(ref)
|
||||
if git_ref_exists(ref):
|
||||
return ref
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def changed_files_and_base() -> tuple[list[str], str]:
|
||||
target = resolve_target_ref()
|
||||
|
||||
if target:
|
||||
print(f"Vergleiche gegen Ziel-Ref: {target}")
|
||||
out = run(f"git diff --name-only {target}...HEAD")
|
||||
files = [line.strip() for line in out.splitlines() if line.strip()]
|
||||
return files, target
|
||||
|
||||
if is_pull_request_build():
|
||||
print("ERROR: Kein gültiger Ziel-Branch-Ref für PR-Build gefunden.")
|
||||
sys.exit(1)
|
||||
|
||||
if git_ref_exists("HEAD~1"):
|
||||
print("Kein Ziel-Branch-Ref gefunden, verwende HEAD~1 für Branch-Build.")
|
||||
out = run("git diff --name-only HEAD~1..HEAD")
|
||||
files = [line.strip() for line in out.splitlines() if line.strip()]
|
||||
return files, "HEAD~1"
|
||||
|
||||
print("Keine Vergleichsbasis gefunden, behandle alle versionierten Dateien als geändert.")
|
||||
out = run("git ls-files")
|
||||
files = [line.strip() for line in out.splitlines() if line.strip()]
|
||||
return files, ""
|
||||
|
||||
|
||||
def commits_in_range(base: str) -> list[str]:
|
||||
if base:
|
||||
if base == "HEAD~1":
|
||||
out = run("git rev-list HEAD~1..HEAD")
|
||||
else:
|
||||
out = run(f"git rev-list {base}..HEAD")
|
||||
else:
|
||||
out = run("git rev-list HEAD")
|
||||
return [line.strip() for line in out.splitlines() if line.strip()]
|
||||
|
||||
|
||||
def is_cpp_file(path: Path) -> bool:
|
||||
return path.suffix.lower() in CPP_EXTENSIONS
|
||||
|
||||
|
||||
def require_clang_format():
|
||||
if not (ROOT / ".clang-format").exists():
|
||||
print("ERROR: .clang-format fehlt im Repository-Root.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def require_allowed_signers():
|
||||
if not ALLOWED_SIGNERS_FILE.exists():
|
||||
print(f"ERROR: {ALLOWED_SIGNERS_FILE} fehlt.")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def check_format(path: Path):
|
||||
subprocess.check_call(f"clang-format --dry-run --Werror '{path}'", shell=True)
|
||||
|
||||
|
||||
def candidate_names(path: Path):
|
||||
stem = path.stem.lower()
|
||||
names = {stem}
|
||||
for suffix in ["_p", "_impl", "_private", "_priv", "_ui"]:
|
||||
if stem.endswith(suffix):
|
||||
names.add(stem[:-len(suffix)])
|
||||
return sorted(n for n in names if n)
|
||||
|
||||
|
||||
def iter_test_files():
|
||||
for test_dir in TEST_DIRS:
|
||||
if test_dir.exists():
|
||||
yield from (p for p in test_dir.rglob("*") if p.is_file())
|
||||
|
||||
|
||||
def has_related_test(path: Path) -> bool:
|
||||
test_files = list(iter_test_files())
|
||||
if not test_files:
|
||||
return False
|
||||
|
||||
test_names = [p.name.lower() for p in test_files]
|
||||
|
||||
for name in candidate_names(path):
|
||||
for pattern in TEST_PATTERNS:
|
||||
if pattern.format(name=name).lower() in test_names:
|
||||
return True
|
||||
for test_name in test_names:
|
||||
if name in test_name and ("test" in test_name or "gtest" in test_name):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def verify_commit_signature(commit: str):
|
||||
env = os.environ.copy()
|
||||
env["GIT_CONFIG_COUNT"] = "1"
|
||||
env["GIT_CONFIG_KEY_0"] = "gpg.ssh.allowedSignersFile"
|
||||
env["GIT_CONFIG_VALUE_0"] = str(ALLOWED_SIGNERS_FILE)
|
||||
|
||||
subprocess.check_call(
|
||||
f"git verify-commit {commit}",
|
||||
shell=True,
|
||||
env=env,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
require_clang_format()
|
||||
require_allowed_signers()
|
||||
|
||||
files, base = changed_files_and_base()
|
||||
if not files:
|
||||
print("Keine geänderten Dateien erkannt.")
|
||||
return
|
||||
|
||||
commits = commits_in_range(base)
|
||||
if not commits:
|
||||
print("Keine neuen Commits im Vergleichsbereich erkannt.")
|
||||
else:
|
||||
for commit in commits:
|
||||
print(f"Prüfe SSH-Signatur: {commit}")
|
||||
try:
|
||||
verify_commit_signature(commit)
|
||||
except subprocess.CalledProcessError:
|
||||
print(
|
||||
"ERROR: Commit ist nicht gültig mit erlaubtem SSH-Signierer signiert: "
|
||||
f"{commit}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
for rel in files:
|
||||
path = ROOT / rel
|
||||
if not path.exists():
|
||||
continue
|
||||
if is_cpp_file(path):
|
||||
print(f"Prüfe: {rel}")
|
||||
try:
|
||||
check_format(path)
|
||||
except subprocess.CalledProcessError:
|
||||
print(f"ERROR: Formatfehler in {rel}")
|
||||
sys.exit(1)
|
||||
|
||||
if not has_related_test(path):
|
||||
print(
|
||||
"ERROR: Kein passender Test unter tests/, test/ oder "
|
||||
"qt6-after/qt6-migrated-benchmark/tests/ gefunden für: "
|
||||
f"{rel}"
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
print("PR-Policy-Prüfungen erfolgreich.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,337 +0,0 @@
|
||||
# Entwicklerhinweise: Anforderungen bei Push und Pull Request
|
||||
|
||||
Dieses Repository verwendet Jenkins-basierte PR- und Branch-Policies.
|
||||
Ziel ist, dass Code bereits **vor dem Merge** bestimmte Qualitätsanforderungen erfüllt.
|
||||
|
||||
Diese Datei beschreibt, **was bei einem Push erwartet wird**, **was in Jenkins geprüft wird** und **wie du Fehler lokal vermeiden kannst**.
|
||||
|
||||
---
|
||||
|
||||
## Überblick
|
||||
|
||||
Bei Pushes und Pull Requests werden automatisierte Policies ausgeführt.
|
||||
|
||||
Je nach Build-Typ gelten leicht unterschiedliche Vergleichsregeln:
|
||||
|
||||
- **bei Pull Requests** wird gegen den Zielbranch verglichen, z. B. `origin/main`
|
||||
- **bei normalen Branch-Builds** wird auf eine lokale Vergleichsbasis zurückgefallen, z. B. `HEAD~1`, falls kein Zielbranch-Ref verfügbar ist
|
||||
|
||||
Die eigentlichen Qualitätsanforderungen bleiben gleich.
|
||||
|
||||
---
|
||||
|
||||
## Was beim Push / PR geprüft wird
|
||||
|
||||
### 1. `.clang-format` muss vorhanden sein
|
||||
Im Repository-Root muss eine Datei `.clang-format` liegen.
|
||||
|
||||
Ohne diese Datei schlägt der Policy-Check fehl.
|
||||
|
||||
---
|
||||
|
||||
### 2. Geänderte C/C++-Dateien müssen korrekt formatiert sein
|
||||
Für geänderte C/C++-Dateien wird `clang-format` geprüft.
|
||||
|
||||
Betroffen sind insbesondere Dateien mit diesen Endungen:
|
||||
|
||||
- `.c`
|
||||
- `.cc`
|
||||
- `.cpp`
|
||||
- `.cxx`
|
||||
- `.h`
|
||||
- `.hh`
|
||||
- `.hpp`
|
||||
- `.hxx`
|
||||
|
||||
Wenn eine Datei nicht korrekt formatiert ist, schlägt der Build fehl.
|
||||
|
||||
#### Lokal prüfen
|
||||
```bash
|
||||
clang-format --dry-run --Werror <datei>
|
||||
```
|
||||
|
||||
#### Lokal automatisch formatieren
|
||||
```bash
|
||||
clang-format -i <datei>
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
```bash
|
||||
clang-format -i main.cpp tests/test_main.cpp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Zu geänderten C/C++-Dateien muss ein passender Test existieren
|
||||
Wenn du eine C/C++-Datei änderst, muss ein passender Test vorhanden sein.
|
||||
|
||||
Es wird nach Tests in diesen Verzeichnissen gesucht:
|
||||
|
||||
- `tests/`
|
||||
- `test/`
|
||||
- `qt6-after/qt6-migrated-benchmark/tests/`
|
||||
|
||||
Unterstützte typische Testnamen sind zum Beispiel:
|
||||
|
||||
- `test_<name>.cpp`
|
||||
- `<name>_test.cpp`
|
||||
- `<name>test.cpp`
|
||||
- `<name>gtest.cpp`
|
||||
|
||||
Zusätzlich erkennt die Policy auch ähnliche Dateinamen heuristisch, solange `test` oder `gtest` im Dateinamen vorkommt.
|
||||
|
||||
#### Beispiel
|
||||
Wenn du `main.cpp` änderst, ist z. B. passend:
|
||||
|
||||
```text
|
||||
tests/test_main.cpp
|
||||
```
|
||||
|
||||
Wenn kein passender Test gefunden wird, schlägt der Build fehl.
|
||||
|
||||
---
|
||||
|
||||
### 4. Nur gültig SSH-signierte Commits sind erlaubt
|
||||
Neue Commits im geprüften Bereich müssen gültig **mit SSH signiert** sein.
|
||||
|
||||
Die Jenkins-Policy prüft Commit-Signaturen gegen eine erlaubte Signiererdatei:
|
||||
|
||||
```text
|
||||
ci/allowed_signers
|
||||
```
|
||||
|
||||
Wenn ein Commit:
|
||||
|
||||
- nicht signiert ist
|
||||
- nicht per SSH signiert ist
|
||||
- mit einem nicht erlaubten SSH-Schlüssel signiert ist
|
||||
|
||||
dann schlägt der Build fehl.
|
||||
|
||||
---
|
||||
|
||||
## SSH-signierte Commits einrichten
|
||||
|
||||
### 1. SSH-Signing-Key erzeugen
|
||||
Falls du noch keinen dedizierten Signing-Key hast:
|
||||
|
||||
```bash
|
||||
ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_signing -C "dein.name@example.com"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Git für SSH-Signing konfigurieren
|
||||
|
||||
```bash
|
||||
git config --global gpg.format ssh
|
||||
git config --global user.signingkey ~/.ssh/id_ed25519_signing.pub
|
||||
git config --global commit.gpgsign true
|
||||
```
|
||||
|
||||
Damit werden neue Commits automatisch mit deinem SSH-Key signiert.
|
||||
|
||||
---
|
||||
|
||||
### 3. Öffentlichen Schlüssel zur Allowlist hinzufügen
|
||||
Dein öffentlicher Schlüssel muss in `ci/allowed_signers` eingetragen sein.
|
||||
|
||||
Format:
|
||||
|
||||
```text
|
||||
<principal> <public-key>
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
max.mustermann@example.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAA...
|
||||
```
|
||||
|
||||
Ohne Eintrag in `ci/allowed_signers` wird dein signierter Commit in Jenkins trotzdem abgelehnt.
|
||||
|
||||
---
|
||||
|
||||
### 4. Optional: lokale Verifikation aktivieren
|
||||
Wenn du lokal dieselbe Vertrauenskette wie Jenkins verwenden willst:
|
||||
|
||||
```bash
|
||||
mkdir -p ~/.config/git
|
||||
cp ci/allowed_signers ~/.config/git/allowed_signers
|
||||
git config --global gpg.ssh.allowedSignersFile ~/.config/git/allowed_signers
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 5. Signierten Commit erstellen
|
||||
```bash
|
||||
git add .
|
||||
git commit -S -m "Meine Änderung"
|
||||
git push
|
||||
```
|
||||
|
||||
Wenn `commit.gpgsign=true` gesetzt ist, signiert Git Commits in der Regel automatisch.
|
||||
|
||||
---
|
||||
|
||||
### 6. Signatur lokal prüfen
|
||||
```bash
|
||||
git log --show-signature -1
|
||||
git verify-commit HEAD
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Lokaler Minimal-Check vor dem Push
|
||||
|
||||
Vor dem Push solltest du mindestens diese Schritte ausführen:
|
||||
|
||||
### Format anwenden
|
||||
```bash
|
||||
clang-format -i main.cpp tests/test_main.cpp
|
||||
```
|
||||
|
||||
### Policy lokal ausführen
|
||||
```bash
|
||||
python3 ci/check_pr_policies.py
|
||||
```
|
||||
|
||||
## Projekt lokal im CI-Container bauen
|
||||
|
||||
Zuerst das lokale CI-Image bauen:
|
||||
|
||||
```bash
|
||||
docker build -t lifemaps/ci-cpp:latest -f Dockerfile.ci .
|
||||
```
|
||||
|
||||
Danach das Projekt im Container bauen und testen:
|
||||
|
||||
```bash
|
||||
docker run --rm -it \
|
||||
-v "$PWD:/workspace" \
|
||||
-w /workspace \
|
||||
lifemaps/ci-cpp:latest \
|
||||
bash -lc '
|
||||
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Debug &&
|
||||
cmake --build build --parallel &&
|
||||
ctest --test-dir build --output-on-failure
|
||||
'
|
||||
```
|
||||
|
||||
Damit wird dieselbe Toolchain verwendet wie in der Jenkins-CI.
|
||||
|
||||
|
||||
## Policy-Check lokal im CI-Container ausführen
|
||||
|
||||
```bash
|
||||
docker run --rm -it \
|
||||
-v "$PWD:/workspace" \
|
||||
-w /workspace \
|
||||
lifemaps/ci-cpp:latest \
|
||||
bash -lc 'python3 ci/check_pr_policies.py'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Verhalten in Jenkins
|
||||
|
||||
Die Jenkins-Pipeline umfasst typischerweise diese Stages:
|
||||
|
||||
1. **Checkout**
|
||||
2. **Policy Checks**
|
||||
3. **Configure**
|
||||
4. **Build**
|
||||
5. **Test**
|
||||
6. **Cleanup**
|
||||
|
||||
Wenn die Policy-Checks fehlschlagen, werden die späteren Build- und Test-Stages nicht mehr ausgeführt.
|
||||
|
||||
---
|
||||
|
||||
## Typische Fehlerursachen
|
||||
|
||||
### Formatfehler
|
||||
Beispiel:
|
||||
```text
|
||||
ERROR: Formatfehler in tests/test_main.cpp
|
||||
```
|
||||
|
||||
#### Lösung
|
||||
```bash
|
||||
clang-format -i tests/test_main.cpp
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Kein passender Test gefunden
|
||||
Beispiel:
|
||||
```text
|
||||
ERROR: Kein passender Test unter tests/, test/ oder qt6-after/qt6-migrated-benchmark/tests für main.cpp gefunden.
|
||||
```
|
||||
|
||||
#### Lösung
|
||||
- passenden Test anlegen
|
||||
- Testdatei passend benennen
|
||||
- Test in einem der erlaubten Testverzeichnisse ablegen
|
||||
|
||||
---
|
||||
|
||||
### Commit nicht gültig SSH-signiert
|
||||
Beispiel:
|
||||
```text
|
||||
ERROR: Commit ist nicht gültig mit erlaubtem SSH-Signierer signiert: <sha>
|
||||
```
|
||||
|
||||
#### Lösung
|
||||
- SSH-Signing lokal korrekt konfigurieren
|
||||
- prüfen, ob der richtige Schlüssel verwendet wird
|
||||
- prüfen, ob der öffentliche Schlüssel in `ci/allowed_signers` eingetragen ist
|
||||
- Commit neu erstellen oder neu signieren
|
||||
|
||||
---
|
||||
|
||||
### `.clang-format` fehlt
|
||||
Beispiel:
|
||||
```text
|
||||
ERROR: .clang-format fehlt im Repository-Root.
|
||||
```
|
||||
|
||||
#### Lösung
|
||||
- `.clang-format` im Repository-Root anlegen oder wiederherstellen
|
||||
|
||||
---
|
||||
|
||||
## Empfohlener Workflow
|
||||
|
||||
1. Feature-Branch erstellen
|
||||
2. Änderungen implementieren
|
||||
3. Test ergänzen oder anpassen
|
||||
4. Code formatieren
|
||||
5. Commit SSH-signiert erstellen
|
||||
6. lokal Policy, Build und Tests ausführen
|
||||
7. pushen
|
||||
8. Pull Request öffnen
|
||||
9. Jenkins-Ergebnis prüfen
|
||||
10. erst bei grünem Status mergen
|
||||
|
||||
---
|
||||
|
||||
## Kurz-Checkliste vor jedem Push
|
||||
|
||||
- [ ] C/C++-Dateien sind formatiert
|
||||
- [ ] passender Test existiert
|
||||
- [ ] Commit ist SSH-signiert
|
||||
- [ ] öffentlicher Schlüssel steht in `ci/allowed_signers`
|
||||
- [ ] `python3 ci/check_pr_policies.py` läuft erfolgreich
|
||||
- [ ] Build und Tests laufen lokal
|
||||
|
||||
---
|
||||
|
||||
## Ziel der Regeln
|
||||
|
||||
Die Policies sollen sicherstellen, dass:
|
||||
|
||||
- Code konsistent formatiert ist
|
||||
- Änderungen nachvollziehbar getestet sind
|
||||
- nur autorisierte Signierer Commits einbringen
|
||||
- PRs frühzeitig automatisiert validiert werden
|
||||
@@ -1,336 +0,0 @@
|
||||
# Entire auf Headless-Server mit EU-Kontext einrichten
|
||||
|
||||
Diese Anleitung beschreibt die Einrichtung von Entire auf einem Headless-Host mit bestehendem Git-Repository auf einem eigenen Gitea-Server und Nutzung des **EU-Kontexts**.
|
||||
|
||||
## Ziel
|
||||
|
||||
- Entire ohne GUI/Browser auf dem Server verwenden
|
||||
- Login über Device-Code-Flow
|
||||
- Tokens ohne OS-Keyring in Datei speichern
|
||||
- EU-Jurisdiktion / EU-Cluster verwenden
|
||||
- Entire lokal in einem bestehenden Git-Repository aktivieren
|
||||
|
||||
---
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
- `entire` CLI ist installiert
|
||||
- Zugriff per SSH auf den Headless-Server
|
||||
- funktionierendes lokales Git-Checkout
|
||||
- `origin`-Remote ist gesetzt
|
||||
- Browser auf einem **anderen lokalen Rechner** verfügbar
|
||||
|
||||
Beispiel prüfen:
|
||||
|
||||
```bash
|
||||
git remote -v
|
||||
```
|
||||
|
||||
Beispielausgabe:
|
||||
|
||||
```bash
|
||||
origin git@devel:lifemaps/qt-migration-copilot-demo.git (fetch)
|
||||
origin git@devel:lifemaps/qt-migration-copilot-demo.git (push)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. Token-Speicherung für Headless-Betrieb aktivieren
|
||||
|
||||
Auf Headless-Systemen gibt es oft keinen Linux-Keyring (`org.freedesktop.secrets`).
|
||||
Deshalb sollten Entire-Tokens in einer Datei gespeichert werden.
|
||||
|
||||
Empfohlene Umgebungsvariablen:
|
||||
|
||||
```bash
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
export ENTIRE_TOKEN_STORE_PATH="$HOME/.config/entire/tokens.json"
|
||||
export ACCESSIBLE=1
|
||||
```
|
||||
|
||||
Temporär in der aktuellen Shell:
|
||||
|
||||
```bash
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
export ENTIRE_TOKEN_STORE_PATH="$HOME/.config/entire/tokens.json"
|
||||
export ACCESSIBLE=1
|
||||
```
|
||||
|
||||
Dauerhaft in `~/.bashrc`:
|
||||
|
||||
```bash
|
||||
echo 'export ENTIRE_TOKEN_STORE=file' >> ~/.bashrc
|
||||
echo 'export ENTIRE_TOKEN_STORE_PATH="$HOME/.config/entire/tokens.json"' >> ~/.bashrc
|
||||
echo 'export ACCESSIBLE=1' >> ~/.bashrc
|
||||
source ~/.bashrc
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Im EU-Kontext anmelden
|
||||
|
||||
Für Headless-Systeme den Device-Code-Flow verwenden:
|
||||
|
||||
```bash
|
||||
entire login --device --server https://eu.auth.entire.io
|
||||
```
|
||||
|
||||
Das CLI zeigt dann etwa:
|
||||
|
||||
- einen **Device Code**
|
||||
- eine **Login-URL**
|
||||
|
||||
Die URL im Browser **auf dem lokalen Rechner** öffnen und die Anmeldung dort bestätigen.
|
||||
|
||||
### Wichtiger Hinweis
|
||||
|
||||
Wenn auf dem Server kein Browser verfügbar ist, ist eine Meldung wie diese normal:
|
||||
|
||||
```text
|
||||
Warning: failed to open browser: xdg-open not found
|
||||
```
|
||||
|
||||
Das ist **nicht** das eigentliche Problem.
|
||||
|
||||
Falls Entire versucht, Tokens im Keyring zu speichern und dabei fehlschlägt, war `ENTIRE_TOKEN_STORE=file` nicht gesetzt.
|
||||
|
||||
---
|
||||
|
||||
## 3. Login prüfen
|
||||
|
||||
Nach erfolgreicher Anmeldung:
|
||||
|
||||
```bash
|
||||
entire auth status
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
Logged in to https://eu.auth.entire.io
|
||||
Context: eu.auth.entire.io
|
||||
Token: stored in file /home/<user>/.config/entire/tokens.json
|
||||
```
|
||||
|
||||
Falls mehrere Kontexte existieren:
|
||||
|
||||
```bash
|
||||
entire auth contexts
|
||||
entire auth use --help
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Prüfen, ob wirklich EU verwendet wird
|
||||
|
||||
Zusätzlich zum Login-Kontext sollte geprüft werden, auf welchem Cluster die Entire-Repositories liegen.
|
||||
|
||||
Projekt-Repositories anzeigen:
|
||||
|
||||
```bash
|
||||
entire repo list <project-id>
|
||||
```
|
||||
|
||||
Wenn in der Spalte `CLUSTER` z. B. Folgendes erscheint:
|
||||
|
||||
```text
|
||||
aws-eu-central-1.entire.io
|
||||
```
|
||||
|
||||
dann liegen diese Repositories auf einem **EU-Cluster**.
|
||||
|
||||
### Empfohlener Doppel-Check
|
||||
|
||||
```bash
|
||||
entire auth status
|
||||
entire repo list <project-id>
|
||||
```
|
||||
|
||||
EU ist praktisch bestätigt, wenn:
|
||||
|
||||
- `Context: eu.auth.entire.io` aktiv ist
|
||||
- die Repositories auf `aws-eu-central-1.entire.io` liegen
|
||||
|
||||
---
|
||||
|
||||
## 5. Entire im lokalen Repository aktivieren
|
||||
|
||||
In das lokale Repository wechseln:
|
||||
|
||||
```bash
|
||||
cd ~/coplint.d/qt-migration-copilot-demo
|
||||
```
|
||||
|
||||
Dann Entire aktivieren:
|
||||
|
||||
```bash
|
||||
entire enable
|
||||
```
|
||||
|
||||
Status prüfen:
|
||||
|
||||
```bash
|
||||
entire status
|
||||
```
|
||||
|
||||
Beispiel:
|
||||
|
||||
```text
|
||||
● Enabled · branch bugfix/ci-pipeline-commissioning
|
||||
Agents · Copilot CLI
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Agent-Integration prüfen oder ergänzen
|
||||
|
||||
Verfügbare Agenten anzeigen:
|
||||
|
||||
```bash
|
||||
entire agent list
|
||||
```
|
||||
|
||||
Hilfe für das Hinzufügen:
|
||||
|
||||
```bash
|
||||
entire agent add --help
|
||||
```
|
||||
|
||||
Neuen Agenten hinzufügen:
|
||||
|
||||
```bash
|
||||
entire agent add <name>
|
||||
```
|
||||
|
||||
Danach erneut prüfen:
|
||||
|
||||
```bash
|
||||
entire status
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Unterschied zwischen lokalem Git-Repo und Entire-Repo verstehen
|
||||
|
||||
Wichtig: Entire verwendet hier **zwei verschiedene Konzepte**.
|
||||
|
||||
### Lokales Git-Repository
|
||||
Das echte Code-Repository, z. B.:
|
||||
|
||||
```bash
|
||||
git@devel:lifemaps/qt-migration-copilot-demo.git
|
||||
```
|
||||
|
||||
### Entire-Repository
|
||||
Ein separates Objekt in Entire, verwaltet über:
|
||||
|
||||
```bash
|
||||
entire repo list <project-id>
|
||||
entire repo get <repo> --project <project-id>
|
||||
entire repo create <name> --project <project-id>
|
||||
```
|
||||
|
||||
### Konsequenz
|
||||
|
||||
`entire enable` aktiviert das **lokale Git-Repository**,
|
||||
aber es erzeugt **nicht automatisch** ein neues `entire repo`-Objekt.
|
||||
|
||||
Darum kann es sein, dass:
|
||||
|
||||
- `entire status` = aktiviert
|
||||
- aber `entire repo list <project-id>` dein lokales Repo **nicht** zeigt
|
||||
|
||||
Das ist normales Verhalten.
|
||||
|
||||
---
|
||||
|
||||
## 8. Optional: Entire-Repo im Projekt anlegen
|
||||
|
||||
Wenn zusätzlich ein eigenes Entire-Repo-Objekt für das lokale Projekt gewünscht ist:
|
||||
|
||||
```bash
|
||||
entire repo create qt-migration-copilot-demo --project <project-id>
|
||||
```
|
||||
|
||||
Danach prüfen:
|
||||
|
||||
```bash
|
||||
entire repo list <project-id>
|
||||
```
|
||||
|
||||
### Hinweis
|
||||
|
||||
Nach bisherigem CLI-Verhalten wird dabei nur ein **Entire-internes Repo-Objekt** erzeugt.
|
||||
Es gibt in der gezeigten Hilfe keinen Hinweis darauf, dass dabei automatisch eine Git-URL aus Gitea importiert oder synchronisiert wird.
|
||||
|
||||
---
|
||||
|
||||
## 9. Typischer Minimal-Workflow
|
||||
|
||||
```bash
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
export ENTIRE_TOKEN_STORE_PATH="$HOME/.config/entire/tokens.json"
|
||||
export ACCESSIBLE=1
|
||||
|
||||
entire login --device --server https://eu.auth.entire.io
|
||||
entire auth status
|
||||
|
||||
cd ~/coplint.d/qt-migration-copilot-demo
|
||||
entire enable
|
||||
entire status
|
||||
entire agent list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. Troubleshooting
|
||||
|
||||
### Kein Browser auf dem Server
|
||||
Normal auf Headless-Hosts.
|
||||
Device-Code-URL auf lokalem Rechner öffnen.
|
||||
|
||||
### Login erfolgreich, aber `Not logged in`
|
||||
Ursache meist fehlender Keyring und keine Datei-Speicherung.
|
||||
|
||||
Lösung:
|
||||
|
||||
```bash
|
||||
export ENTIRE_TOKEN_STORE=file
|
||||
entire login --device --server https://eu.auth.entire.io
|
||||
```
|
||||
|
||||
### Entire ist aktiviert, aber nicht in `entire repo list`
|
||||
Das ist erwartbar, weil lokales Git-Repo und Entire-Projektrepo getrennte Objekte sind.
|
||||
|
||||
### Diagnose
|
||||
Bei Problemen:
|
||||
|
||||
```bash
|
||||
entire doctor
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Empfehlung für dieses Setup
|
||||
|
||||
Für ein Setup mit **Gitea + Jenkins + Entire** gilt:
|
||||
|
||||
- **Gitea** bleibt Source-of-Truth für den Code
|
||||
- **Jenkins** bleibt CI/CD-System
|
||||
- **Entire** ergänzt Session-, Agent- und Checkpoint-Tracking im lokalen Repository
|
||||
- **Entire-Projekte/Repos** sind zusätzliche Verwaltungsobjekte und nicht identisch mit dem Gitea-Remote
|
||||
|
||||
---
|
||||
|
||||
## 12. Nützliche Befehle
|
||||
|
||||
```bash
|
||||
entire auth status
|
||||
entire auth contexts
|
||||
entire project list
|
||||
entire repo list <project-id>
|
||||
entire enable
|
||||
entire status
|
||||
entire agent list
|
||||
entire doctor
|
||||
```
|
||||
@@ -1,280 +0,0 @@
|
||||
# scripts/
|
||||
|
||||
Dieses Verzeichnis enthält Hilfsskripte für den Demo- und Benchmark-Workflow rund um:
|
||||
|
||||
- **Qt5 → Qt6**
|
||||
- **C++11 → C++23**
|
||||
- **Copilot CLI**
|
||||
- **Entire CLI**
|
||||
- **Scaleway BYOK / OpenAI-kompatibler Provider**
|
||||
- **lokale Reproduzierbarkeit der Migration**
|
||||
|
||||
## Übersicht
|
||||
|
||||
### `create-qt5-legacy-benchmark.sh`
|
||||
Erzeugt das Legacy-Beispielprojekt unter:
|
||||
|
||||
```text
|
||||
qt5-before/qt5-legacy-migration-benchmark
|
||||
```
|
||||
|
||||
Das Projekt ist absichtlich veraltet aufgebaut, um eine realistische Migration zu erzwingen, z. B. mit:
|
||||
|
||||
- `QRegExp`
|
||||
- `QDesktopWidget`
|
||||
- `QTextCodec`
|
||||
- alter `SIGNAL/SLOT`-Syntax
|
||||
- `Qt5::Core`, `Qt5::Gui`, `Qt5::Widgets`
|
||||
- `C++11`
|
||||
|
||||
### `run-entire-copilot-migration.sh`
|
||||
Standard-Workflow für eine Copilot-CLI-Migration mit Entire:
|
||||
|
||||
1. Entire für Copilot CLI aktivieren
|
||||
2. Baseline committen
|
||||
3. Copilot-Session starten
|
||||
4. Ergebnis committen
|
||||
5. Checkpoints anzeigen
|
||||
6. letzten Stand mit Entire erklären
|
||||
|
||||
Dieses Skript ist für den normalen Migrationslauf gedacht.
|
||||
|
||||
**Modellwahl:**
|
||||
Das Skript akzeptiert optional ein Modell als zweiten Parameter.
|
||||
|
||||
Default:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration.sh
|
||||
```
|
||||
|
||||
verwendet intern:
|
||||
|
||||
```bash
|
||||
copilot --model "qwen3.5-397b-a17b"
|
||||
```
|
||||
|
||||
Mit explizitem Modell:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration.sh qt5-before/qt5-legacy-migration-benchmark gpt-5
|
||||
```
|
||||
|
||||
### `run-entire-copilot-migration-with-optional-rewind-resume.sh`
|
||||
Wie das Standardskript, aber zusätzlich mit optionalem:
|
||||
|
||||
- `entire checkpoint rewind`
|
||||
- `entire session resume <branch>`
|
||||
|
||||
**Modellwahl:**
|
||||
Auch dieses Skript akzeptiert optional ein Modell als zweiten Parameter.
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration-with-optional-rewind-resume.sh qt5-before/qt5-legacy-migration-benchmark claude-sonnet-4
|
||||
```
|
||||
|
||||
**Wichtig:**
|
||||
Die Entire-Dokumentation ist bei Copilot CLI in Bezug auf Rewind/Resume nicht ganz konsistent.
|
||||
Die Copilot-CLI-Seite beschreibt Rewind und Resume, während die allgemeine Agent-Übersicht teils eingeschränkte Aussagen macht.
|
||||
|
||||
Darum behandelt dieses Skript Rewind/Resume bewusst als **optional** und **fehlertolerant**.
|
||||
|
||||
### `use-scaleway-copilot-debug.sh`
|
||||
Hilfsskript zum Setzen einer **Scaleway OpenAI-kompatiblen BYOK-Konfiguration** für Copilot CLI.
|
||||
|
||||
Typischer Zweck:
|
||||
|
||||
- Provider-Umgebungsvariablen setzen
|
||||
- Copilot CLI mit Scaleway testen
|
||||
- Modellname konfigurieren
|
||||
- Token-Limits für nicht im Copilot-Katalog enthaltene Modelle explizit setzen
|
||||
- Debugging eines externen Providers vereinfachen
|
||||
|
||||
Ein typischer Inhalt wäre z. B.:
|
||||
|
||||
- `COPILOT_PROVIDER_TYPE=openai`
|
||||
- `COPILOT_PROVIDER_BASE_URL=https://api.scaleway.ai/v1`
|
||||
- `COPILOT_PROVIDER_API_KEY=...`
|
||||
- `COPILOT_MODEL=qwen3.5-397b-a17b`
|
||||
- `COPILOT_PROVIDER_MAX_PROMPT_TOKENS=...`
|
||||
- `COPILOT_PROVIDER_MAX_OUTPUT_TOKENS=...`
|
||||
|
||||
---
|
||||
|
||||
## NOTE: Secret in `.envrc` hinterlegen
|
||||
|
||||
Das Secret für den verwendeten LLM-Provider soll **nicht** direkt in die Skripte geschrieben oder committed werden.
|
||||
|
||||
Lege den API-Key stattdessen in einer lokalen `.envrc` ab, z. B.:
|
||||
|
||||
```bash
|
||||
export SCW_SECRET_KEY="YOUR_PROVIDER_SECRET_HERE"
|
||||
```
|
||||
|
||||
oder allgemeiner:
|
||||
|
||||
```bash
|
||||
export OPENAI_API_KEY="YOUR_PROVIDER_SECRET_HERE"
|
||||
```
|
||||
|
||||
Danach die Umgebung neu laden, z. B. mit:
|
||||
|
||||
```bash
|
||||
direnv allow
|
||||
```
|
||||
|
||||
oder die Datei manuell sourcen, falls du kein `direnv` verwendest.
|
||||
|
||||
**Wichtig:**
|
||||
- `.envrc` nicht committen
|
||||
- Secrets nicht im Repository speichern
|
||||
- Provider-Zugangsdaten nur lokal halten
|
||||
|
||||
---
|
||||
|
||||
## Empfohlene Reihenfolge
|
||||
|
||||
### 1. Legacy-Projekt erzeugen
|
||||
|
||||
```bash
|
||||
./scripts/create-qt5-legacy-benchmark.sh
|
||||
```
|
||||
|
||||
### 2. Optional: Copilot mit Scaleway konfigurieren
|
||||
|
||||
```bash
|
||||
source ./scripts/use-scaleway-copilot-debug.sh
|
||||
```
|
||||
|
||||
Danach kannst du prüfen, ob die Variablen gesetzt sind:
|
||||
|
||||
```bash
|
||||
echo "$COPILOT_PROVIDER_BASE_URL"
|
||||
echo "$COPILOT_MODEL"
|
||||
```
|
||||
|
||||
### 3. Migration mit Entire + Copilot starten
|
||||
|
||||
Standard mit Default-Modell:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration.sh qt5-before/qt5-legacy-migration-benchmark
|
||||
```
|
||||
|
||||
Standard mit explizitem Modell:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration.sh qt5-before/qt5-legacy-migration-benchmark qwen3.5-397b-a17b
|
||||
```
|
||||
|
||||
Mit optionalem Rewind/Resume:
|
||||
|
||||
```bash
|
||||
./scripts/run-entire-copilot-migration-with-optional-rewind-resume.sh qt5-before/qt5-legacy-migration-benchmark qwen3.5-397b-a17b
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Copilot-Prompt für die Migration
|
||||
|
||||
Die Migrationsskripte verwenden sinngemäß einen Prompt in dieser Richtung:
|
||||
|
||||
```text
|
||||
Migrate the legacy Qt5/C++11 desktop application to Qt6/C++23.
|
||||
|
||||
Requirements:
|
||||
- Read the legacy source project from qt5-before/qt5-legacy-migration-benchmark
|
||||
- Write the migrated project into qt6-after/qt6-migrated-benchmark
|
||||
- Do not modify the legacy source tree in place
|
||||
- Update CMake from Qt5 to Qt6
|
||||
- Keep Core, Gui, and Widgets explicitly linked
|
||||
- Replace QRegExp with QRegularExpression
|
||||
- Replace QDesktopWidget / QApplication::desktop() usage with QScreen-based Qt6 APIs
|
||||
- Remove legacy QTextCodec usage and use modern Qt6-safe text handling
|
||||
- Modernize old SIGNAL/SLOT syntax to function-pointer connect syntax
|
||||
- Replace legacy null pointer literals with nullptr
|
||||
- Preserve the .ui and .qrc integration
|
||||
- Preserve application behavior
|
||||
- Upgrade the project from C++11 to C++23
|
||||
- For every code change inside a method, add at least three tests covering the changed behavior
|
||||
- Place all generated tests in qt6-after/qt6-migrated-benchmark/tests
|
||||
- Update CMake so the migrated application and all tests build successfully with CTest
|
||||
- Keep the test file naming compatible with repository policy checks
|
||||
- Briefly explain each migration change
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Entire-Hinweise
|
||||
|
||||
Entire erfasst bei unterstützten Agenten u. a.:
|
||||
|
||||
- Gesprächsverlauf
|
||||
- Dateiveränderungen
|
||||
- Zeitstempel
|
||||
- Checkpoints bei Commits
|
||||
- Session-Kontext auf dem Branch `entire/checkpoints/v1`
|
||||
|
||||
Typischer Entire-Grundablauf:
|
||||
|
||||
```bash
|
||||
entire agent add copilot-cli
|
||||
copilot --model "qwen3.5-397b-a17b"
|
||||
git add .
|
||||
git commit -m "..."
|
||||
entire checkpoint list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Hinweise zu `use-scaleway-copilot-debug.sh`
|
||||
|
||||
Das Skript sollte **gesourct** werden, nicht als eigener Prozess laufen, damit exportierte Variablen in deiner aktuellen Shell erhalten bleiben:
|
||||
|
||||
```bash
|
||||
source ./scripts/use-scaleway-copilot-debug.sh
|
||||
```
|
||||
|
||||
oder kurz:
|
||||
|
||||
```bash
|
||||
. ./scripts/use-scaleway-copilot-debug.sh
|
||||
```
|
||||
|
||||
Außerdem:
|
||||
|
||||
- keine echten Secrets committen
|
||||
- API-Keys nicht im Repo speichern
|
||||
- sensible Werte aus `.envrc` oder bereits gesetzten Umgebungsvariablen übernehmen
|
||||
|
||||
Beispiel:
|
||||
|
||||
```bash
|
||||
export SCW_SECRET_KEY="..."
|
||||
source ./scripts/use-scaleway-copilot-debug.sh
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Empfohlene Erweiterungen
|
||||
|
||||
Optional kannst du später noch ergänzen:
|
||||
|
||||
- `build-qt6-after.sh`
|
||||
- `run-ctest-qt6-after.sh`
|
||||
- `compare-before-after.sh`
|
||||
- `capture-copilot-env.sh`
|
||||
|
||||
---
|
||||
|
||||
## Kurzfassung
|
||||
|
||||
Der `scripts/`-Ordner enthält alles, um:
|
||||
|
||||
1. ein Legacy-Qt5-Projekt zu erzeugen,
|
||||
2. Copilot CLI optional mit einem externen Modell zu konfigurieren,
|
||||
3. Secrets lokal über `.envrc` bereitzustellen,
|
||||
4. eine Qt6/C++23-Migration mit Entire zu dokumentieren,
|
||||
5. und den Migrationslauf reproduzierbar zu machen.
|
||||
@@ -1,97 +0,0 @@
|
||||
# Copilot CLI mit Scaleway verwenden
|
||||
|
||||
## 1) `.envrc` anlegen
|
||||
|
||||
Lege in deinem Projekt eine Datei `.envrc` an:
|
||||
|
||||
```bash name=.envrc
|
||||
export STRIX_LLM="qwen3.5-397b-a17b"
|
||||
export SCW_SECRET_KEY="REPLACE_WITH_YOURS"
|
||||
|
||||
export COPILOT_PROVIDER_TYPE="openai"
|
||||
export COPILOT_PROVIDER_API_KEY="${SCW_SECRET_KEY}"
|
||||
export COPILOT_PROVIDER_BASE_URL="https://api.scaleway.ai/v1"
|
||||
export COPILOT_MODEL="${STRIX_LLM}"
|
||||
|
||||
export COPILOT_PROVIDER_MAX_PROMPT_TOKENS="32768"
|
||||
export COPILOT_PROVIDER_MAX_OUTPUT_TOKENS="8192"
|
||||
```
|
||||
|
||||
## 2) `direnv` erlauben
|
||||
|
||||
Falls `direnv` schon installiert ist:
|
||||
|
||||
```bash name=direnv-allow.sh
|
||||
direnv allow
|
||||
```
|
||||
|
||||
Wenn noch nicht in der Shell aktiviert, ergänze deine Shell-Config.
|
||||
|
||||
### Bash
|
||||
```bash name=bash-direnv-hook.sh
|
||||
eval "$(direnv hook bash)"
|
||||
```
|
||||
|
||||
### Zsh
|
||||
```bash name=zsh-direnv-hook.sh
|
||||
eval "$(direnv hook zsh)"
|
||||
```
|
||||
|
||||
Danach Shell neu laden:
|
||||
|
||||
```bash name=reload-shell-config.sh
|
||||
source ~/.bashrc
|
||||
# oder
|
||||
source ~/.zshrc
|
||||
```
|
||||
|
||||
## 3) Startskript anlegen
|
||||
|
||||
```bash name=use-scaleway-copilot.sh
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
if ! command -v copilot >/dev/null 2>&1; then
|
||||
echo "copilot CLI nicht gefunden."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
: "${COPILOT_PROVIDER_TYPE:?COPILOT_PROVIDER_TYPE fehlt}"
|
||||
: "${COPILOT_PROVIDER_API_KEY:?COPILOT_PROVIDER_API_KEY fehlt}"
|
||||
: "${COPILOT_PROVIDER_BASE_URL:?COPILOT_PROVIDER_BASE_URL fehlt}"
|
||||
: "${COPILOT_MODEL:?COPILOT_MODEL fehlt}"
|
||||
|
||||
echo "Starte Copilot CLI mit:"
|
||||
echo " Provider: ${COPILOT_PROVIDER_TYPE}"
|
||||
echo " Base URL: ${COPILOT_PROVIDER_BASE_URL}"
|
||||
echo " Modell: ${COPILOT_MODEL}"
|
||||
|
||||
exec copilot "$@"
|
||||
```
|
||||
|
||||
Dann ausführbar machen:
|
||||
|
||||
```bash name=chmod-script.sh
|
||||
chmod +x use-scaleway-copilot.sh
|
||||
```
|
||||
|
||||
Start:
|
||||
|
||||
```bash name=start-script.sh
|
||||
./use-scaleway-copilot.sh
|
||||
```
|
||||
|
||||
Oder mit Argumenten:
|
||||
|
||||
```bash name=start-script-with-args.sh
|
||||
./use-scaleway-copilot.sh --model="qwen3.5-397b-a17b"
|
||||
```
|
||||
|
||||
## 4) `.gitignore` ergänzen
|
||||
|
||||
Falls noch nicht vorhanden:
|
||||
|
||||
```gitignore name=.gitignore
|
||||
.env
|
||||
.envrc
|
||||
```
|
||||
@@ -0,0 +1,9 @@
|
||||
build-rti-full
|
||||
build-rti-off
|
||||
build-rti-off-notests
|
||||
build-*
|
||||
CMakeFiles
|
||||
fix_qt6_gui_deps2.py
|
||||
fix_qt6_gui_deps.py
|
||||
fix_rti_conditionals.py
|
||||
fix_target_checks.py
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../external
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
BasedOnStyle: LLVM
|
||||
AccessModifierOffset: -2
|
||||
AlignTrailingComments: true
|
||||
AlignAfterOpenBracket: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
BinPackArguments: false
|
||||
BinPackParameters: false
|
||||
BreakBeforeBinaryOperators: false
|
||||
BreakBeforeBraces: Allman
|
||||
BreakConstructorInitializers: BeforeComma
|
||||
BreakInheritanceList: BeforeComma
|
||||
ColumnLimit: 170
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: false
|
||||
IncludeCategories:
|
||||
- Regex: '^".*"$'
|
||||
Priority: 1
|
||||
- Regex: '^<.*>$'
|
||||
Priority: 2
|
||||
IndentCaseLabels: true
|
||||
IndentWidth: 2
|
||||
InsertNewlineAtEOF: true
|
||||
Language: Cpp
|
||||
MaxEmptyLinesToKeep: 2
|
||||
NamespaceIndentation: All
|
||||
PenaltyBreakBeforeFirstCallParameter: 1000
|
||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||
PointerAlignment: Right
|
||||
PointerBindsToType: false
|
||||
SortIncludes: false
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpacesInAngles: false
|
||||
SpacesInParentheses: false
|
||||
Standard: c++20
|
||||
UseTab: Never
|
||||
TabWidth: 2
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
CompactNamespaces: false
|
||||
DerivePointerAlignment: false
|
||||
ReflowComments: false
|
||||
SortUsingDeclarations: false
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 1
|
||||
FixNamespaceComments: false
|
||||
@@ -0,0 +1,3 @@
|
||||
export MIGRATE_PIPELINE_ROOT="/home/torsten/sandbox/qt6-migration-pipeline"
|
||||
export LANG=C.UTF-8
|
||||
export LC_ALL=C.UTF-8
|
||||
@@ -0,0 +1,17 @@
|
||||
build-rti-full
|
||||
build-rti-off
|
||||
build-rti-off-notests
|
||||
build-*
|
||||
CMakeFiles
|
||||
fix_qt6_gui_deps2.py
|
||||
fix_qt6_gui_deps.py
|
||||
fix_rti_conditionals.py
|
||||
fix_target_checks.py
|
||||
Lts/Tools/ArAssetsV2/build
|
||||
Lts/Tools/ArAssetsV3/build
|
||||
Lts/Tools/ArAssetsV3_Qatar/build
|
||||
build/
|
||||
build2/
|
||||
.envrc
|
||||
build4
|
||||
coverage-out/
|
||||
+675
@@ -0,0 +1,675 @@
|
||||
#
|
||||
# Haupt CMakeLists.txt für das Projekt TDS
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
# Setzen von CMAKE_INSTALL_PREFIX auf "/shd/CTC" falls nicht über Kommandozeile angegeben
|
||||
#
|
||||
|
||||
CMAKE_MINIMUM_REQUIRED(VERSION 3.16)
|
||||
IF(POLICY CMP0069)
|
||||
CMAKE_POLICY(SET CMP0069 NEW) # cmake >= 3.9.0 - INTERPROCEDURAL_OPTIMIZATION behavior
|
||||
ENDIF()
|
||||
IF(POLICY CMP0071)
|
||||
CMAKE_POLICY(SET CMP0071 NEW) # cmake >= 3.10.0 - automatically process generated sources with AUTOMOC
|
||||
ENDIF()
|
||||
IF(POLICY CMP0072)
|
||||
CMAKE_POLICY(SET CMP0072 NEW) # cmake >= 3.11.0 - prefer libOpenGL.so over legacy libGL.so
|
||||
ENDIF()
|
||||
|
||||
|
||||
IF(NOT DEFINED CMAKE_INSTALL_PREFIX)
|
||||
SET(CMAKE_INSTALL_PREFIX "/shd/CTC" CACHE STRING "Install path prefix, prepended onto install directories." FORCE)
|
||||
ENDIF()
|
||||
#
|
||||
# # Setzen von CMAKE_BUILD_TYPE auf "Debug" falls nicht über Kommandozeile angegeben
|
||||
#
|
||||
IF(NOT DEFINED CMAKE_BUILD_TYPE)
|
||||
SET(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." FORCE)
|
||||
ENDIF()
|
||||
ADD_DEFINITIONS(-DEXPORT_BDV_TOPIC_AS_JSON)
|
||||
SET(_ver "1.2.0")
|
||||
IF(DEFINED LTS_VERSION_MAJOR AND DEFINED LTS_VERSION_MINOR AND DEFINED LTS_VERSION_PATCH)
|
||||
SET(_ver "${LTS_VERSION_MAJOR}.${LTS_VERSION_MINOR}.${LTS_VERSION_PATCH}")
|
||||
ENDIF()
|
||||
PROJECT(LTS VERSION ${_ver})
|
||||
MESSAGE(STATUS "LTS Version: ${LTS_VERSION_MAJOR}.${LTS_VERSION_MINOR}.${LTS_VERSION_PATCH}")
|
||||
|
||||
# Enable CTest integration for unit tests
|
||||
include(CTest)
|
||||
|
||||
# Release scope: subdirectories are added unconditionally (no per-module migration builds)
|
||||
function(NGL_ADD_SUBDIR_GUARDED subdir)
|
||||
add_subdirectory(${subdir})
|
||||
endfunction()
|
||||
|
||||
SET(LTS_VERSION_DATE "01.01.2018" CACHE STRING "The date when this software was compiled")
|
||||
ADD_DEFINITIONS(-DLTS_VERSION_MAJOR=\"${LTS_VERSION_MAJOR}\")
|
||||
ADD_DEFINITIONS(-DLTS_VERSION_MINOR=\"${LTS_VERSION_MINOR}\")
|
||||
ADD_DEFINITIONS(-DLTS_VERSION_PATCH=\"${LTS_VERSION_PATCH}\")
|
||||
ADD_DEFINITIONS(-DLTS_VERSION_DATE=\"${LTS_VERSION_DATE}\")
|
||||
IF(JENKINS_BUILD_NUMBER)
|
||||
ADD_DEFINITIONS(-DJENKINS_BUILD_NUMBER=\"${JENKINS_BUILD_NUMBER}\")
|
||||
ELSE()
|
||||
ADD_DEFINITIONS(-DJENKINS_BUILD_NUMBER=\"unknown\")
|
||||
ENDIF()
|
||||
|
||||
ADD_DEFINITIONS(-DAPP6C)
|
||||
SET(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
# Resolve <DDS/...> and other framework includes to the real headers under
|
||||
# Lts/ BEFORE the migration stub tree in include/. The stub tree shadows them
|
||||
# otherwise and opens namespace DDSGeneric around framework includes, which
|
||||
# pulls Qt/std definitions into that namespace and breaks every consumer.
|
||||
# (The qt5 legacy has no include/ tree; Lts/ is the real include root.)
|
||||
include_directories(BEFORE ${CMAKE_SOURCE_DIR})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/include)
|
||||
# Include directory for generated export headers
|
||||
include_directories(${CMAKE_BINARY_DIR})
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Z/src)
|
||||
# Include directories for external dependencies and internal modules
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Z/src)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Z/src/ngltools)
|
||||
# Lts/Viewer is outside the release scope; only add the include paths when
|
||||
# present to avoid -Wmissing-include-dirs warnings on validation-scope trees
|
||||
if(EXISTS "${CMAKE_SOURCE_DIR}/Lts/Viewer/SituationMap/Symbology")
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Lts/Viewer/SituationMap/Symbology)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Lts/Viewer/LtsDataView)
|
||||
endif()
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Lts/Proxies/TrainingAreaBase)
|
||||
# Release scope: include path for ProjectDataManagement stub headers
|
||||
IF(RELEASE_BUILD)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Lts/Services/ProjectDataManagement/stubs)
|
||||
ENDIF()
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Lts/Common)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Lts/Common/LtsUtils)
|
||||
include_directories(${CMAKE_BINARY_DIR}/Lts/Common/LtsUtils/LtsUtilsCore)
|
||||
# LtsUtilsSettings's generated ltsutilssettings_export.h is included by bare name from
|
||||
# settings.h. Consumer targets that reach settings.h transitively (without linking
|
||||
# LtsUtilsSettings themselves) cannot see it through the target's PUBLIC interface, so the
|
||||
# directory holding the generated header has to be visible tree-wide. The directory contains
|
||||
# only that generated header, so appending it cannot shadow an existing include.
|
||||
include_directories(${CMAKE_BINARY_DIR}/Lts/Common/LtsUtils/LtsUtilsSettings)
|
||||
include_directories(${CMAKE_SOURCE_DIR}/Z/src/NGLLogger)
|
||||
SET(CMAKE_CXX_STANDARD 17)
|
||||
SET(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
SET(CMAKE_VISIBILITY_INLINES_HIDDEN ON)
|
||||
SET(CMAKE_CXX_VISIBILITY_PRESET hidden)
|
||||
# Qt6 Migration: Removed CMAKE_POSITION_INDEPENDENT_CODE - Qt6 handles PIC automatically
|
||||
# and this setting conflicts with Qt6's reduce_relocations feature
|
||||
# SET(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
|
||||
IF(CMAKE_VERSION VERSION_GREATER 3.15.9)
|
||||
OPTION(USE_UNITY_BUILDS "Use Unity builds to speed up compilation (cmake 3.16 or newer required!)" OFF)
|
||||
IF (USE_UNITY_BUILDS)
|
||||
SET(CMAKE_UNITY_BUILD ON)
|
||||
SET(CMAKE_UNITY_BUILD_BATCH_SIZE 40)
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
# Add Lts/ include path globally for QosProfile and other Lts headers
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/Lts)
|
||||
|
||||
OPTION(BUILD_LTSWEBSOCKET "Build LtsWebSocket module (requires NGLLogger)" ON)
|
||||
|
||||
# Set CMAKE_MODULE_PATH early so FIND_PACKAGE can find our custom Find modules
|
||||
SET(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
|
||||
INCLUDE(FindLibraryWithDebug)
|
||||
|
||||
ENABLE_TESTING()
|
||||
INCLUDE(CTest)
|
||||
CONFIGURE_FILE(CTestCustom.ctest ${CMAKE_BINARY_DIR}/CTestCustom.ctest COPYONLY)
|
||||
CONFIGURE_FILE(lts-cc.conf ${CMAKE_BINARY_DIR}/lts-cc.conf.distrib COPYONLY)
|
||||
|
||||
# Add bundled GeographicLib cmake config to find modern targets (GeographicLib::GeographicLib_SHARED)
|
||||
LIST(APPEND CMAKE_PREFIX_PATH "${CMAKE_SOURCE_DIR}/include/GeographicLib/lib/cmake")
|
||||
SET(EXECUTABLE_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
|
||||
IF(WIN32)
|
||||
SET(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/bin)
|
||||
ELSE(WIN32)
|
||||
SET(LIBRARY_OUTPUT_PATH ${CMAKE_BINARY_DIR}/lib)
|
||||
ENDIF()
|
||||
|
||||
#
|
||||
# Disallow in-source build
|
||||
#
|
||||
INCLUDE(MacroEnsureOutOfSourceBuild)
|
||||
|
||||
macro_ensure_out_of_source_build("In-Source build nicht erlaubt!")
|
||||
|
||||
#
|
||||
# Qt ist wichtig - zuerst suchen
|
||||
#
|
||||
SET(CMAKE_AUTOMOC TRUE)
|
||||
SET(CMAKE_AUTOUIC TRUE)
|
||||
SET(QT_USE_IMPORTED_TARGETS TRUE)
|
||||
|
||||
# Check for OpenGL first (required by Qt6Gui)
|
||||
find_package(OpenGL)
|
||||
|
||||
# Prefer Qt6, fallback to Qt5. Create Qt6::*-aliases when Qt6 is found so existing CMakeLists continue to work.
|
||||
# Try to find Qt6 with GUI components first
|
||||
set(QT6_GUI_AVAILABLE FALSE)
|
||||
set(QT6_FOUND FALSE)
|
||||
set(QT6_CORE5COMPAT_AVAILABLE FALSE)
|
||||
|
||||
# First check if Core5Compat is available
|
||||
find_package(Qt6 6.2 COMPONENTS Core5Compat QUIET)
|
||||
if(Qt6Core5Compat_FOUND)
|
||||
set(QT6_CORE5COMPAT_AVAILABLE TRUE)
|
||||
message(STATUS "Found Qt6Core5Compat")
|
||||
endif()
|
||||
|
||||
# Find Qt6 core components first (without OpenGL which may not be available)
|
||||
# WebSockets is required by the migrated services (FileManagement's THP script
|
||||
# server uses QWebSocketServer; LtsWebSocket/httq enable their real websocket
|
||||
# paths via QT6_WEBSOCKETS_AVAILABLE when the module is found).
|
||||
find_package(Qt6 6.2 COMPONENTS Concurrent Core LinguistTools Network
|
||||
Multimedia Sql SerialPort Test Xml Positioning Svg WebSockets PrintSupport OpenGL QUIET)
|
||||
|
||||
if(Qt6_FOUND)
|
||||
# Check if OpenGL was found (GUI support available)
|
||||
if(OpenGL_FOUND)
|
||||
set(QT6_GUI_AVAILABLE TRUE)
|
||||
message(STATUS "Found Qt6 with GUI support (OpenGL): ${Qt6Core_VERSION}")
|
||||
else()
|
||||
# Skip GUI find_package - it fails without OpenGL. We'll create manual targets below.
|
||||
set(QT6_GUI_AVAILABLE FALSE)
|
||||
message(STATUS "Found Qt6 (GUI will be manually configured): ${Qt6Core_VERSION}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# If Qt6 not found yet, try with full component list including OpenGL
|
||||
if(NOT Qt6_FOUND)
|
||||
find_package(Qt6 6.2 COMPONENTS Concurrent Core LinguistTools Network OpenGL PrintSupport
|
||||
Multimedia Sql Svg SerialPort Test UiTools WebSockets WebEngineCore WebEngineWidgets
|
||||
Widgets Xml Positioning QUIET)
|
||||
if(Qt6_FOUND AND OpenGL_FOUND)
|
||||
set(QT6_GUI_AVAILABLE TRUE)
|
||||
message(STATUS "Found Qt6 with GUI support (OpenGL): ${Qt6Core_VERSION}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT Qt6_FOUND)
|
||||
# Final fallback - try to find Qt6 with minimum required components
|
||||
find_package(Qt6 6.2 COMPONENTS Core REQUIRED)
|
||||
message(STATUS "Found Qt6 (minimal): ${Qt6Core_VERSION}")
|
||||
endif()
|
||||
|
||||
# Manual Qt6 configuration - create imported targets for available libraries
|
||||
if(NOT TARGET Qt6::Core)
|
||||
find_package(Qt6 6.2 COMPONENTS Core REQUIRED)
|
||||
endif()
|
||||
|
||||
# Create imported targets for all available Qt6 libraries
|
||||
# Use Qt6 installation path from find_package or fall back to system paths
|
||||
if(Qt6Core_DIR)
|
||||
# <Qt6Core_DIR> is the cmake package dir: <prefix>/lib/cmake/Qt6Core in the
|
||||
# standard Qt layout and <prefix>/<multiarch>/cmake/Qt6Core in the Debian/
|
||||
# Ubuntu layout — so the library dir is two levels up in both. (Going three
|
||||
# levels up and appending /lib only matches the standard layout; in the
|
||||
# Debian validation container it resolved to /usr/lib/lib and every manual
|
||||
# target below was silently skipped, breaking all targets linking
|
||||
# Qt6::Sql / Qt6::Xml / Qt6::Test.)
|
||||
get_filename_component(QT6_LIB_DIR "${Qt6Core_DIR}/../.." ABSOLUTE)
|
||||
get_filename_component(QT6_ARCH_DIR "${QT6_LIB_DIR}" NAME)
|
||||
# The standard layout keeps headers in <prefix>/include; Debian/Ubuntu keeps
|
||||
# them in <prefix>/include/<multiarch>/qt6 — probe the multiarch form first.
|
||||
if(EXISTS "${QT6_LIB_DIR}/../../include/${QT6_ARCH_DIR}/qt6")
|
||||
get_filename_component(QT6_INC_DIR "${QT6_LIB_DIR}/../../include/${QT6_ARCH_DIR}/qt6" ABSOLUTE)
|
||||
else()
|
||||
get_filename_component(QT6_INC_DIR "${QT6_LIB_DIR}/../include" ABSOLUTE)
|
||||
endif()
|
||||
elseif(DEFINED ENV{QT_BASE_DIR} AND DEFINED ENV{QT_VERSION} AND DEFINED ENV{QT_ARCH_DIR})
|
||||
set(QT6_LIB_DIR "$ENV{QT_BASE_DIR}/$ENV{QT_VERSION}/$ENV{QT_ARCH_DIR}/lib")
|
||||
set(QT6_INC_DIR "$ENV{QT_BASE_DIR}/$ENV{QT_VERSION}/$ENV{QT_ARCH_DIR}/include")
|
||||
else()
|
||||
set(QT6_LIB_DIR "/usr/lib/x86_64-linux-gnu")
|
||||
set(QT6_INC_DIR "/usr/include/x86_64-linux-gnu/qt6")
|
||||
endif()
|
||||
|
||||
# Qt6::DBus
|
||||
if(NOT TARGET Qt6::DBus AND EXISTS "${QT6_LIB_DIR}/libQt6DBus.so.6")
|
||||
add_library(Qt6::DBus SHARED IMPORTED)
|
||||
set_target_properties(Qt6::DBus PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6DBus.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtDBus;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Qt6::Gui (needs OpenGL libs, X11, EGL)
|
||||
if(NOT TARGET Qt6::Gui AND EXISTS "${QT6_LIB_DIR}/libQt6Gui.so.6")
|
||||
add_library(Qt6::Gui SHARED IMPORTED)
|
||||
set_target_properties(Qt6::Gui PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6Gui.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtGui;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core;Qt6::DBus;${QT6_LIB_DIR}/libQt6OpenGL.so.6;${QT6_LIB_DIR}/libEGL.so.1;${QT6_LIB_DIR}/libGLX.so.0;${QT6_LIB_DIR}/libX11.so.6;${CMAKE_DL_LIBS};pthread"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Set variable for legacy code that uses ${Qt6_Gui} — like Qt6_Widgets below,
|
||||
# subdirectory CMakeLists contain `set(Qt6_Gui ${Qt6_Gui})` optional-guard
|
||||
# no-ops that keep this root-scope value.
|
||||
if(TARGET Qt6::Gui)
|
||||
set(Qt6_Gui Qt6::Gui)
|
||||
endif()
|
||||
|
||||
# Qt6::Widgets - create manual target (find_package fails without OpenGL)
|
||||
if(EXISTS "${QT6_LIB_DIR}/libQt6Widgets.so.6")
|
||||
if(NOT TARGET Qt6::Widgets)
|
||||
add_library(Qt6::Widgets SHARED IMPORTED)
|
||||
endif()
|
||||
set_target_properties(Qt6::Widgets PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6Widgets.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "/usr/include/x86_64-linux-gnu/qt6/QtWidgets;/usr/include/x86_64-linux-gnu/qt6"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core;Qt6::Gui"
|
||||
)
|
||||
# Set variable for legacy code that uses ${Qt6_Widgets}
|
||||
set(Qt6_Widgets Qt6::Widgets)
|
||||
set(QT6_GUI_AVAILABLE TRUE)
|
||||
message(STATUS "Manually configured Qt6 Widgets target")
|
||||
endif()
|
||||
|
||||
# Qt6::Sql - create manual target if not found
|
||||
if(NOT TARGET Qt6::Sql AND EXISTS "${QT6_LIB_DIR}/libQt6Sql.so.6")
|
||||
add_library(Qt6::Sql SHARED IMPORTED)
|
||||
set_target_properties(Qt6::Sql PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6Sql.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtSql;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core"
|
||||
)
|
||||
message(STATUS "Manually configured Qt6 Sql target")
|
||||
endif()
|
||||
|
||||
# Qt6::Xml - create manual target if not found
|
||||
if(NOT TARGET Qt6::Xml AND EXISTS "${QT6_LIB_DIR}/libQt6Xml.so.6")
|
||||
add_library(Qt6::Xml SHARED IMPORTED)
|
||||
set_target_properties(Qt6::Xml PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6Xml.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtXml;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core"
|
||||
)
|
||||
message(STATUS "Manually configured Qt6 Xml target")
|
||||
endif()
|
||||
|
||||
# Qt6::Network - create manual target if not found
|
||||
if(NOT TARGET Qt6::Network AND EXISTS "${QT6_LIB_DIR}/libQt6Network.so.6")
|
||||
add_library(Qt6::Network SHARED IMPORTED)
|
||||
set_target_properties(Qt6::Network PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6Network.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtNetwork;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core"
|
||||
)
|
||||
message(STATUS "Manually configured Qt6 Network target")
|
||||
endif()
|
||||
|
||||
# Qt6::Test - create manual target if not found
|
||||
if(NOT TARGET Qt6::Test AND EXISTS "${QT6_LIB_DIR}/libQt6Test.so.6")
|
||||
add_library(Qt6::Test SHARED IMPORTED)
|
||||
set_target_properties(Qt6::Test PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6Test.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtTest;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core"
|
||||
)
|
||||
message(STATUS "Manually configured Qt6 Test target")
|
||||
endif()
|
||||
|
||||
# Qt6::Multimedia - create manual target if not found
|
||||
if(NOT TARGET Qt6::Multimedia AND EXISTS "${QT6_LIB_DIR}/libQt6Multimedia.so.6")
|
||||
add_library(Qt6::Multimedia SHARED IMPORTED)
|
||||
set_target_properties(Qt6::Multimedia PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6Multimedia.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtMultimedia;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core;Qt6::Gui"
|
||||
)
|
||||
message(STATUS "Manually configured Qt6 Multimedia target")
|
||||
endif()
|
||||
|
||||
# Stub Qt6::Multimedia target if not available (needed by OscSystemGuiPlugin, etc.)
|
||||
if(NOT TARGET Qt6::Multimedia)
|
||||
add_library(Qt6::Multimedia INTERFACE IMPORTED)
|
||||
set_target_properties(Qt6::Multimedia PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "QT_NO_MULTIMEDIA"
|
||||
)
|
||||
message(STATUS "Created stub target for Qt6::Multimedia (library not available)")
|
||||
endif()
|
||||
|
||||
# Qt6::Svg - create manual target if not found. Also require the headers:
|
||||
# the container can ship the runtime library (libqt6svg6 via Recommends)
|
||||
# without qt6-svg-dev, and an imported target with a non-existent
|
||||
# INTERFACE_INCLUDE_DIRECTORIES entry fails the CMake generate step for
|
||||
# every Svg consumer. Without headers the stub below is used instead.
|
||||
if(NOT TARGET Qt6::Svg AND EXISTS "${QT6_LIB_DIR}/libQt6Svg.so.6" AND EXISTS "${QT6_INC_DIR}/QtSvg")
|
||||
add_library(Qt6::Svg SHARED IMPORTED)
|
||||
set_target_properties(Qt6::Svg PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6Svg.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtSvg;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core;Qt6::Gui"
|
||||
)
|
||||
message(STATUS "Manually configured Qt6 Svg target")
|
||||
endif()
|
||||
|
||||
# Stub Qt6::Svg target if not available (needed by various plugins)
|
||||
if(NOT TARGET Qt6::Svg)
|
||||
add_library(Qt6::Svg INTERFACE IMPORTED)
|
||||
message(STATUS "Created stub target for Qt6::Svg (library not available)")
|
||||
endif()
|
||||
|
||||
# Qt6::SerialPort - create manual target if not found
|
||||
if(NOT TARGET Qt6::SerialPort AND EXISTS "${QT6_LIB_DIR}/libQt6SerialPort.so.6")
|
||||
add_library(Qt6::SerialPort SHARED IMPORTED)
|
||||
set_target_properties(Qt6::SerialPort PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6SerialPort.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtSerialPort;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core"
|
||||
)
|
||||
message(STATUS "Manually configured Qt6 SerialPort target")
|
||||
endif()
|
||||
|
||||
# Stub Qt6::SerialPort target if not available (needed by TrainingAreaBaseOC)
|
||||
if(NOT TARGET Qt6::SerialPort)
|
||||
add_library(Qt6::SerialPort INTERFACE IMPORTED)
|
||||
set_target_properties(Qt6::SerialPort PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "QT_NO_SERIALPORT"
|
||||
)
|
||||
message(STATUS "Created stub target for Qt6::SerialPort (library not available)")
|
||||
endif()
|
||||
|
||||
# Stub Qt6::WebEngineCore target if not available (needed by thp_viewer)
|
||||
if(NOT TARGET Qt6::WebEngineCore)
|
||||
add_library(Qt6::WebEngineCore INTERFACE IMPORTED)
|
||||
set_target_properties(Qt6::WebEngineCore PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "QT_NO_WEBENGINE"
|
||||
)
|
||||
message(STATUS "Created stub target for Qt6::WebEngineCore (library not available)")
|
||||
endif()
|
||||
|
||||
# Stub Qt6::WebEngineWidgets target if not available (needed by thp_viewer)
|
||||
if(NOT TARGET Qt6::WebEngineWidgets)
|
||||
add_library(Qt6::WebEngineWidgets INTERFACE IMPORTED)
|
||||
set_target_properties(Qt6::WebEngineWidgets PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "QT_NO_WEBENGINE"
|
||||
)
|
||||
message(STATUS "Created stub target for Qt6::WebEngineWidgets (library not available)")
|
||||
endif()
|
||||
|
||||
# Stub Qt6::Core5Compat target if not available (needed by textEditorTest)
|
||||
if(NOT TARGET Qt6::Core5Compat)
|
||||
add_library(Qt6::Core5Compat INTERFACE IMPORTED)
|
||||
set_target_properties(Qt6::Core5Compat PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "QT_NO_CORE5COMPAT"
|
||||
)
|
||||
message(STATUS "Created stub target for Qt6::Core5Compat (library not available)")
|
||||
endif()
|
||||
|
||||
# Qt6::WebSockets - create manual target if not found (the container's Qt6
|
||||
# discovery takes the minimal path, so find_package never creates the
|
||||
# component targets). The migrated FileManagement service (THP script
|
||||
# server) needs the real module.
|
||||
if(NOT TARGET Qt6::WebSockets AND EXISTS "${QT6_LIB_DIR}/libQt6WebSockets.so.6")
|
||||
add_library(Qt6::WebSockets SHARED IMPORTED)
|
||||
set_target_properties(Qt6::WebSockets PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6WebSockets.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtWebSockets;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core;Qt6::Network"
|
||||
)
|
||||
set(QT6_WEBSOCKETS_REAL_FOUND TRUE)
|
||||
message(STATUS "Manually configured Qt6 WebSockets target")
|
||||
endif()
|
||||
|
||||
# Stub Qt6::WebSockets target if not available (needed by LtsWebSocket, httq, etc.)
|
||||
if(NOT TARGET Qt6::WebSockets)
|
||||
add_library(Qt6::WebSockets INTERFACE IMPORTED)
|
||||
set_target_properties(Qt6::WebSockets PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "QT_NO_WEBSOCKETS"
|
||||
)
|
||||
message(STATUS "Created stub target for Qt6::WebSockets (library not available)")
|
||||
endif()
|
||||
|
||||
# Qt6::PrintSupport - create manual target if not found
|
||||
if(NOT TARGET Qt6::PrintSupport AND EXISTS "${QT6_LIB_DIR}/libQt6PrintSupport.so.6")
|
||||
add_library(Qt6::PrintSupport SHARED IMPORTED)
|
||||
set_target_properties(Qt6::PrintSupport PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6PrintSupport.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtPrintSupport;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core;Qt6::Gui;Qt6::Widgets"
|
||||
)
|
||||
message(STATUS "Manually configured Qt6 PrintSupport target")
|
||||
endif()
|
||||
|
||||
# Stub Qt6::PrintSupport target if not available (needed by textEditorTest, thp_viewer)
|
||||
if(NOT TARGET Qt6::PrintSupport)
|
||||
add_library(Qt6::PrintSupport INTERFACE IMPORTED)
|
||||
set_target_properties(Qt6::PrintSupport PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "QT_NO_PRINTSUPPORT"
|
||||
)
|
||||
message(STATUS "Created stub target for Qt6::PrintSupport (library not available)")
|
||||
endif()
|
||||
|
||||
# Qt6::OpenGL - create manual target if not found
|
||||
if(NOT TARGET Qt6::OpenGL AND EXISTS "${QT6_LIB_DIR}/libQt6OpenGL.so.6")
|
||||
add_library(Qt6::OpenGL SHARED IMPORTED)
|
||||
set_target_properties(Qt6::OpenGL PROPERTIES
|
||||
IMPORTED_LOCATION "${QT6_LIB_DIR}/libQt6OpenGL.so.6"
|
||||
INTERFACE_INCLUDE_DIRECTORIES "${QT6_INC_DIR}/QtOpenGL;${QT6_INC_DIR}"
|
||||
INTERFACE_LINK_LIBRARIES "Qt6::Core;Qt6::Gui"
|
||||
)
|
||||
message(STATUS "Manually configured Qt6 OpenGL target")
|
||||
endif()
|
||||
|
||||
# Stub Qt6::OpenGL target if not available (needed by PwsGuiPlugin)
|
||||
if(NOT TARGET Qt6::OpenGL)
|
||||
add_library(Qt6::OpenGL INTERFACE IMPORTED)
|
||||
set_target_properties(Qt6::OpenGL PROPERTIES
|
||||
INTERFACE_COMPILE_DEFINITIONS "QT_NO_OPENGL"
|
||||
)
|
||||
message(STATUS "Created stub target for Qt6::OpenGL (library not available)")
|
||||
endif()
|
||||
|
||||
# Set variable for legacy code that uses ${Qt6_WebSockets} — subdirectory
|
||||
# CMakeLists contain `set(Qt6_WebSockets ${Qt6_WebSockets})` optional-guard
|
||||
# no-ops that keep this root-scope value.
|
||||
if(TARGET Qt6::WebSockets)
|
||||
set(Qt6_WebSockets Qt6::WebSockets)
|
||||
endif()
|
||||
|
||||
# The real Qt6 WebSockets module is available (either via find_package or the
|
||||
# manual import above): enable the real websocket code paths in httq,
|
||||
# LtsWebSocket and the migrated services (they check this macro; without it
|
||||
# they fall back to websocket-less operation).
|
||||
if(Qt6WebSockets_FOUND OR QT6_WEBSOCKETS_REAL_FOUND)
|
||||
add_compile_definitions(QT6_WEBSOCKETS_AVAILABLE)
|
||||
endif()
|
||||
|
||||
MESSAGE(STATUS "Qt version is ${Qt6Core_VERSION}, GUI available: ${QT6_GUI_AVAILABLE}")
|
||||
|
||||
#
|
||||
ADD_DEFINITIONS(-DQT_USE_QSTRINGBUILDER)
|
||||
ADD_DEFINITIONS(-DQT_NO_CAST_TO_ASCII)
|
||||
ADD_DEFINITIONS(-DQT_NO_CAST_FROM_ASCII)
|
||||
ADD_DEFINITIONS(-DQT_NO_URL_CAST_FROM_STRING)
|
||||
ADD_DEFINITIONS(-DQT_DEPRECATED_WARNINGS)
|
||||
ADD_DEFINITIONS(-DQT_NO_CAST_FROM_BYTEARRAY)
|
||||
#ADD_DEFINITIONS(-DQT_NO_DEPRECATED)
|
||||
ADD_DEFINITIONS(-DQT_DISABLE_DEPRECATED_BEFORE=0x050900)
|
||||
ADD_DEFINITIONS(-DQT_NO_FOREACH)
|
||||
IF(NOT WIN32)
|
||||
ADD_DEFINITIONS(-DQT_STRICT_ITERATORS)
|
||||
ENDIF()
|
||||
|
||||
#
|
||||
# Ein paar NGL-spezifische Macros
|
||||
#
|
||||
INCLUDE(NGLMacros)
|
||||
|
||||
#
|
||||
# Projektspezifische Einstellungen?
|
||||
#
|
||||
NGL_INSTALL(SV AAR WS FILES lts-cc.conf DESTINATION . RENAME lts-cc.conf.distrib)
|
||||
|
||||
OPTION(GUEZ_MODE "Build for GUEZ " ON)
|
||||
IF(GUEZ_MODE)
|
||||
ADD_DEFINITIONS(-DGUEZ_MODE)
|
||||
ENDIF()
|
||||
|
||||
#
|
||||
# Doxygen
|
||||
#
|
||||
INCLUDE(DoxygenMacros)
|
||||
IF(NOT DOXYGEN_OUT_DIR)
|
||||
SET(DOXYGEN_OUT_DIR ${CMAKE_BINARY_DIR}/doxygen)
|
||||
ENDIF()
|
||||
ADD_DOXYGEN_TARGET(TDS ${CMAKE_SOURCE_DIR}/doxygen/Doxyfile.in ${CMAKE_BINARY_DIR}/Doxyfile
|
||||
${CMAKE_SOURCE_DIR}/doxygen/rheinmetall_defence_logo.jpg
|
||||
${DOXYGEN_OUT_DIR})
|
||||
|
||||
IF(NOT DOXYGEN_OUT_DIR_2025)
|
||||
SET(DOXYGEN_OUT_DIR_2025 ${CMAKE_BINARY_DIR}/doxygen2025)
|
||||
ENDIF()
|
||||
ADD_DOXYGEN_TARGET(TDS2025 ${CMAKE_SOURCE_DIR}/doxygen/Doxyfile2025.in ${CMAKE_BINARY_DIR}/Doxyfile
|
||||
${CMAKE_SOURCE_DIR}/doxygen/rheinmetall_defence_logo.jpg
|
||||
${DOXYGEN_OUT_DIR_2025})
|
||||
|
||||
#
|
||||
# Compilerflags
|
||||
#
|
||||
IF(WIN32)
|
||||
ADD_DEFINITIONS(-DWIN32 -D_USE_MATH_DEFINES -D_WIN32_WINNT=0x0501 -DUNICODE)
|
||||
ELSE()
|
||||
ADD_DEFINITIONS(-D_LINUX)
|
||||
ENDIF()
|
||||
|
||||
IF(MSVC)
|
||||
# C4250: 'class1' : inherits 'class2::member' via dominance
|
||||
# C4251: 'identifier' : class 'type' needs to have dll-interface to be used by clients of class 'type2'
|
||||
# C4290: C++ exception specification ignored except to indicate a function is not __declspec(nothrow)
|
||||
# C4496: 'function': was declared deprecated
|
||||
# C4267: 'var' : conversion from 'size_t' to 'type', possible loss of data
|
||||
ADD_DEFINITIONS(-wd4250
|
||||
-wd4251
|
||||
-wd4290
|
||||
-wd4496
|
||||
-wd4267
|
||||
-D_CRT_SECURE_NO_DEPRECATE
|
||||
-D_CRT_NONSTDC_NO_DEPRECATE
|
||||
-D_SCL_SECURE_NO_WARNINGS
|
||||
-D_WINSOCK_DEPRECATED_NO_WARNINGS
|
||||
/bigobj
|
||||
)
|
||||
# komplette Programmoptimierung
|
||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} /MP")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
|
||||
# .pdb - Datei nicht gefunden
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /IGNORE:4099")
|
||||
SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /IGNORE:4099")
|
||||
# min() und max() nicht als Macros
|
||||
ADD_DEFINITIONS(-DNOMINMAX)
|
||||
SET(CMAKE_MSVCIDE_RUN_PATH "" CACHE PATH "Run path to use when starting programs from MSVC")
|
||||
ENDIF()
|
||||
|
||||
IF(UNIX)
|
||||
# Alle Warnungen aktivieren
|
||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wall")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wextra -Wmissing-include-dirs")
|
||||
# disable
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-overloaded-virtual")
|
||||
IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
|
||||
IF(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.8.0)
|
||||
MESSAGE(FATAL_ERROR "gcc 4.8 or higher is required to build TDS: ${CMAKE_CXX_COMPILER_VERSION}")
|
||||
ENDIF()
|
||||
IF(CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.9.99)
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-strict-aliasing")
|
||||
# SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-attribute=const")
|
||||
# SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-final-types")
|
||||
# SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-final-methods")
|
||||
# SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
OPTION(USE_IPO "Use interprocedural optimizations (cmake 3.9 or higher required)" OFF)
|
||||
IF (USE_IPO AND CMAKE_VERSION VERSION_GREATER 3.8.9)
|
||||
INCLUDE(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT result OUTPUT output)
|
||||
IF(result)
|
||||
SET(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
ELSE()
|
||||
MESSAGE(WARNING "IPO is not supported: ${output}")
|
||||
ENDIF()
|
||||
ENDIF()
|
||||
|
||||
OPTION(USE_ASAN "Use Address Sanitizer (current Clang only)" OFF)
|
||||
IF (USE_ASAN)
|
||||
IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
MESSAGE(STATUS "enable Address Sanitizer with Clang (is: ${CMAKE_CXX_COMPILER_ID})")
|
||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer -g -O1")
|
||||
ELSE()
|
||||
MESSAGE(STATUS "Address Sanitizer only supported with Clang (is: ${CMAKE_CXX_COMPILER_ID})")
|
||||
ENDIF()
|
||||
ENDIF(USE_ASAN)
|
||||
|
||||
OPTION(USE_UBSAN "Use UndefinedBehavior Sanitizer (current Clang only)" OFF)
|
||||
IF (USE_UBSAN)
|
||||
IF("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
SET(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=undefined")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined")
|
||||
ELSE()
|
||||
MESSAGE(STATUS "UndefinedBehavior only supported with Clang (is: ${CMAKE_CXX_COMPILER_ID})")
|
||||
ENDIF()
|
||||
ENDIF(USE_UBSAN)
|
||||
|
||||
OPTION(BUILD_RPM "Build a RPM - install script in scripts subdir" OFF)
|
||||
|
||||
# Build component options for NGL_ADD_LIBRARY
|
||||
OPTION(BUILD_ALL "Build all components" ON)
|
||||
OPTION(BUILD_SV "Build SV component" ON)
|
||||
OPTION(BUILD_WS "Build WS component" ON)
|
||||
OPTION(BUILD_AAR "Build AAR component" ON)
|
||||
OPTION(BUILD_TOOLSWS "Build TOOLSWS component" ON)
|
||||
OPTION(BUILD_TOOLSSV "Build TOOLSSV component" ON)
|
||||
OPTION(BUILD_HLA "Build HLA component" ON)
|
||||
|
||||
# If BUILD_ALL is ON, enable all individual component flags
|
||||
IF(BUILD_ALL)
|
||||
SET(BUILD_SV ON CACHE BOOL "Build SV component" FORCE)
|
||||
SET(BUILD_WS ON CACHE BOOL "Build WS component" FORCE)
|
||||
SET(BUILD_AAR ON CACHE BOOL "Build AAR component" FORCE)
|
||||
SET(BUILD_TOOLSWS ON CACHE BOOL "Build TOOLSWS component" FORCE)
|
||||
SET(BUILD_TOOLSSV ON CACHE BOOL "Build TOOLSSV component" FORCE)
|
||||
SET(BUILD_HLA ON CACHE BOOL "Build HLA component" FORCE)
|
||||
ENDIF()
|
||||
|
||||
IF(BUILD_RPM)
|
||||
ADD_SUBDIRECTORY(scripts)
|
||||
ELSE()
|
||||
ADD_DEFINITIONS(-DLTS_DEBUG)
|
||||
ENDIF()
|
||||
|
||||
IF (NOT DEFINED _pname)
|
||||
IF(GUEZ_MODE)
|
||||
SET (_pname "guez")
|
||||
ADD_DEFINITIONS(-DPROJECTBUILD="guez")
|
||||
MESSAGE(STATUS "NO ProjectName givin! BUT GUEZ_MODE set PROJECTBUILD to guez")
|
||||
ELSE()
|
||||
MESSAGE(WARNING "NO ProjectName givin! Can't install coresponding lts-cc.conf! and PROJECTBUILD is empty")
|
||||
ADD_DEFINITIONS(-DPROJECTBUILD="")
|
||||
ENDIF()
|
||||
ELSE()
|
||||
MESSAGE(STATUS "ProjectName givin! PROJECTBUILD is ${_pname} ")
|
||||
ADD_DEFINITIONS(-DPROJECTBUILD=\"${_pname}\")
|
||||
ENDIF()
|
||||
|
||||
|
||||
# Release scope: always build only TetraIF, Z/src, and Lts/Common
|
||||
# (TrainingAreaBase is included via Lts/Proxies when Lts is processed)
|
||||
NGL_ADD_SUBDIR_GUARDED(TetraIF)
|
||||
NGL_ADD_SUBDIR_GUARDED(Z/src)
|
||||
NGL_ADD_SUBDIR_GUARDED(Lts)
|
||||
|
||||
# at last, to make sure we get all targets in NGL_TS_TARGETS property
|
||||
NGL_ADD_SUBDIR_GUARDED(translations)
|
||||
#-----------------------------------------------------------------------------------------------------
|
||||
@@ -0,0 +1,2 @@
|
||||
# Minimal CTestCustom.ctest placeholder
|
||||
# Created to satisfy top-level CMake configure step
|
||||
@@ -0,0 +1,47 @@
|
||||
# Prebuilt build image for the release validation-scope branch
|
||||
# (Ubuntu 26.04 + Qt6 + all dependencies for the legatus release build).
|
||||
#
|
||||
# Built by new_docker_build.sh --build-prebuilt-image, which passes the
|
||||
# package list and Qt settings as build args (see build_prebuilt_image()).
|
||||
|
||||
FROM ubuntu:26.04
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
ARG APT_PACKAGES
|
||||
ARG QT_VERSION=6.10.2
|
||||
ARG QT_ARCH=linux_gcc_64
|
||||
ARG QT_BASE_DIR=/opt/Qt
|
||||
|
||||
RUN apt-get update -qq && \
|
||||
apt-get install -y -qq \
|
||||
${APT_PACKAGES} \
|
||||
libqt6svg6-dev \
|
||||
libgtest-dev \
|
||||
libgmock-dev \
|
||||
python3-pip && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install Qt6 packages via aqtinstall
|
||||
RUN pip3 install --break-system-packages --quiet aqtinstall && \
|
||||
aqt install-qt linux desktop ${QT_VERSION} ${QT_ARCH} \
|
||||
-m qtmultimedia qtserialport qtimageformats qtwebsockets qtwebengine \
|
||||
qtpdf qtwebview qt5compat qtwebchannel qtpositioning qtlocation qtsensors \
|
||||
--outputdir ${QT_BASE_DIR} && \
|
||||
rm -rf /root/.cache/Aqt
|
||||
|
||||
# Install KDReports from source (apt package not available in Ubuntu 22.04/26.04)
|
||||
# Try apt install first (will fail), then build from KDAB GitHub mirror
|
||||
RUN apt-get install -y -qq libkdreports-dev 2>/dev/null || \
|
||||
(git clone --depth 1 https://github.com/KDAB/KDReports.git /tmp/KDReports && \
|
||||
cd /tmp/KDReports && \
|
||||
mkdir build && cd build && \
|
||||
cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DCMAKE_PREFIX_PATH=${QT_BASE_DIR} .. && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
cd / && rm -rf /tmp/KDReports)
|
||||
|
||||
# Legacy sources include <KDReports/...>
|
||||
RUN ln -sfn KDReports-qt6 /usr/local/include/KDReports
|
||||
|
||||
CMD ["/bin/bash"]
|
||||
@@ -0,0 +1,35 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(LtsPlaceholder)
|
||||
SET(DOXYGEN_OUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxygen_Lts)
|
||||
if (EXISTS "${CMAKE_SOURCE_DIR}/doxygen/DoxyfileLts.in")
|
||||
ADD_DOXYGEN_TARGET(Lts ${CMAKE_SOURCE_DIR}/doxygen/DoxyfileLts.in ${CMAKE_BINARY_DIR}/DoxyfileLts
|
||||
${CMAKE_SOURCE_DIR}/doxygen/rheinmetall_defence_logo.jpg
|
||||
${DOXYGEN_OUT_DIR})
|
||||
else()
|
||||
message(WARNING "Doxygen config ${CMAKE_SOURCE_DIR}/doxygen/DoxyfileLts.in not found; skipping ADD_DOXYGEN_TARGET(Lts)")
|
||||
endif()
|
||||
|
||||
# Release scope: only build Lts/Common and Lts/Proxies/TrainingAreaBase (tabTelegrams library)
|
||||
# Excludes: Tools, Application, DDS, Services, most of Proxies, Plugins, Viewer, Projects
|
||||
message(STATUS "Building Lts/Common and Lts/Proxies/TrainingAreaBase")
|
||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Common")
|
||||
NGL_ADD_SUBDIR_GUARDED(Common)
|
||||
else()
|
||||
message(STATUS "Skipping missing Lts subdirectory: Common")
|
||||
endif()
|
||||
# TrainingAreaBase provides tabTelegrams library needed by TetraIF/CoPrtCnv
|
||||
if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Proxies/TrainingAreaBase")
|
||||
NGL_ADD_SUBDIR_GUARDED(Proxies/TrainingAreaBase)
|
||||
else()
|
||||
message(STATUS "Skipping missing Lts subdirectory: Proxies/TrainingAreaBase")
|
||||
endif()
|
||||
|
||||
# Export headers included by bare name from module sources (release scope)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/NGLLogger/)
|
||||
INCLUDE_DIRECTORIES(${CMAKE_CURRENT_SOURCE_DIR}/../include/LtsUtilsGuiDds/)
|
||||
@@ -0,0 +1,25 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
|
||||
# Core modules needed for TetraIF and Z
|
||||
ADD_SUBDIRECTORY(CommonUnitTestsPlaceholder)
|
||||
ADD_SUBDIRECTORY(httq)
|
||||
ADD_SUBDIRECTORY(LtsAppDescriptor)
|
||||
ADD_SUBDIRECTORY(LtsCommandLineParser)
|
||||
ADD_SUBDIRECTORY(LtsCoordTrans)
|
||||
ADD_SUBDIRECTORY(LtsLicence)
|
||||
ADD_SUBDIRECTORY(LtsNotification)
|
||||
ADD_SUBDIRECTORY(LtsRestClient)
|
||||
ADD_SUBDIRECTORY(LtsSerialPortQuerry)
|
||||
# Excluded: LtsTimeline (depends on LtsWidgets which has DDS dependencies)
|
||||
ADD_SUBDIRECTORY(LtsUtils)
|
||||
ADD_SUBDIRECTORY(LtsWebSocket)
|
||||
ADD_SUBDIRECTORY(PluginInterfaces)
|
||||
ADD_SUBDIRECTORY(ProjectConfig)
|
||||
# Excluded: CodeGen, LtsAppManager, LtsBotan, LtsCoordinate, LtsElevation, LtsLogin,
|
||||
# LtsMultiMonitor, LtsPluginManager, LtsPrinting, LtsProjectionWorkstation,
|
||||
# LtsScriptTimeline, LtsSession, LtsTimelineClassic, LtsVirtualKeyboard, LtsWidgets,
|
||||
# TopicEditorBase
|
||||
@@ -0,0 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(CommonUnitTestsPlaceholder)
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
option(BUILD_SMOKE_TESTS "Build QtTest smoke tests" OFF)
|
||||
option(BUILD_GTEST_SMOKE "Build GTest smoke tests" OFF)
|
||||
|
||||
add_subdirectory(mocks)
|
||||
|
||||
if(BUILD_SMOKE_TESTS)
|
||||
if(NOT TARGET Qt6::Test)
|
||||
find_package(Qt6 REQUIRED COMPONENTS Test)
|
||||
endif()
|
||||
add_executable(coordTransTests_smoke coordTransTests_smoke.cpp)
|
||||
target_link_libraries(coordTransTests_smoke PRIVATE Qt6::Test MockCoordTrans::MockCoordTrans)
|
||||
add_test(NAME coordTransTests_smoke COMMAND coordTransTests_smoke)
|
||||
endif()
|
||||
|
||||
if(BUILD_GTEST_SMOKE)
|
||||
find_package(GTest QUIET)
|
||||
if(NOT GTest_FOUND)
|
||||
FetchContent_Declare(
|
||||
googletest
|
||||
GIT_REPOSITORY https://github.com/google/googletest.git
|
||||
GIT_TAG release-1.12.1
|
||||
)
|
||||
set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
|
||||
FetchContent_MakeAvailable(googletest)
|
||||
endif()
|
||||
add_executable(coordTransTests_gtest coordTransTests_gtest.cpp)
|
||||
target_link_libraries(coordTransTests_gtest PRIVATE GTest::gtest_main MockCoordTrans::MockCoordTrans)
|
||||
add_test(NAME coordTransTests_gtest COMMAND coordTransTests_gtest)
|
||||
endif()
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
TEST(CoordTransSmokeTest, StubTest)
|
||||
{
|
||||
EXPECT_TRUE(true);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
|
||||
#include <QTest>
|
||||
|
||||
class CoordTransSmokeTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void stubTest() { QVERIFY(true); }
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(CoordTransSmokeTest)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
add_library(MockCoordTrans STATIC MockCoordTrans.h)
|
||||
target_include_directories(MockCoordTrans PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
|
||||
# Export header-only semantics by providing an interface target
|
||||
add_library(MockCoordTrans::MockCoordTrans ALIAS MockCoordTrans)
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
|
||||
#ifndef MOCK_COORD_TRANS_H
|
||||
#define MOCK_COORD_TRANS_H
|
||||
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QPointF>
|
||||
|
||||
// Minimal stub to satisfy linker requirements
|
||||
namespace LtsCoordTrans
|
||||
{
|
||||
// Placeholder types
|
||||
struct UtmCoordinate
|
||||
{
|
||||
static UtmCoordinate fromMgrs(const QString &) { return UtmCoordinate(); }
|
||||
QPointF geographic() const { return QPointF(); }
|
||||
};
|
||||
}
|
||||
|
||||
#endif // MOCK_COORD_TRANS_H
|
||||
@@ -0,0 +1,65 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
# Created by Mike Wille, SLSE, 2016-10-24
|
||||
#
|
||||
|
||||
|
||||
# LtsAppDescriptor (lib)
|
||||
# Make ${Qt6_Widgets} optional
|
||||
if(QT6_GUI_AVAILABLE AND TARGET Qt6::Widgets)
|
||||
set(Qt6_Widgets ${Qt6_Widgets})
|
||||
else()
|
||||
set(Qt6_Widgets "")
|
||||
endif()
|
||||
|
||||
|
||||
SET(LtsAppDescriptor_SRCS
|
||||
lts_app_descriptor.cpp
|
||||
lts_app_descriptor.h
|
||||
)
|
||||
|
||||
NGL_ADD_LIBRARY2(WS LtsAppDescriptor SHARED ${LtsAppDescriptor_SRCS})
|
||||
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsAppDescriptor
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
)
|
||||
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptor
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
)
|
||||
|
||||
NGL_INSTALL_TARGET(LtsAppDescriptor)
|
||||
|
||||
# LtsAppDescriptorChecker
|
||||
SET(LtsAppDescriptorChecker_SRCS
|
||||
checker/main.cpp
|
||||
)
|
||||
|
||||
NGL_ADD_EXECUTABLE(WS LtsAppDescriptorChecker ${LtsAppDescriptorChecker_SRCS})
|
||||
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorChecker
|
||||
PRIVATE
|
||||
LtsAppDescriptor
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
)
|
||||
|
||||
if(QT6_GUI_AVAILABLE)
|
||||
#NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorEditor
|
||||
# PUBLIC
|
||||
# Qt6::Core
|
||||
# ${Qt6_Widgets}
|
||||
# LtsAppDescriptor
|
||||
#)
|
||||
endif()
|
||||
|
||||
#NGL_INSTALL_TARGET(LtsAppDescriptorEditor) #not visible for end-user
|
||||
|
||||
## Executable:
|
||||
#NGL_ADD_EXECUTABLE(WS NO_TRANSLATION LtsAppDescriptorEditorApp main.cpp)
|
||||
#NGL_TARGET_LINK_LIBRARIES(LtsAppDescriptorEditorApp LtsAppDescriptorEditor)
|
||||
@@ -0,0 +1,116 @@
|
||||
#include <LtsAppDescriptor/lts_app_descriptor.h>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QDirIterator>
|
||||
#include <QtCore/QFile>
|
||||
#include <QFileInfoList>
|
||||
#include <QtCore/QRegularExpression>
|
||||
#include <QtCore/QRegularExpressionMatch>
|
||||
#include <QtCore/QTextStream>
|
||||
#include <cstdio>
|
||||
#include <utility>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication a(argc, argv);
|
||||
|
||||
const QStringList args(a.arguments());
|
||||
|
||||
if (args.count() != 3)
|
||||
{
|
||||
printf("usage: %s <current source directory> <apps.json>\n", args.at(0).toStdString().c_str());
|
||||
return -1;
|
||||
}
|
||||
|
||||
const QString currentSrcDir(a.arguments().at(1));
|
||||
|
||||
const QString appsJsonFilePath(QStringLiteral("%1/%2").arg(currentSrcDir, a.arguments().at(2)));
|
||||
QFile f(appsJsonFilePath);
|
||||
|
||||
if (!f.open(QFile::ReadOnly))
|
||||
{
|
||||
printf("LtsAppDescriptorChecker: cannot open apps.json at %s: %s\n", appsJsonFilePath.toStdString().c_str(), f.errorString().toStdString().c_str());
|
||||
return -2;
|
||||
}
|
||||
|
||||
printf("LtsAppDescriptorChecker: checking %s\n", appsJsonFilePath.toStdString().c_str());
|
||||
|
||||
QStringList lastErrors;
|
||||
QStringList dummy1;
|
||||
QString dummy2;
|
||||
LtsAppDescriptor::AppDescriptionVector adv = LtsAppDescriptor::LtsAppDescriptor::getAppDescriptionVector(f.readAll(), lastErrors, dummy1, dummy2);
|
||||
|
||||
if (!lastErrors.isEmpty())
|
||||
{
|
||||
printf("LtsAppDescriptorChecker: apps.json at %s invalid:\n%s\n",
|
||||
appsJsonFilePath.toStdString().c_str(),
|
||||
lastErrors.join(QStringLiteral("\r\n\r\n")).toStdString().c_str());
|
||||
return 1;
|
||||
}
|
||||
|
||||
QFileInfoList guiPluginFiles;
|
||||
QDirIterator it(QStringLiteral("%1/../../Plugins/").arg(currentSrcDir), QDirIterator::Subdirectories);
|
||||
|
||||
while (it.hasNext())
|
||||
{
|
||||
QString filename(it.next());
|
||||
QFileInfo file(filename);
|
||||
|
||||
if (file.isDir())
|
||||
continue;
|
||||
|
||||
if (file.fileName().endsWith(QStringLiteral("_gui_plugin.h"), Qt::CaseInsensitive))
|
||||
guiPluginFiles.append(file);
|
||||
}
|
||||
|
||||
QStringList filenames;
|
||||
filenames.reserve(guiPluginFiles.size());
|
||||
for (const QFileInfo &fi : std::as_const(guiPluginFiles))
|
||||
{
|
||||
filenames += fi.absoluteFilePath();
|
||||
}
|
||||
|
||||
QStringList guiPluginIIDs;
|
||||
for (const QFileInfo &fi : std::as_const(guiPluginFiles))
|
||||
{
|
||||
QFile f2(fi.absoluteFilePath());
|
||||
|
||||
if (!f2.open(QFile::ReadOnly))
|
||||
continue;
|
||||
|
||||
QTextStream ts(&f2);
|
||||
while (!ts.atEnd())
|
||||
{
|
||||
QString line(ts.readLine().trimmed().simplified());
|
||||
static QString iidIdentifier(QStringLiteral("Q_PLUGIN_METADATA"));
|
||||
|
||||
if (line.contains(iidIdentifier))
|
||||
{
|
||||
QRegularExpression re(QStringLiteral("(?<=gui:3:).*(?=\"\\))"));
|
||||
QRegularExpressionMatch match = re.match(line);
|
||||
if (match.hasMatch())
|
||||
{
|
||||
QString matched = match.captured(0);
|
||||
|
||||
guiPluginIIDs += matched;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool ok = true;
|
||||
|
||||
for (const LtsAppDescriptor::AppDescription &appdesc : std::as_const(adv))
|
||||
{
|
||||
for (const LtsAppDescriptor::PluginInfo &pi : std::as_const(appdesc.pluginInfos))
|
||||
{
|
||||
if (!guiPluginIIDs.contains(pi.iid))
|
||||
{
|
||||
printf("LtsAppDescriptorChecker: apps.json at '%s' uses unknown IID '%s'\n", appsJsonFilePath.toStdString().c_str(), pi.iid.toStdString().c_str());
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !ok;
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
|
||||
|
||||
#include "lts_app_descriptor.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QtCore/QJsonValue>
|
||||
|
||||
namespace LtsAppDescriptor
|
||||
{
|
||||
static void addOptionToPlugin(PluginInfo &pluginInfo, const PluginInfo::Flag &flag)
|
||||
{
|
||||
pluginInfo.flags |= flag;
|
||||
}
|
||||
|
||||
static void addOptionToPlugin(QVector<PluginInfo> &pluginInfos, const PluginInfo::Flag &flag, const QString &pluginName)
|
||||
{
|
||||
for (PluginInfo &pi : pluginInfos)
|
||||
{
|
||||
if (pi.iid == pluginName)
|
||||
{
|
||||
addOptionToPlugin(pi, flag);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PluginInfo pi;
|
||||
pi.iid = pluginName;
|
||||
pi.type = PluginInfo::PluginType::GUI;
|
||||
addOptionToPlugin(pi, flag);
|
||||
pluginInfos.append(pi);
|
||||
}
|
||||
|
||||
static bool addOptionsToPlugin(AppDescription &appDesc,
|
||||
const QString &optionIdentifier,
|
||||
const QStringList &ignoredPlugins,
|
||||
const QJsonObject &optionsObject,
|
||||
const PluginInfo::Flag flag,
|
||||
QStringList &lastErrors,
|
||||
bool atLeastOnePluginNeeded)
|
||||
{
|
||||
bool hasPlugins = false;
|
||||
auto optionsObjectIt = optionsObject.constFind(optionIdentifier);
|
||||
if (optionsObjectIt != optionsObject.constEnd())
|
||||
{
|
||||
const QJsonArray plugins((*optionsObjectIt).toObject()[QStringLiteral("Plugins")].toArray());
|
||||
|
||||
for (const auto &pluginElem : plugins)
|
||||
{
|
||||
const QString pluginName = pluginElem.toString();
|
||||
if (!ignoredPlugins.contains(pluginName))
|
||||
{
|
||||
addOptionToPlugin(appDesc.pluginInfos, flag, pluginName);
|
||||
hasPlugins = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (atLeastOnePluginNeeded && !hasPlugins)
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("No plugins specified for '%1' option for app '%2'").arg(optionIdentifier, appDesc.appName));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
App LtsAppDescriptor::appFromInteger(int i)
|
||||
{
|
||||
switch (i)
|
||||
{
|
||||
case int(App::Login):
|
||||
case int(App::Situation):
|
||||
case int(App::ResourceManager):
|
||||
case int(App::PlanEditor):
|
||||
case int(App::BuildingViewer):
|
||||
case int(App::OC):
|
||||
case int(App::OrgChart):
|
||||
case int(App::Scripts):
|
||||
|
||||
case int(App::Snapshot):
|
||||
case int(App::SystemMonitor):
|
||||
case int(App::Timeline):
|
||||
case int(App::SymbolEditor):
|
||||
case int(App::ScriptEditorEssentialsView):
|
||||
case int(App::BaseDownloader):
|
||||
case int(App::OnlineHelp):
|
||||
case int(App::ThpSituationReplay):
|
||||
case int(App::SituationTablet):
|
||||
case int(App::SilhouetteEditor):
|
||||
case int(App::Shootback):
|
||||
case int(App::SAAs):
|
||||
return static_cast<App>(i);
|
||||
}
|
||||
Q_ASSERT_X(false, "LtsAppDescriptor::appFromInteger", qPrintable(QStringLiteral("Unknown app id: %1").arg(i)));
|
||||
return App::Login;
|
||||
}
|
||||
|
||||
AppDescriptionVector LtsAppDescriptor::getAppDescriptionVector(const QByteArray &jsonDocument,
|
||||
QStringList &lastErrors,
|
||||
const QStringList &ignoredPlugins,
|
||||
QString &loginPluginIid)
|
||||
{
|
||||
|
||||
AppDescriptionVector appDescriptions;
|
||||
|
||||
QJsonParseError parseError;
|
||||
QJsonDocument appsDoc(QJsonDocument::fromJson(jsonDocument, &parseError));
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
{
|
||||
|
||||
int row = 0;
|
||||
int column = 0;
|
||||
QChar x = QLatin1Char('\n');
|
||||
|
||||
for (int i = 0; i < jsonDocument.size(); i++)
|
||||
{
|
||||
if (i == parseError.offset)
|
||||
break;
|
||||
|
||||
if (QChar::fromLatin1(jsonDocument[i]) == x)
|
||||
{
|
||||
row++;
|
||||
column = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
column++;
|
||||
}
|
||||
}
|
||||
|
||||
lastErrors.append(QCoreApplication::tr("JSON: %1<br>Row: %2, Column: %3").arg(parseError.errorString()).arg(row + 1).arg(column + 1));
|
||||
return appDescriptions;
|
||||
}
|
||||
|
||||
const QJsonObject root(appsDoc.object());
|
||||
|
||||
loginPluginIid = root[QStringLiteral("LoginPlugin")].toString();
|
||||
|
||||
const QJsonArray appArray(root[QStringLiteral("Apps")].toArray());
|
||||
if (appArray.isEmpty())
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("Invalid json: Could not find 'Apps' array"));
|
||||
return appDescriptions;
|
||||
}
|
||||
|
||||
for (const auto &appElem : appArray)
|
||||
{
|
||||
AppDescription appDesc;
|
||||
const QJsonObject appObject = appElem.toObject();
|
||||
|
||||
appDesc.id = appFromInteger(appObject.value(QStringLiteral("Id")).toInt(-1));
|
||||
if (appDesc.id == App::Login)
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("App '%1': no App Id or invalid App Id given").arg(appDesc.appName));
|
||||
return appDescriptions;
|
||||
}
|
||||
|
||||
appDesc.appName = appObject.value(QStringLiteral("AppName")).toString();
|
||||
|
||||
const QJsonValue valueActive = appObject.value(QStringLiteral("Active"));
|
||||
if (!valueActive.isUndefined())
|
||||
{
|
||||
if (valueActive.type() != QJsonValue::Type::Bool)
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("App '%1': value App.Active is not a bool (%2)").arg(appDesc.appName).arg((int)valueActive.type()));
|
||||
return appDescriptions;
|
||||
}
|
||||
}
|
||||
appDesc.active = valueActive.toBool(false);
|
||||
|
||||
const QJsonValue valueVisible = appObject.value(QStringLiteral("Visible"));
|
||||
if (!valueVisible.isUndefined() && valueVisible.type() != QJsonValue::Type::Bool)
|
||||
{
|
||||
lastErrors.append(QCoreApplication::tr("App '%1': value App.Visible is not a bool (%2)").arg(appDesc.appName).arg((int)valueVisible.type()));
|
||||
return appDescriptions;
|
||||
}
|
||||
appDesc.visible = valueVisible.toBool(true);
|
||||
|
||||
const QJsonArray array(appObject.value(QStringLiteral("ACLs")).toArray());
|
||||
for (const QJsonValue &value : array)
|
||||
appDesc.requiredAcls.append(value.toInt());
|
||||
|
||||
const QJsonObject optionsObject(appObject.value(QStringLiteral("Options")).toObject());
|
||||
|
||||
addOptionsToPlugin(appDesc, QStringLiteral("Payloads"), ignoredPlugins, optionsObject, PluginInfo::Flag::PAYLOAD, lastErrors, false);
|
||||
addOptionsToPlugin(appDesc, QStringLiteral("Actions"), ignoredPlugins, optionsObject, PluginInfo::Flag::ACTIONS, lastErrors, true);
|
||||
addOptionsToPlugin(appDesc, QStringLiteral("Main"), ignoredPlugins, optionsObject, PluginInfo::Flag::MAIN, lastErrors, false);
|
||||
|
||||
appDescriptions.append(appDesc);
|
||||
}
|
||||
|
||||
return appDescriptions;
|
||||
}
|
||||
|
||||
} // namespace LtsAppDescriptor
|
||||
@@ -0,0 +1,106 @@
|
||||
/**
|
||||
* @RDE_HEADER@
|
||||
* App Descriptor - Class to modify App Descriptor JSON Documents
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*
|
||||
* Created by Mike Wille, SLSE, 2016-10-24
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVector>
|
||||
#include <QStringList>
|
||||
|
||||
#ifndef LTSAPPDESCRIPTOR_EXPORT
|
||||
#ifdef BUILD_STATIC_LIBRARIES
|
||||
#define LTSAPPDESCRIPTOR_EXPORT
|
||||
#else
|
||||
#if defined(MAKE_LTSAPPDESCRIPTOR_LIB)
|
||||
#define LTSAPPDESCRIPTOR_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define LTSAPPDESCRIPTOR_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
namespace LtsAppDescriptor
|
||||
{
|
||||
|
||||
enum class App
|
||||
{
|
||||
Login = -1,
|
||||
Situation = 0,
|
||||
ResourceManager = 1,
|
||||
PlanEditor = 2,
|
||||
BuildingViewer = 3,
|
||||
OC = 4,
|
||||
OrgChart = 5,
|
||||
Scripts = 6,
|
||||
// merged into Scripts (aka. ScriptEditor) ScriptPlayer = 7,
|
||||
Snapshot = 8,
|
||||
SystemMonitor = 9,
|
||||
Timeline = 10,
|
||||
SymbolEditor = 11,
|
||||
ScriptEditorEssentialsView = 12,
|
||||
BaseDownloader = 13,
|
||||
OnlineHelp = 14,
|
||||
ThpSituationReplay = 15,
|
||||
SituationTablet = 17,
|
||||
SilhouetteEditor = 18, // for L400P2
|
||||
Shootback = 19, // for L400P2
|
||||
SAAs = 20, //Schnittstelle Adapter Adler
|
||||
};
|
||||
|
||||
struct PluginInfo
|
||||
{
|
||||
enum class PluginType
|
||||
{
|
||||
UNKNOWN,
|
||||
DATA,
|
||||
GUI,
|
||||
GUI_ELEMENT,
|
||||
};
|
||||
|
||||
enum class Flag
|
||||
{
|
||||
UNKNOWN = 1 << 0,
|
||||
ACTIONS = 1 << 1,
|
||||
MAIN = 1 << 2,
|
||||
PAYLOAD = 1 << 3,
|
||||
OPTION = 1 << 4,
|
||||
};
|
||||
|
||||
PluginType type = PluginType::UNKNOWN;
|
||||
QString iid;
|
||||
QFlags<Flag> flags;
|
||||
QStringList options;
|
||||
};
|
||||
|
||||
struct AppDescription
|
||||
{
|
||||
App id = App::Login;
|
||||
bool active = false;
|
||||
bool visible = true;
|
||||
QString appName;
|
||||
QVector<int> requiredAcls;
|
||||
QVector<PluginInfo> pluginInfos;
|
||||
|
||||
bool isValid() const { return !appName.isEmpty(); }
|
||||
};
|
||||
|
||||
typedef QVector<AppDescription> AppDescriptionVector;
|
||||
|
||||
|
||||
namespace LtsAppDescriptor // TODO: make qobject class singleton - for tr
|
||||
{
|
||||
LTSAPPDESCRIPTOR_EXPORT App appFromInteger(int i);
|
||||
LTSAPPDESCRIPTOR_EXPORT AppDescriptionVector getAppDescriptionVector(const QByteArray &jsonDocument,
|
||||
QStringList &lastErrors,
|
||||
const QStringList &ignoredPlugins,
|
||||
QString &loginPluginIid);
|
||||
}
|
||||
|
||||
} // namespace LtsAppDescriptor
|
||||
+532
@@ -0,0 +1,532 @@
|
||||
#include "lts_app_descriptor_editor.h"
|
||||
|
||||
#include "ui_lts_app_descriptor_editor.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtWidgets/QFileDialog>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtWidgets/QMessageBox>
|
||||
#include <QStringListModel>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtWidgets/QListView>
|
||||
#include <QtWidgets/QLineEdit>
|
||||
#include <QtWidgets/QPushButton>
|
||||
|
||||
LtsAppDescriptorEditor::LtsAppDescriptorEditor(QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui_LtsAppDescriptionEditor)
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
m_vector = LtsAppDescriptor::AppDescriptionVector();
|
||||
m_appIndex = QModelIndex();
|
||||
m_pluginIndex = QModelIndex();
|
||||
m_fileName = QString();
|
||||
m_changed = false;
|
||||
|
||||
connect(m_ui->lvApps, &QListView::clicked, this, &LtsAppDescriptorEditor::onAppsClicked);
|
||||
connect(m_ui->lvPlugins, &QListView::clicked, this, &LtsAppDescriptorEditor::onPluginsClicked);
|
||||
connect(m_ui->lePluginType, &QLineEdit::editingFinished, this, &LtsAppDescriptorEditor::pluginTypeChanged);
|
||||
connect(m_ui->pbAddApp, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onAppAddClicked);
|
||||
connect(m_ui->pbAddPlugin, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onPluginAddClicked);
|
||||
connect(m_ui->pbDeleteApp, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onAppDeleteClicked);
|
||||
connect(m_ui->pbDeletePlugin, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onPluginDeletedClicked);
|
||||
connect(m_ui->pbUpdate, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onUpdateClicked);
|
||||
connect(m_ui->pbNewPlugin, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onNewpluginClicked);
|
||||
connect(m_ui->pbNewApp, &QPushButton::pressed, this, &LtsAppDescriptorEditor::onNewAppClicked);
|
||||
connect(m_ui->lePluginName, &QLineEdit::textChanged, this, &LtsAppDescriptorEditor::enableNewPlugin);
|
||||
connect(m_ui->lePluginType, &QLineEdit::textChanged, this, &LtsAppDescriptorEditor::enableNewPlugin);
|
||||
|
||||
m_ui->gbPlugins->setEnabled(false);
|
||||
m_ui->pbDeletePlugin->setEnabled(false);
|
||||
m_ui->pbDeleteApp->setEnabled(false);
|
||||
m_ui->pbAddPlugin->setEnabled(false);
|
||||
m_ui->pbUpdate->setEnabled(false);
|
||||
m_ui->pbNewPlugin->setEnabled(false);
|
||||
m_ui->pbNewApp->setEnabled(false);
|
||||
QTimer::singleShot(0, this, &LtsAppDescriptorEditor::getAppDescriptionVector);
|
||||
}
|
||||
|
||||
LtsAppDescriptorEditor::~LtsAppDescriptorEditor()
|
||||
{
|
||||
checkSave();
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::getAppDescriptionVector()
|
||||
{
|
||||
m_fileName =
|
||||
QFileDialog::getOpenFileName(this, tr("Open JSON"), QLatin1Literal("/shd/CTC/TOOLS/TDS/TDS-source/Lts/Projects/Legatus/resources/"), tr("JSON File (*.json)"));
|
||||
if (m_fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QFile file;
|
||||
file.setFileName(m_fileName);
|
||||
file.open(QIODevice::ReadOnly | QIODevice::Text);
|
||||
QString val(QString::fromUtf8(file.readAll()));
|
||||
file.close();
|
||||
|
||||
QStringList errors;
|
||||
m_vector = LtsAppDescriptor::LtsAppDescriptor::getAppDescriptionVector(val.toUtf8(), errors);
|
||||
|
||||
updateAppModel();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onAppsClicked(const QModelIndex &index)
|
||||
{
|
||||
m_ui->gbPlugins->setEnabled(true);
|
||||
m_ui->pbDeleteApp->setEnabled(true);
|
||||
m_ui->pbAddPlugin->setEnabled(true);
|
||||
m_ui->pbNewApp->setEnabled(true);
|
||||
m_appIndex = index;
|
||||
|
||||
QStringList pluginList;
|
||||
Q_FOREACH (const LtsAppDescriptor::PluginInfo &reqPlugin, m_vector.at(m_appIndex.row()).requiredPlugins)
|
||||
{
|
||||
LtsAppDescriptor::PluginInfo::PluginType pluginType = reqPlugin.type;
|
||||
QString type;
|
||||
if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::DATA)
|
||||
type = QLatin1String("data");
|
||||
else if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::GUI)
|
||||
type = QLatin1String("gui");
|
||||
else
|
||||
type = QLatin1String("unknown");
|
||||
|
||||
if (type == QLatin1String("data"))
|
||||
pluginList << QString(type + QLatin1String(": ") + reqPlugin.iid);
|
||||
else
|
||||
{
|
||||
QString strFlags;
|
||||
QFlags<LtsAppDescriptor::PluginInfo::Flag> flags = reqPlugin.flags;
|
||||
|
||||
bool actionsFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS);
|
||||
if (actionsFlag)
|
||||
strFlags.append(QLatin1String("actions "));
|
||||
bool mainFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN);
|
||||
if (mainFlag)
|
||||
strFlags.append(QLatin1String("main "));
|
||||
bool payloadFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::PAYLOAD);
|
||||
if (payloadFlag)
|
||||
strFlags.append(QLatin1String("payload "));
|
||||
bool optionalFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::OPTION);
|
||||
if (optionalFlag)
|
||||
strFlags.append(QLatin1String("optional "));
|
||||
|
||||
pluginList << QString(type + QLatin1String(": ") + reqPlugin.iid + QLatin1String(" [ ") + strFlags + QLatin1String("]"));
|
||||
}
|
||||
}
|
||||
updatePluginModel(pluginList);
|
||||
|
||||
m_ui->chbAppActive->setChecked(m_vector.at(m_appIndex.row()).active);
|
||||
m_ui->leAppName->setText(m_vector.at(m_appIndex.row()).appName);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onPluginsClicked(const QModelIndex &index)
|
||||
{
|
||||
m_pluginIndex = index;
|
||||
m_ui->pbDeletePlugin->setEnabled(true);
|
||||
m_ui->pbUpdate->setEnabled(true);
|
||||
m_ui->pbNewPlugin->setEnabled(true);
|
||||
|
||||
const LtsAppDescriptor::PluginInfo plugin = m_vector.at(m_appIndex.row()).requiredPlugins.at(m_pluginIndex.row());
|
||||
QString pluginName = plugin.iid;
|
||||
m_ui->lePluginName->setText(pluginName);
|
||||
|
||||
LtsAppDescriptor::PluginInfo::PluginType pluginType = plugin.type;
|
||||
QString type;
|
||||
if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::DATA)
|
||||
type = QLatin1String("data");
|
||||
else if (pluginType == LtsAppDescriptor::PluginInfo::PluginType::GUI)
|
||||
type = QLatin1String("gui");
|
||||
else
|
||||
type = QLatin1String("unknown");
|
||||
m_ui->lePluginType->setText(type);
|
||||
|
||||
if (type == QLatin1String("gui"))
|
||||
{
|
||||
m_ui->chbActions->setEnabled(true);
|
||||
m_ui->chbMain->setEnabled(true);
|
||||
m_ui->chbPayload->setEnabled(true);
|
||||
|
||||
QFlags<LtsAppDescriptor::PluginInfo::Flag> flags = plugin.flags;
|
||||
bool actionsFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::ACTIONS);
|
||||
m_ui->chbActions->setChecked(actionsFlag);
|
||||
bool mainFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::MAIN);
|
||||
m_ui->chbMain->setChecked(mainFlag);
|
||||
bool payloadFlag = flags.testFlag(LtsAppDescriptor::PluginInfo::Flag::PAYLOAD);
|
||||
m_ui->chbPayload->setChecked(payloadFlag);
|
||||
}
|
||||
else
|
||||
{
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbActions->setEnabled(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbMain->setEnabled(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->chbPayload->setEnabled(false);
|
||||
}
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::pluginTypeChanged()
|
||||
{
|
||||
if (m_ui->lePluginType->text().isEmpty())
|
||||
return;
|
||||
|
||||
if (m_ui->lePluginType->text() == QLatin1String("gui"))
|
||||
{
|
||||
m_ui->chbActions->setEnabled(true);
|
||||
m_ui->chbMain->setEnabled(true);
|
||||
m_ui->chbPayload->setEnabled(true);
|
||||
}
|
||||
else if (m_ui->lePluginType->text() == QLatin1String("data"))
|
||||
{
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbActions->setEnabled(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbMain->setEnabled(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->chbPayload->setEnabled(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Information, tr("Error"), tr("Plugin Type must either be \"gui\" or \"data\"."));
|
||||
msgBox.exec();
|
||||
m_ui->lePluginType->setText(QString());
|
||||
}
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onAppAddClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (m_ui->leAppName->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The App needs to have a name."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList appList;
|
||||
Q_FOREACH (const LtsAppDescriptor::AppDescription &appDesc, m_vector)
|
||||
{
|
||||
appList << appDesc.appName;
|
||||
}
|
||||
|
||||
if (appList.contains(m_ui->leAppName->text(), Qt::CaseInsensitive))
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("There already exists an app named \"%1\".").arg(m_ui->leAppName->text()));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
LtsAppDescriptor::AppDescription newApp;
|
||||
newApp.appName = m_ui->leAppName->text();
|
||||
newApp.active = m_ui->chbAppActive->isChecked();
|
||||
m_vector.append(newApp);
|
||||
|
||||
updateAppModel();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onPluginAddClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (m_ui->lePluginType->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The Plugin needs to contain a type."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
if (m_ui->lePluginName->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The Plugin needs to have a name."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
QStringList pluginList;
|
||||
Q_FOREACH (const LtsAppDescriptor::PluginInfo &pluginDesc, m_vector.at(m_appIndex.row()).requiredPlugins)
|
||||
{
|
||||
pluginList << pluginDesc.iid;
|
||||
}
|
||||
|
||||
if (pluginList.contains(m_ui->lePluginName->text(), Qt::CaseInsensitive))
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("There already exists an plugin named \"%1\".").arg(m_ui->lePluginName->text()));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
LtsAppDescriptor::PluginInfo newPlugin;
|
||||
|
||||
newPlugin.iid = m_ui->lePluginName->text();
|
||||
|
||||
if (m_ui->lePluginType->text() == QLatin1String("gui"))
|
||||
newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::GUI;
|
||||
else if (m_ui->lePluginType->text() == QLatin1String("data"))
|
||||
newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::DATA;
|
||||
else
|
||||
newPlugin.type = LtsAppDescriptor::PluginInfo::PluginType::UNKNOWN;
|
||||
|
||||
if (m_ui->chbActions->isChecked())
|
||||
newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::ACTIONS;
|
||||
if (m_ui->chbMain->isChecked())
|
||||
newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::MAIN;
|
||||
if (m_ui->chbPayload->isChecked())
|
||||
newPlugin.flags |= LtsAppDescriptor::PluginInfo::Flag::PAYLOAD;
|
||||
|
||||
m_vector[m_appIndex.row()].requiredPlugins.append(newPlugin);
|
||||
|
||||
onAppsClicked(m_appIndex);
|
||||
onNewpluginClicked();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onAppDeleteClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (!m_appIndex.isValid())
|
||||
return;
|
||||
|
||||
QMessageBox msgBox(tr("Warning"),
|
||||
tr("The App \n\"%1\" \nwill be removed. Continue?").arg(m_appIndex.data().toString()),
|
||||
QMessageBox::Warning,
|
||||
QMessageBox::Yes | QMessageBox::Default,
|
||||
QMessageBox::No | QMessageBox::Escape,
|
||||
QMessageBox::NoButton);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::No)
|
||||
return;
|
||||
|
||||
m_vector.remove(m_appIndex.row());
|
||||
|
||||
updateAppModel();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onPluginDeletedClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (!m_pluginIndex.isValid())
|
||||
return;
|
||||
|
||||
QMessageBox msgBox(tr("Warning"),
|
||||
tr("The Plugin \n\"%1\" \nwill be removed. Continue?").arg(m_pluginIndex.data().toString()),
|
||||
QMessageBox::Warning,
|
||||
QMessageBox::Yes | QMessageBox::Default,
|
||||
QMessageBox::No | QMessageBox::Escape,
|
||||
QMessageBox::NoButton);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::No)
|
||||
return;
|
||||
|
||||
m_vector[m_appIndex.row()].requiredPlugins.remove(m_pluginIndex.row());
|
||||
m_pluginIndex = QModelIndex();
|
||||
m_ui->lePluginName->setText(QString());
|
||||
m_ui->lePluginType->setText(QString());
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
|
||||
m_ui->pbDeletePlugin->setEnabled(false);
|
||||
m_ui->pbUpdate->setEnabled(false);
|
||||
m_ui->pbNewPlugin->setEnabled(false);
|
||||
|
||||
onAppsClicked(m_appIndex);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onUpdateClicked()
|
||||
{
|
||||
m_changed = true;
|
||||
|
||||
if (m_ui->lePluginType->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The Plugin needs to contain a type."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
if (m_ui->lePluginName->text().isEmpty())
|
||||
{
|
||||
QMessageBox msgBox(QMessageBox::Critical, tr("Error"), tr("The Plugin needs to have a name."));
|
||||
msgBox.exec();
|
||||
return;
|
||||
}
|
||||
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].iid = m_ui->lePluginName->text();
|
||||
|
||||
if (m_ui->lePluginType->text() == QLatin1String("gui"))
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type = LtsAppDescriptor::PluginInfo::PluginType::GUI;
|
||||
else if (m_ui->lePluginType->text() == QLatin1String("data"))
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type = LtsAppDescriptor::PluginInfo::PluginType::DATA;
|
||||
else
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].type = LtsAppDescriptor::PluginInfo::PluginType::UNKNOWN;
|
||||
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags = QFlags<LtsAppDescriptor::PluginInfo::Flag>();
|
||||
|
||||
if (m_ui->chbActions->isChecked())
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |= LtsAppDescriptor::PluginInfo::Flag::ACTIONS;
|
||||
if (m_ui->chbMain->isChecked())
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |= LtsAppDescriptor::PluginInfo::Flag::MAIN;
|
||||
if (m_ui->chbPayload->isChecked())
|
||||
m_vector[m_appIndex.row()].requiredPlugins[m_pluginIndex.row()].flags |= LtsAppDescriptor::PluginInfo::Flag::PAYLOAD;
|
||||
|
||||
onAppsClicked(m_appIndex);
|
||||
onNewpluginClicked();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onNewpluginClicked()
|
||||
{
|
||||
m_ui->lePluginName->setText(QString());
|
||||
m_ui->lePluginType->setText(QString());
|
||||
m_ui->chbActions->setEnabled(true);
|
||||
m_ui->chbMain->setEnabled(true);
|
||||
m_ui->chbPayload->setEnabled(true);
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->pbUpdate->setEnabled(false);
|
||||
m_ui->pbNewPlugin->setEnabled(false);
|
||||
m_ui->pbDeletePlugin->setEnabled(false);
|
||||
|
||||
if (m_pluginIndex.isValid())
|
||||
m_ui->lvPlugins->selectionModel()->select(m_pluginIndex, QItemSelectionModel::Deselect);
|
||||
m_pluginIndex = QModelIndex();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onNewAppClicked()
|
||||
{
|
||||
updatePluginModel(QStringList());
|
||||
|
||||
m_ui->gbPlugins->setEnabled(false);
|
||||
m_ui->pbDeletePlugin->setEnabled(false);
|
||||
m_ui->pbDeleteApp->setEnabled(false);
|
||||
m_ui->pbAddPlugin->setEnabled(false);
|
||||
m_ui->pbUpdate->setEnabled(false);
|
||||
m_ui->pbNewPlugin->setEnabled(false);
|
||||
m_ui->leAppName->setText(QString());
|
||||
m_ui->chbAppActive->setChecked(false);
|
||||
m_ui->lePluginName->setText(QString());
|
||||
m_ui->lePluginType->setText(QString());
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->pbNewApp->setEnabled(false);
|
||||
|
||||
if (m_appIndex.isValid())
|
||||
m_ui->lvApps->selectionModel()->select(m_appIndex, QItemSelectionModel::Deselect);
|
||||
m_appIndex = QModelIndex();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::updateAppModel()
|
||||
{
|
||||
onNewAppClicked();
|
||||
QStringList appList;
|
||||
Q_FOREACH (const LtsAppDescriptor::AppDescription &appDesc, m_vector)
|
||||
{
|
||||
appList << appDesc.appName;
|
||||
}
|
||||
|
||||
if (!m_ui->lvApps->model())
|
||||
m_ui->lvApps->setModel(new QStringListModel(this));
|
||||
QStringListModel *m = qobject_cast<QStringListModel *>(m_ui->lvApps->model());
|
||||
if (!m)
|
||||
return;
|
||||
m->setStringList(appList);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::updatePluginModel(const QStringList &list)
|
||||
{
|
||||
if (!m_ui->lvPlugins->model())
|
||||
m_ui->lvPlugins->setModel(new QStringListModel(this));
|
||||
QStringListModel *m = qobject_cast<QStringListModel *>(m_ui->lvPlugins->model());
|
||||
if (!m)
|
||||
return;
|
||||
m->setStringList(list);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::checkSave()
|
||||
{
|
||||
if (m_changed)
|
||||
{
|
||||
QMessageBox msgBox(tr("Warning"),
|
||||
tr("The Data has been modified.\nDo you want to save your changes?"),
|
||||
QMessageBox::Warning,
|
||||
QMessageBox::Yes | QMessageBox::Default,
|
||||
QMessageBox::No | QMessageBox::Escape,
|
||||
QMessageBox::NoButton);
|
||||
|
||||
if (msgBox.exec() == QMessageBox::Yes)
|
||||
onSaveAsClicked();
|
||||
}
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onSaveAsClicked()
|
||||
{
|
||||
QString fileName =
|
||||
QFileDialog::getSaveFileName(this, tr("Save JSON"), QLatin1Literal("/shd/CTC/TOOLS/TDS/TDS-source/Lts/Projects/Legatus/resources/"), tr("JSON File (*.json)"));
|
||||
if (fileName.isEmpty())
|
||||
return;
|
||||
|
||||
QByteArray jsonByteArray;
|
||||
jsonByteArray = LtsAppDescriptor::LtsAppDescriptor::createJson(m_vector);
|
||||
|
||||
QFile f(fileName);
|
||||
f.open(QIODevice::WriteOnly);
|
||||
f.write(jsonByteArray);
|
||||
f.close();
|
||||
|
||||
m_changed = false;
|
||||
|
||||
QMessageBox msgBox(QMessageBox::Information, tr("Success"), tr("Your file has been saved."));
|
||||
msgBox.exec();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onSaveClicked()
|
||||
{
|
||||
const QByteArray jsonByteArray(LtsAppDescriptor::LtsAppDescriptor::createJson(m_vector));
|
||||
|
||||
QFile f(m_fileName);
|
||||
f.open(QIODevice::WriteOnly);
|
||||
f.write(jsonByteArray);
|
||||
f.close();
|
||||
|
||||
m_changed = false;
|
||||
|
||||
QMessageBox msgBox(QMessageBox::Information, tr("Success"), tr("Your file has been saved."));
|
||||
msgBox.exec();
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onOpenClicked()
|
||||
{
|
||||
checkSave();
|
||||
getAppDescriptionVector();
|
||||
m_changed = false;
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onNewClicked()
|
||||
{
|
||||
checkSave();
|
||||
m_ui->chbActions->setChecked(false);
|
||||
m_ui->chbMain->setChecked(false);
|
||||
m_ui->chbPayload->setChecked(false);
|
||||
m_ui->chbAppActive->setChecked(false);
|
||||
m_ui->leAppName->setText(QString());
|
||||
m_ui->lePluginName->setText(QString());
|
||||
m_ui->lePluginType->setText(QString());
|
||||
m_ui->gbPlugins->setEnabled(false);
|
||||
|
||||
m_vector = LtsAppDescriptor::AppDescriptionVector();
|
||||
|
||||
updateAppModel();
|
||||
updatePluginModel(QStringList());
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::enableNewPlugin()
|
||||
{
|
||||
m_ui->pbNewPlugin->setEnabled(true);
|
||||
}
|
||||
|
||||
void LtsAppDescriptorEditor::onQuitClicked()
|
||||
{
|
||||
QApplication::quit();
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
#include "lts_app_descriptor.h"
|
||||
|
||||
#include <QtCore/QModelIndex>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
#ifndef LTSAPPDESCRIPTOREDITOR_EXPORT
|
||||
#ifdef BUILD_STATIC_LIBRARIES
|
||||
#define LTSAPPDESCRIPTOREDITOR_EXPORT
|
||||
#else
|
||||
#if defined(MAKE_LTSAPPDESCRIPTOREDITOR_LIB)
|
||||
#define LTSAPPDESCRIPTOREDITOR_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define LTSAPPDESCRIPTOREDITOR_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
class Ui_LtsAppDescriptionEditor;
|
||||
|
||||
class LTSAPPDESCRIPTOREDITOR_EXPORT LtsAppDescriptorEditor : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LtsAppDescriptorEditor(QWidget *parent = 0);
|
||||
~LtsAppDescriptorEditor();
|
||||
|
||||
private:
|
||||
Ui_LtsAppDescriptionEditor *m_ui;
|
||||
LtsAppDescriptor::AppDescriptionVector m_vector;
|
||||
QModelIndex m_appIndex;
|
||||
QModelIndex m_pluginIndex;
|
||||
|
||||
QString m_fileName;
|
||||
|
||||
bool m_changed;
|
||||
|
||||
void getAppDescriptionVector();
|
||||
void setupMainWindow();
|
||||
void updateAppModel();
|
||||
void updatePluginModel(const QStringList &list);
|
||||
void checkSave();
|
||||
|
||||
private slots:
|
||||
void onAppsClicked(const QModelIndex &index);
|
||||
void onPluginsClicked(const QModelIndex &index);
|
||||
void pluginTypeChanged();
|
||||
void onAppAddClicked();
|
||||
void onPluginAddClicked();
|
||||
void onAppDeleteClicked();
|
||||
void onPluginDeletedClicked();
|
||||
void onUpdateClicked();
|
||||
void onNewpluginClicked();
|
||||
void onNewAppClicked();
|
||||
void enableNewPlugin();
|
||||
|
||||
public slots:
|
||||
void onSaveClicked();
|
||||
void onSaveAsClicked();
|
||||
void onOpenClicked();
|
||||
void onNewClicked();
|
||||
void onQuitClicked();
|
||||
};
|
||||
+434
@@ -0,0 +1,434 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>LtsAppDescriptionEditor</class>
|
||||
<widget class="QWidget" name="LtsAppDescriptionEditor">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>865</width>
|
||||
<height>337</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string/>
|
||||
</property>
|
||||
<property name="autoFillBackground">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="3" column="3" colspan="2">
|
||||
<widget class="QLabel" name="Plugins">
|
||||
<property name="text">
|
||||
<string>Required Plugins:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="3">
|
||||
<widget class="QLabel" name="AppLabel">
|
||||
<property name="text">
|
||||
<string>App:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" rowspan="4" colspan="3">
|
||||
<widget class="QListView" name="lvApps">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>150</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="7">
|
||||
<widget class="QPushButton" name="pbNewPlugin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>New</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="3" colspan="2">
|
||||
<widget class="QLabel" name="AppName">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Name:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="6">
|
||||
<widget class="QPushButton" name="pbDeletePlugin">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="7" rowspan="4" colspan="3">
|
||||
<widget class="QGroupBox" name="gbPlugins">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="title">
|
||||
<string>New Plugin Requirements</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="PluginType">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="lePluginType">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>210</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>16777215</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>gui/data</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="PluginName">
|
||||
<property name="text">
|
||||
<string>IID:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QLineEdit" name="lePluginName">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>210</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Name of the Plugin</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="PluginFlags">
|
||||
<property name="text">
|
||||
<string>Flags:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QCheckBox" name="chbActions">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Actions</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="chbMain">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Main</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QCheckBox" name="chbPayload">
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>25</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Payload</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeType">
|
||||
<enum>QSizePolicy::Expanding</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="3" colspan="2">
|
||||
<widget class="QLabel" name="AppActive">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Active:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="2">
|
||||
<widget class="QPushButton" name="pbDeleteApp">
|
||||
<property name="text">
|
||||
<string>Delete</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="3" colspan="4">
|
||||
<widget class="QListView" name="lvPlugins">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>300</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="9">
|
||||
<widget class="QPushButton" name="pbUpdate">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Update</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="5">
|
||||
<widget class="QLineEdit" name="leAppName">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>100</width>
|
||||
<height>30</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Name of the App</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="8">
|
||||
<widget class="QPushButton" name="pbAddPlugin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QPushButton" name="pbNewApp">
|
||||
<property name="text">
|
||||
<string>New</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="5">
|
||||
<widget class="QCheckBox" name="chbAppActive">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>80</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1">
|
||||
<widget class="QPushButton" name="pbAddApp">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>List of Apps</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="6" rowspan="3">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<tabstops>
|
||||
<tabstop>lvApps</tabstop>
|
||||
<tabstop>pbNewApp</tabstop>
|
||||
<tabstop>pbAddApp</tabstop>
|
||||
<tabstop>pbDeleteApp</tabstop>
|
||||
<tabstop>chbAppActive</tabstop>
|
||||
<tabstop>leAppName</tabstop>
|
||||
<tabstop>lvPlugins</tabstop>
|
||||
<tabstop>pbDeletePlugin</tabstop>
|
||||
<tabstop>pbNewPlugin</tabstop>
|
||||
<tabstop>lePluginType</tabstop>
|
||||
<tabstop>lePluginName</tabstop>
|
||||
<tabstop>chbPayload</tabstop>
|
||||
<tabstop>chbMain</tabstop>
|
||||
<tabstop>chbActions</tabstop>
|
||||
<tabstop>pbAddPlugin</tabstop>
|
||||
<tabstop>pbUpdate</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,55 @@
|
||||
|
||||
|
||||
#include "lts_app_descriptor_editor.h"
|
||||
|
||||
#include <QtGui/QAction>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QMainWindow>
|
||||
#include <QtWidgets/QMenuBar>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication a(argc, argv);
|
||||
|
||||
QMainWindow *mainWindow = new QMainWindow();
|
||||
QMenu *fileMenu = mainWindow->menuBar()->addMenu(QObject::tr("&File"));
|
||||
|
||||
QAction *openAct = new QAction(QObject::tr("&Open"), mainWindow);
|
||||
openAct->setShortcuts(QKeySequence::Open);
|
||||
openAct->setStatusTip(QObject::tr("Open an existing file"));
|
||||
fileMenu->addAction(openAct);
|
||||
|
||||
QAction *newAct = new QAction(QObject::tr("&New"), mainWindow);
|
||||
newAct->setShortcuts(QKeySequence::New);
|
||||
newAct->setStatusTip(QObject::tr("Create a new App"));
|
||||
fileMenu->addAction(newAct);
|
||||
|
||||
QAction *saveAct = new QAction(QObject::tr("&Save"), mainWindow);
|
||||
saveAct->setShortcuts(QKeySequence::Save);
|
||||
saveAct->setStatusTip(QObject::tr("Save modified file"));
|
||||
fileMenu->addAction(saveAct);
|
||||
|
||||
QAction *saveAsAct = new QAction(QObject::tr("&SaveAs"), mainWindow);
|
||||
saveAsAct->setShortcuts(QKeySequence::SaveAs);
|
||||
saveAsAct->setStatusTip(QObject::tr("Save modified file as"));
|
||||
fileMenu->addAction(saveAsAct);
|
||||
|
||||
QAction *quitAct = new QAction(QObject::tr("&Quit"), mainWindow);
|
||||
quitAct->setShortcuts(QKeySequence::Quit);
|
||||
quitAct->setStatusTip(QObject::tr("Quit the Application"));
|
||||
fileMenu->addAction(quitAct);
|
||||
|
||||
LtsAppDescriptorEditor *editor = new LtsAppDescriptorEditor(mainWindow);
|
||||
mainWindow->setCentralWidget(editor);
|
||||
QObject::connect(openAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onOpenClicked);
|
||||
QObject::connect(newAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onNewClicked);
|
||||
QObject::connect(saveAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onSaveClicked);
|
||||
QObject::connect(saveAsAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onSaveAsClicked);
|
||||
QObject::connect(quitAct, &QAction::triggered, editor, &LtsAppDescriptorEditor::onQuitClicked);
|
||||
|
||||
mainWindow->show();
|
||||
|
||||
int ret = a.exec();
|
||||
delete mainWindow;
|
||||
return ret;
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
|
||||
NGL_ADD_LIBRARY2(ALL LtsCommandLineParser SHARED
|
||||
LtsCommandLineParser.cpp
|
||||
LtsCommandLineParser.h
|
||||
)
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsCommandLineParser
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
)
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsCommandLineParser
|
||||
PRIVATE
|
||||
ngltools
|
||||
Qt6::Network
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
)
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
|
||||
|
||||
#include "LtsCommandLineParser.h"
|
||||
|
||||
#include <QtCore/QFlags>
|
||||
#include <QNetworkInterface>
|
||||
#include <QtCore/QSettings>
|
||||
#include <QtCore/QUrl>
|
||||
#include <ngltools/ngltools_filesystem.h>
|
||||
|
||||
namespace LtsCommandLineParser
|
||||
{
|
||||
static constexpr int s_maxDomainId = 232;
|
||||
|
||||
LtsCommandLineParser::LtsCommandLineParser(ProvideCommandLineParameterOptions opts)
|
||||
: QCommandLineParser()
|
||||
, m_enabledOptions(opts)
|
||||
{
|
||||
if (NGL::isAppInBuildBasePath() || m_enabledOptions.testFlag(CreateDomainIdFromIp))
|
||||
{
|
||||
const QList<QHostAddress> addresses = QNetworkInterface::allAddresses();
|
||||
for (const QHostAddress &address : addresses)
|
||||
{
|
||||
if (address.isLoopback() || address.isNull())
|
||||
continue;
|
||||
if (address.protocol() != QAbstractSocket::IPv4Protocol)
|
||||
continue;
|
||||
|
||||
m_generatedDomainId = (address.toIPv4Address() & 0x000000ff) % 232;
|
||||
if (m_generatedDomainId == 0)
|
||||
m_generatedDomainId = s_maxDomainId;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_enabledOptions.testFlag(ExerciseId))
|
||||
addOption({{QStringLiteral("e"), QStringLiteral("exerciseid")},
|
||||
QStringLiteral("Default: -1 (invalid, get from lts-cc.conf)"),
|
||||
QStringLiteral("exerciseId"),
|
||||
QStringLiteral("-1")});
|
||||
if (m_enabledOptions.testFlag(DomainId))
|
||||
addOption({{QStringLiteral("d"), QStringLiteral("domainid")},
|
||||
QStringLiteral("Default: ") + QString::number(m_generatedDomainId),
|
||||
QStringLiteral("domainId"),
|
||||
QString::number(m_generatedDomainId)});
|
||||
if (m_enabledOptions.testFlag(CustomConfigFile))
|
||||
{
|
||||
const QString cf(NGL::findProjectConfigFile());
|
||||
addOption({{QStringLiteral("i"), QStringLiteral("ini")}, QStringLiteral("Default: ") + cf, QStringLiteral("filename"), cf});
|
||||
}
|
||||
if (m_enabledOptions.testFlag(TcpConnectionUrl))
|
||||
{
|
||||
const QString defaultTcpUrl(QStringLiteral("EXCTDS01:7400"));
|
||||
addOption({{QStringLiteral("t"), QStringLiteral("tcpurl")}, QStringLiteral("Default: ") + defaultTcpUrl, QStringLiteral("hostname:port"), defaultTcpUrl});
|
||||
}
|
||||
addHelpOption();
|
||||
|
||||
if (!m_enabledOptions.testFlag(NoVersion))
|
||||
addVersionOption();
|
||||
}
|
||||
|
||||
int LtsCommandLineParser::domainId() const
|
||||
{
|
||||
if (m_domainId == 0 && m_generatedDomainId != 0 && !m_domainIdSetByFunction)
|
||||
{
|
||||
#ifdef WIN32
|
||||
|
||||
fprintf(stdout,
|
||||
"\n\nAttention: using a default domain id != 0 because executable is run inside builddir - default "
|
||||
"domainid is %d\n\n",
|
||||
m_generatedDomainId);
|
||||
#else
|
||||
fprintf(stdout,
|
||||
"\n\n\e[31mAttention: using a default domain id != 0 because executable is run inside builddir - "
|
||||
"default domainid is %d\e[0m\n\n",
|
||||
m_generatedDomainId);
|
||||
#endif
|
||||
return m_generatedDomainId;
|
||||
}
|
||||
return m_domainId;
|
||||
}
|
||||
|
||||
void LtsCommandLineParser::setDomainId(int id)
|
||||
{
|
||||
m_domainIdSetByFunction = true;
|
||||
m_domainId = id;
|
||||
}
|
||||
|
||||
bool LtsCommandLineParser::iniFileSet() const
|
||||
{
|
||||
if (!m_enabledOptions.testFlag(CustomConfigFile))
|
||||
return false;
|
||||
return isSet(QStringLiteral("i"));
|
||||
}
|
||||
|
||||
bool LtsCommandLineParser::useTcpConnection() const
|
||||
{
|
||||
if (!m_enabledOptions.testFlag(TcpConnectionUrl))
|
||||
return false;
|
||||
return isSet(QStringLiteral("t"));
|
||||
}
|
||||
|
||||
QUrl LtsCommandLineParser::tcpConnectionUrl() const
|
||||
{
|
||||
if (!m_enabledOptions.testFlag(TcpConnectionUrl))
|
||||
return QUrl();
|
||||
if (!useTcpConnection())
|
||||
return QUrl();
|
||||
QString str = value(QStringLiteral("t"));
|
||||
|
||||
str.replace(QLatin1String("tcpv4_lan"), QLatin1String("tcpv4lan"));
|
||||
QUrl url = QUrl::fromUserInput(str);
|
||||
if (url.scheme() == QLatin1String("http") || url.scheme() == QLatin1String("file"))
|
||||
url.setScheme(QStringLiteral("tcpv4lan"));
|
||||
return url;
|
||||
}
|
||||
|
||||
bool LtsCommandLineParser::init(bool bParseHelp)
|
||||
{
|
||||
if (!parse(QCoreApplication::arguments()))
|
||||
{
|
||||
fprintf(stdout, "\nBad option: %s \n", qPrintable(errorText()));
|
||||
if (!m_unitModuleTestMode)
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_enabledOptions.testFlag(ExerciseId))
|
||||
m_exerciseId = value(QStringLiteral("e")).toInt();
|
||||
if (m_enabledOptions.testFlag(DomainId))
|
||||
m_domainId = value(QStringLiteral("d")).toInt();
|
||||
if (m_enabledOptions.testFlag(CustomConfigFile))
|
||||
m_iniFile = value(QStringLiteral("i"));
|
||||
|
||||
if (bParseHelp && isSet(QStringLiteral("h")))
|
||||
showHelp();
|
||||
if (!m_unitModuleTestMode && isSet(QStringLiteral("v")))
|
||||
showVersion();
|
||||
if (!domainIdProvided())
|
||||
{
|
||||
const QString iniFile = m_iniFile.isEmpty() ? NGL::findProjectConfigFile() : m_iniFile;
|
||||
if (!iniFile.isEmpty())
|
||||
{
|
||||
QSettings s(iniFile, QSettings::IniFormat);
|
||||
bool bOk;
|
||||
int domainId = s.value(QStringLiteral("Global/DomainId"), m_domainId).toInt(&bOk);
|
||||
if (bOk && domainId > 0 && domainId <= s_maxDomainId)
|
||||
m_domainId = domainId;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void LtsCommandLineParser::showHelp(int exitCode)
|
||||
{
|
||||
if (!m_additionalHelpStr.isEmpty())
|
||||
fprintf(stdout, "%s", qPrintable(m_additionalHelpStr));
|
||||
|
||||
QCommandLineParser::showHelp(exitCode);
|
||||
}
|
||||
|
||||
bool LtsCommandLineParser::domainIdProvided() const
|
||||
{
|
||||
if (!m_enabledOptions.testFlag(DomainId))
|
||||
return false;
|
||||
return isSet(QStringLiteral("d"));
|
||||
}
|
||||
|
||||
bool LtsCommandLineParser::exerciseIdProvided() const
|
||||
{
|
||||
if (!m_enabledOptions.testFlag(ExerciseId))
|
||||
return false;
|
||||
return isSet(QStringLiteral("e"));
|
||||
}
|
||||
|
||||
} // namespace LtsCommandLineParser
|
||||
@@ -0,0 +1,61 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ltscommandlineparser_export.h"
|
||||
|
||||
#include <QCommandLineParser>
|
||||
|
||||
class QUrl;
|
||||
|
||||
namespace LtsCommandLineParser
|
||||
{
|
||||
class LTSCOMMANDLINEPARSER_EXPORT LtsCommandLineParser : public QCommandLineParser
|
||||
{
|
||||
public:
|
||||
enum ProvideCommandLineParameterOptions
|
||||
{
|
||||
None = 0x0000,
|
||||
CreateDomainIdFromIp = 0x0001,
|
||||
DomainId = 0x0002,
|
||||
ExerciseId = 0x0004,
|
||||
CustomConfigFile = 0x0008,
|
||||
TcpConnectionUrl = 0x0010,
|
||||
NoVersion = 0x0020
|
||||
};
|
||||
|
||||
explicit LtsCommandLineParser(ProvideCommandLineParameterOptions opts = ProvideCommandLineParameterOptions(CreateDomainIdFromIp | DomainId | ExerciseId |
|
||||
CustomConfigFile));
|
||||
virtual ~LtsCommandLineParser() = default;
|
||||
int domainId() const;
|
||||
void setDomainId(int id);
|
||||
|
||||
int exerciseId() const { return m_exerciseId; }
|
||||
|
||||
QString iniFile() const { return m_iniFile; }
|
||||
|
||||
bool iniFileSet() const;
|
||||
bool useTcpConnection() const;
|
||||
QUrl tcpConnectionUrl() const;
|
||||
virtual bool init(bool bParseHelp = true);
|
||||
|
||||
void showHelp(int exitCode = 0);
|
||||
bool domainIdProvided() const;
|
||||
bool exerciseIdProvided() const;
|
||||
|
||||
void setUnitModuleTestMode(bool onOff) { m_unitModuleTestMode = onOff; }
|
||||
|
||||
protected:
|
||||
QString m_additionalHelpStr;
|
||||
|
||||
private:
|
||||
QFlags<ProvideCommandLineParameterOptions> m_enabledOptions;
|
||||
int m_exerciseId{0};
|
||||
int m_domainId{0};
|
||||
int m_generatedDomainId{0};
|
||||
bool m_domainIdSetByFunction{false};
|
||||
bool m_unitModuleTestMode{false};
|
||||
QString m_iniFile;
|
||||
};
|
||||
|
||||
} // namespace LtsCommandLineParser
|
||||
@@ -0,0 +1,104 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
# Created by Christian Ehrlicher, SLSE, 2016-08-31
|
||||
#
|
||||
|
||||
# Make ${Qt6_Widgets} optional
|
||||
if(QT6_GUI_AVAILABLE AND TARGET Qt6::Widgets)
|
||||
set(Qt6_Widgets ${Qt6_Widgets})
|
||||
else()
|
||||
set(Qt6_Widgets "")
|
||||
endif()
|
||||
|
||||
|
||||
# Add system GeographicLib Find module path
|
||||
list(APPEND CMAKE_MODULE_PATH /usr/share/cmake/geographiclib)
|
||||
FIND_PACKAGE(GeographicLib)
|
||||
NGL_ADD_LIBRARY2(LtsCoordTrans WS AAR SV HLA SHARED
|
||||
lts_coord_trans.h
|
||||
lts_coord_trans.cpp
|
||||
)
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsCoordTrans
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
)
|
||||
if (GeographicLib_FOUND)
|
||||
TARGET_LINK_LIBRARIES(LtsCoordTrans
|
||||
PRIVATE ${GeographicLib_LIBRARIES}
|
||||
)
|
||||
TARGET_INCLUDE_DIRECTORIES(LtsCoordTrans PRIVATE ${GeographicLib_INCLUDE_DIRS})
|
||||
LINK_DIRECTORIES(${GeographicLib_LIBRARY_DIRS})
|
||||
else()
|
||||
message(STATUS "GeographicLib not found; building LtsCoordTrans without it")
|
||||
endif()
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsCoordTrans
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
SET(ltsCoordWidget_SRCS
|
||||
lts_coord_widget.cpp
|
||||
lts_coord_widget.h
|
||||
lts_coord_widget_p.cpp
|
||||
lts_coord_widget_p.h
|
||||
)
|
||||
NGL_WRAP_UI(ltsCoordWidget_SRCS
|
||||
lts_coord_widget_mgrs.ui
|
||||
lts_coord_widget_wgs84.ui
|
||||
)
|
||||
NGL_ADD_LIBRARY2(ltsCoordWidget WS AAR SV SHARED ${ltsCoordWidget_SRCS})
|
||||
if(QT6_GUI_AVAILABLE)
|
||||
NGL_TARGET_LINK_LIBRARIES(ltsCoordWidget
|
||||
PRIVATE
|
||||
LtsCoordTrans
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
${Qt6_Widgets}
|
||||
)
|
||||
endif()
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(ltsCoordWidget
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../LtsUtils/LtsUtilsCore
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
IF(TARGET LtsCoordTrans)
|
||||
SET(_tools lonlat2mgrs mgrs2lonlat)
|
||||
FOREACH(_tool ${_tools})
|
||||
NGL_ADD_EXECUTABLE(ALL ${_tool} ${_tool}.cpp)
|
||||
TARGET_LINK_LIBRARIES(${_tool}
|
||||
Qt6::Core
|
||||
${GeographicLib_LIBRARIES}
|
||||
)
|
||||
TARGET_INCLUDE_DIRECTORIES(${_tool} PRIVATE ${GeographicLib_INCLUDE_DIRS})
|
||||
LINK_DIRECTORIES(${GeographicLib_LIBRARY_DIRS})
|
||||
NGL_INSTALL_TARGET(${_tool})
|
||||
ENDFOREACH()
|
||||
ENDIF()
|
||||
|
||||
IF(BUILD_TESTING AND TARGET ltsCoordWidget)
|
||||
ADD_EXECUTABLE(lts_coord_widget_test lts_coord_widget_test.cpp)
|
||||
TARGET_LINK_LIBRARIES(lts_coord_widget_test
|
||||
ltsCoordWidget
|
||||
LtsCoordTrans
|
||||
)
|
||||
ENDIF()
|
||||
|
||||
IF(BUILD_TESTING AND TARGET LtsCoordTrans)
|
||||
ADD_EXECUTABLE(lts_coord_trans_unit_test
|
||||
lts_coord_trans_unit_test.cpp
|
||||
lts_coord_trans_unit_test.h
|
||||
)
|
||||
TARGET_LINK_LIBRARIES(lts_coord_trans_unit_test
|
||||
LtsCoordTrans
|
||||
Qt6::Test
|
||||
${GeographicLib_LIBRARIES}
|
||||
)
|
||||
TARGET_INCLUDE_DIRECTORIES(lts_coord_trans_unit_test PRIVATE ${GeographicLib_INCLUDE_DIRS})
|
||||
LINK_DIRECTORIES(${GeographicLib_LIBRARY_DIRS})
|
||||
ADD_TEST(NAME lts_coord_trans_unit_test COMMAND $<TARGET_FILE:lts_coord_trans_unit_test>)
|
||||
ENDIF()
|
||||
@@ -0,0 +1,74 @@
|
||||
|
||||
|
||||
#include <GeographicLib/MGRS.hpp>
|
||||
#include <GeographicLib/UTMUPS.hpp>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc < 3 || argc > 5)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <lon_degree> <lat_degree> [<precision>]\n", argv[0]);
|
||||
fprintf(stderr, "Usage: %s <lon_rad> <lat_rad> -r [<precision>]\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool lonLatInRad = false;
|
||||
double lon = atof(argv[1]);
|
||||
double lat = atof(argv[2]);
|
||||
int prec = 5;
|
||||
if (argc >= 4)
|
||||
{
|
||||
int precArgId = -1;
|
||||
if (strcmp(argv[3], "-r") == 0)
|
||||
{
|
||||
lonLatInRad = true;
|
||||
if (argc == 5)
|
||||
precArgId = 4;
|
||||
}
|
||||
else
|
||||
precArgId = 3;
|
||||
if (precArgId > 0)
|
||||
prec = atoi(argv[precArgId]);
|
||||
}
|
||||
|
||||
std::string mgrs;
|
||||
|
||||
int zone;
|
||||
bool northp;
|
||||
double x, y;
|
||||
try
|
||||
{
|
||||
const double latDeg = lonLatInRad ? lat * 180 / M_PI : lat;
|
||||
const double lonDeg = lonLatInRad ? lon * 180 / M_PI : lon;
|
||||
GeographicLib::UTMUPS::Forward(latDeg, lonDeg, zone, northp, x, y);
|
||||
GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
fprintf(stderr, "Error converting %9.6f, %9.6f to MGRS coords\n", lat, lon);
|
||||
return -2;
|
||||
}
|
||||
std::string part1, part2, part3, part4;
|
||||
part1 = mgrs.substr(0, 3);
|
||||
part2 = mgrs.substr(3, 2);
|
||||
if (prec > 0)
|
||||
{
|
||||
part3 = mgrs.substr(5, prec);
|
||||
part4 = mgrs.substr(5 + prec, prec);
|
||||
}
|
||||
fprintf(stdout,
|
||||
"%13.10f, %13.10f = %s %s %s %s (UTM: %d %.2f, %.2f %c)\n",
|
||||
lon,
|
||||
lat,
|
||||
part1.c_str(),
|
||||
part2.c_str(),
|
||||
part3.c_str(),
|
||||
part4.c_str(),
|
||||
zone,
|
||||
x,
|
||||
y,
|
||||
northp ? 'N' : 'S');
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,437 @@
|
||||
|
||||
|
||||
#include "lts_coord_trans.h"
|
||||
|
||||
#include <GeographicLib/Geocentric.hpp>
|
||||
#include <GeographicLib/MGRS.hpp>
|
||||
#include <GeographicLib/UTMUPS.hpp>
|
||||
#include <QtCore/QPointF>
|
||||
#include <QtCore/QRectF>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QStringList>
|
||||
#include <iomanip>
|
||||
#include <utility>
|
||||
|
||||
namespace LtsCoordTrans
|
||||
{
|
||||
|
||||
static long s_conversion_counter_MGRS_Forward = 0;
|
||||
static long s_conversion_counter_MGRS_Reverse = 0;
|
||||
|
||||
static long s_conversion_counter_WGS84_Forward = 0;
|
||||
static long s_conversion_counter_WGS84_Reverse = 0;
|
||||
|
||||
static long s_conversion_counter_UTMUPS_Forward = 0;
|
||||
static long s_conversion_counter_UTMUPS_Reverse = 0;
|
||||
|
||||
inline void incrementCounter(long &counterRef)
|
||||
{
|
||||
#ifdef LTS_DEBUG
|
||||
++counterRef;
|
||||
#endif
|
||||
}
|
||||
|
||||
static inline std::string cleanupMgrsString(const QString &in)
|
||||
{
|
||||
const QByteArray ba(in.toLatin1());
|
||||
std::string ret;
|
||||
ret.reserve(ba.size());
|
||||
for (const char c : ba)
|
||||
{
|
||||
if (c != ' ')
|
||||
ret += c;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString UtmCoordinate::mgrs(int prec, bool withSpaces) const
|
||||
{
|
||||
if (prec < 0)
|
||||
prec = 0;
|
||||
if (prec > 11)
|
||||
prec = 11;
|
||||
|
||||
std::string mgrs;
|
||||
try
|
||||
{
|
||||
GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs);
|
||||
incrementCounter(s_conversion_counter_MGRS_Forward);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
fprintf(stderr, "Error while calculating mgrs from utm %d, %9.6f, %9.6f: %s\n", zone, x, y, e.what());
|
||||
mgrs.clear();
|
||||
}
|
||||
|
||||
QString ret;
|
||||
if (withSpaces && mgrs.size() == static_cast<size_t>(5 + 2 * prec))
|
||||
{
|
||||
|
||||
const char *ptr = mgrs.c_str();
|
||||
|
||||
ret = QString::fromLatin1(ptr, 3) + QLatin1Char(' ') + QString::fromLatin1(ptr + 3, 2);
|
||||
if (prec > 0)
|
||||
{
|
||||
ret += QLatin1Char(' ') + QString::fromLatin1(ptr + 5, prec) + QLatin1Char(' ') + QString::fromLatin1(ptr + 5 + prec, prec);
|
||||
}
|
||||
}
|
||||
else
|
||||
ret = QString::fromLatin1(mgrs.c_str(), mgrs.size());
|
||||
return ret;
|
||||
}
|
||||
|
||||
QPointF UtmCoordinate::toPoint() const
|
||||
{
|
||||
return {x, y};
|
||||
}
|
||||
|
||||
QPointF UtmCoordinate::geographic(double offsetX, double offsetY) const
|
||||
{
|
||||
QPointF ret;
|
||||
try
|
||||
{
|
||||
GeographicLib::UTMUPS::Reverse(zone, northp, x + offsetX, y + offsetY, ret.ry(), ret.rx());
|
||||
incrementCounter(s_conversion_counter_UTMUPS_Reverse);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
fprintf(stderr, "Error while calculating geographic from utm %d, %9.6f, %9.6f: %s\n", zone, x + offsetX, y + offsetY, e.what());
|
||||
ret = QPointF();
|
||||
}
|
||||
return ret * deg2rad();
|
||||
}
|
||||
|
||||
QPointF UtmCoordinate::geographic(const QPointF &offset) const
|
||||
{
|
||||
return geographic(offset.x(), offset.y());
|
||||
}
|
||||
|
||||
QPointF UtmCoordinate::relativeTo(const UtmCoordinate &other) const
|
||||
{
|
||||
if (zone == other.zone)
|
||||
return QPointF(x - other.x, y - other.y);
|
||||
|
||||
UtmCoordinate otherInMyZone = fromGeographic(other.geographic(), zone);
|
||||
return QPointF(x - otherInMyZone.x, y - otherInMyZone.y);
|
||||
}
|
||||
|
||||
UtmCoordinate UtmCoordinate::fromGeographic(double lon, double lat, int setZone)
|
||||
{
|
||||
|
||||
lon *= rad2deg();
|
||||
lat *= rad2deg();
|
||||
|
||||
UtmCoordinate ret;
|
||||
try
|
||||
{
|
||||
|
||||
GeographicLib::UTMUPS::Forward(lat, lon, ret.zone, ret.northp, ret.x, ret.y, setZone);
|
||||
incrementCounter(s_conversion_counter_UTMUPS_Forward);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
fprintf(stderr, "Error while calculating mgrs base point from %9.6f, %9.6f: %s\n", lat, lon, e.what());
|
||||
ret = UtmCoordinate();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
UtmCoordinate UtmCoordinate::fromGeographic(const QPointF &lonLat, int setZone)
|
||||
{
|
||||
return fromGeographic(lonLat.x(), lonLat.y(), setZone);
|
||||
}
|
||||
|
||||
UtmCoordinate UtmCoordinate::fromMgrs(const QString &mgrs)
|
||||
{
|
||||
UtmCoordinate utm;
|
||||
try
|
||||
{
|
||||
int prec;
|
||||
|
||||
GeographicLib::MGRS::Reverse(cleanupMgrsString(mgrs), utm.zone, utm.northp, utm.x, utm.y, prec, true);
|
||||
incrementCounter(s_conversion_counter_MGRS_Reverse);
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
fprintf(stderr, "Error while converting mgrs coordinate %s to utm: %s\n", qPrintable(mgrs), e.what());
|
||||
utm = UtmCoordinate();
|
||||
}
|
||||
return utm;
|
||||
}
|
||||
|
||||
QString mgrsFromGeographic(double lon, double lat, int prec, bool withSpaces)
|
||||
{
|
||||
return UtmCoordinate::fromGeographic(lon, lat, -1).mgrs(prec, withSpaces);
|
||||
}
|
||||
|
||||
QPointF geographicFromMgrs(const QString &mgrs)
|
||||
{
|
||||
return UtmCoordinate::fromMgrs(mgrs).geographic();
|
||||
}
|
||||
|
||||
QVector<UtmCoordinate> &staticDefaultSquaresHelper()
|
||||
{
|
||||
static QVector<UtmCoordinate> s_defaultMgrsSquares;
|
||||
return s_defaultMgrsSquares;
|
||||
}
|
||||
|
||||
UtmCoordinate &staticPreferedMgrsSquareHelper()
|
||||
{
|
||||
static UtmCoordinate s_preferedMgrsSquare;
|
||||
return s_preferedMgrsSquare;
|
||||
}
|
||||
|
||||
QVector<UtmCoordinate> &staticMgrsSquaresWithOffsetsHelper()
|
||||
{
|
||||
static QVector<UtmCoordinate> s_mgrsSquaresWithOffsets;
|
||||
return s_mgrsSquaresWithOffsets;
|
||||
}
|
||||
|
||||
QRectF addMarginToArea(const QRectF &rect_LatLon_radiant, const QSize &marginMeter)
|
||||
{
|
||||
double lonMin, lonMax, latMin, latMmax;
|
||||
rect_LatLon_radiant.normalized().getCoords(&lonMin, &latMin, &lonMax, &latMmax);
|
||||
|
||||
LtsCoordTrans::UtmCoordinate basePoint;
|
||||
{
|
||||
|
||||
const QPointF centerRad = rect_LatLon_radiant.center();
|
||||
QString mgrs = LtsCoordTrans::mgrsFromGeographic(centerRad.x(), centerRad.y(), -1);
|
||||
mgrs += QStringLiteral("00000000000000");
|
||||
|
||||
basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs);
|
||||
}
|
||||
|
||||
const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin, basePoint.zone));
|
||||
const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax, basePoint.zone));
|
||||
|
||||
int offsetX = marginMeter.width();
|
||||
int offsetY = marginMeter.height();
|
||||
|
||||
const QPointF topLeft(lowerLeft.geographic(-offsetX, -offsetY));
|
||||
const QPointF bottomRight(upperRight.geographic(offsetX, offsetY));
|
||||
|
||||
QRectF res = QRectF(topLeft, bottomRight);
|
||||
return res;
|
||||
}
|
||||
|
||||
QVector<UtmCoordinate> getMgrsSquares(const QRectF &rect, bool setAsDefaultSquares, bool setAsPreferedSquare, bool setAsSquareWithMargin)
|
||||
{
|
||||
QVector<UtmCoordinate> ret = getMgrsSquaresInternal(rect);
|
||||
|
||||
if (setAsDefaultSquares)
|
||||
staticDefaultSquaresHelper() = ret;
|
||||
|
||||
if (setAsPreferedSquare)
|
||||
staticPreferedMgrsSquareHelper() = LtsCoordTrans::UtmCoordinate::fromGeographic(rect.center());
|
||||
|
||||
if (setAsSquareWithMargin)
|
||||
staticMgrsSquaresWithOffsetsHelper() = ret;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QVector<UtmCoordinate> getMgrsSquaresInternal(const QRectF &rect)
|
||||
{
|
||||
QVector<UtmCoordinate> ret;
|
||||
QVector<QString> tmpMgrsSquares;
|
||||
|
||||
double lonMin, lonMax, latMin, latMmax;
|
||||
rect.normalized().getCoords(&lonMin, &latMin, &lonMax, &latMmax);
|
||||
|
||||
bool withInSingleUtmSquare = true;
|
||||
{
|
||||
const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax));
|
||||
const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin));
|
||||
withInSingleUtmSquare = upperRight.zone == lowerLeft.zone;
|
||||
}
|
||||
|
||||
LtsCoordTrans::UtmCoordinate basePoint;
|
||||
{
|
||||
|
||||
QPointF centerRad = rect.center();
|
||||
QString mgrs = LtsCoordTrans::mgrsFromGeographic(centerRad.x(), centerRad.y(), -1);
|
||||
mgrs += QStringLiteral("00000000000000");
|
||||
|
||||
basePoint = LtsCoordTrans::UtmCoordinate::fromMgrs(mgrs);
|
||||
}
|
||||
|
||||
const UtmCoordinate upperRight(UtmCoordinate::fromGeographic(lonMax, latMmax, basePoint.zone));
|
||||
const UtmCoordinate lowerLeft(UtmCoordinate::fromGeographic(lonMin, latMin, basePoint.zone));
|
||||
|
||||
const double maxX(upperRight.x - lowerLeft.x);
|
||||
const double maxY(upperRight.y - lowerLeft.y);
|
||||
|
||||
const int increment = withInSingleUtmSquare ? 10000 : 1000;
|
||||
for (double x = 0; x <= maxX + increment; x += increment)
|
||||
{
|
||||
const double xOfs = std::min(x, maxX);
|
||||
for (double y = 0; y <= maxY + increment; y += increment)
|
||||
{
|
||||
const double yOfs = std::min(y, maxY);
|
||||
|
||||
const UtmCoordinate utm(UtmCoordinate::fromGeographic(lowerLeft.geographic(xOfs, yOfs)));
|
||||
const QString mgrs(utm.mgrs(0));
|
||||
|
||||
if (!tmpMgrsSquares.contains(mgrs))
|
||||
{
|
||||
tmpMgrsSquares += mgrs;
|
||||
ret += utm;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
QStringList getMgrsSurroundingSquares(const QRectF &geoRect, const QSize &offsetMeter)
|
||||
{
|
||||
|
||||
const QVector<UtmCoordinate> mainUtms = getMgrsSquaresInternal(geoRect);
|
||||
QStringList mainSquares;
|
||||
mainSquares.reserve(mainUtms.count());
|
||||
for (const auto &utm : mainUtms)
|
||||
mainSquares.append(utm.mgrs(0));
|
||||
|
||||
const QRectF rectWithOffset = addMarginToArea(geoRect, offsetMeter);
|
||||
|
||||
QVector<UtmCoordinate> mainUtmsWithOffset = getMgrsSquaresInternal(rectWithOffset);
|
||||
QStringList mainSquaresWithOffset;
|
||||
mainSquaresWithOffset.reserve(mainUtmsWithOffset.count());
|
||||
for (const auto &utm : std::as_const(mainUtmsWithOffset))
|
||||
mainSquaresWithOffset.append(utm.mgrs(0));
|
||||
|
||||
QStringList surroundingSquares;
|
||||
surroundingSquares.reserve(mainSquaresWithOffset.count());
|
||||
for (const auto &mgrs0 : mainSquaresWithOffset)
|
||||
{
|
||||
if (!mainSquares.contains(mgrs0))
|
||||
surroundingSquares.append(mgrs0);
|
||||
}
|
||||
|
||||
return surroundingSquares;
|
||||
}
|
||||
|
||||
QVector<UtmCoordinate> defaultMgrsSquareWithOffsets()
|
||||
{
|
||||
return staticMgrsSquaresWithOffsetsHelper();
|
||||
}
|
||||
|
||||
void setDefaultMgrsSquares(const QVector<QString> &squares)
|
||||
{
|
||||
QVector<UtmCoordinate> coords;
|
||||
coords.reserve(squares.size());
|
||||
for (const QString &s : squares)
|
||||
coords += UtmCoordinate::fromMgrs(s + QStringLiteral("00000000000000"));
|
||||
staticDefaultSquaresHelper() = coords;
|
||||
}
|
||||
|
||||
QVector<UtmCoordinate> defaultMgrsSquares()
|
||||
{
|
||||
return staticDefaultSquaresHelper();
|
||||
}
|
||||
|
||||
UtmCoordinate preferedMgrsSquare()
|
||||
{
|
||||
return staticPreferedMgrsSquareHelper();
|
||||
}
|
||||
|
||||
bool lonLatAltToGeocentric(double lon, double lat, double altitude, double &xOut, double &yOut, double &zOut)
|
||||
{
|
||||
|
||||
lon *= rad2deg();
|
||||
lat *= rad2deg();
|
||||
|
||||
try
|
||||
{
|
||||
GeographicLib::Geocentric::WGS84().Forward(lat, lon, altitude, xOut, yOut, zOut);
|
||||
incrementCounter(s_conversion_counter_WGS84_Forward);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
xOut = 0;
|
||||
yOut = 0;
|
||||
zOut = 0;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool geocentric2LonLatAlt(double x, double y, double z, double &lon, double &lat, double &altitude)
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
GeographicLib::Geocentric::WGS84().Reverse(x, y, z, lat, lon, altitude);
|
||||
incrementCounter(s_conversion_counter_WGS84_Reverse);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
lon = 0;
|
||||
lat = 0;
|
||||
altitude = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
lon *= deg2rad();
|
||||
lat *= deg2rad();
|
||||
return true;
|
||||
}
|
||||
|
||||
double distance(const QPointF &p1, const QPointF &p2)
|
||||
{
|
||||
return distance(p1.x(), p1.y(), p2.x(), p2.y());
|
||||
}
|
||||
|
||||
double squareDistance(const QPointF &p1, const QPointF &p2)
|
||||
{
|
||||
return squareDistance(p1.x(), p1.y(), p2.x(), p2.y());
|
||||
}
|
||||
|
||||
std::ostream &operator<<(std::ostream &stream, const UtmCoordinate &coord)
|
||||
{
|
||||
|
||||
stream << "UtmCoordinate(" << std::setprecision(8) << coord.x << "#" << std::setprecision(8) << coord.y << " zone=" << coord.zone << ")";
|
||||
return stream;
|
||||
}
|
||||
|
||||
void dumpConversionCounter()
|
||||
{
|
||||
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_MGRS_Forward : - mgrs : %ld\n", s_conversion_counter_MGRS_Forward);
|
||||
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_MGRS_Reverse - fromMgrs : %ld\n", s_conversion_counter_MGRS_Reverse);
|
||||
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_WGS84_Forward - lonLatAltToGeocentric : %ld\n", s_conversion_counter_WGS84_Forward);
|
||||
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_WGS84_Reverse - geocentric2LonLatAlt : %ld\n", s_conversion_counter_WGS84_Reverse);
|
||||
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_UTMUPS_Forward - fromGeographic : %ld\n", s_conversion_counter_UTMUPS_Forward);
|
||||
fprintf(stderr, "LtsCoordTrans: s_conversion_counter_UTMUPS_Reverse - geographic : %ld\n", s_conversion_counter_UTMUPS_Reverse);
|
||||
}
|
||||
|
||||
long getConversionCounter_MGRS_Forward()
|
||||
{
|
||||
return s_conversion_counter_MGRS_Forward;
|
||||
}
|
||||
|
||||
long getConversionCounter_MGRS_Reverse()
|
||||
{
|
||||
return s_conversion_counter_MGRS_Reverse;
|
||||
}
|
||||
|
||||
long getConversionCounter_WGS84_Forward()
|
||||
{
|
||||
return s_conversion_counter_WGS84_Forward;
|
||||
}
|
||||
|
||||
long getConversionCounter_WGS84_Reverse()
|
||||
{
|
||||
return s_conversion_counter_WGS84_Reverse;
|
||||
}
|
||||
|
||||
long getConversionCounter_UTMUPS_Forward()
|
||||
{
|
||||
return s_conversion_counter_UTMUPS_Forward;
|
||||
}
|
||||
|
||||
long getConversionCounter_UTMUPS_Reverse()
|
||||
{
|
||||
return s_conversion_counter_UTMUPS_Reverse;
|
||||
}
|
||||
|
||||
} // namespace LtsCoordTrans
|
||||
@@ -0,0 +1,183 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QVector>
|
||||
#include <cmath>
|
||||
#include "ltscoordtrans_export.h"
|
||||
#include <string>
|
||||
class QPointF;
|
||||
class QRectF;
|
||||
class QSize;
|
||||
class QString;
|
||||
|
||||
namespace LtsCoordTrans
|
||||
{
|
||||
|
||||
inline Q_CONSTEXPR double pi()
|
||||
{
|
||||
return M_PI;
|
||||
}
|
||||
|
||||
inline Q_CONSTEXPR double deg2rad()
|
||||
{
|
||||
return pi() / 180.;
|
||||
}
|
||||
|
||||
inline Q_CONSTEXPR double rad2deg()
|
||||
{
|
||||
return 1. / deg2rad();
|
||||
}
|
||||
|
||||
struct LTSCOORDTRANS_EXPORT UtmCoordinate
|
||||
{
|
||||
int zone = -1;
|
||||
bool northp = true;
|
||||
double x = 0.;
|
||||
double y = 0.;
|
||||
|
||||
UtmCoordinate() = default;
|
||||
|
||||
UtmCoordinate(int zone_, double x_, double y_, bool northp_ = true)
|
||||
: zone(zone_)
|
||||
, northp(northp_)
|
||||
, x(x_)
|
||||
, y(y_)
|
||||
{
|
||||
}
|
||||
|
||||
bool isValid() const { return zone > 0 && zone <= 60; }
|
||||
|
||||
QString mgrs(int prec = 5, bool withSpaces = true) const;
|
||||
|
||||
QPointF toPoint() const;
|
||||
|
||||
QPointF geographic(double offsetX = 0., double offsetY = 0.) const;
|
||||
|
||||
QPointF geographic(const QPointF &offset) const;
|
||||
|
||||
QPointF relativeTo(const UtmCoordinate &other) const;
|
||||
|
||||
static UtmCoordinate fromGeographic(double lon, double lat, int setZone = -1);
|
||||
|
||||
static UtmCoordinate fromGeographic(const QPointF &lonLat, int setZone = -1);
|
||||
|
||||
static UtmCoordinate fromMgrs(const QString &mgrs);
|
||||
};
|
||||
|
||||
LTSCOORDTRANS_EXPORT QString mgrsFromGeographic(double lon, double lat, int prec = 5, bool withSpaces = true);
|
||||
|
||||
LTSCOORDTRANS_EXPORT QPointF geographicFromMgrs(const QString &mgrs);
|
||||
|
||||
LTSCOORDTRANS_EXPORT QRectF addMarginToArea(const QRectF &rect_LatLon_radiant, const QSize &marginMeter);
|
||||
|
||||
LTSCOORDTRANS_EXPORT QVector<UtmCoordinate> getMgrsSquares(const QRectF &rect, bool setAsDefaultSquares, bool setAsPreferedSquare, bool setAsSquareWithMargin = false);
|
||||
|
||||
QVector<UtmCoordinate> getMgrsSquaresInternal(const QRectF &rect);
|
||||
|
||||
QStringList getMgrsSurroundingSquares(const QRectF &geoRect, const QSize &offsetMeter);
|
||||
|
||||
LTSCOORDTRANS_EXPORT QVector<UtmCoordinate> defaultMgrsSquareWithOffsets();
|
||||
|
||||
LTSCOORDTRANS_EXPORT void setDefaultMgrsSquares(const QVector<QString> &squares);
|
||||
|
||||
LTSCOORDTRANS_EXPORT QVector<UtmCoordinate> defaultMgrsSquares();
|
||||
|
||||
LTSCOORDTRANS_EXPORT UtmCoordinate preferedMgrsSquare();
|
||||
|
||||
LTSCOORDTRANS_EXPORT bool lonLatAltToGeocentric(double lon, double lat, double altitude, double &xOut, double &yOut, double &zOut);
|
||||
|
||||
LTSCOORDTRANS_EXPORT bool geocentric2LonLatAlt(double x, double y, double z, double &lon, double &lat, double &altitude);
|
||||
|
||||
inline double squareDistance(const UtmCoordinate &p1, const UtmCoordinate &p2)
|
||||
{
|
||||
if (p1.zone != p2.zone)
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
return std::nan("");
|
||||
}
|
||||
return std::pow(p1.x - p2.x, 2) + std::pow(p1.y - p2.y, 2);
|
||||
}
|
||||
|
||||
LTSCOORDTRANS_EXPORT double squareDistance(const QPointF &p1, const QPointF &p2);
|
||||
|
||||
inline double squareDistance(const double lon1, const double lat1, const double lon2, const double lat2)
|
||||
{
|
||||
const auto p1(UtmCoordinate::fromGeographic(lon1, lat1));
|
||||
const auto p2(UtmCoordinate::fromGeographic(lon2, lat2, p1.zone));
|
||||
return squareDistance(p1, p2);
|
||||
}
|
||||
|
||||
inline double squareDistance(const UtmCoordinate &p1, const double lon2, const double lat2)
|
||||
{
|
||||
const auto p2(UtmCoordinate::fromGeographic(lon2, lat2, p1.zone));
|
||||
return squareDistance(p1, p2);
|
||||
}
|
||||
|
||||
inline double distance(const UtmCoordinate &p1, const UtmCoordinate &p2)
|
||||
{
|
||||
if (p1.zone != p2.zone)
|
||||
{
|
||||
Q_ASSERT(false);
|
||||
return std::nan("");
|
||||
}
|
||||
return sqrt(squareDistance(p1, p2));
|
||||
}
|
||||
|
||||
LTSCOORDTRANS_EXPORT double distance(const QPointF &p1, const QPointF &p2);
|
||||
|
||||
inline double distance(const double lon1, const double lat1, const double lon2, const double lat2)
|
||||
{
|
||||
return sqrt(squareDistance(lon1, lat1, lon2, lat2));
|
||||
}
|
||||
|
||||
inline double distance(const UtmCoordinate &p1, const double lon2, const double lat2)
|
||||
{
|
||||
return sqrt(squareDistance(p1, lon2, lat2));
|
||||
}
|
||||
|
||||
LTSCOORDTRANS_EXPORT void dumpConversionCounter();
|
||||
|
||||
LTSCOORDTRANS_EXPORT long getConversionCounter_MGRS_Forward();
|
||||
LTSCOORDTRANS_EXPORT long getConversionCounter_MGRS_Reverse();
|
||||
LTSCOORDTRANS_EXPORT long getConversionCounter_WGS84_Forward();
|
||||
LTSCOORDTRANS_EXPORT long getConversionCounter_WGS84_Reverse();
|
||||
LTSCOORDTRANS_EXPORT long getConversionCounter_UTMUPS_Forward();
|
||||
LTSCOORDTRANS_EXPORT long getConversionCounter_UTMUPS_Reverse();
|
||||
|
||||
LTSCOORDTRANS_EXPORT std::ostream &operator<<(std::ostream &stream, const UtmCoordinate &coord);
|
||||
|
||||
class LTSCOORDTRANS_EXPORT UtmCoordinate3D : public LtsCoordTrans::UtmCoordinate
|
||||
{
|
||||
using BaseClass = LtsCoordTrans::UtmCoordinate;
|
||||
|
||||
public:
|
||||
UtmCoordinate3D() { reset(); }
|
||||
|
||||
explicit UtmCoordinate3D(const LtsCoordTrans::UtmCoordinate &utm2d, double _zValue = std::nan(""))
|
||||
: BaseClass()
|
||||
{
|
||||
zone = utm2d.zone;
|
||||
northp = utm2d.northp;
|
||||
x = utm2d.x;
|
||||
y = utm2d.y;
|
||||
zValue = _zValue;
|
||||
}
|
||||
|
||||
void reset()
|
||||
{
|
||||
zone = -1;
|
||||
northp = true;
|
||||
|
||||
x = 0.;
|
||||
y = 0.;
|
||||
zValue = std::nan("");
|
||||
}
|
||||
|
||||
double z() const { return zValue; }
|
||||
|
||||
protected:
|
||||
double zValue{std::nan("")};
|
||||
};
|
||||
|
||||
} // namespace LtsCoordTrans
|
||||
+414
@@ -0,0 +1,414 @@
|
||||
|
||||
|
||||
#include "lts_coord_trans_unit_test.h"
|
||||
|
||||
#include "lts_coord_trans.h"
|
||||
|
||||
#include <GeographicLib/Math.hpp>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtTest/QTest>
|
||||
#include <utility>
|
||||
|
||||
QTEST_MAIN(coordtrans_test)
|
||||
|
||||
void coordtrans_test::initTestCase()
|
||||
{
|
||||
qDebug("sizeof(UtmCoordinate) = %lu", sizeof(LtsCoordTrans::UtmCoordinate));
|
||||
}
|
||||
|
||||
void coordtrans_test::cleanupTestCase() {}
|
||||
|
||||
void coordtrans_test::testFromGeographic()
|
||||
{
|
||||
|
||||
LtsCoordTrans::UtmCoordinate utm;
|
||||
QPointF lonlat;
|
||||
|
||||
lonlat = QPointF(8.6264954126, 53.1138437183) * LtsCoordTrans::deg2rad();
|
||||
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1);
|
||||
QCOMPARE(utm.mgrs(0), QStringLiteral("32U MD"));
|
||||
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
|
||||
QCOMPARE(utm.mgrs(8), QStringLiteral("32U MD 74999999 84999999"));
|
||||
|
||||
lonlat = QPointF(11.3003696521, 52.8224535191) * LtsCoordTrans::deg2rad();
|
||||
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1);
|
||||
QCOMPARE(utm.mgrs(0), QStringLiteral("32U PD"));
|
||||
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
|
||||
QCOMPARE(utm.mgrs(5), QStringLiteral("32U PD 55000 55000"));
|
||||
|
||||
lonlat = QPointF(54.8821607021, 24.2631973752) * LtsCoordTrans::deg2rad();
|
||||
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y(), -1);
|
||||
QCOMPARE(utm.mgrs(0), QStringLiteral("40R BM"));
|
||||
utm = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
|
||||
QCOMPARE(utm.mgrs(5), QStringLiteral("40R BM 85000 85000"));
|
||||
|
||||
utm = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("40R BM 00000 00000"));
|
||||
QCOMPARE(utm.mgrs(0), QStringLiteral("40Q BM"));
|
||||
}
|
||||
|
||||
void coordtrans_test::testMgrsFromGeographic()
|
||||
{
|
||||
|
||||
QString mgrs;
|
||||
QPointF lonlat;
|
||||
|
||||
lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad();
|
||||
mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9);
|
||||
QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000"));
|
||||
|
||||
mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 8, false);
|
||||
QCOMPARE(mgrs, QStringLiteral("32UMD7450000084000000"));
|
||||
}
|
||||
|
||||
void coordtrans_test::testRelativeMetricalOffet_data()
|
||||
{
|
||||
|
||||
QTest::addColumn<QPoint>("offset");
|
||||
QTest::addColumn<QString>("correctMgrs");
|
||||
|
||||
QTest::newRow("set 1") << QPoint(5, 5) << QStringLiteral("32U PD 99995 99995");
|
||||
|
||||
QTest::newRow("set 2") << QPoint(100, 100) << QStringLiteral("32U QE 00090 00090");
|
||||
QTest::newRow("set 3") << QPoint(500, 500) << QStringLiteral("32U QE 00490 00490");
|
||||
}
|
||||
|
||||
void coordtrans_test::testRelativeMetricalOffet()
|
||||
{
|
||||
QFETCH(QPoint, offset);
|
||||
QFETCH(QString, correctMgrs);
|
||||
|
||||
const LtsCoordTrans::UtmCoordinate &utmRef = LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32U PD 99990 99990"));
|
||||
qDebug() << "utmRef = " << utmRef.mgrs();
|
||||
|
||||
{
|
||||
LtsCoordTrans::UtmCoordinate utmRaw;
|
||||
utmRaw.x = utmRef.x + offset.x();
|
||||
utmRaw.y = utmRef.y + offset.y();
|
||||
utmRaw.zone = utmRef.zone;
|
||||
qDebug() << "offset = " << offset << " - utmRaw = " << utmRaw.mgrs();
|
||||
|
||||
QCOMPARE(utmRaw.mgrs(), correctMgrs);
|
||||
}
|
||||
|
||||
{
|
||||
const LtsCoordTrans::UtmCoordinate utmSimple(utmRef.zone, utmRef.x + offset.x(), utmRef.y + offset.y());
|
||||
|
||||
qDebug() << "offset = " << offset << " - utmSimple = " << utmSimple.mgrs();
|
||||
QCOMPARE(utmSimple.mgrs(), correctMgrs);
|
||||
}
|
||||
}
|
||||
|
||||
QRectF createArea(const QString &base, int xCenter, int yCenter, int width, int height)
|
||||
{
|
||||
|
||||
QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height);
|
||||
LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base)));
|
||||
const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top()));
|
||||
const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom()));
|
||||
|
||||
QRectF rect(topLeft, bottomRight);
|
||||
return rect;
|
||||
}
|
||||
|
||||
void coordtrans_test::testAddMarginToArea()
|
||||
{
|
||||
|
||||
{
|
||||
qDebug() << "Altmark";
|
||||
QRectF area = createArea(QStringLiteral("32U PD 0000000000"), 50000, 10000, 30000, 30000);
|
||||
QRectF area1Increased = LtsCoordTrans::addMarginToArea(area, QSize(5000, 20000));
|
||||
|
||||
qDebug() << "topLeft : " << qSetRealNumberPrecision(15) << area.topLeft() << " - degree:" << area.topLeft() * LtsCoordTrans::rad2deg();
|
||||
qDebug() << "topLeftInc : " << qSetRealNumberPrecision(15) << area1Increased.topLeft() << " - degree:" << area1Increased.topLeft() * LtsCoordTrans::rad2deg();
|
||||
|
||||
qDebug() << "bottomRight : " << qSetRealNumberPrecision(15) << area.bottomRight() << " - degree:" << area.bottomRight() * LtsCoordTrans::rad2deg();
|
||||
qDebug() << "bottomRightInc : " << qSetRealNumberPrecision(15) << area1Increased.bottomRight()
|
||||
<< " - degree:" << area1Increased.bottomRight() * LtsCoordTrans::rad2deg();
|
||||
|
||||
QVERIFY2(area1Increased.topLeft().x() < area.topLeft().x(), "addMarginToArea topLeft X failed");
|
||||
QVERIFY2(area1Increased.topLeft().y() < area.topLeft().y(), "addMarginToArea topLeft Y failed");
|
||||
|
||||
QVERIFY2(area1Increased.bottomRight().x() > area.bottomRight().x(), "addMarginToArea bottomRight X failed");
|
||||
QVERIFY2(area1Increased.bottomRight().y() > area.bottomRight().y(), "addMarginToArea bottomRight Y failed");
|
||||
}
|
||||
|
||||
{
|
||||
qDebug() << "Luxemburg";
|
||||
QRectF area = createArea(QStringLiteral("32U LA 0000000000"), 100, 100, 5000, 5000);
|
||||
QRectF area1Increased = LtsCoordTrans::addMarginToArea(area, QSize(50000, 20000));
|
||||
|
||||
qDebug() << "topLeft : " << qSetRealNumberPrecision(15) << area.topLeft() << " - degree:" << area.topLeft() * LtsCoordTrans::rad2deg();
|
||||
qDebug() << "topLeftInc : " << qSetRealNumberPrecision(15) << area1Increased.topLeft() << " - degree:" << area1Increased.topLeft() * LtsCoordTrans::rad2deg();
|
||||
|
||||
qDebug() << "bottomRight : " << qSetRealNumberPrecision(15) << area.bottomRight() << " - degree:" << area.bottomRight() * LtsCoordTrans::rad2deg();
|
||||
qDebug() << "bottomRightInc : " << qSetRealNumberPrecision(15) << area1Increased.bottomRight()
|
||||
<< " - degree:" << area1Increased.bottomRight() * LtsCoordTrans::rad2deg();
|
||||
|
||||
QVERIFY2(area1Increased.topLeft().x() < area.topLeft().x(), "addMarginToArea topLeft X failed");
|
||||
QVERIFY2(area1Increased.topLeft().y() < area.topLeft().y(), "addMarginToArea topLeft Y failed");
|
||||
|
||||
QVERIFY2(area1Increased.bottomRight().x() > area.bottomRight().x(), "addMarginToArea bottomRight X failed");
|
||||
QVERIFY2(area1Increased.bottomRight().y() > area.bottomRight().y(), "addMarginToArea bottomRight Y failed");
|
||||
}
|
||||
}
|
||||
|
||||
void coordtrans_test::testIncreaseRect()
|
||||
{
|
||||
|
||||
QPointF lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad();
|
||||
QString mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9);
|
||||
QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000"));
|
||||
|
||||
const LtsCoordTrans::UtmCoordinate centerPoint = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
|
||||
|
||||
LtsCoordTrans::UtmCoordinate topLeftUtm;
|
||||
LtsCoordTrans::UtmCoordinate topLeftUtm2;
|
||||
|
||||
{
|
||||
QPointF topLeftGeo = centerPoint.geographic(-1111, 2222);
|
||||
QPointF bottomRightGeo = centerPoint.geographic(3000, -4000);
|
||||
|
||||
topLeftUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(topLeftGeo);
|
||||
const LtsCoordTrans::UtmCoordinate bottomRightUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(bottomRightGeo);
|
||||
qDebug() << "topLeftUtm: " << topLeftUtm.x << " - " << qSetRealNumberPrecision(15) << topLeftUtm.y;
|
||||
qDebug() << "bottomRightUtm: " << bottomRightUtm.x << " - " << qSetRealNumberPrecision(15) << bottomRightUtm.y;
|
||||
}
|
||||
|
||||
{
|
||||
topLeftUtm2.x = centerPoint.x - 1111;
|
||||
topLeftUtm2.y = centerPoint.y + 2222;
|
||||
|
||||
LtsCoordTrans::UtmCoordinate bottomRightUtm2;
|
||||
bottomRightUtm2.x = centerPoint.x + 3000;
|
||||
bottomRightUtm2.y = centerPoint.y - 4000;
|
||||
|
||||
qDebug() << "topLeftUtm2: " << topLeftUtm2.x << " - " << qSetRealNumberPrecision(15) << topLeftUtm2.y;
|
||||
qDebug() << "bottomRightUtm2: " << bottomRightUtm2.x << " - " << qSetRealNumberPrecision(15) << bottomRightUtm2.y;
|
||||
}
|
||||
|
||||
QVERIFY2(qFuzzyCompare(topLeftUtm.x, topLeftUtm2.x), "testIncreaseRect x failed");
|
||||
QVERIFY2(qFuzzyCompare(topLeftUtm.y, topLeftUtm2.y), "testIncreaseRect y failed");
|
||||
}
|
||||
|
||||
void coordtrans_test::testIncreaseRect2()
|
||||
{
|
||||
|
||||
QPointF lonlat = QPointF(8.6191049344, 53.1048309844) * LtsCoordTrans::deg2rad();
|
||||
QString mgrs = LtsCoordTrans::mgrsFromGeographic(lonlat.x(), lonlat.y(), 9);
|
||||
QCOMPARE(mgrs, QStringLiteral("32U MD 745000000 840000000"));
|
||||
|
||||
QRectF myRect;
|
||||
QRectF myRectIncreased;
|
||||
|
||||
{
|
||||
const LtsCoordTrans::UtmCoordinate centerPoint = LtsCoordTrans::UtmCoordinate::fromGeographic(lonlat.x(), lonlat.y());
|
||||
|
||||
QPointF topLeftGeo = centerPoint.geographic(-3000, 2000);
|
||||
QPointF bottomRightGeo = centerPoint.geographic(3000, -2000);
|
||||
myRect = QRectF(topLeftGeo, bottomRightGeo).normalized();
|
||||
|
||||
qDebug() << "myRect : " << myRect;
|
||||
qDebug() << "myRect center : " << myRect.center();
|
||||
qDebug() << "myRect height : " << myRect.height();
|
||||
}
|
||||
|
||||
{
|
||||
const QPointF centerGeo = myRect.center();
|
||||
const LtsCoordTrans::UtmCoordinate centerUtm = LtsCoordTrans::UtmCoordinate::fromGeographic(centerGeo);
|
||||
|
||||
QPointF topLeftGeo = centerUtm.geographic(-6000, 4000);
|
||||
QPointF bottomRightGeo = centerUtm.geographic(6000, -4000);
|
||||
|
||||
myRectIncreased = QRectF(topLeftGeo, bottomRightGeo).normalized();
|
||||
|
||||
qDebug() << "myRectIncreased : " << myRectIncreased;
|
||||
qDebug() << "myRectIncreased center: " << myRectIncreased.center();
|
||||
qDebug() << "myRectIncreased height: " << myRectIncreased.height();
|
||||
}
|
||||
|
||||
QVERIFY2((int)(myRectIncreased.center().x() * 10000) == (int)(myRect.center().x() * 10000), "testIncreaseRect2 centerX failed");
|
||||
QVERIFY2((int)(myRectIncreased.center().y() * 10000) == (int)(myRect.center().y() * 10000), "testIncreaseRect2 centerY failed");
|
||||
|
||||
QVERIFY2(myRectIncreased.width() > myRect.width(), "testIncreaseRect2 width failed");
|
||||
QVERIFY2(myRectIncreased.height() > myRect.height(), "testIncreaseRect2 height failed");
|
||||
}
|
||||
|
||||
void coordtrans_test::testGeographicFromMgrs()
|
||||
{
|
||||
|
||||
QString mgrs(QStringLiteral("32UPD7654398765"));
|
||||
|
||||
QPointF geographic = LtsCoordTrans::geographicFromMgrs(mgrs);
|
||||
QCOMPARE(geographic, QPointF(11.6436815550, 53.2088411454) * LtsCoordTrans::deg2rad());
|
||||
}
|
||||
|
||||
void coordtrans_test::testMgrsSquares()
|
||||
{
|
||||
|
||||
checkArea(QStringLiteral("32U PD 0000000000"), 50000, 10000, 30000, 30000, {QStringLiteral("32U PC"), QStringLiteral("32U PD")});
|
||||
checkArea(QStringLiteral("32U MD 0000000000"), 50000, 10000, 30000, 30000, {QStringLiteral("32U MC"), QStringLiteral("32U MD")});
|
||||
checkArea(QStringLiteral("32U MD 0000000000"),
|
||||
89999,
|
||||
89999,
|
||||
21000,
|
||||
21000,
|
||||
{QStringLiteral("32U MD"), QStringLiteral("32U ME"), QStringLiteral("32U ND"), QStringLiteral("32U NE")});
|
||||
|
||||
checkArea(QStringLiteral("39Q XG 0000000000"), 40000, 35000, 60000, 70000, {QStringLiteral("39Q XG"), QStringLiteral("39R XG")});
|
||||
checkArea(QStringLiteral("40R CP 0000000000"), 94000, 9000, 7000, 16000, {QStringLiteral("40R CP")});
|
||||
checkArea(QStringLiteral("40R DN 0000000000"), 500, 95500, 12000, 7000, {QStringLiteral("40R CN"), QStringLiteral("40R DN")});
|
||||
checkArea(QStringLiteral("40R DN 0000000000"),
|
||||
500,
|
||||
96760,
|
||||
30000,
|
||||
30000,
|
||||
{QStringLiteral("40R CN"), QStringLiteral("40R CP"), QStringLiteral("40R DN"), QStringLiteral("40R DP")});
|
||||
checkArea(QStringLiteral("40R BM 0000000000"), 59000, 59500, 46000, 40000, {QStringLiteral("40Q BM"), QStringLiteral("40R BM")});
|
||||
|
||||
checkArea(QStringLiteral("32U LA 0000000000"),
|
||||
10000,
|
||||
10000,
|
||||
5000,
|
||||
5000,
|
||||
{
|
||||
QStringLiteral("32U LA"),
|
||||
});
|
||||
|
||||
checkArea(QStringLiteral("32U LA 0000000000"),
|
||||
100,
|
||||
100,
|
||||
60000,
|
||||
60000,
|
||||
{QStringLiteral("31U GQ"), QStringLiteral("31U GR"), QStringLiteral("32U KA"), QStringLiteral("32U KV"), QStringLiteral("32U LA"), QStringLiteral("32U LV")});
|
||||
|
||||
checkArea(QStringLiteral("32U LA 0000000000"),
|
||||
100,
|
||||
100,
|
||||
150000,
|
||||
150000,
|
||||
{QStringLiteral("31U FQ"),
|
||||
QStringLiteral("31U FR"),
|
||||
QStringLiteral("31U GQ"),
|
||||
QStringLiteral("31U GR"),
|
||||
QStringLiteral("32U KA"),
|
||||
QStringLiteral("32U KV"),
|
||||
QStringLiteral("32U LA"),
|
||||
QStringLiteral("32U LV")});
|
||||
|
||||
checkArea(QStringLiteral("32U LA 0000000000"),
|
||||
100,
|
||||
100,
|
||||
200000,
|
||||
200000,
|
||||
{QStringLiteral("31U FP"),
|
||||
QStringLiteral("31U FQ"),
|
||||
QStringLiteral("31U FR"),
|
||||
QStringLiteral("31U GP"),
|
||||
QStringLiteral("31U GQ"),
|
||||
QStringLiteral("31U GR"),
|
||||
QStringLiteral("31U GS"),
|
||||
QStringLiteral("32U KA"),
|
||||
QStringLiteral("32U KB"),
|
||||
QStringLiteral("32U KV"),
|
||||
QStringLiteral("32U LA"),
|
||||
QStringLiteral("32U LB"),
|
||||
QStringLiteral("32U LV"),
|
||||
QStringLiteral("32U MA"),
|
||||
QStringLiteral("32U MB"),
|
||||
QStringLiteral("32U MV")});
|
||||
}
|
||||
|
||||
QVector<LtsCoordTrans::UtmCoordinate> checkAreaGetMgrsSquares(const QString &base, int xCenter, int yCenter, int width, int height)
|
||||
{
|
||||
|
||||
QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height);
|
||||
LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base)));
|
||||
const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top()));
|
||||
const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom()));
|
||||
|
||||
const auto squares = LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), false, false);
|
||||
|
||||
QStringList src;
|
||||
src.reserve(squares.size());
|
||||
for (const auto &utm : squares)
|
||||
src += utm.mgrs(0);
|
||||
src.sort();
|
||||
|
||||
return squares;
|
||||
}
|
||||
|
||||
void coordtrans_test::testMgrsSquaresWithSurrounding()
|
||||
{
|
||||
|
||||
int initialSize = 5000;
|
||||
|
||||
QVector<LtsCoordTrans::UtmCoordinate> squares1 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, initialSize, initialSize);
|
||||
|
||||
QVERIFY2(squares1.size() == 1, "checkAreaGetMgrsSquares failed");
|
||||
|
||||
int biggerSize = initialSize + (1 * 110000);
|
||||
QVector<LtsCoordTrans::UtmCoordinate> squares9 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, biggerSize, biggerSize);
|
||||
|
||||
QVERIFY2(squares9.size() == 9, "checkAreaGetMgrsSquares biggerSize failed");
|
||||
|
||||
int muchBiggerSize = initialSize + (3 * 110000);
|
||||
QVector<LtsCoordTrans::UtmCoordinate> square33 = checkAreaGetMgrsSquares(QStringLiteral("32U NC 0000000000"), 50000, 50000, muchBiggerSize, muchBiggerSize);
|
||||
|
||||
QVERIFY2(square33.size() == 33, "checkAreaGetMgrsSquares muchBiggerSize failed");
|
||||
|
||||
QStringList string1;
|
||||
string1.reserve(squares1.size());
|
||||
for (const LtsCoordTrans::UtmCoordinate &item : std::as_const(squares1))
|
||||
string1.append(item.mgrs(0));
|
||||
|
||||
QStringList string9;
|
||||
string9.reserve(squares9.size());
|
||||
for (const LtsCoordTrans::UtmCoordinate &item : std::as_const(squares9))
|
||||
string9.append(item.mgrs(0));
|
||||
|
||||
QStringList string33;
|
||||
string33.reserve(square33.size());
|
||||
for (const LtsCoordTrans::UtmCoordinate &item : std::as_const(square33))
|
||||
string33.append(item.mgrs(0));
|
||||
|
||||
{
|
||||
QStringList surroundingStrings;
|
||||
surroundingStrings.reserve(squares9.size());
|
||||
for (const QString &str : std::as_const(string9))
|
||||
if (!string1.contains(str))
|
||||
surroundingStrings.append(str);
|
||||
|
||||
QVERIFY2(surroundingStrings.size() == 8, "checkAreaGetMgrsSquares squaresDiff failed");
|
||||
}
|
||||
|
||||
{
|
||||
QStringList surroundingStrings;
|
||||
surroundingStrings.reserve(string33.size());
|
||||
for (const QString &str : std::as_const(string33))
|
||||
if (!string1.contains(str))
|
||||
surroundingStrings.append(str);
|
||||
|
||||
QVERIFY2(surroundingStrings.size() == 32, "checkAreaGetMgrsSquares squaresDiff failed");
|
||||
}
|
||||
}
|
||||
|
||||
void coordtrans_test::checkArea(const QString &base, int xCenter, int yCenter, int width, int height, const QStringList &coords)
|
||||
{
|
||||
qDebug() << "checkArea : base: " << base;
|
||||
QRectF areaRect = QRectF(xCenter - width / 2, yCenter - height / 2, width, height);
|
||||
LtsCoordTrans::UtmCoordinate utmBase(LtsCoordTrans::UtmCoordinate::fromGeographic(LtsCoordTrans::geographicFromMgrs(base)));
|
||||
const QPointF topLeft(utmBase.geographic(areaRect.left(), areaRect.top()));
|
||||
const QPointF bottomRight(utmBase.geographic(areaRect.right(), areaRect.bottom()));
|
||||
qDebug() << "TopLeft: " << topLeft * LtsCoordTrans::rad2deg();
|
||||
qDebug() << "BottomRight: " << bottomRight * LtsCoordTrans::rad2deg();
|
||||
|
||||
const auto squares = LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), false, false);
|
||||
|
||||
QStringList src;
|
||||
src.reserve(squares.size());
|
||||
for (const auto &utm : squares)
|
||||
src += utm.mgrs(0);
|
||||
src.sort();
|
||||
qDebug() << "src: '" << src << "' expected: '" << coords << "'";
|
||||
QCOMPARE(src, coords);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QStringList>
|
||||
|
||||
class coordtrans_test : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void cleanupTestCase();
|
||||
void testFromGeographic();
|
||||
void testMgrsFromGeographic();
|
||||
|
||||
void testRelativeMetricalOffet_data();
|
||||
void testRelativeMetricalOffet();
|
||||
|
||||
void testAddMarginToArea();
|
||||
void testIncreaseRect();
|
||||
void testIncreaseRect2();
|
||||
|
||||
void testGeographicFromMgrs();
|
||||
void testMgrsSquares();
|
||||
|
||||
void testMgrsSquaresWithSurrounding();
|
||||
|
||||
private:
|
||||
void checkArea(const QString &base, int xCenter, int yCenter, int width, int height, const QStringList &coords);
|
||||
};
|
||||
@@ -0,0 +1,186 @@
|
||||
|
||||
|
||||
#include "lts_coord_widget.h"
|
||||
|
||||
#include "lts_coord_widget_p.h"
|
||||
|
||||
#include <LtsCoordTrans/lts_coord_trans.h>
|
||||
#include <QtWidgets/QToolButton>
|
||||
|
||||
namespace LtsCoordWidget
|
||||
{
|
||||
|
||||
LtsCoordWidget::LtsCoordWidget(DisplayType dt, QWidget *parent)
|
||||
: QWidget(parent)
|
||||
, m_displayType(dt)
|
||||
{
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
void LtsCoordWidget::setCoordinate(const QPointF &geoCoord, bool bUseAsDefaultValue)
|
||||
{
|
||||
if (m_coordinate != geoCoord)
|
||||
emit coordinateChanged(geoCoord);
|
||||
if (bUseAsDefaultValue)
|
||||
m_defaultCoordinate = geoCoord;
|
||||
if (m_inputWidgetHandler)
|
||||
m_inputWidgetHandler->setCoordinate(geoCoord);
|
||||
}
|
||||
|
||||
void LtsCoordWidget::clear()
|
||||
{
|
||||
if (m_inputWidgetHandler)
|
||||
m_inputWidgetHandler->clear();
|
||||
}
|
||||
|
||||
void LtsCoordWidget::setCoordinate(double lon, double lat, bool bUseAsDefaultValue)
|
||||
{
|
||||
setCoordinate(QPointF(lon, lat), bUseAsDefaultValue);
|
||||
}
|
||||
|
||||
QPointF LtsCoordWidget::coordinate() const
|
||||
{
|
||||
if (!m_inputWidgetHandler)
|
||||
return QPointF();
|
||||
return m_inputWidgetHandler->coordinate();
|
||||
}
|
||||
|
||||
LtsCoordWidget::DisplayType LtsCoordWidget::displayType() const
|
||||
{
|
||||
return m_displayType;
|
||||
}
|
||||
|
||||
void LtsCoordWidget::setDisplayType(LtsCoordWidget::DisplayType dt)
|
||||
{
|
||||
if (m_displayType == dt)
|
||||
return;
|
||||
m_displayType = dt;
|
||||
rebuildUi();
|
||||
}
|
||||
|
||||
bool LtsCoordWidget::withResetButton() const
|
||||
{
|
||||
return m_bWithResetButton;
|
||||
}
|
||||
|
||||
void LtsCoordWidget::setWithResetButton(bool bWith)
|
||||
{
|
||||
if (m_bWithResetButton == bWith)
|
||||
return;
|
||||
m_bWithResetButton = bWith;
|
||||
if (m_inputWidgetHandler)
|
||||
m_inputWidgetHandler->resetButton()->setVisible(m_bWithResetButton);
|
||||
}
|
||||
|
||||
bool LtsCoordWidget::showToolTip() const
|
||||
{
|
||||
return m_bShowToolTip;
|
||||
}
|
||||
|
||||
void LtsCoordWidget::setShowToolTip(bool bShow)
|
||||
{
|
||||
if (m_bShowToolTip == bShow)
|
||||
return;
|
||||
m_bShowToolTip = bShow;
|
||||
|
||||
updateToolTip();
|
||||
}
|
||||
|
||||
bool LtsCoordWidget::mgrsSquareEditable() const
|
||||
{
|
||||
return m_bMgrsSquareEditable;
|
||||
}
|
||||
|
||||
void LtsCoordWidget::setMgrsSquareEditable(bool bEditable)
|
||||
{
|
||||
if (m_bMgrsSquareEditable == bEditable)
|
||||
return;
|
||||
m_bMgrsSquareEditable = bEditable;
|
||||
if (m_inputWidgetHandler)
|
||||
m_inputWidgetHandler->setSquaresEditable(bEditable);
|
||||
}
|
||||
|
||||
void LtsCoordWidget::setWithSurroundingMgrsSquares(bool bEnable)
|
||||
{
|
||||
if (m_inputWidgetHandler)
|
||||
m_inputWidgetHandler->setWithSurroundingSquare(bEnable);
|
||||
}
|
||||
|
||||
void LtsCoordWidget::setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords)
|
||||
{
|
||||
if (m_inputWidgetHandler)
|
||||
m_inputWidgetHandler->setMgrsSquares(geoCoords);
|
||||
}
|
||||
|
||||
bool LtsCoordWidget::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::ToolTip)
|
||||
updateToolTip();
|
||||
return QWidget::event(event);
|
||||
}
|
||||
|
||||
void LtsCoordWidget::rebuildUi()
|
||||
{
|
||||
|
||||
if (m_inputWidgetHandler)
|
||||
{
|
||||
m_inputWidgetHandler->cleanup();
|
||||
delete m_inputWidgetHandler;
|
||||
delete layout();
|
||||
}
|
||||
|
||||
switch (m_displayType)
|
||||
{
|
||||
case DisplayType::MGRS5:
|
||||
m_inputWidgetHandler = new MgrsEdit(5, this);
|
||||
break;
|
||||
case DisplayType::MGRS7:
|
||||
m_inputWidgetHandler = new MgrsEdit(7, this);
|
||||
break;
|
||||
case DisplayType::UTM:
|
||||
|
||||
qt_assert("DisplayType::UTM not yet implemented", __FILE__, __LINE__);
|
||||
break;
|
||||
case DisplayType::WGS84:
|
||||
m_inputWidgetHandler = new Wgs84Edit(this);
|
||||
break;
|
||||
}
|
||||
if (!m_inputWidgetHandler)
|
||||
return;
|
||||
|
||||
m_inputWidgetHandler->setSquaresEditable(m_bMgrsSquareEditable);
|
||||
m_inputWidgetHandler->resetButton()->setVisible(m_bWithResetButton);
|
||||
connect(m_inputWidgetHandler->resetButton(), &QToolButton::clicked, this, &LtsCoordWidget::onResetToDefaultCoordinate);
|
||||
connect(m_inputWidgetHandler, &LtsCoordInputWidgetHandler::coordinateChanged, this, &LtsCoordWidget::coordinateChanged);
|
||||
connect(m_inputWidgetHandler, &LtsCoordInputWidgetHandler::invalidCoordinate, this, &LtsCoordWidget::invalidCoordinate);
|
||||
if (!m_defaultCoordinate.isNull())
|
||||
m_inputWidgetHandler->setCoordinate(m_defaultCoordinate);
|
||||
}
|
||||
|
||||
void LtsCoordWidget::updateToolTip()
|
||||
{
|
||||
QString tt;
|
||||
if (m_bShowToolTip)
|
||||
{
|
||||
const QPointF p(coordinate());
|
||||
if (!p.isNull())
|
||||
{
|
||||
const QString mgrs = LtsCoordTrans::mgrsFromGeographic(p.x(), p.y());
|
||||
const QPointF p2(p * LtsCoordTrans::rad2deg());
|
||||
tt = tr("MGRS: %1\nlat/lon: %2/%3").arg(mgrs).arg(p2.y(), 0, 'f', 10).arg(p2.x(), 0, 'f', 10);
|
||||
}
|
||||
}
|
||||
setToolTip(tt);
|
||||
}
|
||||
|
||||
void LtsCoordWidget::onResetToDefaultCoordinate()
|
||||
{
|
||||
if (!m_inputWidgetHandler)
|
||||
return;
|
||||
if (m_defaultCoordinate.isNull())
|
||||
m_inputWidgetHandler->clear();
|
||||
m_inputWidgetHandler->setCoordinate(m_defaultCoordinate);
|
||||
}
|
||||
|
||||
} // namespace LtsCoordWidget
|
||||
@@ -0,0 +1,77 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <LtsCoordTrans/lts_coord_trans.h>
|
||||
#include <LtsCoordTrans/ltscoordwidget_export.h>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
class QToolButton;
|
||||
|
||||
namespace LtsCoordWidget
|
||||
{
|
||||
class LtsCoordInputWidgetHandler;
|
||||
|
||||
class LTSCOORDWIDGET_EXPORT LtsCoordWidget : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class DisplayType
|
||||
{
|
||||
MGRS5,
|
||||
MGRS7,
|
||||
UTM,
|
||||
WGS84,
|
||||
MGRS = DisplayType::MGRS5
|
||||
};
|
||||
Q_ENUM(DisplayType)
|
||||
Q_PROPERTY(DisplayType displayType READ displayType WRITE setDisplayType)
|
||||
Q_PROPERTY(bool withResetButton READ withResetButton WRITE setWithResetButton)
|
||||
Q_PROPERTY(bool showToolTip READ showToolTip WRITE setShowToolTip)
|
||||
Q_PROPERTY(bool mgrsSquareEditable READ mgrsSquareEditable WRITE setMgrsSquareEditable)
|
||||
|
||||
explicit LtsCoordWidget(QWidget *parent = nullptr)
|
||||
: LtsCoordWidget(LtsCoordWidget::DisplayType::MGRS, parent)
|
||||
{
|
||||
}
|
||||
|
||||
explicit LtsCoordWidget(DisplayType dt, QWidget *parent = nullptr);
|
||||
|
||||
QPointF coordinate() const;
|
||||
DisplayType displayType() const;
|
||||
bool withResetButton() const;
|
||||
bool showToolTip() const;
|
||||
bool mgrsSquareEditable() const;
|
||||
bool event(QEvent *event) override;
|
||||
public Q_SLOTS:
|
||||
|
||||
void setCoordinate(const QPointF &geoCoord, bool bUseAsDefaultValue = true);
|
||||
void setCoordinate(double lon, double lat, bool bUseAsDefaultValue = true);
|
||||
void setDisplayType(LtsCoordWidget::DisplayType dt);
|
||||
void setWithResetButton(bool bWith);
|
||||
void setShowToolTip(bool bShow);
|
||||
void setMgrsSquareEditable(bool bEditable);
|
||||
void setWithSurroundingMgrsSquares(bool bEnable);
|
||||
void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords);
|
||||
void clear();
|
||||
|
||||
Q_SIGNALS:
|
||||
void coordinateChanged(const QPointF &newGeoCoord);
|
||||
void invalidCoordinate();
|
||||
|
||||
private:
|
||||
void rebuildUi();
|
||||
void updateToolTip();
|
||||
private Q_SLOTS:
|
||||
void onResetToDefaultCoordinate();
|
||||
|
||||
private:
|
||||
bool m_bWithResetButton = false;
|
||||
bool m_bShowToolTip = true;
|
||||
bool m_bMgrsSquareEditable = false;
|
||||
LtsCoordWidget::DisplayType m_displayType = LtsCoordWidget::DisplayType::MGRS;
|
||||
LtsCoordInputWidgetHandler *m_inputWidgetHandler = nullptr;
|
||||
QPointF m_coordinate;
|
||||
QPointF m_defaultCoordinate;
|
||||
};
|
||||
} // namespace LtsCoordWidget
|
||||
+55
@@ -0,0 +1,55 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Mgrs</class>
|
||||
<widget class="QWidget" name="Mgrs">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>322</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QComboBox" name="cbxZone"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="leEast">
|
||||
<property name="toolTip">
|
||||
<string>Pos X</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="leNorth">
|
||||
<property name="toolTip">
|
||||
<string>Pos Y</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="tbReset">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>:/style/arrow_back.svg</normaloff>:/style/arrow_back.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,304 @@
|
||||
|
||||
|
||||
#include "lts_coord_widget_p.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace LtsCoordWidget
|
||||
{
|
||||
|
||||
void LtsCoordInputWidgetHandler::onCoordinateDataChanged()
|
||||
{
|
||||
const QPointF coord(coordinate());
|
||||
if (coord.isNull())
|
||||
{
|
||||
emit invalidCoordinate();
|
||||
return;
|
||||
}
|
||||
emit coordinateChanged(coord);
|
||||
}
|
||||
|
||||
MgrsEdit::MgrsEdit(int precision, QWidget *widget)
|
||||
: LtsCoordInputWidgetHandler(widget)
|
||||
, m_precision(precision)
|
||||
, m_ui(new Ui::Mgrs)
|
||||
{
|
||||
m_ui->setupUi(widget);
|
||||
|
||||
if (m_precision < 0)
|
||||
m_precision = 0;
|
||||
if (m_precision > 11)
|
||||
m_precision = 11;
|
||||
const int upperLimit = pow(10, m_precision) - 1;
|
||||
m_ui->leEast->setValidator(new QIntValidator(0, upperLimit, m_ui->leEast));
|
||||
m_ui->leNorth->setValidator(new QIntValidator(0, upperLimit, m_ui->leNorth));
|
||||
setDefaultMgrsSquares();
|
||||
|
||||
connect(m_ui->cbxZone, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MgrsEdit::onCoordinateDataChanged);
|
||||
connect(m_ui->leEast, &QLineEdit::editingFinished, this, &MgrsEdit::onCoordinateDataChanged);
|
||||
connect(m_ui->leNorth, &QLineEdit::editingFinished, this, &MgrsEdit::onCoordinateDataChanged);
|
||||
}
|
||||
|
||||
MgrsEdit::~MgrsEdit()
|
||||
{
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
bool MgrsEdit::isValid()
|
||||
{
|
||||
|
||||
const QString &east = m_ui->leEast->text();
|
||||
if (east.size() != m_precision)
|
||||
return false;
|
||||
|
||||
const QString &north = m_ui->leNorth->text();
|
||||
if (north.size() != m_precision)
|
||||
return false;
|
||||
|
||||
if (m_ui->cbxZone->currentIndex() < 0)
|
||||
return false;
|
||||
|
||||
if (m_ui->cbxZone->currentText().isEmpty())
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void MgrsEdit::setCoordinate(const QPointF &geoCoord)
|
||||
{
|
||||
const QString mgrsString = LtsCoordTrans::mgrsFromGeographic(geoCoord.x(), geoCoord.y(), m_precision, false);
|
||||
if (mgrsString.isEmpty())
|
||||
{
|
||||
m_ui->leEast->clear();
|
||||
m_ui->leNorth->clear();
|
||||
return;
|
||||
}
|
||||
|
||||
m_ui->leEast->setText(QString());
|
||||
m_ui->leNorth->setText(QString());
|
||||
|
||||
const QString mgrsSquare = mgrsString.left(3) + QLatin1Char(' ') + mgrsString.mid(3, 2);
|
||||
QString east = mgrsString.mid(5, m_precision);
|
||||
QString north = mgrsString.mid(5 + m_precision, m_precision);
|
||||
int idx = m_ui->cbxZone->findText(mgrsSquare);
|
||||
if (idx < 0)
|
||||
{
|
||||
addMgrsSquare(mgrsSquare);
|
||||
idx = m_ui->cbxZone->findText(mgrsSquare);
|
||||
}
|
||||
m_ui->cbxZone->setCurrentIndex(idx);
|
||||
m_ui->leEast->setText(east);
|
||||
m_ui->leNorth->setText(north);
|
||||
}
|
||||
|
||||
static inline QString getMgrsCoord(QLineEdit *le, int prec)
|
||||
{
|
||||
QString ret = le->text();
|
||||
if (ret.isEmpty())
|
||||
return ret;
|
||||
while (ret.size() < prec)
|
||||
ret += QLatin1Char('0');
|
||||
le->setText(ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
QPointF MgrsEdit::coordinate() const
|
||||
{
|
||||
const QString east = getMgrsCoord(m_ui->leEast, m_precision);
|
||||
const QString north = getMgrsCoord(m_ui->leNorth, m_precision);
|
||||
if (east.isEmpty() || north.isEmpty() || m_ui->cbxZone->currentIndex() < 0)
|
||||
return QPointF();
|
||||
|
||||
const QString mgrsString(m_ui->cbxZone->currentText() + east + north);
|
||||
return LtsCoordTrans::geographicFromMgrs(mgrsString);
|
||||
}
|
||||
|
||||
QToolButton *MgrsEdit::resetButton()
|
||||
{
|
||||
return m_ui->tbReset;
|
||||
}
|
||||
|
||||
void MgrsEdit::cleanup()
|
||||
{
|
||||
|
||||
delete m_ui->horizontalLayout;
|
||||
delete m_ui->cbxZone;
|
||||
delete m_ui->leEast;
|
||||
delete m_ui->leNorth;
|
||||
delete m_ui->tbReset;
|
||||
}
|
||||
|
||||
void MgrsEdit::clear()
|
||||
{
|
||||
m_ui->cbxZone->clear();
|
||||
m_ui->leEast->clear();
|
||||
m_ui->leNorth->clear();
|
||||
setDefaultMgrsSquares();
|
||||
}
|
||||
|
||||
void MgrsEdit::setSquaresEditable(bool bEditable)
|
||||
{
|
||||
m_ui->cbxZone->setEditable(bEditable);
|
||||
}
|
||||
|
||||
void MgrsEdit::setWithSurroundingSquare(bool bEnabled)
|
||||
{
|
||||
m_withMgrsSquareOffset = bEnabled;
|
||||
}
|
||||
|
||||
static QStringList defaultMgrsSquares()
|
||||
{
|
||||
const QVector<LtsCoordTrans::UtmCoordinate> squares(LtsCoordTrans::defaultMgrsSquares());
|
||||
QStringList sl;
|
||||
sl.reserve(squares.size());
|
||||
for (const LtsCoordTrans::UtmCoordinate &c : squares)
|
||||
sl += c.mgrs(0);
|
||||
std::sort(sl.begin(), sl.end());
|
||||
return sl;
|
||||
}
|
||||
|
||||
static QString preferedMgrsSquare()
|
||||
{
|
||||
const LtsCoordTrans::UtmCoordinate c(LtsCoordTrans::preferedMgrsSquare());
|
||||
return c.mgrs(0);
|
||||
}
|
||||
|
||||
void MgrsEdit::setDefaultMgrsSquares()
|
||||
{
|
||||
m_ui->cbxZone->addItems(defaultMgrsSquares());
|
||||
m_ui->cbxZone->setCurrentIndex(m_ui->cbxZone->findText(preferedMgrsSquare()));
|
||||
}
|
||||
|
||||
void MgrsEdit::addMgrsSquare(const QString &newMgrsSquare)
|
||||
{
|
||||
QStringList sl = defaultMgrsSquares();
|
||||
sl += newMgrsSquare;
|
||||
if (m_withMgrsSquareOffset)
|
||||
{
|
||||
QStringList squares = utmCoordsToList(LtsCoordTrans::defaultMgrsSquareWithOffsets());
|
||||
|
||||
for (const auto &sq : std::as_const(squares))
|
||||
if (!sl.contains(sq))
|
||||
sl.append(sq);
|
||||
}
|
||||
std::sort(sl.begin(), sl.end());
|
||||
m_ui->cbxZone->clear();
|
||||
m_ui->cbxZone->addItems(sl);
|
||||
}
|
||||
|
||||
QStringList MgrsEdit::utmCoordsToList(const QVector<LtsCoordTrans::UtmCoordinate> &coords)
|
||||
{
|
||||
QStringList list;
|
||||
list.reserve(coords.count());
|
||||
for (const auto &utm : coords)
|
||||
list.append(utm.mgrs(0));
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void MgrsEdit::setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords)
|
||||
{
|
||||
QStringList sl = utmCoordsToList(geoCoords);
|
||||
if (sl.isEmpty())
|
||||
return;
|
||||
|
||||
std::sort(sl.begin(), sl.end());
|
||||
m_ui->cbxZone->clear();
|
||||
m_ui->cbxZone->addItems(sl);
|
||||
}
|
||||
|
||||
Wgs84Edit::Wgs84Edit(QWidget *widget)
|
||||
: LtsCoordInputWidgetHandler(widget)
|
||||
, m_ui(new Ui::Wgs84)
|
||||
{
|
||||
m_ui->setupUi(widget);
|
||||
|
||||
DoubleValidator *v = new DoubleValidator(-180, 180, 9, m_ui->leLon);
|
||||
v->setNotation(QDoubleValidator::StandardNotation);
|
||||
m_ui->leLon->setValidator(v);
|
||||
|
||||
v = new DoubleValidator(-180, 180, 9, m_ui->leLat);
|
||||
v->setNotation(QDoubleValidator::StandardNotation);
|
||||
m_ui->leLat->setValidator(v);
|
||||
|
||||
connect(m_ui->leLon, &QLineEdit::editingFinished, this, &Wgs84Edit::onCoordinateDataChanged);
|
||||
connect(m_ui->leLat, &QLineEdit::editingFinished, this, &Wgs84Edit::onCoordinateDataChanged);
|
||||
}
|
||||
|
||||
Wgs84Edit::~Wgs84Edit()
|
||||
{
|
||||
delete m_ui;
|
||||
}
|
||||
|
||||
bool Wgs84Edit::isValid()
|
||||
{
|
||||
|
||||
return (!m_ui->leLon->text().isEmpty()) && (!m_ui->leLat->text().isEmpty());
|
||||
}
|
||||
|
||||
void Wgs84Edit::setCoordinate(const QPointF &geoCoord)
|
||||
{
|
||||
const QPointF coord(geoCoord * LtsCoordTrans::rad2deg());
|
||||
const QLocale l;
|
||||
m_ui->leLon->setText(l.toString(coord.x(), 'f', 9));
|
||||
m_ui->leLat->setText(l.toString(coord.y(), 'f', 9));
|
||||
}
|
||||
|
||||
QPointF Wgs84Edit::coordinate() const
|
||||
{
|
||||
QPointF ret;
|
||||
const QLocale l;
|
||||
|
||||
const QString lonStr = m_ui->leLon->text().remove(l.groupSeparator());
|
||||
const QString latStr = m_ui->leLat->text().remove(l.groupSeparator());
|
||||
if (lonStr.isEmpty() || latStr.isEmpty())
|
||||
return ret;
|
||||
|
||||
ret.rx() = l.toDouble(lonStr);
|
||||
ret.ry() = l.toDouble(latStr);
|
||||
|
||||
return ret * LtsCoordTrans::deg2rad();
|
||||
}
|
||||
|
||||
QToolButton *Wgs84Edit::resetButton()
|
||||
{
|
||||
return m_ui->tbReset;
|
||||
}
|
||||
|
||||
void Wgs84Edit::cleanup()
|
||||
{
|
||||
|
||||
delete m_ui->horizontalLayout;
|
||||
delete m_ui->leLon;
|
||||
delete m_ui->leLat;
|
||||
delete m_ui->tbReset;
|
||||
delete m_ui->lblLon;
|
||||
delete m_ui->lblLat;
|
||||
}
|
||||
|
||||
void Wgs84Edit::clear()
|
||||
{
|
||||
m_ui->leLon->clear();
|
||||
m_ui->leLat->clear();
|
||||
}
|
||||
|
||||
DoubleValidator::DoubleValidator(double bottom, double top, int decimals, QObject *parent)
|
||||
: QDoubleValidator(bottom, top, decimals, parent)
|
||||
{
|
||||
}
|
||||
|
||||
QValidator::State DoubleValidator::validate(QString &input, int &pos) const
|
||||
{
|
||||
const QValidator::State ret = QDoubleValidator::validate(input, pos);
|
||||
if (ret != QValidator::Acceptable)
|
||||
return ret;
|
||||
QLocale l;
|
||||
const QChar gs = l.groupSeparator().at(0);
|
||||
if (gs.isNull())
|
||||
return ret;
|
||||
if (input.contains(gs))
|
||||
return QValidator::Invalid;
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace LtsCoordWidget
|
||||
@@ -0,0 +1,101 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ui_lts_coord_widget_mgrs.h"
|
||||
#include "ui_lts_coord_widget_wgs84.h"
|
||||
|
||||
#include <LtsCoordTrans/lts_coord_trans.h>
|
||||
#include <QtWidgets/QComboBox>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
#include <QtWidgets/QWidget>
|
||||
|
||||
namespace LtsCoordWidget
|
||||
{
|
||||
class LtsCoordInputWidgetHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LtsCoordInputWidgetHandler(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool isValid() = 0;
|
||||
virtual void setCoordinate(const QPointF &geoCoord) = 0;
|
||||
virtual QPointF coordinate() const = 0;
|
||||
virtual QToolButton *resetButton() = 0;
|
||||
virtual void cleanup() = 0;
|
||||
virtual void clear() = 0;
|
||||
virtual void setSquaresEditable(bool bEditable) = 0;
|
||||
virtual void setWithSurroundingSquare(bool bEnabled) = 0;
|
||||
virtual void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords) = 0;
|
||||
protected Q_SLOTS:
|
||||
void onCoordinateDataChanged();
|
||||
Q_SIGNALS:
|
||||
void coordinateChanged(const QPointF &newGeoCoord);
|
||||
void invalidCoordinate();
|
||||
};
|
||||
|
||||
class MgrsEdit final : public LtsCoordInputWidgetHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit MgrsEdit(int precision, QWidget *widget);
|
||||
~MgrsEdit();
|
||||
|
||||
bool isValid() override;
|
||||
void setCoordinate(const QPointF &geoCoord) override;
|
||||
QPointF coordinate() const override;
|
||||
QToolButton *resetButton() override;
|
||||
void cleanup() override;
|
||||
void clear() override;
|
||||
void setSquaresEditable(bool bEditable) override;
|
||||
void setWithSurroundingSquare(bool bEnabled) override;
|
||||
void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords) override;
|
||||
|
||||
private:
|
||||
void setDefaultMgrsSquares();
|
||||
void addMgrsSquare(const QString &newMgrsSquare);
|
||||
QStringList utmCoordsToList(const QVector<LtsCoordTrans::UtmCoordinate> &coords);
|
||||
|
||||
private:
|
||||
int m_precision;
|
||||
bool m_withMgrsSquareOffset = false;
|
||||
Ui::Mgrs *m_ui;
|
||||
};
|
||||
|
||||
class Wgs84Edit final : public LtsCoordInputWidgetHandler
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Wgs84Edit(QWidget *widget);
|
||||
~Wgs84Edit();
|
||||
|
||||
bool isValid() override;
|
||||
void setCoordinate(const QPointF &geoCoord) override;
|
||||
QPointF coordinate() const override;
|
||||
QToolButton *resetButton() override;
|
||||
void cleanup() override;
|
||||
void clear() override;
|
||||
|
||||
void setSquaresEditable(bool bEditable) override { Q_UNUSED(bEditable); }
|
||||
|
||||
void setWithSurroundingSquare(bool bEnabled) override { Q_UNUSED(bEnabled); }
|
||||
|
||||
void setMgrsSquares(const QVector<LtsCoordTrans::UtmCoordinate> &geoCoords) override { Q_UNUSED(geoCoords); }
|
||||
|
||||
private:
|
||||
Ui::Wgs84 *m_ui;
|
||||
};
|
||||
|
||||
class DoubleValidator final : public QDoubleValidator
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
DoubleValidator(double bottom, double top, int decimals, QObject *parent = nullptr);
|
||||
|
||||
QValidator::State validate(QString &input, int &pos) const override;
|
||||
};
|
||||
} // namespace LtsCoordWidget
|
||||
@@ -0,0 +1,100 @@
|
||||
|
||||
|
||||
#include "lts_coord_widget.h"
|
||||
|
||||
#include <LtsCoordTrans/lts_coord_trans.h>
|
||||
#include <QtWidgets/QApplication>
|
||||
#include <QtWidgets/QDialog>
|
||||
#include <QtWidgets/QLabel>
|
||||
#include <QtWidgets/QPushButton>
|
||||
#include <QtWidgets/QVBoxLayout>
|
||||
|
||||
static void updateLabel(const QPointF &coord, QLabel *lbl, int prec)
|
||||
{
|
||||
QString mgrs = LtsCoordTrans::mgrsFromGeographic(coord.x(), coord.y(), prec);
|
||||
QString str =
|
||||
QStringLiteral("mgrs: %1, lon: %2, lat: %3").arg(mgrs).arg(coord.x() * LtsCoordTrans::rad2deg(), 0, 'f', 10).arg(coord.y() * LtsCoordTrans::rad2deg(), 0, 'f', 10);
|
||||
lbl->setText(str);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
LtsCoordTrans::UtmCoordinate base(LtsCoordTrans::UtmCoordinate::fromMgrs(QStringLiteral("32UMD00")));
|
||||
QPointF topLeft(base.geographic(+50000, -50000));
|
||||
QPointF bottomRight(base.geographic(-50000, 50000));
|
||||
LtsCoordTrans::getMgrsSquares(QRectF(topLeft, bottomRight), true, true);
|
||||
|
||||
QDialog *dialog(new QDialog);
|
||||
QVBoxLayout *lay(new QVBoxLayout(dialog));
|
||||
|
||||
const QPointF defCoord(QPointF(53.0, 8.0) * LtsCoordTrans::deg2rad());
|
||||
|
||||
LtsCoordWidget::LtsCoordWidget *wMgrs5(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS, dialog));
|
||||
wMgrs5->setWithResetButton(true);
|
||||
wMgrs5->setCoordinate(defCoord, true);
|
||||
lay->addWidget(wMgrs5);
|
||||
QLabel *lbl5(new QLabel(dialog));
|
||||
lay->addWidget(lbl5);
|
||||
QObject::connect(wMgrs5, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, lbl5, [lbl5](const QPointF &coord) { updateLabel(coord, lbl5, 5); });
|
||||
|
||||
LtsCoordWidget::LtsCoordWidget *wMgrs7(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7, dialog));
|
||||
wMgrs7->setWithResetButton(true);
|
||||
wMgrs7->setCoordinate(defCoord, true);
|
||||
lay->addWidget(wMgrs7);
|
||||
QLabel *lbl7(new QLabel(dialog));
|
||||
lay->addWidget(lbl7);
|
||||
QObject::connect(wMgrs7, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, wMgrs7, [lbl7](const QPointF &coord) { updateLabel(coord, lbl7, 7); });
|
||||
|
||||
LtsCoordWidget::LtsCoordWidget *w84(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84, dialog));
|
||||
w84->setWithResetButton(true);
|
||||
w84->setCoordinate(defCoord, true);
|
||||
lay->addWidget(w84);
|
||||
QLabel *lbl84(new QLabel(dialog));
|
||||
lay->addWidget(lbl84);
|
||||
QObject::connect(w84, &LtsCoordWidget::LtsCoordWidget::coordinateChanged, w84, [lbl84](const QPointF &coord) { updateLabel(coord, lbl84, 11); });
|
||||
|
||||
QHBoxLayout *hLay = new QHBoxLayout;
|
||||
lay->addLayout(hLay);
|
||||
LtsCoordWidget::LtsCoordWidget *wChange(new LtsCoordWidget::LtsCoordWidget(LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84, dialog));
|
||||
wChange->setWithResetButton(true);
|
||||
wChange->setCoordinate(defCoord, true);
|
||||
hLay->addWidget(wChange);
|
||||
QPushButton *pbReset(new QPushButton(QStringLiteral("With Reset"), dialog));
|
||||
hLay->addWidget(pbReset);
|
||||
QObject::connect(pbReset, &QPushButton::clicked, wChange, [wChange]() { wChange->setWithResetButton(!wChange->withResetButton()); });
|
||||
QPushButton *pbNext(new QPushButton(QStringLiteral("Next Type"), dialog));
|
||||
hLay->addWidget(pbNext);
|
||||
QObject::connect(pbNext,
|
||||
&QPushButton::clicked,
|
||||
wChange,
|
||||
[wChange]()
|
||||
{
|
||||
LtsCoordWidget::LtsCoordWidget::DisplayType dt = wChange->displayType();
|
||||
switch (dt)
|
||||
{
|
||||
case LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS5:
|
||||
dt = LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7;
|
||||
break;
|
||||
case LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS7:
|
||||
dt = LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84;
|
||||
break;
|
||||
case LtsCoordWidget::LtsCoordWidget::DisplayType::WGS84:
|
||||
dt = LtsCoordWidget::LtsCoordWidget::DisplayType::MGRS5;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
wChange->setDisplayType(dt);
|
||||
});
|
||||
|
||||
QPushButton *pb(new QPushButton(QStringLiteral("Quit"), dialog));
|
||||
lay->addWidget(pb);
|
||||
QObject::connect(pb, &QPushButton::clicked, qApp, QCoreApplication::quit);
|
||||
|
||||
dialog->show();
|
||||
int ret = app.exec();
|
||||
delete dialog;
|
||||
return ret;
|
||||
}
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>Wgs84</class>
|
||||
<widget class="QWidget" name="Wgs84">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>293</width>
|
||||
<height>32</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblLat">
|
||||
<property name="text">
|
||||
<string>Lat:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="leLat"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="lblLon">
|
||||
<property name="text">
|
||||
<string>Lon:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="leLon"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QToolButton" name="tbReset">
|
||||
<property name="icon">
|
||||
<iconset>
|
||||
<normaloff>icon:arrow_back.svg</normaloff>icon:arrow_back.svg</iconset>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
||||
@@ -0,0 +1,52 @@
|
||||
|
||||
|
||||
#include <GeographicLib/MGRS.hpp>
|
||||
#include <GeographicLib/UTMUPS.hpp>
|
||||
#include <string>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 2)
|
||||
{
|
||||
fprintf(stderr, "Usage: %s <mgrs>\n", argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string mgrs = argv[1];
|
||||
mgrs.erase(remove_if(mgrs.begin(), mgrs.end(), isspace), mgrs.end());
|
||||
|
||||
int prec;
|
||||
double lonDeg, latDeg;
|
||||
try
|
||||
{
|
||||
int zone;
|
||||
bool northp;
|
||||
double x, y;
|
||||
GeographicLib::MGRS::Reverse(mgrs, zone, northp, x, y, prec, true);
|
||||
GeographicLib::UTMUPS::Reverse(zone, northp, x, y, latDeg, lonDeg);
|
||||
|
||||
GeographicLib::UTMUPS::Forward(latDeg, lonDeg, zone, northp, x, y);
|
||||
GeographicLib::MGRS::Forward(zone, northp, x, y, prec, mgrs);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
fprintf(stderr, "Error converting %s to Lon/Lat coords\n", mgrs.c_str());
|
||||
return -2;
|
||||
}
|
||||
std::string part1, part2, part3, part4;
|
||||
part1 = mgrs.substr(0, 3);
|
||||
part2 = mgrs.substr(3, 2);
|
||||
part3 = mgrs.substr(5, prec);
|
||||
part4 = mgrs.substr(5 + prec, prec);
|
||||
fprintf(stdout,
|
||||
"%s %s %s %s = %13.10f, %13.10f ( %13.10f, %13.10f )\n",
|
||||
part1.c_str(),
|
||||
part2.c_str(),
|
||||
part3.c_str(),
|
||||
part4.c_str(),
|
||||
lonDeg,
|
||||
latDeg,
|
||||
lonDeg / 180. * M_PI,
|
||||
latDeg / 180. * M_PI);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
# Created by hjl, SLSE, 2016-05-31
|
||||
#
|
||||
|
||||
FIND_PACKAGE(OpenSSL)
|
||||
|
||||
SET(ltslicence_SRCS
|
||||
aes_crypt.cpp
|
||||
aes_crypt.h
|
||||
lts_licence.cpp
|
||||
lts_licence.h
|
||||
)
|
||||
|
||||
if(OPENSSL_FOUND)
|
||||
NGL_ADD_LIBRARY2(AAR SV WS LtsLicence SHARED ${ltslicence_SRCS})
|
||||
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsLicence
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
)
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsLicence
|
||||
PRIVATE
|
||||
${OPENSSL_LIBRARIES}
|
||||
Qt6::Xml
|
||||
NGLLogger
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
)
|
||||
|
||||
IF (BUILD_TESTING AND TARGET LtsLicence)
|
||||
ADD_EXECUTABLE(aes_crypt_test aes_crypt_test.cpp aes_crypt_test.h)
|
||||
TARGET_LINK_LIBRARIES(aes_crypt_test
|
||||
LtsLicence
|
||||
NGLLogger
|
||||
Qt6::Core
|
||||
Qt6::Test
|
||||
)
|
||||
ADD_TEST(NAME aes_crypt_test COMMAND $<TARGET_FILE:aes_crypt_test>)
|
||||
ENDIF()
|
||||
else()
|
||||
message(WARNING "LtsLicence skipped: OpenSSL not found")
|
||||
endif()
|
||||
@@ -0,0 +1,185 @@
|
||||
|
||||
|
||||
#include "aes_crypt.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QRandomGenerator>
|
||||
#include <openssl/err.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
namespace LtsLicence
|
||||
{
|
||||
struct AesCryptPrivate
|
||||
{
|
||||
AesCryptPrivate()
|
||||
: passPhrase(QLatin1String("LTS " LTS_VERSION_MAJOR "." LTS_VERSION_MINOR))
|
||||
, logger(NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsLicenceLib.AesCrypt"))
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER < 0x1010000fL
|
||||
ERR_load_crypto_strings();
|
||||
#endif
|
||||
}
|
||||
|
||||
~AesCryptPrivate()
|
||||
{
|
||||
#if OPENSSL_VERSION_NUMBER < 0x1010000fL
|
||||
ERR_free_strings();
|
||||
#endif
|
||||
}
|
||||
|
||||
QString passPhrase;
|
||||
NGL::INGLLogger &logger;
|
||||
};
|
||||
Q_GLOBAL_STATIC(AesCryptPrivate, s_Private)
|
||||
|
||||
void setPassPhrase(const QString &passPhrase)
|
||||
{
|
||||
s_Private->passPhrase = passPhrase;
|
||||
}
|
||||
|
||||
QString passPhrase()
|
||||
{
|
||||
return s_Private->passPhrase;
|
||||
}
|
||||
|
||||
QByteArray decryptData(const QByteArray &dataToDecrypt, const QString &pw, bool *bOk)
|
||||
{
|
||||
if (bOk)
|
||||
*bOk = false;
|
||||
|
||||
int ret;
|
||||
int outSize1 = 0;
|
||||
int outSize2 = 0;
|
||||
unsigned char key[32];
|
||||
|
||||
const QByteArray baPassword = pw.toUtf8();
|
||||
const unsigned char *dataPw = reinterpret_cast<const unsigned char *>(baPassword.data());
|
||||
int i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), nullptr, dataPw, baPassword.size(), 5, key, nullptr);
|
||||
if (i != 32)
|
||||
{
|
||||
ERROR_LOG(s_Private->logger) << "Wrong key size - should be 256 bits";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
const QByteArray baSalt = dataToDecrypt.left(EVP_MAX_IV_LENGTH);
|
||||
const unsigned char *salt = reinterpret_cast<const unsigned char *>(baSalt.data());
|
||||
|
||||
const QByteArray bufEncrypted = dataToDecrypt.mid(EVP_MAX_IV_LENGTH);
|
||||
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
ret = EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key, salt);
|
||||
if (ret == 0)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
char errbuf[256];
|
||||
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||
ERROR_LOG(s_Private->logger) << "EVP_DecryptInit_ex failed";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray bufDecrypted;
|
||||
bufDecrypted.resize(bufEncrypted.size() + EVP_CIPHER_CTX_block_size(ctx));
|
||||
const unsigned char *dataIn = reinterpret_cast<const unsigned char *>(bufEncrypted.data());
|
||||
unsigned char *dataOut = reinterpret_cast<unsigned char *>(bufDecrypted.data());
|
||||
|
||||
ret = EVP_DecryptUpdate(ctx, dataOut, &outSize1, dataIn, bufEncrypted.size());
|
||||
if (ret == 0)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
char errbuf[256];
|
||||
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||
ERROR_LOG(s_Private->logger) << "EVP_DecryptUpdate failed";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
ret = EVP_DecryptFinal_ex(ctx, dataOut + outSize1, &outSize2);
|
||||
if (ret == 0)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
char errbuf[256];
|
||||
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||
ERROR_LOG(s_Private->logger) << "EVP_DecryptFinal_ex failed";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
bufDecrypted.resize(outSize1 + outSize2);
|
||||
|
||||
if (bOk)
|
||||
*bOk = true;
|
||||
return bufDecrypted;
|
||||
}
|
||||
|
||||
QByteArray encryptData(const QByteArray &dataToEncrypt, const QString &pw, bool *bOk)
|
||||
{
|
||||
if (bOk)
|
||||
*bOk = false;
|
||||
|
||||
int ret;
|
||||
int outSize1 = 0;
|
||||
int outSize2 = 0;
|
||||
unsigned char key[32];
|
||||
QByteArray bufEncrypted;
|
||||
|
||||
const QByteArray baPassword = pw.toUtf8();
|
||||
const unsigned char *dataPw = reinterpret_cast<const unsigned char *>(baPassword.data());
|
||||
int i = EVP_BytesToKey(EVP_aes_256_cbc(), EVP_sha1(), nullptr, dataPw, baPassword.size(), 5, key, nullptr);
|
||||
if (i != 32)
|
||||
{
|
||||
ERROR_LOG(s_Private->logger) << "Wrong key size - should be 256 bits";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
QByteArray baSalt(EVP_MAX_IV_LENGTH, 0);
|
||||
for (int i = 0; i < EVP_MAX_IV_LENGTH; ++i)
|
||||
baSalt[i] = static_cast<char>(QRandomGenerator::global()->bounded(256));
|
||||
const unsigned char *salt = reinterpret_cast<const unsigned char *>(baSalt.data());
|
||||
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
ret = EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, key, salt);
|
||||
if (ret == 0)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
char errbuf[256];
|
||||
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||
ERROR_LOG(s_Private->logger) << "EVP_EncryptInit_ex failed";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
bufEncrypted = QByteArray::fromRawData(reinterpret_cast<const char *>(salt), EVP_MAX_IV_LENGTH);
|
||||
bufEncrypted.resize(bufEncrypted.size() + dataToEncrypt.size() + EVP_CIPHER_CTX_block_size(ctx));
|
||||
const unsigned char *dataIn = reinterpret_cast<const unsigned char *>(dataToEncrypt.data());
|
||||
unsigned char *dataOut = reinterpret_cast<unsigned char *>(bufEncrypted.data());
|
||||
dataOut += baSalt.size();
|
||||
ret = EVP_EncryptUpdate(ctx, dataOut, &outSize1, dataIn, dataToEncrypt.size());
|
||||
if (ret == 0)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
char errbuf[256];
|
||||
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||
ERROR_LOG(s_Private->logger) << "EVP_EncryptUpdate failed";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
ret = EVP_EncryptFinal_ex(ctx, dataOut + outSize1, &outSize2);
|
||||
if (ret == 0)
|
||||
{
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
char errbuf[256];
|
||||
ERR_error_string_n(ERR_get_error(), errbuf, sizeof(errbuf));
|
||||
ERROR_LOG(s_Private->logger) << "EVP_EncryptFinal_ex failed";
|
||||
return QByteArray();
|
||||
}
|
||||
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
|
||||
bufEncrypted.resize(outSize1 + outSize2 + baSalt.size());
|
||||
|
||||
if (bOk)
|
||||
*bOk = true;
|
||||
return bufEncrypted;
|
||||
}
|
||||
|
||||
} // namespace LtsLicence
|
||||
@@ -0,0 +1,18 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ltslicence_export.h"
|
||||
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace LtsLicence
|
||||
{
|
||||
|
||||
LTSLICENCE_EXPORT void setPassPhrase(const QString &passPhrase);
|
||||
LTSLICENCE_EXPORT QString passPhrase();
|
||||
|
||||
LTSLICENCE_EXPORT QByteArray decryptData(const QByteArray &dataToDecrypt, const QString &pw = passPhrase(), bool *bOk = nullptr);
|
||||
|
||||
LTSLICENCE_EXPORT QByteArray encryptData(const QByteArray &dataToEncrypt, const QString &pw = passPhrase(), bool *bOk = nullptr);
|
||||
} // namespace LtsLicence
|
||||
@@ -0,0 +1,53 @@
|
||||
|
||||
|
||||
#include "aes_crypt_test.h"
|
||||
|
||||
#include "aes_crypt.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
#include <QtCore/QCoreApplication>
|
||||
#include <QtTest/QTest>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
QTEST_MAIN(aes_crypt_test)
|
||||
|
||||
void aes_crypt_test::initTestCase()
|
||||
{
|
||||
NGL::NGLLoggerBootstrap::createNGLRootLogger("log4cxx.props", false);
|
||||
}
|
||||
|
||||
void aes_crypt_test::truePassTest()
|
||||
{
|
||||
qDebug("Simple TestCase");
|
||||
bool bOk;
|
||||
const QByteArray content("30.06.2013");
|
||||
const QString pw(QStringLiteral("aes_crypt_test"));
|
||||
const QByteArray enc = LtsLicence::encryptData(content, pw, &bOk);
|
||||
QVERIFY2(bOk, "Error encrypting data...");
|
||||
|
||||
QCOMPARE(((content.size() + 15 + EVP_MAX_IV_LENGTH) / 18) * 16, enc.size());
|
||||
const QByteArray dec = LtsLicence::decryptData(enc, pw, &bOk);
|
||||
QVERIFY2(bOk, "Error encrypting data...");
|
||||
QCOMPARE(dec, content);
|
||||
|
||||
const QByteArray bigContent(12345, '1');
|
||||
const QByteArray bigEnc = LtsLicence::encryptData(bigContent, pw, &bOk);
|
||||
QVERIFY2(bOk, "Error encrypting data...");
|
||||
|
||||
QCOMPARE(((bigContent.size() + 15 + EVP_MAX_IV_LENGTH) / 16) * 16, bigEnc.size());
|
||||
}
|
||||
|
||||
void aes_crypt_test::falsePassTest()
|
||||
{
|
||||
qDebug("False Password TestCase");
|
||||
bool bOk;
|
||||
const QByteArray content("30.06.2013");
|
||||
const QString pw(QStringLiteral("aes_crypt_test"));
|
||||
const QString pw2(QStringLiteral("aes_crypt_test2"));
|
||||
const QByteArray enc = LtsLicence::encryptData(content, pw, &bOk);
|
||||
QVERIFY2(bOk, "Error encrypting data...");
|
||||
QVERIFY2(enc.size() != 20, "Error encrypting data...");
|
||||
const QByteArray dec = LtsLicence::decryptData(enc, pw2, &bOk);
|
||||
QVERIFY2(!bOk, "Error encrypting data...");
|
||||
QVERIFY2(dec.isEmpty(), "decrypted data is not empty...");
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class aes_crypt_test : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void truePassTest();
|
||||
void falsePassTest();
|
||||
};
|
||||
@@ -0,0 +1,146 @@
|
||||
|
||||
|
||||
#include "lts_licence.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
#include <QtXml/QDomDocument>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
namespace LtsLicence
|
||||
{
|
||||
static bool extractLicence(const QDomElement &licenceElement, QVector<Licence> &licenceVec);
|
||||
|
||||
static NGL::INGLLogger &getLogger()
|
||||
{
|
||||
return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger("LtsLicence");
|
||||
}
|
||||
|
||||
QVector<Licence> readLicences(const QByteArray &unencryptedXmlData)
|
||||
{
|
||||
QVector<Licence> licenceVec;
|
||||
|
||||
QDomDocument xmlDoc;
|
||||
|
||||
const auto parseResult = xmlDoc.setContent(unencryptedXmlData);
|
||||
if (!parseResult)
|
||||
{
|
||||
ERROR_LOG(getLogger()) << "Error reading xml data, "
|
||||
<< "error in line " << int(parseResult.errorLine) << ", column " << int(parseResult.errorColumn)
|
||||
<< ", message: " << parseResult.errorMessage;
|
||||
return licenceVec;
|
||||
}
|
||||
|
||||
INFO_LOG(getLogger()) << "Try to extract licences";
|
||||
|
||||
QDomElement licenceElement = xmlDoc.documentElement().firstChildElement(QLatin1String("licence"));
|
||||
|
||||
while (!licenceElement.isNull())
|
||||
{
|
||||
if (!extractLicence(licenceElement, licenceVec))
|
||||
{
|
||||
licenceVec.clear();
|
||||
return licenceVec;
|
||||
}
|
||||
|
||||
licenceElement = licenceElement.nextSiblingElement(QLatin1String("licence"));
|
||||
}
|
||||
return licenceVec;
|
||||
}
|
||||
|
||||
QByteArray writeUnencryptedLicences(const QVector<Licence> &licences)
|
||||
{
|
||||
QDomDocument xmlDoc;
|
||||
|
||||
QDomProcessingInstruction instr = xmlDoc.createProcessingInstruction(QLatin1String("xml"), QLatin1String("version='1.0' encoding='UTF-8' standalone='yes'"));
|
||||
xmlDoc.appendChild(instr);
|
||||
|
||||
QDomElement root = xmlDoc.createElement(QLatin1String("licences"));
|
||||
xmlDoc.appendChild(root);
|
||||
|
||||
for (int i = 0; i < licences.size(); ++i)
|
||||
{
|
||||
QDomElement lic = xmlDoc.createElement(QLatin1String("licence"));
|
||||
root.appendChild(lic);
|
||||
|
||||
QDomElement element = xmlDoc.createElement(QLatin1String("name"));
|
||||
QDomText text = xmlDoc.createTextNode(licences[i].name);
|
||||
element.appendChild(text);
|
||||
lic.appendChild(element);
|
||||
|
||||
element = xmlDoc.createElement(QLatin1String("ignoreMaxCount"));
|
||||
text = xmlDoc.createTextNode(QString::number(licences[i].ignoreMaxCount));
|
||||
element.appendChild(text);
|
||||
lic.appendChild(element);
|
||||
|
||||
element = xmlDoc.createElement(QLatin1String("ignoreExpiration"));
|
||||
text = xmlDoc.createTextNode(QString::number(licences[i].ignoreExpiration));
|
||||
element.appendChild(text);
|
||||
lic.appendChild(element);
|
||||
|
||||
element = xmlDoc.createElement(QLatin1String("maxCount"));
|
||||
text = xmlDoc.createTextNode(QString::number(licences[i].maxCount));
|
||||
element.appendChild(text);
|
||||
lic.appendChild(element);
|
||||
|
||||
const QLatin1String format("dd.MM.yyyy hh:mm:ss");
|
||||
const QString qs = licences[i].expiration.toString(format);
|
||||
|
||||
element = xmlDoc.createElement(QLatin1String("expiration"));
|
||||
text = xmlDoc.createTextNode(qs);
|
||||
element.appendChild(text);
|
||||
lic.appendChild(element);
|
||||
}
|
||||
INFO_LOG(getLogger()) << "Generated XML Data...\n" << xmlDoc.toByteArray();
|
||||
return xmlDoc.toByteArray();
|
||||
}
|
||||
|
||||
bool extractLicence(const QDomElement &licenceElement, QVector<Licence> &licenceVec)
|
||||
{
|
||||
Licence licence;
|
||||
|
||||
QDomElement tag = licenceElement.firstChildElement(QLatin1String("name"));
|
||||
licence.name = tag.text();
|
||||
|
||||
tag = licenceElement.firstChildElement(QLatin1String("ignoreMaxCount"));
|
||||
licence.ignoreMaxCount = tag.text().toInt();
|
||||
|
||||
tag = licenceElement.firstChildElement(QLatin1String("ignoreExpiration"));
|
||||
licence.ignoreExpiration = tag.text().toInt();
|
||||
|
||||
tag = licenceElement.firstChildElement(QLatin1String("maxCount"));
|
||||
licence.maxCount = tag.text().toInt();
|
||||
|
||||
tag = licenceElement.firstChildElement(QLatin1String("expiration"));
|
||||
|
||||
const QString qs(tag.text());
|
||||
|
||||
const QLatin1String format("dd.MM.yyyy hh:mm:ss");
|
||||
const QDateTime qdt = QDateTime::fromString(qs, format);
|
||||
|
||||
if (!qdt.isValid())
|
||||
{
|
||||
ERROR_LOG(getLogger()) << "Invalid expiration format! " << qs << " used format: " << format;
|
||||
return false;
|
||||
}
|
||||
|
||||
licence.expiration = qdt;
|
||||
INFO_LOG(getLogger()) << "Read licence for:'" << licence.name << "'"
|
||||
<< " IgnoreMaxCount: " << licence.ignoreMaxCount << " IgnoreExpiration: " << licence.ignoreExpiration << " MaxCount: " << licence.maxCount
|
||||
<< " Expiration: " << licence.expiration.date().toString() << " " << licence.expiration.time().toString()
|
||||
<< " RefCount: " << licence.licenceRefCount;
|
||||
|
||||
licenceVec.append(licence);
|
||||
return true;
|
||||
}
|
||||
|
||||
QString Licence::toString() const
|
||||
{
|
||||
return QObject::tr("Name: %1, Count: %2, ExpireDate: %3, Expires: %4, UseMaxCount: %5")
|
||||
.arg(name)
|
||||
.arg(maxCount)
|
||||
.arg(expiration.toString())
|
||||
.arg(ignoreExpiration)
|
||||
.arg(ignoreMaxCount);
|
||||
}
|
||||
|
||||
} // namespace LtsLicence
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ltslicence_export.h"
|
||||
|
||||
#include <QtCore/QDateTime>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVector>
|
||||
|
||||
namespace LtsLicence
|
||||
{
|
||||
|
||||
struct LTSLICENCE_EXPORT Licence
|
||||
{
|
||||
QString name;
|
||||
bool ignoreMaxCount = false;
|
||||
bool ignoreExpiration = false;
|
||||
int maxCount = 0;
|
||||
QDateTime expiration;
|
||||
int licenceRefCount = 0;
|
||||
|
||||
QString toString() const;
|
||||
};
|
||||
|
||||
LTSLICENCE_EXPORT QVector<Licence> readLicences(const QByteArray &unencryptedXmlData);
|
||||
|
||||
LTSLICENCE_EXPORT QByteArray writeUnencryptedLicences(const QVector<Licence> &licences);
|
||||
} // namespace LtsLicence
|
||||
@@ -0,0 +1,38 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
# Created by Mike Wille, SLSE, 2016-01-13
|
||||
#
|
||||
|
||||
|
||||
# Make ${Qt6_Widgets} optional
|
||||
if(QT6_GUI_AVAILABLE AND TARGET Qt6::Widgets)
|
||||
set(Qt6_Widgets ${Qt6_Widgets})
|
||||
else()
|
||||
set(Qt6_Widgets "")
|
||||
endif()
|
||||
|
||||
|
||||
SET(LtsNoti_SRCS
|
||||
lts_notification.h
|
||||
lts_notification.cpp
|
||||
lts_notification_ctl.h
|
||||
lts_notification_ctl.cpp
|
||||
)
|
||||
|
||||
NGL_ADD_LIBRARY2(WS LtsNotification SHARED ${LtsNoti_SRCS})
|
||||
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsNotification
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
${CMAKE_CURRENT_BINARY_DIR}/../
|
||||
)
|
||||
|
||||
if(QT6_GUI_AVAILABLE)
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsNotification
|
||||
${Qt6_Widgets}
|
||||
)
|
||||
endif()
|
||||
|
||||
NGL_INSTALL_TARGET(LtsNotification)
|
||||
@@ -0,0 +1,66 @@
|
||||
|
||||
|
||||
#include "lts_notification.h"
|
||||
|
||||
#include "lts_notification_ctl.h"
|
||||
|
||||
#include <QtGui/QIcon>
|
||||
|
||||
namespace LtsNotification
|
||||
{
|
||||
|
||||
LtsNotification::LtsNotification(LtsNotificationController *ctl)
|
||||
: QObject()
|
||||
, m_controller(ctl)
|
||||
{
|
||||
connect(ctl, &LtsNotificationController::destroyed, this, &LtsNotification::slotCtlDestroyed);
|
||||
}
|
||||
|
||||
LtsNotification::~LtsNotification()
|
||||
{
|
||||
delete m_controller;
|
||||
}
|
||||
|
||||
void LtsNotification::setIcon(const QIcon &icon)
|
||||
{
|
||||
m_controller->setIcon(icon);
|
||||
}
|
||||
|
||||
void LtsNotification::setAction(const std::function<void(void)> &action)
|
||||
{
|
||||
m_controller->setAction(action);
|
||||
}
|
||||
|
||||
void LtsNotification::setToolTip(const QString &toolTip)
|
||||
{
|
||||
m_controller->setToolTip(toolTip);
|
||||
}
|
||||
|
||||
const QString &LtsNotification::tooltip() const
|
||||
{
|
||||
return m_controller->tooltip();
|
||||
}
|
||||
|
||||
void LtsNotification::setCount(const int count)
|
||||
{
|
||||
m_controller->setCount(count);
|
||||
}
|
||||
|
||||
void LtsNotification::setEnabled(bool enable)
|
||||
{
|
||||
m_controller->setEnabled(enable);
|
||||
}
|
||||
|
||||
void LtsNotification::setBackground(const int r, const int g, const int b)
|
||||
{
|
||||
m_controller->setBackground(r, g, b);
|
||||
}
|
||||
|
||||
void LtsNotification::slotCtlDestroyed(QObject *obj)
|
||||
{
|
||||
(void)obj;
|
||||
|
||||
m_controller = nullptr;
|
||||
}
|
||||
|
||||
} // namespace LtsNotification
|
||||
@@ -0,0 +1,45 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lts_notification_export.h"
|
||||
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QObject>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
namespace LtsSession
|
||||
{
|
||||
class LtsSession;
|
||||
}
|
||||
|
||||
namespace LtsNotification
|
||||
{
|
||||
class LtsNotificationController;
|
||||
|
||||
class LTSNOTIFICATION_EXPORT LtsNotification : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LtsNotification(LtsNotificationController *ctl);
|
||||
~LtsNotification();
|
||||
|
||||
void setIcon(const QIcon &icon);
|
||||
void setAction(const std::function<void(void)> &action);
|
||||
void setToolTip(const QString &toolTip);
|
||||
void setCount(const int count);
|
||||
void setEnabled(bool enable);
|
||||
void setBackground(const int r, const int g, const int b);
|
||||
|
||||
const QString &tooltip() const;
|
||||
|
||||
private:
|
||||
LtsNotificationController *m_controller;
|
||||
|
||||
private slots:
|
||||
void slotCtlDestroyed(QObject *obj);
|
||||
};
|
||||
|
||||
} // namespace LtsNotification
|
||||
@@ -0,0 +1,160 @@
|
||||
|
||||
|
||||
#include "lts_notification_ctl.h"
|
||||
|
||||
#include <QtGui/QColor>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtGui/QIcon>
|
||||
#include <QtGui/QImage>
|
||||
#include <QtGui/QPainter>
|
||||
#include <QtWidgets/QToolButton>
|
||||
#include <utility>
|
||||
|
||||
namespace LtsNotification
|
||||
{
|
||||
|
||||
LtsNotificationController::LtsNotificationController(const QString &identifier)
|
||||
: QObject()
|
||||
, m_count(0)
|
||||
, m_identifier(QStringLiteral("LtsNotification%1").arg(identifier))
|
||||
{
|
||||
}
|
||||
|
||||
LtsNotificationController::~LtsNotificationController()
|
||||
{
|
||||
qDeleteAll(m_notiList);
|
||||
}
|
||||
|
||||
void LtsNotificationController::setIcon(const QIcon &icon)
|
||||
{
|
||||
m_icon = icon;
|
||||
|
||||
setAllIcons();
|
||||
}
|
||||
|
||||
void LtsNotificationController::setCount(const int count)
|
||||
{
|
||||
m_count = count;
|
||||
|
||||
setAllIcons();
|
||||
}
|
||||
|
||||
void LtsNotificationController::setBackground(const int r, const int g, const int b)
|
||||
{
|
||||
|
||||
for (QToolButton *tb : std::as_const(m_notiList))
|
||||
{
|
||||
|
||||
QString stylesheet;
|
||||
|
||||
if (r == -1 && g == -1 && b == -1)
|
||||
stylesheet = QStringLiteral("");
|
||||
else
|
||||
stylesheet = QStringLiteral("QToolButton {background-color: rgb(") + QString::number(r) + QStringLiteral(",") + QString::number(g) + QStringLiteral(",") +
|
||||
QString::number(b) + QStringLiteral(");}");
|
||||
|
||||
tb->setStyleSheet(stylesheet);
|
||||
}
|
||||
}
|
||||
|
||||
void LtsNotificationController::setEnabled(bool enable)
|
||||
{
|
||||
m_enable = enable;
|
||||
|
||||
setAllIcons();
|
||||
}
|
||||
|
||||
void LtsNotificationController::setAction(const std::function<void(void)> &action)
|
||||
{
|
||||
m_action = action;
|
||||
}
|
||||
|
||||
void LtsNotificationController::setToolTip(const QString &toolTip)
|
||||
{
|
||||
m_toolTip = toolTip;
|
||||
|
||||
for (QToolButton *tb : std::as_const(m_notiList))
|
||||
{
|
||||
tb->setToolTip(toolTip);
|
||||
tb->setIconSize(QSize(30, 30));
|
||||
}
|
||||
}
|
||||
|
||||
void LtsNotificationController::setAllIcons()
|
||||
{
|
||||
if (m_icon.isNull())
|
||||
return;
|
||||
|
||||
QIcon icon = modifyIcon(m_icon);
|
||||
|
||||
for (QToolButton *tb : std::as_const(m_notiList))
|
||||
{
|
||||
tb->setIcon(icon);
|
||||
tb->setIconSize(QSize(30, 30));
|
||||
tb->setEnabled(m_enable);
|
||||
}
|
||||
}
|
||||
|
||||
#define SIZE 40
|
||||
|
||||
QIcon LtsNotificationController::modifyIcon(const QIcon &icon)
|
||||
{
|
||||
|
||||
if (m_count < 1)
|
||||
return icon;
|
||||
|
||||
QPixmap iconImage(icon.pixmap(SIZE * 8 / 10, SIZE * 8 / 10));
|
||||
QPainter painter(&iconImage);
|
||||
|
||||
painter.save();
|
||||
painter.setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing | QPainter::SmoothPixmapTransform);
|
||||
|
||||
QFont f;
|
||||
f.setFamily(QStringLiteral("Monospace"));
|
||||
f.setPointSize(16);
|
||||
f.setBold(true);
|
||||
painter.setFont(f);
|
||||
|
||||
QColor color(Qt::red);
|
||||
painter.setBrush(color);
|
||||
painter.setPen(QPen(color, 3.1));
|
||||
painter.drawText(QRect(0, 0, 30, 30), QString::number(m_count));
|
||||
painter.restore();
|
||||
|
||||
return iconImage;
|
||||
}
|
||||
|
||||
QToolButton *LtsNotificationController::createWidget()
|
||||
{
|
||||
QToolButton *b = new QToolButton();
|
||||
b->setToolButtonStyle(Qt::ToolButtonIconOnly);
|
||||
b->setObjectName(m_identifier);
|
||||
|
||||
b->setMinimumSize(SIZE, SIZE);
|
||||
b->setMaximumSize(SIZE, SIZE);
|
||||
|
||||
connect(b, &QToolButton::destroyed, this, &LtsNotificationController::slotNotiWidgetDestroyed);
|
||||
connect(b,
|
||||
&QToolButton::clicked,
|
||||
this,
|
||||
[this]()
|
||||
{
|
||||
if (m_action)
|
||||
m_action();
|
||||
});
|
||||
|
||||
if (!m_icon.isNull())
|
||||
b->setIcon(modifyIcon(m_icon));
|
||||
|
||||
b->setToolTip(m_toolTip);
|
||||
|
||||
m_notiList.append(b);
|
||||
return b;
|
||||
}
|
||||
|
||||
void LtsNotificationController::slotNotiWidgetDestroyed(QObject *obj)
|
||||
{
|
||||
m_notiList.removeAll(static_cast<QToolButton *>(obj));
|
||||
}
|
||||
|
||||
} // namespace LtsNotification
|
||||
@@ -0,0 +1,63 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "lts_notification_export.h"
|
||||
|
||||
#include <QtGui/QIcon>
|
||||
#include <QtCore/QList>
|
||||
#include <QtCore/QObject>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
class QToolButton;
|
||||
|
||||
namespace LtsSession
|
||||
{
|
||||
class LtsSession;
|
||||
}
|
||||
|
||||
namespace LtsNotification
|
||||
{
|
||||
class LtsNotificationWidget;
|
||||
|
||||
class LTSNOTIFICATION_EXPORT LtsNotificationController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LtsNotificationController(const QString &identifier);
|
||||
~LtsNotificationController();
|
||||
|
||||
QToolButton *createWidget();
|
||||
|
||||
void setIcon(const QIcon &icon);
|
||||
void setAction(const std::function<void(void)> &action);
|
||||
void setToolTip(const QString &toolTip);
|
||||
void setCount(const int count);
|
||||
void setBackground(const int r, const int g, const int b);
|
||||
|
||||
const QString &identifier() const { return m_identifier; }
|
||||
|
||||
const QString &tooltip() const { return m_toolTip; }
|
||||
|
||||
void setEnabled(bool enable);
|
||||
|
||||
private:
|
||||
QList<QToolButton *> m_notiList;
|
||||
|
||||
QIcon m_icon;
|
||||
std::function<void(void)> m_action;
|
||||
QString m_toolTip;
|
||||
int m_count;
|
||||
bool m_enable{true};
|
||||
QString m_identifier;
|
||||
|
||||
void setAllIcons();
|
||||
QIcon modifyIcon(const QIcon &icon);
|
||||
|
||||
private slots:
|
||||
void slotNotiWidgetDestroyed(QObject *obj);
|
||||
};
|
||||
|
||||
} // namespace LtsNotification
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
#ifndef LTSNOTIFICATION_EXPORT
|
||||
#ifdef BUILD_STATIC_LIBRARIES
|
||||
#define LTSNOTIFICATION_EXPORT
|
||||
#else
|
||||
#if defined(MAKE_LTSNOTIFICATION_LIB)
|
||||
#define LTSNOTIFICATION_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define LTSNOTIFICATION_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,27 @@
|
||||
#
|
||||
# Lightweight REST Client Framework
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
# Created by Hannes Fritsche, ESZS, 2026-08-21
|
||||
|
||||
#
|
||||
# --- Library LtsRestClient --------------------------------------------------------------------------------
|
||||
#
|
||||
SET(LtsRestClient_SRCS
|
||||
lts_rest_client.cpp
|
||||
lts_rest_client.h
|
||||
)
|
||||
|
||||
NGL_ADD_LIBRARY2(ALL LtsRestClient SHARED ${LtsRestClient_SRCS})
|
||||
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsRestClient
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
)
|
||||
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsRestClient
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
Qt6::Network
|
||||
)
|
||||
@@ -0,0 +1,175 @@
|
||||
/**
|
||||
* Lightweight REST Client Framework
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Hannes Fritsche, ESZS, 2026-08-21
|
||||
*/
|
||||
|
||||
#include "lts_rest_client.h"
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QtNetwork/QNetworkAccessManager>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
|
||||
namespace LtsCommon
|
||||
{
|
||||
|
||||
LtsRestClient::LtsRestClient(const QString &baseUrl, QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_baseUrl(baseUrl)
|
||||
, m_manager(new QNetworkAccessManager(this))
|
||||
{
|
||||
}
|
||||
|
||||
void LtsRestClient::ping(PingSuccessCallback onSuccess, ErrorCallback onError, int timeoutMs)
|
||||
{
|
||||
const QNetworkRequest request((QUrl(m_baseUrl)));
|
||||
QNetworkReply *reply = m_manager->head(request);
|
||||
|
||||
if (!reply)
|
||||
{
|
||||
if (onError)
|
||||
onError(QStringLiteral("Failed to initiate ping."));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// 1. Setup the timer (parented to reply for auto-cleanup)
|
||||
QTimer *timeoutTimer = new QTimer(reply);
|
||||
timeoutTimer->setSingleShot(true);
|
||||
|
||||
// 2. Connect the timeout signal to abort the request
|
||||
connect(timeoutTimer,
|
||||
&QTimer::timeout,
|
||||
this,
|
||||
[reply]()
|
||||
{
|
||||
if (reply && reply->isRunning())
|
||||
reply->abort(); // Triggers finished() with OperationCanceledError
|
||||
});
|
||||
|
||||
// 3. Connect the finished signal to handle the response
|
||||
connect(reply,
|
||||
&QNetworkReply::finished,
|
||||
this,
|
||||
[reply, timeoutTimer, onSuccess, onError]()
|
||||
{
|
||||
timeoutTimer->stop();
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
if (onError)
|
||||
{
|
||||
if (reply->error() == QNetworkReply::OperationCanceledError)
|
||||
onError(QStringLiteral("Ping timed out."));
|
||||
else
|
||||
onError(reply->errorString());
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
if (onSuccess)
|
||||
onSuccess(statusCode);
|
||||
});
|
||||
|
||||
// 4. Start the timer only after all connections are successfully established
|
||||
timeoutTimer->start(timeoutMs);
|
||||
}
|
||||
|
||||
void LtsRestClient::get(const QString &endpoint, SuccessCallback onSuccess, ErrorCallback onError)
|
||||
{
|
||||
sendRequest(QStringLiteral("GET"), endpoint, QByteArray(), onSuccess, onError);
|
||||
}
|
||||
|
||||
void LtsRestClient::post(const QString &endpoint, const QJsonDocument &json, SuccessCallback onSuccess, ErrorCallback onError)
|
||||
{
|
||||
sendRequest(QStringLiteral("POST"), endpoint, json.toJson(), onSuccess, onError);
|
||||
}
|
||||
|
||||
void LtsRestClient::put(const QString &endpoint, const QJsonDocument &json, SuccessCallback onSuccess, ErrorCallback onError)
|
||||
{
|
||||
sendRequest(QStringLiteral("PUT"), endpoint, json.toJson(), onSuccess, onError);
|
||||
}
|
||||
|
||||
void LtsRestClient::del(const QString &endpoint, SuccessCallback onSuccess, ErrorCallback onError)
|
||||
{
|
||||
sendRequest(QStringLiteral("DELETE"), endpoint, QByteArray(), onSuccess, onError);
|
||||
}
|
||||
|
||||
void LtsRestClient::sendRequest(const QString &method, const QString &endpoint, const QByteArray &data, SuccessCallback onSuccess, ErrorCallback onError)
|
||||
{
|
||||
QUrl url(m_baseUrl);
|
||||
url.setPath(url.path() + endpoint);
|
||||
|
||||
QNetworkRequest request(url);
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
|
||||
|
||||
QNetworkReply *reply = nullptr;
|
||||
|
||||
if (method == QStringLiteral("GET"))
|
||||
reply = m_manager->get(request);
|
||||
else if (method == QStringLiteral("POST"))
|
||||
reply = m_manager->post(request, data);
|
||||
else if (method == QStringLiteral("PUT"))
|
||||
reply = m_manager->put(request, data);
|
||||
else if (method == QStringLiteral("DELETE"))
|
||||
reply = m_manager->deleteResource(request);
|
||||
|
||||
if (!reply)
|
||||
{
|
||||
if (onError)
|
||||
onError(QStringLiteral("Failed to initiate request."));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
connect(reply,
|
||||
&QNetworkReply::finished,
|
||||
this,
|
||||
[reply, onSuccess, onError]()
|
||||
{
|
||||
reply->deleteLater();
|
||||
|
||||
// Handle network errors
|
||||
if (reply->error() != QNetworkReply::NoError)
|
||||
{
|
||||
if (onError)
|
||||
onError(reply->errorString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle HTTP errors
|
||||
const int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
if (statusCode >= 400)
|
||||
{
|
||||
if (onError)
|
||||
onError(QStringLiteral("HTTP Error %1").arg(statusCode));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse JSON safely
|
||||
QJsonParseError parseError;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(reply->readAll(), &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError)
|
||||
{
|
||||
if (onError)
|
||||
onError(QStringLiteral("JSON Parse Error: ") + parseError.errorString());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (onSuccess)
|
||||
onSuccess(doc);
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace LtsCommon
|
||||
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Lightweight REST Client Framework
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
* Created by Hannes Fritsche, ESZS, 2026-08-21
|
||||
*/
|
||||
|
||||
// Short documentation on how to use it can be found at https://confluence-classified.rh-int.de/spaces/LEG/pages/207915647/HOWTO+LtsRestClient
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <functional>
|
||||
#include <LtsRestClient/ltsrestclient_export.h>
|
||||
|
||||
class QNetworkAccessManager;
|
||||
|
||||
namespace LtsCommon
|
||||
{
|
||||
|
||||
class LTSRESTCLIENT_EXPORT LtsRestClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit LtsRestClient(const QString &baseUrl, QObject *parent = nullptr);
|
||||
|
||||
// type aliases
|
||||
using PingSuccessCallback = std::function<void(int statusCode)>;
|
||||
using SuccessCallback = std::function<void(const QJsonDocument &)>;
|
||||
using ErrorCallback = std::function<void(const QString &)>;
|
||||
|
||||
// HTTP Header Ping to m_baseUrl to check server availability
|
||||
void ping(PingSuccessCallback onSuccess, ErrorCallback onError = nullptr, int timeoutMs = 3000);
|
||||
|
||||
// HTTP Methods
|
||||
void get(const QString &endpoint, SuccessCallback onSuccess, ErrorCallback onError = nullptr);
|
||||
void post(const QString &endpoint, const QJsonDocument &json, SuccessCallback onSuccess, ErrorCallback onError = nullptr);
|
||||
void put(const QString &endpoint, const QJsonDocument &json, SuccessCallback onSuccess, ErrorCallback onError = nullptr);
|
||||
void del(const QString &endpoint, SuccessCallback onSuccess, ErrorCallback onError = nullptr);
|
||||
|
||||
private:
|
||||
void sendRequest(const QString &method, const QString &endpoint, const QByteArray &data, SuccessCallback onSuccess, ErrorCallback onError);
|
||||
|
||||
QString m_baseUrl;
|
||||
QNetworkAccessManager *m_manager;
|
||||
}; // class RestClient
|
||||
} // namespace LtsCommon
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
|
||||
#
|
||||
# Lts SerialPortQuerry library
|
||||
#
|
||||
|
||||
SET(ltsspq_SRCS
|
||||
lts_serial_port_querry.h
|
||||
lts_serial_port_querry.cpp
|
||||
logger.h
|
||||
logger.cpp
|
||||
)
|
||||
|
||||
|
||||
NGL_ADD_LIBRARY2(AAR SV LtsSerialPortQuerry SHARED ${ltsspq_SRCS} )
|
||||
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsSerialPortQuerry
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
)
|
||||
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsSerialPortQuerry
|
||||
PRIVATE
|
||||
PUBLIC
|
||||
NGLLogger
|
||||
$<IF:$<TARGET_EXISTS:Qt6::SerialPort>,Qt6::SerialPort,>
|
||||
)
|
||||
@@ -0,0 +1,30 @@
|
||||
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
#include <NGLLogger/NGLLoggerBootstrap.h>
|
||||
|
||||
namespace LtsSerialPortQuerry
|
||||
{
|
||||
|
||||
LOGGER_T getLogger(const char *name)
|
||||
{
|
||||
static const char ns[] = "SPQ.";
|
||||
std::string loggerName;
|
||||
const size_t len = strlen(name);
|
||||
loggerName.reserve(sizeof(ns) + len);
|
||||
loggerName.append(ns, sizeof(ns) - 1);
|
||||
loggerName.append(name, len);
|
||||
|
||||
try
|
||||
{
|
||||
return NGL::NGLLoggerBootstrap::getNGLRootLogger().getNGLLogger(loggerName);
|
||||
}
|
||||
catch (const std::exception &ex)
|
||||
{
|
||||
fprintf(stderr, "Error retrieving logger: %s", ex.what());
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace LtsSerialPortQuerry
|
||||
@@ -0,0 +1,17 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <NGLLogger/INGLLogger.h>
|
||||
|
||||
namespace LtsSerialPortQuerry
|
||||
{
|
||||
typedef NGL::INGLLogger &LOGGER_T;
|
||||
|
||||
LOGGER_T getLogger(const char *name);
|
||||
|
||||
inline LOGGER_T getLogger(const QString &loggerName)
|
||||
{
|
||||
return getLogger(loggerName.toUtf8().constData());
|
||||
}
|
||||
} // namespace LtsSerialPortQuerry
|
||||
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "lts_seri"
|
||||
#include "ltsserialportquerry_export.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtSerialPort/QSerialPortInfo>
|
||||
#include <QtSerialPort/QtSerialPort>
|
||||
|
||||
namespace LtsSerialPortQuerry
|
||||
{
|
||||
class LTSSERIALPORTQUERRY_EXPORT LtsSerialPortQuerry : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LtsSerialPortQuerry(QObject *parent = nullptr);
|
||||
|
||||
void setSerialPortSettings(const QSerialPort::BaudRate &baudRate,
|
||||
const QSerialPort::OpenModeFlag &openModeFlag,
|
||||
const QSerialPort::DataBits &dataBits,
|
||||
const QSerialPort::FlowControl &flowControll,
|
||||
const QSerialPort::Parity &parity,
|
||||
const QSerialPort::StopBits &stopBits);
|
||||
void continueQuerry();
|
||||
void restartQuerry();
|
||||
QSerialPort *currentSerialPort();
|
||||
|
||||
signals:
|
||||
void connectionEstablished();
|
||||
|
||||
private:
|
||||
void querrySerialPort(const QSerialPortInfo &info);
|
||||
void closeAndDeleteCurrentPort();
|
||||
|
||||
private:
|
||||
void onSerialPortError(QSerialPort::SerialPortError serialPortError);
|
||||
|
||||
private:
|
||||
QList<QSerialPortInfo> m_serialPortInfos;
|
||||
int m_currentQuerry;
|
||||
QSerialPort::BaudRate m_baudRate;
|
||||
QSerialPort::OpenModeFlag m_openModeFlag;
|
||||
QSerialPort::DataBits m_dataBits;
|
||||
QSerialPort::FlowControl m_flowControll;
|
||||
QSerialPort::Parity m_parity;
|
||||
QSerialPort::StopBits m_stopBits;
|
||||
QSerialPort *m_currentSerialPort;
|
||||
};
|
||||
|
||||
} // namespace LtsSerialPortQuerry
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
#include "lts_serial_port_querry.h"
|
||||
|
||||
#include <QtSerialPort/QSerialPortInfo>
|
||||
|
||||
namespace LtsSerialPortQuerry
|
||||
{
|
||||
|
||||
LtsSerialPortQuerry::LtsSerialPortQuerry(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_logger(getLogger("LtsSerialPortQuerry"))
|
||||
, m_currentQuerry(0)
|
||||
, m_baudRate(QSerialPort::Baud115200)
|
||||
, m_openModeFlag(QSerialPort::ReadWrite)
|
||||
, m_dataBits(QSerialPort::Data8)
|
||||
, m_flowControll(QSerialPort::NoFlowControl)
|
||||
, m_parity(QSerialPort::NoParity)
|
||||
, m_stopBits(QSerialPort::OneStop)
|
||||
, m_currentSerialPort(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
void LtsSerialPortQuerry::setSerialPortSettings(const QSerialPort::BaudRate &baudRate,
|
||||
const QIODevice::OpenModeFlag &openModeFlag,
|
||||
const QSerialPort::DataBits &dataBits,
|
||||
const QSerialPort::FlowControl &flowControll,
|
||||
const QSerialPort::Parity &parity,
|
||||
const QSerialPort::StopBits &stopBits)
|
||||
{
|
||||
m_baudRate = baudRate;
|
||||
m_openModeFlag = openModeFlag;
|
||||
m_dataBits = dataBits;
|
||||
m_flowControll = flowControll;
|
||||
m_parity = parity;
|
||||
m_stopBits = stopBits;
|
||||
}
|
||||
|
||||
void LtsSerialPortQuerry::continueQuerry()
|
||||
{
|
||||
m_currentQuerry++;
|
||||
if (m_currentQuerry >= m_serialPortInfos.size())
|
||||
{
|
||||
|
||||
restartQuerry();
|
||||
return;
|
||||
}
|
||||
querrySerialPort(m_serialPortInfos.at(m_currentQuerry));
|
||||
}
|
||||
|
||||
void LtsSerialPortQuerry::restartQuerry()
|
||||
{
|
||||
DEBUG_LOG(m_logger) << "restart querry ";
|
||||
m_serialPortInfos = QSerialPortInfo::availablePorts();
|
||||
m_currentQuerry = 0;
|
||||
if (m_serialPortInfos.size())
|
||||
querrySerialPort(m_serialPortInfos.at(m_currentQuerry));
|
||||
else
|
||||
QTimer::singleShot(1000, this, &LtsSerialPortQuerry::restartQuerry);
|
||||
}
|
||||
|
||||
QSerialPort *LtsSerialPortQuerry::currentSerialPort()
|
||||
{
|
||||
return m_currentSerialPort;
|
||||
}
|
||||
|
||||
void LtsSerialPortQuerry::querrySerialPort(const QSerialPortInfo &info)
|
||||
{
|
||||
closeAndDeleteCurrentPort();
|
||||
m_currentSerialPort = new QSerialPort(info, this);
|
||||
m_currentSerialPort->setBaudRate(m_baudRate);
|
||||
m_currentSerialPort->setDataBits(m_dataBits);
|
||||
m_currentSerialPort->setParity(m_parity);
|
||||
m_currentSerialPort->setStopBits(m_stopBits);
|
||||
m_currentSerialPort->setFlowControl(m_flowControll);
|
||||
|
||||
DEBUG_LOG(m_logger) << "Try opening Comport:" << m_currentSerialPort->portName() << " index is: " << m_currentQuerry;
|
||||
|
||||
if (!m_currentSerialPort->open(QSerialPort::ReadWrite))
|
||||
{
|
||||
|
||||
DEBUG_LOG(m_logger) << "Oppening failed query next";
|
||||
|
||||
continueQuerry();
|
||||
}
|
||||
else
|
||||
{
|
||||
connect(m_currentSerialPort, &QSerialPort::errorOccurred, this, &LtsSerialPortQuerry::onSerialPortError, Qt::UniqueConnection);
|
||||
DEBUG_LOG(m_logger) << "Oppened comport" << m_currentSerialPort->portName();
|
||||
emit connectionEstablished();
|
||||
}
|
||||
}
|
||||
|
||||
void LtsSerialPortQuerry::closeAndDeleteCurrentPort()
|
||||
{
|
||||
if (m_currentSerialPort)
|
||||
{
|
||||
m_currentSerialPort->disconnect();
|
||||
if (m_currentSerialPort->isOpen())
|
||||
m_currentSerialPort->close();
|
||||
m_currentSerialPort->deleteLater();
|
||||
|
||||
m_currentSerialPort = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void LtsSerialPortQuerry::onSerialPortError(QSerialPort::SerialPortError serialPortError)
|
||||
{
|
||||
|
||||
if (!m_currentSerialPort)
|
||||
return;
|
||||
DEBUG_LOG(m_logger) << "Spontaneous error " << serialPortError << " doesn't have to be an error ...";
|
||||
|
||||
const QString errStr(QStringLiteral("QSerialPort::SerialPortError ") + QString::number(serialPortError) + QStringLiteral(" from serial port ") +
|
||||
m_currentSerialPort->portName() + QStringLiteral(" error: ") + m_currentSerialPort->errorString());
|
||||
|
||||
switch (serialPortError)
|
||||
{
|
||||
case QSerialPort::SerialPortError::NoError:
|
||||
{
|
||||
|
||||
break;
|
||||
}
|
||||
case QSerialPort::SerialPortError::DeviceNotFoundError:
|
||||
case QSerialPort::SerialPortError::PermissionError:
|
||||
case QSerialPort::SerialPortError::OpenError:
|
||||
case QSerialPort::SerialPortError::NotOpenError:
|
||||
case QSerialPort::SerialPortError::UnknownError:
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Closing port and try to reopen it. " << m_currentSerialPort->portName();
|
||||
continueQuerry();
|
||||
|
||||
Q_FALLTHROUGH();
|
||||
}
|
||||
case QSerialPort::SerialPortError::UnsupportedOperationError:
|
||||
{
|
||||
ERROR_LOG(m_logger) << errStr;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
ERROR_LOG(m_logger) << "Got an Error> " << errStr << "restarting";
|
||||
continueQuerry();
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
} // namespace LtsSerialPortQuerry
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#pragma once
|
||||
|
||||
#include "logger.h"
|
||||
#include "ltsserialportquerry_export.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtSerialPort/QSerialPortInfo>
|
||||
#include <QtSerialPort/QtSerialPort>
|
||||
|
||||
namespace LtsSerialPortQuerry
|
||||
{
|
||||
class LTSSERIALPORTQUERRY_EXPORT LtsSerialPortQuerry : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit LtsSerialPortQuerry(QObject *parent = nullptr);
|
||||
|
||||
void setSerialPortSettings(const QSerialPort::BaudRate &baudRate,
|
||||
const QSerialPort::OpenModeFlag &openModeFlag,
|
||||
const QSerialPort::DataBits &dataBits,
|
||||
const QSerialPort::FlowControl &flowControll,
|
||||
const QSerialPort::Parity &parity,
|
||||
const QSerialPort::StopBits &stopBits);
|
||||
|
||||
void restartQuerry();
|
||||
QSerialPort *currentSerialPort();
|
||||
|
||||
public slots:
|
||||
void continueQuerry();
|
||||
|
||||
signals:
|
||||
void connectionEstablished();
|
||||
|
||||
private:
|
||||
NGL::INGLLogger &m_logger;
|
||||
void querrySerialPort(const QSerialPortInfo &info);
|
||||
void closeAndDeleteCurrentPort();
|
||||
void onSerialPortError(QSerialPort::SerialPortError serialPortError);
|
||||
QList<QSerialPortInfo> m_serialPortInfos;
|
||||
int m_currentQuerry;
|
||||
QSerialPort::BaudRate m_baudRate;
|
||||
QSerialPort::OpenModeFlag m_openModeFlag;
|
||||
QSerialPort::DataBits m_dataBits;
|
||||
QSerialPort::FlowControl m_flowControll;
|
||||
QSerialPort::Parity m_parity;
|
||||
QSerialPort::StopBits m_stopBits;
|
||||
QSerialPort *m_currentSerialPort;
|
||||
};
|
||||
|
||||
} // namespace LtsSerialPortQuerry
|
||||
@@ -0,0 +1,11 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
# Created by Mike Wille, SLSE, 2016-09-19
|
||||
#
|
||||
|
||||
|
||||
ADD_SUBDIRECTORY(LtsUtilsCore)
|
||||
ADD_SUBDIRECTORY(LtsUtilsSettings)
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
#
|
||||
# $Id$
|
||||
# $HeadURL$
|
||||
# $Author$
|
||||
#
|
||||
|
||||
SET(SRCS_hdr_only
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/data_modification_enum.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/dds_bitarray.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/dds_vector.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/connection.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/json_object.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/json.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/outdated_position.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/qbytearray_byteaccess.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/qpoint3d.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/await.h
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/lts_translatable_identifier.h
|
||||
)
|
||||
ADD_LIBRARY(LtsUtilsCoreHeaderOnly INTERFACE)
|
||||
TARGET_INCLUDE_DIRECTORIES(LtsUtilsCoreHeaderOnly
|
||||
INTERFACE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
${CMAKE_CURRENT_BINARY_DIR}/../
|
||||
)
|
||||
TARGET_SOURCES(LtsUtilsCoreHeaderOnly INTERFACE ${SRCS_hdr_only})
|
||||
|
||||
SET(SRCS
|
||||
pgarray.cpp
|
||||
qplane3d.cpp
|
||||
qpoint3d.cpp
|
||||
scoped_connection.cpp
|
||||
sfm_bytemessage_converter.cpp
|
||||
promise.cpp
|
||||
lts_translatable_identifier.cpp
|
||||
lts_portrange.cpp
|
||||
#variant_operators.h
|
||||
)
|
||||
|
||||
NGL_ADD_LIBRARY2(ALL LtsUtilsCore SHARED ${SRCS})
|
||||
NGL_TARGET_INCLUDE_DIRECTORIES(LtsUtilsCore
|
||||
PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/../
|
||||
${CMAKE_CURRENT_BINARY_DIR}/../
|
||||
)
|
||||
NGL_TARGET_LINK_LIBRARIES(LtsUtilsCore
|
||||
PUBLIC
|
||||
Qt6::Core
|
||||
LtsUtilsCoreHeaderOnly
|
||||
)
|
||||
+1149
File diff suppressed because it is too large.
Load diff
+55
@@ -0,0 +1,55 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class LtsUtilsTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
private Q_SLOTS:
|
||||
|
||||
void test_toIntArray();
|
||||
void test_fromIntArray();
|
||||
void test_toBigintArray();
|
||||
void test_fromBigintArray();
|
||||
void test_toIntArray2D();
|
||||
void test_fromIntArray2D();
|
||||
void test_toBigintArray2D();
|
||||
void test_fromBigintArray2D();
|
||||
|
||||
void test_QPoint3D();
|
||||
|
||||
void test_bitArray();
|
||||
|
||||
void test_convert();
|
||||
|
||||
void test_connection();
|
||||
void test_queuedConnection();
|
||||
void test_signalSignalConnection();
|
||||
void test_danglingConnection1();
|
||||
void test_danglingConnection2();
|
||||
void test_danglingConnection3();
|
||||
|
||||
void test_jsonObject_fromJson();
|
||||
void test_jsonObject_contains();
|
||||
void test_jsonObject_value();
|
||||
void test_jsonObject_valueWithError();
|
||||
void test_jsonObject_valueOr();
|
||||
void test_jsonObject_integers();
|
||||
void test_jsonObject_nullValues();
|
||||
void test_jsonArray_fromJson();
|
||||
void test_jsonArray();
|
||||
void test_jsonArray_nullArray();
|
||||
|
||||
void test_byteStreamFromSfmMessage();
|
||||
void test_byteStreamFromSfmMessage2();
|
||||
void test_byteStreamToSfmMessage();
|
||||
|
||||
void test_promise();
|
||||
void test_promise_example1();
|
||||
void test_promise_example2();
|
||||
void test_promise_example3();
|
||||
|
||||
void byteAccess();
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
#pragma once
|
||||
|
||||
#include <QEventLoop>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QTimer>
|
||||
#include <memory>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
template <typename Func> void addConnect(QEventLoop &x, bool &success, const typename QtPrivate::FunctionPointer<Func>::Object *sender, Func signal, bool isSuccess)
|
||||
{
|
||||
if (isSuccess)
|
||||
{
|
||||
QObject::connect(sender, signal, &x, [&success]() { success = true; });
|
||||
}
|
||||
|
||||
QObject::connect(sender, signal, &x, &QEventLoop::quit, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
template <typename Func, typename... Connect>
|
||||
void addConnect(QEventLoop &x, bool &success, const typename QtPrivate::FunctionPointer<Func>::Object *sender, Func signal, bool isSuccess, Connect... connects)
|
||||
{
|
||||
addConnect(x, success, sender, signal, isSuccess);
|
||||
addConnect(x, success, connects...);
|
||||
}
|
||||
|
||||
template <typename... Connect> void await(Connect... connects) noexcept(false)
|
||||
{
|
||||
QEventLoop x;
|
||||
bool success = false;
|
||||
|
||||
addConnect(x, success, connects...);
|
||||
|
||||
x.exec();
|
||||
|
||||
if (!success)
|
||||
{
|
||||
|
||||
throw std::runtime_error("Failed.");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Connect> void awaitTimeout(const int timeoutMs, Connect... connects) noexcept(false)
|
||||
{
|
||||
std::unique_ptr<QTimer> t(new QTimer);
|
||||
bool timeout = false;
|
||||
|
||||
if (timeoutMs > -1)
|
||||
{
|
||||
t->setInterval(timeoutMs);
|
||||
t->setSingleShot(true);
|
||||
QObject::connect(t.get(),
|
||||
&QTimer::timeout,
|
||||
t.get(),
|
||||
[&timeout]()
|
||||
{
|
||||
fprintf(stderr, "awaitTimeout: timed out!\n");
|
||||
timeout = true;
|
||||
});
|
||||
t->start();
|
||||
}
|
||||
|
||||
await(t.get(), &QTimer::timeout, false, connects...);
|
||||
|
||||
if (timeout)
|
||||
{
|
||||
|
||||
throw std::runtime_error("Timeout.");
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Func1, typename Func2>
|
||||
void awaitWithCancelationSignal(const typename QtPrivate::FunctionPointer<Func1>::Object *sender1,
|
||||
Func1 signal1,
|
||||
const typename QtPrivate::FunctionPointer<Func2>::Object *sender2,
|
||||
Func2 signal2,
|
||||
const int timeoutMs = 0) noexcept(false)
|
||||
{
|
||||
if (timeoutMs > 0)
|
||||
{
|
||||
awaitTimeout(timeoutMs, sender1, signal1, true, sender2, signal2, false);
|
||||
return;
|
||||
}
|
||||
|
||||
await(sender1, signal1, true, sender2, signal2, false);
|
||||
}
|
||||
} // namespace LtsUtils
|
||||
@@ -0,0 +1,107 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QMetaObject>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QPointer>
|
||||
#include <QtGlobal>
|
||||
#include <type_traits>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
|
||||
class IConnection
|
||||
{
|
||||
public:
|
||||
virtual ~IConnection() = default;
|
||||
virtual void connect(Qt::ConnectionType = Qt::AutoConnection) = 0;
|
||||
virtual void disconnect() = 0;
|
||||
};
|
||||
|
||||
class DummyConnection : public IConnection
|
||||
{
|
||||
public:
|
||||
void connect(Qt::ConnectionType = Qt::AutoConnection) override {}
|
||||
|
||||
void disconnect() override {}
|
||||
};
|
||||
|
||||
namespace detail
|
||||
{
|
||||
|
||||
template <class T> struct ExtractClass;
|
||||
|
||||
template <class T, class Member> struct ExtractClass<Member T::*>
|
||||
{
|
||||
using type = T;
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <class Signal, class Slot> class Connection : public IConnection
|
||||
{
|
||||
static_assert(std::is_member_function_pointer_v<Signal>, "Must be a pointer to method");
|
||||
static_assert(std::is_member_function_pointer_v<Slot>, "Must be a pointer to method");
|
||||
|
||||
using Emitter = typename detail::ExtractClass<Signal>::type;
|
||||
using Target = typename detail::ExtractClass<Slot>::type;
|
||||
|
||||
static_assert(std::is_base_of_v<QObject, Emitter>, "Must be derived from QObject");
|
||||
static_assert(std::is_base_of_v<QObject, Target>, "Must be derived from QObject");
|
||||
|
||||
public:
|
||||
Connection(Emitter *emitter, Signal signal, Target *target, Slot slot)
|
||||
: m_emitter(emitter)
|
||||
, m_signal(signal)
|
||||
, m_target(target)
|
||||
, m_slot(slot)
|
||||
{
|
||||
Q_ASSERT(emitter);
|
||||
Q_ASSERT(signal);
|
||||
Q_ASSERT(target);
|
||||
Q_ASSERT(slot);
|
||||
}
|
||||
|
||||
void connect(Qt::ConnectionType connectionType = Qt::AutoConnection) override
|
||||
{
|
||||
Q_ASSERT(!m_connected);
|
||||
|
||||
m_connected = true;
|
||||
|
||||
if (!m_emitter || !m_target)
|
||||
return;
|
||||
|
||||
m_conn = QObject::connect(m_emitter.data(), m_signal, m_target.data(), m_slot, connectionType);
|
||||
Q_ASSERT(m_conn);
|
||||
}
|
||||
|
||||
void disconnect() override
|
||||
{
|
||||
Q_ASSERT(m_connected);
|
||||
|
||||
if (m_conn)
|
||||
QObject::disconnect(m_conn);
|
||||
|
||||
m_connected = false;
|
||||
}
|
||||
|
||||
private:
|
||||
bool m_connected{false};
|
||||
QPointer<Emitter> m_emitter;
|
||||
Signal m_signal;
|
||||
QPointer<Target> m_target;
|
||||
Slot m_slot;
|
||||
QMetaObject::Connection m_conn;
|
||||
};
|
||||
|
||||
template <class Signal, class Slot>
|
||||
Connection<Signal, Slot> *createConnection(typename detail::ExtractClass<Signal>::type *emitter,
|
||||
Signal signal,
|
||||
typename detail::ExtractClass<Slot>::type *target,
|
||||
Slot slot)
|
||||
{
|
||||
return new Connection<Signal, Slot>(emitter, signal, target, slot);
|
||||
}
|
||||
|
||||
} // namespace LtsUtils
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <ostream>
|
||||
|
||||
enum class DataModification
|
||||
{
|
||||
eAdded,
|
||||
eModified,
|
||||
eRemoved
|
||||
};
|
||||
|
||||
inline std::ostream &operator<<(std::ostream &str, DataModification mod)
|
||||
{
|
||||
switch (mod)
|
||||
{
|
||||
case DataModification::eAdded:
|
||||
str << "DataModification::eAdded";
|
||||
break;
|
||||
case DataModification::eModified:
|
||||
str << "DataModification::eModified";
|
||||
break;
|
||||
case DataModification::eRemoved:
|
||||
str << "DataModification::eRemoved";
|
||||
break;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef SUPPORT_QBITARRAY
|
||||
#include <QtCore/QBitArray>
|
||||
#endif
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
|
||||
namespace BitArray
|
||||
{
|
||||
constexpr inline int bitNumber(int position)
|
||||
{
|
||||
return 7 - (position % 8);
|
||||
}
|
||||
|
||||
template <typename T> inline bool testBit(const T &bitField, int position, bool *oob = nullptr)
|
||||
{
|
||||
const size_t byteNum = position / 8;
|
||||
if (byteNum >= bitField.size())
|
||||
{
|
||||
if (oob)
|
||||
*oob = true;
|
||||
return false;
|
||||
}
|
||||
if (oob)
|
||||
*oob = false;
|
||||
|
||||
const typename T::value_type mask = 1 << bitNumber(position);
|
||||
return ((bitField.at(byteNum) & mask) == mask);
|
||||
}
|
||||
|
||||
template <typename T> inline bool setBit(T &bitField, int position, bool set = true)
|
||||
{
|
||||
const size_t byteNum = position / 8;
|
||||
if (byteNum >= bitField.size())
|
||||
return false;
|
||||
|
||||
const typename T::value_type mask = 1 << bitNumber(position);
|
||||
if (set)
|
||||
bitField[byteNum] |= mask;
|
||||
else
|
||||
bitField[byteNum] &= ~mask;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename T> inline bool clearBit(T &bitField, int position)
|
||||
{
|
||||
return setBit(bitField, position, false);
|
||||
}
|
||||
|
||||
#ifdef SUPPORT_QBITARRAY
|
||||
template <typename T> inline T convert(const QBitArray &bitArray)
|
||||
{
|
||||
T ret;
|
||||
ret.resize((bitArray.size() + 7) / 8, 0);
|
||||
|
||||
for (int i = 0; i < bitArray.size(); ++i)
|
||||
{
|
||||
const size_t byteNum = i / 8;
|
||||
const typename T::value_type mask = (1 << bitNumber(i));
|
||||
if (bitArray[i])
|
||||
ret[byteNum] |= mask;
|
||||
else
|
||||
ret[byteNum] &= ~mask;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <typename T> inline QBitArray convert(T &bitField, int realSize)
|
||||
{
|
||||
QBitArray ba(realSize);
|
||||
for (int32_t i = 0; i < realSize; ++i)
|
||||
{
|
||||
const size_t byteNum = i / 8;
|
||||
const typename T::value_type mask = (1 << bitNumber(i));
|
||||
bool bit = ((bitField[byteNum] & mask) == mask);
|
||||
ba[i] = bit;
|
||||
}
|
||||
return ba;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
} // namespace BitArray
|
||||
|
||||
} // namespace LtsUtils
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
|
||||
template <typename T, typename S> inline void convert(const S &in, T &out)
|
||||
{
|
||||
static_assert(!std::is_same_v<S, T>, "Try to convert a type to the same type - is useless");
|
||||
out.reserve(static_cast<int>(in.size()));
|
||||
std::copy(in.begin(), in.end(), std::back_inserter(out));
|
||||
}
|
||||
|
||||
template <typename T, typename S> inline T convert(const S &in)
|
||||
{
|
||||
static_assert(!std::is_same_v<S, T>, "Try to convert a type to the same type - is useless");
|
||||
T out;
|
||||
convert(in, out);
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename T> inline QByteArray toQByteArray(const T &in)
|
||||
{
|
||||
return convert<QByteArray>(in);
|
||||
}
|
||||
|
||||
template <typename T, typename V = typename T::value_type> bool isInVector(const T &vec, const V &valueToSearch)
|
||||
{
|
||||
const auto end = vec.end();
|
||||
return std::find(vec.begin(), end, valueToSearch) != end;
|
||||
}
|
||||
|
||||
} // namespace LtsUtils
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "ltsutilscore_export.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
class QByteArray;
|
||||
class QPixmap;
|
||||
class QImage;
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
|
||||
LTSUTILSCORE_EXPORT QPixmap getPixmap(const std::vector<uint8_t> &data);
|
||||
LTSUTILSCORE_EXPORT QPixmap getPixmap(const QByteArray &data);
|
||||
LTSUTILSCORE_EXPORT QImage getImage(const std::vector<uint8_t> &data);
|
||||
LTSUTILSCORE_EXPORT QImage getImage(const QByteArray &data);
|
||||
LTSUTILSCORE_EXPORT std::vector<uint8_t> fromPixmap(const QPixmap &pixmap, bool *bIsPng = nullptr);
|
||||
LTSUTILSCORE_EXPORT std::vector<uint8_t> fromImage(const QImage &image, bool *bIsPng = nullptr);
|
||||
|
||||
} // namespace LtsUtils
|
||||
@@ -0,0 +1 @@
|
||||
#include <LtsUtilsCore/json_object.h>
|
||||
@@ -0,0 +1,579 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/QJsonArray>
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QJsonObject>
|
||||
#include <QJsonParseError>
|
||||
#include <QtCore/QJsonValue>
|
||||
#include <QtCore/QMap>
|
||||
#include <QtCore/QString>
|
||||
#include <QtCore/QVector>
|
||||
#include <cstdint>
|
||||
#include <exception>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
|
||||
class JsonParseError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
JsonParseError(const QJsonParseError &err)
|
||||
: std::runtime_error("json parse error: " + err.errorString().toStdString() + " - at offset " + std::to_string(err.offset))
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class JsonError : public std::runtime_error
|
||||
{
|
||||
public:
|
||||
using std::runtime_error::runtime_error;
|
||||
};
|
||||
|
||||
class JsonKeyError : public JsonError
|
||||
{
|
||||
public:
|
||||
using JsonError::JsonError;
|
||||
};
|
||||
|
||||
class JsonRangeError : public JsonError
|
||||
{
|
||||
public:
|
||||
using JsonError::JsonError;
|
||||
};
|
||||
|
||||
class JsonTypeError : public JsonError
|
||||
{
|
||||
public:
|
||||
using JsonError::JsonError;
|
||||
};
|
||||
|
||||
class JsonObject
|
||||
{
|
||||
public:
|
||||
class Key;
|
||||
|
||||
JsonObject() = default;
|
||||
|
||||
explicit JsonObject(const QJsonObject &json)
|
||||
: m_json(json)
|
||||
, m_jsonIsSet(true)
|
||||
{
|
||||
}
|
||||
|
||||
explicit JsonObject(QJsonObject &&json)
|
||||
: m_json(std::move(json))
|
||||
, m_jsonIsSet(true)
|
||||
{
|
||||
}
|
||||
|
||||
static inline JsonObject fromJson(const char *json, int len = -1);
|
||||
|
||||
static inline JsonObject fromJson(const QByteArray &json) { return fromJson(json.data(), json.size()); }
|
||||
|
||||
static inline JsonObject fromJson(const std::string &json) { return fromJson(json.c_str(), json.size()); }
|
||||
|
||||
static inline JsonObject fromJsonSafe(const char *json, int len = -1) noexcept(false);
|
||||
|
||||
static inline JsonObject fromJsonSafe(const QByteArray &json) noexcept(false) { return fromJsonSafe(json.data(), json.size()); }
|
||||
|
||||
static inline JsonObject fromJsonSafe(const std::string &json) noexcept(false) { return fromJsonSafe(json.c_str(), json.size()); }
|
||||
|
||||
template <class T> [[nodiscard]] bool contains(const Key &key) const;
|
||||
|
||||
[[nodiscard]] bool isNull(const Key &key) const { return contains<void>(key); }
|
||||
|
||||
[[nodiscard]] bool isEmpty() const { return m_json.isEmpty(); }
|
||||
|
||||
[[nodiscard]] bool isValid() const { return m_jsonIsSet; }
|
||||
|
||||
template <class T> T value(const Key &key) const noexcept(false);
|
||||
|
||||
template <class T> T valueOr(const Key &key, const T &defaultValue) const noexcept(false);
|
||||
|
||||
template <class T> [[nodiscard]] bool isMap() const;
|
||||
|
||||
template <class T> QMap<QString, T> toMap() const noexcept(false);
|
||||
|
||||
template <class T> std::map<std::string, T> toStdMap() const;
|
||||
|
||||
bool operator==(const JsonObject &other) const { return m_json == other.m_json; }
|
||||
|
||||
bool operator!=(const JsonObject &other) const { return !(*this == other); }
|
||||
|
||||
class Key
|
||||
{
|
||||
public:
|
||||
Key(QString s)
|
||||
: m_s(std::move(s))
|
||||
{
|
||||
}
|
||||
|
||||
Key(const std::string &s)
|
||||
: m_s(QString::fromStdString(s))
|
||||
{
|
||||
}
|
||||
|
||||
Key(const char *s)
|
||||
: m_s(QString::fromUtf8(s))
|
||||
{
|
||||
}
|
||||
|
||||
operator const QString &() const { return m_s; }
|
||||
|
||||
private:
|
||||
const QString m_s;
|
||||
};
|
||||
|
||||
operator const QJsonObject &() const { return m_json; }
|
||||
|
||||
[[nodiscard]] const QJsonObject &object() const { return m_json; }
|
||||
|
||||
private:
|
||||
[[nodiscard]] bool containsAny(const QString &key) const { return m_json.contains(key); }
|
||||
|
||||
QJsonObject m_json;
|
||||
bool m_jsonIsSet = false;
|
||||
};
|
||||
|
||||
JsonObject JsonObject::fromJson(const char *json, int len)
|
||||
{
|
||||
if (len < 0)
|
||||
len = strlen(json);
|
||||
QJsonParseError error;
|
||||
const auto doc = QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
return {};
|
||||
|
||||
if (!doc.isObject())
|
||||
return {};
|
||||
|
||||
return JsonObject(doc.object());
|
||||
}
|
||||
|
||||
JsonObject JsonObject::fromJsonSafe(const char *json, int len) noexcept(false)
|
||||
{
|
||||
if (len < 0)
|
||||
len = strlen(json);
|
||||
QJsonParseError error;
|
||||
const auto doc = QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
throw JsonParseError(error);
|
||||
|
||||
if (!doc.isObject())
|
||||
throw JsonTypeError("JSON root is not an object");
|
||||
|
||||
return JsonObject(doc.object());
|
||||
}
|
||||
|
||||
class JsonArray
|
||||
{
|
||||
public:
|
||||
JsonArray() = default;
|
||||
|
||||
explicit JsonArray(const QJsonArray &json)
|
||||
: m_json(json)
|
||||
, m_jsonIsSet(true)
|
||||
{
|
||||
}
|
||||
|
||||
explicit JsonArray(QJsonArray &&json)
|
||||
: m_json(std::move(json))
|
||||
, m_jsonIsSet(true)
|
||||
{
|
||||
}
|
||||
|
||||
static inline JsonArray fromJson(const char *json, int len = -1);
|
||||
|
||||
static inline JsonArray fromJson(const QByteArray &json) { return fromJson(json.data(), json.size()); }
|
||||
|
||||
static inline JsonArray fromJson(const std::string &json) { return fromJson(json.c_str(), json.size()); }
|
||||
|
||||
static inline JsonArray fromJsonSafe(const char *json, int len = -1) noexcept(false);
|
||||
|
||||
static inline JsonArray fromJsonSafe(const QByteArray &json) noexcept(false) { return fromJson(json.data(), json.size()); }
|
||||
|
||||
static inline JsonArray fromJsonSafe(const std::string &json) noexcept(false) { return fromJson(json.c_str(), json.size()); }
|
||||
|
||||
[[nodiscard]] int size() const { return m_json.size(); }
|
||||
|
||||
[[nodiscard]] bool isEmpty() const { return m_json.isEmpty(); }
|
||||
|
||||
[[nodiscard]] bool isValid() const { return m_jsonIsSet; }
|
||||
|
||||
template <class T> [[nodiscard]] bool contains(int index) const noexcept(false);
|
||||
|
||||
[[nodiscard]] bool isNull(int index) const noexcept(false) { return contains<void>(index); }
|
||||
|
||||
template <class T> T at(int index) const noexcept(false);
|
||||
|
||||
template <class T> [[nodiscard]] bool isVector() const;
|
||||
|
||||
template <class T> QVector<T> toVector() const noexcept(false);
|
||||
|
||||
template <class T> std::vector<T> toStdVector() const;
|
||||
|
||||
bool operator==(const JsonArray &other) const { return m_json == other.m_json; }
|
||||
|
||||
bool operator!=(const JsonArray &other) const { return !(*this == other); }
|
||||
|
||||
const QJsonArray &operator()() const { return m_json; }
|
||||
|
||||
private:
|
||||
QJsonArray m_json;
|
||||
bool m_jsonIsSet = false;
|
||||
};
|
||||
|
||||
JsonArray JsonArray::fromJson(const char *json, int len)
|
||||
{
|
||||
if (len < 0)
|
||||
len = strlen(json);
|
||||
QJsonParseError error;
|
||||
QJsonDocument doc = QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
return {};
|
||||
|
||||
if (!doc.isArray())
|
||||
return {};
|
||||
|
||||
return JsonArray(doc.array());
|
||||
}
|
||||
|
||||
JsonArray JsonArray::fromJsonSafe(const char *json, int len) noexcept(false)
|
||||
{
|
||||
if (len < 0)
|
||||
len = strlen(json);
|
||||
QJsonParseError error;
|
||||
const auto doc = QJsonDocument::fromJson(QByteArray::fromRawData(json, len), &error);
|
||||
if (error.error != QJsonParseError::NoError)
|
||||
throw JsonParseError(error);
|
||||
|
||||
if (!doc.isArray())
|
||||
throw JsonTypeError("JSON root is not an array");
|
||||
|
||||
return JsonArray(doc.array());
|
||||
}
|
||||
|
||||
template <class T> struct JsonValue;
|
||||
|
||||
template <class Int> struct JsonLimits
|
||||
{
|
||||
static constexpr Int lowest() { return std::numeric_limits<Int>::lowest(); }
|
||||
|
||||
static constexpr Int max() { return std::numeric_limits<Int>::max(); }
|
||||
};
|
||||
|
||||
template <> struct JsonLimits<int64_t>
|
||||
{
|
||||
static constexpr int64_t lowest() { return -(static_cast<int64_t>(1) << 53) + 1; }
|
||||
|
||||
static constexpr int64_t max() { return (static_cast<int64_t>(1) << 53) - 1; }
|
||||
};
|
||||
|
||||
template <> struct JsonLimits<uint64_t>
|
||||
{
|
||||
static constexpr uint64_t lowest() { return 0; }
|
||||
|
||||
static constexpr uint64_t max() { return JsonLimits<int64_t>::max(); }
|
||||
};
|
||||
|
||||
template <class Int> struct JsonIntValue
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value)
|
||||
{
|
||||
if (!value.isDouble())
|
||||
return false;
|
||||
double val = value.toDouble();
|
||||
|
||||
if (val < JsonLimits<Int>::lowest())
|
||||
return false;
|
||||
if (val > JsonLimits<Int>::max())
|
||||
return false;
|
||||
|
||||
double wholeInt = static_cast<Int>(val);
|
||||
return val == wholeInt;
|
||||
}
|
||||
|
||||
static Int value(const QJsonValue &value) noexcept(false)
|
||||
{
|
||||
if (!matchesType(value))
|
||||
throw JsonTypeError("JSON value not of type Int");
|
||||
return static_cast<Int>(value.toDouble());
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct JsonValue<int16_t> : JsonIntValue<int16_t>
|
||||
{
|
||||
};
|
||||
|
||||
template <> struct JsonValue<uint16_t> : JsonIntValue<uint16_t>
|
||||
{
|
||||
};
|
||||
|
||||
template <> struct JsonValue<int32_t> : JsonIntValue<int32_t>
|
||||
{
|
||||
};
|
||||
|
||||
template <> struct JsonValue<uint32_t> : JsonIntValue<uint32_t>
|
||||
{
|
||||
};
|
||||
|
||||
template <> struct JsonValue<int64_t> : JsonIntValue<int64_t>
|
||||
{
|
||||
};
|
||||
|
||||
template <> struct JsonValue<uint64_t> : JsonIntValue<uint64_t>
|
||||
{
|
||||
};
|
||||
|
||||
template <> struct JsonValue<double>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return value.isDouble(); }
|
||||
|
||||
static double value(const QJsonValue &value) noexcept(false)
|
||||
{
|
||||
if (!matchesType(value))
|
||||
throw JsonTypeError("JSON value not of type Double");
|
||||
return value.toDouble();
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct JsonValue<bool>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return value.isBool(); }
|
||||
|
||||
static bool value(const QJsonValue &value) noexcept(false)
|
||||
{
|
||||
if (!matchesType(value))
|
||||
throw JsonTypeError("JSON value not of type Bool");
|
||||
return value.toBool();
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct JsonValue<QString>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return value.isString(); }
|
||||
|
||||
static QString value(const QJsonValue &value) noexcept(false)
|
||||
{
|
||||
if (!matchesType(value))
|
||||
throw JsonTypeError("JSON value not of type String");
|
||||
return value.toString();
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct JsonValue<std::string>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return value.isString(); }
|
||||
|
||||
static std::string value(const QJsonValue &value) { return JsonValue<QString>::value(value).toStdString(); }
|
||||
};
|
||||
|
||||
template <> struct JsonValue<QJsonObject>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return value.isObject(); }
|
||||
|
||||
static QJsonObject value(const QJsonValue &value) noexcept(false)
|
||||
{
|
||||
if (!matchesType(value))
|
||||
throw JsonTypeError("JSON value not of type Object");
|
||||
return value.toObject();
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct JsonValue<JsonObject>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return value.isObject(); }
|
||||
|
||||
static JsonObject value(const QJsonValue &value) { return JsonObject(JsonValue<QJsonObject>::value(value)); }
|
||||
};
|
||||
|
||||
template <> struct JsonValue<void>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return value.isNull(); }
|
||||
};
|
||||
|
||||
template <> struct JsonValue<QJsonArray>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return value.isArray(); }
|
||||
|
||||
static QJsonArray value(const QJsonValue &value) noexcept(false)
|
||||
{
|
||||
if (!matchesType(value))
|
||||
throw JsonTypeError("JSON value not of type Array");
|
||||
return value.toArray();
|
||||
}
|
||||
};
|
||||
|
||||
template <> struct JsonValue<JsonArray>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return value.isArray(); }
|
||||
|
||||
static JsonArray value(const QJsonValue &value) { return JsonArray(JsonValue<QJsonArray>::value(value)); }
|
||||
};
|
||||
|
||||
template <class T> struct JsonValue<QVector<T>>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value)
|
||||
{
|
||||
if (!value.isArray())
|
||||
return false;
|
||||
|
||||
const JsonArray array(value.toArray());
|
||||
return array.isVector<T>();
|
||||
}
|
||||
|
||||
static QVector<T> value(const QJsonValue &value)
|
||||
{
|
||||
const JsonArray array(JsonValue<QJsonArray>::value(value));
|
||||
return array.toVector<T>();
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> struct JsonValue<std::vector<T>>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return JsonValue<QVector<T>>::matchesType(value); }
|
||||
|
||||
static std::vector<T> value(const QJsonValue &value)
|
||||
{
|
||||
const JsonArray array(JsonValue<QJsonArray>::value(value));
|
||||
return array.toStdVector<T>();
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> struct JsonValue<QMap<QString, T>>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value)
|
||||
{
|
||||
if (!value.isObject())
|
||||
return false;
|
||||
|
||||
const JsonObject obj(JsonValue<QJsonObject>::value(value));
|
||||
return obj.isMap<T>();
|
||||
}
|
||||
|
||||
static QMap<QString, T> value(const QJsonValue &value)
|
||||
{
|
||||
const JsonObject obj(JsonValue<QJsonObject>::value(value));
|
||||
return obj.toMap<T>();
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> struct JsonValue<std::map<std::string, T>>
|
||||
{
|
||||
static bool matchesType(const QJsonValue &value) { return JsonValue<QMap<QString, T>>::matchesType(value); }
|
||||
|
||||
static std::map<std::string, T> value(const QJsonValue &value)
|
||||
{
|
||||
const JsonObject obj(JsonValue<QJsonObject>::value(value));
|
||||
return obj.toStdMap<T>();
|
||||
}
|
||||
};
|
||||
|
||||
template <class T> bool JsonObject::contains(const Key &key) const
|
||||
{
|
||||
if (!m_json.contains(key))
|
||||
return false;
|
||||
return JsonValue<T>::matchesType(m_json.value(key));
|
||||
}
|
||||
|
||||
template <class T> T JsonObject::value(const Key &key) const noexcept(false)
|
||||
{
|
||||
if (!containsAny(key))
|
||||
throw JsonKeyError("no element with key: " + static_cast<const QString &>(key).toStdString());
|
||||
try
|
||||
{
|
||||
return JsonValue<T>::value(m_json.value(key));
|
||||
}
|
||||
catch (const JsonTypeError &typeError)
|
||||
{
|
||||
throw JsonTypeError(std::string(typeError.what()) + " - key: " + static_cast<const QString &>(key).toStdString());
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> T JsonObject::valueOr(const Key &key, const T &defaultValue) const
|
||||
{
|
||||
try
|
||||
{
|
||||
return value<T>(key);
|
||||
}
|
||||
|
||||
catch (const JsonKeyError &)
|
||||
{
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
template <class T> bool JsonObject::isMap() const
|
||||
{
|
||||
const QStringList keys = m_json.keys();
|
||||
for (const auto &key : keys)
|
||||
if (!JsonValue<T>::matchesType(m_json.value(key)))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T> QMap<QString, T> JsonObject::toMap() const
|
||||
{
|
||||
QMap<QString, T> result;
|
||||
for (auto current = m_json.begin(); current != m_json.end(); ++current)
|
||||
result.insert(current.key(), JsonValue<T>::value(current.value()));
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T> std::map<std::string, T> JsonObject::toStdMap() const
|
||||
{
|
||||
std::map<std::string, T> result;
|
||||
for (auto current = m_json.begin(); current != m_json.end(); ++current)
|
||||
result.insert({current.key().toStdString(), JsonValue<T>::value(current.value())});
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T> bool JsonArray::contains(int index) const noexcept(false)
|
||||
{
|
||||
if (index < 0 || index >= m_json.size())
|
||||
throw JsonRangeError("index out of range: " + std::to_string(index));
|
||||
return JsonValue<T>::matchesType(m_json.at(index));
|
||||
}
|
||||
|
||||
template <class T> T JsonArray::at(int index) const noexcept(false)
|
||||
{
|
||||
if (index < 0 || index >= m_json.size())
|
||||
throw JsonRangeError("index out of range: " + std::to_string(index));
|
||||
return JsonValue<T>::value(m_json.at(index));
|
||||
}
|
||||
|
||||
template <class T> bool JsonArray::isVector() const
|
||||
{
|
||||
for (const QJsonValue &value : m_json)
|
||||
if (!JsonValue<T>::matchesType(value))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
template <class T> QVector<T> JsonArray::toVector() const
|
||||
{
|
||||
QVector<T> result;
|
||||
result.reserve(m_json.size());
|
||||
for (const QJsonValue &value : m_json)
|
||||
result.push_back(JsonValue<T>::value(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
template <class T> std::vector<T> JsonArray::toStdVector() const
|
||||
{
|
||||
std::vector<T> result;
|
||||
result.reserve(m_json.size());
|
||||
for (const QJsonValue &value : m_json)
|
||||
result.push_back(JsonValue<T>::value(value));
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace LtsUtils
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "lts_portrange.h"
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
|
||||
LtsUtils::LtsPortRange fromQPoint(const QPoint &point)
|
||||
{
|
||||
if (point.x() < 1025 || point.y() > 65535 || point.x() > point.y())
|
||||
return {};
|
||||
|
||||
return {static_cast<quint16>(point.x()), static_cast<quint16>(point.y())};
|
||||
}
|
||||
|
||||
} // namespace LtsUtils
|
||||
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "ltsutilscore_export.h"
|
||||
|
||||
#include <QtCore/QPair>
|
||||
#include <QtCore/QPoint>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
typedef QPair<quint16, quint16> LtsPortRange;
|
||||
|
||||
LTSUTILSCORE_EXPORT LtsPortRange fromQPoint(const QPoint &point);
|
||||
} // namespace LtsUtils
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
|
||||
|
||||
#include "lts_translatable_identifier.h"
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
LtsTranslatableIdentifier::LtsTranslatableIdentifier(const QString &identifier, const QObject *context, const char *translatableText)
|
||||
: m_identifier(identifier)
|
||||
, m_translationContext(context->metaObject()->className())
|
||||
, m_translatableText(translatableText)
|
||||
{
|
||||
}
|
||||
|
||||
const QString LtsTranslatableIdentifier::translatedText(const char *disambiguation, int n) const
|
||||
{
|
||||
if (m_identifier.isEmpty() || m_translationContext == nullptr || m_translatableText == nullptr)
|
||||
return {};
|
||||
return QCoreApplication::translate(m_translationContext, m_translatableText, disambiguation, n);
|
||||
}
|
||||
} // namespace LtsUtils
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "ltsutilscore_export.h"
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
class LTSUTILSCORE_EXPORT LtsTranslatableIdentifier
|
||||
{
|
||||
public:
|
||||
LtsTranslatableIdentifier()
|
||||
: m_translationContext(nullptr)
|
||||
, m_translatableText("")
|
||||
{
|
||||
}
|
||||
|
||||
LtsTranslatableIdentifier(const QString &identifier, const QObject *context, const char *translatableText);
|
||||
|
||||
const QString &identifier() const { return m_identifier; }
|
||||
|
||||
const QString translatedText(const char *disambiguation = nullptr, int n = -1) const;
|
||||
|
||||
const QString _context() const { return QString::fromUtf8(m_translationContext); }
|
||||
|
||||
private:
|
||||
QString m_identifier;
|
||||
const char *m_translationContext;
|
||||
|
||||
const char *m_translatableText;
|
||||
};
|
||||
} // namespace LtsUtils
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Export definitions for LtsUtilsCore
|
||||
* $Id$
|
||||
* $HeadURL$
|
||||
* $Author$
|
||||
*/
|
||||
#pragma once
|
||||
|
||||
#include <QtCore/qglobal.h>
|
||||
|
||||
#ifndef LTSUTILSCORE_EXPORT
|
||||
#ifdef BUILD_STATIC_LIBRARIES
|
||||
#define LTSUTILSCORE_EXPORT
|
||||
#else
|
||||
#if defined(MAKE_LTSUTILSCORE_LIB)
|
||||
#define LTSUTILSCORE_EXPORT Q_DECL_EXPORT
|
||||
#else
|
||||
#define LTSUTILSCORE_EXPORT Q_DECL_IMPORT
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
@@ -0,0 +1,14 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <qglobal.h>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
inline bool isPositionTimeOutdated(qint64 curTime, qint64 posTime)
|
||||
{
|
||||
|
||||
return curTime > posTime + 11 * 60 * 1000;
|
||||
}
|
||||
} // namespace LtsUtils
|
||||
@@ -0,0 +1,145 @@
|
||||
|
||||
|
||||
#include "pgarray.h"
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QStringView>
|
||||
#include <QtCore/QVector>
|
||||
#include <functional>
|
||||
#include <ltsutilscore_export.h>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
|
||||
template <typename T> static inline QString convertToPgString(const std::vector<T> &data)
|
||||
{
|
||||
if (data.size() == 0)
|
||||
return QLatin1String("{}");
|
||||
QString ret(QLatin1Char('{'));
|
||||
for (T i : data)
|
||||
ret += QString::number(i) + QLatin1Char(',');
|
||||
ret.chop(1);
|
||||
ret += QLatin1Char('}');
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <> LTSUTILSCORE_EXPORT QString IntArray::toPgString() const
|
||||
{
|
||||
return convertToPgString(m_data);
|
||||
}
|
||||
|
||||
template <> LTSUTILSCORE_EXPORT QString BigintArray::toPgString() const
|
||||
{
|
||||
return convertToPgString(m_data);
|
||||
}
|
||||
|
||||
template <typename T> static inline T convertFromPgString(const QString &str, const std::function<typename T::Type(QStringView val, bool *bOk)> &convertFunc)
|
||||
{
|
||||
typename T::ContainerType result;
|
||||
|
||||
int idxStart = str.indexOf(QLatin1Char('{'));
|
||||
int idxEnd = str.lastIndexOf(QLatin1Char('}'));
|
||||
if (idxEnd < idxStart)
|
||||
return T(result);
|
||||
if (idxStart < 0 && idxEnd < 0)
|
||||
{
|
||||
|
||||
idxStart = -1;
|
||||
idxEnd = str.length() + 1;
|
||||
}
|
||||
|
||||
const QStringView strRef = QStringView(str).mid(idxStart + 1, idxEnd - idxStart - 1);
|
||||
const QVector<QStringView> vec = strRef.split(QLatin1Char(','), Qt::SkipEmptyParts);
|
||||
result.reserve(vec.size());
|
||||
for (const QStringView &s : vec)
|
||||
{
|
||||
bool ok = false;
|
||||
const typename T::Type i = convertFunc(s, &ok);
|
||||
if (ok)
|
||||
result.push_back(i);
|
||||
}
|
||||
|
||||
return T(result);
|
||||
}
|
||||
|
||||
template <> LTSUTILSCORE_EXPORT IntArray IntArray::fromPgString(const QString &str)
|
||||
{
|
||||
return convertFromPgString<IntArray>(str, [](QStringView val, bool *bOk) { return val.toString().toInt(bOk); });
|
||||
}
|
||||
|
||||
template <> LTSUTILSCORE_EXPORT BigintArray BigintArray::fromPgString(const QString &str)
|
||||
{
|
||||
return convertFromPgString<BigintArray>(str, [](QStringView val, bool *bOk) { return val.toString().toLongLong(bOk); });
|
||||
}
|
||||
|
||||
template <typename T> static inline QString convertToPgString(const std::map<T, T> &data)
|
||||
{
|
||||
if (data.size() == 0)
|
||||
return QLatin1String("{}");
|
||||
QString ret(QLatin1Char('{'));
|
||||
for (const auto &it : data)
|
||||
{
|
||||
ret += QLatin1Char('{') + QString::number(it.first) + QLatin1Char(',') + QString::number(it.second) + QLatin1String("},");
|
||||
}
|
||||
ret.chop(1);
|
||||
ret += QLatin1Char('}');
|
||||
return ret;
|
||||
}
|
||||
|
||||
template <> LTSUTILSCORE_EXPORT QString IntArray2D::toPgString() const
|
||||
{
|
||||
return convertToPgString<IntArray2D::Type>(m_data);
|
||||
}
|
||||
|
||||
template <> LTSUTILSCORE_EXPORT QString BigintArray2D::toPgString() const
|
||||
{
|
||||
return convertToPgString<BigintArray2D::Type>(m_data);
|
||||
}
|
||||
|
||||
template <typename T> static inline T convertFromPgString2D(const QString &str, const std::function<typename T::Type(QStringView val)> &convertFunc)
|
||||
{
|
||||
typename T::ContainerType ret;
|
||||
int idxStart = str.indexOf(QLatin1Char('{'));
|
||||
int idxEnd = str.lastIndexOf(QLatin1Char('}'));
|
||||
if (idxEnd < idxStart)
|
||||
return T(ret);
|
||||
if (idxStart < 0 && idxEnd < 0)
|
||||
{
|
||||
|
||||
idxStart = -1;
|
||||
idxEnd = str.length() + 1;
|
||||
}
|
||||
|
||||
const QStringView strRef = QStringView(str).mid(idxStart + 1, idxEnd - idxStart - 1);
|
||||
idxStart = strRef.indexOf(QLatin1Char('{'));
|
||||
while (idxStart >= 0)
|
||||
{
|
||||
idxEnd = strRef.indexOf(QLatin1Char('}'), idxStart);
|
||||
if (idxEnd < 0)
|
||||
break;
|
||||
|
||||
const QStringView keyValueStr = strRef.mid(idxStart + 1, idxEnd - idxStart - 1);
|
||||
const QVector<QStringView> keyValuePair = keyValueStr.split(QLatin1Char(','), Qt::SkipEmptyParts);
|
||||
if (keyValuePair.size() != 2)
|
||||
break;
|
||||
const typename T::Type key = convertFunc(keyValuePair.at(0).trimmed());
|
||||
const typename T::Type value = convertFunc(keyValuePair.at(1).trimmed());
|
||||
ret.insert(std::make_pair(key, value));
|
||||
|
||||
idxStart = strRef.indexOf(QLatin1Char('{'), idxEnd + 1);
|
||||
}
|
||||
|
||||
return T(ret);
|
||||
}
|
||||
|
||||
template <> LTSUTILSCORE_EXPORT IntArray2D IntArray2D::fromPgString(const QString &str)
|
||||
{
|
||||
return convertFromPgString2D<IntArray2D>(str, [](QStringView val) { return val.toString().toInt(); });
|
||||
}
|
||||
|
||||
template <> LTSUTILSCORE_EXPORT BigintArray2D BigintArray2D::fromPgString(const QString &str)
|
||||
{
|
||||
return convertFromPgString2D<BigintArray2D>(str, [](QStringView val) { return val.toString().toLongLong(); });
|
||||
}
|
||||
|
||||
} // namespace LtsUtils
|
||||
@@ -0,0 +1,40 @@
|
||||
|
||||
|
||||
#pragma once
|
||||
#include <QtCore/QString>
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
namespace LtsUtils
|
||||
{
|
||||
|
||||
template <typename T, typename C> class PgArray
|
||||
{
|
||||
public:
|
||||
using Type = T;
|
||||
using ContainerType = C;
|
||||
|
||||
PgArray() = default;
|
||||
|
||||
Q_DECL_CONSTEXPR explicit PgArray(const C &data)
|
||||
: m_data(data)
|
||||
{
|
||||
}
|
||||
|
||||
Q_DECL_CONSTEXPR C operator()() const { return m_data; }
|
||||
|
||||
[[nodiscard]] QString toPgString() const;
|
||||
static PgArray fromPgString(const QString &str);
|
||||
|
||||
private:
|
||||
C m_data;
|
||||
};
|
||||
|
||||
using IntArray = PgArray<int, std::vector<int>>;
|
||||
using BigintArray = PgArray<int64_t, std::vector<int64_t>>;
|
||||
|
||||
using IntArray2D = PgArray<int, std::map<int, int>>;
|
||||
using BigintArray2D = PgArray<int64_t, std::map<int64_t, int64_t>>;
|
||||
|
||||
} // namespace LtsUtils
|
||||
Loaded 100 of 1389 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user