////////////////////////////////////////////////////////////////////// // This file is part of Remere's Map Editor ////////////////////////////////////////////////////////////////////// // Remere's Map Editor is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Remere's Map Editor is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . ////////////////////////////////////////////////////////////////////// #include "main.h" #include "application.h" #include "sprites.h" #include "editor.h" #include "common_windows.h" #include "palette_window.h" #include "preferences.h" #include "result_window.h" #include "minimap_window.h" #include "about_window.h" #include "main_menubar.h" #include "updater.h" #include "artprovider.h" #include "dark_mode_manager.h" #include "materials.h" #include "map.h" #include "complexitem.h" #include "creature.h" // Add exception handling includes #include #include #include #include #include #include #if defined(__LINUX__) || defined(__WINDOWS__) #include #endif #include "../brushes/icon/editor_icon.xpm" #include "color_utils.h" BEGIN_EVENT_TABLE(MainFrame, wxFrame) EVT_CLOSE(MainFrame::OnExit) // Update check complete #ifdef _USE_UPDATER_ EVT_ON_UPDATE_CHECK_FINISHED(wxID_ANY, MainFrame::OnUpdateReceived) #endif EVT_ON_UPDATE_MENUS(wxID_ANY, MainFrame::OnUpdateMenus) // Idle event handler EVT_IDLE(MainFrame::OnIdle) END_EVENT_TABLE() BEGIN_EVENT_TABLE(MapWindow, wxPanel) EVT_SIZE(MapWindow::OnSize) EVT_COMMAND_SCROLL_TOP(MAP_WINDOW_HSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_BOTTOM(MAP_WINDOW_HSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_THUMBTRACK(MAP_WINDOW_HSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_LINEUP(MAP_WINDOW_HSCROLL, MapWindow::OnScrollLineUp) EVT_COMMAND_SCROLL_LINEDOWN(MAP_WINDOW_HSCROLL, MapWindow::OnScrollLineDown) EVT_COMMAND_SCROLL_PAGEUP(MAP_WINDOW_HSCROLL, MapWindow::OnScrollPageUp) EVT_COMMAND_SCROLL_PAGEDOWN(MAP_WINDOW_HSCROLL, MapWindow::OnScrollPageDown) EVT_COMMAND_SCROLL_TOP(MAP_WINDOW_VSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_BOTTOM(MAP_WINDOW_VSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_THUMBTRACK(MAP_WINDOW_VSCROLL, MapWindow::OnScroll) EVT_COMMAND_SCROLL_LINEUP(MAP_WINDOW_VSCROLL, MapWindow::OnScrollLineUp) EVT_COMMAND_SCROLL_LINEDOWN(MAP_WINDOW_VSCROLL, MapWindow::OnScrollLineDown) EVT_COMMAND_SCROLL_PAGEUP(MAP_WINDOW_VSCROLL, MapWindow::OnScrollPageUp) EVT_COMMAND_SCROLL_PAGEDOWN(MAP_WINDOW_VSCROLL, MapWindow::OnScrollPageDown) EVT_BUTTON(MAP_WINDOW_GEM, MapWindow::OnGem) END_EVENT_TABLE() BEGIN_EVENT_TABLE(MapScrollBar, wxScrollBar) EVT_KEY_DOWN(MapScrollBar::OnKey) EVT_KEY_UP(MapScrollBar::OnKey) EVT_CHAR(MapScrollBar::OnKey) EVT_SET_FOCUS(MapScrollBar::OnFocus) EVT_MOUSEWHEEL(MapScrollBar::OnWheel) END_EVENT_TABLE() wxIMPLEMENT_APP(Application); Application::~Application() { // Destroy } bool Application::OnInit() { #if defined __DEBUG_MODE__ && defined __WINDOWS__ _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF); #endif std::cout << "This is free software: you are free to change and redistribute it." << std::endl; std::cout << "There is NO WARRANTY, to the extent permitted by law." << std::endl; std::cout << "Review COPYING in RME distribution for details." << std::endl; mt_seed(time(nullptr)); srand(time(nullptr)); // Set up global exception handling #ifndef __DEBUG_MODE__ wxHandleFatalExceptions(true); #endif // Discover data directory g_gui.discoverDataDirectory("clients.xml"); // Tell that we are the real thing wxAppConsole::SetInstance(this); wxArtProvider::Push(newd ArtProvider()); #if defined(__LINUX__) || defined(__WINDOWS__) int argc = 1; char* argv[1] = { wxString(this->argv[0]).char_str() }; glutInit(&argc, argv); #endif // Load some internal stuff g_settings.load(); FixVersionDiscrapencies(); g_gui.LoadHotkeys(); ClientVersion::loadVersions(); // Initialize dark mode manager g_darkMode.Initialize(); #ifdef _USE_PROCESS_COM m_single_instance_checker = newd wxSingleInstanceChecker; // Instance checker has to stay alive throughout the applications lifetime // Parse command line arguments first to allow overriding single instance setting m_file_to_open = wxEmptyString; ParseCommandLineMap(m_file_to_open); if (g_settings.getInteger(Config::ONLY_ONE_INSTANCE) && m_single_instance_checker->IsAnotherRunning()) { RMEProcessClient client; wxConnectionBase* connection = client.MakeConnection("localhost", "rme_host", "rme_talk"); if (connection) { if (m_file_to_open != wxEmptyString) { wxLogNull nolog; // We might get a timeout message if the file fails to open on the running instance. Let's not show that message. connection->Execute(m_file_to_open); } connection->Disconnect(); wxDELETE(connection); } wxDELETE(m_single_instance_checker); return false; // Since we return false - OnExit is never called } // We act as server then m_proc_server = newd RMEProcessServer(); if (!m_proc_server->Create("rme_host")) { wxLogWarning("Could not register IPC service!"); } #endif // Image handlers // wxImage::AddHandler(newd wxBMPHandler); wxImage::AddHandler(newd wxPNGHandler); wxImage::AddHandler(newd wxJPEGHandler); wxImage::AddHandler(newd wxTGAHandler); g_gui.gfx.loadEditorSprites(); #ifndef __DEBUG_MODE__ // Enable fatal exception handler wxHandleFatalExceptions(true); #endif // Load all the dependency files std::string error; StringVector warnings; // Don't parse command line map again since we already did it above if (m_file_to_open == wxEmptyString) { ParseCommandLineMap(m_file_to_open); } g_gui.root = newd MainFrame(__W_RME_APPLICATION_NAME__, wxDefaultPosition, wxSize(700, 500)); SetTopWindow(g_gui.root); g_gui.SetTitle(""); g_gui.root->LoadRecentFiles(); // Load palette g_gui.LoadPerspective(); // Create icon and apply color shift wxBitmap iconBitmap(editor_icon); wxImage iconImage = iconBitmap.ConvertToImage(); ColorUtils::ShiftHue(iconImage, ColorUtils::GetRandomHueShift()); iconBitmap = wxBitmap(iconImage); // Convert to icon for the window and set both wxIcon icon; icon.CopyFromBitmap(iconBitmap); g_gui.root->SetIcon(icon); // Create a unique log directory for this session wxDateTime now = wxDateTime::Now(); wxString logDir = wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "logs" + wxFileName::GetPathSeparator() + now.Format("%Y%m%d_%H%M%S"); // Make sure it exists wxFileName dirPath(logDir); if (!dirPath.DirExists()) { dirPath.Mkdir(wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL); } // Let them know about this session std::ofstream sessionLog((logDir + wxFileName::GetPathSeparator() + "session.log").ToStdString()); if (sessionLog.is_open()) { sessionLog << "RME Session started at " << now.FormatISOCombined() << std::endl; sessionLog << "Version: " << __W_RME_VERSION__ << std::endl; sessionLog.close(); } // Show welcome dialog with color-shifted bitmap if (g_settings.getInteger(Config::WELCOME_DIALOG) == 1 && m_file_to_open == wxEmptyString) { g_gui.ShowWelcomeDialog(iconBitmap); } else { g_gui.root->Show(); } // Set idle event handling mode wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED); // Goto RME website? if (g_settings.getInteger(Config::GOTO_WEBSITE_ON_BOOT) == 1) { ::wxLaunchDefaultBrowser(__SITE_URL__, wxBROWSER_NEW_WINDOW); g_settings.setInteger(Config::GOTO_WEBSITE_ON_BOOT, 0); } // Check for updates #ifdef _USE_UPDATER_ if (g_settings.getInteger(Config::USE_UPDATER) == -1) { int ret = g_gui.PopupDialog( "Notice", "Do you want the editor to automatically check for updates?\n" "It will connect to the internet if you choose yes.\n" "You can change this setting in the preferences later.", wxYES | wxNO ); if (ret == wxID_YES) { g_settings.setInteger(Config::USE_UPDATER, 1); } else { g_settings.setInteger(Config::USE_UPDATER, 0); } } if (g_settings.getInteger(Config::USE_UPDATER) == 1) { // UpdateChecker updater; // updater.connect(g_gui.root); } #endif // Keep track of first event loop entry m_startup = true; return true; } void Application::OnEventLoopEnter(wxEventLoopBase* loop) { // First startup? if (!m_startup) { return; } m_startup = false; // Don't try to create a map if we didn't load the client map. if (ClientVersion::getLatestVersion() == nullptr) { return; } // Open a map. if (m_file_to_open != wxEmptyString) { g_gui.LoadMap(FileName(m_file_to_open)); } else if (!g_gui.IsWelcomeDialogShown() && g_gui.NewMap()) { // Open a new empty map // You generally don't want to save this map... g_gui.GetCurrentEditor()->map.clearChanges(); } // Check when the URLs were last opened time_t currentTime = time(nullptr); time_t lastOpenTime = static_cast(g_settings.getInteger(Config::LAST_WEBSITES_OPEN_TIME)); // Calculate difference in days const time_t secondsPerDay = 60 * 60 * 24; const int daysToWait = 3; // If last open time is 0 (never opened) or if 7+ days have passed if (lastOpenTime == 0 || difftime(currentTime, lastOpenTime) > daysToWait * secondsPerDay) { // Open Discord and Idler.live URLs in the default browser ::wxLaunchDefaultBrowser("https://discord.gg/FD2cYKBq5E", wxBROWSER_NEW_WINDOW); ::wxLaunchDefaultBrowser("https://paypal.me/PatrykZmyslony", wxBROWSER_NEW_WINDOW); // Update the last open time g_settings.setInteger(Config::LAST_WEBSITES_OPEN_TIME, static_cast(currentTime)); } } void Application::MacOpenFiles(const wxArrayString& fileNames) { if (!fileNames.IsEmpty()) { g_gui.LoadMap(FileName(fileNames.Item(0))); } } void Application::FixVersionDiscrapencies() { // Here the registry should be fixed, if the version has been changed if (g_settings.getInteger(Config::VERSION_ID) < MAKE_VERSION_ID(1, 0, 5)) { g_settings.setInteger(Config::USE_MEMCACHED_SPRITES_TO_SAVE, 0); } if (g_settings.getInteger(Config::VERSION_ID) < __RME_VERSION_ID__ && ClientVersion::getLatestVersion() != nullptr) { g_settings.setInteger(Config::DEFAULT_CLIENT_VERSION, ClientVersion::getLatestVersion()->getID()); } wxString ss = wxstr(g_settings.getString(Config::SCREENSHOT_DIRECTORY)); if (ss.empty()) { ss = wxStandardPaths::Get().GetDocumentsDir(); #ifdef __WINDOWS__ ss += "/My Pictures/RME/"; #endif } g_settings.setString(Config::SCREENSHOT_DIRECTORY, nstr(ss)); // Set registry to newest version g_settings.setInteger(Config::VERSION_ID, __RME_VERSION_ID__); } void Application::Unload() { g_gui.CloseAllEditors(); g_gui.UnloadVersion(); g_gui.SaveHotkeys(); g_gui.SavePerspective(); g_gui.root->SaveRecentFiles(); ClientVersion::saveVersions(); ClientVersion::unloadVersions(); g_settings.save(true); g_gui.root = nullptr; } int Application::OnExit() { #ifdef _USE_PROCESS_COM wxDELETE(m_proc_server); wxDELETE(m_single_instance_checker); #endif return 1; } void Application::OnFatalException() { // Log the fatal exception to a file wxDateTime now = wxDateTime::Now(); wxString logFileName = wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "fatal_error_" + now.Format("%Y%m%d_%H%M%S") + ".log"; // Create log directory if it doesn't exist wxFileName logDir(wxStandardPaths::Get().GetUserDataDir()); if (!logDir.DirExists()) { wxMkdir(logDir.GetPath()); } // Log details to a file std::ofstream logFile(logFileName.ToStdString(), std::ios::app); if (logFile.is_open()) { logFile << "Fatal exception occurred at " << now.FormatISOCombined() << std::endl; logFile << "RME version: " << __W_RME_VERSION__ << std::endl; logFile << "Please report this crash to the developers." << std::endl; logFile.close(); } // Create and show a simple crash dialog wxString msg = "A fatal error has occurred in " + wxString(__W_RME_APPLICATION_NAME__) + ".\n\n"; msg += "The application will now close. A log file has been created at:\n"; msg += logFileName + "\n\n"; msg += "Please report this error to the developers."; wxMessageBox(msg, "Fatal Error", wxICON_ERROR | wxOK); } bool Application::ParseCommandLineMap(wxString& fileName) { if (argc == 2) { // Check if it's a special command to force multiple instances if (wxString(argv[1]) == "-force-multi-instance") { g_settings.setInteger(Config::ONLY_ONE_INSTANCE, 0); return false; } fileName = wxString(argv[1]); return true; } else if (argc == 3) { if (argv[1] == "-ws") { g_settings.setInteger(Config::WELCOME_DIALOG, argv[2] == "1" ? 1 : 0); } else if (argv[1] == "-multi-instance") { // Allow forcing multiple instances via command line g_settings.setInteger(Config::ONLY_ONE_INSTANCE, argv[2] == "1" ? 0 : 1); } } return false; } MainFrame::MainFrame(const wxString& title, const wxPoint& pos, const wxSize& size) : wxFrame((wxFrame*)nullptr, -1, title, pos, size, wxDEFAULT_FRAME_STYLE) { // Receive idle events SetExtraStyle(wxWS_EX_PROCESS_IDLE); #if wxCHECK_VERSION(3, 1, 0) // 3.1.0 or higher // Make sure ShowFullScreen() uses the full screen API on macOS EnableFullScreenView(true); #endif // Creates the file-dropdown menu menu_bar = newd MainMenuBar(this); wxArrayString warnings; wxString error; wxFileName filename; filename.Assign(g_gui.getFoundDataDirectory() + "menubar.xml"); if (!filename.FileExists()) { filename = FileName(GUI::GetDataDirectory() + "menubar.xml"); } if (!menu_bar->Load(filename, warnings, error)) { wxLogError(wxString() + "Could not load menubar.xml, editor will NOT be able to show its menu.\n"); } wxStatusBar* statusbar = CreateStatusBar(); statusbar->SetFieldsCount(4); SetStatusText(wxString("Welcome to ") << __W_RME_APPLICATION_NAME__ << " " << __W_RME_VERSION__); // Le sizer g_gui.aui_manager = newd wxAuiManager(this); g_gui.tabbook = newd MapTabbook(this, wxID_ANY); tool_bar = newd MainToolBar(this, g_gui.aui_manager); g_gui.aui_manager->AddPane(g_gui.tabbook, wxAuiPaneInfo().CenterPane().Floatable(false).CloseButton(false).PaneBorder(false)); g_gui.aui_manager->Update(); UpdateMenubar(); // Apply dark mode if enabled if (g_darkMode.IsDarkModeEnabled()) { g_darkMode.ApplyTheme(this); g_darkMode.ApplyThemeToMainMenuBar(menu_bar); g_darkMode.ApplyThemeToMainToolBar(tool_bar); g_darkMode.ApplyThemeToStatusBar(GetStatusBar()); } } MainFrame::~MainFrame() = default; void MainFrame::OnIdle(wxIdleEvent& event) { g_gui.CheckAutoSave(); event.Skip(); } #ifdef _USE_UPDATER_ void MainFrame::OnUpdateReceived(wxCommandEvent& event) { std::string data = *(std::string*)event.GetClientData(); delete (std::string*)event.GetClientData(); size_t first_colon = data.find(':'); size_t second_colon = data.find(':', first_colon + 1); if (first_colon == std::string::npos || second_colon == std::string::npos) { return; } std::string update = data.substr(0, first_colon); std::string verstr = data.substr(first_colon + 1, second_colon - first_colon - 1); std::string url = (second_colon == data.size() ? "" : data.substr(second_colon + 1)); if (update == "yes") { int ret = g_gui.PopupDialog( "Update Notice", wxString("There is a newd update available (") << wxstr(verstr) << "). Do you want to go to the website and download it?", wxYES | wxNO, "I don't want any update notices", Config::AUTOCHECK_FOR_UPDATES ); if (ret == wxID_YES) { ::wxLaunchDefaultBrowser(wxstr(url), wxBROWSER_NEW_WINDOW); } } } #endif void MainFrame::OnUpdateMenus(wxCommandEvent&) { UpdateMenubar(); g_gui.UpdateMinimap(true); g_gui.UpdateTitle(); } #ifdef __WINDOWS__ bool MainFrame::MSWTranslateMessage(WXMSG* msg) { if (g_gui.AreHotkeysEnabled()) { if (wxFrame::MSWTranslateMessage(msg)) { return true; } } else { if (wxWindow::MSWTranslateMessage(msg)) { return true; } } return false; } #endif void MainFrame::UpdateMenubar() { menu_bar->Update(); tool_bar->UpdateButtons(); } bool MainFrame::DoQueryClose() { Editor* editor = g_gui.GetCurrentEditor(); if (editor) { if (editor->IsLive()) { long ret = g_gui.PopupDialog( "Must Close Server", wxString("You are currently connected to a live server, to close this map the connection must be severed."), wxOK | wxCANCEL ); if (ret == wxID_OK) { editor->CloseLiveServer(); } else { return false; } } } return true; } bool MainFrame::DoQuerySaveTileset(bool doclose) { if (!g_materials.needSave()) { // skip dialog when there is nothing to save return true; } long ret = g_gui.PopupDialog( "Export tileset", "Do you want to export your tileset changes before exiting?", wxYES | wxNO | wxCANCEL ); if (ret == wxID_NO) { // "no" - exit without saving return true; } else if (ret == wxID_CANCEL) { // "cancel" - just close the dialog return false; } // "yes" button was pressed, open tileset exporting dialog if (g_gui.GetCurrentEditor()) { ExportTilesetsWindow dlg(this, *g_gui.GetCurrentEditor()); dlg.ShowModal(); dlg.Destroy(); } return !g_materials.needSave(); } bool MainFrame::DoQuerySave(bool doclose) { if (!g_gui.IsEditorOpen()) { return true; } Editor* editor = g_gui.GetCurrentEditor(); if (g_gui.HasDetachedViews(editor)) { wxString message = "This map has one or more detached views open.\n"; message += "You must close all detached views before closing the map."; int choice = wxMessageBox( message, "Detached Views Open", wxOK | wxCANCEL | wxICON_EXCLAMATION ); if (choice == wxOK) { // User chose to close detached views g_gui.CloseDetachedViews(editor); } else { // User canceled operation return false; } } if (!DoQuerySaveTileset()) { return false; } if (editor->IsLiveClient()) { long ret = g_gui.PopupDialog( "Disconnect", "Do you want to disconnect?", wxYES | wxNO ); if (ret != wxID_YES) { return false; } editor->CloseLiveServer(); return DoQuerySave(doclose); } else if (editor->IsLiveServer()) { long ret = g_gui.PopupDialog( "Shutdown", "Do you want to shut down the server? (any clients will be disconnected)", wxYES | wxNO ); if (ret != wxID_YES) { return false; } editor->CloseLiveServer(); return DoQuerySave(doclose); } else if (g_gui.ShouldSave()) { long ret = g_gui.PopupDialog( "Save changes", "Do you want to save your changes to \"" + wxstr(g_gui.GetCurrentMap().getName()) + "\"?", wxYES | wxNO | wxCANCEL ); if (ret == wxID_YES) { if (g_gui.GetCurrentMap().hasFile()) { g_gui.SaveCurrentMap(true); } else { wxFileDialog file(this, "Save...", "", "", "*.otbm", wxFD_SAVE | wxFD_OVERWRITE_PROMPT); int32_t result = file.ShowModal(); if (result == wxID_OK) { g_gui.SaveCurrentMap(file.GetPath(), true); } else { return false; } } } else if (ret == wxID_CANCEL) { return false; } } if (doclose) { UnnamedRenderingLock(); g_gui.CloseCurrentEditor(); } return true; } bool MainFrame::DoQueryImportCreatures() { if (g_creatures.hasMissing()) { long ret = g_gui.PopupDialog("Missing creatures", "There are missing creatures and/or NPC in the editor, do you want to load them from an OT monster/npc file?", wxYES | wxNO); if (ret == wxID_YES) { do { wxFileDialog dlg(g_gui.root, "Import monster/npc file", "", "", "*.xml", wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST); if (dlg.ShowModal() == wxID_OK) { wxArrayString paths; dlg.GetPaths(paths); for (uint32_t i = 0; i < paths.GetCount(); ++i) { wxString error; wxArrayString warnings; bool ok = g_creatures.importXMLFromOT(FileName(paths[i]), error, warnings); if (ok) { g_gui.ListDialog("Monster loader errors", warnings); } else { wxMessageBox("Error OT data file \"" + paths[i] + "\".\n" + error, "Error", wxOK | wxICON_INFORMATION, g_gui.root); } } } else { break; } } while (g_creatures.hasMissing()); } } g_gui.RefreshPalettes(); return true; } void MainFrame::UpdateFloorMenu() { menu_bar->UpdateFloorMenu(); } bool MainFrame::LoadMap(FileName name) { return g_gui.LoadMap(name); } void MainFrame::OnExit(wxCloseEvent& event) { // clicking 'x' button // do you want to save map changes? while (g_gui.IsEditorOpen()) { if (!DoQuerySave()) { if (event.CanVeto()) { event.Veto(); return; } else { break; } } } g_gui.aui_manager->UnInit(); ((Application&)wxGetApp()).Unload(); #ifdef __RELEASE__ // Hack, "crash" gracefully in release builds, let OS handle cleanup of windows exit(0); #endif Destroy(); } void MainFrame::AddRecentFile(const FileName& file) { menu_bar->AddRecentFile(file); } void MainFrame::LoadRecentFiles() { menu_bar->LoadRecentFiles(); } void MainFrame::SaveRecentFiles() { menu_bar->SaveRecentFiles(); } std::vector MainFrame::GetRecentFiles() { return menu_bar->GetRecentFiles(); } void MainFrame::PrepareDC(wxDC& dc) { dc.SetLogicalOrigin(0, 0); dc.SetAxisOrientation(1, 0); dc.SetUserScale(1.0, 1.0); dc.SetMapMode(wxMM_TEXT); } // Add this method to handle exceptions in the main event loop bool Application::OnExceptionInMainLoop() { try { throw; // Rethrow the exception to catch it } catch (const std::exception& e) { // Log the exception wxDateTime now = wxDateTime::Now(); wxString logFileName = wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "exception_" + now.Format("%Y%m%d_%H%M%S") + ".log"; // Create log directory wxFileName logDir(wxStandardPaths::Get().GetUserDataDir()); if (!logDir.DirExists()) { wxMkdir(logDir.GetPath()); } // Log details std::ofstream logFile(logFileName.ToStdString(), std::ios::app); if (logFile.is_open()) { logFile << "Exception in main loop at " << now.FormatISOCombined() << std::endl; logFile << "Exception: " << e.what() << std::endl; logFile << "RME version: " << __W_RME_VERSION__ << std::endl; logFile.close(); } // Only show dialog during drawing operations for serious errors if (g_gui.IsEditorOpen()) { // Silently continue for editor operations to prevent UI freezes return true; // Continue execution } else { wxString msg = "An error occurred in " + wxString(__W_RME_APPLICATION_NAME__) + ".\n\n"; msg += "Error details: " + wxString(e.what()) + "\n\n"; msg += "The application will try to continue. If problems persist, please restart."; wxMessageBox(msg, "Error", wxICON_WARNING | wxOK); return true; // Continue execution } } catch (...) { // Unknown exception - more serious wxDateTime now = wxDateTime::Now(); wxString logFileName = wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "unknown_exception_" + now.Format("%Y%m%d_%H%M%S") + ".log"; // Create log directory wxFileName logDir(wxStandardPaths::Get().GetUserDataDir()); if (!logDir.DirExists()) { wxMkdir(logDir.GetPath()); } // Log whatever we can std::ofstream logFile(logFileName.ToStdString(), std::ios::app); if (logFile.is_open()) { logFile << "Unknown exception in main loop at " << now.FormatISOCombined() << std::endl; logFile << "RME version: " << __W_RME_VERSION__ << std::endl; logFile.close(); } // Only show dialog during drawing operations for serious errors if (g_gui.IsEditorOpen()) { // Silently continue for editor operations return true; // Continue execution } else { wxString msg = "An unknown error occurred in " + wxString(__W_RME_APPLICATION_NAME__) + ".\n\n"; msg += "The application will try to continue. If problems persist, please restart."; wxMessageBox(msg, "Error", wxICON_WARNING | wxOK); return true; // Continue execution } } return false; // If all else fails, let the default handler take over }