1 /* See license.txt for terms of usage */
  2 
  3 define([
  4     "firebug/lib/object",
  5     "firebug/firebug",
  6     "firebug/lib/events",
  7     "firebug/chrome/menu",
  8     "firebug/lib/dom",
  9     "firebug/lib/locale",
 10     "firebug/lib/css",
 11     "firebug/lib/options",
 12 ],
 13 function(Obj, Firebug, Events, Menu, Dom, Locale, Css, Options) {
 14 
 15 // ********************************************************************************************* //
 16 // Constants
 17 
 18 var Cc = Components.classes;
 19 var Ci = Components.interfaces;
 20 var Cu = Components.utils;
 21 
 22 var MODE_JAVASCRIPT = "js";
 23 var CONTEXT_MENU = "";
 24 var TEXT_CHANGED = "";
 25 
 26 try
 27 {
 28     // Introduced in Firefox 8
 29     Cu["import"]("resource:///modules/source-editor.jsm");
 30 
 31     MODE_JAVASCRIPT = SourceEditor.MODES.JAVASCRIPT;
 32     CONTEXT_MENU = SourceEditor.EVENTS.CONTEXT_MENU;
 33     TEXT_CHANGED = SourceEditor.EVENTS.TEXT_CHANGED;
 34 }
 35 catch (err)
 36 {
 37     if (FBTrace.DBG_ERRORS)
 38         FBTrace.sysout("commandEditor: EXCEPTION source-editors is not available!");
 39 }
 40 
 41 // ********************************************************************************************* //
 42 // Command Editor
 43 
 44 Firebug.CommandEditor = Obj.extend(Firebug.Module,
 45 {
 46     dispatchName: "commandEditor",
 47 
 48     editor: null,
 49 
 50     initialize: function()
 51     {
 52         Firebug.Module.initialize.apply(this, arguments);
 53 
 54         if (this.editor)
 55             return;
 56 
 57         // The current implementation of the SourceEditor (based on Orion) doesn't
 58         // support zooming. So, the TextEditor (based on textarea) can be used
 59         // by setting extensions.firebug.enableOrion pref to false.
 60         // See issue 5678
 61         if (typeof(SourceEditor) != "undefined" && Options.get("enableOrion"))
 62             this.editor = new SourceEditor();
 63         else
 64             this.editor = new TextEditor();
 65 
 66         var config =
 67         {
 68             mode: MODE_JAVASCRIPT,
 69             showLineNumbers: false,
 70             theme: "chrome://firebug/skin/orion-firebug.css"
 71         };
 72 
 73         // Custom shortcuts for Orion editor
 74         config.keys = [{
 75             action: "firebug-cmdEditor-execute",
 76             code: KeyEvent.DOM_VK_RETURN,
 77             accel: true,
 78             callback: this.onExecute.bind(this),
 79         },{
 80             action: "firebug-cmdEditor-escape",
 81             code: KeyEvent.DOM_VK_ESCAPE,
 82             callback: this.onEscape.bind(this),
 83         }];
 84 
 85         // Initialize Orion editor.
 86         this.parent = document.getElementById("fbCommandEditor");
 87         this.editor.init(this.parent, config, this.onEditorLoad.bind(this));
 88 
 89         if (FBTrace.DBG_COMMANDEDITOR)
 90             FBTrace.sysout("commandEditor: SourceEditor initialized");
 91     },
 92 
 93     shutdown: function()
 94     {
 95         if (!this.editor)
 96             return;
 97 
 98         this.editor.removeEventListener(CONTEXT_MENU, this.onContextMenu);
 99         this.editor.removeEventListener(TEXT_CHANGED, this.onTextChanged);
100 
101         this.editor.destroy();
102         this.editor = null;
103     },
104 
105     /**
106      * The load event handler for the source editor. This method does post-load
107      * editor initialization.
108      */
109     onEditorLoad: function()
110     {
111         // xxxHonza: Context menu support is going to change in SourceEditor
112         this.editor.addEventListener(CONTEXT_MENU, this.onContextMenu);
113         this.editor.addEventListener(TEXT_CHANGED, this.onTextChanged);
114 
115         this.editor.setCaretOffset(this.editor.getCharCount());
116 
117         Firebug.chrome.applyTextSize(Firebug.textSize);
118 
119         if (FBTrace.DBG_COMMANDEDITOR)
120             FBTrace.sysout("commandEditor.onEditorLoad; SourceEditor loaded");
121     },
122 
123     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
124     // Keyboard shortcuts
125 
126     onExecute: function()
127     {
128         var context = Firebug.currentContext;
129         Firebug.CommandLine.update(context);
130         Firebug.CommandLine.enter(context);
131         return true;
132     },
133 
134     onEscape: function()
135     {
136         var context = Firebug.currentContext;
137         Firebug.CommandLine.update(context);
138         Firebug.CommandLine.cancel(context);
139         return true;
140     },
141 
142     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
143     // Other Events
144 
145     onTextChanged: function(event)
146     {
147         // Ignore changes that are triggered by Firebug's restore logic.
148         if (Firebug.CommandEditor.ignoreChanges)
149             return;
150 
151         var context = Firebug.currentContext;
152         Firebug.CommandLine.update(context);
153     },
154 
155     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
156     // Context Menu
157 
158     onContextMenu: function(event)
159     {
160         var popup = document.getElementById("fbCommandEditorPopup");
161         Dom.eraseNode(popup);
162 
163         var items = Firebug.CommandEditor.getContextMenuItems();
164         Menu.createMenuItems(popup, items);
165 
166         if (!popup.childNodes.length)
167             return;
168 
169         popup.openPopupAtScreen(event.screenX, event.screenY, true);
170     },
171 
172     getContextMenuItems: function()
173     {
174         var items = [];
175         items.push({label: Locale.$STR("Cut"), commandID: "cmd_cut"});
176         items.push({label: Locale.$STR("Copy"), commandID: "cmd_copy"});
177         items.push({label: Locale.$STR("Paste"), commandID: "cmd_paste"});
178         items.push({label: Locale.$STR("Delete"), commandID: "cmd_delete"});
179         items.push("-");
180         items.push({label: Locale.$STR("SelectAll"), commandID: "cmd_selectAll"});
181         return items;
182     },
183 
184     // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
185     // Public API
186 
187     setText: function(text)
188     {
189         try
190         {
191             // When manually setting the text, ignore the TEXT_CHANGED event.
192             this.ignoreChanges = true;
193 
194             if (this.editor)
195                 this.editor.setText(text);
196         }
197         catch (err)
198         {
199             // No exception is really expected, we just need the finally clause.
200         }
201         finally
202         {
203             this.ignoreChanges = false;
204         }
205     },
206 
207     getText: function()
208     {
209         if (this.editor)
210             return this.editor.getText();
211     },
212 
213     setSelectionRange: function(start, end)
214     {
215         if (this.editor)
216             this.editor.setSelection(start, end);
217     },
218 
219     select: function()
220     {
221         // TODO xxxHonza
222     },
223 
224     // returns the applicable commands
225     getExpression: function()
226     {
227         if (this.editor)
228         {
229             if (this.isCollapsed())
230                 return this.getText();
231             else
232                 return this.editor.getSelectedText();
233         }
234     },
235 
236     isCollapsed: function()
237     {
238         var selection;
239         if (this.editor)
240         {
241             selection = this.editor.getSelection(); 
242             return selection.start === selection.end;
243         }
244         return true;
245     },
246 
247     hasFocus: function()
248     {
249         try
250         {
251             if (this.editor)
252                 return this.editor.hasFocus();
253         }
254         catch (e)
255         {
256         }
257     },
258 
259     focus: function()
260     {
261         if (this.editor)
262             this.editor.focus();
263     },
264 
265     fontSizeAdjust: function(adjust)
266     {
267         if (!this.editor || !this.editor._view)
268             return;
269 
270         if (typeof(SourceEditor) != "undefined")
271         {
272             var doc = this.editor._view._frame.contentDocument;
273 
274             // See issue 5488
275             //doc.body.style.fontSizeAdjust = adjust;
276         }
277         else
278         {
279             this.editor.textBox.style.fontSizeAdjust = adjust;
280         }
281     }
282 });
283 
284 // ********************************************************************************************* //
285 // Getters/setters
286 
287 Firebug.CommandEditor.__defineGetter__("value", function()
288 {
289     return this.getText();
290 });
291 
292 Firebug.CommandEditor.__defineSetter__("value", function(val)
293 {
294     this.setText(val);
295 });
296 
297 // ********************************************************************************************* //
298 // Text Editor
299 
300 /**
301  * A simple <textbox> element is used in environments where the Orion SourceEditor is not
302  * available (such as SeaMonkey)
303  */
304 function TextEditor() {}
305 TextEditor.prototype =
306 {
307     init: function(editorElement, config, callback)
308     {
309         var commandEditorBox = editorElement.parentNode;
310 
311         this.textBox = commandEditorBox.ownerDocument.createElement("textbox");
312         this.textBox.setAttribute("id", "fbCommandEditor");
313         this.textBox.setAttribute("multiline", "true");
314         this.textBox.setAttribute("flex", "1");
315         this.textBox.setAttribute("newlines", "pasteintact");
316         this.textBox.setAttribute("label", "CommandEditor");
317 
318         commandEditorBox.replaceChild(this.textBox, editorElement);
319 
320         // The original source editor is also loaded asynchronously.
321         setTimeout(callback);
322     },
323 
324     destroy: function()
325     {
326     },
327 
328     addEventListener: function(type, callback)
329     {
330         if (!type)
331             return;
332 
333         Events.addEventListener(this.textBox, type, callback, true);
334     },
335 
336     removeEventListener: function(type, callback)
337     {
338         if (!type)
339             return;
340 
341         Events.removeEventListener(this.textBox, type, callback, true);
342     },
343 
344     setCaretOffset: function(offset)
345     {
346     },
347 
348     getCharCount: function()
349     {
350         return this.textBox.value ? this.textBox.value.length : 0;
351     },
352 
353     setText: function(text)
354     {
355         this.textBox.value = text;
356     },
357 
358     getText: function()
359     {
360         return this.textBox.value;
361     },
362 
363     setSelection: function(start, end)
364     {
365         this.textBox.setSelectionRange(start, end);
366     },
367 
368     getSelection: function()
369     {
370         return {
371             start: this.textBox.selectionStart,
372             end: this.textBox.selectionEnd
373         };
374     },
375 
376     hasFocus: function()
377     {
378         return this.textBox.getAttribute("focused") == "true";
379     },
380 
381     focus: function()
382     {
383         this.textBox.focus();
384     },
385 
386     getSelectedText: function()
387     {
388         var start = this.textBox.selectionStart;
389         var end = this.textBox.selectionEnd;
390 
391         return this.textBox.value.substring(start, end);
392     } 
393 }
394 
395 // ********************************************************************************************* //
396 // Registration
397 
398 Firebug.registerModule(Firebug.CommandEditor);
399 
400 return Firebug.CommandEditor;
401 
402 // ********************************************************************************************* //
403 });
404