Pyrogenesis  13997
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
GUIManager.cpp
Go to the documentation of this file.
1 /* Copyright (C) 2013 Wildfire Games.
2  * This file is part of 0 A.D.
3  *
4  * 0 A.D. is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * 0 A.D. is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
16  */
17 
18 #include "precompiled.h"
19 
20 #include "GUIManager.h"
21 
22 #include "CGUI.h"
23 
24 #include "lib/timer.h"
25 #include "ps/Filesystem.h"
26 #include "ps/CLogger.h"
27 #include "ps/Profile.h"
28 #include "ps/XML/Xeromyces.h"
31 
33 
34 
35 // General TODOs:
36 //
37 // A lot of the CGUI data could (and should) be shared between
38 // multiple pages, instead of treating them as completely independent, to save
39 // memory and loading time.
40 
41 
42 // called from main loop when (input) events are received.
43 // event is passed to other handlers if false is returned.
44 // trampoline: we don't want to make the HandleEvent implementation static
46 {
47  PROFILE("GUI event handler");
48  return g_GUI->HandleEvent(ev);
49 }
50 
52  m_ScriptInterface(scriptInterface)
53 {
54  ENSURE(ScriptInterface::GetCallbackData(scriptInterface.GetContext()) == NULL);
55  scriptInterface.SetCallbackData(this);
56 
57  scriptInterface.LoadGlobalScripts();
58 }
59 
61 {
62 }
63 
65 {
66  return !m_PageStack.empty();
67 }
68 
69 void CGUIManager::SwitchPage(const CStrW& pageName, CScriptVal initData)
70 {
71  m_PageStack.clear();
72  PushPage(pageName, initData);
73 }
74 
75 void CGUIManager::PushPage(const CStrW& pageName, CScriptVal initData)
76 {
77  m_PageStack.push_back(SGUIPage());
78  m_PageStack.back().name = pageName;
79  m_PageStack.back().initData = CScriptValRooted(m_ScriptInterface.GetContext(), initData);
80  LoadPage(m_PageStack.back());
81 }
82 
84 {
85  if (m_PageStack.size() < 2)
86  {
87  debug_warn(L"Tried to pop GUI page when there's < 2 in the stack");
88  return;
89  }
90 
91  m_PageStack.pop_back();
92 }
93 
94 void CGUIManager::DisplayMessageBox(int width, int height, const CStrW& title, const CStrW& message)
95 {
96  // Set up scripted init data for the standard message box window
97  CScriptValRooted data;
98  m_ScriptInterface.Eval("({})", data);
99  m_ScriptInterface.SetProperty(data.get(), "width", width, false);
100  m_ScriptInterface.SetProperty(data.get(), "height", height, false);
101  m_ScriptInterface.SetProperty(data.get(), "mode", 2, false);
102  m_ScriptInterface.SetProperty(data.get(), "title", std::wstring(title), false);
103  m_ScriptInterface.SetProperty(data.get(), "message", std::wstring(message), false);
104 
105  // Display the message box
106  PushPage(L"page_msgbox.xml", data.get());
107 }
108 
110 {
111  // If we're hotloading then try to grab some data from the previous page
112  CScriptValRooted hotloadData;
113  if (page.gui)
114  m_ScriptInterface.CallFunction(OBJECT_TO_JSVAL(page.gui->GetScriptObject()), "getHotloadData", hotloadData);
115 
116  page.inputs.clear();
117  page.gui.reset(new CGUI());
118  page.gui->Initialize();
119 
120  VfsPath path = VfsPath("gui") / page.name;
121  page.inputs.insert(path);
122 
123  CXeromyces xero;
124  if (xero.Load(g_VFS, path) != PSRETURN_OK)
125  // Fail silently (Xeromyces reported the error)
126  return;
127 
128  int elmt_page = xero.GetElementID("page");
129  int elmt_include = xero.GetElementID("include");
130 
131  XMBElement root = xero.GetRoot();
132 
133  if (root.GetNodeName() != elmt_page)
134  {
135  LOGERROR(L"GUI page '%ls' must have root element <page>", page.name.c_str());
136  return;
137  }
138 
139  XERO_ITER_EL(root, node)
140  {
141  if (node.GetNodeName() != elmt_include)
142  {
143  LOGERROR(L"GUI page '%ls' must only have <include> elements inside <page>", page.name.c_str());
144  continue;
145  }
146 
147  CStrW name (node.GetText().FromUTF8());
148 
149  PROFILE2("load gui xml");
150  PROFILE2_ATTR("name: %ls", name.c_str());
151 
152  TIMER(name.c_str());
153  VfsPath path = VfsPath("gui") / name;
154  page.gui->LoadXmlFile(path, page.inputs);
155  }
156 
157  // Remember this GUI page, in case the scripts call FindObjectByName
158  shared_ptr<CGUI> oldGUI = m_CurrentGUI;
159  m_CurrentGUI = page.gui;
160 
161  page.gui->SendEventToAll("load");
162 
163  // Call the init() function
164  if (!m_ScriptInterface.CallFunctionVoid(OBJECT_TO_JSVAL(page.gui->GetScriptObject()), "init", page.initData, hotloadData))
165  {
166  LOGERROR(L"GUI page '%ls': Failed to call init() function", page.name.c_str());
167  }
168 
169  m_CurrentGUI = oldGUI;
170 }
171 
173 {
174  for (PageStackType::iterator it = m_PageStack.begin(); it != m_PageStack.end(); ++it)
175  {
176  if (it->inputs.count(path))
177  {
178  LOGMESSAGE(L"GUI file '%ls' changed - reloading page '%ls'", path.string().c_str(), it->name.c_str());
179  LoadPage(*it);
180  // TODO: this can crash if LoadPage runs an init script which modifies the page stack and breaks our iterators
181  }
182  }
183 
184  return INFO::OK;
185 }
186 
188 {
189  CScriptVal data;
190  if (!m_ScriptInterface.CallFunction(OBJECT_TO_JSVAL(top()->GetScriptObject()), "getSavedGameData", data))
191  LOGERROR(L"Failed to call getSavedGameData() on the current GUI page");
192  return data;
193 }
194 
196 {
197  // We want scripts to have access to the raw input events, so they can do complex
198  // processing when necessary (e.g. for unit selection and camera movement).
199  // Sometimes they'll want to be layered behind the GUI widgets (e.g. to detect mousedowns on the
200  // visible game area), sometimes they'll want to intercepts events before the GUI (e.g.
201  // to capture all mouse events until a mouseup after dragging).
202  // So we call two separate handler functions:
203 
204  bool handled;
205 
206  {
207  PROFILE("handleInputBeforeGui");
208  if (m_ScriptInterface.CallFunction(OBJECT_TO_JSVAL(top()->GetScriptObject()), "handleInputBeforeGui", *ev, top()->FindObjectUnderMouse(), handled))
209  if (handled)
210  return IN_HANDLED;
211  }
212 
213  {
214  PROFILE("handle event in native GUI");
215  InReaction r = top()->HandleEvent(ev);
216  if (r != IN_PASS)
217  return r;
218  }
219 
220  {
221  PROFILE("handleInputAfterGui");
222  if (m_ScriptInterface.CallFunction(OBJECT_TO_JSVAL(top()->GetScriptObject()), "handleInputAfterGui", *ev, handled))
223  if (handled)
224  return IN_HANDLED;
225  }
226 
227  return IN_PASS;
228 }
229 
230 
232 {
233  return top()->GetPreDefinedColor(name, output);
234 }
235 
236 bool CGUIManager::IconExists(const CStr& str) const
237 {
238  return top()->IconExists(str);
239 }
240 
241 SGUIIcon CGUIManager::GetIcon(const CStr& str) const
242 {
243  return top()->GetIcon(str);
244 }
245 
247 {
248  // This can be called from scripts run by TickObjects,
249  // and we want to return it the same GUI page as is being ticked
250  if (m_CurrentGUI)
251  return m_CurrentGUI->FindObjectByName(name);
252  else
253  return top()->FindObjectByName(name);
254 }
255 
256 void CGUIManager::SendEventToAll(const CStr& eventName)
257 {
258  top()->SendEventToAll(eventName);
259 }
260 
262 {
263  PROFILE3("gui tick");
264 
265  // Save an immutable copy so iterators aren't invalidated by tick handlers
266  PageStackType pageStack = m_PageStack;
267 
268  for (PageStackType::iterator it = pageStack.begin(); it != pageStack.end(); ++it)
269  {
270  m_CurrentGUI = it->gui;
271  it->gui->TickObjects();
272  }
273  m_CurrentGUI.reset();
274 }
275 
277 {
278  PROFILE3_GPU("gui");
279 
280  for (PageStackType::iterator it = m_PageStack.begin(); it != m_PageStack.end(); ++it)
281  it->gui->Draw();
282 }
283 
285 {
286  for (PageStackType::iterator it = m_PageStack.begin(); it != m_PageStack.end(); ++it)
287  it->gui->UpdateResolution();
288 }
289 
291 {
292  return top()->GetScriptObject();
293 }
294 
295 // This returns a shared_ptr to make sure the CGUI doesn't get deallocated
296 // while we're in the middle of calling a function on it (e.g. if a GUI script
297 // calls SwitchPage)
298 shared_ptr<CGUI> CGUIManager::top() const
299 {
300  ENSURE(m_PageStack.size());
301  return m_PageStack.back().gui;
302 }
void LoadPage(SGUIPage &page)
Definition: GUIManager.cpp:109
InReaction gui_handler(const SDL_Event_ *ev)
Definition: GUIManager.cpp:45
void PopPage()
Unload the currently active GUI page, and make the previous page active.
Definition: GUIManager.cpp:83
Path VfsPath
VFS path of the form &quot;(dir/)*file?&quot;.
Definition: vfs_path.h:40
PageStackType m_PageStack
Definition: GUIManager.h:159
PSRETURN Load(const PIVFS &vfs, const VfsPath &filename)
Load from an XML file (with invisible XMB caching).
Definition: Xeromyces.cpp:65
const Status OK
Definition: status.h:386
#define LOGERROR
Definition: CLogger.h:35
void SetCallbackData(void *cbdata)
const PSRETURN PSRETURN_OK
Definition: Errors.h:103
tuple output
Definition: tests.py:116
Definition: Overlay.h:34
int GetElementID(const char *Name) const
Definition: XeroXMB.cpp:104
#define LOGMESSAGE
Definition: CLogger.h:32
CGUIManager(ScriptInterface &scriptInterface)
Definition: GUIManager.cpp:51
bool CallFunctionVoid(jsval val, const char *name)
Call the named property on the given object, with void return type and 0 arguments.
void PushPage(const CStrW &name, CScriptVal initData)
Load a new GUI page and make it active.
Definition: GUIManager.cpp:75
#define XERO_ITER_EL(parent_element, child_element)
Definition: Xeromyces.h:91
A trivial wrapper around a jsval.
Definition: ScriptVal.h:29
bool GetPreDefinedColor(const CStr &name, CColor &output)
See CGUI::GetPreDefinedColor; applies to the currently active page.
Definition: GUIManager.cpp:231
Base settings, all objects possess these settings in their m_BaseSettings Instructions can be found i...
Definition: IGUIObject.h:140
bool LoadGlobalScripts()
Load global scripts that most script contexts need, located in the /globalscripts directory...
CScriptValRooted initData
Definition: GUIManager.h:149
CGUIManager * g_GUI
Definition: GUIManager.cpp:32
boost::unordered_set< VfsPath > inputs
Definition: GUIManager.h:146
void UpdateResolution()
See CGUI::UpdateResolution; applies to all loaded pages.
Definition: GUIManager.cpp:284
std::vector< SGUIPage > PageStackType
Definition: GUIManager.h:158
The main object that represents a whole GUI page.
Definition: CGUI.h:97
CScriptVal GetSavedGameData()
Calls the current page&#39;s script function getSavedGameData() and returns the result.
Definition: GUIManager.cpp:187
bool IconExists(const CStr &str) const
See CGUI::IconExists; applies to the currently active page.
Definition: GUIManager.cpp:236
SGUIIcon GetIcon(const CStr &str) const
See CGUI::GetIcon; applies to the currently active page.
Definition: GUIManager.cpp:241
#define ENSURE(expr)
ensure the expression &lt;expr&gt; evaluates to non-zero.
Definition: debug.h:282
#define PROFILE2(region)
Starts timing from now until the end of the current scope.
Definition: Profiler2.h:446
static void * GetCallbackData(JSContext *cx)
Definition: path.h:75
const String & string() const
Definition: path.h:123
IGUIObject * FindObjectByName(const CStr &name) const
See CGUI::FindObjectByName; applies to the currently active page.
Definition: GUIManager.cpp:246
InReaction
Definition: input.h:34
Definition: input.h:40
JSObject * GetScriptObject()
See CGUI::GetScriptObject; applies to the currently active page.
Definition: GUIManager.cpp:290
bool Eval(const char *code)
void TickObjects()
See CGUI::TickObjects; applies to all loaded pages.
Definition: GUIManager.cpp:261
#define PROFILE(name)
Definition: Profile.h:195
i64 Status
Error handling system.
Definition: status.h:171
void DisplayMessageBox(int width, int height, const CStrW &title, const CStrW &message)
Display a modal message box with an &quot;OK&quot; button.
Definition: GUIManager.cpp:94
#define TIMER(description)
Measures the time taken to execute code up until end of the current scope; displays it via debug_prin...
Definition: timer.h:108
bool CallFunction(jsval val, const char *name, R &ret)
Call the named property on the given object, with return type R and 0 arguments.
#define PROFILE2_ATTR
Associates a string (with printf-style formatting) with the current region or event.
Definition: Profiler2.h:461
void SwitchPage(const CStrW &name, CScriptVal initData)
Load a new GUI page and make it active.
Definition: GUIManager.cpp:69
XMBElement GetRoot() const
Definition: XeroXMB.cpp:84
#define PROFILE3(name)
Definition: Profile.h:201
jsval get() const
Returns the current value (or JSVAL_VOID if uninitialised).
Definition: ScriptVal.cpp:45
Abstraction around a SpiderMonkey JSContext.
ScriptInterface & m_ScriptInterface
Definition: GUIManager.h:163
#define PROFILE3_GPU(name)
Definition: Profile.h:204
#define debug_warn(expr)
display the error dialog with the given text.
Definition: debug.h:324
void Draw()
See CGUI::Draw; applies to all loaded pages.
Definition: GUIManager.cpp:276
shared_ptr< CGUI > gui
Definition: GUIManager.h:151
shared_ptr< CGUI > top() const
Definition: GUIManager.cpp:298
External interface to the GUI system.
Definition: GUIManager.h:45
shared_ptr< CGUI > m_CurrentGUI
Definition: GUIManager.h:161
bool SetProperty(jsval obj, const char *name, const T &value, bool constant=false, bool enumerate=true)
Set the named property on the given object.
PIVFS g_VFS
Definition: Filesystem.cpp:30
bool HasPages()
Returns whether there are any current pages.
Definition: GUIManager.cpp:64
JSContext * GetContext() const
InReaction HandleEvent(const SDL_Event_ *ev)
Pass input events to the currently active GUI page.
Definition: GUIManager.cpp:195
void SendEventToAll(const CStr &eventName)
See CGUI::SendEventToAll; applies to the currently active page.
Definition: GUIManager.cpp:256
Status ReloadChangedFiles(const VfsPath &path)
Call when a file has bee modified, to hotload pages if their .xml files changed.
Definition: GUIManager.cpp:172
int GetNodeName() const
Definition: XeroXMB.cpp:166