From 7bfe6473663cd8226f3a0c64d5c6e34b78a16f19 Mon Sep 17 00:00:00 2001 From: Ykkrosh Date: Thu, 3 Nov 2005 03:49:57 +0000 Subject: [PATCH] Atlas: Initial terrain-selection panel. TextureManager: Skip *.jbf too. This was SVN commit r3085. --- source/graphics/TextureManager.cpp | 9 +- source/tools/atlas/AtlasObject/AtlasObject.h | 2 +- .../AtlasUI/ArchiveViewer/ArchiveViewer.cpp | 3 +- .../SnapSplitterWindow/SnapSplitterWindow.cpp | 40 ++++-- .../SnapSplitterWindow/SnapSplitterWindow.h | 5 +- .../AtlasUI/ScenarioEditor/ScenarioEditor.cpp | 59 ++++----- .../AtlasUI/ScenarioEditor/ScenarioEditor.h | 3 + .../AtlasUI/ScenarioEditor/SectionLayout.cpp | 112 +++++++++++++++++ .../AtlasUI/ScenarioEditor/SectionLayout.h | 18 +++ .../Sections/Common/Sidebar.cpp | 7 ++ .../ScenarioEditor/Sections/Common/Sidebar.h | 9 +- .../Sections/Terrain/Terrain.cpp | 116 +++++++++++++++++- .../ScenarioEditor/Sections/Terrain/Terrain.h | 11 ++ source/tools/atlas/GameInterface/GameLoop.cpp | 12 +- .../GameInterface/Handlers/GraphicsSetup.cpp | 3 +- .../Handlers/TerrainHandlers.cpp | 28 +++++ .../tools/atlas/GameInterface/MessagePasser.h | 8 +- .../atlas/GameInterface/MessagePasserImpl.cpp | 49 ++++++-- .../atlas/GameInterface/MessagePasserImpl.h | 2 +- source/tools/atlas/GameInterface/Messages.h | 21 +++- .../tools/atlas/GameInterface/MessagesSetup.h | 51 ++++++-- 21 files changed, 493 insertions(+), 75 deletions(-) create mode 100644 source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp create mode 100644 source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.h diff --git a/source/graphics/TextureManager.cpp b/source/graphics/TextureManager.cpp index 5ff4465337..c561e4fbbf 100755 --- a/source/graphics/TextureManager.cpp +++ b/source/graphics/TextureManager.cpp @@ -109,9 +109,14 @@ void CTextureManager::LoadTextures(CTerrainPropertiesPtr props, const char* dir) // may later be added and that interface doesn't support specifying // multiple extensions. const char* ext = strrchr(texture_name, '.'); - if(!ext || !stricmp(ext, ".xml") || !stricmp(ext, ".xmb") || !stricmp(ext, ".dtd")) + if(!ext + || !stricmp(ext, ".xml") + || !stricmp(ext, ".xmb") + || !stricmp(ext, ".dtd") + || !stricmp(ext, ".jbf") // PSP browser files. (This list is getting quite long and ugly...) + ) continue; - // Also allow storage of temporary files in the texture directories + // Also allow storage of other temporary files in the texture directories if(ext[strlen(ext)-1] == '~') continue; diff --git a/source/tools/atlas/AtlasObject/AtlasObject.h b/source/tools/atlas/AtlasObject/AtlasObject.h index 73b2707158..31d47762f1 100644 --- a/source/tools/atlas/AtlasObject/AtlasObject.h +++ b/source/tools/atlas/AtlasObject/AtlasObject.h @@ -26,7 +26,7 @@ public: AtSmartPtr(const AtSmartPtr& r) : ptr(r.ptr) { inc_ref(); } // Assignment operators AtSmartPtr& operator=(T* p) { dec_ref(); ptr = p; inc_ref(); return *this; } - AtSmartPtr& operator=(const AtSmartPtr& r) { dec_ref(); ptr = r.ptr; inc_ref(); return *this; } + AtSmartPtr& operator=(const AtSmartPtr& r) { if (&r != this) { dec_ref(); ptr = r.ptr; inc_ref(); } return *this; } // Destructor ~AtSmartPtr() { dec_ref(); } // Allow conversion from non-const T* to const T* diff --git a/source/tools/atlas/AtlasUI/ArchiveViewer/ArchiveViewer.cpp b/source/tools/atlas/AtlasUI/ArchiveViewer/ArchiveViewer.cpp index f11293ae48..a4c415f8f1 100644 --- a/source/tools/atlas/AtlasUI/ArchiveViewer/ArchiveViewer.cpp +++ b/source/tools/atlas/AtlasUI/ArchiveViewer/ArchiveViewer.cpp @@ -513,7 +513,8 @@ void ArchiveViewer::OnEnablePreview(wxCommandEvent& event) if (GetSize().GetWidth() < 700) SetSize(-1, -1, 900, -1); // nobody is still using 800x600, are they? - m_Splitter->SplitVertically(m_Splitter->GetWindow1(), m_PreviewWindow, 600); + m_Splitter->SetDefaultSashPosition(600); + m_Splitter->SplitVertically(m_Splitter->GetWindow1(), m_PreviewWindow); } else m_Splitter->Unsplit(); diff --git a/source/tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.cpp b/source/tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.cpp index d566c17609..50d376c510 100644 --- a/source/tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.cpp +++ b/source/tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.cpp @@ -16,22 +16,36 @@ SnapSplitterWindow::SnapSplitterWindow(wxWindow* parent, long style) } -bool SnapSplitterWindow::SplitVertically(wxWindow *window1, wxWindow *window2, int sashPosition) -{ - m_DefaultSashPosition = sashPosition; - return DoSplit(wxSPLIT_VERTICAL, window1, window2, sashPosition); -} -bool SnapSplitterWindow::SplitHorizontally(wxWindow *window1, wxWindow *window2, int sashPosition) -{ - m_DefaultSashPosition = sashPosition; - return DoSplit(wxSPLIT_HORIZONTAL, window1, window2, sashPosition); -} +void SnapSplitterWindow::SetDefaultSashPosition(int sashPosition) +{ + m_DefaultSashPosition = sashPosition; + + // Set gravity so that the unspecified-size window is resized + if (sashPosition < 0) + SetSashGravity(1.0); + else if (sashPosition == 0) + SetSashGravity(0.5); + else + SetSashGravity(0.0); +} + +bool SnapSplitterWindow::SplitVertically(wxWindow *window1, wxWindow *window2) +{ + return wxSplitterWindow::SplitVertically(window1, window2, m_DefaultSashPosition); +} + +bool SnapSplitterWindow::SplitHorizontally(wxWindow *window1, wxWindow *window2) +{ + return wxSplitterWindow::SplitHorizontally(window1, window2, m_DefaultSashPosition); +} void SnapSplitterWindow::OnSashPosChanging(wxSplitterEvent& evt) { - if (evt.GetSashPosition() >= m_DefaultSashPosition-m_SnapTolerance && - evt.GetSashPosition() <= m_DefaultSashPosition+m_SnapTolerance) + int defaultPos = ConvertSashPosition(m_DefaultSashPosition); + + if (evt.GetSashPosition() >= defaultPos-m_SnapTolerance && + evt.GetSashPosition() <= defaultPos+m_SnapTolerance) { - evt.SetSashPosition(m_DefaultSashPosition); + evt.SetSashPosition(defaultPos); } } diff --git a/source/tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.h b/source/tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.h index 3d7a76c1c5..824b0dd0a9 100644 --- a/source/tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.h +++ b/source/tools/atlas/AtlasUI/CustomControls/SnapSplitterWindow/SnapSplitterWindow.h @@ -4,8 +4,9 @@ class SnapSplitterWindow : public wxSplitterWindow { public: SnapSplitterWindow(wxWindow* parent, long style = wxSP_3D); - virtual bool SplitVertically(wxWindow *window1, wxWindow *window2, int sashPosition); - virtual bool SplitHorizontally(wxWindow *window1, wxWindow *window2, int sashPosition); + void SetDefaultSashPosition(int sashPosition); + virtual bool SplitVertically(wxWindow *window1, wxWindow *window2); + virtual bool SplitHorizontally(wxWindow *window1, wxWindow *window2); private: void OnSashPosChanging(wxSplitterEvent& evt); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp index 2306b98963..0cb1f264ad 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp @@ -3,15 +3,13 @@ #include "ScenarioEditor.h" #include "wx/glcanvas.h" +#include "wx/evtloop.h" #include "SnapSplitterWindow/SnapSplitterWindow.h" #include "HighResTimer/HighResTimer.h" #include "GameInterface/MessagePasser.h" #include "GameInterface/Messages.h" -#include "Sections/Map/Map.h" -#include "Sections/Terrain/Terrain.h" - #include "tools/Common/Tools.h" //#define UI_ONLY @@ -352,7 +350,8 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent) ////////////////////////////////////////////////////////////////////////// // Main window - SnapSplitterWindow* splitter = new SnapSplitterWindow(this); + //SnapSplitterWindow* splitter = new SnapSplitterWindow(this); + m_SectionLayout.SetWindow(this); // Set up GL canvas: @@ -364,7 +363,8 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent) WX_GL_MIN_ALPHA, 8, // alpha bits 0 }; - Canvas* canvas = new Canvas(splitter, glAttribList); + Canvas* canvas = new Canvas(m_SectionLayout.GetCanvasParent(), glAttribList); + m_SectionLayout.SetCanvas(canvas); // 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. @@ -372,15 +372,7 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent) // Set up sidebars: - // TODO: wxWidgets bug (http://sourceforge.net/tracker/index.php?func=detail&aid=1298803&group_id=9863&atid=109863) - // - pressing menu keys (e.g. alt+f) with notebook tab focussed causes application to freeze - wxNotebook* sidebar = new wxNotebook(splitter, wxID_ANY); - sidebar->AddPage(new MapSidebar(sidebar), _("Map"), false); - sidebar->AddPage(new TerrainSidebar(sidebar), _("Terrain"), false); - - // Build layout: - - splitter->SplitVertically(sidebar, canvas, 200); + m_SectionLayout.Build(); // Send setup messages to game engine: @@ -398,13 +390,6 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent) POST_MESSAGE(CommandString("render_enable")); #endif -// { -// AtlasMessage::qGetTerrainGroups qry(0); -// qry.Post(); -// for (std::vector::iterator it = qry.groupnames.begin(); it != qry.groupnames.end(); ++it) -// wxLogMessage(L"%s", wxString(it->c_str())); -// } - // 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)) @@ -415,17 +400,15 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent) void ScenarioEditor::OnClose(wxCloseEvent&) { + SetCurrentTool(_T("")); + #ifndef UI_ONLY POST_MESSAGE(CommandString("shutdown")); #endif - POST_MESSAGE(CommandString("exit")); - SetCurrentTool(_T("")); - - // TODO: If it's still rendering while we're destroying the canvas, things - // often crash. - // HACK: Instead of actually solving the problem, just sleep. - wxSleep(1); + AtlasMessage::qExit().Post(); + // blocks until engine has noticed the message, so we won't be + // destroying the GLCanvas while it's still rendering Destroy(); } @@ -493,3 +476,23 @@ AtlasMessage::Position::Position(const wxPoint& pt) type1.x = pt.x; type1.y = pt.y; } + +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(); +} +void AtlasMessage::QueryMessage::Post() +{ + g_MessagePasser->Query(this, &QueryCallback); +} diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h index 2723c9ac11..3080bf76f3 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h @@ -2,6 +2,7 @@ #define SCENARIOEDITOR_H__ #include "General/AtlasWindowCommandProc.h" +#include "SectionLayout.h" class ScenarioEditor : public wxFrame { @@ -24,6 +25,8 @@ public: private: wxTimer m_Timer; + SectionLayout m_SectionLayout; + DECLARE_EVENT_TABLE(); }; diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp new file mode 100644 index 0000000000..23e7e1ceaa --- /dev/null +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp @@ -0,0 +1,112 @@ +#include "stdafx.h" + +#include "SectionLayout.h" + +#include "SnapSplitterWindow/SnapSplitterWindow.h" + +#include "Sections/Map/Map.h" +#include "Sections/Terrain/Terrain.h" + +////////////////////////////////////////////////////////////////////////// + +class SidebarNotebook : public wxNotebook +{ +public: + SidebarNotebook(wxWindow *parent, SnapSplitterWindow* splitter) + : wxNotebook(parent, wxID_ANY), m_Splitter(splitter) + { + } + + // Only allow Sidebar objects to be added + bool AddPage(Sidebar* sidebar, const wxString& text) + { + return wxNotebook::AddPage(sidebar, text); + } + +protected: + void OnPageChanged(wxNotebookEvent& event) + { + Sidebar* oldPage = NULL; + Sidebar* newPage = NULL; + + if (event.GetOldSelection() != -1) + oldPage = wxDynamicCast(GetPage(event.GetOldSelection()), Sidebar); + + if (event.GetSelection() != -1) + newPage = wxDynamicCast(GetPage(event.GetSelection()), Sidebar); + + if (m_Splitter->IsSplit()) + { + wxWindow* bottom; + if (newPage && NULL != (bottom = newPage->GetBottomBar(m_Splitter))) + { + m_Splitter->ReplaceWindow(m_Splitter->GetWindow2(), bottom); + } + else + { + m_Splitter->Unsplit(); + } + } + else + { + wxWindow* bottom; + if (newPage && NULL != (bottom = newPage->GetBottomBar(m_Splitter))) + { + m_Splitter->SplitHorizontally(m_Splitter->GetWindow1(), bottom); + } + } + event.Skip(); + } + +private: + SnapSplitterWindow* m_Splitter; + + DECLARE_EVENT_TABLE(); +}; + +BEGIN_EVENT_TABLE(SidebarNotebook, wxNotebook) + EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, SidebarNotebook::OnPageChanged) +END_EVENT_TABLE(); + +////////////////////////////////////////////////////////////////////////// + + + +SectionLayout::SectionLayout() +{ +} + +SectionLayout::~SectionLayout() +{ +} + +void SectionLayout::SetWindow(wxWindow* window) +{ + m_HorizSplitter = new SnapSplitterWindow(window, wxSP_NOBORDER); + m_VertSplitter = new SnapSplitterWindow(m_HorizSplitter, wxSP_3D); +} + +wxWindow* SectionLayout::GetCanvasParent() +{ + return m_VertSplitter; +} + +void SectionLayout::SetCanvas(wxWindow* canvas) +{ + m_Canvas = canvas; +} + +void SectionLayout::Build() +{ + // TODO: wxWidgets bug (http://sourceforge.net/tracker/index.php?func=detail&aid=1298803&group_id=9863&atid=109863) + // - pressing menu keys (e.g. alt+f) with notebook tab focussed causes application to freeze + wxNotebook* sidebar = new SidebarNotebook(m_HorizSplitter, m_VertSplitter); + sidebar->AddPage(new MapSidebar(sidebar), _("Map"), false); + sidebar->AddPage(new TerrainSidebar(sidebar), _("Terrain"), false); + + m_VertSplitter->SetDefaultSashPosition(-165); + m_VertSplitter->Initialize(m_Canvas); + + m_HorizSplitter->SetDefaultSashPosition(200); + m_HorizSplitter->SplitVertically(sidebar, m_VertSplitter); +} diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.h b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.h new file mode 100644 index 0000000000..a20b873310 --- /dev/null +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.h @@ -0,0 +1,18 @@ +class SnapSplitterWindow; + +class SectionLayout +{ +public: + SectionLayout(); + ~SectionLayout(); + + void SetWindow(wxWindow* window); + wxWindow* GetCanvasParent(); + void SetCanvas(wxWindow*); + void Build(); + +private: + wxWindow* m_Canvas; + SnapSplitterWindow* m_HorizSplitter; + SnapSplitterWindow* m_VertSplitter; +}; diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.cpp index 79a361fd81..0dae31ad61 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.cpp @@ -2,9 +2,16 @@ #include "Sidebar.h" +IMPLEMENT_DYNAMIC_CLASS(Sidebar, wxPanel) + Sidebar::Sidebar(wxWindow* parent) : wxPanel(parent) { m_MainSizer = new wxBoxSizer(wxVERTICAL); SetSizer(m_MainSizer); } + +wxWindow* Sidebar::GetBottomBar(wxWindow* WXUNUSED(parent)) +{ + return NULL; +} diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h index 1db1bceb79..7d86349d84 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Common/Sidebar.h @@ -3,11 +3,18 @@ class Sidebar : public wxPanel { + DECLARE_DYNAMIC_CLASS(Sidebar); + public: + Sidebar() {} Sidebar(wxWindow* parent); + virtual wxWindow* GetBottomBar(wxWindow* parent); + // called whenever the bottom bar is made visible; should usually be + // lazily constructed, then cached forever (to maximise responsiveness) + protected: - wxSizer* m_MainSizer; // vertical box sizer + wxSizer* m_MainSizer; // vertical box sizer, used by most sidebars }; #endif // SIDEBAR_H__ diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp index fe0bcba4ec..9bcd8fb7a4 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp @@ -13,7 +13,7 @@ #include "wx/spinctrl.h" TerrainSidebar::TerrainSidebar(wxWindow* parent) -: Sidebar(parent) +: Sidebar(parent), m_BottomBar(NULL) { // TODO: Less ugliness @@ -34,3 +34,117 @@ TerrainSidebar::TerrainSidebar(wxWindow* parent) } } + +wxWindow* TerrainSidebar::GetBottomBar(wxWindow* parent) +{ + if (m_BottomBar) + return m_BottomBar; + + m_BottomBar = new TerrainBottomBar(parent); + return m_BottomBar; +} + +////////////////////////////////////////////////////////////////////////// + +class TextureNotebookPage : public wxPanel +{ +public: + TextureNotebookPage(wxWindow* parent, const wxString& name) + : wxPanel(parent, wxID_ANY), m_Name(name), m_Loaded(false) + { + } + + void OnSelected() + { + if (m_Loaded) + return; + + const int imageWidth = 64; + const int imageHeight = 32; + + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + + wxListCtrl* list = new wxListCtrl(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLC_ICON | wxLC_SINGLE_SEL | wxLC_AUTOARRANGE); + wxImageList* imglist = new wxImageList(imageWidth, imageHeight, false, 0); + + AtlasMessage::qGetTerrainGroupPreviews qry(m_Name.c_str(), imageWidth, imageHeight); + qry.Post(); + + int i = 0; + for (std::vector::iterator it = qry.previews.begin(); it != qry.previews.end(); ++it) + { + wxImage img (imageWidth, imageHeight, it->imagedata); + imglist->Add(wxBitmap(img)); + + // Add spaces into the name, so Windows doesn't just say + // "grass_..." in the list ctrl for every terrain + wxString name = it->name.c_str(); + name.Replace(_T("_"), _T(" ")); + + list->InsertItem(i, name, i); + ++i; + } + list->AssignImageList(imglist, wxIMAGE_LIST_NORMAL); + + sizer->Add(list, wxSizerFlags().Expand().Proportion(1)); + SetSizer(sizer); + Layout(); // required to make things size correctly + + m_Loaded = true; + } + +private: + bool m_Loaded; + wxString m_Name; +}; + +class TextureNotebook : public wxNotebook +{ +public: + TextureNotebook(wxWindow *parent) + : wxNotebook(parent, wxID_ANY/*, wxDefaultPosition, wxDefaultSize, wxNB_FIXEDWIDTH*/) + { + // Get the list of terrain groups from the engine + AtlasMessage::qGetTerrainGroups qry; + qry.Post(); + for (std::vector::iterator it = qry.groupnames.begin(); it != qry.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(this, m_TerrainGroups[i]), visibleName); + } + } + +protected: + void OnPageChanged(wxNotebookEvent& event) + { + if (event.GetSelection() != -1) + { + static_cast(GetPage(event.GetSelection()))->OnSelected(); + } + event.Skip(); + } + +private: + wxArrayString m_TerrainGroups; + DECLARE_EVENT_TABLE(); +}; + +BEGIN_EVENT_TABLE(TextureNotebook, wxNotebook) + EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, TextureNotebook::OnPageChanged) +END_EVENT_TABLE(); + +////////////////////////////////////////////////////////////////////////// + +TerrainBottomBar::TerrainBottomBar(wxWindow* parent) + : wxPanel(parent, wxID_ANY) +{ + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + wxNotebook* notebook = new TextureNotebook(this); + sizer->Add(notebook, wxSizerFlags().Expand().Proportion(1)); + SetSizer(sizer); +} \ No newline at end of file diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h index dd0d080f99..db14573965 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h @@ -4,4 +4,15 @@ class TerrainSidebar : public Sidebar { public: TerrainSidebar(wxWindow* parent); + wxWindow* GetBottomBar(wxWindow* parent); + +private: + wxWindow* m_BottomBar; }; + +class TerrainBottomBar : public wxPanel +{ +public: + TerrainBottomBar(wxWindow* parent); +private: +}; \ No newline at end of file diff --git a/source/tools/atlas/GameInterface/GameLoop.cpp b/source/tools/atlas/GameInterface/GameLoop.cpp index ec5e6b09ed..94b863c4c4 100644 --- a/source/tools/atlas/GameInterface/GameLoop.cpp +++ b/source/tools/atlas/GameInterface/GameLoop.cpp @@ -122,8 +122,9 @@ bool BeginAtlas(int argc, char* argv[], void* dll) name += "_"; name += static_cast(msg)->name; // use 'static_cast' when casting messages, to make it clear - // that it's slightly dangerous - we have to just assume that - // GetName is correct, since we can't use proper RTTI + // that something slightly dangerous is happening - we have + // to just assume that GetName is correct, since we can't use + // proper RTTI. } msgHandlers::const_iterator it = GetMsgHandlers().find(name); @@ -144,6 +145,13 @@ bool BeginAtlas(int argc, char* argv[], void* dll) // that the query has now been processed. sem_post((sem_t*) static_cast(msg)->m_Semaphore); // (msg may have been destructed at this point, so don't use it again) + + // It's quite possible that the querier is going to do a tiny + // bit of processing on the query results and then issue another + // query, and repeat lots of times in a loop. To avoid slowing + // that down by rendering between every query, make this + // thread yield now. + SDL_Delay(0); } else { diff --git a/source/tools/atlas/GameInterface/Handlers/GraphicsSetup.cpp b/source/tools/atlas/GameInterface/Handlers/GraphicsSetup.cpp index 175346ce8c..876893e992 100644 --- a/source/tools/atlas/GameInterface/Handlers/GraphicsSetup.cpp +++ b/source/tools/atlas/GameInterface/Handlers/GraphicsSetup.cpp @@ -39,8 +39,9 @@ MESSAGEHANDLER_STR(shutdown) } -MESSAGEHANDLER_STR(exit) +QUERYHANDLER(Exit) { + UNUSED2(msg); g_GameLoop->running = false; } diff --git a/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp index 71ef474818..62e723a968 100644 --- a/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp @@ -5,6 +5,7 @@ #include "../CommandProc.h" #include "graphics/TextureManager.h" +#include "graphics/TextureEntry.h" #include "../Brushes.h" @@ -17,4 +18,31 @@ QUERYHANDLER(GetTerrainGroups) msg->groupnames.push_back(CStrW(it->first)); } +QUERYHANDLER(GetTerrainGroupPreviews) +{ + CTerrainGroup* group = g_TexMan.FindGroup(msg->groupname); + for (std::vector::const_iterator it = group->GetTerrains().begin(); it != group->GetTerrains().end(); ++it) + { + msg->previews.push_back(AtlasMessage::sTerrainGroupPreview()); + msg->previews.back().name = CStrW((*it)->GetTag()); + + u32 c = (*it)->GetBaseColor(); + unsigned char* buf = (unsigned char*)malloc(msg->imagewidth*msg->imageheight*3); + + // TODO: An actual preview of the texture. (There's no need to shrink + // the entire texture to fit, since it's the small details in the + // texture that are interesting, so we could just crop a chunk out of + // the middle.) + for (int i = 0; i < msg->imagewidth*msg->imageheight; ++i) + { + buf[i*3+0] = (c>>16) & 0xff; + buf[i*3+1] = (c>>8) & 0xff; + buf[i*3+2] = (c>>0) & 0xff; + } + + msg->previews.back().imagedata = buf; + } + +} + } diff --git a/source/tools/atlas/GameInterface/MessagePasser.h b/source/tools/atlas/GameInterface/MessagePasser.h index 430de9f735..18fd8ae04b 100644 --- a/source/tools/atlas/GameInterface/MessagePasser.h +++ b/source/tools/atlas/GameInterface/MessagePasser.h @@ -10,9 +10,13 @@ struct QueryMessage; class MessagePasser { public: - virtual void Add(IMessage*)=0; // takes ownership of IMessage object + virtual void Add(IMessage*)=0; + // takes ownership of IMessage object + virtual IMessage* Retrieve()=0; - virtual void Query(QueryMessage*)=0; // blocks; caller retains ownership + + virtual void Query(QueryMessage*, void(*timeoutCallback)())=0; + // blocks; caller retains ownership of QueryMessage object }; extern MessagePasser* g_MessagePasser; diff --git a/source/tools/atlas/GameInterface/MessagePasserImpl.cpp b/source/tools/atlas/GameInterface/MessagePasserImpl.cpp index 97205683ee..4aff3a09b3 100644 --- a/source/tools/atlas/GameInterface/MessagePasserImpl.cpp +++ b/source/tools/atlas/GameInterface/MessagePasserImpl.cpp @@ -5,6 +5,10 @@ #include "lib/timer.h" +#if OS_WIN +#include "sysdep/win/win_internal.h" +#endif + using namespace AtlasMessage; @@ -48,7 +52,7 @@ IMessage* MessagePasserImpl::Retrieve() return msg; } -void MessagePasserImpl::Query(QueryMessage* qry) +void MessagePasserImpl::Query(QueryMessage* qry, void(*timeoutCallback)()) { debug_assert(qry); debug_assert(qry->GetType() == IMessage::Query); @@ -72,17 +76,48 @@ void MessagePasserImpl::Query(QueryMessage* qry) m_Queue.push(qry); m_Mutex.Unlock(); - // Wait until the query handler has handled the query and called sem_post - while (0 != (err = sem_wait(&sem))) + // Wait until the query handler has handled the query and called sem_post: + + // At least on Win32, it is necessary for the UI thread to run its event + // loop to avoid deadlocking the system (particularly when the game + // tries to show a dialog box); so timeoutCallback is called whenever we + // think it's necessary for that to happen. +#if OS_WIN + // On Win32, use MsgWaitForMultipleObjects, which waits on the semaphore + // but is also interrupted by incoming Windows-messages. + extern HANDLE sem_t_to_HANDLE(sem_t* sem); + HANDLE h = sem_t_to_HANDLE(&sem); + DWORD rc; + while (WAIT_OBJECT_0 != (rc = MsgWaitForMultipleObjects(1, &h, FALSE, INFINITE, QS_ALLINPUT))) { - // Keep retrying while EINTR - if (errno != EINTR) + // If woken up by a message, call the callback and try again + if (rc == WAIT_OBJECT_0 + 1) + timeoutCallback(); + else { - // Other errors are probably fatal - debug_warn("sem_wait failed"); + debug_warn("MsgWaitForMultipleObjects returned unexpected value"); return; } } +#else + // TODO: On non-Win32, I have no idea whether the same problem exists; but + // it might do, so call the callback every few seconds just in case it helps. + struct timespec abs_timeout; + clock_gettime(CLOCK_REALTIME, &abs_timeout); + abs_timeout.tv_sec += 2; + while (0 != (err = sem_timedwait(&sem, &abs_timeout))) + { + // If timed out, call callback and try again + if (errno == ETIMEDOUT) + timeoutCallback(); + // Keep retrying while EINTR, but other errors are probably fatal + else if (errno != EINTR) + { + debug_warn("sem_wait failed"); + return; // (leak the semaphore) + } + } +#endif // Clean up qry->m_Semaphore = NULL; diff --git a/source/tools/atlas/GameInterface/MessagePasserImpl.h b/source/tools/atlas/GameInterface/MessagePasserImpl.h index e5fd0e9e0d..1e4045c352 100644 --- a/source/tools/atlas/GameInterface/MessagePasserImpl.h +++ b/source/tools/atlas/GameInterface/MessagePasserImpl.h @@ -9,7 +9,7 @@ public: MessagePasserImpl(); virtual void Add(AtlasMessage::IMessage* msg); virtual AtlasMessage::IMessage* Retrieve(); - virtual void Query(AtlasMessage::QueryMessage* qry); + virtual void Query(AtlasMessage::QueryMessage* qry, void(*timeoutCallback)()); bool IsEmpty(); diff --git a/source/tools/atlas/GameInterface/Messages.h b/source/tools/atlas/GameInterface/Messages.h index 795924bec0..1727f82d12 100644 --- a/source/tools/atlas/GameInterface/Messages.h +++ b/source/tools/atlas/GameInterface/Messages.h @@ -49,7 +49,7 @@ MESSAGE(Screenshot, MESSAGE(Brush, ((int, width)) // number of vertices ((int, height)) - ((float*, data)) // width*height array, allocated with new[] + ((float*, data)) // width*height array, allocated with new[] (handler will delete[]) ); MESSAGE(BrushPreview, @@ -61,11 +61,26 @@ MESSAGE(BrushPreview, ////////////////////////////////////////////////////////////////////////// QUERY(GetTerrainGroups, - ((int, null)) // urgh - I can't do zero-input queries easily - , + , // no inputs ((std::vector, groupnames)) ); +struct sTerrainGroupPreview +{ + std::wstring name; + unsigned char* imagedata; // RGB*size*size, allocated with malloc (querier should free) +}; +QUERY(GetTerrainGroupPreviews, + ((std::wstring, groupname)) + ((int, imagewidth)) + ((int, imageheight)) + , + ((std::vector, previews)) + ); + + +QUERY(Exit,,); // no inputs nor outputs + ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// diff --git a/source/tools/atlas/GameInterface/MessagesSetup.h b/source/tools/atlas/GameInterface/MessagesSetup.h index f6fc790e3c..90765d77b8 100644 --- a/source/tools/atlas/GameInterface/MessagesSetup.h +++ b/source/tools/atlas/GameInterface/MessagesSetup.h @@ -81,11 +81,12 @@ MESSAGESTRUCT(MergeCommand) }; struct QueryMessage : public IMessage { Type GetType() const { return Type::Query; } - void Post() { g_MessagePasser->Query(this); } + void Post(); // defined in ScenarioEditor.cpp void* m_Semaphore; // for use by MessagePasser implementations (yay encapsulation) }; + #define QUERYSTRUCT(t) \ struct q##t : public QueryMessage { \ const char* GetName() const { return #t; } \ @@ -120,6 +121,8 @@ const bool NOMERGE = false; #include #include #include +#include + #define B_TYPE(elem) BOOST_PP_TUPLE_ELEM(2, 0, elem) #define B_NAME(elem) BOOST_PP_TUPLE_ELEM(2, 1, elem) #define B_CONSTRUCTORARGS(r, data, n, elem) BOOST_PP_COMMA_IF(n) B_TYPE(elem) BOOST_PP_CAT(B_NAME(elem),_) @@ -127,6 +130,15 @@ const bool NOMERGE = false; #define B_CONSTMEMBERS(r, data, n, elem) const B_TYPE(elem) B_NAME(elem); #define B_MEMBERS(r, data, n, elem) B_TYPE(elem) B_NAME(elem); +/* For each message type, generate something roughly like: + struct mBlah : public IMessage { + const char* GetName() const { return "Blah"; } + mBlah(int in0_, bool in1_) : in0(in0_), in1(in1_) {} + const int in0; + const bool in1; + } +*/ + #define MESSAGE(name, vals) \ MESSAGESTRUCT(name) \ m##name( BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORARGS, ~, vals) ) \ @@ -134,7 +146,24 @@ const bool NOMERGE = false; BOOST_PP_SEQ_FOR_EACH_I(B_CONSTMEMBERS, ~, vals) \ }; -#define QUERY(name, in_vals, out_vals) \ +#define COMMAND(name, merge, vals) \ + COMMANDDATASTRUCT(name) \ + d##name( BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORARGS, ~, vals) ) \ + : BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORINIT, ~, vals) {} \ + BOOST_PP_SEQ_FOR_EACH_I(B_CONSTMEMBERS, ~, vals) \ + }; \ + COMMANDSTRUCT(name, merge); + + +// Need different syntax depending on whether there are some input values in the query: + +#define QUERY_WITHOUT_INPUTS(name, in_vals, out_vals) \ + QUERYSTRUCT(name) \ + q##name() {} \ + BOOST_PP_SEQ_FOR_EACH_I(B_MEMBERS, ~, out_vals) /* other members */ \ + }; + +#define QUERY_WITH_INPUTS(name, in_vals, out_vals) \ QUERYSTRUCT(name) \ q##name( BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORARGS, ~, in_vals) ) \ : BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORINIT, ~, in_vals) {} \ @@ -142,13 +171,13 @@ const bool NOMERGE = false; BOOST_PP_SEQ_FOR_EACH_I(B_MEMBERS, ~, out_vals) \ }; -#define COMMAND(name, merge, vals) \ - COMMANDDATASTRUCT(name) \ - d##name( BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORARGS, ~, vals) ) \ - : BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORINIT, ~, vals) {} \ - BOOST_PP_SEQ_FOR_EACH_I(B_CONSTMEMBERS, ~, vals) \ - }; \ - COMMANDSTRUCT(name, merge); +#define QUERY(name, in_vals, out_vals) \ + BOOST_PP_IIF( \ + BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE((~)in_vals), 1), \ + QUERY_WITHOUT_INPUTS, \ + QUERY_WITH_INPUTS) \ + (name, in_vals, out_vals) + ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// @@ -166,8 +195,10 @@ const bool NOMERGE = false; #undef B_CONSTMEMBERS #undef B_MEMBERS #undef MESSAGE -#undef QUERY #undef COMMAND +#undef QUERY_WITHOUT_INPUTS +#undef QUERY_WITH_INPUTS +#undef QUERY }