Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp (revision 9796) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp (revision 9797) @@ -1,399 +1,399 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "Terrain.h" #include "Buttons/ToolButton.h" #include "ScenarioEditor/ScenarioEditor.h" #include "ScenarioEditor/Tools/Common/Brushes.h" #include "ScenarioEditor/Tools/Common/MiscState.h" #include "AtlasScript/ScriptInterface.h" #include "GameInterface/Messages.h" #include "wx/spinctrl.h" #include "wx/listctrl.h" #include "wx/image.h" #include "wx/imaglist.h" #include "wx/busyinfo.h" #include "wx/notebook.h" class TextureNotebook; class TerrainBottomBar : public wxPanel { public: TerrainBottomBar(ScenarioEditor& scenarioEditor, wxWindow* parent); void LoadTerrain(); private: TextureNotebook* m_Textures; }; enum { ID_Passability = 1, ID_ShowPriorities, ID_ResizeMap }; TerrainSidebar::TerrainSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) : Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer) { { ///////////////////////////////////////////////////////////////////////// // Terrain elevation wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Elevation tools")); wxSizer* gridSizer = new wxGridSizer(3); gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Modify"), _T("AlterElevation")), wxSizerFlags().Expand()); gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Smooth"), _T("SmoothElevation")), wxSizerFlags().Expand()); gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Flatten"), _T("FlattenElevation")), wxSizerFlags().Expand()); sizer->Add(gridSizer, wxSizerFlags().Expand()); m_MainSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Terrain texture wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Texture tools")); wxSizer* gridSizer = new wxGridSizer(3); gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Paint"), _T("PaintTerrain")), wxSizerFlags().Expand()); gridSizer->Add(new ToolButton(scenarioEditor.GetToolManager(), this, _("Replace"), _T("ReplaceTerrain")), wxSizerFlags().Expand()); sizer->Add(gridSizer, wxSizerFlags().Expand()); m_MainSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Brush settings wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Brush")); g_Brush_Elevation.CreateUI(this, sizer); m_MainSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } { ///////////////////////////////////////////////////////////////////////// // Visualise wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Visualise")); m_MainSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); wxSizer* visSizer = new wxFlexGridSizer(2, 2, 5, 5); sizer->Add(visSizer); wxArrayString defaultChoices; defaultChoices.Add(_("(none)")); m_PassabilityChoice = new wxChoice(this, ID_Passability, wxDefaultPosition, wxDefaultSize, defaultChoices); m_PassabilityChoice->SetSelection(0); visSizer->Add(new wxStaticText(this, wxID_ANY, _("Passability")), wxSizerFlags().Align(wxALIGN_CENTER|wxALIGN_RIGHT)); visSizer->Add(m_PassabilityChoice); visSizer->Add(new wxStaticText(this, wxID_ANY, _("Priorities")), wxSizerFlags().Align(wxALIGN_CENTER|wxALIGN_RIGHT)); visSizer->Add(new wxCheckBox(this, ID_ShowPriorities, _(""))); } { ///////////////////////////////////////////////////////////////////////// // Misc tools wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Misc tools")); sizer->Add(new wxButton(this, ID_ResizeMap, _("Resize map")), wxSizerFlags().Expand()); m_MainSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); } m_BottomBar = new TerrainBottomBar(scenarioEditor, bottomBarContainer); } void TerrainSidebar::OnFirstDisplay() { AtlasMessage::qGetTerrainPassabilityClasses qry; qry.Post(); std::vector passClasses = *qry.classNames; for (size_t i = 0; i < passClasses.size(); ++i) - m_PassabilityChoice->Append(passClasses[i]); + m_PassabilityChoice->Append(passClasses[i].c_str()); static_cast(m_BottomBar)->LoadTerrain(); } void TerrainSidebar::OnPassabilityChoice(wxCommandEvent& evt) { if (evt.GetSelection() == 0) POST_MESSAGE(SetViewParamS, (AtlasMessage::eRenderView::GAME, L"passability", L"")); else POST_MESSAGE(SetViewParamS, (AtlasMessage::eRenderView::GAME, L"passability", evt.GetString().c_str())); } void TerrainSidebar::OnShowPriorities(wxCommandEvent& evt) { POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::GAME, L"priorities", evt.IsChecked())); } void TerrainSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt)) { wxArrayString sizeNames; std::vector sizeTiles; // Load the map sizes list AtlasMessage::qGetMapSizes qrySizes; qrySizes.Post(); AtObj sizes = AtlasObject::LoadFromJSON(m_ScenarioEditor.GetScriptInterface().GetContext(), *qrySizes.sizes); for (AtIter s = sizes["Sizes"]["item"]; s.defined(); ++s) { long tiles = 0; wxString(s["Tiles"]).ToLong(&tiles); sizeNames.Add(wxString(s["Name"])); sizeTiles.push_back((size_t)tiles); } // TODO: set default based on current map size wxSingleChoiceDialog dlg(this, _("Select new map size. WARNING: This probably only works reliably on blank maps, and cannot be undone."), _("Resize map"), sizeNames); if (dlg.ShowModal() != wxID_OK) return; size_t tiles = sizeTiles.at(dlg.GetSelection()); POST_COMMAND(ResizeMap, (tiles)); } BEGIN_EVENT_TABLE(TerrainSidebar, Sidebar) EVT_CHOICE(ID_Passability, TerrainSidebar::OnPassabilityChoice) EVT_CHECKBOX(ID_ShowPriorities, TerrainSidebar::OnShowPriorities) EVT_BUTTON(ID_ResizeMap, TerrainSidebar::OnResizeMap) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// class TextureNotebookPage : public wxPanel { private: static const int imageWidth = 120; static const int imageHeight = 40; public: TextureNotebookPage(ScenarioEditor& scenarioEditor, wxWindow* parent, const wxString& name) : wxPanel(parent, wxID_ANY), m_ScenarioEditor(scenarioEditor), m_Timer(this), m_Name(name), m_Loaded(false) { m_ScrolledPanel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxVSCROLL); m_ScrolledPanel->SetScrollRate(0, 10); m_ScrolledPanel->SetBackgroundColour(wxColour(255, 255, 255)); wxSizer* sizer = new wxBoxSizer(wxVERTICAL); sizer->Add(m_ScrolledPanel, wxSizerFlags().Proportion(1).Expand()); SetSizer(sizer); m_ItemSizer = new wxGridSizer(6, 4, 0); m_ScrolledPanel->SetSizer(m_ItemSizer); } void OnDisplay() { // Trigger the terrain loading on first display if (m_Loaded) return; m_Loaded = true; wxBusyInfo busy (_("Loading terrain previews")); ReloadPreviews(); } void ReloadPreviews() { Freeze(); m_ScrolledPanel->DestroyChildren(); m_ItemSizer->Clear(); m_LastTerrainSelection = NULL; // clear any reference to deleted button AtlasMessage::qGetTerrainGroupPreviews qry(m_Name.c_str(), imageWidth, imageHeight); qry.Post(); std::vector previews = *qry.previews; bool allLoaded = true; for (size_t i = 0; i < previews.size(); ++i) { if (!previews[i].loaded) allLoaded = false; // Construct the wrapped-text label wxString name = previews[i].name.c_str(); // Add spaces into the displayed name so there are more wrapping opportunities wxString labelText = name; labelText.Replace(_T("_"), _T(" ")); wxStaticText* label = new wxStaticText(m_ScrolledPanel, wxID_ANY, labelText, wxDefaultPosition, wxDefaultSize, wxALIGN_CENTER); label->Wrap(imageWidth); unsigned char* buf = (unsigned char*)(malloc(previews[i].imageData.GetSize())); // imagedata.GetBuffer() gives a Shareable*, which // is stored the same as a unsigned char*, so we can just copy it. memcpy(buf, previews[i].imageData.GetBuffer(), previews[i].imageData.GetSize()); wxImage img (imageWidth, imageHeight, buf); wxButton* button = new wxBitmapButton(m_ScrolledPanel, wxID_ANY, wxBitmap(img)); // Store the texture name in the clientdata slot button->SetClientObject(new wxStringClientData(name)); wxSizer* imageSizer = new wxBoxSizer(wxVERTICAL); imageSizer->Add(button, wxSizerFlags().Center()); imageSizer->Add(label, wxSizerFlags().Proportion(1).Center()); m_ItemSizer->Add(imageSizer, wxSizerFlags().Expand().Center()); } m_ScrolledPanel->Fit(); Layout(); Thaw(); // If not all textures were loaded yet, run a timer to reload the previews // every so often until they've all finished if (allLoaded && m_Timer.IsRunning()) { m_Timer.Stop(); } else if (!allLoaded && !m_Timer.IsRunning()) { m_Timer.Start(2000); } } void OnButton(wxCommandEvent& evt) { wxButton* button = wxDynamicCast(evt.GetEventObject(), wxButton); wxString name = static_cast(button->GetClientObject())->GetData(); g_SelectedTexture = name; if (m_LastTerrainSelection) m_LastTerrainSelection->SetBackgroundColour(wxNullColour); button->SetBackgroundColour(wxColour(255, 255, 0)); m_LastTerrainSelection = button; // Slight hack: Default to Paint mode because that's probably what the user wanted // when they selected a terrain; unless already explicitly in Replace mode, because // then the user probably wanted that instead if (m_ScenarioEditor.GetToolManager().GetCurrentToolName() != _T("ReplaceTerrain")) m_ScenarioEditor.GetToolManager().SetCurrentTool(_T("PaintTerrain")); } void OnSize(wxSizeEvent& evt) { int numCols = std::max(1, (int)(evt.GetSize().GetWidth() / (imageWidth + 16))); m_ItemSizer->SetCols(numCols); evt.Skip(); } void OnTimer(wxTimerEvent& WXUNUSED(evt)) { ReloadPreviews(); } private: ScenarioEditor& m_ScenarioEditor; bool m_Loaded; wxTimer m_Timer; wxString m_Name; wxScrolledWindow* m_ScrolledPanel; wxGridSizer* m_ItemSizer; wxButton* m_LastTerrainSelection; // button that was last selected, so we can undo its colouring DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(TextureNotebookPage, wxPanel) EVT_BUTTON(wxID_ANY, TextureNotebookPage::OnButton) EVT_SIZE(TextureNotebookPage::OnSize) EVT_TIMER(wxID_ANY, TextureNotebookPage::OnTimer) END_EVENT_TABLE(); class TextureNotebook : public wxNotebook { public: TextureNotebook(ScenarioEditor& scenarioEditor, wxWindow *parent) : wxNotebook(parent, wxID_ANY/*, wxDefaultPosition, wxDefaultSize, wxNB_FIXEDWIDTH*/), m_ScenarioEditor(scenarioEditor) { } void LoadTerrain() { wxBusyInfo busy (_("Loading terrain groups")); DeleteAllPages(); m_TerrainGroups.Clear(); // Get the list of terrain groups from the engine AtlasMessage::qGetTerrainGroups qry; qry.Post(); std::vector groupnames = *qry.groupNames; for (std::vector::iterator it = groupnames.begin(); it != groupnames.end(); ++it) m_TerrainGroups.Add(it->c_str()); for (size_t i = 0; i < m_TerrainGroups.GetCount(); ++i) { wxString visibleName = m_TerrainGroups[i]; if (visibleName.Len()) visibleName[0] = wxToupper(visibleName[0]); AddPage(new TextureNotebookPage(m_ScenarioEditor, this, m_TerrainGroups[i]), visibleName); } } protected: void OnPageChanged(wxNotebookEvent& event) { if (event.GetSelection() >= 0 && event.GetSelection() < (int)GetPageCount()) { static_cast(GetPage(event.GetSelection()))->OnDisplay(); } event.Skip(); } private: ScenarioEditor& m_ScenarioEditor; wxArrayString m_TerrainGroups; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(TextureNotebook, wxNotebook) EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, TextureNotebook::OnPageChanged) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// TerrainBottomBar::TerrainBottomBar(ScenarioEditor& scenarioEditor, wxWindow* parent) : wxPanel(parent, wxID_ANY) { wxSizer* sizer = new wxBoxSizer(wxVERTICAL); m_Textures = new TextureNotebook(scenarioEditor, this); sizer->Add(m_Textures, wxSizerFlags().Expand().Proportion(1)); SetSizer(sizer); } void TerrainBottomBar::LoadTerrain() { m_Textures->LoadTerrain(); } Index: ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp =================================================================== --- ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp (revision 9796) +++ ps/trunk/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp (revision 9797) @@ -1,911 +1,912 @@ /* Copyright (C) 2011 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #include "precompiled.h" #include "ScenarioEditor.h" #include "wx/busyinfo.h" #include "wx/config.h" #include "wx/evtloop.h" #include "wx/ffile.h" #include "wx/filename.h" #include "wx/image.h" #include "wx/tooltip.h" #include "wx/dir.h" #include "General/AtlasEventLoop.h" #include "General/Datafile.h" #include "CustomControls/HighResTimer/HighResTimer.h" #include "CustomControls/Buttons/ToolButton.h" #include "CustomControls/Canvas/Canvas.h" #include "GameInterface/MessagePasser.h" #include "GameInterface/Messages.h" #include "AtlasScript/ScriptInterface.h" #include "Misc/KeyMap.h" #include "Tools/Common/Tools.h" #include "Tools/Common/Brushes.h" #include "Tools/Common/MiscState.h" static HighResTimer g_Timer; using namespace AtlasMessage; ////////////////////////////////////////////////////////////////////////// // GL functions exported from DLL, and called by game (in a separate // thread to the standard wx one) ATLASDLLIMPEXP void Atlas_GLSetCurrent(void* canvas) { static_cast(canvas)->SetCurrent(); } ATLASDLLIMPEXP void Atlas_GLSwapBuffers(void* canvas) { static_cast(canvas)->SwapBuffers(); } ////////////////////////////////////////////////////////////////////////// class GameCanvas : public Canvas { public: GameCanvas(ScenarioEditor& scenarioEditor, wxWindow* parent, int* attribList) : Canvas(parent, attribList, wxWANTS_CHARS), m_ScenarioEditor(scenarioEditor), m_MouseState(NONE), m_LastMouseState(NONE) { } private: bool KeyScroll(wxKeyEvent& evt, bool enable) { int dir; switch (evt.GetKeyCode()) { case 'A': case WXK_LEFT: dir = eScrollConstantDir::LEFT; break; case 'D': case WXK_RIGHT: dir = eScrollConstantDir::RIGHT; break; case 'W': case WXK_UP: dir = eScrollConstantDir::FORWARDS; break; case 'S': case WXK_DOWN: dir = eScrollConstantDir::BACKWARDS; break; case 'E': case ']': dir = eScrollConstantDir::CLOCKWISE; break; case 'Q': case '[': dir = eScrollConstantDir::ANTICLOCKWISE; break; case WXK_SHIFT: case WXK_CONTROL: dir = -1; break; default: return false; } float speed = 120.f * ScenarioEditor::GetSpeedModifier(); if (dir == -1) // changed modifier keys - update all currently-scrolling directions { if (wxGetKeyState(WXK_LEFT)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::LEFT, speed)); if (wxGetKeyState(WXK_RIGHT)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::RIGHT, speed)); if (wxGetKeyState(WXK_UP)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::FORWARDS, speed)); if (wxGetKeyState(WXK_DOWN)) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::BACKWARDS, speed)); if (wxGetKeyState((wxKeyCode)']')) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::CLOCKWISE, speed)); if (wxGetKeyState((wxKeyCode)'[')) POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::ANTICLOCKWISE, speed)); return false; } else { POST_MESSAGE(ScrollConstant, (eRenderView::GAME, dir, enable ? speed : 0.0f)); return true; } } void OnKeyDown(wxKeyEvent& evt) { if (m_ScenarioEditor.GetToolManager().GetCurrentTool()->OnKey(evt, ITool::KEY_DOWN)) { // Key event has been handled by the tool, so don't try // to use it for camera motion too return; } 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)); evt.Skip(); } void OnKeyUp(wxKeyEvent& evt) { if (m_ScenarioEditor.GetToolManager().GetCurrentTool()->OnKey(evt, ITool::KEY_UP)) return; 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)); evt.Skip(); } void OnChar(wxKeyEvent& evt) { if (m_ScenarioEditor.GetToolManager().GetCurrentTool()->OnKey(evt, ITool::KEY_CHAR)) return; // Alt+enter toggles fullscreen if (evt.GetKeyCode() == WXK_RETURN && wxGetKeyState(WXK_ALT)) { if (m_ScenarioEditor.IsFullScreen()) m_ScenarioEditor.ShowFullScreen(false); else m_ScenarioEditor.ShowFullScreen(true, wxFULLSCREEN_NOBORDER | wxFULLSCREEN_NOCAPTION); return; } if (evt.GetKeyCode() == 'c') { POST_MESSAGE(CameraReset, ()); return; } int dir = 0; if (evt.GetKeyCode() == '-' || evt.GetKeyCode() == '_') dir = -1; else if (evt.GetKeyCode() == '+' || evt.GetKeyCode() == '=') dir = +1; // TODO: internationalisation (-/_ and +/= don't always share a key) if (dir) { float speed = 16.f * ScenarioEditor::GetSpeedModifier(); POST_MESSAGE(SmoothZoom, (eRenderView::GAME, speed*dir)); } else { // Slight hack: Only pass 'normal' keys; special keys will generate a KeyDown/KeyUp event instead if (evt.GetKeyCode() < 256) POST_MESSAGE(GuiCharEvent, (GetSDLKeyFromWxKeyCode(evt.GetKeyCode()), evt.GetUnicodeKey())); evt.Skip(); } } void OnKillFocus(wxFocusEvent& evt) { // Stop any scrolling, since otherwise we'll carry on forever if // we lose focus and the KeyUp events go to a different window POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::LEFT, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::RIGHT, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::FORWARDS, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::BACKWARDS, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::CLOCKWISE, 0.0f)); POST_MESSAGE(ScrollConstant, (eRenderView::GAME, eScrollConstantDir::ANTICLOCKWISE, 0.0f)); evt.Skip(); } virtual void HandleMouseEvent(wxMouseEvent& evt) { // TODO or at least to think about: When using other controls in the // editor, it's annoying that keyboard/scrollwheel no longer navigate // around the world until you click on it. // Setting focus back whenever the mouse moves over the GL window // feels like a fairly natural solution to me, since I can use // e.g. brush-editing controls normally, and then move the mouse to // see the brush outline and magically get given back full control // of the camera. if (evt.Moving()) SetFocus(); if (m_ScenarioEditor.GetToolManager().GetCurrentTool()->OnMouse(evt)) { // Mouse event has been handled by the tool, so don't try // to use it for camera motion too return; } // Global mouse event handlers (for camera motion) if (evt.GetWheelRotation()) { float speed = 16.f * ScenarioEditor::GetSpeedModifier(); POST_MESSAGE(SmoothZoom, (eRenderView::GAME, evt.GetWheelRotation() * speed / evt.GetWheelDelta())); } else { if (evt.MiddleIsDown()) { if (wxGetKeyState(WXK_CONTROL) || evt.RightIsDown()) m_MouseState = ROTATEAROUND; else m_MouseState = SCROLL; } else m_MouseState = NONE; if (m_MouseState != m_LastMouseState) { switch (m_MouseState) { case NONE: break; case SCROLL: POST_MESSAGE(Scroll, (eRenderView::GAME, eScrollType::FROM, evt.GetPosition())); break; case ROTATEAROUND: POST_MESSAGE(RotateAround, (eRenderView::GAME, eRotateAroundType::FROM, evt.GetPosition())); break; default: wxFAIL; } m_LastMouseState = m_MouseState; } else if (evt.Dragging()) { switch (m_MouseState) { case NONE: break; case SCROLL: POST_MESSAGE(Scroll, (eRenderView::GAME, eScrollType::TO, evt.GetPosition())); break; case ROTATEAROUND: POST_MESSAGE(RotateAround, (eRenderView::GAME, eRotateAroundType::TO, evt.GetPosition())); break; default: wxFAIL; } } } if (evt.ButtonDown()) POST_MESSAGE(GuiMouseButtonEvent, (evt.GetButton(), true, evt.GetPosition())); else if (evt.ButtonUp()) POST_MESSAGE(GuiMouseButtonEvent, (evt.GetButton(), false, evt.GetPosition())); else if (evt.GetEventType() == wxEVT_MOTION) POST_MESSAGE(GuiMouseMotionEvent, (evt.GetPosition())); } enum { NONE, SCROLL, ROTATEAROUND }; int m_MouseState, m_LastMouseState; ScenarioEditor& m_ScenarioEditor; DECLARE_EVENT_TABLE(); }; BEGIN_EVENT_TABLE(GameCanvas, Canvas) EVT_KEY_DOWN(GameCanvas::OnKeyDown) EVT_KEY_UP(GameCanvas::OnKeyUp) EVT_CHAR(GameCanvas::OnChar) EVT_KILL_FOCUS(GameCanvas::OnKillFocus) END_EVENT_TABLE() ////////////////////////////////////////////////////////////////////////// volatile bool g_FrameHasEnded; // Called from game thread ATLASDLLIMPEXP void Atlas_NotifyEndOfFrame() { g_FrameHasEnded = true; } enum { ID_Quit = 1, ID_New, ID_Open, ID_Save, ID_SaveAs, ID_Wireframe, ID_MessageTrace, ID_Screenshot, ID_JavaScript, ID_CameraReset, ID_RenderPathFixed, ID_RenderPathShader, ID_DumpState, ID_DumpBinaryState, ID_Toolbar // must be last in the list }; BEGIN_EVENT_TABLE(ScenarioEditor, wxFrame) EVT_CLOSE(ScenarioEditor::OnClose) EVT_TIMER(wxID_ANY, ScenarioEditor::OnTimer) EVT_MENU(ID_New, ScenarioEditor::OnNew) EVT_MENU(ID_Open, ScenarioEditor::OnOpen) EVT_MENU(ID_Save, ScenarioEditor::OnSave) EVT_MENU(ID_SaveAs, ScenarioEditor::OnSaveAs) EVT_MENU_RANGE(wxID_FILE1, wxID_FILE9, ScenarioEditor::OnMRUFile) EVT_MENU(ID_Quit, ScenarioEditor::OnQuit) EVT_MENU(wxID_UNDO, ScenarioEditor::OnUndo) EVT_MENU(wxID_REDO, ScenarioEditor::OnRedo) EVT_MENU(ID_Wireframe, ScenarioEditor::OnWireframe) EVT_MENU(ID_MessageTrace, ScenarioEditor::OnMessageTrace) EVT_MENU(ID_Screenshot, ScenarioEditor::OnScreenshot) EVT_MENU(ID_JavaScript, ScenarioEditor::OnJavaScript) EVT_MENU(ID_CameraReset, ScenarioEditor::OnCameraReset) EVT_MENU(ID_DumpState, ScenarioEditor::OnDumpState) EVT_MENU(ID_DumpBinaryState, ScenarioEditor::OnDumpState) EVT_MENU(ID_RenderPathFixed, ScenarioEditor::OnRenderPath) EVT_MENU(ID_RenderPathShader, ScenarioEditor::OnRenderPath) EVT_IDLE(ScenarioEditor::OnIdle) END_EVENT_TABLE() static AtlasWindowCommandProc g_CommandProc; AtlasWindowCommandProc& ScenarioEditor::GetCommandProc() { return g_CommandProc; } ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterface) : wxFrame(parent, wxID_ANY, _T(""), wxDefaultPosition, wxSize(1024, 768)) , m_FileHistory(_T("Scenario Editor")), m_ScriptInterface(scriptInterface) , m_ObjectSettings(g_SelectedObjects, AtlasMessage::eRenderView::GAME) , m_ToolManager(this) { // Global application initialisation: // wxLog::SetTraceMask(wxTraceMessages); SetOpenFilename(_T("")); #if defined(__WXMSW__) SetIcon(wxIcon(_T("ICON_ScenarioEditor"))); // load from atlas.rc #else { const wxString relativePath (_T("tools/atlas/icons/ScenarioEditor.ico")); wxFileName filename (relativePath, wxPATH_UNIX); filename.MakeAbsolute(Datafile::GetDataDirectory()); SetIcon(wxIcon(filename.GetFullPath())); } #endif wxToolTip::Enable(true); wxImage::AddHandler(new wxPNGHandler); ////////////////////////////////////////////////////////////////////////// // Do some early game initialisation: // (This must happen before constructing the GL canvas.) POST_MESSAGE(Init, ()); // Wait for it to finish running Init qPing qry; qry.Post(); ////////////////////////////////////////////////////////////////////////// // Menu wxMenuBar* menuBar = new wxMenuBar; SetMenuBar(menuBar); wxMenu *menuFile = new wxMenu; menuBar->Append(menuFile, _("&File")); { menuFile->Append(ID_New, _("&New...")); menuFile->Append(ID_Open, _("&Open...")); menuFile->Append(ID_Save, _("&Save")); menuFile->Append(ID_SaveAs, _("Save &As...")); menuFile->AppendSeparator();//----------- menuFile->Append(ID_Quit, _("E&xit")); m_FileHistory.UseMenu(menuFile);//------- m_FileHistory.AddFilesToMenu(); } // m_menuItem_Save = menuFile->FindItem(ID_Save); // remember this item, to let it be greyed out // wxASSERT(m_menuItem_Save); wxMenu *menuEdit = new wxMenu; menuBar->Append(menuEdit, _("&Edit")); { menuEdit->Append(wxID_UNDO, _("&Undo")); menuEdit->Append(wxID_REDO, _("&Redo")); } GetCommandProc().SetEditMenu(menuEdit); GetCommandProc().Initialize(); wxMenu *menuMisc = new wxMenu; menuBar->Append(menuMisc, _("&Misc hacks")); { menuMisc->AppendCheckItem(ID_Wireframe, _("&Wireframe")); menuMisc->AppendCheckItem(ID_MessageTrace, _("Message debug trace")); menuMisc->Append(ID_Screenshot, _("&Screenshot")); menuMisc->Append(ID_JavaScript, _("&JS console")); menuMisc->Append(ID_CameraReset, _("&Reset camera")); wxMenu *menuSS = new wxMenu; menuMisc->AppendSubMenu(menuSS, _("Si&mulation state")); menuSS->Append(ID_DumpState, _("&Dump to disk")); menuSS->Append(ID_DumpBinaryState, _("Dump &binary to disk")); wxMenu *menuRP = new wxMenu; menuMisc->AppendSubMenu(menuRP, _("Render &path")); menuRP->Append(ID_RenderPathFixed, _("&Fixed function")); menuRP->Append(ID_RenderPathShader, _("&Shader (default)")); } m_FileHistory.Load(*wxConfigBase::Get()); m_SectionLayout.SetWindow(this); // Toolbar: ToolButtonBar* toolbar = new ToolButtonBar(m_ToolManager, this, &m_SectionLayout, ID_Toolbar); // TODO: configurable small vs large icon images // (button label; tooltip text; image; internal tool name; section to switch to) toolbar->AddToolButton(_("Default"), _("Default"), _T("default.png"), _T(""), _T("")); toolbar->AddToolButton(_("Move"), _("Move/rotate object"), _T("moveobject.png"), _T("TransformObject"), _T("")/*_T("ObjectSidebar")*/); toolbar->AddToolButton(_("Elevation"), _("Alter terrain elevation"), _T("alterelevation.png"), _T("AlterElevation"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Smooth"), _("Smooth terrain elevation"), _T("smoothelevation.png"), _T("SmoothElevation"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Flatten"), _("Flatten terrain elevation"), _T("flattenelevation.png"), _T("FlattenElevation"), _T("")/*_T("TerrainSidebar")*/); toolbar->AddToolButton(_("Paint Terrain"), _("Paint terrain texture"), _T("paintterrain.png"), _T("PaintTerrain"), _T("")/*_T("TerrainSidebar")*/); toolbar->Realize(); SetToolBar(toolbar); // Set the default tool to be selected m_ToolManager.SetCurrentTool(_T("")); // Set up GL canvas: int glAttribList[] = { WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 24, // TODO: wx documentation doesn't say 24 is valid WX_GL_STENCIL_SIZE, 8, WX_GL_BUFFER_SIZE, 24, // colour bits WX_GL_MIN_ALPHA, 8, // alpha bits 0 }; Canvas* canvas = new GameCanvas(*this, m_SectionLayout.GetCanvasParent(), glAttribList); m_SectionLayout.SetCanvas(canvas); // Set up sidebars: m_SectionLayout.Build(*this); #if defined(__WXMSW__) // The canvas' context gets made current on creation; but it can only be // current for one thread at a time, and it needs to be current for the // thread that is doing the draw calls, so disable it for this one. wglMakeCurrent(NULL, NULL); #elif defined(__WXGTK__) // Need to make sure the canvas is realized by GTK, so that its context is valid Show(true); wxSafeYield(); #endif // Send setup messages to game engine: POST_MESSAGE(SetCanvas, (static_cast(canvas))); POST_MESSAGE(InitGraphics, ()); canvas->InitSize(); // Start with a blank map (so that the editor can assume there's always // a valid map loaded) POST_MESSAGE(LoadMap, (_T("_default"))); // Select the initial sidebar (after the map has loaded) m_SectionLayout.SelectPage(_T("MapSidebar")); // Wait for blank map qry.Post(); POST_MESSAGE(RenderEnable, (eRenderView::GAME)); // Set up a timer to make sure tool-updates happen frequently (in addition // to the idle handler (which makes them happen more frequently if there's nothing // else to do)) m_Timer.SetOwner(this); m_Timer.Start(20); #ifdef __WXGTK__ // HACK: because of how we fiddle with stuff earlier to make sure the canvas // is displayed, the layout gets messed up, and it only seems to be fixable // by changing the window's size SetSize(GetSize() + wxSize(1, 0)); #endif } float ScenarioEditor::GetSpeedModifier() { if (wxGetKeyState(WXK_SHIFT) && wxGetKeyState(WXK_CONTROL)) return 1.f/64.f; else if (wxGetKeyState(WXK_CONTROL)) return 1.f/4.f; else if (wxGetKeyState(WXK_SHIFT)) return 4.f; else return 1.f; } void ScenarioEditor::OnClose(wxCloseEvent&) { m_ToolManager.SetCurrentTool(_T("")); m_FileHistory.Save(*wxConfigBase::Get()); POST_MESSAGE(Shutdown, ()); qExit().Post(); // blocks until engine has noticed the message, so we won't be // destroying the GLCanvas while it's still rendering Destroy(); } static void UpdateTool(ToolManager& toolManager) { // Don't keep posting events if the game can't keep up if (g_FrameHasEnded) { g_FrameHasEnded = false; // (thread safety doesn't matter here) // TODO: Smoother timing stuff? static double last = g_Timer.GetTime(); double time = g_Timer.GetTime(); toolManager.GetCurrentTool()->OnTick(time-last); last = time; } } void ScenarioEditor::OnTimer(wxTimerEvent&) { UpdateTool(m_ToolManager); } void ScenarioEditor::OnIdle(wxIdleEvent&) { UpdateTool(m_ToolManager); } void ScenarioEditor::OnQuit(wxCommandEvent&) { Close(); } void ScenarioEditor::OnUndo(wxCommandEvent&) { GetCommandProc().Undo(); } void ScenarioEditor::OnRedo(wxCommandEvent&) { GetCommandProc().Redo(); } ////////////////////////////////////////////////////////////////////////// void ScenarioEditor::OnNew(wxCommandEvent& WXUNUSED(event)) { if (wxMessageBox(_("Discard current map and start blank new map?"), _("New map"), wxOK|wxCANCEL|wxICON_QUESTION, this) == wxOK) OpenFile(_T("_default"), _T("")); } void ScenarioEditor::OpenFile(const wxString& name, const wxString& filename) { wxBusyInfo busy(_("Loading map")); wxBusyCursor busyc; // TODO: Work when the map is not in .../maps/scenarios/ std::wstring map = name.c_str(); // Deactivate tools, so they don't carry forwards into the new CWorld // and crash. m_ToolManager.SetCurrentTool(_T("")); // TODO: clear the undo buffer, etc POST_MESSAGE(LoadMap, (map)); SetOpenFilename(filename); // Wait for it to load, while the wxBusyInfo is telling the user that we're doing that qPing qry; qry.Post(); NotifyOnMapReload(); // TODO: Make this a non-undoable command } // TODO (eventually): replace all this file-handling stuff with the Workspace Editor void ScenarioEditor::OnOpen(wxCommandEvent& WXUNUSED(event)) { wxFileDialog dlg (NULL, wxFileSelectorPromptStr, Datafile::GetDataDirectory() + _T("/mods/public/maps/scenarios"), m_OpenFilename, _T("PMP files (*.pmp)|*.pmp|All files (*.*)|*.*"), wxOPEN); wxString cwd = wxFileName::GetCwd(); if (dlg.ShowModal() == wxID_OK) OpenFile(dlg.GetFilename(), dlg.GetFilename()); wxCHECK_RET(cwd == wxFileName::GetCwd(), _T("cwd changed")); // paranoia - MSDN says OFN_NOCHANGEDIR (used when we don't give wxCHANGE_DIR) // "is ineffective for GetOpenFileName", but it seems to work anyway // TODO: Make this a non-undoable command } void ScenarioEditor::OnMRUFile(wxCommandEvent& event) { wxString filename(m_FileHistory.GetHistoryFile(event.GetId() - wxID_FILE1)); if (filename.Len()) { OpenFile(filename, filename); } else { //Remove from MRU m_FileHistory.RemoveFileFromHistory(event.GetId() - wxID_FILE1); wxMessageDialog msgDlg(NULL, _T("File '/mods/*/maps/scenarios/")+filename + _T("' does not exist!"), _T("Error"), wxICON_ERROR); msgDlg.ShowModal(); } } void ScenarioEditor::OnSave(wxCommandEvent& event) { if (m_OpenFilename.IsEmpty()) { OnSaveAs(event); } else { wxBusyInfo busy(_("Saving map")); // Deactivate tools, so things like unit previews don't get saved. // (TODO: Would be nicer to leave the tools active, and just not save // the preview units.) m_ToolManager.SetCurrentTool(_T("")); std::wstring map = m_OpenFilename.c_str(); POST_MESSAGE(SaveMap, (map)); // Wait for it to finish saving qPing qry; qry.Post(); } } void ScenarioEditor::OnSaveAs(wxCommandEvent& WXUNUSED(event)) { wxFileDialog dlg (NULL, wxFileSelectorPromptStr, Datafile::GetDataDirectory() + _T("/mods/public/maps/scenarios"), m_OpenFilename, _T("PMP files (*.pmp)|*.pmp|All files (*.*)|*.*"), wxSAVE | wxOVERWRITE_PROMPT); if (dlg.ShowModal() == wxID_OK) { wxBusyInfo busy(_("Saving map")); m_ToolManager.SetCurrentTool(_T("")); // TODO: Work when the map is not in .../maps/scenarios/ std::wstring map = dlg.GetFilename().c_str(); POST_MESSAGE(SaveMap, (map)); SetOpenFilename(dlg.GetFilename()); // Wait for it to finish saving qPing qry; qry.Post(); } } void ScenarioEditor::SetOpenFilename(const wxString& filename) { SetTitle(wxString::Format(_("Atlas - Scenario Editor - %s"), (filename.IsEmpty() ? wxString(_("(untitled)")) : filename).c_str())); m_OpenFilename = filename; if (! filename.IsEmpty()) m_FileHistory.AddFileToHistory(filename); } void ScenarioEditor::NotifyOnMapReload() { m_SectionLayout.OnMapReload(); } ////////////////////////////////////////////////////////////////////////// void ScenarioEditor::OnWireframe(wxCommandEvent& event) { POST_MESSAGE(RenderStyle, (event.IsChecked())); } void ScenarioEditor::OnMessageTrace(wxCommandEvent& event) { POST_MESSAGE(MessageTrace, (event.IsChecked())); } void ScenarioEditor::OnScreenshot(wxCommandEvent& WXUNUSED(event)) { POST_MESSAGE(Screenshot, (10)); } void ScenarioEditor::OnJavaScript(wxCommandEvent& WXUNUSED(event)) { wxString cmd = ::wxGetTextFromUser(_T(""), _("JS command"), _T(""), this); if (cmd.IsEmpty()) return; POST_MESSAGE(JavaScript, (cmd.c_str())); } void ScenarioEditor::OnCameraReset(wxCommandEvent& WXUNUSED(event)) { POST_MESSAGE(CameraReset, ()); } void ScenarioEditor::OnRenderPath(wxCommandEvent& event) { switch (event.GetId()) { case ID_RenderPathFixed: POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"fixed")); break; case ID_RenderPathShader: POST_MESSAGE(SetViewParamS, (eRenderView::GAME, L"renderpath", L"shader")); break; } } void ScenarioEditor::OnDumpState(wxCommandEvent& event) { wxDateTime time = wxDateTime::Now(); wxString filename; bool doBinary = false; switch (event.GetId()) { case ID_DumpState: filename = wxString::Format(_T("sim-dump-%d.txt"), time.GetTicks()); break; case ID_DumpBinaryState: doBinary = true; filename = wxString::Format(_T("sim-dump-%d.dat"), time.GetTicks()); break; } qSimStateDebugDump q(doBinary); q.Post(); - wxString state(std::wstring(*q.dump)); + std::wstring dump = *q.dump; + wxString state(dump.c_str()); wxFFile file(filename.c_str(), _T("w")); if (file.IsOpened() && !file.Error()) { file.Write(state); file.Close(); } else { wxMessageDialog msgDlg(NULL, _("Error writing to file: ") + filename, _("Error"), wxICON_ERROR); msgDlg.ShowModal(); } } ////////////////////////////////////////////////////////////////////////// Position::Position(const wxPoint& pt) : type(1) { type1.x = pt.x; type1.y = pt.y; } ////////////////////////////////////////////////////////////////////////// /* Disabled (and should be removed if it turns out to be unnecessary) - see MessagePasserImpl.cpp for information static void QueryCallback() { // If this thread completely blocked on the semaphore inside Query, it would // never respond to window messages, and the system deadlocks if the // game tries to display an assertion failure dialog. (See // WaitForSingleObject on MSDN.) // So, this callback is called occasionally, and gives wx a change to // handle messages. // This is kind of like wxYield, but without the ProcessPendingEvents - // it's enough to make Windows happy and stop deadlocking, without actually // calling the event handlers (which could lead to nasty recursion) // while (wxEventLoop::GetActive()->Pending()) // wxEventLoop::GetActive()->Dispatch(); // Oh dear, we can't use that either - it (at least in wx 2.6.3) still // processes messages, which causes reentry into various things that we // don't want to be reentrant. So do it all manually, accepting Windows // messages and sticking them on a list for later processing (in a custom // event loop class): // (TODO: Rethink this entire process on Linux) // (Alt TODO: Could we make the game never pop up windows (or use the Win32 // GUI in any other way) when it's running under Atlas, so we wouldn't need // to do any message processing here at all?) #ifdef _WIN32 AtlasEventLoop* evtLoop = (AtlasEventLoop*)wxEventLoop::GetActive(); // evtLoop might be NULL, particularly if we're still initialising windows // and haven't got into the normal event loop yet. But we'd have to process // messages anyway, to avoid the deadlocks that this is for. So, don't bother // with that and just crash instead. // (Maybe it could be solved better by constructing/finding an event loop // object here and setting it as the global one, assuming it's not overwritten // later by wx.) while (evtLoop->Pending()) { // Based on src/msw/evtloop.cpp's wxEventLoop::Dispatch() MSG msg; BOOL rc = ::GetMessage(&msg, (HWND) NULL, 0, 0); if (rc == 0) { // got WM_QUIT return; } if (rc == -1) { wxLogLastError(wxT("GetMessage")); return; } // Our special bits: if (msg.message == WM_PAINT) { // "GetMessage does not remove WM_PAINT messages from the queue. // The messages remain in the queue until processed." // So let's process them, to avoid infinite loops... PAINTSTRUCT paint; ::BeginPaint(msg.hwnd, &paint); ::EndPaint(msg.hwnd, &paint); // Remember that some painting was needed - we'll just repaint // the whole screen when this is finished. evtLoop->NeedsPaint(); } else { // Add this message to a queue for later processing. (That's // probably kind of valid, at least in most cases.) MSG* pMsg = new MSG(msg); evtLoop->AddMessage(pMsg); } } #endif } */ void QueryMessage::Post() { // g_MessagePasser->Query(this, &QueryCallback); g_MessagePasser->Query(this, NULL); }