application.cpp

main 797 lines · 23.1 KB Raw
Alex Alex Commit Map Editor 18/10/2026 23:40
1//////////////////////////////////////////////////////////////////////
2// This file is part of Remere's Map Editor
3//////////////////////////////////////////////////////////////////////
4// Remere's Map Editor is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// Remere's Map Editor is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16//////////////////////////////////////////////////////////////////////
17
18#include "main.h"
19
20#include "application.h"
21#include "sprites.h"
22#include "editor.h"
23#include "common_windows.h"
24#include "palette_window.h"
25#include "preferences.h"
26#include "result_window.h"
27#include "minimap_window.h"
28#include "about_window.h"
29#include "main_menubar.h"
30#include "updater.h"
31#include "artprovider.h"
32#include "dark_mode_manager.h"
33
34#include "materials.h"
35#include "map.h"
36#include "complexitem.h"
37#include "creature.h"
38
39// Add exception handling includes
40#include <exception>
41#include <fstream>
42#include <wx/datetime.h>
43#include <wx/filename.h>
44#include <wx/stdpaths.h>
45
46#include <wx/snglinst.h>
47
48#if defined(__LINUX__) || defined(__WINDOWS__)
49 #include <GL/glut.h>
50#endif
51
52#include "color_utils.h"
53
54BEGIN_EVENT_TABLE(MainFrame, wxFrame)
55EVT_CLOSE(MainFrame::OnExit)
56
57// Update check complete
58#ifdef _USE_UPDATER_
59EVT_ON_UPDATE_CHECK_FINISHED(wxID_ANY, MainFrame::OnUpdateReceived)
60#endif
61EVT_ON_UPDATE_MENUS(wxID_ANY, MainFrame::OnUpdateMenus)
62
63// Idle event handler
64EVT_IDLE(MainFrame::OnIdle)
65END_EVENT_TABLE()
66
67BEGIN_EVENT_TABLE(MapWindow, wxPanel)
68EVT_SIZE(MapWindow::OnSize)
69
70EVT_COMMAND_SCROLL_TOP(MAP_WINDOW_HSCROLL, MapWindow::OnScroll)
71EVT_COMMAND_SCROLL_BOTTOM(MAP_WINDOW_HSCROLL, MapWindow::OnScroll)
72EVT_COMMAND_SCROLL_THUMBTRACK(MAP_WINDOW_HSCROLL, MapWindow::OnScroll)
73EVT_COMMAND_SCROLL_LINEUP(MAP_WINDOW_HSCROLL, MapWindow::OnScrollLineUp)
74EVT_COMMAND_SCROLL_LINEDOWN(MAP_WINDOW_HSCROLL, MapWindow::OnScrollLineDown)
75EVT_COMMAND_SCROLL_PAGEUP(MAP_WINDOW_HSCROLL, MapWindow::OnScrollPageUp)
76EVT_COMMAND_SCROLL_PAGEDOWN(MAP_WINDOW_HSCROLL, MapWindow::OnScrollPageDown)
77
78EVT_COMMAND_SCROLL_TOP(MAP_WINDOW_VSCROLL, MapWindow::OnScroll)
79EVT_COMMAND_SCROLL_BOTTOM(MAP_WINDOW_VSCROLL, MapWindow::OnScroll)
80EVT_COMMAND_SCROLL_THUMBTRACK(MAP_WINDOW_VSCROLL, MapWindow::OnScroll)
81EVT_COMMAND_SCROLL_LINEUP(MAP_WINDOW_VSCROLL, MapWindow::OnScrollLineUp)
82EVT_COMMAND_SCROLL_LINEDOWN(MAP_WINDOW_VSCROLL, MapWindow::OnScrollLineDown)
83EVT_COMMAND_SCROLL_PAGEUP(MAP_WINDOW_VSCROLL, MapWindow::OnScrollPageUp)
84EVT_COMMAND_SCROLL_PAGEDOWN(MAP_WINDOW_VSCROLL, MapWindow::OnScrollPageDown)
85
86EVT_BUTTON(MAP_WINDOW_GEM, MapWindow::OnGem)
87END_EVENT_TABLE()
88
89BEGIN_EVENT_TABLE(MapScrollBar, wxScrollBar)
90EVT_KEY_DOWN(MapScrollBar::OnKey)
91EVT_KEY_UP(MapScrollBar::OnKey)
92EVT_CHAR(MapScrollBar::OnKey)
93EVT_SET_FOCUS(MapScrollBar::OnFocus)
94EVT_MOUSEWHEEL(MapScrollBar::OnWheel)
95END_EVENT_TABLE()
96
97wxIMPLEMENT_APP(Application);
98
99Application::~Application() {
100 // Destroy
101}
102
103bool Application::OnInit() {
104#if defined __DEBUG_MODE__ && defined __WINDOWS__
105 _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
106#endif
107
108 std::cout << "This is free software: you are free to change and redistribute it." << std::endl;
109 std::cout << "There is NO WARRANTY, to the extent permitted by law." << std::endl;
110 std::cout << "Review COPYING in RME distribution for details." << std::endl;
111 mt_seed(time(nullptr));
112 srand(time(nullptr));
113
114 // Set up global exception handling
115#ifndef __DEBUG_MODE__
116 wxHandleFatalExceptions(true);
117#endif
118
119 // Discover data directory
120 g_gui.discoverDataDirectory("clients.xml");
121
122 // Tell that we are the real thing
123 wxAppConsole::SetInstance(this);
124 wxArtProvider::Push(newd ArtProvider());
125
126#if defined(__LINUX__) || defined(__WINDOWS__)
127 int argc = 1;
128 char* argv[1] = { wxString(this->argv[0]).char_str() };
129 glutInit(&argc, argv);
130#endif
131
132 // Load some internal stuff
133 g_settings.load();
134 FixVersionDiscrapencies();
135 g_gui.LoadHotkeys();
136 ClientVersion::loadVersions();
137
138 // Initialize dark mode manager
139 g_darkMode.Initialize();
140
141#ifdef _USE_PROCESS_COM
142 m_single_instance_checker = newd wxSingleInstanceChecker; // Instance checker has to stay alive throughout the applications lifetime
143
144 // Parse command line arguments first to allow overriding single instance setting
145 m_file_to_open = wxEmptyString;
146 ParseCommandLineMap(m_file_to_open);
147
148 if (g_settings.getInteger(Config::ONLY_ONE_INSTANCE) && m_single_instance_checker->IsAnotherRunning()) {
149 RMEProcessClient client;
150 wxConnectionBase* connection = client.MakeConnection("localhost", "rme_host", "rme_talk");
151 if (connection) {
152 if (m_file_to_open != wxEmptyString) {
153 wxLogNull nolog; // We might get a timeout message if the file fails to open on the running instance. Let's not show that message.
154 connection->Execute(m_file_to_open);
155 }
156 connection->Disconnect();
157 wxDELETE(connection);
158 }
159 wxDELETE(m_single_instance_checker);
160 return false; // Since we return false - OnExit is never called
161 }
162 // We act as server then
163 m_proc_server = newd RMEProcessServer();
164 if (!m_proc_server->Create("rme_host")) {
165 wxLogWarning("Could not register IPC service!");
166 }
167#endif
168
169 // Image handlers
170 // wxImage::AddHandler(newd wxBMPHandler);
171 wxImage::AddHandler(newd wxPNGHandler);
172 wxImage::AddHandler(newd wxJPEGHandler);
173 wxImage::AddHandler(newd wxTGAHandler);
174
175 g_gui.gfx.loadEditorSprites();
176
177#ifndef __DEBUG_MODE__
178 // Enable fatal exception handler
179 wxHandleFatalExceptions(true);
180#endif
181 // Load all the dependency files
182 std::string error;
183 StringVector warnings;
184
185 // Don't parse command line map again since we already did it above
186 if (m_file_to_open == wxEmptyString) {
187 ParseCommandLineMap(m_file_to_open);
188 }
189
190 g_gui.root = newd MainFrame(__W_RME_APPLICATION_NAME__, wxDefaultPosition, wxSize(700, 500));
191 SetTopWindow(g_gui.root);
192 g_gui.SetTitle("");
193
194 g_gui.root->LoadRecentFiles();
195
196 // Load palette
197 g_gui.LoadPerspective();
198
199 // Use the same icon for the executable and the running editor window.
200#ifdef __WINDOWS__
201 wxIcon icon("RD_MAIN_ICON", wxBITMAP_TYPE_ICO_RESOURCE);
202#else
203 FileName iconPath(wxStandardPaths::Get().GetExecutablePath());
204 iconPath.AppendDir("icons");
205 iconPath.SetFullName("RD_Map_Editor.png");
206 wxIcon icon(iconPath.GetFullPath(), wxBITMAP_TYPE_PNG);
207#endif
208 g_gui.root->SetIcon(icon);
209 wxBitmap iconBitmap(icon);
210
211 // Create a unique log directory for this session
212 wxDateTime now = wxDateTime::Now();
213 wxString logDir = wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() +
214 "logs" + wxFileName::GetPathSeparator() + now.Format("%Y%m%d_%H%M%S");
215
216 // Make sure it exists
217 wxFileName dirPath(logDir);
218 if (!dirPath.DirExists()) {
219 dirPath.Mkdir(wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL);
220 }
221
222 // Let them know about this session
223 std::ofstream sessionLog((logDir + wxFileName::GetPathSeparator() + "session.log").ToStdString());
224 if (sessionLog.is_open()) {
225 sessionLog << "RME Session started at " << now.FormatISOCombined() << std::endl;
226 sessionLog << "Version: " << __W_RME_VERSION__ << std::endl;
227 sessionLog.close();
228 }
229
230 // Show welcome dialog with color-shifted bitmap
231 if (g_settings.getInteger(Config::WELCOME_DIALOG) == 1 && m_file_to_open == wxEmptyString) {
232 g_gui.ShowWelcomeDialog(iconBitmap);
233 } else {
234 g_gui.root->Show();
235 }
236
237 // Set idle event handling mode
238 wxIdleEvent::SetMode(wxIDLE_PROCESS_SPECIFIED);
239
240 // Goto RME website?
241 if (g_settings.getInteger(Config::GOTO_WEBSITE_ON_BOOT) == 1) {
242 ::wxLaunchDefaultBrowser(__SITE_URL__, wxBROWSER_NEW_WINDOW);
243 g_settings.setInteger(Config::GOTO_WEBSITE_ON_BOOT, 0);
244 }
245
246 // Check for updates
247#ifdef _USE_UPDATER_
248 if (g_settings.getInteger(Config::USE_UPDATER) == -1) {
249 int ret = g_gui.PopupDialog(
250 "Notice",
251 "Do you want the editor to automatically check for updates?\n"
252 "It will connect to the internet if you choose yes.\n"
253 "You can change this setting in the preferences later.",
254 wxYES | wxNO
255 );
256 if (ret == wxID_YES) {
257 g_settings.setInteger(Config::USE_UPDATER, 1);
258 } else {
259 g_settings.setInteger(Config::USE_UPDATER, 0);
260 }
261 }
262 if (g_settings.getInteger(Config::USE_UPDATER) == 1) {
263 // UpdateChecker updater;
264 // updater.connect(g_gui.root);
265 }
266#endif
267
268 // Keep track of first event loop entry
269 m_startup = true;
270 return true;
271}
272
273void Application::OnEventLoopEnter(wxEventLoopBase* loop) {
274
275 // First startup?
276 if (!m_startup) {
277 return;
278 }
279 m_startup = false;
280
281 // Don't try to create a map if we didn't load the client map.
282 if (ClientVersion::getLatestVersion() == nullptr) {
283 return;
284 }
285
286 // Open a map.
287 if (m_file_to_open != wxEmptyString) {
288 g_gui.LoadMap(FileName(m_file_to_open));
289 } else if (!g_gui.IsWelcomeDialogShown() && g_gui.NewMap()) { // Open a new empty map
290 // You generally don't want to save this map...
291 g_gui.GetCurrentEditor()->map.clearChanges();
292 }
293
294}
295
296void Application::MacOpenFiles(const wxArrayString& fileNames) {
297 if (!fileNames.IsEmpty()) {
298 g_gui.LoadMap(FileName(fileNames.Item(0)));
299 }
300}
301
302void Application::FixVersionDiscrapencies() {
303 // Here the registry should be fixed, if the version has been changed
304 if (g_settings.getInteger(Config::VERSION_ID) < MAKE_VERSION_ID(1, 0, 5)) {
305 g_settings.setInteger(Config::USE_MEMCACHED_SPRITES_TO_SAVE, 0);
306 }
307
308 if (g_settings.getInteger(Config::VERSION_ID) < __RME_VERSION_ID__ && ClientVersion::getLatestVersion() != nullptr) {
309 g_settings.setInteger(Config::DEFAULT_CLIENT_VERSION, ClientVersion::getLatestVersion()->getID());
310 }
311
312 wxString ss = wxstr(g_settings.getString(Config::SCREENSHOT_DIRECTORY));
313 if (ss.empty()) {
314 ss = wxStandardPaths::Get().GetDocumentsDir();
315#ifdef __WINDOWS__
316 ss += "/My Pictures/RME/";
317#endif
318 }
319 g_settings.setString(Config::SCREENSHOT_DIRECTORY, nstr(ss));
320
321 // Set registry to newest version
322 g_settings.setInteger(Config::VERSION_ID, __RME_VERSION_ID__);
323}
324
325void Application::Unload() {
326 g_gui.CloseAllEditors();
327 g_gui.UnloadVersion();
328 g_gui.SaveHotkeys();
329 g_gui.SavePerspective();
330 g_gui.root->SaveRecentFiles();
331 ClientVersion::saveVersions();
332 ClientVersion::unloadVersions();
333 g_settings.save(true);
334 g_gui.root = nullptr;
335}
336
337int Application::OnExit() {
338#ifdef _USE_PROCESS_COM
339 wxDELETE(m_proc_server);
340 wxDELETE(m_single_instance_checker);
341#endif
342 return 1;
343}
344
345void Application::OnFatalException() {
346 // Log the fatal exception to a file
347 wxDateTime now = wxDateTime::Now();
348 wxString logFileName = wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() + "fatal_error_" +
349 now.Format("%Y%m%d_%H%M%S") + ".log";
350
351 // Create log directory if it doesn't exist
352 wxFileName logDir(wxStandardPaths::Get().GetUserDataDir());
353 if (!logDir.DirExists()) {
354 wxMkdir(logDir.GetPath());
355 }
356
357 // Log details to a file
358 std::ofstream logFile(logFileName.ToStdString(), std::ios::app);
359 if (logFile.is_open()) {
360 logFile << "Fatal exception occurred at " << now.FormatISOCombined() << std::endl;
361 logFile << "RME version: " << __W_RME_VERSION__ << std::endl;
362 logFile << "Please report this crash to the developers." << std::endl;
363 logFile.close();
364 }
365
366 // Create and show a simple crash dialog
367 wxString msg = "A fatal error has occurred in " + wxString(__W_RME_APPLICATION_NAME__) + ".\n\n";
368 msg += "The application will now close. A log file has been created at:\n";
369 msg += logFileName + "\n\n";
370 msg += "Please report this error to the developers.";
371
372 wxMessageBox(msg, "Fatal Error", wxICON_ERROR | wxOK);
373}
374
375bool Application::ParseCommandLineMap(wxString& fileName) {
376 if (argc == 2) {
377 // Check if it's a special command to force multiple instances
378 if (wxString(argv[1]) == "-force-multi-instance") {
379 g_settings.setInteger(Config::ONLY_ONE_INSTANCE, 0);
380 return false;
381 }
382 fileName = wxString(argv[1]);
383 return true;
384 } else if (argc == 3) {
385 if (argv[1] == "-ws") {
386 g_settings.setInteger(Config::WELCOME_DIALOG, argv[2] == "1" ? 1 : 0);
387 } else if (argv[1] == "-multi-instance") {
388 // Allow forcing multiple instances via command line
389 g_settings.setInteger(Config::ONLY_ONE_INSTANCE, argv[2] == "1" ? 0 : 1);
390 }
391 }
392 return false;
393}
394
395MainFrame::MainFrame(const wxString& title, const wxPoint& pos, const wxSize& size) :
396 wxFrame((wxFrame*)nullptr, -1, title, pos, size, wxDEFAULT_FRAME_STYLE) {
397 // Receive idle events
398 SetExtraStyle(wxWS_EX_PROCESS_IDLE);
399
400#if wxCHECK_VERSION(3, 1, 0) // 3.1.0 or higher
401 // Make sure ShowFullScreen() uses the full screen API on macOS
402 EnableFullScreenView(true);
403#endif
404
405 // Creates the file-dropdown menu
406 menu_bar = newd MainMenuBar(this);
407 wxArrayString warnings;
408 wxString error;
409
410 wxFileName filename;
411 filename.Assign(g_gui.getFoundDataDirectory() + "menubar.xml");
412 if (!filename.FileExists()) {
413 filename = FileName(GUI::GetDataDirectory() + "menubar.xml");
414 }
415
416 if (!menu_bar->Load(filename, warnings, error)) {
417 wxLogError(wxString() + "Could not load menubar.xml, editor will NOT be able to show its menu.\n");
418 }
419
420 wxStatusBar* statusbar = CreateStatusBar();
421 statusbar->SetFieldsCount(4);
422 SetStatusText(wxString("Welcome to ") << __W_RME_APPLICATION_NAME__ << " " << __W_RME_VERSION__);
423
424 // Le sizer
425 g_gui.aui_manager = newd wxAuiManager(this);
426 g_gui.tabbook = newd MapTabbook(this, wxID_ANY);
427
428 tool_bar = newd MainToolBar(this, g_gui.aui_manager);
429
430 g_gui.aui_manager->AddPane(g_gui.tabbook, wxAuiPaneInfo().CenterPane().Floatable(false).CloseButton(false).PaneBorder(false));
431 g_gui.aui_manager->Update();
432
433 UpdateMenubar();
434
435 // Apply dark mode if enabled
436 if (g_darkMode.IsDarkModeEnabled()) {
437 g_darkMode.ApplyTheme(this);
438 g_darkMode.ApplyThemeToMainMenuBar(menu_bar);
439 g_darkMode.ApplyThemeToMainToolBar(tool_bar);
440 g_darkMode.ApplyThemeToStatusBar(GetStatusBar());
441 }
442}
443
444MainFrame::~MainFrame() = default;
445
446void MainFrame::OnIdle(wxIdleEvent& event) {
447 g_gui.CheckAutoSave();
448 event.Skip();
449}
450
451#ifdef _USE_UPDATER_
452void MainFrame::OnUpdateReceived(wxCommandEvent& event) {
453 std::string data = *(std::string*)event.GetClientData();
454 delete (std::string*)event.GetClientData();
455 size_t first_colon = data.find(':');
456 size_t second_colon = data.find(':', first_colon + 1);
457
458 if (first_colon == std::string::npos || second_colon == std::string::npos) {
459 return;
460 }
461
462 std::string update = data.substr(0, first_colon);
463 std::string verstr = data.substr(first_colon + 1, second_colon - first_colon - 1);
464 std::string url = (second_colon == data.size() ? "" : data.substr(second_colon + 1));
465
466 if (update == "yes") {
467 int ret = g_gui.PopupDialog(
468 "Update Notice",
469 wxString("There is a newd update available (") << wxstr(verstr) << "). Do you want to go to the website and download it?",
470 wxYES | wxNO,
471 "I don't want any update notices",
472 Config::AUTOCHECK_FOR_UPDATES
473 );
474 if (ret == wxID_YES) {
475 ::wxLaunchDefaultBrowser(wxstr(url), wxBROWSER_NEW_WINDOW);
476 }
477 }
478}
479#endif
480
481void MainFrame::OnUpdateMenus(wxCommandEvent&) {
482 UpdateMenubar();
483 g_gui.UpdateMinimap(true);
484 g_gui.UpdateTitle();
485}
486
487#ifdef __WINDOWS__
488bool MainFrame::MSWTranslateMessage(WXMSG* msg) {
489 if (g_gui.AreHotkeysEnabled()) {
490 if (wxFrame::MSWTranslateMessage(msg)) {
491 return true;
492 }
493 } else {
494 if (wxWindow::MSWTranslateMessage(msg)) {
495 return true;
496 }
497 }
498 return false;
499}
500#endif
501
502void MainFrame::UpdateMenubar() {
503 menu_bar->Update();
504 tool_bar->UpdateButtons();
505}
506
507bool MainFrame::DoQueryClose() {
508 Editor* editor = g_gui.GetCurrentEditor();
509 if (editor) {
510 if (editor->IsLive()) {
511 long ret = g_gui.PopupDialog(
512 "Must Close Server",
513 wxString("You are currently connected to a live server, to close this map the connection must be severed."),
514 wxOK | wxCANCEL
515 );
516 if (ret == wxID_OK) {
517 editor->CloseLiveServer();
518 } else {
519 return false;
520 }
521 }
522 }
523 return true;
524}
525
526bool MainFrame::DoQuerySaveTileset(bool doclose) {
527
528 if (!g_materials.needSave()) {
529 // skip dialog when there is nothing to save
530 return true;
531 }
532
533 long ret = g_gui.PopupDialog(
534 "Export tileset",
535 "Do you want to export your tileset changes before exiting?",
536 wxYES | wxNO | wxCANCEL
537 );
538
539 if (ret == wxID_NO) {
540 // "no" - exit without saving
541 return true;
542 } else if (ret == wxID_CANCEL) {
543 // "cancel" - just close the dialog
544 return false;
545 }
546
547 // "yes" button was pressed, open tileset exporting dialog
548 if (g_gui.GetCurrentEditor()) {
549 ExportTilesetsWindow dlg(this, *g_gui.GetCurrentEditor());
550 dlg.ShowModal();
551 dlg.Destroy();
552 }
553
554 return !g_materials.needSave();
555}
556
557bool MainFrame::DoQuerySave(bool doclose) {
558 if (!g_gui.IsEditorOpen()) {
559 return true;
560 }
561
562 Editor* editor = g_gui.GetCurrentEditor();
563 if (g_gui.HasDetachedViews(editor)) {
564 wxString message = "This map has one or more detached views open.\n";
565 message += "You must close all detached views before closing the map.";
566
567 int choice = wxMessageBox(
568 message,
569 "Detached Views Open",
570 wxOK | wxCANCEL | wxICON_EXCLAMATION
571 );
572
573 if (choice == wxOK) {
574 // User chose to close detached views
575 g_gui.CloseDetachedViews(editor);
576 } else {
577 // User canceled operation
578 return false;
579 }
580 }
581
582 if (!DoQuerySaveTileset()) {
583 return false;
584 }
585
586 if (editor->IsLiveClient()) {
587 long ret = g_gui.PopupDialog(
588 "Disconnect",
589 "Do you want to disconnect?",
590 wxYES | wxNO
591 );
592
593 if (ret != wxID_YES) {
594 return false;
595 }
596
597 editor->CloseLiveServer();
598 return DoQuerySave(doclose);
599 } else if (editor->IsLiveServer()) {
600 long ret = g_gui.PopupDialog(
601 "Shutdown",
602 "Do you want to shut down the server? (any clients will be disconnected)",
603 wxYES | wxNO
604 );
605
606 if (ret != wxID_YES) {
607 return false;
608 }
609
610 editor->CloseLiveServer();
611 return DoQuerySave(doclose);
612 } else if (g_gui.ShouldSave()) {
613 long ret = g_gui.PopupDialog(
614 "Save changes",
615 "Do you want to save your changes to \"" + wxstr(g_gui.GetCurrentMap().getName()) + "\"?",
616 wxYES | wxNO | wxCANCEL
617 );
618
619 if (ret == wxID_YES) {
620 if (g_gui.GetCurrentMap().hasFile()) {
621 g_gui.SaveCurrentMap(true);
622 } else {
623 wxFileDialog file(this, "Save...", "", "", "*.otbm", wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
624 int32_t result = file.ShowModal();
625 if (result == wxID_OK) {
626 g_gui.SaveCurrentMap(file.GetPath(), true);
627 } else {
628 return false;
629 }
630 }
631 } else if (ret == wxID_CANCEL) {
632 return false;
633 }
634 }
635
636 if (doclose) {
637 UnnamedRenderingLock();
638 g_gui.CloseCurrentEditor();
639 }
640
641 return true;
642}
643
644bool MainFrame::DoQueryImportCreatures() {
645 if (g_creatures.hasMissing()) {
646 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);
647 if (ret == wxID_YES) {
648 do {
649 wxFileDialog dlg(g_gui.root, "Import monster/npc file", "", "", "*.xml", wxFD_OPEN | wxFD_MULTIPLE | wxFD_FILE_MUST_EXIST);
650 if (dlg.ShowModal() == wxID_OK) {
651 wxArrayString paths;
652 dlg.GetPaths(paths);
653 for (uint32_t i = 0; i < paths.GetCount(); ++i) {
654 wxString error;
655 wxArrayString warnings;
656 bool ok = g_creatures.importXMLFromOT(FileName(paths[i]), error, warnings);
657 if (ok) {
658 g_gui.ListDialog("Monster loader errors", warnings);
659 } else {
660 wxMessageBox("Error OT data file \"" + paths[i] + "\".\n" + error, "Error", wxOK | wxICON_INFORMATION, g_gui.root);
661 }
662 }
663 } else {
664 break;
665 }
666 } while (g_creatures.hasMissing());
667 }
668 }
669 g_gui.RefreshPalettes();
670 return true;
671}
672
673void MainFrame::UpdateFloorMenu() {
674 menu_bar->UpdateFloorMenu();
675}
676
677bool MainFrame::LoadMap(FileName name) {
678 return g_gui.LoadMap(name);
679}
680
681void MainFrame::OnExit(wxCloseEvent& event) {
682 // clicking 'x' button
683
684 // do you want to save map changes?
685 while (g_gui.IsEditorOpen()) {
686 if (!DoQuerySave()) {
687 if (event.CanVeto()) {
688 event.Veto();
689 return;
690 } else {
691 break;
692 }
693 }
694 }
695 g_gui.aui_manager->UnInit();
696 ((Application&)wxGetApp()).Unload();
697#ifdef __RELEASE__
698 // Hack, "crash" gracefully in release builds, let OS handle cleanup of windows
699 exit(0);
700#endif
701 Destroy();
702}
703
704void MainFrame::AddRecentFile(const FileName& file) {
705 menu_bar->AddRecentFile(file);
706}
707
708void MainFrame::LoadRecentFiles() {
709 menu_bar->LoadRecentFiles();
710}
711
712void MainFrame::SaveRecentFiles() {
713 menu_bar->SaveRecentFiles();
714}
715
716std::vector<wxString> MainFrame::GetRecentFiles() {
717 return menu_bar->GetRecentFiles();
718}
719
720void MainFrame::PrepareDC(wxDC& dc) {
721 dc.SetLogicalOrigin(0, 0);
722 dc.SetAxisOrientation(1, 0);
723 dc.SetUserScale(1.0, 1.0);
724 dc.SetMapMode(wxMM_TEXT);
725}
726
727// Add this method to handle exceptions in the main event loop
728bool Application::OnExceptionInMainLoop() {
729 try {
730 throw; // Rethrow the exception to catch it
731 } catch (const std::exception& e) {
732 // Log the exception
733 wxDateTime now = wxDateTime::Now();
734 wxString logFileName = wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() +
735 "exception_" + now.Format("%Y%m%d_%H%M%S") + ".log";
736
737 // Create log directory
738 wxFileName logDir(wxStandardPaths::Get().GetUserDataDir());
739 if (!logDir.DirExists()) {
740 wxMkdir(logDir.GetPath());
741 }
742
743 // Log details
744 std::ofstream logFile(logFileName.ToStdString(), std::ios::app);
745 if (logFile.is_open()) {
746 logFile << "Exception in main loop at " << now.FormatISOCombined() << std::endl;
747 logFile << "Exception: " << e.what() << std::endl;
748 logFile << "RME version: " << __W_RME_VERSION__ << std::endl;
749 logFile.close();
750 }
751
752 // Only show dialog during drawing operations for serious errors
753 if (g_gui.IsEditorOpen()) {
754 // Silently continue for editor operations to prevent UI freezes
755 return true; // Continue execution
756 } else {
757 wxString msg = "An error occurred in " + wxString(__W_RME_APPLICATION_NAME__) + ".\n\n";
758 msg += "Error details: " + wxString(e.what()) + "\n\n";
759 msg += "The application will try to continue. If problems persist, please restart.";
760 wxMessageBox(msg, "Error", wxICON_WARNING | wxOK);
761 return true; // Continue execution
762 }
763 } catch (...) {
764 // Unknown exception - more serious
765 wxDateTime now = wxDateTime::Now();
766 wxString logFileName = wxStandardPaths::Get().GetUserDataDir() + wxFileName::GetPathSeparator() +
767 "unknown_exception_" + now.Format("%Y%m%d_%H%M%S") + ".log";
768
769 // Create log directory
770 wxFileName logDir(wxStandardPaths::Get().GetUserDataDir());
771 if (!logDir.DirExists()) {
772 wxMkdir(logDir.GetPath());
773 }
774
775 // Log whatever we can
776 std::ofstream logFile(logFileName.ToStdString(), std::ios::app);
777 if (logFile.is_open()) {
778 logFile << "Unknown exception in main loop at " << now.FormatISOCombined() << std::endl;
779 logFile << "RME version: " << __W_RME_VERSION__ << std::endl;
780 logFile.close();
781 }
782
783 // Only show dialog during drawing operations for serious errors
784 if (g_gui.IsEditorOpen()) {
785 // Silently continue for editor operations
786 return true; // Continue execution
787 } else {
788 wxString msg = "An unknown error occurred in " + wxString(__W_RME_APPLICATION_NAME__) + ".\n\n";
789 msg += "The application will try to continue. If problems persist, please restart.";
790 wxMessageBox(msg, "Error", wxICON_WARNING | wxOK);
791 return true; // Continue execution
792 }
793 }
794
795 return false; // If all else fails, let the default handler take over
796}
797
Top