1 /* See license.txt for terms of usage */
  2 /* Reused code from Keyconfig by Dorando: http://mozilla.dorando.at/keyconfig.xpi*/
  3 
  4 // ************************************************************************************************
  5 // Constants
  6 
  7 const Cc = Components.classes;
  8 const Ci = Components.interfaces;
  9 
 10 Components.utils.import("resource://gre/modules/Services.jsm");
 11 
 12 var branch = Services.prefs.getBranch("extensions.firebug.key.shortcut.");
 13 var defaultBranch = Services.prefs.getDefaultBranch("extensions.firebug.key.shortcut.");
 14 
 15 // Initialized from window parameters.
 16 var FBL;
 17 var FBTrace;
 18 var Locale;
 19 
 20 // Global variables used by this dialog.
 21 var shortcutNames = null;
 22 var gVKNames = [];
 23 var gLocaleKeys = [];
 24 var gPlatformKeys = new Object();
 25 var updatedShortcuts = {}
 26 var modified = false;
 27 var mustBeKeyChars = {
 28     VK_SEMICOLON      : ";",
 29     VK_EQUALS         : "=",
 30     VK_MULTIPLY       : "*",
 31     VK_ADD            : "+",
 32     VK_SUBTRACT       : "-",
 33     VK_DECIMAL        : ".",
 34     VK_DIVIDE         : "/",
 35     VK_COMMA          : ",",
 36     VK_PERIOD         : ".",
 37     VK_SLASH          : "/",
 38     VK_BACK_QUOTE     : "`",
 39     VK_OPEN_BRACKET   : "[",
 40     VK_BACK_SLASH     : "\\",
 41     VK_CLOSE_BRACKET  : "]",
 42     VK_QUOTE          : "'"
 43 };
 44 
 45 // ************************************************************************************************
 46 // Implemantation
 47 
 48 function init()
 49 {
 50     var args = window.arguments[0];
 51     FBL = args.FBL;
 52     FBTrace = args.FBTrace;
 53     Locale = FBL;
 54 
 55     setKeyInfo();
 56     shortcutNames = branch.getChildList("", {});
 57     shortcutNames.sort();
 58     shortcutNames.forEach(addShortcutRow);
 59     setHandlers();
 60     document.title = Locale.$STR("customizeShortcuts");
 61 
 62     if (FBTrace.DBG_SHORTCUTS)
 63         FBTrace.sysout("shortcuts.init; Customize Shortcuts dialog initialized.");
 64 }
 65 
 66 function setKeyInfo()
 67 {
 68     gLocaleKeys = document.getElementById("localeKeys");
 69     var platformKeys = document.getElementById("platformKeys");
 70     gPlatformKeys.shift = Locale.$STR("VK_SHIFT", platformKeys);
 71     gPlatformKeys.meta = Locale.$STR("VK_META", platformKeys);
 72     gPlatformKeys.alt = Locale.$STR("VK_ALT", platformKeys);
 73     gPlatformKeys.ctrl = Locale.$STR("VK_CONTROL", platformKeys);
 74     gPlatformKeys.sep = Locale.$STR("MODIFIER_SEPARATOR", platformKeys);
 75 
 76     switch (Services.prefs.getIntPref("ui.key.accelKey"))
 77     {
 78         case KeyEvent.DOM_VK_CONTROL:
 79             gPlatformKeys.accel = gPlatformKeys.ctrl;
 80             break;
 81         case KeyEvent.DOM_VK_ALT:
 82             gPlatformKeys.accel = gPlatformKeys.alt;
 83             break;
 84         case KeyEvent.DOM_VK_META:
 85             gPlatformKeys.accel = gPlatformKeys.meta;
 86             break;
 87         default:
 88             gPlatformKeys.accel = (window.navigator.platform.search("Mac") == 0 ? gPlatformKeys.meta : gPlatformKeys.ctrl);
 89     }
 90 
 91     for ( var property in KeyEvent)
 92     {
 93         gVKNames[KeyEvent[property]] = property.replace("DOM_", "");
 94     }
 95     gVKNames[8] = "VK_BACK";
 96 }
 97 
 98 function setHandlers()
 99 {
100     var i;
101     var shortcutSinks = document.getElementsByClassName("shortcutSink");
102     for (i = 0; i < shortcutSinks.length; i++)
103         shortcutSinks[i].addEventListener("keydown", recognizeShortcut, false);
104 
105     var resetBtns = document.getElementsByClassName("shortcutResetBtn");
106     for (i = 0; i < resetBtns.length; i++)
107         resetBtns[i].addEventListener("command", handleResetBtn, false);
108 }
109 
110 function saveChanges()
111 {
112     if (!modified)
113         return true;
114 
115     shortcutNames.forEach(saveShortcut);
116 
117     var e = Services.wm.getEnumerator("navigator:browser");
118     while (e.hasMoreElements())
119     {
120         var fbug = e.getNext().Firebug
121         fbug && fbug.ShortcutsModel.initShortcuts();
122     }
123     return true;
124 }
125 
126 function saveShortcut(shortcutId, index, array)
127 {
128     if (shortcutId in updatedShortcuts)
129         branch.setCharPref(shortcutId, updatedShortcuts[shortcutId]);
130 }
131 
132 function handleResetBtn(event)
133 {
134     var element = event.target.id.replace("_reset", "");
135     if (branch.prefHasUserValue(element))
136     {
137         branch.clearUserPref(element);
138         modified = true;
139     }
140 
141     event.target.hidden = true;
142     var textbox = document.getElementById(element + "_shortcut");
143     if (textbox)
144         textbox.value = getHumanShortcut(element);
145 }
146 
147 function getHumanShortcut(element, getDefault)
148 {
149     var shortcut = (getDefault ? defaultBranch : branch).getCharPref(element);
150     var tokens = shortcut.split(" ");
151     var keyCode = tokens.pop();
152 
153     if (keyCode.length == 1)
154         return getFormattedKey(tokens.join(","), keyCode, null);
155     else
156         return getFormattedKey(tokens.join(","), null, keyCode);
157 }
158 
159 function addShortcutRow(element, index, array)
160 {
161     // Get key configuration from preference
162     var shortcut = getHumanShortcut(element);
163     var defaultShortcut = getHumanShortcut(element, true);
164     var rows = document.getElementById("shortcutGridRows");
165     var row = document.createElement("row");
166 
167     var label = document.createElement("label");
168     // Get the label from firebug.properties
169     var labelText = Locale.$STR("firebug.shortcut."+element+".label");
170     var tooltipText = Locale.$STR("firebug.shortcut.tip."+element);
171 
172     // $STR defaults to property name (label) if it's not defined. We don't want that
173     if (labelText == "label")
174         labelText = element;
175     label.setAttribute("value", labelText);
176     row.appendChild(label);
177 
178     var textbox = document.createElement("textbox");
179     textbox.id = element + "_shortcut";
180     textbox.className = "shortcutSink";
181     row.setAttribute("tooltiptext", tooltipText != "tip" ? tooltipText : "");
182     textbox.setAttribute("value", shortcut);
183     textbox.setAttribute("default_value", defaultShortcut);
184     row.appendChild(textbox);
185 
186     var resetBtn = document.createElement("toolbarbutton");
187     resetBtn.id = element + "_reset";
188     resetBtn.setAttribute("label", Locale.$STR("a11y.labels.reset"));
189     resetBtn.setAttribute("aria-label", Locale.$STRF("a11y.labels.reset_shortcut", [labelText]));
190     resetBtn.className = "shortcutResetBtn";
191     resetBtn.hidden = defaultShortcut == shortcut;
192     row.appendChild(resetBtn);
193     rows.appendChild(row);
194 }
195 
196 function recognizeShortcut(event)
197 {
198     // We're using keydown, so we always start with keycode
199     var shortcut = "";
200     if ([KeyEvent.DOM_VK_TAB, KeyEvent.DOM_VK_SHIFT, KeyEvent.DOM_VK_CONTROL, KeyEvent.DOM_VK_ALT].
201             indexOf(event.keyCode) != -1 ||
202         ((!event.shiftKey && !event.altKey && !event.ctrlKey) &&
203         [KeyEvent.DOM_VK_RETURN, KeyEvent.DOM_VK_ESCAPE].indexOf(event.keyCode) != -1))
204     {
205         // Always let tab pass. Let enter & escape pass, if no modifiers are used
206         return;
207     }
208 
209     modified = true;
210     event.preventDefault();
211     event.stopPropagation();
212 
213     var target = event.target;
214 
215     // Backspace pressed
216     if (event.keyCode == 8 && !event.shiftKey && !event.altKey && !event.ctrlKey)
217     {
218         updatedShortcuts[target.id.replace("_shortcut", "")] = "";
219         target.value = "";
220 
221         // Update reset button visibility
222         target.nextSibling.hidden = false;
223 
224         return false;
225     }
226 
227     var modifiers = [];
228     if (event.altKey)
229         modifiers.push("alt");
230     if (event.ctrlKey)
231         modifiers.push("control");
232     if (event.metaKey)
233         modifiers.push("meta");
234     if (event.shiftKey)
235         modifiers.push("shift");
236 
237     modifiers = modifiers.join(" ");
238     var keyConstant = key = null;
239 
240     keyConstant = gVKNames[event.keyCode];
241 
242     if (!keyConstant) //should not happen
243         return;
244 
245     // Check if the keycode is actually a printable character
246     // 1. Convert some of the punctuation keyConstants (e.g. VK_COMMA) back to actual characters
247     if (mustBeKeyChars[keyConstant])
248     {
249         key = mustBeKeyChars[keyConstant];
250     }
251     else
252     {
253         // 2. Detect basic alphanumeric keys
254         var keyNameGuess = keyConstant.replace("VK_", "");
255         if (keyNameGuess.length == 1)
256             key = keyNameGuess.toLowerCase();
257     }
258 
259     if (modifiers.length > 0)
260         shortcut += modifiers+" ";
261     shortcut += (key ? key : keyConstant);
262 
263     updatedShortcuts[target.id.replace("_shortcut", "")] = shortcut;
264 
265     // Show formatted shortcut in textbox
266     modifiers = modifiers.replace(" ", ",");
267     var formatted = getFormattedKey(modifiers, key, keyConstant);
268 
269     target.value = formatted;
270 
271     // Update reset button visibility
272     target.nextSibling.hidden = formatted == target.getAttribute("default_value");
273     return false;
274 }
275 
276 function getFormattedKey(modifiers, key, keyConstant)
277 {
278     if ((modifiers == "shift,alt,control,accel" && keyConstant == "VK_SCROLL_LOCK") ||
279         (key == "" || (!key && keyConstant == "")))
280     {
281         return "";
282     }
283 
284     var val = "";
285     if (modifiers)
286     {
287         val = modifiers.replace(/^[\s,]+|[\s,]+$/g, "").split(/[\s,]+/g).join(gPlatformKeys.sep).
288             replace("alt", gPlatformKeys.alt).replace("shift", gPlatformKeys.shift).
289             replace("control", gPlatformKeys.ctrl).replace("meta", gPlatformKeys.meta).
290             replace("accel", gPlatformKeys.accel) +
291             gPlatformKeys.sep;
292     }
293 
294     if (key)
295         return val += key;
296 
297     if (keyConstant)
298     {
299         try
300         {
301             // See if a localized version for keyConstant exists (F keys, arrow, enter, pgup, etc.)
302             val += gLocaleKeys.getString(keyConstant);
303         }
304         catch (e)
305         {
306             // Create human friendly alternative ourself
307             val += keyConstant.replace("VK_", "").replace("_", " ").toLowerCase();
308         }
309     }
310     return val;
311 }
312 
313 // ********************************************************************************************* //
314