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