diff options
Diffstat (limited to 'opentrack')
-rw-r--r-- | opentrack/CMakeLists.txt | 25 | ||||
-rw-r--r-- | opentrack/appnap_mac.mm | 47 | ||||
-rw-r--r-- | opentrack/defs.hpp | 16 | ||||
-rw-r--r-- | opentrack/lang/de_DE.ts | 193 | ||||
-rw-r--r-- | opentrack/lang/nl_NL.ts | 189 | ||||
-rw-r--r-- | opentrack/lang/ru_RU.ts | 193 | ||||
-rw-r--r-- | opentrack/lang/stub.ts | 189 | ||||
-rw-r--r-- | opentrack/lang/zh_CN.ts | 193 | ||||
-rw-r--r-- | opentrack/main-window.cpp | 1140 | ||||
-rw-r--r-- | opentrack/main-window.hpp | 153 | ||||
-rw-r--r-- | opentrack/main-window.ui | 1343 | ||||
-rw-r--r-- | opentrack/main.cpp | 23 | ||||
-rw-r--r-- | opentrack/new_config.ui | 52 | ||||
-rw-r--r-- | opentrack/new_file_dialog.cpp | 37 | ||||
-rw-r--r-- | opentrack/new_file_dialog.h | 25 | ||||
-rw-r--r-- | opentrack/opentrack.ico | bin | 0 -> 67134 bytes | |||
-rw-r--r-- | opentrack/resources.rc | 2 |
17 files changed, 3820 insertions, 0 deletions
diff --git a/opentrack/CMakeLists.txt b/opentrack/CMakeLists.txt new file mode 100644 index 00000000..0fd72475 --- /dev/null +++ b/opentrack/CMakeLists.txt @@ -0,0 +1,25 @@ +if(MSVC) + add_compile_options(-EHsc) + add_definitions(-D_HAS_EXCEPTIONS=1) +endif() + +otr_module(executable EXECUTABLE BIN) + +set_target_properties(opentrack-executable PROPERTIES + SUFFIX "${opentrack-binary-suffix}" + OUTPUT_NAME "opentrack" + PREFIX "" +) + +set_source_files_properties(resources.rc OBJECT_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/opentrack.ico") + +if(APPLE) + set_source_files_properties(appnap_mac.mm PROPERTIES COMPILE_FLAGS "-fno-objc-arc") + target_sources(${self} PRIVATE appnap_mac.mm) +endif() + +target_link_libraries(${self} opentrack-user-interface opentrack-version) + +if(APPLE) + target_link_libraries(${self} "-framework Foundation") +endif() diff --git a/opentrack/appnap_mac.mm b/opentrack/appnap_mac.mm new file mode 100644 index 00000000..3e0bea73 --- /dev/null +++ b/opentrack/appnap_mac.mm @@ -0,0 +1,47 @@ +#ifdef __APPLE__ + +#import <Foundation/Foundation.h> + +/** + * Used to prevent macOS from throttling the opentrack process. + */ + +id token = nil; + +void disable_appnap_start(); +void disable_appnap_stop(); + +void disable_appnap_start() { + + if(token){ + NSLog(@"disable_appnap_start: already started"); + return; + } + + + NSLog(@"disable_appnap_start"); + token = [[NSProcessInfo processInfo] + beginActivityWithOptions: NSActivityUserInitiatedAllowingIdleSystemSleep + reason: @"Disable AppNap"]; + [token retain]; +} + +void disable_appnap_stop() { + if(!token){ + NSLog(@"disable_appnap_start: not started"); + return; + } + + NSLog(@"disable_appnap_stop"); + [[NSProcessInfo processInfo] endActivity:token]; + [token release]; + token = nil; +} + + + +#endif + + + + diff --git a/opentrack/defs.hpp b/opentrack/defs.hpp new file mode 100644 index 00000000..b2f0c467 --- /dev/null +++ b/opentrack/defs.hpp @@ -0,0 +1,16 @@ +#pragma once + +//#define UI_FORCED_TRACKER "pt" +//#define UI_FORCED_FILTER "accela" + +//#define UI_NO_TRACKER_COMBOBOX +//#define UI_NO_FILTER_COMBOBOX + +//#define UI_NO_TRACKER_SETTINGS_BUTTON +//#define UI_NO_FILTER_SETTINGS_BUTTON + +//#define UI_NO_RAW_DATA +//#define UI_NO_GAME_DATA +//#define UI_NO_VIDEO_FEED + +//define UI_ACCELA_OLD_STAIRCASE diff --git a/opentrack/lang/de_DE.ts b/opentrack/lang/de_DE.ts new file mode 100644 index 00000000..6eaf28d4 --- /dev/null +++ b/opentrack/lang/de_DE.ts @@ -0,0 +1,193 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="de_DE"> +<context> + <name>UI_new_config</name> + <message> + <source>Config filename</source> + <translation>Konfigurationsdatei</translation> + </message> + <message> + <source>New file name:</source> + <translation>Neuer Dateiname:</translation> + </message> +</context> +<context> + <name>main_window</name> + <message> + <source>Raw tracker data</source> + <translation>Rohe Tracker-Daten</translation> + </message> + <message> + <source>Z</source> + <translation>Z</translation> + </message> + <message> + <source>Pitch</source> + <translation>Nicken</translation> + </message> + <message> + <source>Y</source> + <translation>Y</translation> + </message> + <message> + <source>X</source> + <translation>X</translation> + </message> + <message> + <source>Roll</source> + <translation>Rollen</translation> + </message> + <message> + <source>Yaw</source> + <translation>Gieren</translation> + </message> + <message> + <source>Game data</source> + <translation>Spieldaten</translation> + </message> + <message> + <source>Profile</source> + <translation>Profil</translation> + </message> + <message> + <source>Options</source> + <translation>Optionen</translation> + </message> + <message> + <source>Mapping</source> + <translation>Abbildung</translation> + </message> + <message> + <source>Tracking</source> + <translation>Tracking</translation> + </message> + <message> + <source>Start</source> + <translation>Starten</translation> + </message> + <message> + <source>Stop</source> + <translation>Stoppen</translation> + </message> + <message> + <source>Input</source> + <translation>Eingabe</translation> + </message> + <message> + <source>🔨</source> + <translation>🔨</translation> + </message> + <message> + <source>Output</source> + <translation>Ausgabe</translation> + </message> + <message> + <source>Filter</source> + <translation>Filter</translation> + </message> + <message> + <source>Create new empty config</source> + <translation>Neue leere Konfiguration erstellen</translation> + </message> + <message> + <source>Create new copied config</source> + <translation>Neue kopierte Konfiguration erstellen</translation> + </message> + <message> + <source>Open configuration directory</source> + <translation>Konfigurationsordner öffnen</translation> + </message> + <message> + <source>opentrack</source> + <translation>opentrack</translation> + </message> + <message> + <source> (debug)</source> + <translation> (Fehlersuche)</translation> + </message> + <message> + <source>Show the Octopus</source> + <translation>Den Oktopus anzeigen</translation> + </message> + <message> + <source>Hide the Octopus</source> + <translation>Den Oktopus verstecken</translation> + </message> + <message> + <source>Tracker settings</source> + <translation>Tracker-Einstellungen</translation> + </message> + <message> + <source>Filter settings</source> + <translation>Filter-Einstellungen</translation> + </message> + <message> + <source>Protocol settings</source> + <translation>Protokoll-Einstellungen</translation> + </message> + <message> + <source>Mappings</source> + <translation>Abbildungen</translation> + </message> + <message> + <source>Exit</source> + <translation>Beenden</translation> + </message> + <message> + <source>The Octopus is sad</source> + <translation>Der Oktopus ist traurig</translation> + </message> + <message> + <source>Check permissions for your .ini directory: + +"%1"%2 + +Exiting now.</source> + <translation>Überprüfe die Berechtigungen des .ini-Ordners: + +"%1"%2 + +Das Programm beendet sich nun.</translation> + </message> + <message> + <source> :: </source> + <translation> :: </translation> + </message> + <message> + <source>Running as root is bad</source> + <translation>Das Starten als root ist schlecht</translation> + </message> + <message> + <source>Do not run as root. Set correct device node permissions.</source> + <translation>Führe dieses Programm nicht als root aus. Setze die korrekten Gerätedatei-Berechtigungen.</translation> + </message> + <message> + <source>Running as root is bad, seriously</source> + <translation>Das Starten als root ist schlecht, ernsthaft</translation> + </message> + <message> + <source>Do not run as root. I'll keep whining at every startup.</source> + <translation>Führe dieses Programm nicht als root aus. Ich werde bei jedem Start rumheulen.</translation> + </message> + <message> + <source>Be annoyed, comprehensively.</source> + <translation>Sei verärgert, vollumfänglich.</translation> + </message> + <message> + <source>Don't run as root to remove these annoying messages.</source> + <translation>Führe das Programm nicht mehr als root aus, um diese ärgerliche Nachricht loszuwerden.</translation> + </message> +</context> +<context> + <name>new_file_dialog</name> + <message> + <source>File exists</source> + <translation>Die Datei existiert</translation> + </message> + <message> + <source>This file already exists. Pick another name.</source> + <translation>Diese Datei existiert bereits. Wähle einen anderen Namen.</translation> + </message> +</context> +</TS> diff --git a/opentrack/lang/nl_NL.ts b/opentrack/lang/nl_NL.ts new file mode 100644 index 00000000..7a465820 --- /dev/null +++ b/opentrack/lang/nl_NL.ts @@ -0,0 +1,189 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="nl_NL"> +<context> + <name>UI_new_config</name> + <message> + <source>Config filename</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>New file name:</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>main_window</name> + <message> + <source>Raw tracker data</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Z</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Pitch</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Y</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>X</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Roll</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yaw</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Game data</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Profile</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Options</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mapping</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracking</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Start</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Stop</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Input</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>🔨</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Output</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Running as root is bad</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Do not run as root. Set correct device node permissions.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Running as root is bad, seriously</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Do not run as root. I'll keep whining at every startup.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Create new empty config</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Create new copied config</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Open configuration directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>opentrack</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> (debug)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Show the Octopus</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hide the Octopus</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracker settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Protocol settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mappings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Exit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>The Octopus is sad</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Check permissions for your .ini directory: + +"%1"%2 + +Exiting now.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> :: </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Be annoyed, comprehensively.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Don't run as root to remove these annoying messages.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>new_file_dialog</name> + <message> + <source>File exists</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>This file already exists. Pick another name.</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/opentrack/lang/ru_RU.ts b/opentrack/lang/ru_RU.ts new file mode 100644 index 00000000..684a8212 --- /dev/null +++ b/opentrack/lang/ru_RU.ts @@ -0,0 +1,193 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="ru_RU"> +<context> + <name>UI_new_config</name> + <message> + <source>Config filename</source> + <translation>Создание профиля</translation> + </message> + <message> + <source>New file name:</source> + <translation>Новое имя профиля:</translation> + </message> +</context> +<context> + <name>main_window</name> + <message> + <source>Create new empty config</source> + <translation>Создать новый пустой профиль</translation> + </message> + <message> + <source>Create new copied config</source> + <translation>Создать новый профиль на основе текущего</translation> + </message> + <message> + <source>Open configuration directory</source> + <translation>Открыть каталог с профилями</translation> + </message> + <message> + <source>opentrack</source> + <translation></translation> + </message> + <message> + <source> (debug)</source> + <translation></translation> + </message> + <message> + <source>Show the Octopus</source> + <translation>Показать осьминожка</translation> + </message> + <message> + <source>Hide the Octopus</source> + <translation>Спрятать осьминожка</translation> + </message> + <message> + <source>Tracker settings</source> + <translation>Настройка источника данных</translation> + </message> + <message> + <source>Filter settings</source> + <translation>Настройка фильтрации/сглаживания</translation> + </message> + <message> + <source>Protocol settings</source> + <translation>Настройка выходного интерфейса</translation> + </message> + <message> + <source>Mappings</source> + <translation>Настройка кривых</translation> + </message> + <message> + <source>Options</source> + <translation>Настройки</translation> + </message> + <message> + <source>Exit</source> + <translation>Закрыть</translation> + </message> + <message> + <source>The Octopus is sad</source> + <translation>Осьминожек опечален</translation> + </message> + <message> + <source>Check permissions for your .ini directory: + +"%1"%2 + +Exiting now.</source> + <translation>Проверьте права доступа на Вашу .ini папку: + +"%1"%2 + +Закрытие программы.</translation> + </message> + <message> + <source> :: </source> + <translation> :: </translation> + </message> + <message> + <source>Raw tracker data</source> + <translation>Исходные данные</translation> + </message> + <message> + <source>Z</source> + <translation>Z</translation> + </message> + <message> + <source>Pitch</source> + <translation>Pitch</translation> + </message> + <message> + <source>Y</source> + <translation>Y</translation> + </message> + <message> + <source>X</source> + <translation>X</translation> + </message> + <message> + <source>Roll</source> + <translation>Roll</translation> + </message> + <message> + <source>Yaw</source> + <translation>Yaw</translation> + </message> + <message> + <source>Game data</source> + <translation>Игровые данные</translation> + </message> + <message> + <source>Profile</source> + <translation>Профиль</translation> + </message> + <message> + <source>Mapping</source> + <translation> Кривые</translation> + </message> + <message> + <source>Tracking</source> + <translation>Статус работы трекера</translation> + </message> + <message> + <source>Start</source> + <translation>Запустить</translation> + </message> + <message> + <source>Stop</source> + <translation>Остановить</translation> + </message> + <message> + <source>Input</source> + <translation>Источник данных</translation> + </message> + <message> + <source>🔨</source> + <translation></translation> + </message> + <message> + <source>Output</source> + <translation>Выходной интерфейс</translation> + </message> + <message> + <source>Filter</source> + <translation>Фильтрация/сглаживание</translation> + </message> + <message> + <source>Running as root is bad</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Do not run as root. Set correct device node permissions.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Running as root is bad, seriously</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Do not run as root. I'll keep whining at every startup.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Be annoyed, comprehensively.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Don't run as root to remove these annoying messages.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>new_file_dialog</name> + <message> + <source>File exists</source> + <translation>Файл создан</translation> + </message> + <message> + <source>This file already exists. Pick another name.</source> + <translation>Данный файл уже создан. Пожалуйста выберите другое имя.</translation> + </message> +</context> +</TS> diff --git a/opentrack/lang/stub.ts b/opentrack/lang/stub.ts new file mode 100644 index 00000000..04555dd0 --- /dev/null +++ b/opentrack/lang/stub.ts @@ -0,0 +1,189 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1"> +<context> + <name>UI_new_config</name> + <message> + <source>Config filename</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>New file name:</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>main_window</name> + <message> + <source>Raw tracker data</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Z</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Pitch</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Y</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>X</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Roll</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Yaw</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Game data</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Profile</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Options</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mapping</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracking</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Start</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Stop</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Input</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>🔨</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Output</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Running as root is bad</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Do not run as root. Set correct device node permissions.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Running as root is bad, seriously</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Do not run as root. I'll keep whining at every startup.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Create new empty config</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Create new copied config</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Open configuration directory</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>opentrack</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> (debug)</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Show the Octopus</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Hide the Octopus</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Tracker settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Filter settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Protocol settings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Mappings</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Exit</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>The Octopus is sad</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Check permissions for your .ini directory: + +"%1"%2 + +Exiting now.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source> :: </source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Be annoyed, comprehensively.</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>Don't run as root to remove these annoying messages.</source> + <translation type="unfinished"></translation> + </message> +</context> +<context> + <name>new_file_dialog</name> + <message> + <source>File exists</source> + <translation type="unfinished"></translation> + </message> + <message> + <source>This file already exists. Pick another name.</source> + <translation type="unfinished"></translation> + </message> +</context> +</TS> diff --git a/opentrack/lang/zh_CN.ts b/opentrack/lang/zh_CN.ts new file mode 100644 index 00000000..15609d70 --- /dev/null +++ b/opentrack/lang/zh_CN.ts @@ -0,0 +1,193 @@ +<?xml version="1.0" encoding="utf-8"?> +<!DOCTYPE TS> +<TS version="2.1" language="zh_CN"> +<context> + <name>UI_new_config</name> + <message> + <source>Config filename</source> + <translation>配置文件名字:</translation> + </message> + <message> + <source>New file name:</source> + <translation>新文件名字:</translation> + </message> +</context> +<context> + <name>main_window</name> + <message> + <source>Raw tracker data</source> + <translation>跟踪器原始数据</translation> + </message> + <message> + <source>Z</source> + <translation></translation> + </message> + <message> + <source>Pitch</source> + <translation>仰俯</translation> + </message> + <message> + <source>Y</source> + <translation></translation> + </message> + <message> + <source>X</source> + <translation></translation> + </message> + <message> + <source>Roll</source> + <translation>横滚</translation> + </message> + <message> + <source>Yaw</source> + <translation>偏航</translation> + </message> + <message> + <source>Game data</source> + <translation>游戏得到的数据</translation> + </message> + <message> + <source>Profile</source> + <translation>配置文件</translation> + </message> + <message> + <source>Options</source> + <translation>选项</translation> + </message> + <message> + <source>Mapping</source> + <translation>映射</translation> + </message> + <message> + <source>Tracking</source> + <translation>跟踪</translation> + </message> + <message> + <source>Start</source> + <translation>开始</translation> + </message> + <message> + <source>Stop</source> + <translation>停止</translation> + </message> + <message> + <source>Input</source> + <translation>输入</translation> + </message> + <message> + <source>🔨</source> + <translation></translation> + </message> + <message> + <source>Output</source> + <translation>输出</translation> + </message> + <message> + <source>Filter</source> + <translation>过滤器</translation> + </message> + <message> + <source>Running as root is bad</source> + <translation>以管理员运行不是什么好主意</translation> + </message> + <message> + <source>Do not run as root. Set correct device node permissions.</source> + <translation>请不要以管理员运行。可以设置何时的设备访问权限来解决。</translation> + </message> + <message> + <source>Running as root is bad, seriously</source> + <translation>再说一遍,以管理员运行不是什么好主意</translation> + </message> + <message> + <source>Do not run as root. I'll keep whining at every startup.</source> + <translation>请不要以管理员运行。这话每次启动我都会强调一遍。</translation> + </message> + <message> + <source>Don't run as root to remove these annoying messages.</source> + <translation>以管理员运行真的非常不好</translation> + </message> + <message> + <source>Be annoyed, comprehensively.</source> + <translation>请不要以管理员运行。烦了吧?赶紧动作起来</translation> + </message> + <message> + <source>Create new empty config</source> + <translation>新建一个空的配置</translation> + </message> + <message> + <source>Create new copied config</source> + <translation>新建一个复制配置</translation> + </message> + <message> + <source>Open configuration directory</source> + <translation>打开配置目录</translation> + </message> + <message> + <source>opentrack</source> + <translation></translation> + </message> + <message> + <source> (debug)</source> + <translation> (调试)</translation> + </message> + <message> + <source>Show the Octopus</source> + <translation>显示八爪鱼</translation> + </message> + <message> + <source>Hide the Octopus</source> + <translation>隐藏八爪鱼</translation> + </message> + <message> + <source>Tracker settings</source> + <translation>跟踪器设置</translation> + </message> + <message> + <source>Filter settings</source> + <translation>过滤器设置</translation> + </message> + <message> + <source>Protocol settings</source> + <translation>协议设置</translation> + </message> + <message> + <source>Mappings</source> + <translation>影射</translation> + </message> + <message> + <source>Exit</source> + <translation>退出</translation> + </message> + <message> + <source>The Octopus is sad</source> + <translation>八爪鱼不开心了</translation> + </message> + <message> + <source>Check permissions for your .ini directory: + +"%1"%2 + +Exiting now.</source> + <translation>检查一下你的 .ini 目录权限: + +"%1"%2 + +退出先.</translation> + </message> + <message> + <source> :: </source> + <translation></translation> + </message> +</context> +<context> + <name>new_file_dialog</name> + <message> + <source>File exists</source> + <translation>文件已经存在</translation> + </message> + <message> + <source>This file already exists. Pick another name.</source> + <translation>文件重名了,换个其他名字</translation> + </message> +</context> +</TS> diff --git a/opentrack/main-window.cpp b/opentrack/main-window.cpp new file mode 100644 index 00000000..20506d81 --- /dev/null +++ b/opentrack/main-window.cpp @@ -0,0 +1,1140 @@ +/* Copyright (c) 2013-2016, Stanislaw Halik <sthalik@misaki.pl> + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#include "main-window.hpp" +#include "logic/pipeline.hpp" +#include "options/options.hpp" +#include "new_file_dialog.h" +#include "migration/migration.hpp" +#include "compat/check-visible.hpp" +#include "compat/sleep.hpp" +#include "compat/macros.h" +#include "compat/library-path.hpp" +#include "compat/math.hpp" +#include "compat/sysexits.hpp" +#include "opentrack/defs.hpp" + +#include <cstring> +#include <utility> + +#include <QMessageBox> +#include <QDesktopServices> +#include <QDesktopWidget> +#include <QApplication> + +#include <QFile> +#include <QFileInfo> +#include <QDir> +#include <QDateTime> + + +#ifdef __APPLE__ +void disable_appnap_start(); +void disable_appnap_stop(); +#endif + +extern "C" const char* const opentrack_version; + +using namespace options::globals; +using namespace options; + +main_window::main_window() : State(OPENTRACK_BASE_PATH + OPENTRACK_LIBRARY_PATH) +{ + ui.setupUi(this); + +#if !defined _WIN32 + annoy_if_root(); +#endif + + adjustSize(); + setWindowFlag(Qt::MSWindowsFixedSizeDialogHint); + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + init_profiles(); + init_buttons(); + init_dylibs(); + init_shortcuts(); + + init_tray_menu(); + setVisible(!start_in_tray()); + ensure_tray(); + + connect(&pose_update_timer, &QTimer::timeout, + this, &main_window::show_pose, Qt::DirectConnection); + connect(&det_timer, &QTimer::timeout, + this, &main_window::maybe_start_profile_from_executable); + det_timer.start(1000); + connect(&*s.b, &options::bundle_::reloading, this, &main_window::register_shortcuts); + connect(&*s.b, &options::bundle_::saving, this, &main_window::register_shortcuts); + + ui.btnStartTracker->setFocus(); +#ifdef UI_NO_VIDEO_FEED + fake_video_frame.resize(640, 480); + fake_video_frame_parent.setVisible(false); +#endif +} + +void main_window::init_shortcuts() +{ + register_shortcuts(); + + // ctrl+q exits + connect(&kbd_quit, &QShortcut::activated, this, [this]() { main_window::exit(EXIT_SUCCESS); }, Qt::DirectConnection); + kbd_quit.setEnabled(true); +} + +void main_window::init_dylibs() +{ + using dylib_ptr = Modules::dylib_ptr; + using dylib_list = Modules::dylib_list; + + modules.filters().insert(modules.filters().begin(), + std::make_shared<dylib>("", dylib_type::Filter)); + +#ifndef UI_NO_TRACKER_COMBOBOX + for (dylib_ptr& x : modules.trackers()) + ui.iconcomboTrackerSource->addItem(x->icon, x->name, x->module_name); +#endif + + for (dylib_ptr& x : modules.protocols()) + ui.iconcomboProtocol->addItem(x->icon, x->name, x->module_name); + +#ifndef UI_NO_FILTER_COMBOBOX + for (dylib_ptr& x : modules.filters()) + ui.iconcomboFilter->addItem(x->icon, x->name, x->module_name); +#endif + +#ifndef UI_NO_TRACKER_COMBOBOX + connect(ui.iconcomboTrackerSource, &QComboBox::currentTextChanged, + this, [this](const QString&) { pTrackerDialog = nullptr; if (options_widget) options_widget->tracker_module_changed(); }); +#endif + + connect(ui.iconcomboProtocol, &QComboBox::currentTextChanged, + this, [this](const QString&) { pProtocolDialog = nullptr; if (options_widget) options_widget->proto_module_changed(); }); + +#ifndef UI_NO_FILTER_COMBOBOX + connect(ui.iconcomboFilter, &QComboBox::currentTextChanged, + this, [this](const QString&) { pFilterDialog = nullptr; if (options_widget) options_widget->filter_module_changed(); }); +#endif + + connect(&m.tracker_dll, value_::value_changed<QString>(), + this, &main_window::save_modules, + Qt::DirectConnection); + + connect(&m.protocol_dll, value_::value_changed<QString>(), + this, &main_window::save_modules, + Qt::DirectConnection); + + connect(&m.filter_dll, value_::value_changed<QString>(), + this, &main_window::save_modules, + Qt::DirectConnection); + + { + struct list { + dylib_list& libs; + QComboBox* input; + value<QString>& place; + }; + + list types[] { +#ifndef UI_NO_TRACKER_COMBOBOX + { modules.trackers(), ui.iconcomboTrackerSource, m.tracker_dll }, +#endif + { modules.protocols(), ui.iconcomboProtocol, m.protocol_dll }, +#ifndef UI_NO_FILTER_COMBOBOX + { modules.filters(), ui.iconcomboFilter, m.filter_dll }, +#endif + }; + + for (list& type : types) + { + list t = type; + tie_setting(t.place, t.input, + [t](const QString& name) { + auto [ptr, idx] = module_by_name(name, t.libs); + return idx; + }, + [t](int, const QVariant& userdata) { + auto [ptr, idx] = module_by_name(userdata.toString(), t.libs); + if (ptr) + return ptr->module_name; + else + return QString(); + }); + } + } +} + +void main_window::init_profiles() +{ + copy_presets(); + refresh_profile_list(); + // implicitly created by `ini_directory()' + if (ini_directory().isEmpty() || !QDir(ini_directory()).isReadable()) + die_on_profile_not_writable(); + + set_profile(ini_filename()); + + // profile menu + profile_menu.addAction(tr("Create new empty config"), this, &main_window::create_empty_profile); + profile_menu.addAction(tr("Create new copied config"), this, &main_window::create_copied_profile); + profile_menu.addAction(tr("Open configuration directory"), this, &main_window::open_profile_directory); + ui.profile_button->setMenu(&profile_menu); + + connect(&profile_list_timer, &QTimer::timeout, this, &main_window::refresh_profile_list); + profile_list_timer.start(1000 * 5); + + connect(ui.iconcomboProfile, &QComboBox::currentTextChanged, + this, [this](const QString& x) { main_window::set_profile(x); }); +} + +void main_window::init_tray_menu() +{ + tray_menu.clear(); + + QString display_name(opentrack_version); + if (display_name.startsWith("opentrack-")) + display_name = tr("opentrack") + " " + display_name.mid(sizeof("opentrack-") - 1); + if (display_name.endsWith("-DEBUG")) + display_name.replace(display_name.size() - int(sizeof("DEBUG")), display_name.size(), tr(" (debug)")); + + menu_action_header.setEnabled(false); + menu_action_header.setText(display_name); + menu_action_header.setIcon(QIcon(":/images/opentrack.png")); + tray_menu.addAction(&menu_action_header); + + menu_action_show.setIconVisibleInMenu(true); + menu_action_show.setText(isHidden() ? tr("Show the Octopus") : tr("Hide the Octopus")); + menu_action_show.setIcon(QIcon(":/images/opentrack.png")); + QObject::connect(&menu_action_show, &QAction::triggered, this, [this] { toggle_restore_from_tray(QSystemTrayIcon::Trigger); }); + tray_menu.addAction(&menu_action_show); + + tray_menu.addSeparator(); + + menu_action_tracker.setText(tr("Tracker settings")); + menu_action_tracker.setIcon(QIcon(":/images/tools.png")); + QObject::connect(&menu_action_tracker, &QAction::triggered, this, &main_window::show_tracker_settings); + tray_menu.addAction(&menu_action_tracker); + + menu_action_filter.setText(tr("Filter settings")); + menu_action_filter.setIcon(QIcon(":/images/filter-16.png")); + QObject::connect(&menu_action_filter, &QAction::triggered, this, &main_window::show_filter_settings); + tray_menu.addAction(&menu_action_filter); + + menu_action_proto.setText(tr("Protocol settings")); + menu_action_proto.setIcon(QIcon(":/images/settings16.png")); + QObject::connect(&menu_action_proto, &QAction::triggered, this, &main_window::show_proto_settings); + tray_menu.addAction(&menu_action_proto); + + tray_menu.addSeparator(); + + menu_action_mappings.setIcon(QIcon(":/images/curves.png")); + menu_action_mappings.setText(tr("Mappings")); + QObject::connect(&menu_action_mappings, &QAction::triggered, this, &main_window::show_mapping_window); + tray_menu.addAction(&menu_action_mappings); + + menu_action_options.setIcon(QIcon(":/images/tools.png")); + menu_action_options.setText(tr("Options")); + QObject::connect(&menu_action_options, &QAction::triggered, this, [this] { show_options_dialog(true); }); + tray_menu.addAction(&menu_action_options); + + tray_menu.addSeparator(); + + menu_action_exit.setText(tr("Exit")); + QObject::connect(&menu_action_exit, &QAction::triggered, this, &main_window::exit); + tray_menu.addAction(&menu_action_exit); + + connect(&s.tray_enabled, value_::value_changed<bool>(), + this, &main_window::ensure_tray); +} + +void main_window::init_buttons() +{ + update_button_state(false, false); + connect(ui.btnEditCurves, &QPushButton::clicked, this, &main_window::show_mapping_window); + connect(ui.btnShortcuts, &QPushButton::clicked, this, [this] { show_options_dialog(true); }); +#ifndef UI_NO_TRACKER_SETTINGS_BUTTON + connect(ui.btnShowEngineControls, &QPushButton::clicked, this, &main_window::show_tracker_settings); +#endif + connect(ui.btnShowServerControls, &QPushButton::clicked, this, &main_window::show_proto_settings); +#ifndef UI_NO_FILTER_SETTINGS_BUTTON + connect(ui.btnShowFilterControls, &QPushButton::clicked, this, &main_window::show_filter_settings); +#endif + connect(ui.btnStartTracker, &QPushButton::clicked, this, &main_window::start_tracker_); + connect(ui.btnStopTracker, &QPushButton::clicked, this, &main_window::stop_tracker_); +} + +void main_window::register_shortcuts() +{ + global_shortcuts.reload({ + { s.key_start_tracking1, [this](bool) { start_tracker(); }, true }, + { s.key_start_tracking2, [this](bool) { start_tracker(); }, true }, + + { s.key_stop_tracking1, [this](bool) { stop_tracker(); }, true }, + { s.key_stop_tracking2, [this](bool) { stop_tracker(); }, true }, + + { s.key_toggle_tracking1, [this](bool) { toggle_tracker(); }, true }, + { s.key_toggle_tracking2, [this](bool) { toggle_tracker(); }, true }, + + { s.key_restart_tracking1, [this](bool) { restart_tracker(); }, true }, + { s.key_restart_tracking2, [this](bool) { restart_tracker(); }, true }, + }); + + if (work) + work->reload_shortcuts(); +} + +void main_window::die_on_profile_not_writable() +{ + stop_tracker_(); + + static const QString pad(16, QChar(' ')); + + QMessageBox::critical(this, + tr("The Octopus is sad"), + tr("Check permissions for your .ini directory:\n\n\"%1\"%2\n\nExiting now.").arg(ini_directory(), pad), + QMessageBox::Close, QMessageBox::NoButton); + + exit(EX_OSFILE); +} + +bool main_window::profile_name_from_dialog(QString& ret) +{ + new_file_dialog dlg; + dlg.exec(); + return dlg.is_ok(ret); +} + +main_window::~main_window() +{ + // stupid ps3 eye has LED issues +#ifndef UI_NO_VIDEO_FEED + if (work && ui.video_frame->layout()) +#else + if (work) +#endif + { + hide(); + stop_tracker_(); + close(); + + portable::sleep(1000); + } + + exit(); +} + +void main_window::save_modules() +{ + m.b->save(); +} + +void main_window::create_empty_profile() +{ + QString name; + if (profile_name_from_dialog(name)) + { + (void)create_profile_from_preset(name); + refresh_profile_list(); + + if (profile_list.contains(name)) + { + QSignalBlocker q(ui.iconcomboProfile); + + set_profile(name, false); + mark_profile_as_not_needing_migration(); + } + } +} + +void main_window::create_copied_profile() +{ + const QString cur = ini_pathname(); + QString name; + if (!cur.isEmpty() && profile_name_from_dialog(name)) + { + const QString new_name = ini_combine(name); + (void) QFile::remove(new_name); + QFile::copy(cur, new_name); + + refresh_profile_list(); + + if (profile_list.contains(name)) + { + QSignalBlocker q(ui.iconcomboProfile); + + set_profile(name, false); + mark_profile_as_not_needing_migration(); + } + } + +} + +void main_window::open_profile_directory() +{ + QDesktopServices::openUrl("file:///" + QDir::toNativeSeparators(ini_directory())); +} + +void main_window::refresh_profile_list() +{ + if (work) + return; + + QStringList list = ini_list(); + QString current = ini_filename(); + + if (list == profile_list) + return; + + if (!list.contains(current)) + current = OPENTRACK_DEFAULT_PROFILE; + + profile_list = list; + + static const QIcon icon(":/images/settings16.png"); + + QSignalBlocker l(ui.iconcomboProfile); + + ui.iconcomboProfile->clear(); + ui.iconcomboProfile->addItems(list); + + for (int i = 0; i < list.size(); i++) + ui.iconcomboProfile->setItemIcon(i, icon); + + ui.iconcomboProfile->setCurrentText(current); +} + + + +void main_window::update_button_state(bool running, bool inertialp) +{ + bool not_running = !running; + ui.iconcomboProfile->setEnabled(not_running); + ui.btnStartTracker->setEnabled(not_running); + ui.btnStopTracker->setEnabled(running); + ui.iconcomboProtocol->setEnabled(not_running); +#ifndef UI_NO_FILTER_COMBOBOX + ui.iconcomboFilter->setEnabled(not_running); +#endif +#ifndef UI_NO_TRACKER_COMBOBOX + ui.iconcomboTrackerSource->setEnabled(not_running); +#endif + ui.profile_button->setEnabled(not_running); +#ifndef UI_NO_VIDEO_FEED + ui.video_frame_label->setVisible(not_running || inertialp); + if(not_running) + { + ui.video_frame_label->setPixmap(QPixmap(":/images/tracking-not-started.png")); + } + else { + ui.video_frame_label->setPixmap(QPixmap(":/images/no-feed.png")); + } +#endif +} + +void main_window::start_tracker_() +{ + if (work) + return; + +#ifdef __APPLE__ + disable_appnap_start(); +#endif + + +#ifndef UI_NO_VIDEO_FEED + auto* frame = ui.video_frame; +#else + auto* frame = &fake_video_frame; +#endif + work = std::make_shared<Work>(pose, frame, current_tracker(), current_protocol(), current_filter()); + + if (!work->is_ok()) + { + work = nullptr; + return; + } + + { + double p[6] = {0,0,0, 0,0,0}; + show_pose_(p, p); + } + + if (pTrackerDialog) + pTrackerDialog->register_tracker(&*work->libs.pTracker); + + if (pFilterDialog && work->libs.pFilter) + pFilterDialog->register_filter(&*work->libs.pFilter); + + if (pProtocolDialog) + pProtocolDialog->register_protocol(&*work->libs.pProtocol); + + if (options_widget) + { + options_widget->register_tracker(&*work->libs.pTracker); + options_widget->register_protocol(&*work->libs.pProtocol); + if (work->libs.pFilter) + options_widget->register_filter(&*work->libs.pFilter); + } + + pose_update_timer.start(1000/30); + + // NB check valid since SelectedLibraries ctor called + // trackers take care of layout state updates + const bool is_inertial = frame->layout() == nullptr; + update_button_state(true, is_inertial); + + ui.btnStopTracker->setFocus(); +} + +void main_window::stop_tracker_() +{ + if (!work) + return; + +#ifdef __APPLE__ + disable_appnap_stop(); +#endif + + force_is_visible(true); + with_tracker_teardown sentinel; + + pose_update_timer.stop(); + ui.pose_display->present(0,0,0, 0,0,0); + + if (options_widget) + { + // XXX TODO other module types + options_widget->unregister_tracker(); + } + + if (pTrackerDialog) + pTrackerDialog->unregister_tracker(); + + if (pProtocolDialog) + pProtocolDialog->unregister_protocol(); + + if (pFilterDialog) + pFilterDialog->unregister_filter(); + + work = nullptr; + + { + double p[6] {}; + show_pose_(p, p); + } + + update_button_state(false, false); + set_title(); + ui.btnStartTracker->setFocus(); +} + +void main_window::show_pose_(const double* mapped, const double* raw) +{ + ui.pose_display->present(mapped[Yaw], mapped[Pitch], -mapped[Roll], + mapped[TX], mapped[TY], mapped[TZ]); + +#ifndef UI_NO_RAW_DATA + QLCDNumber* raw_[] = { + ui.raw_x, ui.raw_y, ui.raw_z, + ui.raw_yaw, ui.raw_pitch, ui.raw_roll, + }; +#endif +#ifndef UI_NO_GAME_DATA + QLCDNumber* mapped_[] = { + ui.pose_x, ui.pose_y, ui.pose_z, + ui.pose_yaw, ui.pose_pitch, ui.pose_roll, + }; +#endif + +#if !defined UI_NO_RAW_DATA || !defined UI_NO_GAME_DATA + for (int k = 0; k < 6; k++) +#endif + { +#ifndef UI_NO_RAW_DATA + raw_[k]->display(iround(raw[k])); +#endif +#ifndef UI_NO_GAME_DATA + mapped_[k]->display(iround(mapped[k])); +#endif + } + + QString game_title; + if (work && work->libs.pProtocol) + game_title = work->libs.pProtocol->game_name(); + set_title(game_title); +} + +void main_window::set_title(const QString& game_title) +{ + static const QString version{opentrack_version}; + static const QString sep { tr(" :: ") }; + static const QString pat1{ version + sep + "%1" + sep + "%2" }; + static const QString pat2{ version + sep + "%1" }; + + const QString current = ini_filename(); + + if (game_title.isEmpty()) + setWindowTitle(pat2.arg(current)); + else + setWindowTitle(pat1.arg(current, game_title)); +} + +void main_window::show_pose() +{ + set_is_visible(*this); + + if (mapping_widget) + mapping_widget->refresh_tab(); + + if (!check_is_visible()) + return; + + double mapped[6], raw[6]; + + work->pipeline_.raw_and_mapped_pose(mapped, raw); + + show_pose_(mapped, raw); +} + +bool main_window::module_tabs_enabled() const +{ + return true; +#if 0 + enum module_tab_state { tabs_maybe = -1, tabs_disable, tabs_enable }; + + static const auto force = progn( + auto str = getenv("OPENTRACK_MODULE_TABS"); + if (!str || !*str) + return tabs_maybe; + constexpr const char* strings_for_false[] = { + "0", "n", "f", "disable", + }; + constexpr const char* strings_for_true[] = { + "1", "y", "t", "enable", + }; + for (const auto* x : strings_for_false) + if (!strncasecmp(str, x, strlen(x))) + return tabs_disable; + for (const auto* x : strings_for_true) + if (!strncasecmp(str, x, strlen(x))) + return tabs_enable; + qDebug() << "main-window: invalid boolean for OPENTRACK_MODULE_TABS:" + << QLatin1String{str}; + return tabs_maybe; + ); + switch (force) + { + case tabs_disable: return false; + case tabs_enable: return true; + case tabs_maybe: (void)0; + } + auto* d = QApplication::desktop(); + if (!d) + return false; + // Windows 10: 40px, Windows 11: 48px, KDE: 51px + constexpr int taskbar_size = 51; + constexpr int min_avail_height = 768 - taskbar_size; + QRect rect = d->availableGeometry(this); + return rect.height() >= min_avail_height; +#endif +} + +static void show_window(QWidget& d, bool fresh) +{ + if (fresh) + { + d.setWindowFlag(Qt::MSWindowsFixedSizeDialogHint); + d.adjustSize(); + d.setFixedSize(d.size()); + d.show(); +#ifdef __APPLE__ + d.raise(); +#endif + d.activateWindow(); + } + else + { + d.show(); +#ifdef __APPLE__ + d.raise(); +#endif + d.activateWindow(); + } +} + +template<typename t, typename... Args> +static bool mk_window(std::unique_ptr<t>& d, bool show, Args&&... params) +{ + bool fresh = false; + + if (!(d && d->isVisible())) + { + d = std::make_unique<t>(std::forward<Args>(params)...); + fresh = !!d; + } + + if (d) + { + if (show && !d->embeddable()) + show_window(*d, fresh); + } + + return fresh; +} + +template<typename Instance, typename Dialog> +static void show_module_settings(std::shared_ptr<Instance> instance, + std::unique_ptr<Dialog>& dialog, + const Modules::dylib_ptr& lib, + std::unique_ptr<options_dialog>& options_widget, + main_window* win, + bool show, + void(Dialog::*register_fun)(Instance*), + void(options_dialog::*switch_tab_fun)()) +{ + if (!lib || !lib->Dialog) + return; + + bool fresh = !(dialog && dialog->isVisible()); + if (fresh) + dialog = std::unique_ptr<Dialog>{(Dialog*)lib->Dialog()}; + bool embed = dialog->embeddable() && win->module_tabs_enabled(); + + if (!embed) + { + if (fresh) + { + if (instance) + ((*dialog).*register_fun)(&*instance); + } + if (show) + show_window(*dialog, fresh); + } + else if (show) + { + bool fresh = !options_widget; + win->show_options_dialog(false); + ((*options_widget).*switch_tab_fun)(); + show_window(*options_widget, fresh); + } +} + +void main_window::show_tracker_settings_(bool show) +{ + show_module_settings(work ? work->libs.pTracker : nullptr, pTrackerDialog, current_tracker(), + options_widget, this, show, + &ITrackerDialog::register_tracker, &options_dialog::switch_to_tracker_tab); +} + +void main_window::show_proto_settings_(bool show) +{ + show_module_settings(work ? work->libs.pProtocol : nullptr, pProtocolDialog, current_protocol(), + options_widget, this, show, + &IProtocolDialog::register_protocol, &options_dialog::switch_to_proto_tab); +} + +void main_window::show_filter_settings_(bool show) +{ + show_module_settings(work ? work->libs.pFilter : nullptr, pFilterDialog, current_filter(), + options_widget, this, show, + &IFilterDialog::register_filter, &options_dialog::switch_to_filter_tab); +} + +void main_window::show_options_dialog(bool show) +{ + if (options_widget && options_widget->isVisible()) + { + if (show) + show_window(*options_widget, false); + return; + } + + bool embed = module_tabs_enabled(); + + if (embed) + { + show_tracker_settings_(false); + show_proto_settings_(false); + show_filter_settings_(false); + } + + // make them into unique_ptr<BaseDialog> + std::unique_ptr<ITrackerDialog> empty_ITD; + std::unique_ptr<IProtocolDialog> empty_IPD; + std::unique_ptr<IFilterDialog> empty_IFD; + + mk_window(options_widget, false, + embed ? pTrackerDialog : empty_ITD, + embed ? pProtocolDialog : empty_IPD, + embed ? pFilterDialog : empty_IFD, + [this](bool flag) { set_keys_enabled(!flag); }); + + if (work) + { + if (work->libs.pTracker) + options_widget->register_tracker(&*work->libs.pTracker); + if (work->libs.pProtocol) + options_widget->register_protocol(&*work->libs.pProtocol); + if (work->libs.pFilter) + options_widget->register_filter(&*work->libs.pFilter); + } + + if (show) + show_window(*options_widget, true); +} + +void main_window::show_mapping_window() +{ + mk_window(mapping_widget, true, pose); +} + +void main_window::exit(int status) +{ + // don't use std::call_once here, leads to freeze in Microsoft's CRT + // this function never needs reentrancy anyway + + // this is probably harmless, but better safe than sorry + if (exiting_already) + return; + exiting_already = true; + + //qDebug() << "opentrack: exiting"; + + if (tray) + tray->hide(); + tray = nullptr; + + //close(); + QApplication::setQuitOnLastWindowClosed(true); + QApplication::exit(status); +} + +void main_window::set_profile(const QString& new_name_, bool migrate) +{ + QSignalBlocker b(ui.iconcomboProfile); + + QString new_name = new_name_; + bool do_refresh = false; + + if (!profile_list.contains(new_name_)) + { + new_name = QStringLiteral(OPENTRACK_DEFAULT_PROFILE); + if (!profile_list.contains(new_name)) + { + migrate = false; + do_refresh = true; + } + } + + if (create_profile_from_preset(new_name)) + migrate = true; + + if (do_refresh) + refresh_profile_list(); + assert(profile_list.contains(new_name)); + + const bool status = new_name != ini_filename(); + + if (status) + set_profile_in_registry(new_name); + + using bundler = options::detail::bundler; + + bundler::reload_no_notify(); + + if (migrate) + // migrations are for config layout changes and other user-visible + // incompatibilities in future versions + run_migrations(); + else + mark_profile_as_not_needing_migration(); + + bundler::notify(); + + set_title(); + + if (status) + ui.iconcomboProfile->setCurrentText(new_name); +} + +void main_window::ensure_tray() +{ + if (!tray_enabled()) + { + if (!isVisible()) + { + show(); + +#ifdef __APPLE__ + raise(); // for OSX +#endif + activateWindow(); // for Windows + } + + if (tray) + tray->hide(); + tray = nullptr; + + QApplication::setQuitOnLastWindowClosed(true); + } + else + { + if (!tray) + { + tray = std::make_unique<QSystemTrayIcon>(this); + tray->setIcon(QIcon(":/images/opentrack.png")); + tray->setContextMenu(&tray_menu); + tray->show(); + + connect(&*tray, &QSystemTrayIcon::activated, + this, &main_window::toggle_restore_from_tray); + } + + QApplication::setQuitOnLastWindowClosed(false); + } +} + +void main_window::toggle_restore_from_tray(QSystemTrayIcon::ActivationReason e) +{ + switch (e) + { + // if we enable double click also then it causes + // toggle back to the original state + //case QSystemTrayIcon::DoubleClick: + case QSystemTrayIcon::Trigger: // single click + break; + default: + return; + } + + ensure_tray(); + + const bool is_minimized = isHidden() || !tray_enabled(); + + menu_action_show.setText(!isHidden() ? tr("Show the Octopus") : tr("Hide the Octopus")); + + setVisible(is_minimized); + + if (is_minimized) + { +#ifdef __APPLE__ + raise(); // for OSX +#endif + activateWindow(); // for Windows + } + else + { + lower(); + clearFocus(); + } +} + +bool main_window::maybe_hide_to_tray(QEvent* e) +{ + if (e->type() == QEvent::WindowStateChange && + (windowState() & Qt::WindowMinimized) && + tray_enabled()) + { + e->accept(); + ensure_tray(); + hide(); + + return true; + } + + return false; +} + +void main_window::closeEvent(QCloseEvent*) +{ + exit(); +} + +void main_window::maybe_start_profile_from_executable() +{ + if (!work) + { + QString profile; + if (det.profile_to_start(profile) && profile_list.contains(profile)) + { + set_profile(profile); + start_tracker_(); + } + } + else + { + if (det.should_stop()) + stop_tracker_(); + } +} + +void main_window::set_keys_enabled(bool flag) +{ + if (!flag) + { + if (work) + work->sc.reload({}); + global_shortcuts.reload({}); + } + else + register_shortcuts(); +} + +void main_window::changeEvent(QEvent* e) +{ + if (!maybe_hide_to_tray(e)) + e->ignore(); +} + +bool main_window::event(QEvent* event) +{ + using t = QEvent::Type; + + if (work) + { + switch (event->type()) + { + case t::Hide: + case t::WindowActivate: + case t::WindowDeactivate: + case t::WindowStateChange: + case t::FocusIn: + set_is_visible(*this, true); + break; + default: + break; + } + } + return QMainWindow::event(event); +} + +bool main_window::tray_enabled() +{ + return s.tray_enabled && QSystemTrayIcon::isSystemTrayAvailable(); +} + +bool main_window::start_in_tray() +{ + return tray_enabled() && s.tray_start; +} + +void main_window::set_profile_in_registry(const QString &profile) +{ + with_global_settings_object([&](QSettings& s) { + s.setValue(OPENTRACK_PROFILE_FILENAME_KEY, profile); + mark_global_ini_modified(); + }); +} + +void main_window::restart_tracker_() +{ + qDebug() << "restart tracker"; + + stop_tracker_(); + start_tracker_(); +} + +void main_window::toggle_tracker_() +{ + qDebug() << "toggle tracker"; + + if (work) + stop_tracker_(); + else + start_tracker_(); +} + +void main_window::copy_presets() +{ + const QString preset_dir = library_path + "/presets/"; + const QDir dir{preset_dir}; + if (!dir.exists()) + { + qDebug() << "no preset dir"; + return; + } + with_global_settings_object([&](QSettings& s) { + const QString& key = QStringLiteral("last-preset-copy-time"); + const auto last_time = s.value(key, -1LL).toLongLong(); + for (const auto& file : dir.entryInfoList({ "*.ini" }, QDir::Files, QDir::Name)) + { + if (file.fileName() == QStringLiteral("default.ini")) + continue; + if (last_time < file.lastModified().toSecsSinceEpoch()) + { + qDebug() << "copy preset" << file.fileName(); + (void)QFile::copy(file.filePath(), ini_combine(file.fileName())); + } + } + s.setValue(key, QDateTime::currentSecsSinceEpoch()); + }); +} + +bool main_window::create_profile_from_preset(const QString& name) +{ + const QString dest = ini_combine(name); + + if (QFile::exists(dest)) + return false; + + const QString& default_name = QStringLiteral(OPENTRACK_DEFAULT_PROFILE); + const QString default_preset = (library_path + "/presets/%1").arg(default_name); + + if (QFile::exists(default_preset)) + { + bool ret = QFile::copy(default_preset, dest); + if (ret) + qDebug() << "create profile" << name << "from default preset" << (ret ? "" : "FAILED!"); + return ret; + } + else + { + (void)QFile(ini_combine(name)).open(QFile::ReadWrite); + return false; + } +} + +#if !defined _WIN32 +# include <unistd.h> +void main_window::annoy_if_root() +{ + if (geteuid() == 0) + { + struct lst { + QString caption; + QString msg; + int sleep_ms; + }; + + const lst list[] = { + { + tr("Running as root is bad"), + tr("Do not run as root. Set correct device node permissions."), + 1000, + }, + { + tr("Running as root is bad, seriously"), + tr("Do not run as root. I'll keep whining at every startup."), + 3000, + }, + { + tr("Be annoyed, comprehensively."), + tr("Don't run as root to remove these annoying messages."), + 0 + } + }; + + for (const auto& x : list) + { + QMessageBox::critical(this, x.caption, x.msg, QMessageBox::Ok); + portable::sleep(x.sleep_ms); + } + } +} +#endif diff --git a/opentrack/main-window.hpp b/opentrack/main-window.hpp new file mode 100644 index 00000000..1dcbd0eb --- /dev/null +++ b/opentrack/main-window.hpp @@ -0,0 +1,153 @@ +/* Copyright (c) 2013-2016, Stanislaw Halik <sthalik@misaki.pl> + + * Permission to use, copy, modify, and/or distribute this + * software for any purpose with or without fee is hereby granted, + * provided that the above copyright notice and this permission + * notice appear in all copies. + */ + +#pragma once + +#include "opentrack/defs.hpp" +#include "api/plugin-support.hpp" +#include "gui/mapping-dialog.hpp" +#include "gui/options-dialog.hpp" +#include "gui/process_detector.h" +#include "logic/main-settings.hpp" +#include "logic/pipeline.hpp" +#include "logic/shortcuts.h" +#include "logic/work.hpp" +#include "logic/state.hpp" +#include "options/options.hpp" +#include "compat/qt-signal.hpp" + +#include <QMainWindow> +#include <QKeySequence> +#include <QShortcut> +#include <QTimer> +#include <QSystemTrayIcon> +#include <QString> +#include <QMenu> +#include <QAction> +#include <QList> + +#include <memory> + +#include "ui_main-window.h" + +class main_window final : public QMainWindow, private State +{ + Q_DECLARE_TR_FUNCTIONS(main_window) + + Ui::main_window ui; + + std::unique_ptr<QSystemTrayIcon> tray; + QMenu tray_menu { this }; + + QTimer pose_update_timer { this }; + QTimer det_timer; + QTimer profile_list_timer; + + Shortcuts global_shortcuts; + QShortcut kbd_quit { QKeySequence("Ctrl+Q"), this }; + +#ifdef UI_NO_VIDEO_FEED + QWidget fake_video_frame_parent; + QFrame fake_video_frame{&fake_video_frame_parent}; +#endif + + std::unique_ptr<options_dialog> options_widget; + std::unique_ptr<mapping_dialog> mapping_widget; + + std::unique_ptr<IFilterDialog> pFilterDialog; + std::unique_ptr<IProtocolDialog> pProtocolDialog; + std::unique_ptr<ITrackerDialog> pTrackerDialog; + + process_detector_worker det; + QMenu profile_menu; + + QList<QString> profile_list; + + QAction menu_action_header { &tray_menu }, + menu_action_show { &tray_menu }, + menu_action_exit { &tray_menu }, + menu_action_tracker { &tray_menu }, + menu_action_filter { &tray_menu }, + menu_action_proto { &tray_menu }, + menu_action_options { &tray_menu }, + menu_action_mappings { &tray_menu }; + + bool exiting_already { false }; + + qt_signal<void> start_tracker { this, &main_window::start_tracker_, Qt::QueuedConnection }; + qt_signal<void> stop_tracker { this, &main_window::stop_tracker_, Qt::QueuedConnection }; + qt_signal<void> toggle_tracker { this, &main_window::toggle_tracker_, Qt::QueuedConnection }; + qt_signal<void> restart_tracker { this, &main_window::restart_tracker_, Qt::QueuedConnection }; + +public: + void init_dylibs(); + void init_tray_menu(); + void init_profiles(); + void init_buttons(); + + void init_shortcuts(); + void register_shortcuts(); + void set_keys_enabled(bool flag); + + void update_button_state(bool running, bool inertialp); + +#if !defined _WIN32 + void annoy_if_root(); +#endif + + void changeEvent(QEvent* e) override; + bool maybe_hide_to_tray(QEvent* e); + void closeEvent(QCloseEvent *event) override; + bool event(QEvent *event) override; + + void show_tracker_settings_(bool show); + void show_proto_settings_(bool show); + void show_filter_settings_(bool show); + void show_tracker_settings() { show_tracker_settings_(true); } + void show_proto_settings() { show_proto_settings_(true); } + void show_filter_settings() { show_filter_settings_(true); } + + void show_options_dialog(bool show); + void show_mapping_window(); + + void show_pose(); + void show_pose_(const double* mapped, const double* raw); + void set_title(const QString& game_title = QString()); + + void start_tracker_(); + void stop_tracker_(); + void restart_tracker_(); + void toggle_tracker_(); + + [[nodiscard]] bool create_profile_from_preset(const QString& name); + void set_profile(const QString& new_name, bool migrate = true); + void set_profile_in_registry(const QString& profile); + void refresh_profile_list(); + void die_on_profile_not_writable(); + void maybe_start_profile_from_executable(); + [[nodiscard]] static bool profile_name_from_dialog(QString& ret); + void copy_presets(); + + void create_empty_profile(); + void create_copied_profile(); + void open_profile_directory(); + + void ensure_tray(); + void toggle_restore_from_tray(QSystemTrayIcon::ActivationReason e); + bool tray_enabled(); + bool start_in_tray(); + + void save_modules(); + bool module_tabs_enabled() const; + + void exit(int status = EXIT_SUCCESS); + +public: + main_window(); + ~main_window() override; +}; diff --git a/opentrack/main-window.ui b/opentrack/main-window.ui new file mode 100644 index 00000000..32c9f57a --- /dev/null +++ b/opentrack/main-window.ui @@ -0,0 +1,1343 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <author>Lovecraftian Octopus</author> + <class>main_window</class> + <widget class="QMainWindow" name="main_window"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>655</width> + <height>502</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>500</height> + </size> + </property> + <property name="windowIcon"> + <iconset resource="../gui/opentrack-res.qrc"> + <normaloff>:/images/opentrack.png</normaloff>:/images/opentrack.png</iconset> + </property> + <property name="styleSheet"> + <string notr="true">#video_feed { border: 0; } +</string> + </property> + <widget class="QWidget" name="content"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QGridLayout" name="gridLayout_2"> + <property name="leftMargin"> + <number>0</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>9</number> + </property> + <property name="bottomMargin"> + <number>9</number> + </property> + <item row="0" column="0"> + <widget class="QFrame" name="video_feed"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>320</width> + <height>240</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>320</width> + <height>240</height> + </size> + </property> + <property name="lineWidth"> + <number>0</number> + </property> + <widget class="QFrame" name="video_frame"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>320</width> + <height>240</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>320</width> + <height>240</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>320</width> + <height>240</height> + </size> + </property> + <widget class="QLabel" name="video_frame_label"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>320</width> + <height>240</height> + </rect> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>320</width> + <height>240</height> + </size> + </property> + <property name="maximumSize"> + <size> + <width>320</width> + <height>240</height> + </size> + </property> + <property name="font"> + <font> + <family>Candara</family> + <pointsize>37</pointsize> + <weight>50</weight> + <bold>false</bold> + <kerning>true</kerning> + </font> + </property> + <property name="text"> + <string/> + </property> + <property name="pixmap"> + <pixmap resource="../gui/opentrack-res.qrc">:/images/tracking-not-started.png</pixmap> + </property> + <property name="scaledContents"> + <bool>false</bool> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + <property name="wordWrap"> + <bool>true</bool> + </property> + </widget> + </widget> + </widget> + </item> + <item row="0" column="1"> + <widget class="pose_widget" name="pose_display" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>320</width> + <height>240</height> + </size> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QGroupBox" name="box_raw_headpose"> + <property name="minimumSize"> + <size> + <width>316</width> + <height>0</height> + </size> + </property> + <property name="title"> + <string>Raw tracker data</string> + </property> + <layout class="QGridLayout" name="gridLayout_12"> + <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> + <property name="spacing"> + <number>0</number> + </property> + <item row="0" column="3"> + <widget class="QLCDNumber" name="raw_yaw"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="lblZ_4"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Z</string> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="lblRotY_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Pitch</string> + </property> + </widget> + </item> + <item row="1" column="3"> + <widget class="QLCDNumber" name="raw_pitch"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="lblY_4"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Y</string> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="lblX_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>X</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLCDNumber" name="raw_x"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="lblRotZ_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Roll</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="lblRotX_4"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Yaw</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLCDNumber" name="raw_y"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLCDNumber" name="raw_roll"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLCDNumber" name="raw_z"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="1" column="1"> + <widget class="QGroupBox" name="box_mapped_headpose"> + <property name="minimumSize"> + <size> + <width>316</width> + <height>0</height> + </size> + </property> + <property name="title"> + <string>Game data</string> + </property> + <layout class="QGridLayout" name="gridLayout_14"> + <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> + <property name="spacing"> + <number>0</number> + </property> + <item row="1" column="3"> + <widget class="QLCDNumber" name="pose_pitch"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="0" column="3"> + <widget class="QLCDNumber" name="pose_yaw"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLCDNumber" name="pose_z"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="lblx"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>X</string> + </property> + </widget> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="lblY_2"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Y</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLCDNumber" name="pose_x"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="1" column="2"> + <widget class="QLabel" name="lblRotY_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Pitch</string> + </property> + </widget> + </item> + <item row="2" column="2"> + <widget class="QLabel" name="lblRotZ_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Roll</string> + </property> + </widget> + </item> + <item row="0" column="2"> + <widget class="QLabel" name="lblRotX_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Yaw</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="lblZ_2"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="autoFillBackground"> + <bool>false</bool> + </property> + <property name="frameShadow"> + <enum>QFrame::Raised</enum> + </property> + <property name="text"> + <string>Z</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLCDNumber" name="pose_y"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + <item row="2" column="3"> + <widget class="QLCDNumber" name="pose_roll"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <stylestrategy>NoAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="frameShape"> + <enum>QFrame::NoFrame</enum> + </property> + <property name="smallDecimalPoint"> + <bool>true</bool> + </property> + <property name="digitCount"> + <number>4</number> + </property> + <property name="segmentStyle"> + <enum>QLCDNumber::Flat</enum> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="1"> + <widget class="QWidget" name="groupControls" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Preferred" vsizetype="Minimum"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>315</width> + <height>0</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>4</number> + </property> + <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="QWidget" name="groupProfile" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>0</number> + </property> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QToolButton" name="profile_button"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="text"> + <string>Profile</string> + </property> + <property name="popupMode"> + <enum>QToolButton::InstantPopup</enum> + </property> + <property name="toolButtonStyle"> + <enum>Qt::ToolButtonTextBesideIcon</enum> + </property> + <property name="autoRaise"> + <bool>true</bool> + </property> + <property name="arrowType"> + <enum>Qt::DownArrow</enum> + </property> + </widget> + </item> + <item> + <widget class="QComboBox" name="iconcomboProfile"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>245</width> + <height>0</height> + </size> + </property> + <property name="focusPolicy"> + <enum>Qt::StrongFocus</enum> + </property> + <property name="maxVisibleItems"> + <number>20</number> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QWidget" name="groupOptions" native="true"> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>0</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>0</number> + </property> + <item> + <widget class="QPushButton" name="btnShortcuts"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Options</string> + </property> + <property name="icon"> + <iconset resource="../gui/opentrack-res.qrc"> + <normaloff>:/images/tools.png</normaloff>:/images/tools.png</iconset> + </property> + <property name="iconSize"> + <size> + <width>80</width> + <height>24</height> + </size> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnEditCurves"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="text"> + <string>Mapping</string> + </property> + <property name="icon"> + <iconset resource="../gui/opentrack-res.qrc"> + <normaloff>:/images/curves.png</normaloff>:/images/curves.png</iconset> + </property> + <property name="iconSize"> + <size> + <width>80</width> + <height>24</height> + </size> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupStartStop"> + <property name="sizePolicy"> + <sizepolicy hsizetype="MinimumExpanding" vsizetype="Maximum"> + <horstretch>4</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>0</height> + </size> + </property> + <property name="title"> + <string>Tracking</string> + </property> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <property name="spacing"> + <number>7</number> + </property> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <item> + <widget class="QToolButton" name="btnStartTracker"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>34</height> + </size> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Start</string> + </property> + </widget> + </item> + <item> + <widget class="QToolButton" name="btnStopTracker"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>0</width> + <height>34</height> + </size> + </property> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Stop</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + <item row="2" column="0"> + <widget class="QWidget" name="modules" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Minimum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>310</width> + <height>0</height> + </size> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <property name="spacing"> + <number>4</number> + </property> + <property name="leftMargin"> + <number>4</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="QGroupBox" name="groupTracker"> + <property name="title"> + <string>Input</string> + </property> + <layout class="QGridLayout" name="gridLayout"> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <property name="horizontalSpacing"> + <number>6</number> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QComboBox" name="iconcomboTrackerSource"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QToolButton" name="btnShowEngineControls"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <stylestrategy>PreferAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="focusPolicy"> + <enum>Qt::ClickFocus</enum> + </property> + <property name="text"> + <string>🔨</string> + </property> + <property name="flat" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupProto"> + <property name="title"> + <string>Output</string> + </property> + <layout class="QGridLayout" name="gridLayout_4"> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <property name="horizontalSpacing"> + <number>6</number> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QComboBox" name="iconcomboProtocol"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QToolButton" name="btnShowServerControls"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <stylestrategy>PreferAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="focusPolicy"> + <enum>Qt::ClickFocus</enum> + </property> + <property name="text"> + <string>🔨</string> + </property> + <property name="flat" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupFilter"> + <property name="title"> + <string>Filter</string> + </property> + <layout class="QGridLayout" name="gridLayout_3"> + <property name="leftMargin"> + <number>4</number> + </property> + <property name="topMargin"> + <number>4</number> + </property> + <property name="rightMargin"> + <number>4</number> + </property> + <property name="bottomMargin"> + <number>4</number> + </property> + <property name="horizontalSpacing"> + <number>6</number> + </property> + <property name="verticalSpacing"> + <number>0</number> + </property> + <item row="0" column="0"> + <widget class="QComboBox" name="iconcomboFilter"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Minimum" vsizetype="Maximum"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="focusPolicy"> + <enum>Qt::TabFocus</enum> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QToolButton" name="btnShowFilterControls"> + <property name="enabled"> + <bool>true</bool> + </property> + <property name="sizePolicy"> + <sizepolicy hsizetype="Maximum" vsizetype="Preferred"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="font"> + <font> + <family>DejaVu Sans</family> + <stylestrategy>PreferAntialias</stylestrategy> + <kerning>false</kerning> + </font> + </property> + <property name="focusPolicy"> + <enum>Qt::ClickFocus</enum> + </property> + <property name="text"> + <string>🔨</string> + </property> + <property name="flat" stdset="0"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </item> + </layout> + </widget> + </widget> + <customwidgets> + <customwidget> + <class>pose_widget</class> + <extends>QWidget</extends> + <header>pose-widget/pose-widget.hpp</header> + </customwidget> + </customwidgets> + <resources> + <include location="../gui/opentrack-res.qrc"/> + </resources> + <connections/> +</ui> diff --git a/opentrack/main.cpp b/opentrack/main.cpp new file mode 100644 index 00000000..2c1dc607 --- /dev/null +++ b/opentrack/main.cpp @@ -0,0 +1,23 @@ +#include "gui/init.hpp" +#include "main-window.hpp" + +#if defined _WIN32 +# include <windows.h> +#endif + +#ifdef __clang__ +# pragma GCC diagnostic ignored "-Wmain" +#endif + +int main(int argc, char** argv) +{ + return run_application(argc, argv, [] { return std::make_unique<main_window>(); }); +} + +#if defined _MSC_VER + +int CALLBACK WinMain(HINSTANCE, HINSTANCE, LPSTR, int /* nCmdShow */) +{ + return main(__argc, __argv); +} +#endif diff --git a/opentrack/new_config.ui b/opentrack/new_config.ui new file mode 100644 index 00000000..05b8a14f --- /dev/null +++ b/opentrack/new_config.ui @@ -0,0 +1,52 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>UI_new_config</class> + <widget class="QDialog" name="UI_new_config"> + <property name="windowModality"> + <enum>Qt::ApplicationModal</enum> + </property> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>437</width> + <height>110</height> + </rect> + </property> + <property name="windowTitle"> + <string>Config filename</string> + </property> + <property name="windowIcon"> + <iconset> + <normaloff>images/opentrack.png</normaloff>images/opentrack.png</iconset> + </property> + <layout class="QGridLayout" name="gridLayout"> + <item row="0" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>New file name:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QLineEdit" name="lineEdit"> + <property name="minimumSize"> + <size> + <width>271</width> + <height>0</height> + </size> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="standardButtons"> + <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> + </property> + </widget> + </item> + </layout> + </widget> + <resources/> + <connections/> +</ui> diff --git a/opentrack/new_file_dialog.cpp b/opentrack/new_file_dialog.cpp new file mode 100644 index 00000000..70816c5d --- /dev/null +++ b/opentrack/new_file_dialog.cpp @@ -0,0 +1,37 @@ +#include "new_file_dialog.h" + +new_file_dialog::new_file_dialog(QWidget* parent) : QDialog(parent) +{ + ui.setupUi(this); + connect(ui.buttonBox, SIGNAL(accepted()), this, SLOT(ok_clicked())); + connect(ui.buttonBox, SIGNAL(rejected()), this, SLOT(cancel_clicked())); + setFixedSize(size()); +} + +bool new_file_dialog::is_ok(QString& name_) +{ + name_ = name; + return ok; +} + +void new_file_dialog::cancel_clicked() { close(); } + +void new_file_dialog::ok_clicked() +{ + QString text = ui.lineEdit->text(); + text = text.replace('/', ""); + text = text.replace('\\', ""); + if (text != "" && !text.endsWith(".ini")) + text += ".ini"; + if (text == "" || text == ".ini" || QFile(options::globals::ini_directory() + "/" + text).exists()) + { + QMessageBox::warning(this, + tr("File exists"), + tr("This file already exists. Pick another name."), + QMessageBox::Ok, QMessageBox::NoButton); + return; + } + ok = true; + close(); + name = text; +} diff --git a/opentrack/new_file_dialog.h b/opentrack/new_file_dialog.h new file mode 100644 index 00000000..7244e524 --- /dev/null +++ b/opentrack/new_file_dialog.h @@ -0,0 +1,25 @@ +#pragma once + +#include "ui_new_config.h" +#include "options/options.hpp" +#include <QFile> +#include <QRegExp> +#include <QString> +#include <QMessageBox> + +class new_file_dialog : public QDialog +{ + Q_OBJECT +public: + new_file_dialog(QWidget* parent = nullptr); + bool is_ok(QString& name_); + +private: + Ui::UI_new_config ui; + bool ok = false; + QString name; + +private slots: + void cancel_clicked(); + void ok_clicked(); +}; diff --git a/opentrack/opentrack.ico b/opentrack/opentrack.ico Binary files differnew file mode 100644 index 00000000..5cac8da1 --- /dev/null +++ b/opentrack/opentrack.ico diff --git a/opentrack/resources.rc b/opentrack/resources.rc new file mode 100644 index 00000000..6fd0253b --- /dev/null +++ b/opentrack/resources.rc @@ -0,0 +1,2 @@ +#include <windows.h> +IDI_ICON1 ICON "opentrack.ico" |