OLD | NEW |
| (Empty) |
1 // Copyright 2010 The Ginsu Authors. | |
2 // Use of this source code is governed by a BSD-style license that can | |
3 // be found in the LICENSE file. | |
4 | |
5 /** | |
6 * @fileoverview The ginsu Application object. This object instantiates a | |
7 * Trackball object and connects it to the element named |ginsu_instance|. | |
8 */ | |
9 | |
10 goog.provide('ginsu.Application'); | |
11 | |
12 goog.require('goog.Disposable'); | |
13 goog.require('goog.style') | |
14 | |
15 goog.require('ginsu.controllers.KeyboardHandler'); | |
16 goog.require('ginsu.controllers.Toolbar'); | |
17 goog.require('ginsu.controllers.ToolManager'); | |
18 goog.require('ginsu.controllers.ViewController'); | |
19 goog.require('ginsu.events.Event'); | |
20 goog.require('ginsu.events.EventType'); | |
21 goog.require('ginsu.tools.Tool'); | |
22 goog.require('ginsu.tools.OrbitTool'); | |
23 goog.require('ginsu.tools.LineTool'); | |
24 | |
25 /** | |
26 * Constructor for the Application class. Use the run() method to populate | |
27 * the object with controllers and wire up the events. | |
28 * @constructor | |
29 * @extends {goog.Disposable} | |
30 */ | |
31 ginsu.Application = function() { | |
32 goog.Disposable.call(this); | |
33 } | |
34 goog.inherits(ginsu.Application, goog.Disposable); | |
35 | |
36 /** | |
37 * The tool manager for the application. This object keeps track of all the | |
38 * tools, and which one is currently active. | |
39 * @type {ginsu.ToolManager} | |
40 * @private | |
41 */ | |
42 ginsu.Application.prototype.toolManager_ = null; | |
43 | |
44 /** | |
45 * The toolbar for the application. This object is just a UI element that | |
46 * selects a tool. Connects to element with id | |
47 * ginsu.Application.DomIds_.TOOLBAR. | |
48 * @type {ginsu.view.Toolbar} | |
49 * @private | |
50 */ | |
51 ginsu.Application.prototype.toolBar_ = null; | |
52 | |
53 /** | |
54 * The keyboard shortcut handler for the application. A UI element that | |
55 * listens for keyboard input and transforms that into ACTION events. | |
56 * @type {ginsu.KeyboardHandle} | |
57 * @private | |
58 */ | |
59 ginsu.Application.prototype.keyboardHandler_ = null; | |
60 | |
61 /** | |
62 * The view controller for the application. A DOM element that encapsulates | |
63 * the grayskull plugin; this is allocated at run time. Connects to the | |
64 * element with id ginsu.Application.DomIds_.VIEW. | |
65 * @type {ginsu.ViewController} | |
66 * @private | |
67 */ | |
68 ginsu.Application.prototype.viewController_ = null; | |
69 | |
70 /** | |
71 * The ids used for elements in the DOM. The GSApplication expects these | |
72 * elements to exist. | |
73 * @enum {string} | |
74 * @private | |
75 */ | |
76 ginsu.Application.DomIds_ = { | |
77 TOOLBAR: 'ginsu_toolbar', // The toolbar element id. | |
78 VIEW: 'ginsu_view', // The <div> containing the NaCl element. | |
79 MODULE: 'ginsu_module' // The <embed> element representing the NaCl module. | |
80 }; | |
81 | |
82 /** | |
83 * The images used to render the tool buttons. | |
84 * @enum {string} | |
85 * @private | |
86 */ | |
87 ginsu.Application.ToolImages_ = { | |
88 LINE: 'icons/line.png', // The line tool. | |
89 ORBIT: 'icons/orbit.png', // The orbit tool. | |
90 PUSH_PULL: 'icons/pushpull.png' // The push/pull tool. | |
91 }; | |
92 | |
93 /** | |
94 * @desc Label for the Line tool, used for both the 'alt' and 'title' | |
95 * properties. | |
96 * @private | |
97 */ | |
98 ginsu.Application.MSG_LINE_ = goog.getMsg('Line'); | |
99 | |
100 /** | |
101 * @desc Label for the Orbit tool, used for both the 'alt' and 'title' | |
102 * properties. | |
103 * @private | |
104 */ | |
105 ginsu.Application.MSG_ORBIT_ = goog.getMsg('Orbit'); | |
106 | |
107 /** | |
108 * @desc Label for the Push/Pull tool, used for both the 'alt' and 'title' | |
109 * properties. | |
110 * @private | |
111 */ | |
112 ginsu.Application.MSG_PUSH_PULL_ = goog.getMsg('Push/Pull'); | |
113 | |
114 /** | |
115 * Error messages. | |
116 * @type {string} | |
117 * @private | |
118 */ | |
119 ginsu.Application.MSG_MISSING_ELEMENT_ = goog.getMsg('missing element'); | |
120 | |
121 /** | |
122 * Place-holder to make the onload event handling process all work. | |
123 */ | |
124 var loadingGinsuApp_ = {}; | |
125 | |
126 /** | |
127 * Override of disposeInternal() to dispose of retained objects. | |
128 * @override | |
129 */ | |
130 ginsu.Application.prototype.disposeInternal = function() { | |
131 this.terminate(); | |
132 ginsu.Application.superClass_.disposeInternal.call(this); | |
133 } | |
134 | |
135 /** | |
136 * Helper function to allocate the tools and build a map that contains things | |
137 * like the DOM content used to render the tool's button and the tool's | |
138 * keyboard shortcut. | |
139 * @private | |
140 * @return {Array.<Object>} The tool map that can be used later to register the | |
141 * tools with the various GUI controllers. | |
142 */ | |
143 ginsu.Application.prototype.createToolMap_ = function() { | |
144 | |
145 /** | |
146 * Helper function to create a tool map entry. | |
147 * @param {!ginsu.Tool} tool The instance of a tool for this entry. | |
148 * @param {!goog.ui.ControlContent} content The DOM content used to display | |
149 * the tool's button. | |
150 * @param {string} shortcut The keyboard shortcut used to activate the tool. | |
151 * @return {Object} An entry into the tool map array. | |
152 */ | |
153 function createToolEntry(tool, content, shortcut) { | |
154 return toolEntry = {'tool': tool, 'content': content, 'shortcut': shortcut}; | |
155 }; | |
156 | |
157 /** | |
158 * Build a map of the tools. This is an array of objects that contains an | |
159 * instance of the tool, the DOM content used to display the tool's button, | |
160 * and the tool's keyboard shortcut. | |
161 * Note: add new tools here; they will be picked up in the following | |
162 * forEach() loop. | |
163 * @type {Array.<Object>} | |
164 */ | |
165 var toolMap = [ | |
166 createToolEntry(new ginsu.tools.LineTool(), | |
167 goog.dom.createDom('img', { | |
168 'src': ginsu.Application.ToolImages_.LINE, | |
169 'alt': ginsu.Application.MSG_LINE_, | |
170 'title': ginsu.Application.MSG_LINE_}), | |
171 'l'), | |
172 createToolEntry(new ginsu.tools.OrbitTool(), | |
173 goog.dom.createDom('img', { | |
174 'src': ginsu.Application.ToolImages_.ORBIT, | |
175 'alt': ginsu.Application.MSG_ORBIT_, | |
176 'title': ginsu.Application.MSG_ORBIT_}), | |
177 'o'), | |
178 createToolEntry(new ginsu.tools.Tool(ginsu.events.EventType.PUSH_PULL), | |
179 goog.dom.createDom('img', { | |
180 'src': ginsu.Application.ToolImages_.PUSH_PULL, | |
181 'alt': ginsu.Application.MSG_PUSH_PULL_, | |
182 'title': ginsu.Application.MSG_PUSH_PULL_}), | |
183 'p') | |
184 ]; | |
185 return toolMap; | |
186 }; | |
187 | |
188 /** | |
189 * Called by the module loading function once the module has been loaded. | |
190 * At this point, the Ginsu NaCL module is known to be loaded, so now is the | |
191 * time to wire up all the UI elements and create the controller objects. | |
192 * @param {?Element} nativeModule The instance of the native module. | |
193 */ | |
194 ginsu.Application.prototype.moduleDidLoad = function(nativeModule) { | |
195 // First make sure that all the necessary DOM elements are in place. If any | |
196 // are missing, throw an error and exit. | |
197 var toolBarElement = goog.dom.$(ginsu.Application.DomIds_.TOOLBAR); | |
198 if (!toolBarElement) { | |
199 // TODO(dspringer): we need a GSError object. | |
200 throw new Error('Application.moduleDidLoad(): ' + | |
201 ginsu.Application.MSG_MISSING_ELEMENT_ + | |
202 "'" + ginsu.Application.DomIds_.TOOLBAR + "'"); | |
203 } | |
204 | |
205 // Allocate an instance of the various controllers and register all the tools. | |
206 this.toolManager_ = new ginsu.controllers.ToolManager(); | |
207 this.toolBar_ = new ginsu.controllers.Toolbar(); | |
208 this.keyboardHandler_ = new ginsu.controllers.KeyboardHandler(); | |
209 var toolMap = this.createToolMap_(); | |
210 | |
211 // Listen for 'unload' in order to terminate cleanly. | |
212 goog.events.listen(window, goog.events.EventType.UNLOAD, this.terminate); | |
213 | |
214 // Add all the tools to the various UI elements and other controllers. | |
215 goog.array.forEach(toolMap, function(toolEntry) { | |
216 var toolId = toolEntry.tool.toolId(); | |
217 this.toolBar_.addToggleButton(toolEntry.content, toolId); | |
218 this.toolManager_.addTool(toolEntry.tool); | |
219 this.keyboardHandler_.registerShortcut(toolId, toolEntry.shortcut); | |
220 }, this); | |
221 | |
222 // Put the toolbar into the document. | |
223 this.toolBar_.render(toolBarElement, true); | |
224 | |
225 // Set up the plugin wrapper. | |
226 this.viewController_ = new ginsu.controllers.ViewController(nativeModule); | |
227 | |
228 // Set up the event listeners. First, tell the tool manager to observe | |
229 // the drag events coming from the grayskull view container. | |
230 goog.events.listen(this.viewController_, ginsu.events.EventType.DRAG_START, | |
231 this.toolManager_.handleDragStartEvent, false, this.toolManager_); | |
232 goog.events.listen(this.viewController_, ginsu.events.EventType.DRAG, | |
233 this.toolManager_.handleDragEvent, false, this.toolManager_); | |
234 goog.events.listen(this.viewController_, ginsu.events.EventType.DRAG_END, | |
235 this.toolManager_.handleDragEndEvent, false, this.toolManager_); | |
236 | |
237 // Tell the tool manager to observe the various ACTION events. | |
238 goog.events.listen(this.toolBar_, ginsu.events.EventType.ACTION, | |
239 this.toolManager_.handleActionEvent, false, this.toolManager_); | |
240 goog.events.listen(this.keyboardHandler_, ginsu.events.EventType.ACTION, | |
241 this.toolManager_.handleActionEvent, false, this.toolManager_); | |
242 goog.events.listen(this.viewController_, ginsu.events.EventType.ACTION, | |
243 this.toolManager_.handleActionEvent, false, this.toolManager_); | |
244 | |
245 // Tell the toolbar to observe the various ACTION events. | |
246 goog.events.listen(this.toolManager_, ginsu.events.EventType.ACTION, | |
247 this.toolBar_.handleActionEvent, false, this.toolBar_); | |
248 goog.events.listen(this.keyboardHandler_, ginsu.events.EventType.ACTION, | |
249 this.toolBar_.handleActionEvent, false, this.toolBar_); | |
250 goog.events.listen(this.viewController_, ginsu.events.EventType.ACTION, | |
251 this.toolBar_.handleActionEvent, false, this.toolBar_); | |
252 | |
253 // Activate the orbit tool as the default. | |
254 this.toolManager_.activateToolWithId(ginsu.events.EventType.ORBIT); | |
255 } | |
256 | |
257 /** | |
258 * Asserts that cond is true; issues an alert and throws an Error otherwise. | |
259 * @param {bool} cond The condition. | |
260 * @param {String} message The error message issued if cond is false. | |
261 */ | |
262 ginsu.Application.prototype.assert = function(cond, message) { | |
263 if (!cond) { | |
264 message = "Assertion failed: " + message; | |
265 alert(message); | |
266 throw new Error(message); | |
267 } | |
268 } | |
269 | |
270 /** | |
271 * The run() method starts and 'runs' the application. An <embed> tag is | |
272 * injected into the <div> element |opt_viewDivName| which causes the Ginsu NaCl | |
273 * module to be loaded. Once loaded, the moduleDidLoad() method is called. | |
274 * @param {?String} opt_viewDivName The id of a DOM element in which to | |
275 * embed the Ginsu module. If unspecified, defaults to DomIds_.VIEW. The | |
276 * DOM element must exist. | |
277 */ | |
278 ginsu.Application.prototype.run = function(opt_viewDivName) { | |
279 var viewDivName = opt_viewDivName || ginsu.Application.DomIds_.VIEW; | |
280 var viewDiv = goog.dom.$(viewDivName); | |
281 this.assert(viewDiv, "Missing DOM element '" + viewDivName + "'"); | |
282 // Load the published .nexe. This includes the 'nexes' attribute which | |
283 // shows how to load multi-architecture modules. Each entry in the | |
284 // table is a key-value pair: the key is the runtime ('x86-32', | |
285 // 'x86-64', etc.); the value is a URL for the desired NaCl module. | |
286 var nexes = 'x86-32: ginsu_x86_32.nexe\n' | |
287 + 'x86-64: ginsu_x86_64.nexe\n' | |
288 + 'ARM: ginsu_arm.nexe '; | |
289 // This assumes that the <div> containers for Ginsu modules each have a | |
290 // unique name on the page. | |
291 var uniqueModuleName = viewDivName + ginsu.Application.DomIds_.MODULE; | |
292 // This is a bit of a hack: when the |onload| event fires, |this| is set to | |
293 // the DOM window object, *not* the <embed> element. So, we keep a global | |
294 // pointer to |this| because there is no way to make a closure here. See | |
295 // http://code.google.com/p/nativeclient/issues/detail?id=693 | |
296 loadingGinsuApp_[uniqueModuleName] = this; | |
297 var onLoadJS = "loadingGinsuApp_['" | |
298 + uniqueModuleName | |
299 + "'].moduleDidLoad(document.getElementById('" | |
300 + uniqueModuleName | |
301 + "'));" | |
302 var viewSize = goog.style.getSize(viewDiv); | |
303 viewDiv.innerHTML = '<embed id="' + uniqueModuleName + '" ' | |
304 + 'class="ginsu_autosize_view" ' | |
305 // + 'nexes="' + nexes + '" ' | |
306 + 'type="application/x-nacl-srpc" ' | |
307 + 'width="' + viewSize.width + '" ' | |
308 + 'height="' + viewSize.height + '" ' | |
309 + 'dimensions="3" ' | |
310 + 'onload="' + onLoadJS + '" />' | |
311 // Note: this code is here to work around a bug in the Chrome Browser. | |
312 // http://code.google.com/p/nativeclient/issues/detail?id=500 | |
313 goog.dom.$(uniqueModuleName).nexes = nexes; | |
314 } | |
315 | |
316 /** | |
317 * Shut down the application instance. This unhooks all the event listeners | |
318 * and deletes the objects created in moduleDidLoad(). | |
319 */ | |
320 ginsu.Application.prototype.terminate = function() { | |
321 goog.events.removeAll(); | |
322 this.toolManager_ = null; | |
323 this.toolBar_ = null; | |
324 this.keyboardHandler_ = null; | |
325 this.viewController_ = null; | |
326 } | |
OLD | NEW |