Index: source/lib/sysdep/os/osx/osx_atlas.h =================================================================== --- /dev/null +++ source/lib/sysdep/os/osx/osx_atlas.h @@ -0,0 +1,33 @@ +/* Copyright (C) 2020 Wildfire Games. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef OSX_ATLAS_H +#define OSX_ATLAS_H + + +/** + * Runs a new pyrogenesis process with the -editor argument. + * Necessary because SDL and WxWidgets conflict. + */ +void startNewAtlasProcess(); + +#endif // OSX_ATLAS_H Index: source/lib/sysdep/os/osx/osx_atlas.mm =================================================================== --- /dev/null +++ source/lib/sysdep/os/osx/osx_atlas.mm @@ -0,0 +1,53 @@ +/* Copyright (C) 2020 Wildfire Games. + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#import // MAC_OS_X_VERSION_MIN_REQUIRED +#include + +#import "osx_atlas.h" + +#include +#include "lib/types.h" +#include "ps/CStr.h" + +extern std::vector g_modsLoaded; + +void startNewAtlasProcess() +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + NSMutableArray *args = [[NSMutableArray alloc] init]; + [args addObject:@"--editor"]; + + // Pass mods on the command line. + for (const CStr& mod : g_modsLoaded) + { + std::string arg = std::string("-mod=") + mod; + [args addObject:[[NSString alloc] initWithUTF8String:arg.c_str()]]; + } + + NSDictionary *params = @{ NSWorkspaceLaunchConfigurationArguments: args }; + + [[NSWorkspace sharedWorkspace] launchApplicationAtURL:[[NSRunningApplication currentApplication] executableURL] options:NSWorkspaceLaunchNewInstance configuration:params error:nil]; + + [pool drain]; +} Index: source/main.cpp =================================================================== --- source/main.cpp +++ source/main.cpp @@ -85,6 +85,10 @@ #include // geteuid #endif // OS_UNIX +#if OS_MACOSX +#include "lib/sysdep/os/osx/osx_atlas.h" +#endif + #if MSC_VERSION #include #define getpid _getpid // Use the non-deprecated function name @@ -649,8 +653,13 @@ } while (g_Shutdown == ShutdownType::Restart); +#if OS_MACOSX + if (g_Shutdown == ShutdownType::RestartAsAtlas) + startNewAtlasProcess(); +#else if (g_Shutdown == ShutdownType::RestartAsAtlas) ATLAS_RunIfOnCmdLine(args, true); +#endif CXeromyces::Terminate(); } Index: source/ps/GameSetup/HWDetect.cpp =================================================================== --- source/ps/GameSetup/HWDetect.cpp +++ source/ps/GameSetup/HWDetect.cpp @@ -309,7 +309,8 @@ snprintf(version, ARRAY_SIZE(version), "%d.%d.%d", runtime.major, runtime.minor, runtime.patch); scriptInterface.SetProperty(settings, "sdl_runtime_version", version); - const char* backend = GetSDLSubsystem(g_VideoMode.GetWindow()); + // This is null in atlas (and further the call triggers an assertion). + const char* backend = g_VideoMode.GetWindow() ? GetSDLSubsystem(g_VideoMode.GetWindow()) : "none"; scriptInterface.SetProperty(settings, "sdl_video_backend", backend ? backend : "unknown"); } Index: source/tools/atlas/AtlasUI/Misc/DLLInterface.cpp =================================================================== --- source/tools/atlas/AtlasUI/Misc/DLLInterface.cpp +++ source/tools/atlas/AtlasUI/Misc/DLLInterface.cpp @@ -180,13 +180,6 @@ { public: -#ifdef __WXOSX__ - virtual bool OSXIsGUIApplication() - { - return false; - } -#endif - virtual bool OnInit() { // _CrtSetBreakAlloc(5632); Index: source/tools/atlas/AtlasUI/Misc/KeyMap.cpp =================================================================== --- source/tools/atlas/AtlasUI/Misc/KeyMap.cpp +++ source/tools/atlas/AtlasUI/Misc/KeyMap.cpp @@ -77,6 +77,8 @@ case WXK_F14: return SDLK_F14; case WXK_F15: return SDLK_F15; + case WXK_BACK: return SDLK_BACKSPACE; + case WXK_DELETE: return SDLK_DELETE; case WXK_NUMLOCK: return SDLK_NUMLOCKCLEAR; case WXK_SCROLL: return SDLK_SCROLLLOCK; // case WXK_: return SDLK_CAPSLOCK; Index: source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp +++ source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp @@ -132,9 +132,7 @@ if (KeyScroll(evt, true)) return; - // Slight hack: Only pass 'special' keys; normal keys will generate a translated Char event instead - if (evt.GetKeyCode() >= 256) - POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), true)); + POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), true)); evt.Skip(); } @@ -147,9 +145,7 @@ if (KeyScroll(evt, false)) return; - // Slight hack: Only pass 'special' keys; normal keys will generate a translated Char event instead - if (evt.GetKeyCode() >= 256) - POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), false)); + POST_MESSAGE(GuiKeyEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey(), false)); evt.Skip(); } @@ -189,8 +185,8 @@ } else { - // Slight hack: Only pass 'normal' keys; special keys will generate a KeyDown/KeyUp event instead - if (evt.GetKeyCode() < 256) + // Slight hack: Only pass 'alphanumeric' keys; special keys will generate a KeyDown/KeyUp event instead + if (evt.GetKeyCode() >= 33 && evt.GetKeyCode() <= 126) POST_MESSAGE(GuiCharEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey())); evt.Skip(); @@ -315,14 +311,14 @@ { ID_Quit = 1, - ID_New, + ID_New, ID_Open, ID_Save, ID_SaveAs, ID_ImportHeightmap, - ID_Copy, - ID_Paste, + ID_Copy, + ID_Paste, ID_Wireframe, ID_MessageTrace, @@ -345,7 +341,7 @@ EVT_CLOSE(ScenarioEditor::OnClose) EVT_TIMER(wxID_ANY, ScenarioEditor::OnTimer) - EVT_MENU(ID_New, ScenarioEditor::OnNew) + EVT_MENU(ID_New, ScenarioEditor::OnNew) EVT_MENU(ID_Open, ScenarioEditor::OnOpen) EVT_MENU(ID_Save, ScenarioEditor::OnSave) EVT_MENU(ID_SaveAs, ScenarioEditor::OnSaveAs) @@ -355,8 +351,8 @@ EVT_MENU(ID_Quit, ScenarioEditor::OnQuit) EVT_MENU(wxID_UNDO, ScenarioEditor::OnUndo) EVT_MENU(wxID_REDO, ScenarioEditor::OnRedo) - EVT_MENU(ID_Copy, ScenarioEditor::OnCopy) - EVT_MENU(ID_Paste, ScenarioEditor::OnPaste) + EVT_MENU(ID_Copy, ScenarioEditor::OnCopy) + EVT_MENU(ID_Paste, ScenarioEditor::OnPaste) EVT_MENU(ID_Wireframe, ScenarioEditor::OnWireframe) EVT_MENU(ID_MessageTrace, ScenarioEditor::OnMessageTrace) @@ -439,7 +435,7 @@ wxMenu *menuFile = new wxMenu; menuBar->Append(menuFile, _("&File")); { - menuFile->Append(ID_New, _("&New...")); + menuFile->Append(ID_New, _("&New...")); menuFile->Append(ID_Open, _("&Open...")); menuFile->Append(ID_Save, _("&Save")); menuFile->Append(ID_SaveAs, _("Save &As...")); @@ -459,14 +455,14 @@ { menuEdit->Append(wxID_UNDO, _("&Undo")); menuEdit->Append(wxID_REDO, _("&Redo")); - menuEdit->AppendSeparator(); - menuEdit->Append(ID_Copy, _("&Copy")); - menuEdit->Enable(ID_Copy, false); - menuEdit->Append(ID_Paste, _("&Paste")); - menuEdit->Enable(ID_Paste, false); + menuEdit->AppendSeparator(); + menuEdit->Append(ID_Copy, _("&Copy")); + menuEdit->Enable(ID_Copy, false); + menuEdit->Append(ID_Paste, _("&Paste")); + menuEdit->Enable(ID_Paste, false); } - g_SelectedObjects.RegisterObserver(0, &ScenarioEditor::OnSelectedObjectsChange, this); + g_SelectedObjects.RegisterObserver(0, &ScenarioEditor::OnSelectedObjectsChange, this); GetCommandProc().SetEditMenu(menuEdit); GetCommandProc().Initialize(); @@ -636,13 +632,13 @@ void ScenarioEditor::OnClose(wxCloseEvent& event) { - if (event.CanVeto() && GetCommandProc().IsDirty()) - { - if (wxMessageBox(_T("You have unsaved changes. Are you sure you want to quit?"), _T("Discard unsaved changes?"), wxICON_QUESTION | wxYES_NO) != wxYES) - { - event.Veto(); - return; - } + if (event.CanVeto() && GetCommandProc().IsDirty()) + { + if (wxMessageBox(_T("You have unsaved changes. Are you sure you want to quit?"), _T("Discard unsaved changes?"), wxICON_QUESTION | wxYES_NO) != wxYES) + { + event.Veto(); + return; + } } m_ToolManager.SetCurrentTool(_T("")); @@ -698,16 +694,16 @@ void ScenarioEditor::OnCopy(wxCommandEvent& WXUNUSED(event)) { - if (GetToolManager().GetCurrentToolName() == _T("TransformObject")) - GetToolManager().GetCurrentTool()->OnCommand(_T("copy"), NULL); + if (GetToolManager().GetCurrentToolName() == _T("TransformObject")) + GetToolManager().GetCurrentTool()->OnCommand(_T("copy"), NULL); } void ScenarioEditor::OnPaste(wxCommandEvent& WXUNUSED(event)) { - if (GetToolManager().GetCurrentToolName() != _T("TransformObject")) - GetToolManager().SetCurrentTool(_T("TransformObject"), NULL); + if (GetToolManager().GetCurrentToolName() != _T("TransformObject")) + GetToolManager().SetCurrentTool(_T("TransformObject"), NULL); - GetToolManager().GetCurrentTool()->OnCommand(_T("paste"), NULL); + GetToolManager().GetCurrentTool()->OnCommand(_T("paste"), NULL); } ////////////////////////////////////////////////////////////////////////// @@ -837,7 +833,7 @@ qPing qry; qry.Post(); - GetCommandProc().MarkAsSaved(); + GetCommandProc().MarkAsSaved(); } } @@ -861,7 +857,7 @@ qPing qry; qry.Post(); - GetCommandProc().MarkAsSaved(); + GetCommandProc().MarkAsSaved(); } } @@ -978,7 +974,7 @@ void ScenarioEditor::OnSelectedObjectsChange(const std::vector& selectedObjects) { - GetMenuBar()->Enable(ID_Copy, !selectedObjects.empty()); + GetMenuBar()->Enable(ID_Copy, !selectedObjects.empty()); } void ScenarioEditor::OnHelp(wxCommandEvent& event) @@ -996,50 +992,50 @@ void ScenarioEditor::OnMenuOpen(wxMenuEvent& event) { - // Ignore wxMSW system menu events, see https://trac.wildfiregames.com/ticket/5501 - const wxMenu* menu = event.GetMenu(); - if (!menu) - return; - - // This could be done far more elegantly if wxMenuItem had changeable id. - wxMenu* pasteMenuItem = NULL; - menu->FindItem(ID_Paste, &pasteMenuItem); - - GetMenuBar()->Enable(ID_Paste, false); - - if (!pasteMenuItem) - return; - - wxString content; - if (wxTheClipboard->Open()) - { - if (wxTheClipboard->IsSupported(wxDF_TEXT)) - { - wxTextDataObject data; - wxTheClipboard->GetData(data); - content = data.GetText(); - } - - wxTheClipboard->Close(); - } - - if (content.empty()) - return; - - wxInputStream* is = new wxStringInputStream(content); - wxXmlDocument doc; - { - wxLogNull stopComplaining; - static_cast(stopComplaining); - if (!doc.Load(*is)) - return; - } - - wxXmlNode* root = doc.GetRoot(); - if (!root || root->GetName() != wxT("Entities")) - return; - - GetMenuBar()->Enable(ID_Paste, true); + // Ignore wxMSW system menu events, see https://trac.wildfiregames.com/ticket/5501 + const wxMenu* menu = event.GetMenu(); + if (!menu) + return; + + // This could be done far more elegantly if wxMenuItem had changeable id. + wxMenu* pasteMenuItem = NULL; + menu->FindItem(ID_Paste, &pasteMenuItem); + + GetMenuBar()->Enable(ID_Paste, false); + + if (!pasteMenuItem) + return; + + wxString content; + if (wxTheClipboard->Open()) + { + if (wxTheClipboard->IsSupported(wxDF_TEXT)) + { + wxTextDataObject data; + wxTheClipboard->GetData(data); + content = data.GetText(); + } + + wxTheClipboard->Close(); + } + + if (content.empty()) + return; + + wxInputStream* is = new wxStringInputStream(content); + wxXmlDocument doc; + { + wxLogNull stopComplaining; + static_cast(stopComplaining); + if (!doc.Load(*is)) + return; + } + + wxXmlNode* root = doc.GetRoot(); + if (!root || root->GetName() != wxT("Entities")) + return; + + GetMenuBar()->Enable(ID_Paste, true); } Index: source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp =================================================================== --- source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp +++ source/tools/atlas/GameInterface/Handlers/MiscHandlers.cpp @@ -198,16 +198,20 @@ MESSAGEHANDLER(GuiCharEvent) { - // wxWidgets has special Char events but SDL doesn't, so convert it to - // a keydown+keyup sequence. (We do the conversion here instead of on - // the wx side to avoid nondeterministic behaviour caused by async messaging.) - + // Simulate special 'text input' events in the SDL + // This isn't quite compatible with WXWidget's handling, + // so to avoid trouble we only send 'letter-like' ASCII input. SDL_Event_ ev = { { 0 } }; - ev.ev.type = SDL_KEYDOWN; - ev.ev.key.keysym.sym = (SDL_Keycode)(int)msg->sdlkey; + ev.ev.type = SDL_TEXTEDITING; + ev.ev.text.type = SDL_TEXTEDITING; + ev.ev.text.text[0] = (char)msg->sdlkey; + ev.ev.text.text[1] = (char)0; in_dispatch_event(&ev); - ev.ev.type = SDL_KEYUP; + ev.ev.type = SDL_TEXTINPUT; + ev.ev.text.type = SDL_TEXTINPUT; + ev.ev.text.text[0] = (char)msg->sdlkey; + ev.ev.text.text[1] = (char)0; in_dispatch_event(&ev); }