MetaImGUI 1.0.0
ImGui Application Template for C++20
Loading...
Searching...
No Matches
Application.cpp
Go to the documentation of this file.
1/*
2 MetaImGUI
3 Copyright (C) 2026 A P Nicholson
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
17*/
18
19#include "Application.h"
20
21#include "ConfigManager.h"
22#include "DialogManager.h"
23#include "ISSTracker.h"
24#include "Localization.h"
25#include "Logger.h"
26#include "UIRenderer.h"
27#include "UpdateChecker.h"
28#include "WindowManager.h"
29#include "version.h"
30
31#include <GLFW/glfw3.h>
32#include <imgui.h>
33
34#include <cstdlib> // for std::getenv
35
36#ifdef __APPLE__
37#include <mach-o/dyld.h> // for _NSGetExecutablePath
38#endif
39
40namespace MetaImGUI {
41
42Application::Application() : m_statusMessage("Ready") {}
43
47
49 if (m_initialized) {
50 return true;
51 }
52
53 // Initialize logger first - use appropriate location per platform
54#ifdef __APPLE__
55 // macOS: Use ~/Library/Logs/MetaImGUI/ directory
56 // NOLINTNEXTLINE(concurrency-mt-unsafe) - Safe: called during single-threaded initialization
57 const char* home = std::getenv("HOME");
58 const std::string logPath =
59 (home != nullptr) ? std::string(home) + "/Library/Logs/MetaImGUI/metaimgui.log" : "metaimgui.log";
60#elif defined(_WIN32)
61 // Windows: Use LOCALAPPDATA directory
62 // NOLINTNEXTLINE(concurrency-mt-unsafe) - Safe: called during single-threaded initialization
63 const char* localAppData = std::getenv("LOCALAPPDATA");
64 const std::string logPath = (localAppData != nullptr)
65 ? std::string(localAppData) + "\\MetaImGUI\\logs\\metaimgui.log"
66 : "logs\\metaimgui.log";
67#else
68 // Linux: Use ~/.local/share/MetaImGUI/logs/ directory
69 // NOLINTNEXTLINE(concurrency-mt-unsafe) - Safe: called during single-threaded initialization
70 const char* home = std::getenv("HOME");
71 const std::string logPath =
72 (home != nullptr) ? std::string(home) + "/.local/share/MetaImGUI/logs/metaimgui.log" : "logs/metaimgui.log";
73#endif
75 LOG_INFO("Initializing MetaImGUI v{}", Version::VERSION);
76
77 // Load configuration
78 m_configManager = std::make_unique<ConfigManager>();
79 if (m_configManager->Load()) {
80 LOG_INFO("Configuration loaded successfully");
81 } else {
82 LOG_INFO("Using default configuration");
83 }
84
85 // Load translations and set language from config
86 // CRITICAL: translations.json MUST be present and valid
87 // Try multiple locations for translations file (for different package formats)
88 std::vector<std::string> translationPaths = {
89 "resources/translations/translations.json", // Development/local build
90 };
91
92 // Check if running from AppImage (METAIMGUI_APPDIR set by custom AppRun)
93 // NOLINTNEXTLINE(concurrency-mt-unsafe) - Safe: called during single-threaded initialization
94 const char* appdir = std::getenv("METAIMGUI_APPDIR");
95 if (appdir != nullptr) {
96 const std::string appdir_path =
97 std::string(appdir) + "/usr/share/MetaImGUI/resources/translations/translations.json";
98 translationPaths.insert(translationPaths.begin(), appdir_path); // Try AppImage location first
99 }
100
101#ifdef __APPLE__
102 // macOS bundle resources path - use executable path to find bundle location
103 // When launched from Finder, CWD is not reliable, so we need absolute path
104 char executablePath[1024];
105 uint32_t size = sizeof(executablePath);
106 if (_NSGetExecutablePath(executablePath, &size) == 0) {
107 // executablePath is like: /Applications/MetaImGUI.app/Contents/MacOS/MetaImGUI
108 std::string exePath(executablePath);
109 // Go up to Contents directory and into Resources
110 size_t macosPos = exePath.rfind("/MacOS/");
111 if (macosPos != std::string::npos) {
112 std::string bundleResourcePath =
113 exePath.substr(0, macosPos) + "/Resources/resources/translations/translations.json";
114 translationPaths.insert(translationPaths.begin(), bundleResourcePath);
115 LOG_DEBUG("macOS bundle resource path: {}", bundleResourcePath);
116 }
117 }
118 // Fallback: relative path (for terminal launch from MacOS directory)
119 translationPaths.emplace_back("../Resources/resources/translations/translations.json");
120 translationPaths.emplace_back("MetaImGUI.app/Contents/Resources/resources/translations/translations.json");
121#endif
122
123 // Add system installation paths
124 translationPaths.emplace_back(
125 "../share/MetaImGUI/resources/translations/translations.json"); // Installed (relative to bin)
126 translationPaths.emplace_back(
127 "/usr/share/MetaImGUI/resources/translations/translations.json"); // System-wide install
128 translationPaths.emplace_back(
129 "/usr/local/share/MetaImGUI/resources/translations/translations.json"); // Local install
130
131 bool translationsLoaded = false;
132 for (const auto& path : translationPaths) {
134 translationsLoaded = true;
135 break;
136 }
137 }
138
139 if (!translationsLoaded) {
140 LOG_ERROR("========================================");
141 LOG_ERROR("CRITICAL: Failed to load translations!");
142 LOG_ERROR("UI will show translation keys instead of actual text");
143 LOG_ERROR("Tried the following locations:");
144 for (const auto& path : translationPaths) {
145 LOG_ERROR(" - {}", path);
146 }
147 LOG_ERROR("This is a PACKAGING ERROR - file is missing from bundle");
148 LOG_ERROR("========================================");
149 }
150
151 const std::string language = m_configManager->GetString("language").value_or("en");
153
154 // Create and initialize window manager
155 auto windowSize = m_configManager->GetWindowSize();
156 const int width = windowSize ? windowSize->first : DEFAULT_WIDTH;
157 const int height = windowSize ? windowSize->second : DEFAULT_HEIGHT;
158
159 m_windowManager = std::make_unique<WindowManager>(WINDOW_TITLE, width, height);
160 if (!m_windowManager->Initialize()) {
161 LOG_ERROR("Failed to initialize window manager");
162 return false;
163 }
164 LOG_INFO("Window manager initialized");
165
166 // Set up window callbacks
167 m_windowManager->SetFramebufferSizeCallback(
168 [this](int width, int height) { this->OnFramebufferSizeChanged(width, height); });
169 m_windowManager->SetKeyCallback(
170 [this](int key, int scancode, int action, int mods) { this->OnKeyPressed(key, scancode, action, mods); });
171 m_windowManager->SetWindowCloseCallback([this]() { this->OnWindowCloseRequested(); });
172 m_windowManager->SetContextLossCallback([this]() { return this->OnContextLoss(); });
173
174 // Create and initialize UI renderer
175 m_uiRenderer = std::make_unique<UIRenderer>();
176 if (!m_uiRenderer->Initialize(m_windowManager->GetNativeWindow())) {
177 LOG_ERROR("Failed to initialize UI renderer");
178 return false;
179 }
180 LOG_INFO("UI renderer initialized");
181
182 // Initialize dialog manager
183 m_dialogManager = std::make_unique<DialogManager>();
184 LOG_INFO("Dialog manager initialized");
185
186 // Initialize update checker
187 m_updateChecker = std::make_unique<UpdateChecker>("andynicholson", "MetaImGUI");
188 LOG_INFO("Update checker initialized");
189
190 // Initialize ISS tracker
191 m_issTracker = std::make_unique<ISSTracker>();
192 LOG_INFO("ISS tracker initialized");
193
194 // Check for updates asynchronously
195 CheckForUpdates();
196
197 m_initialized = true;
198 LOG_INFO("Application initialized successfully");
199 return true;
200}
201
203 while (!ShouldClose()) {
204 ProcessInput();
205 Render();
206 }
207}
208
210 if (!m_initialized) {
211 return;
212 }
213
214 LOG_INFO("Shutting down application...");
215
216 // Save configuration before shutdown
217 if (m_configManager && m_windowManager) {
218 // Save window size
219 int width = 0;
220 int height = 0;
221 m_windowManager->GetWindowSize(width, height);
222 m_configManager->SetWindowSize(width, height);
223 LOG_INFO("Saving window size: {}x{}", width, height);
224
225 // Save current language
226 m_configManager->SetString("language", Localization::Instance().GetCurrentLanguage());
227
228 if (m_configManager->Save()) {
229 LOG_INFO("Configuration saved successfully");
230 }
231 }
232
233 // Shutdown subsystems in reverse order of initialization
234 if (m_issTracker) {
235 m_issTracker->StopTracking();
236 }
237 m_issTracker.reset();
238 m_updateChecker.reset();
239 m_dialogManager.reset();
240 m_uiRenderer.reset();
241 m_windowManager.reset();
242 m_configManager.reset();
243
244 m_initialized = false;
245 LOG_INFO("Application shut down successfully");
246
247 // Shutdown logger last
249}
250
252 return m_windowManager && m_windowManager->ShouldClose();
253}
254
255void Application::ProcessInput() {
256 if (m_windowManager) {
257 m_windowManager->PollEvents();
258 }
259}
260
261void Application::Render() {
262 if (!m_windowManager || !m_uiRenderer) {
263 return;
264 }
265
266 // Get frame time for FPS calculation
267 const ImGuiIO& io = ImGui::GetIO();
268 m_lastFrameTime = io.Framerate;
269
270 // Prepare window for rendering
271 m_windowManager->BeginFrame();
272
273 // Start ImGui frame
274 m_uiRenderer->BeginFrame();
275
276 // Create full-screen main window
277 ImGuiViewport* viewport = ImGui::GetMainViewport();
278 ImGui::SetNextWindowPos(viewport->Pos);
279 ImGui::SetNextWindowSize(viewport->Size);
280
281 const ImGuiWindowFlags window_flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
282 ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings |
283 ImGuiWindowFlags_MenuBar | ImGuiWindowFlags_NoBringToFrontOnFocus;
284
285 ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 0.0f);
286 ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
287 ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
288
289 if (ImGui::Begin("MetaImGUI Main", nullptr, window_flags)) {
290 // Render menu bar
291 m_uiRenderer->RenderMenuBar([this]() { this->OnExitRequested(); }, [this]() { this->OnToggleDemoWindow(); },
292 [this]() { this->OnCheckUpdatesRequested(); },
293 [this]() { this->OnShowAboutRequested(); }, m_showDemoWindow,
294 [this]() { this->OnToggleISSTracker(); }, m_showISSTracker);
295
296 // Render main window content
297 m_uiRenderer->RenderMainWindow([this]() { this->OnShowAboutRequested(); },
298 [this]() { m_showDemoWindow = true; },
299 [this]() { this->OnShowInputDialogRequested(); });
300
301 // Render status bar
302 m_uiRenderer->RenderStatusBar(m_statusMessage, m_lastFrameTime, Version::VERSION, m_updateCheckInProgress);
303 }
304 ImGui::End();
305
306 ImGui::PopStyleVar(3);
307
308 // Render additional windows
309 if (m_showAboutWindow) {
310 m_uiRenderer->RenderAboutWindow(m_showAboutWindow);
311 }
312
313 if (m_showDemoWindow) {
314 m_uiRenderer->ShowDemoWindow(m_showDemoWindow);
315 }
316
317 if (m_showUpdateNotification) {
318 m_uiRenderer->RenderUpdateNotification(m_showUpdateNotification, m_latestUpdateInfo.get());
319 }
320
321 if (m_showISSTracker) {
322 m_uiRenderer->RenderISSTrackerWindow(m_showISSTracker, m_issTracker.get());
323 }
324
325 // Render exit confirmation dialog
326 if (m_showExitDialog) {
327 auto& loc = Localization::Instance();
328 const std::string title = loc.Tr("exit.title");
329 const std::string message = loc.Tr("exit.message");
330
331 m_dialogManager->ShowConfirmation(title, message, [this](bool confirmed) {
332 if (confirmed && m_windowManager) {
333 m_windowManager->RequestClose();
334 } else if (m_windowManager) {
335 // User cancelled - make sure close flag is cleared
336 m_windowManager->CancelClose();
337 }
338 m_showExitDialog = false;
339 });
340 }
341
342 // Render dialogs
343 if (m_dialogManager) {
344 m_dialogManager->Render();
345 }
346
347 // End ImGui frame and render
348 m_uiRenderer->EndFrame();
349
350 // Present the frame
351 m_windowManager->EndFrame();
352}
353
354// Event Handlers
355
356void Application::OnWindowCloseRequested() {
357 // Intercept window close button - cancel the close and show dialog
358 if (m_windowManager) {
359 m_windowManager->CancelClose();
360 }
361 m_showExitDialog = true;
362}
363
364void Application::OnExitRequested() {
365 // Show exit confirmation dialog instead of closing immediately
366 m_showExitDialog = true;
367}
368
369void Application::OnToggleDemoWindow() {
370 m_showDemoWindow = !m_showDemoWindow;
371}
372
373void Application::OnCheckUpdatesRequested() {
374 CheckForUpdates();
375}
376
377void Application::OnShowAboutRequested() {
378 m_showAboutWindow = true;
379}
380
381void Application::OnShowInputDialogRequested() {
382 if (m_dialogManager) {
383 auto& loc = Localization::Instance();
384 m_dialogManager->ShowInputDialog(loc.Tr("input_dialog.title"), loc.Tr("input_dialog.prompt"), "",
385 [this](const std::string& result) {
386 auto& loc = Localization::Instance();
387 if (!result.empty()) {
388 m_statusMessage = loc.Tr("status.input_received") + " " + result;
389 LOG_INFO("User input: {}", result);
390 } else {
391 m_statusMessage = loc.Tr("status.input_cancelled");
392 }
393 });
394 }
395}
396
397void Application::OnToggleISSTracker() {
398 m_showISSTracker = !m_showISSTracker;
399}
400
401// Input Callbacks
402
403void Application::OnFramebufferSizeChanged(int width, int height) {
404 // Handle framebuffer size changes if needed
405 // Currently handled automatically by WindowManager
406 (void)width;
407 (void)height;
408}
409
410void Application::OnKeyPressed(int key, int scancode, int action, int mods) {
411 (void)scancode; // Unused parameter
412
413 if (action == GLFW_PRESS) {
414 switch (key) {
415 case GLFW_KEY_ESCAPE:
416 OnExitRequested();
417 break;
418 case GLFW_KEY_A:
419 if ((mods & GLFW_MOD_CONTROL) != 0) {
420 OnShowAboutRequested();
421 }
422 break;
423 case GLFW_KEY_F9:
424 // DEBUG: Simulate context loss for testing
425 if ((mods & GLFW_MOD_SHIFT) != 0) {
426 LOG_WARNING("DEBUG: User triggered context loss simulation via Shift+F9");
427 if (OnContextLoss()) {
428 m_statusMessage = "DEBUG: Context recovery successful";
429 } else {
430 m_statusMessage = "DEBUG: Context recovery failed";
431 }
432 }
433 break;
434 default:
435 // Ignore other keys
436 break;
437 }
438 }
439}
440
441// Update Checking
442
443void Application::CheckForUpdates() {
444 if (!m_updateChecker || m_updateCheckInProgress) {
445 return;
446 }
447
448 m_updateCheckInProgress = true;
449 m_statusMessage = "Checking for updates...";
450
451 // Check asynchronously
452 m_updateChecker->CheckForUpdatesAsync([this](const UpdateInfo& info) { this->OnUpdateCheckComplete(info); });
453}
454
455void Application::OnUpdateCheckComplete(const UpdateInfo& updateInfo) {
456 m_updateCheckInProgress = false;
457
458 // Always store the update info and show the dialog for better UX
459 m_latestUpdateInfo = std::make_unique<UpdateInfo>(updateInfo);
460 m_showUpdateNotification = true;
461
462 if (updateInfo.updateAvailable) {
463 m_statusMessage = "Update available: v" + updateInfo.latestVersion;
464 LOG_INFO("Update available: v{} (current: v{})", updateInfo.latestVersion, updateInfo.currentVersion);
465 } else {
466 m_statusMessage = "Ready";
467 LOG_INFO("No updates available (current version: v{})", updateInfo.currentVersion);
468 }
469}
470
471bool Application::OnContextLoss() {
472 LOG_WARNING("Application handling context loss - attempting to recreate UI renderer");
473
474 // Shutdown UI renderer (this destroys ImGui/ImPlot contexts)
475 if (m_uiRenderer) {
476 m_uiRenderer->Shutdown();
477 }
478
479 // Recreate UI renderer with new contexts
480 if (!m_uiRenderer->Initialize(m_windowManager->GetNativeWindow())) {
481 LOG_ERROR("Failed to reinitialize UI renderer after context loss");
482 m_statusMessage = "ERROR: Failed to recover from context loss";
483 return false;
484 }
485
486 LOG_INFO("UI renderer successfully reinitialized after context loss");
487 m_statusMessage = "Recovered from display context loss";
488 return true;
489}
490
491} // namespace MetaImGUI
#define LOG_INFO(...)
Definition Logger.h:218
#define LOG_DEBUG(...)
Definition Logger.h:217
#define LOG_ERROR(...)
Definition Logger.h:220
#define LOG_WARNING(...)
Definition Logger.h:219
void Shutdown()
Shutdown the application and cleanup resources.
bool ShouldClose() const
Check if the application should close.
bool Initialize()
Initialize the application and all subsystems.
void Run()
Run the main application loop.
static Localization & Instance()
Get singleton instance.
void SetLanguage(const std::string &languageCode)
Set current language.
bool LoadTranslations(const std::string &filepath)
Load translations from JSON file.
void Shutdown()
Shutdown logger and flush buffers.
Definition Logger.cpp:72
void Initialize(const std::filesystem::path &logFilePath="", LogLevel minLevel=LogLevel::Info)
Initialize logger with optional log file.
Definition Logger.cpp:39
static Logger & Instance()
Get singleton instance.
Definition Logger.cpp:34
@ Info
Informational messages.