| @@ -0,0 +1,796 @@ | |||
| 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 | + | ||
| 54 | + | BEGIN_EVENT_TABLE(MainFrame, wxFrame) | |
| 55 | + | EVT_CLOSE(MainFrame::OnExit) | |
| 56 | + | ||
| 57 | + | // Update check complete | |
| 58 | + | #ifdef _USE_UPDATER_ | |
| 59 | + | EVT_ON_UPDATE_CHECK_FINISHED(wxID_ANY, MainFrame::OnUpdateReceived) | |
| 60 | + | #endif | |
| 61 | + | EVT_ON_UPDATE_MENUS(wxID_ANY, MainFrame::OnUpdateMenus) | |
| 62 | + | ||
| 63 | + | // Idle event handler | |
| 64 | + | EVT_IDLE(MainFrame::OnIdle) | |
| 65 | + | END_EVENT_TABLE() | |
| 66 | + | ||
| 67 | + | BEGIN_EVENT_TABLE(MapWindow, wxPanel) | |
| 68 | + | EVT_SIZE(MapWindow::OnSize) | |
| 69 | + | ||
| 70 | + | EVT_COMMAND_SCROLL_TOP(MAP_WINDOW_HSCROLL, MapWindow::OnScroll) | |
| 71 | + | EVT_COMMAND_SCROLL_BOTTOM(MAP_WINDOW_HSCROLL, MapWindow::OnScroll) | |
| 72 | + | EVT_COMMAND_SCROLL_THUMBTRACK(MAP_WINDOW_HSCROLL, MapWindow::OnScroll) | |
| 73 | + | EVT_COMMAND_SCROLL_LINEUP(MAP_WINDOW_HSCROLL, MapWindow::OnScrollLineUp) | |
| 74 | + | EVT_COMMAND_SCROLL_LINEDOWN(MAP_WINDOW_HSCROLL, MapWindow::OnScrollLineDown) | |
| 75 | + | EVT_COMMAND_SCROLL_PAGEUP(MAP_WINDOW_HSCROLL, MapWindow::OnScrollPageUp) | |
| 76 | + | EVT_COMMAND_SCROLL_PAGEDOWN(MAP_WINDOW_HSCROLL, MapWindow::OnScrollPageDown) | |
| 77 | + | ||
| 78 | + | EVT_COMMAND_SCROLL_TOP(MAP_WINDOW_VSCROLL, MapWindow::OnScroll) | |
| 79 | + | EVT_COMMAND_SCROLL_BOTTOM(MAP_WINDOW_VSCROLL, MapWindow::OnScroll) | |
| 80 | + | EVT_COMMAND_SCROLL_THUMBTRACK(MAP_WINDOW_VSCROLL, MapWindow::OnScroll) | |
| 81 | + | EVT_COMMAND_SCROLL_LINEUP(MAP_WINDOW_VSCROLL, MapWindow::OnScrollLineUp) | |
| 82 | + | EVT_COMMAND_SCROLL_LINEDOWN(MAP_WINDOW_VSCROLL, MapWindow::OnScrollLineDown) | |
| 83 | + | EVT_COMMAND_SCROLL_PAGEUP(MAP_WINDOW_VSCROLL, MapWindow::OnScrollPageUp) | |
| 84 | + | EVT_COMMAND_SCROLL_PAGEDOWN(MAP_WINDOW_VSCROLL, MapWindow::OnScrollPageDown) | |
| 85 | + | ||
| 86 | + | EVT_BUTTON(MAP_WINDOW_GEM, MapWindow::OnGem) | |
| 87 | + | END_EVENT_TABLE() | |
| 88 | + | ||
| 89 | + | BEGIN_EVENT_TABLE(MapScrollBar, wxScrollBar) | |
| 90 | + | EVT_KEY_DOWN(MapScrollBar::OnKey) | |
| 91 | + | EVT_KEY_UP(MapScrollBar::OnKey) | |
| 92 | + | EVT_CHAR(MapScrollBar::OnKey) | |
| 93 | + | EVT_SET_FOCUS(MapScrollBar::OnFocus) | |
| 94 | + | EVT_MOUSEWHEEL(MapScrollBar::OnWheel) | |
| 95 | + | END_EVENT_TABLE() | |
| 96 | + | ||
| 97 | + | wxIMPLEMENT_APP(Application); | |
| 98 | + | ||
| 99 | + | Application::~Application() { | |
| 100 | + | // Destroy | |
| 101 | + | } | |
| 102 | + | ||
| 103 | + | bool 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 | + | ||
| 273 | + | void 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 | + | ||
| 296 | + | void Application::MacOpenFiles(const wxArrayString& fileNames) { | |
| 297 | + | if (!fileNames.IsEmpty()) { | |
| 298 | + | g_gui.LoadMap(FileName(fileNames.Item(0))); | |
| 299 | + | } | |
| 300 | + | } | |
| 301 | + | ||
| 302 | + | void 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 | + | ||
| 325 | + | void 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 | + | ||
| 337 | + | int 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 | + | ||
| 345 | + | void 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 | + | ||
| 375 | + | bool 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 | + | ||
| 395 | + | MainFrame::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 | + | ||
| 444 | + | MainFrame::~MainFrame() = default; | |
| 445 | + | ||
| 446 | + | void MainFrame::OnIdle(wxIdleEvent& event) { | |
| 447 | + | g_gui.CheckAutoSave(); | |
| 448 | + | event.Skip(); | |
| 449 | + | } | |
| 450 | + | ||
| 451 | + | #ifdef _USE_UPDATER_ | |
| 452 | + | void 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 | + | ||
| 481 | + | void MainFrame::OnUpdateMenus(wxCommandEvent&) { | |
| 482 | + | UpdateMenubar(); | |
| 483 | + | g_gui.UpdateMinimap(true); | |
| 484 | + | g_gui.UpdateTitle(); | |
| 485 | + | } | |
| 486 | + | ||
| 487 | + | #ifdef __WINDOWS__ | |
| 488 | + | bool 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 | + | ||
| 502 | + | void MainFrame::UpdateMenubar() { | |
| 503 | + | menu_bar->Update(); | |
| 504 | + | tool_bar->UpdateButtons(); | |
| 505 | + | } | |
| 506 | + | ||
| 507 | + | bool 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 | + | ||
| 526 | + | bool 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 | + | ||
| 557 | + | bool 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 | + | ||
| 644 | + | bool 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 | + | ||
| 673 | + | void MainFrame::UpdateFloorMenu() { | |
| 674 | + | menu_bar->UpdateFloorMenu(); | |
| 675 | + | } | |
| 676 | + | ||
| 677 | + | bool MainFrame::LoadMap(FileName name) { | |
| 678 | + | return g_gui.LoadMap(name); | |
| 679 | + | } | |
| 680 | + | ||
| 681 | + | void 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 | + | ||
| 704 | + | void MainFrame::AddRecentFile(const FileName& file) { | |
| 705 | + | menu_bar->AddRecentFile(file); | |
| 706 | + | } | |
| 707 | + | ||
| 708 | + | void MainFrame::LoadRecentFiles() { | |
| 709 | + | menu_bar->LoadRecentFiles(); | |
| 710 | + | } | |
| 711 | + | ||
| 712 | + | void MainFrame::SaveRecentFiles() { | |
| 713 | + | menu_bar->SaveRecentFiles(); | |
| 714 | + | } | |
| 715 | + | ||
| 716 | + | std::vector<wxString> MainFrame::GetRecentFiles() { | |
| 717 | + | return menu_bar->GetRecentFiles(); | |
| 718 | + | } | |
| 719 | + | ||
| 720 | + | void 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 | |
| 728 | + | bool 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 | + | } |
| @@ -0,0 +1,110 @@ | |||
| 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 | + | #ifndef RME_APPLICATION_H_ | |
| 19 | + | #define RME_APPLICATION_H_ | |
| 20 | + | ||
| 21 | + | #include "gui.h" | |
| 22 | + | #include "main_toolbar.h" | |
| 23 | + | #include "action.h" | |
| 24 | + | #include "settings.h" | |
| 25 | + | ||
| 26 | + | #include "process_com.h" | |
| 27 | + | #include "map_display.h" | |
| 28 | + | #include "welcome_dialog.h" | |
| 29 | + | ||
| 30 | + | class Item; | |
| 31 | + | class Creature; | |
| 32 | + | ||
| 33 | + | class MainFrame; | |
| 34 | + | class MapWindow; | |
| 35 | + | class wxEventLoopBase; | |
| 36 | + | class wxSingleInstanceChecker; | |
| 37 | + | ||
| 38 | + | class Application : public wxApp { | |
| 39 | + | public: | |
| 40 | + | ~Application(); | |
| 41 | + | virtual bool OnInit(); | |
| 42 | + | virtual void OnEventLoopEnter(wxEventLoopBase* loop); | |
| 43 | + | virtual void MacOpenFiles(const wxArrayString& fileNames); | |
| 44 | + | virtual int OnExit(); | |
| 45 | + | virtual bool OnExceptionInMainLoop(); | |
| 46 | + | void Unload(); | |
| 47 | + | ||
| 48 | + | private: | |
| 49 | + | bool m_startup; | |
| 50 | + | wxString m_file_to_open; | |
| 51 | + | void FixVersionDiscrapencies(); | |
| 52 | + | bool ParseCommandLineMap(wxString& fileName); | |
| 53 | + | ||
| 54 | + | virtual void OnFatalException(); | |
| 55 | + | ||
| 56 | + | #ifdef _USE_PROCESS_COM | |
| 57 | + | RMEProcessServer* m_proc_server; | |
| 58 | + | wxSingleInstanceChecker* m_single_instance_checker; | |
| 59 | + | #endif | |
| 60 | + | }; | |
| 61 | + | ||
| 62 | + | class MainMenuBar; | |
| 63 | + | ||
| 64 | + | class MainFrame : public wxFrame { | |
| 65 | + | public: | |
| 66 | + | MainFrame(const wxString& title, const wxPoint& pos, const wxSize& size); | |
| 67 | + | ~MainFrame(); | |
| 68 | + | ||
| 69 | + | void UpdateMenubar(); | |
| 70 | + | bool DoQueryClose(); | |
| 71 | + | bool DoQuerySave(bool doclose = true); | |
| 72 | + | bool DoQuerySaveTileset(bool doclose = true); | |
| 73 | + | bool DoQueryImportCreatures(); | |
| 74 | + | bool LoadMap(FileName name); | |
| 75 | + | ||
| 76 | + | void AddRecentFile(const FileName& file); | |
| 77 | + | void LoadRecentFiles(); | |
| 78 | + | void SaveRecentFiles(); | |
| 79 | + | std::vector<wxString> GetRecentFiles(); | |
| 80 | + | ||
| 81 | + | MainToolBar* GetAuiToolBar() const { | |
| 82 | + | return tool_bar; | |
| 83 | + | } | |
| 84 | + | ||
| 85 | + | void OnUpdateMenus(wxCommandEvent& event); | |
| 86 | + | void UpdateFloorMenu(); | |
| 87 | + | void OnIdle(wxIdleEvent& event); | |
| 88 | + | void OnExit(wxCloseEvent& event); | |
| 89 | + | ||
| 90 | + | #ifdef _USE_UPDATER_ | |
| 91 | + | void OnUpdateReceived(wxCommandEvent& event); | |
| 92 | + | #endif | |
| 93 | + | ||
| 94 | + | #ifdef __WINDOWS__ | |
| 95 | + | virtual bool MSWTranslateMessage(WXMSG* msg); | |
| 96 | + | #endif | |
| 97 | + | ||
| 98 | + | void PrepareDC(wxDC& dc); | |
| 99 | + | ||
| 100 | + | protected: | |
| 101 | + | MainMenuBar* menu_bar; | |
| 102 | + | MainToolBar* tool_bar; | |
| 103 | + | ||
| 104 | + | friend class Application; | |
| 105 | + | friend class GUI; | |
| 106 | + | ||
| 107 | + | DECLARE_EVENT_TABLE() | |
| 108 | + | }; | |
| 109 | + | ||
| 110 | + | #endif |
| @@ -0,0 +1,109 @@ | |||
| 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 | + | #include "artprovider.h" | |
| 20 | + | ||
| 21 | + | #include "../icons/circular_1.xpm" | |
| 22 | + | #include "../icons/circular_2.xpm" | |
| 23 | + | #include "../icons/circular_3.xpm" | |
| 24 | + | #include "../icons/circular_4.xpm" | |
| 25 | + | #include "../icons/circular_5.xpm" | |
| 26 | + | #include "../icons/circular_6.xpm" | |
| 27 | + | #include "../icons/circular_7.xpm" | |
| 28 | + | #include "../icons/nologout_zone.xpm" | |
| 29 | + | #include "../icons/nopvp_zone.xpm" | |
| 30 | + | #include "../icons/position_go.xpm" | |
| 31 | + | #include "../icons/protected_zone.xpm" | |
| 32 | + | #include "../icons/pvp_zone.xpm" | |
| 33 | + | #include "../icons/rectangular_1.xpm" | |
| 34 | + | #include "../icons/rectangular_2.xpm" | |
| 35 | + | #include "../icons/rectangular_3.xpm" | |
| 36 | + | #include "../icons/rectangular_4.xpm" | |
| 37 | + | #include "../icons/rectangular_5.xpm" | |
| 38 | + | #include "../icons/rectangular_6.xpm" | |
| 39 | + | #include "../icons/rectangular_7.xpm" | |
| 40 | + | #include "../brushes/door_normal_small.xpm" | |
| 41 | + | #include "../brushes/door_locked_small.xpm" | |
| 42 | + | #include "../brushes/door_magic_small.xpm" | |
| 43 | + | #include "../brushes/door_quest_small.xpm" | |
| 44 | + | #include "../brushes/door_normal_alt_small.xpm" | |
| 45 | + | #include "../brushes/door_archway_small.xpm" | |
| 46 | + | ||
| 47 | + | wxBitmap ArtProvider::CreateBitmap(const wxArtID& id, const wxArtClient& client, const wxSize& WXUNUSED(size)) { | |
| 48 | + | if (client == wxART_TOOLBAR) { | |
| 49 | + | if (id == ART_CIRCULAR) { | |
| 50 | + | return wxBitmap(circular_4_xpm); | |
| 51 | + | } else if (id == ART_CIRCULAR_1) { | |
| 52 | + | return wxBitmap(circular_1_xpm); | |
| 53 | + | } else if (id == ART_CIRCULAR_2) { | |
| 54 | + | return wxBitmap(circular_2_xpm); | |
| 55 | + | } else if (id == ART_CIRCULAR_3) { | |
| 56 | + | return wxBitmap(circular_3_xpm); | |
| 57 | + | } else if (id == ART_CIRCULAR_4) { | |
| 58 | + | return wxBitmap(circular_4_xpm); | |
| 59 | + | } else if (id == ART_CIRCULAR_5) { | |
| 60 | + | return wxBitmap(circular_5_xpm); | |
| 61 | + | } else if (id == ART_CIRCULAR_6) { | |
| 62 | + | return wxBitmap(circular_6_xpm); | |
| 63 | + | } else if (id == ART_CIRCULAR_7) { | |
| 64 | + | return wxBitmap(circular_7_xpm); | |
| 65 | + | } else if (id == ART_NOLOOUT_BRUSH) { | |
| 66 | + | return wxBitmap(nologout_zone_xpm); | |
| 67 | + | } else if (id == ART_NOPVP_BRUSH) { | |
| 68 | + | return wxBitmap(nopvp_zone_xpm); | |
| 69 | + | } else if (id == ART_POSITION_GO) { | |
| 70 | + | return wxBitmap(position_go_xpm); | |
| 71 | + | } else if (id == ART_PVP_BRUSH) { | |
| 72 | + | return wxBitmap(pvp_zone_xpm); | |
| 73 | + | } else if (id == ART_PZ_BRUSH) { | |
| 74 | + | return wxBitmap(protected_zone_xpm); | |
| 75 | + | } else if (id == ART_RECTANGULAR) { | |
| 76 | + | return wxBitmap(rectangular_4_xpm); | |
| 77 | + | } else if (id == ART_RECTANGULAR_1) { | |
| 78 | + | return wxBitmap(rectangular_1_xpm); | |
| 79 | + | } else if (id == ART_RECTANGULAR_2) { | |
| 80 | + | return wxBitmap(rectangular_2_xpm); | |
| 81 | + | } else if (id == ART_RECTANGULAR_3) { | |
| 82 | + | return wxBitmap(rectangular_3_xpm); | |
| 83 | + | } else if (id == ART_RECTANGULAR_4) { | |
| 84 | + | return wxBitmap(rectangular_4_xpm); | |
| 85 | + | } else if (id == ART_RECTANGULAR_5) { | |
| 86 | + | return wxBitmap(rectangular_5_xpm); | |
| 87 | + | } else if (id == ART_RECTANGULAR_6) { | |
| 88 | + | return wxBitmap(rectangular_6_xpm); | |
| 89 | + | } else if (id == ART_RECTANGULAR_7) { | |
| 90 | + | return wxBitmap(rectangular_7_xpm); | |
| 91 | + | } | |
| 92 | + | ||
| 93 | + | else if (id == ART_DOOR_NORMAL_SMALL) { | |
| 94 | + | return wxBitmap(door_normal_small_xpm); | |
| 95 | + | } else if (id == ART_DOOR_LOCKED_SMALL) { | |
| 96 | + | return wxBitmap(door_locked_small_xpm); | |
| 97 | + | } else if (id == ART_DOOR_MAGIC_SMALL) { | |
| 98 | + | return wxBitmap(door_magic_small_xpm); | |
| 99 | + | } else if (id == ART_DOOR_QUEST_SMALL) { | |
| 100 | + | return wxBitmap(door_quest_small_xpm); | |
| 101 | + | } else if (id == ART_DOOR_NORMAL_ALT_SMALL) { | |
| 102 | + | return wxBitmap(door_normal_alt_small_xpm); | |
| 103 | + | } else if (id == ART_DOOR_ARCHWAY_SMALL) { | |
| 104 | + | return wxBitmap(door_archway_small_xpm); | |
| 105 | + | } | |
| 106 | + | } | |
| 107 | + | ||
| 108 | + | return wxNullBitmap; | |
| 109 | + | } |
| @@ -0,0 +1,63 @@ | |||
| 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 | + | #ifndef RME_ART_PROVIDER_H_ | |
| 19 | + | #define RME_ART_PROVIDER_H_ | |
| 20 | + | ||
| 21 | + | #include <wx/artprov.h> | |
| 22 | + | ||
| 23 | + | #define ART_CIRCULAR wxART_MAKE_ART_ID(ART_CIRCULAR) | |
| 24 | + | #define ART_CIRCULAR_1 wxART_MAKE_ART_ID(ART_CIRCULAR_1) | |
| 25 | + | #define ART_CIRCULAR_2 wxART_MAKE_ART_ID(ART_CIRCULAR_2) | |
| 26 | + | #define ART_CIRCULAR_3 wxART_MAKE_ART_ID(ART_CIRCULAR_3) | |
| 27 | + | #define ART_CIRCULAR_4 wxART_MAKE_ART_ID(ART_CIRCULAR_4) | |
| 28 | + | #define ART_CIRCULAR_5 wxART_MAKE_ART_ID(ART_CIRCULAR_5) | |
| 29 | + | #define ART_CIRCULAR_6 wxART_MAKE_ART_ID(ART_CIRCULAR_6) | |
| 30 | + | #define ART_CIRCULAR_7 wxART_MAKE_ART_ID(ART_CIRCULAR_7) | |
| 31 | + | #define ART_NOLOOUT_BRUSH wxART_MAKE_ART_ID(ART_NOLOOUT_BRUSH) | |
| 32 | + | #define ART_NOPVP_BRUSH wxART_MAKE_ART_ID(ART_NOPVP_BRUSH) | |
| 33 | + | #define ART_POSITION_GO wxART_MAKE_ART_ID(ART_POSITION_GO) | |
| 34 | + | #define ART_PVP_BRUSH wxART_MAKE_ART_ID(ART_PVP_BRUSH) | |
| 35 | + | #define ART_PZ_BRUSH wxART_MAKE_ART_ID(ART_PZ_BRUSH) | |
| 36 | + | #define ART_RECTANGULAR wxART_MAKE_ART_ID(ART_RECTANGULAR) | |
| 37 | + | #define ART_RECTANGULAR_1 wxART_MAKE_ART_ID(ART_RECTANGULAR_1) | |
| 38 | + | #define ART_RECTANGULAR_2 wxART_MAKE_ART_ID(ART_RECTANGULAR_2) | |
| 39 | + | #define ART_RECTANGULAR_3 wxART_MAKE_ART_ID(ART_RECTANGULAR_3) | |
| 40 | + | #define ART_RECTANGULAR_4 wxART_MAKE_ART_ID(ART_RECTANGULAR_4) | |
| 41 | + | #define ART_RECTANGULAR_5 wxART_MAKE_ART_ID(ART_RECTANGULAR_5) | |
| 42 | + | #define ART_RECTANGULAR_6 wxART_MAKE_ART_ID(ART_RECTANGULAR_6) | |
| 43 | + | #define ART_RECTANGULAR_7 wxART_MAKE_ART_ID(ART_RECTANGULAR_7) | |
| 44 | + | ||
| 45 | + | #define ART_DOOR_NORMAL wxART_MAKE_ART_ID(ART_DOOR_NORMAL) | |
| 46 | + | #define ART_DOOR_NORMAL_SMALL wxART_MAKE_ART_ID(ART_DOOR_NORMAL_SMALL) | |
| 47 | + | #define ART_DOOR_LOCKED wxART_MAKE_ART_ID(ART_DOOR_LOCKED) | |
| 48 | + | #define ART_DOOR_LOCKED_SMALL wxART_MAKE_ART_ID(ART_DOOR_LOCKED_SMALL) | |
| 49 | + | #define ART_DOOR_MAGIC wxART_MAKE_ART_ID(ART_DOOR_MAGIC) | |
| 50 | + | #define ART_DOOR_MAGIC_SMALL wxART_MAKE_ART_ID(ART_DOOR_MAGIC_SMALL) | |
| 51 | + | #define ART_DOOR_QUEST wxART_MAKE_ART_ID(ART_DOOR_QUEST) | |
| 52 | + | #define ART_DOOR_QUEST_SMALL wxART_MAKE_ART_ID(ART_DOOR_QUEST_SMALL) | |
| 53 | + | #define ART_DOOR_NORMAL_ALT wxART_MAKE_ART_ID(ART_DOOR_NORMAL_ALT) | |
| 54 | + | #define ART_DOOR_NORMAL_ALT_SMALL wxART_MAKE_ART_ID(ART_DOOR_NORMAL_ALT_SMALL) | |
| 55 | + | #define ART_DOOR_ARCHWAY wxART_MAKE_ART_ID(ART_DOOR_ARCHWAY) | |
| 56 | + | #define ART_DOOR_ARCHWAY_SMALL wxART_MAKE_ART_ID(ART_DOOR_ARCHWAY_SMALL) | |
| 57 | + | ||
| 58 | + | class ArtProvider : public wxArtProvider { | |
| 59 | + | protected: | |
| 60 | + | virtual wxBitmap CreateBitmap(const wxArtID& id, const wxArtClient& client, const wxSize& size) override; | |
| 61 | + | }; | |
| 62 | + | ||
| 63 | + | #endif // RME_ART_PROVIDER_H_ |
| @@ -0,0 +1,216 @@ | |||
| 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 | + | #include "automagic_settings.h" | |
| 20 | + | #include "settings.h" | |
| 21 | + | #include "gui.h" | |
| 22 | + | ||
| 23 | + | BEGIN_EVENT_TABLE(AutomagicSettingsDialog, wxDialog) | |
| 24 | + | EVT_BUTTON(wxID_OK, AutomagicSettingsDialog::OnClickOK) | |
| 25 | + | EVT_BUTTON(wxID_CANCEL, AutomagicSettingsDialog::OnClickCancel) | |
| 26 | + | EVT_CHECKBOX(wxID_ANY, AutomagicSettingsDialog::OnAutomagicCheck) | |
| 27 | + | EVT_CLOSE(AutomagicSettingsDialog::OnClose) | |
| 28 | + | END_EVENT_TABLE() | |
| 29 | + | ||
| 30 | + | AutomagicSettingsDialog::AutomagicSettingsDialog(wxWindow* parent) : | |
| 31 | + | wxDialog(parent, wxID_ANY, "Automagic Settings", wxDefaultPosition, wxDefaultSize, wxDEFAULT_DIALOG_STYLE) | |
| 32 | + | { | |
| 33 | + | // Create main sizer | |
| 34 | + | wxBoxSizer* main_sizer = newd wxBoxSizer(wxVERTICAL); | |
| 35 | + | ||
| 36 | + | // Create a static box for the settings | |
| 37 | + | wxStaticBoxSizer* settings_sizer = newd wxStaticBoxSizer(wxVERTICAL, this, "Border Settings"); | |
| 38 | + | ||
| 39 | + | // Create checkboxes | |
| 40 | + | automagic_enabled_checkbox = newd wxCheckBox(this, wxID_ANY, "Enable Automagic"); | |
| 41 | + | automagic_enabled_checkbox->SetValue(g_settings.getBoolean(Config::USE_AUTOMAGIC)); | |
| 42 | + | automagic_enabled_checkbox->SetToolTip("Automatically apply borders and wall connections when editing"); | |
| 43 | + | settings_sizer->Add(automagic_enabled_checkbox, 0, wxALL, 5); | |
| 44 | + | ||
| 45 | + | same_ground_type_checkbox = newd wxCheckBox(this, wxID_ANY, "Same Ground Type Border"); | |
| 46 | + | same_ground_type_checkbox->SetValue(g_settings.getBoolean(Config::SAME_GROUND_TYPE_BORDER)); | |
| 47 | + | same_ground_type_checkbox->SetToolTip("Preserve existing borders and only apply borders for the current ground type"); | |
| 48 | + | same_ground_type_checkbox->Enable(automagic_enabled_checkbox->GetValue()); | |
| 49 | + | settings_sizer->Add(same_ground_type_checkbox, 0, wxALL, 5); | |
| 50 | + | ||
| 51 | + | walls_repel_borders_checkbox = newd wxCheckBox(this, wxID_ANY, "Walls Repel Borders"); | |
| 52 | + | walls_repel_borders_checkbox->SetValue(g_settings.getBoolean(Config::WALLS_REPEL_BORDERS)); | |
| 53 | + | walls_repel_borders_checkbox->SetToolTip("When enabled, walls will block border generation, preventing borders from crossing through walls"); | |
| 54 | + | walls_repel_borders_checkbox->Enable(automagic_enabled_checkbox->GetValue()); | |
| 55 | + | settings_sizer->Add(walls_repel_borders_checkbox, 0, wxALL, 5); | |
| 56 | + | ||
| 57 | + | layer_carpets_checkbox = newd wxCheckBox(this, wxID_ANY, "Layer Carpets"); | |
| 58 | + | layer_carpets_checkbox->SetValue(g_settings.getBoolean(Config::LAYER_CARPETS)); | |
| 59 | + | layer_carpets_checkbox->SetToolTip("When enabled, carpet brushes will be placed on top of existing carpets instead of replacing them"); | |
| 60 | + | layer_carpets_checkbox->Enable(automagic_enabled_checkbox->GetValue()); | |
| 61 | + | settings_sizer->Add(layer_carpets_checkbox, 0, wxALL, 5); | |
| 62 | + | ||
| 63 | + | borderize_delete_checkbox = newd wxCheckBox(this, wxID_ANY, "Borderize on Delete"); | |
| 64 | + | borderize_delete_checkbox->SetValue(g_settings.getBoolean(Config::BORDERIZE_DELETE)); | |
| 65 | + | borderize_delete_checkbox->SetToolTip("When enabled, deleting items will trigger automatic bordering of surrounding tiles"); | |
| 66 | + | borderize_delete_checkbox->Enable(automagic_enabled_checkbox->GetValue()); | |
| 67 | + | settings_sizer->Add(borderize_delete_checkbox, 0, wxALL, 5); | |
| 68 | + | ||
| 69 | + | // Create custom border controls | |
| 70 | + | wxBoxSizer* custom_border_sizer = newd wxBoxSizer(wxHORIZONTAL); | |
| 71 | + | ||
| 72 | + | custom_border_checkbox = newd wxCheckBox(this, wxID_ANY, "Use Custom Border"); | |
| 73 | + | custom_border_checkbox->SetValue(g_settings.getBoolean(Config::CUSTOM_BORDER_ENABLED)); | |
| 74 | + | custom_border_checkbox->SetToolTip("Override automatic border selection with a specific border ID"); | |
| 75 | + | custom_border_checkbox->Enable(automagic_enabled_checkbox->GetValue()); | |
| 76 | + | custom_border_sizer->Add(custom_border_checkbox, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); | |
| 77 | + | ||
| 78 | + | custom_border_id_label = newd wxStaticText(this, wxID_ANY, "Border ID:"); | |
| 79 | + | custom_border_id_label->Enable(custom_border_checkbox->GetValue() && automagic_enabled_checkbox->GetValue()); | |
| 80 | + | custom_border_sizer->Add(custom_border_id_label, 0, wxALIGN_CENTER_VERTICAL | wxRIGHT, 5); | |
| 81 | + | ||
| 82 | + | custom_border_id_field = newd wxSpinCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(80, -1)); | |
| 83 | + | custom_border_id_field->SetRange(1, 65535); | |
| 84 | + | custom_border_id_field->SetValue(g_settings.getInteger(Config::CUSTOM_BORDER_ID) > 0 ? | |
| 85 | + | g_settings.getInteger(Config::CUSTOM_BORDER_ID) : 1); | |
| 86 | + | custom_border_id_field->Enable(custom_border_checkbox->GetValue() && automagic_enabled_checkbox->GetValue()); | |
| 87 | + | custom_border_id_field->SetToolTip("The ID of the border to use when drawing any tile"); | |
| 88 | + | custom_border_sizer->Add(custom_border_id_field, 0, wxALIGN_CENTER_VERTICAL); | |
| 89 | + | ||
| 90 | + | settings_sizer->Add(custom_border_sizer, 0, wxALL, 5); | |
| 91 | + | ||
| 92 | + | // Add description text | |
| 93 | + | wxStaticText* description = newd wxStaticText(this, wxID_ANY, | |
| 94 | + | "When 'Same Ground Type Border' is enabled, the editor will:\n" | |
| 95 | + | "- Preserve existing borders on tiles\n" | |
| 96 | + | "- Only apply borders for the current ground type\n" | |
| 97 | + | "- Respect Z-axis positioning of existing borders\n" | |
| 98 | + | "- Allow multiple border layering\n\n" | |
| 99 | + | "When 'Walls Repel Borders' is enabled, the editor will:\n" | |
| 100 | + | "- Prevent borders from crossing through walls\n" | |
| 101 | + | "- Treat walls as barriers for border generation\n" | |
| 102 | + | "- Preserve the structure of buildings and houses\n\n" | |
| 103 | + | "When 'Layer Carpets' is enabled, the editor will:\n" | |
| 104 | + | "- Place new carpets on top of existing carpets\n" | |
| 105 | + | "- Allow creating multi-layered carpet designs\n\n" | |
| 106 | + | "When 'Use Custom Border' is enabled, the editor will:\n" | |
| 107 | + | "- Override automatic border selection with the specified border ID\n" | |
| 108 | + | "- Apply the same border pattern around any tile you draw\n" | |
| 109 | + | "- Ignore terrain type border transitions"); | |
| 110 | + | settings_sizer->Add(description, 0, wxALL, 5); | |
| 111 | + | ||
| 112 | + | main_sizer->Add(settings_sizer, 0, wxEXPAND | wxALL, 10); | |
| 113 | + | ||
| 114 | + | // Create buttons | |
| 115 | + | buttons_sizer = newd wxStdDialogButtonSizer(); | |
| 116 | + | ok_button = newd wxButton(this, wxID_OK, "OK"); | |
| 117 | + | cancel_button = newd wxButton(this, wxID_CANCEL, "Cancel"); | |
| 118 | + | ||
| 119 | + | buttons_sizer->AddButton(ok_button); | |
| 120 | + | buttons_sizer->AddButton(cancel_button); | |
| 121 | + | buttons_sizer->Realize(); | |
| 122 | + | ||
| 123 | + | main_sizer->Add(buttons_sizer, 0, wxALIGN_CENTER | wxALL, 10); | |
| 124 | + | ||
| 125 | + | // Set sizer and fit | |
| 126 | + | SetSizer(main_sizer); | |
| 127 | + | Fit(); | |
| 128 | + | Centre(); | |
| 129 | + | } | |
| 130 | + | ||
| 131 | + | AutomagicSettingsDialog::~AutomagicSettingsDialog() { | |
| 132 | + | // Nothing to clean up | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | bool AutomagicSettingsDialog::IsAutomagicEnabled() const { | |
| 136 | + | return automagic_enabled_checkbox->GetValue(); | |
| 137 | + | } | |
| 138 | + | ||
| 139 | + | bool AutomagicSettingsDialog::IsSameGroundTypeBorderEnabled() const { | |
| 140 | + | return same_ground_type_checkbox->GetValue(); | |
| 141 | + | } | |
| 142 | + | ||
| 143 | + | bool AutomagicSettingsDialog::IsWallsRepelBordersEnabled() const { | |
| 144 | + | return walls_repel_borders_checkbox->GetValue(); | |
| 145 | + | } | |
| 146 | + | ||
| 147 | + | bool AutomagicSettingsDialog::IsLayerCarpetsEnabled() const { | |
| 148 | + | return layer_carpets_checkbox->GetValue(); | |
| 149 | + | } | |
| 150 | + | ||
| 151 | + | bool AutomagicSettingsDialog::IsBorderizeDeleteEnabled() const { | |
| 152 | + | return borderize_delete_checkbox->GetValue(); | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | bool AutomagicSettingsDialog::IsCustomBorderEnabled() const { | |
| 156 | + | return custom_border_checkbox->GetValue(); | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | int AutomagicSettingsDialog::GetCustomBorderId() const { | |
| 160 | + | return custom_border_id_field->GetValue(); | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | void AutomagicSettingsDialog::OnClickOK(wxCommandEvent& event) { | |
| 164 | + | // Save settings | |
| 165 | + | g_settings.setInteger(Config::USE_AUTOMAGIC, IsAutomagicEnabled() ? 1 : 0); | |
| 166 | + | g_settings.setInteger(Config::BORDER_IS_GROUND, IsAutomagicEnabled() ? 1 : 0); | |
| 167 | + | g_settings.setInteger(Config::SAME_GROUND_TYPE_BORDER, IsSameGroundTypeBorderEnabled() ? 1 : 0); | |
| 168 | + | g_settings.setInteger(Config::WALLS_REPEL_BORDERS, IsWallsRepelBordersEnabled() ? 1 : 0); | |
| 169 | + | g_settings.setInteger(Config::LAYER_CARPETS, IsLayerCarpetsEnabled() ? 1 : 0); | |
| 170 | + | g_settings.setInteger(Config::BORDERIZE_DELETE, IsBorderizeDeleteEnabled() ? 1 : 0); | |
| 171 | + | ||
| 172 | + | // Save custom border settings | |
| 173 | + | g_settings.setInteger(Config::CUSTOM_BORDER_ENABLED, IsCustomBorderEnabled() ? 1 : 0); | |
| 174 | + | g_settings.setInteger(Config::CUSTOM_BORDER_ID, GetCustomBorderId()); | |
| 175 | + | ||
| 176 | + | // Update status text | |
| 177 | + | if (IsAutomagicEnabled()) { | |
| 178 | + | g_gui.SetStatusText("Automagic enabled."); | |
| 179 | + | } else { | |
| 180 | + | g_gui.SetStatusText("Automagic disabled."); | |
| 181 | + | } | |
| 182 | + | ||
| 183 | + | EndModal(wxID_OK); | |
| 184 | + | } | |
| 185 | + | ||
| 186 | + | void AutomagicSettingsDialog::OnClickCancel(wxCommandEvent& event) { | |
| 187 | + | // Don't save settings | |
| 188 | + | EndModal(wxID_CANCEL); | |
| 189 | + | } | |
| 190 | + | ||
| 191 | + | void AutomagicSettingsDialog::OnAutomagicCheck(wxCommandEvent& event) { | |
| 192 | + | // Get the checkbox that triggered the event | |
| 193 | + | wxCheckBox* checkbox = dynamic_cast<wxCheckBox*>(event.GetEventObject()); | |
| 194 | + | ||
| 195 | + | // Update all checkbox states based on automagic checkbox | |
| 196 | + | bool automagic_enabled = automagic_enabled_checkbox->GetValue(); | |
| 197 | + | ||
| 198 | + | // If this is the automagic checkbox, update all dependent checkboxes | |
| 199 | + | if (checkbox == automagic_enabled_checkbox) { | |
| 200 | + | same_ground_type_checkbox->Enable(automagic_enabled); | |
| 201 | + | walls_repel_borders_checkbox->Enable(automagic_enabled); | |
| 202 | + | layer_carpets_checkbox->Enable(automagic_enabled); | |
| 203 | + | borderize_delete_checkbox->Enable(automagic_enabled); | |
| 204 | + | custom_border_checkbox->Enable(automagic_enabled); | |
| 205 | + | } | |
| 206 | + | ||
| 207 | + | // Always update the custom border fields based on both checkboxes | |
| 208 | + | bool custom_border_enabled = custom_border_checkbox->GetValue() && automagic_enabled; | |
| 209 | + | custom_border_id_label->Enable(custom_border_enabled); | |
| 210 | + | custom_border_id_field->Enable(custom_border_enabled); | |
| 211 | + | } | |
| 212 | + | ||
| 213 | + | void AutomagicSettingsDialog::OnClose(wxCloseEvent& event) { | |
| 214 | + | // Same as cancel | |
| 215 | + | EndModal(wxID_CANCEL); | |
| 216 | + | } |
| @@ -0,0 +1,53 @@ | |||
| 1 | + | #ifndef RME_AUTOMAGIC_SETTINGS_H_ | |
| 2 | + | #define RME_AUTOMAGIC_SETTINGS_H_ | |
| 3 | + | ||
| 4 | + | #include <wx/dialog.h> | |
| 5 | + | #include <wx/checkbox.h> | |
| 6 | + | #include <wx/button.h> | |
| 7 | + | #include <wx/sizer.h> | |
| 8 | + | #include <wx/stattext.h> | |
| 9 | + | #include <wx/statbox.h> | |
| 10 | + | #include <wx/spinctrl.h> | |
| 11 | + | ||
| 12 | + | class AutomagicSettingsDialog : public wxDialog { | |
| 13 | + | public: | |
| 14 | + | AutomagicSettingsDialog(wxWindow* parent); | |
| 15 | + | virtual ~AutomagicSettingsDialog(); | |
| 16 | + | ||
| 17 | + | // Get the current settings from the dialog | |
| 18 | + | bool IsAutomagicEnabled() const; | |
| 19 | + | bool IsSameGroundTypeBorderEnabled() const; | |
| 20 | + | bool IsWallsRepelBordersEnabled() const; | |
| 21 | + | bool IsLayerCarpetsEnabled() const; | |
| 22 | + | bool IsBorderizeDeleteEnabled() const; | |
| 23 | + | // New custom border methods | |
| 24 | + | bool IsCustomBorderEnabled() const; | |
| 25 | + | int GetCustomBorderId() const; | |
| 26 | + | ||
| 27 | + | protected: | |
| 28 | + | // Event handlers | |
| 29 | + | void OnClickOK(wxCommandEvent& event); | |
| 30 | + | void OnClickCancel(wxCommandEvent& event); | |
| 31 | + | void OnAutomagicCheck(wxCommandEvent& event); | |
| 32 | + | void OnSameGroundTypeCheck(wxCommandEvent& event); | |
| 33 | + | void OnClose(wxCloseEvent& event); | |
| 34 | + | void OnCustomBorderCheck(wxCommandEvent& event); // New event handler | |
| 35 | + | ||
| 36 | + | // UI elements | |
| 37 | + | wxCheckBox* automagic_enabled_checkbox; | |
| 38 | + | wxCheckBox* same_ground_type_checkbox; | |
| 39 | + | wxCheckBox* walls_repel_borders_checkbox; | |
| 40 | + | wxCheckBox* layer_carpets_checkbox; | |
| 41 | + | wxCheckBox* borderize_delete_checkbox; | |
| 42 | + | // New UI elements for custom border | |
| 43 | + | wxCheckBox* custom_border_checkbox; | |
| 44 | + | wxSpinCtrl* custom_border_id_field; | |
| 45 | + | wxStaticText* custom_border_id_label; | |
| 46 | + | wxStdDialogButtonSizer* buttons_sizer; | |
| 47 | + | wxButton* ok_button; | |
| 48 | + | wxButton* cancel_button; | |
| 49 | + | ||
| 50 | + | DECLARE_EVENT_TABLE() | |
| 51 | + | }; | |
| 52 | + | ||
| 53 | + | #endif // RME_AUTOMAGIC_SETTINGS_H_ |
| @@ -0,0 +1,306 @@ | |||
| 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 "tile.h" | |
| 21 | + | #include "basemap.h" | |
| 22 | + | ||
| 23 | + | BaseMap::BaseMap() : | |
| 24 | + | allocator(), | |
| 25 | + | tilecount(0), | |
| 26 | + | root(*this) { | |
| 27 | + | //// | |
| 28 | + | } | |
| 29 | + | ||
| 30 | + | BaseMap::~BaseMap() { | |
| 31 | + | //// | |
| 32 | + | } | |
| 33 | + | ||
| 34 | + | void BaseMap::clear(bool del) { | |
| 35 | + | PositionVector pos_vec; | |
| 36 | + | for (MapIterator map_iter = begin(); map_iter != end(); ++map_iter) { | |
| 37 | + | Tile* t = (*map_iter)->get(); | |
| 38 | + | pos_vec.push_back(t->getPosition()); | |
| 39 | + | } | |
| 40 | + | for (PositionVector::iterator pos_iter = pos_vec.begin(); pos_iter != pos_vec.end(); ++pos_iter) { | |
| 41 | + | setTile(*pos_iter, nullptr, del); | |
| 42 | + | } | |
| 43 | + | } | |
| 44 | + | ||
| 45 | + | void BaseMap::clearVisible(uint32_t mask) { | |
| 46 | + | root.clearVisible(mask); | |
| 47 | + | } | |
| 48 | + | ||
| 49 | + | Tile* BaseMap::createTile(int x, int y, int z) { | |
| 50 | + | ASSERT(z < MAP_LAYERS); | |
| 51 | + | QTreeNode* leaf = root.getLeafForce(x, y); | |
| 52 | + | TileLocation* loc = leaf->createTile(x, y, z); | |
| 53 | + | if (loc->get()) { | |
| 54 | + | return loc->get(); | |
| 55 | + | } | |
| 56 | + | Tile* t = allocator(loc); | |
| 57 | + | leaf->setTile(x, y, z, t); | |
| 58 | + | return t; | |
| 59 | + | } | |
| 60 | + | ||
| 61 | + | Tile* BaseMap::getOrCreateTile(const Position& pos) { | |
| 62 | + | if (Tile* t = getTile(pos)) { | |
| 63 | + | return t; | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | Tile* newTile = createTile(pos.x, pos.y, pos.z); | |
| 67 | + | newTile->setLocation(createTileL(pos)); | |
| 68 | + | return newTile; | |
| 69 | + | } | |
| 70 | + | ||
| 71 | + | TileLocation* BaseMap::getTileL(int x, int y, int z) { | |
| 72 | + | ASSERT(z < MAP_LAYERS); | |
| 73 | + | QTreeNode* leaf = root.getLeaf(x, y); | |
| 74 | + | if (leaf) { | |
| 75 | + | Floor* floor = leaf->getFloor(z); | |
| 76 | + | if (floor) { | |
| 77 | + | return &floor->locs[(x & 3) * 4 + (y & 3)]; | |
| 78 | + | } | |
| 79 | + | } | |
| 80 | + | ||
| 81 | + | return nullptr; | |
| 82 | + | } | |
| 83 | + | ||
| 84 | + | const TileLocation* BaseMap::getTileL(int x, int y, int z) const { | |
| 85 | + | // Don't create static const maps! | |
| 86 | + | BaseMap* self = const_cast<BaseMap*>(this); | |
| 87 | + | return self->getTileL(x, y, z); | |
| 88 | + | } | |
| 89 | + | ||
| 90 | + | TileLocation* BaseMap::getTileL(const Position& pos) { | |
| 91 | + | return getTileL(pos.x, pos.y, pos.z); | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | const TileLocation* BaseMap::getTileL(const Position& pos) const { | |
| 95 | + | return getTileL(pos.x, pos.y, pos.z); | |
| 96 | + | } | |
| 97 | + | ||
| 98 | + | TileLocation* BaseMap::createTileL(int x, int y, int z) { | |
| 99 | + | ASSERT(z < MAP_LAYERS); | |
| 100 | + | ||
| 101 | + | QTreeNode* leaf = root.getLeafForce(x, y); | |
| 102 | + | Floor* floor = leaf->createFloor(x, y, z); | |
| 103 | + | uint32_t offsetX = x & 3; | |
| 104 | + | uint32_t offsetY = y & 3; | |
| 105 | + | ||
| 106 | + | return &floor->locs[offsetX * 4 + offsetY]; | |
| 107 | + | } | |
| 108 | + | ||
| 109 | + | TileLocation* BaseMap::createTileL(const Position& pos) { | |
| 110 | + | return createTileL(pos.x, pos.y, pos.z); | |
| 111 | + | } | |
| 112 | + | ||
| 113 | + | void BaseMap::setTile(int x, int y, int z, Tile* newtile, bool remove) { | |
| 114 | + | ASSERT(!newtile || newtile->getX() == int(x)); | |
| 115 | + | ASSERT(!newtile || newtile->getY() == int(y)); | |
| 116 | + | ASSERT(!newtile || newtile->getZ() == int(z)); | |
| 117 | + | ||
| 118 | + | QTreeNode* leaf = root.getLeafForce(x, y); | |
| 119 | + | Tile* old = leaf->setTile(x, y, z, newtile); | |
| 120 | + | if (remove) { | |
| 121 | + | delete old; | |
| 122 | + | } | |
| 123 | + | } | |
| 124 | + | ||
| 125 | + | Tile* BaseMap::swapTile(int x, int y, int z, Tile* newtile) { | |
| 126 | + | ASSERT(z < MAP_LAYERS); | |
| 127 | + | ASSERT(!newtile || newtile->getX() == int(x)); | |
| 128 | + | ASSERT(!newtile || newtile->getY() == int(y)); | |
| 129 | + | ASSERT(!newtile || newtile->getZ() == int(z)); | |
| 130 | + | ||
| 131 | + | QTreeNode* leaf = root.getLeafForce(x, y); | |
| 132 | + | return leaf->setTile(x, y, z, newtile); | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | // Iterators | |
| 136 | + | ||
| 137 | + | MapIterator::MapIterator(BaseMap* _map) : | |
| 138 | + | local_i(0), | |
| 139 | + | local_z(0), | |
| 140 | + | current_tile(nullptr), | |
| 141 | + | map(_map) { | |
| 142 | + | //// | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | MapIterator::~MapIterator() { | |
| 146 | + | //// | |
| 147 | + | } | |
| 148 | + | ||
| 149 | + | MapIterator::MapIterator(const MapIterator& other) { | |
| 150 | + | for (std::vector<MapIterator::NodeIndex>::const_iterator it = other.nodestack.begin(); it != other.nodestack.end(); it++) { | |
| 151 | + | nodestack.push_back(MapIterator::NodeIndex(*it)); | |
| 152 | + | } | |
| 153 | + | local_i = other.local_i; | |
| 154 | + | local_z = other.local_z; | |
| 155 | + | map = other.map; | |
| 156 | + | current_tile = other.current_tile; | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | MapIterator BaseMap::begin() { | |
| 160 | + | MapIterator it(this); | |
| 161 | + | it.nodestack.push_back(MapIterator::NodeIndex(&root)); | |
| 162 | + | ||
| 163 | + | while (true) { | |
| 164 | + | MapIterator::NodeIndex& current = it.nodestack.back(); | |
| 165 | + | QTreeNode* node = current.node; | |
| 166 | + | int& index = current.index; | |
| 167 | + | // printf("Contemplating %p of %p (stack size %d)\n", node, this, it.nodestack.size()); | |
| 168 | + | ||
| 169 | + | bool unwind = false; | |
| 170 | + | for (; index < MAP_LAYERS; ++index) { | |
| 171 | + | // printf("\tChecking index %d of %p\n", index, node); | |
| 172 | + | if (QTreeNode* child = node->child[index]) { | |
| 173 | + | if (child->isLeaf) { | |
| 174 | + | QTreeNode* leaf = child; | |
| 175 | + | // printf("\t%p is leaf\n", child); | |
| 176 | + | for (it.local_z = 0; it.local_z < MAP_LAYERS; ++it.local_z) { | |
| 177 | + | if (Floor* floor = leaf->array[it.local_z]) { | |
| 178 | + | for (it.local_i = 0; it.local_i < MAP_LAYERS; ++it.local_i) { | |
| 179 | + | // printf("\tit(%d;%d;%d)\n", it.local_x, it.local_y, it.local_z); | |
| 180 | + | TileLocation& t = floor->locs[it.local_i]; | |
| 181 | + | if (t.get()) { | |
| 182 | + | // printf("return it\n"); | |
| 183 | + | it.current_tile = &t; | |
| 184 | + | return it; | |
| 185 | + | } | |
| 186 | + | } | |
| 187 | + | } | |
| 188 | + | } | |
| 189 | + | } else { | |
| 190 | + | // printf("\tAdding %p\n", child); | |
| 191 | + | ++index; | |
| 192 | + | it.nodestack.push_back(MapIterator::NodeIndex(child)); | |
| 193 | + | unwind = true; | |
| 194 | + | break; | |
| 195 | + | } | |
| 196 | + | } | |
| 197 | + | } | |
| 198 | + | if (unwind) { | |
| 199 | + | continue; | |
| 200 | + | } | |
| 201 | + | ||
| 202 | + | // printf("Discarding dead node %p\n", node); | |
| 203 | + | it.nodestack.pop_back(); | |
| 204 | + | if (it.nodestack.empty()) { | |
| 205 | + | break; | |
| 206 | + | } | |
| 207 | + | } | |
| 208 | + | return end(); | |
| 209 | + | } | |
| 210 | + | ||
| 211 | + | MapIterator BaseMap::end() { | |
| 212 | + | MapIterator it(this); | |
| 213 | + | it.local_i = -1; | |
| 214 | + | it.local_z = -1; | |
| 215 | + | return it; | |
| 216 | + | } | |
| 217 | + | ||
| 218 | + | TileLocation* MapIterator::operator*() { | |
| 219 | + | return current_tile; | |
| 220 | + | } | |
| 221 | + | ||
| 222 | + | TileLocation* MapIterator::operator->() { | |
| 223 | + | return current_tile; | |
| 224 | + | } | |
| 225 | + | ||
| 226 | + | MapIterator& MapIterator::operator++() { | |
| 227 | + | // printf("MapIterator::operator++"); | |
| 228 | + | bool increased = false; | |
| 229 | + | bool first = true; | |
| 230 | + | while (true) { | |
| 231 | + | MapIterator::NodeIndex& current = nodestack.back(); | |
| 232 | + | QTreeNode* node = current.node; | |
| 233 | + | int& index = current.index; | |
| 234 | + | // printf("Contemplating %p (stack size %d)\n", node, nodestack.size()); | |
| 235 | + | ||
| 236 | + | bool unwind = false; | |
| 237 | + | for (; index < MAP_LAYERS; ++index) { | |
| 238 | + | // printf("\tChecking index %d of %p\n", index, node); | |
| 239 | + | if (QTreeNode* child = node->child[index]) { | |
| 240 | + | if (child->isLeaf) { | |
| 241 | + | QTreeNode* leaf = child; | |
| 242 | + | // printf("\t%p is leaf\n", child); | |
| 243 | + | for (; local_z < MAP_LAYERS; ++local_z) { | |
| 244 | + | // printf("\t\tIterating over Z:%d of %p", local_z, child); | |
| 245 | + | if (Floor* floor = leaf->array[local_z]) { | |
| 246 | + | // printf("\n"); | |
| 247 | + | for (; local_i < MAP_LAYERS; ++local_i) { | |
| 248 | + | // printf("\t\tIterating over Y:%d of %p\n", local_y, child); | |
| 249 | + | TileLocation& t = floor->locs[local_i]; | |
| 250 | + | if (t.get()) { | |
| 251 | + | if (increased) { | |
| 252 | + | // printf("Modified %p to %p\n", current_tile, t); | |
| 253 | + | current_tile = &t; | |
| 254 | + | return *this; | |
| 255 | + | } else { | |
| 256 | + | increased = true; | |
| 257 | + | } | |
| 258 | + | } else if (first) { | |
| 259 | + | increased = true; | |
| 260 | + | first = false; | |
| 261 | + | } | |
| 262 | + | } | |
| 263 | + | ||
| 264 | + | if (local_i > MAP_MAX_LAYER) { | |
| 265 | + | // printf("\t\tReset local_x\n"); | |
| 266 | + | local_i = 0; | |
| 267 | + | } | |
| 268 | + | } else { | |
| 269 | + | // printf(":dead floor\n"); | |
| 270 | + | } | |
| 271 | + | } | |
| 272 | + | if (local_z == MAP_LAYERS) { | |
| 273 | + | // printf("\t\tReset local_z\n"); | |
| 274 | + | local_z = 0; | |
| 275 | + | } | |
| 276 | + | } else { | |
| 277 | + | // printf("\tAdding %p\n", child); | |
| 278 | + | ++index; | |
| 279 | + | nodestack.push_back(MapIterator::NodeIndex(child)); | |
| 280 | + | unwind = true; | |
| 281 | + | break; | |
| 282 | + | } | |
| 283 | + | } | |
| 284 | + | } | |
| 285 | + | if (unwind) { | |
| 286 | + | continue; | |
| 287 | + | } | |
| 288 | + | ||
| 289 | + | // printf("Discarding dead node %p\n", node); | |
| 290 | + | nodestack.pop_back(); | |
| 291 | + | if (nodestack.size() == 0) { | |
| 292 | + | // Set all values to "end" | |
| 293 | + | // printf("END\n"); | |
| 294 | + | local_z = -1; | |
| 295 | + | local_i = -1; | |
| 296 | + | return *this; | |
| 297 | + | } | |
| 298 | + | } | |
| 299 | + | return *this; | |
| 300 | + | } | |
| 301 | + | ||
| 302 | + | MapIterator MapIterator::operator++(int) { | |
| 303 | + | MapIterator i(*this); | |
| 304 | + | ++*this; | |
| 305 | + | return i; | |
| 306 | + | } |
| @@ -0,0 +1,173 @@ | |||
| 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 | + | #ifndef RME_BASE_MAP_H_ | |
| 19 | + | #define RME_BASE_MAP_H_ | |
| 20 | + | ||
| 21 | + | #include "main.h" | |
| 22 | + | #include "position.h" | |
| 23 | + | #include "filehandle.h" | |
| 24 | + | #include "map_allocator.h" | |
| 25 | + | #include "tile.h" | |
| 26 | + | ||
| 27 | + | // Class declarations | |
| 28 | + | class QTreeNode; | |
| 29 | + | class BaseMap; | |
| 30 | + | class MapIterator; | |
| 31 | + | class Floor; | |
| 32 | + | class QTreeNode; | |
| 33 | + | class TileLocation; | |
| 34 | + | ||
| 35 | + | class MapIterator { | |
| 36 | + | public: | |
| 37 | + | MapIterator(BaseMap* _map = nullptr); | |
| 38 | + | ~MapIterator(); | |
| 39 | + | MapIterator(const MapIterator& other); | |
| 40 | + | ||
| 41 | + | TileLocation* operator*(); | |
| 42 | + | TileLocation* operator->(); | |
| 43 | + | MapIterator& operator++(); | |
| 44 | + | MapIterator operator++(int); | |
| 45 | + | bool operator==(const MapIterator& other) const { | |
| 46 | + | if (other.local_z != local_z) { | |
| 47 | + | return false; | |
| 48 | + | } | |
| 49 | + | if (other.local_i != local_i) { | |
| 50 | + | return false; | |
| 51 | + | } | |
| 52 | + | if (other.nodestack == nodestack) { | |
| 53 | + | return true; | |
| 54 | + | } | |
| 55 | + | if (other.current_tile == current_tile) { | |
| 56 | + | return true; | |
| 57 | + | } | |
| 58 | + | return false; | |
| 59 | + | } | |
| 60 | + | bool operator!=(const MapIterator& other) const { | |
| 61 | + | return !(other == *this); | |
| 62 | + | } | |
| 63 | + | ||
| 64 | + | struct NodeIndex { | |
| 65 | + | NodeIndex(QTreeNode* _node) : | |
| 66 | + | index(0), node(_node) { } | |
| 67 | + | NodeIndex(const NodeIndex& other) : | |
| 68 | + | index(other.index), node(other.node) { } | |
| 69 | + | int index; | |
| 70 | + | QTreeNode* node; | |
| 71 | + | ||
| 72 | + | bool operator==(const NodeIndex& n) const { | |
| 73 | + | return n.node == node && n.index == index; | |
| 74 | + | } | |
| 75 | + | }; | |
| 76 | + | ||
| 77 | + | private: | |
| 78 | + | std::vector<NodeIndex> nodestack; | |
| 79 | + | int local_i, local_z; | |
| 80 | + | TileLocation* current_tile; | |
| 81 | + | BaseMap* map; | |
| 82 | + | ||
| 83 | + | friend class BaseMap; | |
| 84 | + | }; | |
| 85 | + | ||
| 86 | + | class BaseMap { | |
| 87 | + | public: | |
| 88 | + | BaseMap(); | |
| 89 | + | virtual ~BaseMap(); | |
| 90 | + | ||
| 91 | + | // This doesn't destroy the map structure, just clears it, if param is true, delete all tiles too. | |
| 92 | + | void clear(bool del = true); | |
| 93 | + | MapIterator begin(); | |
| 94 | + | MapIterator end(); | |
| 95 | + | uint64_t size() const { | |
| 96 | + | return tilecount; | |
| 97 | + | } | |
| 98 | + | ||
| 99 | + | // these functions take a position and returns a tile on the map | |
| 100 | + | Tile* createTile(int x, int y, int z); | |
| 101 | + | Tile* getTile(int x, int y, int z); | |
| 102 | + | Tile* getTile(const Position& pos); | |
| 103 | + | Tile* getOrCreateTile(const Position& pos); | |
| 104 | + | const Tile* getTile(int x, int y, int z) const; | |
| 105 | + | const Tile* getTile(const Position& pos) const; | |
| 106 | + | TileLocation* getTileL(int x, int y, int z); | |
| 107 | + | TileLocation* getTileL(const Position& pos); | |
| 108 | + | TileLocation* createTileL(int x, int y, int z); | |
| 109 | + | TileLocation* createTileL(const Position& pos); | |
| 110 | + | const TileLocation* getTileL(int x, int y, int z) const; | |
| 111 | + | const TileLocation* getTileL(const Position& pos) const; | |
| 112 | + | ||
| 113 | + | // Get a Quad Tree Leaf from the map | |
| 114 | + | QTreeNode* getLeaf(int x, int y) { | |
| 115 | + | return root.getLeaf(x, y); | |
| 116 | + | } | |
| 117 | + | QTreeNode* createLeaf(int x, int y) { | |
| 118 | + | return root.getLeafForce(x, y); | |
| 119 | + | } | |
| 120 | + | ||
| 121 | + | // Assigns a tile, it might seem pointless to provide position, but it is not, as the passed tile may be nullptr | |
| 122 | + | void setTile(int _x, int _y, int _z, Tile* newtile, bool remove = false); | |
| 123 | + | void setTile(const Position& pos, Tile* newtile, bool remove = false) { | |
| 124 | + | setTile(pos.x, pos.y, pos.z, newtile, remove); | |
| 125 | + | } | |
| 126 | + | void setTile(Tile* newtile, bool remove = false) { | |
| 127 | + | setTile(newtile->getX(), newtile->getY(), newtile->getZ(), newtile, remove); | |
| 128 | + | } | |
| 129 | + | // Replaces a tile and returns the old one | |
| 130 | + | Tile* swapTile(int _x, int _y, int _z, Tile* newtile); | |
| 131 | + | Tile* swapTile(const Position& pos, Tile* newtile) { | |
| 132 | + | return swapTile(pos.x, pos.y, pos.z, newtile); | |
| 133 | + | } | |
| 134 | + | ||
| 135 | + | // Clears the visiblity according to the mask passed | |
| 136 | + | void clearVisible(uint32_t mask); | |
| 137 | + | ||
| 138 | + | uint64_t getTileCount() const { | |
| 139 | + | return tilecount; | |
| 140 | + | } | |
| 141 | + | ||
| 142 | + | public: | |
| 143 | + | MapAllocator allocator; | |
| 144 | + | ||
| 145 | + | protected: | |
| 146 | + | uint64_t tilecount; | |
| 147 | + | ||
| 148 | + | QTreeNode root; // The Quad Tree root | |
| 149 | + | ||
| 150 | + | friend class QTreeNode; | |
| 151 | + | }; | |
| 152 | + | ||
| 153 | + | inline Tile* BaseMap::getTile(int x, int y, int z) { | |
| 154 | + | TileLocation* l = getTileL(x, y, z); | |
| 155 | + | return l ? l->get() : nullptr; | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | inline Tile* BaseMap::getTile(const Position& pos) { | |
| 159 | + | TileLocation* l = getTileL(pos); | |
| 160 | + | return l ? l->get() : nullptr; | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | inline const Tile* BaseMap::getTile(int x, int y, int z) const { | |
| 164 | + | const TileLocation* l = getTileL(x, y, z); | |
| 165 | + | return l ? l->get() : nullptr; | |
| 166 | + | } | |
| 167 | + | ||
| 168 | + | inline const Tile* BaseMap::getTile(const Position& pos) const { | |
| 169 | + | const TileLocation* l = getTileL(pos); | |
| 170 | + | return l ? l->get() : nullptr; | |
| 171 | + | } | |
| 172 | + | ||
| 173 | + | #endif |
| @@ -0,0 +1,129 @@ | |||
| 1 | + | #include "main.h" | |
| 2 | + | #include "borderize_window.h" | |
| 3 | + | #include <wx/gauge.h> | |
| 4 | + | #include <wx/stattext.h> | |
| 5 | + | #include <wx/button.h> | |
| 6 | + | #include <wx/spinctrl.h> | |
| 7 | + | #include "gui.h" | |
| 8 | + | ||
| 9 | + | BEGIN_EVENT_TABLE(BorderizeWindow, wxDialog) | |
| 10 | + | EVT_BUTTON(wxID_ANY, BorderizeWindow::OnClickNext) | |
| 11 | + | ||
| 12 | + | EVT_SPINCTRL(wxID_ANY, BorderizeWindow::OnChunkSizeChange) | |
| 13 | + | END_EVENT_TABLE() | |
| 14 | + | ||
| 15 | + | BorderizeWindow::BorderizeWindow(wxWindow* parent, Editor& editor) : | |
| 16 | + | wxDialog(parent, wxID_ANY, "Borderize Progress", wxDefaultPosition, wxDefaultSize), | |
| 17 | + | editor(editor), | |
| 18 | + | current_chunk(0), | |
| 19 | + | total_chunks(0), | |
| 20 | + | processing_whole_map(false) | |
| 21 | + | { | |
| 22 | + | wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); | |
| 23 | + | ||
| 24 | + | // Add chunk size control | |
| 25 | + | wxBoxSizer* chunk_sizer = new wxBoxSizer(wxHORIZONTAL); | |
| 26 | + | chunk_sizer->Add(new wxStaticText(this, wxID_ANY, "Tiles per chunk:"), 0, wxALIGN_CENTER_VERTICAL | wxALL, 5); | |
| 27 | + | chunk_size_spin = new wxSpinCtrl(this, wxID_ANY, "500", wxDefaultPosition, wxDefaultSize, | |
| 28 | + | wxSP_ARROW_KEYS, 100, 10000, 500); | |
| 29 | + | chunk_sizer->Add(chunk_size_spin, 0, wxALL, 5); | |
| 30 | + | sizer->Add(chunk_sizer); | |
| 31 | + | ||
| 32 | + | status_text = new wxStaticText(this, wxID_ANY, "Preparing to borderize..."); | |
| 33 | + | sizer->Add(status_text, 0, wxALL, 5); | |
| 34 | + | ||
| 35 | + | progress = new wxGauge(this, wxID_ANY, 100, wxDefaultPosition, wxSize(300, 20)); | |
| 36 | + | sizer->Add(progress, 0, wxEXPAND | wxALL, 5); | |
| 37 | + | ||
| 38 | + | wxBoxSizer* button_sizer = new wxBoxSizer(wxHORIZONTAL); | |
| 39 | + | next_button = new wxButton(this, wxID_ANY, "Process Next Chunk"); | |
| 40 | + | ||
| 41 | + | ||
| 42 | + | button_sizer->Add(next_button, 1, wxALL, 5); | |
| 43 | + | button_sizer->Add(cancel_button, 1, wxALL, 5); | |
| 44 | + | sizer->Add(button_sizer, 0, wxEXPAND | wxALL, 5); | |
| 45 | + | ||
| 46 | + | SetSizer(sizer); | |
| 47 | + | Fit(); | |
| 48 | + | Centre(); | |
| 49 | + | ||
| 50 | + | Bind(wxEVT_CLOSE_WINDOW, [this](wxCloseEvent& event) { | |
| 51 | + | // Clear remaining tiles and close without processing | |
| 52 | + | remaining_tiles.clear(); | |
| 53 | + | EndModal(0); | |
| 54 | + | }); | |
| 55 | + | } | |
| 56 | + | ||
| 57 | + | BorderizeWindow::~BorderizeWindow() {} | |
| 58 | + | ||
| 59 | + | void BorderizeWindow::Start() { | |
| 60 | + | // Initialize the tile list based on whether we're processing selection or whole map | |
| 61 | + | if (editor.selection.size() > 0) { | |
| 62 | + | processing_whole_map = false; | |
| 63 | + | for (Tile* tile : editor.selection) { | |
| 64 | + | remaining_tiles.push_back(tile); | |
| 65 | + | } | |
| 66 | + | } else { | |
| 67 | + | processing_whole_map = true; | |
| 68 | + | for (TileLocation* tileLocation : editor.map) { | |
| 69 | + | if (Tile* tile = tileLocation->get()) { | |
| 70 | + | remaining_tiles.push_back(tile); | |
| 71 | + | } | |
| 72 | + | } | |
| 73 | + | } | |
| 74 | + | ||
| 75 | + | total_chunks = (remaining_tiles.size() + 499) / 500; // Process 500 tiles per chunk | |
| 76 | + | UpdateProgress(0, total_chunks); | |
| 77 | + | ShowModal(); | |
| 78 | + | } | |
| 79 | + | ||
| 80 | + | void BorderizeWindow::UpdateProgress(int current, int total) { | |
| 81 | + | progress->SetValue((current * 100) / total); | |
| 82 | + | status_text->SetLabel(wxString::Format("Processing chunk %d of %d (%d tiles remaining)", | |
| 83 | + | current + 1, total, (int)remaining_tiles.size())); | |
| 84 | + | } | |
| 85 | + | ||
| 86 | + | void BorderizeWindow::OnChunkSizeChange(wxSpinEvent& event) { | |
| 87 | + | // Recalculate total chunks based on new chunk size | |
| 88 | + | if (!remaining_tiles.empty()) { | |
| 89 | + | total_chunks = (remaining_tiles.size() + chunk_size_spin->GetValue() - 1) / chunk_size_spin->GetValue(); | |
| 90 | + | UpdateProgress(current_chunk, total_chunks); | |
| 91 | + | } | |
| 92 | + | } | |
| 93 | + | ||
| 94 | + | void BorderizeWindow::OnClickNext(wxCommandEvent& WXUNUSED(event)) { | |
| 95 | + | if (remaining_tiles.empty()) { | |
| 96 | + | EndModal(1); | |
| 97 | + | return; | |
| 98 | + | } | |
| 99 | + | ||
| 100 | + | // Process next chunk of tiles using the user-defined chunk size | |
| 101 | + | Action* action = editor.actionQueue->createAction(ACTION_BORDERIZE); | |
| 102 | + | ||
| 103 | + | size_t chunk_size = chunk_size_spin->GetValue(); | |
| 104 | + | size_t tiles_to_process = std::min<size_t>(chunk_size, remaining_tiles.size()); | |
| 105 | + | ||
| 106 | + | for (size_t i = 0; i < tiles_to_process; ++i) { | |
| 107 | + | Tile* tile = remaining_tiles.front(); | |
| 108 | + | remaining_tiles.pop_front(); | |
| 109 | + | ||
| 110 | + | Tile* newTile = tile->deepCopy(editor.map); | |
| 111 | + | newTile->borderize(&editor.map); | |
| 112 | + | if (!processing_whole_map) { | |
| 113 | + | newTile->select(); | |
| 114 | + | } | |
| 115 | + | action->addChange(newd Change(newTile)); | |
| 116 | + | } | |
| 117 | + | ||
| 118 | + | editor.addAction(action); | |
| 119 | + | current_chunk++; | |
| 120 | + | UpdateProgress(current_chunk, total_chunks); | |
| 121 | + | ||
| 122 | + | if (remaining_tiles.empty()) { | |
| 123 | + | next_button->SetLabel("Finish"); | |
| 124 | + | } | |
| 125 | + | ||
| 126 | + | editor.map.doChange(); | |
| 127 | + | // Refresh the view after each chunk | |
| 128 | + | g_gui.RefreshView(); | |
| 129 | + | } |
| @@ -0,0 +1,39 @@ | |||
| 1 | + | #ifndef RME_BORDERIZE_WINDOW_H | |
| 2 | + | #define RME_BORDERIZE_WINDOW_H | |
| 3 | + | ||
| 4 | + | #include <wx/dialog.h> | |
| 5 | + | #include "editor.h" | |
| 6 | + | ||
| 7 | + | class wxGauge; | |
| 8 | + | class wxStaticText; | |
| 9 | + | class wxSpinCtrl; | |
| 10 | + | ||
| 11 | + | class BorderizeWindow : public wxDialog { | |
| 12 | + | public: | |
| 13 | + | BorderizeWindow(wxWindow* parent, Editor& editor); | |
| 14 | + | ~BorderizeWindow(); | |
| 15 | + | ||
| 16 | + | void Start(); | |
| 17 | + | void UpdateProgress(int current, int total); | |
| 18 | + | ||
| 19 | + | private: | |
| 20 | + | void OnClickNext(wxCommandEvent& event); | |
| 21 | + | void OnClickCancel(wxCommandEvent& event); | |
| 22 | + | void OnChunkSizeChange(wxSpinEvent& event); | |
| 23 | + | ||
| 24 | + | Editor& editor; | |
| 25 | + | wxGauge* progress; | |
| 26 | + | wxStaticText* status_text; | |
| 27 | + | wxButton* next_button; | |
| 28 | + | wxButton* cancel_button; | |
| 29 | + | wxSpinCtrl* chunk_size_spin; | |
| 30 | + | ||
| 31 | + | size_t current_chunk; | |
| 32 | + | size_t total_chunks; | |
| 33 | + | bool processing_whole_map; | |
| 34 | + | TileList remaining_tiles; | |
| 35 | + | ||
| 36 | + | DECLARE_EVENT_TABLE() | |
| 37 | + | }; | |
| 38 | + | ||
| 39 | + | #endif |