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);
}