diff --git a/binaries/data/tools/atlas/scripts/editorstate.js b/binaries/data/tools/atlas/scripts/editorstate.js new file mode 100644 index 0000000000..2d9e3f8dd9 --- /dev/null +++ b/binaries/data/tools/atlas/scripts/editorstate.js @@ -0,0 +1,114 @@ +// Define the default state settings. +// (It's done this way so that this script can be dynamically reloaded, +// and won't overwrite the previous runtime state but will still pick +// up any new properties) +var defaults = { + objectSettings: { + view: undefined, + selectedObjects: [], + playerID: 1, + actorSelections: [], + variantGroups: [] + } +}; + +defaults.objectSettings.toSObjectSettings = function () { + return { + player: this.playerID, + selections: this.actorSelections + }; +} + +defaults.objectSettings.onSelectionChange = function () { + if (! this.selectedObjects.length) + return; // TODO: do something sensible here + + // TODO: Support multiple selections + var selection = this.selectedObjects[0]; + + var settings = Atlas.Message.GetObjectSettings(this.view, selection).settings; + + this.playerID = settings.player; + this.actorSelections = settings.selections; + this.variantGroups = settings.variantgroups; + + this.notifyObservers(); +} + +/** + * Returns the current actor variation (as a list of chosen variants), based + * on its variant groups and the selection strings. This is equivalent to the + * variation rendered by the game. + */ +defaults.objectSettings.getActorVariation = function () +{ + var selectionMap = {}; + for each (var s in this.actorSelections) + selectionMap[s] = 1; + + var variation = []; + GROUP: for each (var group in this.variantGroups) { + for each (var variant in group) { + if (variant in selectionMap) { + variation.push(variant); + continue GROUP; + } + } + // None selected; default to first + variation.push(group[0]); + } + return variation; +} + +// Merges the 'defs' tree into 'obj', overwriting any old values with +// the same keys +function setDefaults(defs, obj) +{ + for (var k in defs) { + if (k in obj) + setDefaults(defs[k], obj[k]); + else + obj[k] = defaults[k]; + } +} + +function makeObservable(obj) +{ + // If the object has already been set up as observable, don't do any more work. + // (In particular, don't delete any old observers, so they don't get lost when + // this script is reloaded.) + if (typeof obj._observers != 'undefined') + return; + + obj._observers = []; + obj.registerObserver = function (callback) { + this._observers.push(callback); + } + obj.unregisterObserver = function (callback) { + this._observers = this._observers.filter(function (o) { return o !== callback }); + } + obj.notifyObservers = function () { + for each (var o in this._observers) + o(this); + }; +} + +function postObjectSettingsToGame(objectSettings) +{ + if (objectSettings.selectedObjects.length) + Atlas.Message.SetObjectSettings(objectSettings.view, + objectSettings.selectedObjects[0], objectSettings.toSObjectSettings()); +} + +function init() +{ + setDefaults(defaults, Atlas.State); + + makeObservable(Atlas.State.objectSettings); + Atlas.State.objectSettings.registerObserver(postObjectSettingsToGame); +} + +function deinit() { + Atlas.State.objectSettings.unregisterObserver(postObjectSettingsToGame); +} + diff --git a/binaries/data/tools/atlas/scripts/main.js b/binaries/data/tools/atlas/scripts/main.js index 1f571e4815..190f13bd0a 100644 --- a/binaries/data/tools/atlas/scripts/main.js +++ b/binaries/data/tools/atlas/scripts/main.js @@ -18,24 +18,56 @@ function loadScript(name /*, ...*/) var script = file.readAll(); // TODO: handle errors file.close(); - var args = []; - for (var i = 1; i < arguments.length; ++i) - args.push(arguments[i]) - var script = Atlas.LoadScript(name+'.js', script); - scriptReloader.add(name, args, filename); - - script.init.apply(null, args); + + // Extract the arguments which the function will actually use + // (Can't use Array.slice since arguments isn't an Array) + // (Have to do this rather than use arguments.length, since we sometimes + // pass unused bottomWindows into init and then destroy the window when realising + // it wasn't used, and then we mustn't send the destroyed window again later) + var args = []; + if (script.init) + for (var i = 1; i < 1+script.init.length; ++i) + args.push(arguments[i]); + scriptReloader.add(name, args, filename, script); + + if (script.init) + script.init.apply(null, args); + return script; } +/** + * Helper function for C++ to easily set dot-separated names + */ +function setValue(name, value) +{ + var obj = global; + var props = name.split("."); + for (var i = 0; i < props.length-1; ++i) + obj = obj[props[i]]; + obj[props[props.length-1]] = value; +} + +/** + * Helper function for C++ to easily get dot-separated names + */ +function getValue(name) +{ + var obj = global; + var props = name.split("."); + for (var i = 0; i < props.length; ++i) + obj = obj[props[i]]; + return obj; +} + function loadXML(name) { var relativePath = 'tools/atlas/' + name + '.xml'; var filename = new wxFileName(relativePath, wxPathFormat.UNIX); filename.normalize(wxPathNormalize.DOTS | wxPathNormalize.ABSOLUTE | wxPathNormalize.TILDE, - Atlas.GetDataDirectory()); // equivalent to MakeAbsolute(dir); + Atlas.GetDataDirectory()); // equivalent to MakeAbsolute(dir); var file = new wxFFile(filename.fullPath); var xml = file.readAll(); // TODO: handle errors @@ -46,11 +78,10 @@ function loadXML(name) return new XML(xml); } -function init() { /* dummy function to make the script reloader happy */ } - +// Automatically reload scripts from disk when they have been modified var scriptReloader = { timer: new wxTimer(), - scripts: [], // [ [filename,window], ... ] + scripts: [], // [ {name, args, filename, mtime, window, script}, ... ] notify: function () { for each (var script in scriptReloader.scripts) @@ -59,13 +90,18 @@ var scriptReloader = { if (mtime - script.mtime != 0) { print('*** Modifications detected - reloading "' + script.name + '"...\n'); + script.mtime = mtime; + + if (script.script && script.script.deinit) + script.script.deinit(); + if (script.name == 'main') { // Special case for this file to reload itself - var obj = loadScript(script.name, null); + script.script = loadScript(script.name, null); // Copy the important state into the new version of this file - obj.scriptReloader.scripts = scriptReloader.scripts; + script.script.scriptReloader.scripts = scriptReloader.scripts; // Stop this one scriptReloader.timer.stop(); } @@ -74,25 +110,30 @@ var scriptReloader = { // TODO: know which arguments are really windows that should be regenerated for each (var window in script.args) window.destroyChildren(); - loadScript.apply(null, [script.name].concat(script.args)); + script.script = loadScript.apply(null, [script.name].concat(script.args)); for each (var window in script.args) window.layout(); } } } }, - add: function (name, args, filename) + add: function (name, args, filename, script) { - for each (var script in this.scripts) - if (script.name == name) + for each (var s in this.scripts) + if (s.name == name) return; // stop if this is already loaded - this.scripts.push({ name:name, args:args, filename:filename, mtime:filename.modificationTime }); + + this.scripts.push({ name:name, args:args, filename:filename, mtime:filename.modificationTime, script:script }); } }; scriptReloader.timer.onNotify = scriptReloader.notify; scriptReloader.timer.start(1000); scriptReloader.add('main', null, getScriptFilename('main')); +loadScript('editorstate'); + // Export global functions: global.loadScript = loadScript; global.loadXML = loadXML; +global.setValue = setValue; +global.getValue = getValue; diff --git a/binaries/data/tools/atlas/scripts/section/object.js b/binaries/data/tools/atlas/scripts/section/object.js new file mode 100644 index 0000000000..602aceb131 --- /dev/null +++ b/binaries/data/tools/atlas/scripts/section/object.js @@ -0,0 +1,120 @@ +function setObjectFilter(objectList, objects, type) +{ + objectList.freeze(); + objectList.clear(); + var ids = []; + for each (var object in objects) { + if (object.type == type) { + ids.push(object.id); + objectList.append(object.name); + } + } + objectList.objectIDs = ids; + objectList.thaw(); +} + +var g_observer; + +function init(window, bottomWindow) +{ + window.sizer = new wxBoxSizer(wxOrientation.VERTICAL); + + var objects = Atlas.Message.GetObjectsList().objects; + + var objectList = new wxListBox(window, -1, wxDefaultPosition, wxDefaultSize, [], + wxListBox.SINGLE | wxListBox.HSCROLL); + objectList.onListBox = function (evt) { + if (evt.selection == -1) + return; + var id = objectList.objectIDs[evt.selection]; + Atlas.SetCurrentToolWith('PlaceObject', id); + }; + setObjectFilter(objectList, objects, 0); + + var groupSelector = new wxChoice(window, -1, wxDefaultPosition, wxDefaultSize, + ["Entities", "Actors (all)"] + ); + groupSelector.onChoice = function (evt) { + setObjectFilter(objectList, objects, evt.selection); + }; + + window.sizer.add(groupSelector, 0, wxStretch.EXPAND); + window.sizer.add(objectList, 1, wxStretch.EXPAND); + + + bottomWindow.sizer = new wxBoxSizer(wxOrientation.VERTICAL); + var playerSelector = new wxChoice(bottomWindow, -1, wxDefaultPosition, wxDefaultSize, + ["Gaia", "Player 1", "Player 2", "Player 3", "Player 4", "Player 5", "Player 6", "Player 7", "Player 8"] + ); + bottomWindow.sizer.add(playerSelector); + + playerSelector.selection = Atlas.State.objectSettings.playerID; + playerSelector.onChoice = function (evt) { + Atlas.State.objectSettings.playerID = evt.selection; + Atlas.State.objectSettings.notifyObservers(); + }; + function updatePlayerSelector() { + playerSelector.selection = Atlas.State.objectSettings.playerID; + } + + + var variationControl = new wxScrolledWindow(bottomWindow, -1); + variationControl.setScrollRate(0, 5); + variationControl.sizer = new wxBoxSizer(wxOrientation.VERTICAL); + var variationControlBox = new wxStaticBoxSizer(new wxStaticBox(bottomWindow, -1, "Actor Variation"), wxOrientation.VERTICAL); + variationControl.sizer.minSize = new wxSize(160, -1); + variationControlBox.add(variationControl, 1); + bottomWindow.sizer.add(variationControlBox, 1); + + function onVariationSelect() { + // It's possible for a variant name to appear in multiple groups. + // If so, assume that all the names in each group are the same, so + // we don't have to worry about some impossible combinations (e.g. + // one group "a,b", a second "b,c", and a third "c,a", where's there's + // no set of selections that matches one (and only one) of each group). + // + // So... When a combo box is changed from 'a' to 'b', add 'b' to the new + // selections and make sure any other combo boxes containing both 'a' and + // 'b' no longer contain 'a'. + + var sel = this.stringSelection; + var selections = [ sel ]; + for each (var c in variationControl.children) + if (c.findString(sel) == wxNOT_FOUND) + selections.push(c.stringSelection); + + Atlas.State.objectSettings.actorSelections = selections; + Atlas.State.objectSettings.notifyObservers(); + } + + function updateVariationControl() { + variationControl.sizer.clear(true); + var settings = Atlas.State.objectSettings; + var variation = settings.getActorVariation(); + for (var i = 0; i < settings.variantGroups.length; ++i) { + var choice = new wxChoice(variationControl, -1, wxDefaultPosition, new wxSize(80, -1), + settings.variantGroups[i]); + choice.onChoice = onVariationSelect; + choice.stringSelection = variation[i]; + variationControl.sizer.add(choice, 0, wxStretch.EXPAND); + } + variationControlBox.layout(); + variationControl.sizer.layout(); + bottomWindow.sizer.layout(); + } + + g_observer = function() { + updatePlayerSelector(); + updateVariationControl(); + }; + Atlas.State.objectSettings.registerObserver(g_observer); + + // Initialise the controls + g_observer(); +} + +function deinit() +{ + Atlas.State.objectSettings.unregisterObserver(g_observer); +} + diff --git a/binaries/data/tools/atlas/scripts/section/terrain.js b/binaries/data/tools/atlas/scripts/section/terrain.js index b66f7c3aa9..5d16ec0af1 100644 --- a/binaries/data/tools/atlas/scripts/section/terrain.js +++ b/binaries/data/tools/atlas/scripts/section/terrain.js @@ -94,6 +94,8 @@ TerrainPreviewPage.prototype = { itemSizer.cols = numCols; }; + // TODO: Do something clever like load the preview images asynchronously, + // to avoid the annoying freeze when switching tabs var previews = Atlas.Message.GetTerrainGroupPreviews(this.name, w, h).previews; var i = 0; var names = []; @@ -113,6 +115,8 @@ TerrainPreviewPage.prototype = { itemSizer.add(imgSizer, 0, wxAlignment.CENTRE | wxStretch.EXPAND); } + // TODO: fix keyboard navigation of the terrain previews + this.panel.layout(); this.loaded = true; @@ -124,10 +128,9 @@ function init(window, bottomWindow) window.sizer = new wxBoxSizer(wxOrientation.VERTICAL); var tools = [ - /* text label; internal tool name; button */ - [ 'Modify', 'AlterElevation', undefined ], - [ 'Flatten', 'FlattenElevation', undefined ], - [ 'Paint', 'PaintTerrain', undefined ] + { label: 'Modify', name: 'AlterElevation' }, + { label: 'Flatten', name: 'FlattenElevation' }, + { label: 'Paint', name: 'PaintTerrain' }, ]; var selectedTool = null; // null if none selected, else an element of 'tools' @@ -135,9 +138,9 @@ function init(window, bottomWindow) window.sizer.add(toolSizer, 0, wxStretch.EXPAND); for each (var tool in tools) { - var button = new wxButton(window, -1, tool[0]); + var button = new wxButton(window, -1, tool.label); toolSizer.add(button, 1); - tool[2] = button; + tool.button = button; // Explicitly set the background to the default colour, so that the button // is always owner-drawn (by the wxButton code), rather than initially using the @@ -157,15 +160,16 @@ function init(window, bottomWindow) { // Disable the old tool if (selectedTool) - selectedTool[2].backgroundColour = wxSystemSettings.getColour(wxSystemSettings.COLOUR_BTNFACE); + selectedTool.button.backgroundColour = wxSystemSettings.getColour(wxSystemSettings.COLOUR_BTNFACE); // Enable the new one selectedTool = tool; this.backgroundColour = new wxColour(0xEE, 0xCC, 0x55); - Atlas.SetCurrentTool(tool[1]); + Atlas.SetCurrentTool(tool.name); brush.send(); } }; })(tool); + // TODO: Need to make this interact properly with Tools.cpp/RegisterToolButton so all the buttons are in sync } var brushSizer = new wxStaticBoxSizer(new wxStaticBox(window, -1, 'Brush'), wxOrientation.VERTICAL); diff --git a/source/tools/atlas/AtlasScript/ScriptInterface.cpp b/source/tools/atlas/AtlasScript/ScriptInterface.cpp index 52b827ad82..7fafbe4016 100644 --- a/source/tools/atlas/AtlasScript/ScriptInterface.cpp +++ b/source/tools/atlas/AtlasScript/ScriptInterface.cpp @@ -18,6 +18,10 @@ #include "ScriptInterface.h" #include +#ifndef _WIN32 +# include +# include +#endif #include "js/jsapi.h" @@ -37,30 +41,49 @@ #include "GameInterface/Shareable.h" #include "GameInterface/Messages.h" + #include #include +#include + #define FAIL(msg) do { JS_ReportError(cx, msg); return false; } while (false) -const int RUNTIME_SIZE = 1024*1024; // TODO: how much memory is needed? +const int RUNTIME_SIZE = 4*1024*1024; // TODO: how much memory is needed? const int STACK_CHUNK_SIZE = 8192; +SubmitCommand g_SubmitCommand; // TODO: globals are ugly + //////////////////////////////////////////////////////////////// namespace { + template + void ReportError(JSContext* cx, const char* title) + { + // TODO: SetPendingException turns the error into a JS-catchable exception, + // but the error report doesn't say anything useful like the line number, + // so I'm just using ReportError instead for now (and failures are uncatchable + // and will terminate the whole script) + //JS_SetPendingException(cx, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "%s: Unhandled type", title))); +#ifdef _WIN32 + JS_ReportError(cx, "%s: Unhandled type", title); +#else + // Give a more informative message on GCC + int status; + char* name = abi::__cxa_demangle(typeid(T).name(), 0, 0, &status); + JS_ReportError(cx, "%s: Unhandled type '%s'", title, name); + free(name); +#endif + } + // Use templated structs instead of functions, so that we can use partial specialisation: template struct FromJSVal { static bool Convert(JSContext* cx, jsval WXUNUSED(v), T& WXUNUSED(out)) { - JS_ReportError(cx, "Unrecognised argument type"); - // TODO: SetPendingException turns the error into a JS-catchable exception, - // but the error report doesn't say anything useful like the line number, - // so I'm just using ReportError instead for now (and failures are uncatchable - // and will terminate the whole script) - //JS_SetPendingException(cx, STRING_TO_JSVAL(JS_NewStringCopyZ(cx, "Unrecognised argument type"))); + ReportError(cx, "FromJSVal"); return false; } }; @@ -92,7 +115,18 @@ namespace static bool Convert(JSContext* cx, jsval v, int& out) { int32 ret; - if (! JS_ValueToInt32(cx, v, &ret)) return false; + if (! JS_ValueToECMAInt32(cx, v, &ret)) return false; + out = ret; + return true; + } + }; + + template<> struct FromJSVal + { + static bool Convert(JSContext* cx, jsval v, size_t& out) + { + uint32 ret; + if (! JS_ValueToECMAUint32(cx, v, &ret)) return false; out = ret; return true; } @@ -163,12 +197,13 @@ namespace }; //////////////////////////////////////////////////////////////// - + // Primitive types: + template struct ToJSVal { static jsval Convert(JSContext* cx, const T& WXUNUSED(val)) { - JS_ReportError(cx, "Unrecognised query return type"); + ReportError(cx, "ToJSVal"); return JSVAL_VOID; } }; @@ -191,6 +226,14 @@ namespace } }; + template<> struct ToJSVal + { + static jsval Convert(JSContext* WXUNUSED(cx), const size_t& val) + { + return INT_TO_JSVAL(val); + } + }; + template<> struct ToJSVal { static jsval Convert(JSContext* cx, const wxString& val) @@ -221,6 +264,9 @@ namespace } }; + //////////////////////////////////////////////////////////////// + // Compound types: + template struct ToJSVal > { static jsval Convert(JSContext* cx, const std::vector& val) @@ -247,6 +293,7 @@ namespace }; //////////////////////////////////////////////////////////////// + // AtlasMessage structures: template<> struct ToJSVal { @@ -269,6 +316,65 @@ namespace } }; + template<> struct ToJSVal + { + static jsval Convert(JSContext* cx, const AtlasMessage::sObjectsListItem& val) + { + JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); + if (! obj) return JSVAL_VOID; + JS_AddRoot(cx, &obj); + JS_DefineProperty(cx, obj, "id", ToJSVal::Convert(cx, *val.id), NULL, NULL, JSPROP_ENUMERATE); + JS_DefineProperty(cx, obj, "name", ToJSVal::Convert(cx, *val.name), NULL, NULL, JSPROP_ENUMERATE); + JS_DefineProperty(cx, obj, "type", ToJSVal::Convert(cx, val.type), NULL, NULL, JSPROP_ENUMERATE); + JS_RemoveRoot(cx, &obj); + return OBJECT_TO_JSVAL(obj); + } + }; + + template<> struct ToJSVal + { + static jsval Convert(JSContext* cx, const AtlasMessage::sObjectSettings& val) + { + JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL); + if (! obj) return JSVAL_VOID; + JS_AddRoot(cx, &obj); + JS_DefineProperty(cx, obj, "player", ToJSVal::Convert(cx, val.player), NULL, NULL, JSPROP_ENUMERATE); + JS_DefineProperty(cx, obj, "selections", ToJSVal >::Convert(cx, *val.selections), NULL, NULL, JSPROP_ENUMERATE); + JS_DefineProperty(cx, obj, "variantgroups", ToJSVal > >::Convert(cx, *val.variantgroups), NULL, NULL, JSPROP_ENUMERATE); + JS_RemoveRoot(cx, &obj); + return OBJECT_TO_JSVAL(obj); + } + }; + + template<> struct FromJSVal + { + static bool Convert(JSContext* cx, jsval v, AtlasMessage::sObjectSettings& out) + { + JSObject* obj; + if (! JS_ValueToObject(cx, v, &obj) || obj == NULL) + FAIL("Argument must be an array"); + jsval val; + + int player; + if (! JS_GetProperty(cx, obj, "player", &val)) + FAIL("Failed to get 'player'"); + if (! ScriptInterface::FromJSVal(cx, val, player)) + FAIL("Failed to convert 'player'"); + out.player = player; + + std::vector selections; + if (! JS_GetProperty(cx, obj, "selections", &val)) + FAIL("Failed to get 'selections'"); + if (! ScriptInterface::FromJSVal(cx, val, selections)) + FAIL("Failed to convert 'selections'"); + out.selections = selections; + + // variantgroups is only used in engine-to-editor, so we don't + // bother converting it here + + return true; + } + }; } template bool ScriptInterface::FromJSVal(JSContext* cx, jsval v, T& out) @@ -284,10 +390,10 @@ template jsval ScriptInterface::ToJSVal(JSContext* cx, const T& v) // Explicit instantiation of functions that would otherwise be unused in this file // but are required for linking with other files template bool ScriptInterface::FromJSVal(JSContext*, jsval, wxString&); - template bool ScriptInterface::FromJSVal(JSContext*, jsval, float&); - template jsval ScriptInterface::ToJSVal(JSContext*, wxString const&); +template jsval ScriptInterface::ToJSVal(JSContext*, int const&); +template jsval ScriptInterface::ToJSVal >(JSContext*, std::vector const&); //////////////////////////////////////////////////////////////// @@ -327,6 +433,7 @@ namespace wxLogWarning(_T("%s"), logMessage.c_str()); else wxLogError(_T("%s"), logMessage.c_str()); + VALGRIND_PRINTF_BACKTRACE("->"); wxPrintf(_T("wxJS %s: %s\n--------\n"), isWarning ? _T("warning") : _T("error"), logMessage.c_str()); } @@ -377,7 +484,8 @@ ScriptInterface_impl::ScriptInterface_impl() m_cx = JS_NewContext(m_rt, STACK_CHUNK_SIZE); assert(m_cx); - JS_BeginRequest(m_cx); // if you get linker errors, see the comment in the .h about JS_THREADSAFE + JS_BeginRequest(m_cx); // if you get linker errors, see the comment in ScriptInterface.h about JS_THREADSAFE + // (TODO: are we using requests correctly? (Probably not; how much does it matter?)) JS_SetContextPrivate(m_cx, NULL); @@ -400,9 +508,10 @@ ScriptInterface_impl::ScriptInterface_impl() JS_DefineFunction(m_cx, m_glob, "print", ::print, 0, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT); - m_atlas = JS_DefineObject(m_cx, m_glob, "Atlas", NULL, NULL, JSPROP_READONLY|JSPROP_PERMANENT); + m_atlas = JS_DefineObject(m_cx, m_glob, "Atlas", NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT); JS_DefineFunction(m_cx, m_atlas, "ForceGC", ::ForceGC, 0, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT); JS_DefineFunction(m_cx, m_atlas, "LoadScript", ::LoadScript, 2, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT); + JS_DefineObject(m_cx, m_atlas, "State", NULL, NULL, JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT); RegisterMessages(m_atlas); } @@ -435,9 +544,10 @@ void ScriptInterface_impl::Register(const char* name, JSNative fptr, uintN nargs } -ScriptInterface::ScriptInterface() - : m(new ScriptInterface_impl) +ScriptInterface::ScriptInterface(SubmitCommand submitCommand) + : m(new ScriptInterface_impl()) { + g_SubmitCommand = submitCommand; } ScriptInterface::~ScriptInterface() @@ -459,6 +569,55 @@ void ScriptInterface::Register(const char* name, JSNative fptr, size_t nargs) m->Register(name, fptr, (uintN)nargs); } +JSContext* ScriptInterface::GetContext() +{ + return m->m_cx; +} + +void ScriptInterface::AddRoot(void* ptr) +{ + JS_AddRoot(m->m_cx, ptr); +} + +void ScriptInterface::RemoveRoot(void* ptr) +{ + JS_RemoveRoot(m->m_cx, ptr); +} + +void ScriptInterface::SetValue_(const wxString& name, jsval val) +{ + jsval jsName = ToJSVal(m->m_cx, name); + + const uintN argc = 2; + jsval argv[argc] = { jsName, val }; + jsval rval; + JSBool ok = JS_CallFunctionName(m->m_cx, m->m_glob, "setValue", argc, argv, &rval); // TODO: error checking +} + +bool ScriptInterface::GetValue_(const wxString& name, jsval& ret) +{ + jsval jsName = ToJSVal(m->m_cx, name); + + const uintN argc = 1; + jsval argv[argc] = { jsName }; + return JS_CallFunctionName(m->m_cx, m->m_glob, "getValue", argc, argv, &ret); +} + +void ScriptInterface::Eval(const wxString& script) +{ + jsval rval; + JSBool ok = JS_EvaluateScript(m->m_cx, m->m_glob, + script.mb_str(), script.length(), NULL, 0, &rval); + // TODO: error checking +} + +bool ScriptInterface::Eval_(const wxString& script, jsval& rval) +{ + JSBool ok = JS_EvaluateScript(m->m_cx, m->m_glob, + script.mb_str(), script.length(), NULL, 0, &rval); + return ok; +} + void ScriptInterface::LoadScript(const wxString& filename, const wxString& code) { size_t codeLength; @@ -540,6 +699,15 @@ std::pair ScriptInterface::LoadScriptAsSidebar(const wxStrin return JS_TRUE; \ } +#define COMMAND(name, merge, vals) \ + JSBool call_##name(JSContext* cx, JSObject* WXUNUSED(obj), uintN WXUNUSED(argc), jsval* argv, jsval* WXUNUSED(rval)) \ + { \ + (void)cx; (void)argv; /* avoid 'unused parameter' warnings */ \ + BOOST_PP_SEQ_FOR_EACH_I(CONVERT_ARGS, ~, vals) \ + g_SubmitCommand(new AtlasMessage::m##name (AtlasMessage::d##name ( BOOST_PP_SEQ_FOR_EACH_I(ARG_LIST, ~, vals) ))); \ + return JS_TRUE; \ + } + #define QUERY(name, in_vals, out_vals) \ JSBool call_##name(JSContext* cx, JSObject* WXUNUSED(obj), uintN WXUNUSED(argc), jsval* argv, jsval* rval) \ { \ @@ -554,8 +722,6 @@ std::pair ScriptInterface::LoadScriptAsSidebar(const wxStrin return JS_TRUE; \ } -#define COMMAND(name, merge, vals) - #define MESSAGES_SKIP_SETUP #define MESSAGES_SKIP_STRUCTS @@ -570,6 +736,7 @@ namespace } #undef MESSAGE +#undef COMMAND #undef QUERY void ScriptInterface_impl::RegisterMessages(JSObject* parent) @@ -583,13 +750,20 @@ void ScriptInterface_impl::RegisterMessages(JSObject* parent) ret = JS_DefineFunction(m_cx, obj, #name, call_##name, BOOST_PP_SEQ_SIZE((~)vals)-1, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT); + #define COMMAND(name, merge, vals) \ + ret = JS_DefineFunction(m_cx, obj, #name, call_##name, BOOST_PP_SEQ_SIZE((~)vals)-1, \ + JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT); + #define QUERY(name, in_vals, out_vals) \ ret = JS_DefineFunction(m_cx, obj, #name, call_##name, BOOST_PP_SEQ_SIZE((~)in_vals)-1, \ JSPROP_ENUMERATE|JSPROP_READONLY|JSPROP_PERMANENT); - - // TODO: #define COMMAND(name, merge, vals) ... #undef INCLUDED_MESSAGES - + #include "GameInterface/Messages.h" + + #undef MESSAGE + #undef COMMAND + #undef QUERY } + diff --git a/source/tools/atlas/AtlasScript/ScriptInterface.h b/source/tools/atlas/AtlasScript/ScriptInterface.h index 1c4e9971af..bfab1766d3 100644 --- a/source/tools/atlas/AtlasScript/ScriptInterface.h +++ b/source/tools/atlas/AtlasScript/ScriptInterface.h @@ -37,15 +37,23 @@ class wxWindow; class wxString; class wxPanel; +namespace AtlasMessage { struct mWorldCommand; } +typedef void (*SubmitCommand)(AtlasMessage::mWorldCommand* command); + struct ScriptInterface_impl; class ScriptInterface { public: - ScriptInterface(); + ScriptInterface(SubmitCommand submitCommand); ~ScriptInterface(); void SetCallbackData(void* cbdata); static void* GetCallbackData(JSContext* cx); + template void SetValue(const wxString& name, const T& val); + template bool GetValue(const wxString& name, T& ret); + void Eval(const wxString& script); + template bool Eval(const wxString& script, T& ret); + // Defined elsewhere: // template // void RegisterFunction(const char* functionName); @@ -59,8 +67,43 @@ public: template static bool FromJSVal(JSContext* cx, jsval val, T& ret); template static jsval ToJSVal(JSContext* cx, const T& val); private: + JSContext* GetContext(); + void AddRoot(void* ptr); + void RemoveRoot(void* ptr); + void SetValue_(const wxString& name, jsval val); + bool GetValue_(const wxString& name, jsval& ret); + bool Eval_(const wxString& name, jsval& ret); + void Register(const char* name, JSNative fptr, size_t nargs); std::auto_ptr m; // The nasty macro/template bits are split into a separate file so you don't have to look at them #include "NativeWrapper.inl" + +template +void ScriptInterface::SetValue(const wxString& name, const T& val) +{ + return SetValue_(name, ToJSVal(GetContext(), val)); +} + +template +bool ScriptInterface::GetValue(const wxString& name, T& ret) +{ + jsval jsRet; + if (! GetValue_(name, jsRet)) return false; + AddRoot(&jsRet); // root it while we do some more work (TODO: is this really necessary?) + bool ok = FromJSVal(GetContext(), jsRet, ret); + RemoveRoot(&jsRet); + return ok; +} + +template +bool ScriptInterface::Eval(const wxString& script, T& ret) +{ + jsval jsRet; + if (! Eval_(script, jsRet)) return false; + AddRoot(&jsRet); // root it while we do some more work + bool ok = FromJSVal(GetContext(), jsRet, ret); + RemoveRoot(&jsRet); + return ok; +} diff --git a/source/tools/atlas/AtlasUI/ActorViewer/ActorViewer.cpp b/source/tools/atlas/AtlasUI/ActorViewer/ActorViewer.cpp index 05eff0e7b0..b90d926741 100644 --- a/source/tools/atlas/AtlasUI/ActorViewer/ActorViewer.cpp +++ b/source/tools/atlas/AtlasUI/ActorViewer/ActorViewer.cpp @@ -27,7 +27,7 @@ #include "ScenarioEditor/Tools/Common/Tools.h" #include "ScenarioEditor/ScenarioEditor.h" #include "ScenarioEditor/Sections/Environment/LightControl.h" -#include "ScenarioEditor/Sections/Object/VariationControl.h" +//#include "ScenarioEditor/Sections/Object/VariationControl.h" #include "GameInterface/Messages.h" @@ -182,15 +182,20 @@ static void SendToGame(const AtlasMessage::sEnvironmentSettings& settings) POST_COMMAND(SetEnvironmentSettings, (settings)); } -ActorViewer::ActorViewer(wxWindow* parent) +ActorViewer::ActorViewer(wxWindow* parent, ScriptInterface& scriptInterface) : wxFrame(parent, wxID_ANY, _("Actor Viewer"), wxDefaultPosition, wxSize(800, 600)), m_CurrentSpeed(0.f), m_BackgroundColour(wxColour(255, 255, 255)), m_ToggledWalking(false), m_ToggledWireframe(false), m_ToggledGround(true), m_ToggledShadows(true), m_ToggledStats(false), - m_ObjectSettings(m_ObjectSelection, AtlasMessage::eRenderView::ACTOR) + m_ScriptInterface(scriptInterface), + m_ObjectSettings(m_ObjectSelection, m_ScriptInterface) { SetIcon(wxIcon(_T("ICON_ActorEditor"))); + // XXX: need to init m_ScriptInterface + + m_ObjectSettings.Init(AtlasMessage::eRenderView::ACTOR); + SnapSplitterWindow* splitter = new SnapSplitterWindow(this, 0); splitter->SetDefaultSashPosition(250); @@ -301,7 +306,7 @@ ActorViewer::ActorViewer(wxWindow* parent) wxSizer* bottomRightSizer = new wxBoxSizer(wxVERTICAL); wxSizer* playButtonSizer = new wxBoxSizer(wxHORIZONTAL); wxSizer* optionButtonSizer = new wxBoxSizer(wxVERTICAL); - wxSizer* variationSizer = new wxStaticBoxSizer(wxVERTICAL, sidePanel, _("Variation")); +// wxSizer* variationSizer = new wxStaticBoxSizer(wxVERTICAL, sidePanel, _("Variation")); playButtonSizer->Add(new wxButton(sidePanel, ID_Play, _("Play")), wxSizerFlags().Proportion(1)); playButtonSizer->Add(new wxButton(sidePanel, ID_Pause, _("Pause")), wxSizerFlags().Proportion(1)); @@ -315,7 +320,7 @@ ActorViewer::ActorViewer(wxWindow* parent) optionButtonSizer->Add(Tooltipped(new wxButton(sidePanel, ID_ToggleShadows, _("Shadows")), _("Toggle shadow rendering")), wxSizerFlags().Expand()); optionButtonSizer->Add(Tooltipped(new wxButton(sidePanel, ID_ToggleStats, _("Poly count")), _("Toggle polygon-count statistics - turn off ground and shadows for more useful data")), wxSizerFlags().Expand()); - variationSizer->Add(new VariationControl(sidePanel, m_ObjectSettings), wxSizerFlags().Expand().Proportion(1)); +// variationSizer->Add(new VariationControl(sidePanel, m_ObjectSettings), wxSizerFlags().Expand().Proportion(1)); mainSizer->Add(m_TreeCtrl, wxSizerFlags().Expand().Proportion(1)); mainSizer->Add(bottomSizer, wxSizerFlags().Expand()); @@ -328,7 +333,7 @@ ActorViewer::ActorViewer(wxWindow* parent) bottomRightSizer->Add(m_AnimationBox, wxSizerFlags().Expand()); bottomRightSizer->Add(playButtonSizer, wxSizerFlags().Expand()); - bottomRightSizer->Add(variationSizer, wxSizerFlags().Expand().Proportion(1)); +// bottomRightSizer->Add(variationSizer, wxSizerFlags().Expand().Proportion(1)); sidePanel->SetSizer(mainSizer); diff --git a/source/tools/atlas/AtlasUI/ActorViewer/ActorViewer.h b/source/tools/atlas/AtlasUI/ActorViewer/ActorViewer.h index 88557bfc77..ab60e1533e 100644 --- a/source/tools/atlas/AtlasUI/ActorViewer/ActorViewer.h +++ b/source/tools/atlas/AtlasUI/ActorViewer/ActorViewer.h @@ -25,11 +25,12 @@ class wxTreeCtrl; class wxTreeEvent; +class ScriptInterface; class ActorViewer : public wxFrame { public: - ActorViewer(wxWindow* parent); + ActorViewer(wxWindow* parent, ScriptInterface& scriptInterface); private: void SetActorView(bool flushCache = false); @@ -49,8 +50,10 @@ private: wxString m_CurrentActor; float m_CurrentSpeed; + ScriptInterface& m_ScriptInterface; + Observable > m_ObjectSelection; - Observable m_ObjectSettings; + ObjectSettings m_ObjectSettings; wxColour m_BackgroundColour; bool m_ToggledWireframe, m_ToggledWalking, m_ToggledGround, m_ToggledShadows, m_ToggledStats; diff --git a/source/tools/atlas/AtlasUI/General/Observable.h b/source/tools/atlas/AtlasUI/General/Observable.h index 0e6cd4fe8c..eb4f5fcbb7 100644 --- a/source/tools/atlas/AtlasUI/General/Observable.h +++ b/source/tools/atlas/AtlasUI/General/Observable.h @@ -53,7 +53,7 @@ public: template explicit Observable(const T1& a1) : T(a1) {} template - explicit Observable(T1& a1, T2 a2) : T(a1, a2) {} + explicit Observable(T1& a1, T2& a2) : T(a1, a2) {} template ObservableConnection RegisterObserver(int order, void (C::*callback) (const T&), C* obj) { diff --git a/source/tools/atlas/AtlasUI/Misc/DLLInterface.cpp b/source/tools/atlas/AtlasUI/Misc/DLLInterface.cpp index 0fb9d5e79e..8ffeea9a14 100644 --- a/source/tools/atlas/AtlasUI/Misc/DLLInterface.cpp +++ b/source/tools/atlas/AtlasUI/Misc/DLLInterface.cpp @@ -155,6 +155,10 @@ ATLASDLLIMPEXP void Atlas_ReportError() ///ReportError(); // janwas: disabled until ErrorReporter.cpp compiles } +void ScenarioEditorSubmitCommand(AtlasMessage::mWorldCommand* command) +{ + ScenarioEditor::GetCommandProc().Submit(new WorldCommand(command)); +} class AtlasDLLApp : public wxApp { @@ -188,7 +192,6 @@ public: wxFrame* frame; #define MAYBE(t) if (g_InitialWindowType == _T(#t)) frame = new t(NULL); else MAYBE(ActorEditor) - MAYBE(ActorViewer) MAYBE(ColourTester) #ifdef USE_AOE3ED MAYBE(ArchiveViewer) @@ -198,9 +201,14 @@ public: // else if (g_InitialWindowType == _T("ScenarioEditor")) { - m_ScriptInterface = new ScriptInterface(); + m_ScriptInterface = new ScriptInterface(&ScenarioEditorSubmitCommand); frame = new ScenarioEditor(NULL, *m_ScriptInterface); } + else if (g_InitialWindowType == _T("ActorViewer")) + { + m_ScriptInterface = new ScriptInterface(&ScenarioEditorSubmitCommand); + frame = new ActorViewer(NULL, *m_ScriptInterface); + } else { wxFAIL_MSG(_("Internal error: invalid window type")); diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp index 500ce885b4..c040922eae 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.cpp @@ -30,8 +30,8 @@ #include "General/AtlasEventLoop.h" #include "General/Datafile.h" -#include "HighResTimer/HighResTimer.h" -#include "Buttons/ToolButton.h" +#include "CustomControls/HighResTimer/HighResTimer.h" +#include "CustomControls/Buttons/ToolButton.h" #include "CustomControls/Canvas/Canvas.h" #include "GameInterface/MessagePasser.h" @@ -295,11 +295,18 @@ AtlasWindowCommandProc& ScenarioEditor::GetCommandProc() { return g_CommandProc; namespace { - // Wrapper function because SetCurrentTool takes an optional argument, which JS doesn't like - void SetCurrentTool_script(void* cbdata, wxString name) + // Wrapper functions for scripts + + void SetCurrentTool_(void* cbdata, wxString name) { static_cast(cbdata)->GetToolManager().SetCurrentTool(name); } + + void SetCurrentToolWith(void* cbdata, wxString name, wxString arg) + { + static_cast(cbdata)->GetToolManager().SetCurrentTool(name, &arg); + } + wxString GetDataDirectory(void*) { return Datafile::GetDataDirectory(); @@ -319,7 +326,7 @@ namespace 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_ObjectSettings(g_SelectedObjects, m_ScriptInterface) , m_ToolManager(this) { // Global application initialisation: @@ -338,7 +345,8 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterfac // Script interface functions GetScriptInterface().SetCallbackData(static_cast(this)); GetScriptInterface().RegisterFunction("GetDataDirectory"); - GetScriptInterface().RegisterFunction("SetCurrentTool"); + GetScriptInterface().RegisterFunction("SetCurrentTool"); + GetScriptInterface().RegisterFunction("SetCurrentToolWith"); GetScriptInterface().RegisterFunction("SetBrushStrength"); GetScriptInterface().RegisterFunction("SetSelectedTexture"); @@ -353,6 +361,9 @@ ScenarioEditor::ScenarioEditor(wxWindow* parent, ScriptInterface& scriptInterfac GetScriptInterface().LoadScript(filename.GetFullName(), script); } + // Initialise things that rely on scripts + m_ObjectSettings.Init(AtlasMessage::eRenderView::GAME); + ////////////////////////////////////////////////////////////////////////// // Menu diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h index 57d4d48149..d1b716e987 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/ScenarioEditor.h @@ -58,7 +58,7 @@ public: static float GetSpeedModifier(); ScriptInterface& GetScriptInterface() const { return m_ScriptInterface; } - Observable& GetObjectSettings() { return m_ObjectSettings; } + ObjectSettings& GetObjectSettings() { return m_ObjectSettings; } ToolManager& GetToolManager() { return m_ToolManager; } diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp index bad6d7954e..7541bc2e4b 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/SectionLayout.cpp @@ -22,13 +22,12 @@ #include "SectionLayout.h" -#include "SnapSplitterWindow/SnapSplitterWindow.h" +#include "CustomControls/SnapSplitterWindow/SnapSplitterWindow.h" #include "ScenarioEditor.h" #include "AtlasScript/ScriptInterface.h" #include "Sections/Terrain/Terrain.h" -#include "Sections/Object/Object.h" #include "Sections/Environment/Environment.h" #include "Sections/Cinematic/Cinematic.h" #include "Sections/Trigger/Trigger.h" @@ -317,7 +316,7 @@ void SectionLayout::Build(ScenarioEditor& scenarioEditor) ADD_SIDEBAR_SCRIPT(_T("map"), _T("map.png"), _("Map")); ADD_SIDEBAR_SCRIPT(_T("terrain"), _T("terrain.png"), _("Terrain")); //ADD_SIDEBAR(TerrainSidebar, _T("terrain.png"), _("Terrain")); - ADD_SIDEBAR(ObjectSidebar, _T("object.png"), _("Object")); + ADD_SIDEBAR_SCRIPT(_T("object"), _T("object.png"), _("Object")); ADD_SIDEBAR(EnvironmentSidebar, _T("environment.png"), _("Environment")); #ifndef ATLAS_PUBLIC_RELEASE diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp deleted file mode 100644 index bb26ef7fa5..0000000000 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.cpp +++ /dev/null @@ -1,208 +0,0 @@ -/* Copyright (C) 2009 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 "Object.h" - -#include "Buttons/ToolButton.h" -#include "ScenarioEditor/ScenarioEditor.h" -#include "ScenarioEditor/Tools/Common/ObjectSettings.h" -#include "ScenarioEditor/Tools/Common/MiscState.h" -#include "VariationControl.h" - -#include "GameInterface/Messages.h" - -#include "wx/busyinfo.h" - -class ObjectSelectListBox : public wxListBox -{ -public: - ObjectSelectListBox(wxWindow* parent, ToolManager& toolManager) - : wxListBox(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, NULL, wxLB_SINGLE|wxLB_HSCROLL) - , m_ToolManager(toolManager) - { - } - - void OnSelect(wxCommandEvent& evt) - { - // On selecting an object, enable the PlaceObject tool with this object - wxString id = static_cast(evt.GetClientObject())->GetData(); - m_ToolManager.SetCurrentTool(_T("PlaceObject"), &id); - } - -private: - ToolManager& m_ToolManager; - DECLARE_EVENT_TABLE(); -}; -BEGIN_EVENT_TABLE(ObjectSelectListBox, wxListBox) - EVT_LISTBOX(wxID_ANY, ObjectSelectListBox::OnSelect) -END_EVENT_TABLE(); - - -class ObjectChoiceCtrl : public wxChoice -{ -public: - ObjectChoiceCtrl(wxWindow* parent, const wxArrayString& strings, ObjectSidebar& sidebar) - : wxChoice(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, strings), - m_Sidebar(sidebar) - { - SetSelection(0); - } - - void OnSelect(wxCommandEvent& evt) - { - // Switch between displayed lists of objects (e.g. entities vs actors) - m_Sidebar.SetObjectFilter(evt.GetSelection()); - } - -private: - ObjectSidebar& m_Sidebar; - - DECLARE_EVENT_TABLE(); -}; -BEGIN_EVENT_TABLE(ObjectChoiceCtrl, wxChoice) - EVT_CHOICE(wxID_ANY, ObjectChoiceCtrl::OnSelect) -END_EVENT_TABLE(); - -////////////////////////////////////////////////////////////////////////// - - -class ObjectBottomBar : public wxPanel -{ -public: - ObjectBottomBar(wxWindow* parent, Observable& objectSettings); -}; - -struct ObjectSidebarImpl -{ - ObjectSidebarImpl() : m_BottomBar(NULL), m_ObjectListBox(NULL) { } - wxWindow* m_BottomBar; - wxListBox* m_ObjectListBox; - std::vector m_Objects; -}; - -ObjectSidebar::ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer) -: Sidebar(scenarioEditor, sidebarContainer, bottomBarContainer), p(new ObjectSidebarImpl()) -{ - wxArrayString strings; - strings.Add(_("Entities")); - strings.Add(_("Actors (all)")); - m_MainSizer->Add(new ObjectChoiceCtrl(this, strings, *this), wxSizerFlags().Expand()); - - p->m_ObjectListBox = new ObjectSelectListBox(this, scenarioEditor.GetToolManager()); - m_MainSizer->Add(p->m_ObjectListBox, wxSizerFlags().Proportion(1).Expand()); - - m_BottomBar = new ObjectBottomBar(bottomBarContainer, scenarioEditor.GetObjectSettings()); -} - -ObjectSidebar::~ObjectSidebar() -{ - delete p; -} - -void ObjectSidebar::OnFirstDisplay() -{ - wxBusyInfo busy (_("Loading list of objects")); - - // Get the list of objects from the game - AtlasMessage::qGetObjectsList qry; - qry.Post(); - p->m_Objects = *qry.objects; - // Display first group of objects - SetObjectFilter(0); -} - -void ObjectSidebar::SetObjectFilter(int type) -{ - p->m_ObjectListBox->Freeze(); - p->m_ObjectListBox->Clear(); - for (std::vector::iterator it = p->m_Objects.begin(); it != p->m_Objects.end(); ++it) - { - if (it->type == type) - { - wxString id = it->id.c_str(); - wxString name = it->name.c_str(); - p->m_ObjectListBox->Append(name, new wxStringClientData(id)); - } - } - p->m_ObjectListBox->Thaw(); -} - -////////////////////////////////////////////////////////////////////////// - -class PlayerComboBox : public wxComboBox -{ -public: - PlayerComboBox(wxWindow* parent, wxArrayString& choices, Observable& objectSettings) - : wxComboBox(parent, -1, choices[objectSettings.GetPlayerID()], wxDefaultPosition, wxDefaultSize, choices, wxCB_READONLY) - , m_ObjectSettings(objectSettings) - { - m_Conn = m_ObjectSettings.RegisterObserver(1, &PlayerComboBox::OnObjectSettingsChange, this); - } - -private: - - ObservableScopedConnection m_Conn; - Observable& m_ObjectSettings; - - void OnObjectSettingsChange(const ObjectSettings& settings) - { - SetSelection((long)settings.GetPlayerID()); - } - - void OnSelect(wxCommandEvent& evt) - { - m_ObjectSettings.SetPlayerID(evt.GetInt()); - m_ObjectSettings.NotifyObserversExcept(m_Conn); - } - - DECLARE_EVENT_TABLE(); -}; -BEGIN_EVENT_TABLE(PlayerComboBox, wxComboBox) - EVT_COMBOBOX(wxID_ANY, PlayerComboBox::OnSelect) -END_EVENT_TABLE(); - -////////////////////////////////////////////////////////////////////////// - -ObjectBottomBar::ObjectBottomBar(wxWindow* parent, Observable& objectSettings) - : wxPanel(parent, wxID_ANY) -{ - wxSizer* sizer = new wxBoxSizer(wxVERTICAL); - - wxArrayString players; - // TODO: get proper player names - players.Add(_("Gaia")); - players.Add(_("Player 1")); - players.Add(_("Player 2")); - players.Add(_("Player 3")); - players.Add(_("Player 4")); - players.Add(_("Player 5")); - players.Add(_("Player 6")); - players.Add(_("Player 7")); - players.Add(_("Player 8")); - wxComboBox* playerSelect = new PlayerComboBox(this, players, objectSettings); - sizer->Add(playerSelect); - - wxWindow* variationSelect = new VariationControl(this, objectSettings); - variationSelect->SetMinSize(wxSize(160, -1)); - wxSizer* variationSizer = new wxStaticBoxSizer(wxVERTICAL, this, _("Variation")); - variationSizer->Add(variationSelect, wxSizerFlags().Proportion(1).Expand()); - sizer->Add(variationSizer, wxSizerFlags().Proportion(1)); - - SetSizer(sizer); -} diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.h deleted file mode 100644 index 3c02e0870a..0000000000 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/Object.h +++ /dev/null @@ -1,33 +0,0 @@ -/* Copyright (C) 2009 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 "../Common/Sidebar.h" - -struct ObjectSidebarImpl; -class ObjectSidebar : public Sidebar -{ -public: - ObjectSidebar(ScenarioEditor& scenarioEditor, wxWindow* sidebarContainer, wxWindow* bottomBarContainer); - ~ObjectSidebar(); - void SetObjectFilter(int type); - -protected: - virtual void OnFirstDisplay(); - -private: - ObjectSidebarImpl* p; -}; diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/VariationControl.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/VariationControl.cpp deleted file mode 100644 index 66bbb7400b..0000000000 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/VariationControl.cpp +++ /dev/null @@ -1,146 +0,0 @@ -/* Copyright (C) 2009 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 "VariationControl.h" - -#include "ScenarioEditor/Tools/Common/ObjectSettings.h" - -VariationControl::VariationControl(wxWindow* parent, Observable& objectSettings) -: wxScrolledWindow(parent, -1), -m_ObjectSettings(objectSettings) -{ - m_Conn = m_ObjectSettings.RegisterObserver(1, &VariationControl::OnObjectSettingsChange, this); - - SetScrollRate(0, 5); - - m_Sizer = new wxBoxSizer(wxVERTICAL); - SetSizer(m_Sizer); -} - -// Event handler shared by all the combo boxes created by this window -void VariationControl::OnSelect(wxCommandEvent& evt) -{ - std::set selections; - - // It's possible for a variant name to appear in multiple groups. - // If so, assume that all the names in each group are the same, so - // we don't have to worry about some impossible combinations (e.g. - // one group "a,b", a second "b,c", and a third "c,a", where's there's - // no set of selections that matches one (and only one) of each group). - // - // So... When a combo box is changed from 'a' to 'b', add 'b' to the new - // selections and make sure any other combo boxes containing both 'a' and - // 'b' no longer contain 'a'. - - wxComboBox* thisComboBox = wxDynamicCast(evt.GetEventObject(), wxComboBox); - wxCHECK(thisComboBox != NULL, ); - wxString newValue = thisComboBox->GetValue(); - - selections.insert(newValue); - - for (size_t i = 0; i < m_ComboBoxes.size(); ++i) - { - wxComboBox* comboBox = m_ComboBoxes[i]; - // If our newly selected value is used in another combobox, we want - // that combobox to use the new value, so don't add its old value - // to the list of selections - if (comboBox->FindString(newValue) == wxNOT_FOUND) - selections.insert(comboBox->GetValue()); - } - - m_ObjectSettings.SetActorSelections(selections); - m_ObjectSettings.NotifyObserversExcept(m_Conn); - RefreshObjectSettings(); -} - -void VariationControl::OnObjectSettingsChange(const ObjectSettings& settings) -{ - Freeze(); - - const std::vector& variation = settings.GetActorVariation(); - - // Creating combo boxes seems to be pretty expensive - so we create as - // few as possible, by never deleting any. - - size_t oldCount = m_ComboBoxes.size(); - size_t newCount = variation.size(); - - // If we have too many combo boxes, hide the excess ones - for (size_t i = newCount; i < oldCount; ++i) - { - m_ComboBoxes[i]->Show(false); - } - - for (size_t i = 0; i < variation.size(); ++i) - { - const ObjectSettings::Group& group = variation[i]; - - if (i < oldCount) - { - // Already got enough boxes available, so use an old one - wxComboBox* comboBox = m_ComboBoxes[i]; - // Replace the contents of the old combobox with the new data - comboBox->Freeze(); - comboBox->Clear(); - comboBox->Append(group.variants); - comboBox->SetValue(group.chosen); - comboBox->Show(true); - comboBox->Thaw(); - } - else - { - // Create an initially empty combobox, because we can fill it - // quicker than the default constructor can - wxComboBox* combo = new wxComboBox(this, -1, wxEmptyString, wxDefaultPosition, - wxSize(80, wxDefaultCoord), wxArrayString(), wxCB_READONLY); - // Freeze it before adding all the values - combo->Freeze(); - combo->Append(group.variants); - combo->SetValue(group.chosen); - combo->Thaw(); - // Add the on-select event handler - combo->Connect(wxID_ANY, wxEVT_COMMAND_COMBOBOX_SELECTED, - wxCommandEventHandler(VariationControl::OnSelect), NULL, this); - // Add box to sizer and list - m_Sizer->Add(combo, wxSizerFlags().Expand()); - m_ComboBoxes.push_back(combo); - } - } - - Layout(); - - Thaw(); - - // Make the scrollbars appear when appropriate - FitInside(); -} - -void VariationControl::RefreshObjectSettings() -{ - const std::vector& variation = m_ObjectSettings.GetActorVariation(); - - // For each group, set the corresponding combobox's value to the chosen one - size_t i = 0; - for (std::vector::const_iterator group = variation.begin(); - group != variation.end() && i < m_ComboBoxes.size(); - ++group, ++i) - { - m_ComboBoxes[i]->SetValue(group->chosen); - } -} diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/VariationControl.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/VariationControl.h deleted file mode 100644 index 1088f3788f..0000000000 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Object/VariationControl.h +++ /dev/null @@ -1,42 +0,0 @@ -/* Copyright (C) 2009 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 . - */ - -#ifndef INCLUDED_VARIATIONCONTROL -#define INCLUDED_VARIATIONCONTROL - -#include "General/Observable.h" - -class ObjectSettings; - -class VariationControl : public wxScrolledWindow -{ -public: - VariationControl(wxWindow* parent, Observable& objectSettings); - -private: - void OnSelect(wxCommandEvent& evt); - void OnObjectSettingsChange(const ObjectSettings& settings); - void RefreshObjectSettings(); - - ObservableScopedConnection m_Conn; - - Observable& m_ObjectSettings; - std::vector m_ComboBoxes; - wxSizer* m_Sizer; -}; - -#endif // INCLUDED_VARIATIONCONTROL diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp index 171267f41a..67db07713a 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.cpp @@ -21,133 +21,42 @@ #include "GameInterface/Messages.h" #include "ScenarioEditor/ScenarioEditor.h" +#include "AtlasScript/ScriptInterface.h" -ObjectSettings::ObjectSettings(Observable >& selectedObjects, int view) -: m_PlayerID(0), m_SelectedObjects(selectedObjects), m_View(view) +ObjectSettings::ObjectSettings(Observable >& selectedObjects, ScriptInterface& scriptInterface) +: m_ScriptInterface(scriptInterface) { - m_Conn = m_SelectedObjects.RegisterObserver(0, &ObjectSettings::OnSelectionChange, this); + m_Conn = selectedObjects.RegisterObserver(0, &ObjectSettings::OnSelectionChange, this); } -size_t ObjectSettings::GetPlayerID() const +void ObjectSettings::Init(int view) { - return m_PlayerID; + m_ScriptInterface.SetValue(_T("Atlas.State.objectSettings.view"), view); } void ObjectSettings::SetPlayerID(int playerID) { - m_PlayerID = playerID; - PostToGame(); -} - -const std::set& ObjectSettings::GetActorSelections() const -{ - return m_ActorSelections; -} - -void ObjectSettings::SetActorSelections(const std::set& selections) -{ - m_ActorSelections = selections; - PostToGame(); -} - -const std::vector ObjectSettings::GetActorVariation() const -{ - std::vector variation; - - for (std::vector::const_iterator grp = m_VariantGroups.begin(); - grp != m_VariantGroups.end(); - ++grp) - { - Group group; - group.variants = *grp; - - // Variant choice method, as used by the game: Choose the first variant - // which matches any of the selections - - size_t chosen = 0; // default to first - for (size_t i = 0; i < grp->GetCount(); ++i) - { - if (m_ActorSelections.find(grp->Item(i)) != m_ActorSelections.end()) - { - chosen = i; - break; - } - } - group.chosen = grp->Item(chosen); - - variation.push_back(group); - } - - return variation; + m_ScriptInterface.SetValue(_T("Atlas.State.objectSettings.playerID"), playerID); } AtlasMessage::sObjectSettings ObjectSettings::GetSettings() const { AtlasMessage::sObjectSettings settings; - - settings.player = m_PlayerID; - - // Copy selections from set into vector - std::vector selections; - for (std::set::const_iterator it = m_ActorSelections.begin(); - it != m_ActorSelections.end(); - ++it) - { - selections.push_back(it->c_str()); - } - settings.selections = selections; - + bool ok = m_ScriptInterface.Eval(_T("Atlas.State.objectSettings.toSObjectSettings()"), settings); + wxASSERT(ok); return settings; } void ObjectSettings::OnSelectionChange(const std::vector& selection) { - // TODO: what would be the sensible action if nothing's selected? - // and if multiple objects are selected? + // Convert to ints so they can be passed to JS + std::vector objs (selection.begin(), selection.end()); - if (selection.empty()) - return; - - AtlasMessage::qGetObjectSettings qry (m_View, selection[0]); - qry.Post(); - - m_PlayerID = qry.settings->player; - - m_ActorSelections.clear(); - m_VariantGroups.clear(); - - std::vector > variation = *qry.settings->variantgroups; - for (std::vector >::iterator grp = variation.begin(); - grp != variation.end(); - ++grp) - { - wxArrayString variants; - - for (std::vector::iterator it = grp->begin(); - it != grp->end(); - ++it) - { - variants.Add(it->c_str()); - } - - m_VariantGroups.push_back(variants); - } - - std::vector selections = *qry.settings->selections; - for (std::vector::iterator sel = selections.begin(); - sel != selections.end(); - ++sel) - { - m_ActorSelections.insert(sel->c_str()); - } - - static_cast*>(this)->NotifyObservers(); + m_ScriptInterface.SetValue(_T("Atlas.State.objectSettings.selectedObjects"), objs); + m_ScriptInterface.Eval(_T("Atlas.State.objectSettings.onSelectionChange()")); } -void ObjectSettings::PostToGame() +void ObjectSettings::NotifyObservers() { - if (m_SelectedObjects.empty()) - return; - - POST_COMMAND(SetObjectSettings, (m_View, m_SelectedObjects[0], GetSettings())); + m_ScriptInterface.Eval(_T("Atlas.State.objectSettings.notifyObservers()")); } diff --git a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.h b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.h index 9d16d34b89..71f6160c9a 100644 --- a/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.h +++ b/source/tools/atlas/AtlasUI/ScenarioEditor/Tools/Common/ObjectSettings.h @@ -19,62 +19,37 @@ #define INCLUDED_OBJECTSETTINGS #include -#include #include "ScenarioEditor/Tools/Common/MiscState.h" +class ScriptInterface; + namespace AtlasMessage { struct sObjectSettings; } -// Various settings to be applied to newly created units, or to the currently -// selected unit. If a unit is selected or being previewed, it should match -// these settings. +// This class is now just an interface to the JS Atlas.State.objectSettings, +// for old C++ code that hasn't been ported to JS yet. class ObjectSettings { public: - ObjectSettings(Observable >& selectedObjects, int view); + ObjectSettings(Observable >& selectedObjects, ScriptInterface& scriptInterface); + void Init(int view); - size_t GetPlayerID() const; void SetPlayerID(int playerID); - struct Group - { - wxArrayString variants; - wxString chosen; - }; - - const std::vector GetActorVariation() const; - - const std::set& GetActorSelections() const; - void SetActorSelections(const std::set& selections); - // Constructs new sObjectSettings object from settings AtlasMessage::sObjectSettings GetSettings() const; + void NotifyObservers(); + private: - Observable >& m_SelectedObjects; - - int m_View; - - // 0 = gaia, 1..inf = normal players - size_t m_PlayerID; - - // Set of user-chosen actor selections, potentially a superset of any single - // actor's possible variants (since it doesn't get reset if you select - // a new actor, and will accumulate variant names) - std::set m_ActorSelections; - - // List of actor variant groups (each a list of variant names) - std::vector m_VariantGroups; + ScriptInterface& m_ScriptInterface; // Observe changes to unit selection ObservableScopedConnection m_Conn; void OnSelectionChange(const std::vector& selection); - - // Transfer current settings to the currently selected unit (if any) - void PostToGame(); }; #endif // INCLUDED_OBJECTSETTINGS