/***** BEGIN LICENSE BLOCK ***** {{{ Version: MPL 1.1/GPL 2.0/LGPL 2.1 The contents of this file are subject to the Mozilla Public License Version 1.1 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.mozilla.org/MPL/ Software distributed under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for the specific language governing rights and limitations under the License. (c) 2006-2008: Martin Stubenschrott Alternatively, the contents of this file may be used under the terms of either the GNU General Public License Version 2 or later (the "GPL"), or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), in which case the provisions of the GPL or the LGPL are applicable instead of those above. If you wish to allow use of your version of this file only under the terms of either the GPL or the LGPL, and not to allow others to use your version of this file under the terms of the MPL, indicate your decision by deleting the provisions above and replace them with the notice and other provisions required by the GPL or the LGPL. If you do not delete the provisions above, a recipient may use your version of this file under the terms of any one of the MPL, the GPL or the LGPL. }}} ***** END LICENSE BLOCK *****/ vimperator.Option = function (names, type, extraInfo) //{{{ { if (!names || !type) return null; var value = null; this.name = names[0]; this.names = names; this.type = type; if (extraInfo) { this.shortHelp = extraInfo.shortHelp || null; // "", 0 are valid default values if (extraInfo.defaultValue !== undefined) this.defaultValue = extraInfo.defaultValue; else this.defaultValue = null; value = this.defaultValue; if (extraInfo.setter) this.setter = extraInfo.setter; if (extraInfo.getter) this.getter = extraInfo.getter; this.completer = extraInfo.completer || null; this.validator = extraInfo.validator || null; } // add noOPTION variant of boolean OPTION to this.names // FIXME: are these variants really considered names? if (this.type == "boolean") { this.names = []; // reset since order is important for (var i = 0; i < names.length; i++) { this.names.push(names[i]); this.names.push("no" + names[i]); } } // NOTE: forced defaults need to use vimperator.options.getPref this.__defineGetter__("value", function () { if (this.getter) this.getter.call(this); return value; } ); this.__defineSetter__("value", function (newValue) { value = newValue; if (this.setter) this.setter.call(this, value); } ); // TODO: add is[Type]() queries for use in set()? // : add isValid() or just throw an exception? this.hasName = function (name) { return this.names.some(function (e) { return e == name; }); }; this.isValidValue = function (value) { if (this.validator) return this.validator(value); else return true; }; this.reset = function () { this.value = this.defaultValue; }; }; //}}} vimperator.Options = function () //{{{ { //////////////////////////////////////////////////////////////////////////////// ////////////////////// PRIVATE SECTION ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ var firefoxBranch = Components.classes["@mozilla.org/preferences-service;1"] .getService(Components.interfaces.nsIPrefBranch); var defaultBranch = firefoxBranch.getDefaultBranch(""); var vimperatorBranch = firefoxBranch.getBranch("extensions.vimperator."); var options = []; // save if we already changed a GUI related option, used for setInitialGUI var guioptionsDone = false, showtablineDone = false, laststatusDone = false; function optionsIterator() { for (var i = 0; i < options.length; i++) yield options[i]; throw StopIteration; } function storePreference(name, value, branch) { if (!branch) branch = firefoxBranch; var type = branch.getPrefType(name); switch (typeof value) { case "string": if (type == branch.PREF_INVALID || type == branch.PREF_STRING) branch.setCharPref(name, value); else if (type == branch.PREF_INT) vimperator.echoerr("E521: Number required after =: " + name + "=" + value); else vimperator.echoerr("E474: Invalid argument: " + name + "=" + value); break; case "number": if (type == branch.PREF_INVALID || type == branch.PREF_INT) branch.setIntPref(name, value); else vimperator.echoerr("E474: Invalid argument: " + name + "=" + value); break; case "boolean": if (type == branch.PREF_INVALID || type == branch.PREF_BOOL) branch.setBoolPref(name, value); else if (type == branch.PREF_INT) vimperator.echoerr("E521: Number required after =: " + name + "=" + value); else vimperator.echoerr("E474: Invalid argument: " + name + "=" + value); break; default: vimperator.echoerr("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")"); } } function loadPreference(name, forcedDefault, branch) { var defaultValue = null; if (forcedDefault != null) // this argument sets defaults for non-user settable options (like comp_history) defaultValue = forcedDefault; if (!branch) branch = firefoxBranch; var type = branch.getPrefType(name); try { switch (type) { case branch.PREF_STRING: var value = branch.getComplexValue(name, Components.interfaces.nsISupportsString).data; // Try in case it's a localized string (will throw an exception if not) if (!branch.prefIsLocked(name) && !branch.prefHasUserValue(name) && /^chrome:\/\/.+\/locale\/.+\.properties/.test(value)) value = branch.getComplexValue(name, Components.interfaces.nsIPrefLocalizedString).data; return value; case branch.PREF_INT: return branch.getIntPref(name); case branch.PREF_BOOL: return branch.getBoolPref(name); default: return defaultValue; } } catch (e) { return defaultValue; } } // show/hide the menubar, toolbar and bookmarks toolbar function setGuiOptions(value) { // FIXME: when we release a vimperator with a Firefox3 requirement (no beta), remove the old ids try { document.getElementById("toolbar-menubar").collapsed = !/m/.test(value); // these the new ids for Firefox > 20080130 document.getElementById("navigation-toolbar").collapsed = !/T/.test(value); document.getElementById("personal-toolbar"). collapsed = !/b/.test(value); } catch (e) { // these two ids are for Firefox <= 20080130 document.getElementById("nav-bar"). collapsed = !/T/.test(value); document.getElementById("PersonalToolbar").collapsed = !/b/.test(value); // vimperator.log("setGuiOptions raised an exception"); } guioptionsDone = true; } function setLastStatus(value) { if (value == 0) document.getElementById("status-bar").collapsed = true; else if (value == 1) vimperator.echo("show status line only with > 1 window not implemented yet"); else document.getElementById("status-bar").collapsed = false; laststatusDone = true; } function setShowTabline(value) { var tabs = getBrowser().mStrip.getElementsByClassName("tabbrowser-tabs")[0]; if (!tabs) return; if (value == 0) { tabs.collapsed = true; } else if (value == 1) { storePreference("browser.tabs.autoHide", true); tabs.collapsed = false; } else { storePreference("browser.tabs.autoHide", false); tabs.collapsed = false; } showtablineDone = true; } function setTitleString(value) { document.getElementById("main-window").setAttribute("titlemodifier", value); if (window.content.document.title.length > 0) document.title = window.content.document.title + " - " + value; else document.title = value; } function setPopups(value) { var values = [ [0, 1], // always in current tab [0, 3], // in a new tab [2, 3], // in a new window if it has specified sizes [1, 2]];// always in new window storePreference("browser.link.open_newwindow.restriction", values[value][0]); storePreference("browser.link.open_newwindow", values[value][1]); } // // firefox preferences which need to be changed to work well with vimperator // // work around firefox popup blocker var popupAllowedEvents = loadPreference("dom.popup_allowed_events", "change click dblclick mouseup reset submit"); if (!/keypress/.test(popupAllowedEvents)) storePreference("dom.popup_allowed_events", popupAllowedEvents + " keypress"); // TODO: shouldn't we be resetting these in destroy() as well? // we have our own typeahead find implementation storePreference("accessibility.typeaheadfind.autostart", false); storePreference("accessibility.typeaheadfind", false); // actually the above setting should do it, but has no effect in firefox // start with saved session storePreference("browser.startup.page", 3); /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// PUBLIC SECTION ////////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ var optionManager = { __iterator__: function () { return optionsIterator(); }, get: function (name) { for (var i = 0; i < options.length; i++) { if (options[i].hasName(name)) return options[i]; } return null; }, add: function (option) { this.__defineGetter__(option.name, function () { return option.value; }); this.__defineSetter__(option.name, function (value) { option.value = value; }); options.push(option); }, destroy: function () { // reset some modified firefox prefs if (loadPreference("dom.popup_allowed_events", "change click dblclick mouseup reset submit") == popupAllowedEvents + " keypress") storePreference("dom.popup_allowed_events", popupAllowedEvents); }, list: function (onlyNondefault) { var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + ""; var name, value, def; for (var i = 0; i < options.length; i++) { name = options[i].name; value = options[i].value; def = options[i].defaultValue; if (onlyNondefault && value == def) continue; if (options[i].type == "boolean") { name = value ? " " + name : "no" + name; if (value != def) name = "" + name + " (default: " + (def ? "" : "no") + options[i].name + ")"; list += ""; } else { if (value != def) { name = "" + name + ""; value = vimperator.util.colorize(value, false) + " (default: " + def + ")"; } else value = vimperator.util.colorize(value, false); list += ""; } } list += "
--- Options ---
" + name + "
" + " " + name + "=" + value + "
"; vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); }, listFirefoxPrefs: function (onlyNondefault, filter) { if (!filter) filter = ""; var prefArray = firefoxBranch.getChildList("", {value: 0}); prefArray.sort(); var list = ":" + vimperator.util.escapeHTML(vimperator.commandline.getCommand()) + "
" + ""; var name, value; for (var i = 0; i < prefArray.length; i++) { var userValue = firefoxBranch.prefHasUserValue(prefArray[i]); if ((!onlyNondefault || userValue) && prefArray[i].indexOf(filter) >= 0) { name = prefArray[i]; value = this.getFirefoxPref(name); if (typeof value == "string") value = value.substr(0,100).replace(/\n/g, " "); value = vimperator.util.colorize(value, false); defaultValue = loadPreference(name, null, defaultBranch); if (defaultValue == null) defaultValue = "no default"; else defaultValue = "default: " + defaultValue; if (userValue) { list += ""; } else list += ""; } } list += "
--- Firefox Options ---
" + name + "=" + value + " (" + defaultValue + ")
" + name + "=" + value + "
"; vimperator.commandline.echo(list, vimperator.commandline.HL_NORMAL, vimperator.commandline.FORCE_MULTILINE); }, // this hack is only needed, because we need to do asynchronous loading of the .vimperatorrc setInitialGUI: function () { if (vimperator.config.name != "Vimperator") return; if (!guioptionsDone) this.get("guioptions").reset(); if (!laststatusDone) this.get("laststatus").reset(); if (!showtablineDone) this.get("showtabline").reset(); }, // TODO: separate Preferences from Options? Would these utility functions // be better placed in the 'core' vimperator namespace somewhere? setPref: function (name, value) { return storePreference(name, value, vimperatorBranch); }, getPref: function (name, forcedDefault) { return loadPreference(name, forcedDefault, vimperatorBranch); }, setFirefoxPref: function (name, value) { return storePreference(name, value); }, getFirefoxPref: function (name, forcedDefault) { return loadPreference(name, forcedDefault); }, resetFirefoxPref: function (name) { return firefoxBranch.clearUserPref(name); }, // this works only for booleans invertFirefoxPref: function (name) { if (firefoxBranch.getPrefType(name) == firefoxBranch.PREF_BOOL) this.setFirefoxPref(name, !this.getFirefoxPref(name)); else vimperator.echoerr("E488: Trailing characters: " + name + "!"); } }; /////////////////////////////////////////////////////////////////////////////}}} ////////////////////// DEFAULT OPTIONS ///////////////////////////////////////// /////////////////////////////////////////////////////////////////////////////{{{ const DEFAULT_HINTTAGS = "//*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " + "//input[not(@type='hidden')] | //a | //area | //iframe | //textarea | //button | //select | " + "//xhtml:*[@onclick or @onmouseover or @onmousedown or @onmouseup or @oncommand or @class='lk' or @class='s'] | " + "//xhtml:input[not(@type='hidden')] | //xhtml:a | //xhtml:area | //xhtml:iframe | //xhtml:textarea | //xhtml:button | //xhtml:select"; optionManager.add(new vimperator.Option(["activate", "act"], "stringlist", { shortHelp: "Define when tabs are automatically activated", defaultValue: "homepage,quickmark,tabopen,paste", validator: function (value) { return value.split(",").every(function (item) { return /^(homepage|quickmark|tabopen|paste|)$/.test(item); }); } } )); optionManager.add(new vimperator.Option(["complete", "cpt"], "charlist", { shortHelp: "Items which are completed at the :[tab]open prompt", defaultValue: "sfbh", validator: function (value) { return !/[^sfbh]/.test(value); } } )); optionManager.add(new vimperator.Option(["defsearch", "ds"], "string", { shortHelp: "Set the default search engine", defaultValue: "google" } )); optionManager.add(new vimperator.Option(["editor"], "string", { shortHelp: "Set the external text editor", defaultValue: "gvim -f" } )); optionManager.add(new vimperator.Option(["extendedhinttags", "eht"], "string", { shortHelp: "XPath string of hintable elements activated by ';'", defaultValue: DEFAULT_HINTTAGS } )); optionManager.add(new vimperator.Option(["focusedhintstyle", "fhs"], "string", { shortHelp: "CSS specification of focused hints", defaultValue: "z-index:5000; font-family:monospace; font-size:12px; color:ButtonText; background-color:ButtonShadow; " + "border-color:ButtonShadow; border-width:1px; border-style:solid; padding:0px 1px 0px 1px; position:absolute;" } )); optionManager.add(new vimperator.Option(["fullscreen", "fs"], "boolean", { shortHelp: "Show the current window fullscreen", setter: function (value) { window.fullScreen = value; }, getter: function () { return window.fullScreen; }, defaultValue: false } )); optionManager.add(new vimperator.Option(["guioptions", "go"], "charlist", { shortHelp: "Show or hide the menu, toolbar and scrollbars", setter: function (value) { setGuiOptions(value); }, defaultValue: "", validator: function (value) { return !/[^mTb]/.test(value); } } )); optionManager.add(new vimperator.Option(["hinttimeout", "hto"], "number", { shortHelp: "Automatically follow non unique numerical hint after {arg} ms", defaultValue: 0, validator: function (value) { return value >= 0; } } )); optionManager.add(new vimperator.Option(["hintstyle", "hs"], "string", { shortHelp: "CSS specification of unfocused hints", defaultValue: "z-index:5000; font-family:monospace; font-size:12px; color:white; background-color:red; " + "border-color:ButtonShadow; border-width:0px; border-style:solid; padding:0px 1px 0px 1px; position:absolute;" } )); optionManager.add(new vimperator.Option(["hinttags", "ht"], "string", { shortHelp: "XPath string of hintable elements activated by 'f' and 'F'", defaultValue: DEFAULT_HINTTAGS } )); optionManager.add(new vimperator.Option(["history", "hi"], "number", { shortHelp: "Number of Ex commands and search patterns to store in the commandline history", defaultValue: 500 } )); optionManager.add(new vimperator.Option(["hlsearch", "hls"], "boolean", { shortHelp: "Highlight previous search pattern matches", setter: function (value) { if (value) vimperator.search.highlight(); else vimperator.search.clear(); }, defaultValue: false } )); optionManager.add(new vimperator.Option(["hlsearchstyle", "hlss"], "string", { shortHelp: "CSS specification of highlighted search items", defaultValue: "color: black; background-color: yellow; padding: 0; display: inline;" } )); optionManager.add(new vimperator.Option(["ignorecase", "ic"], "boolean", { shortHelp: "Ignore case in search patterns", defaultValue: true } )); optionManager.add(new vimperator.Option(["incsearch", "is"], "boolean", { shortHelp: "Show where the search pattern matches as it is typed", defaultValue: true } )); optionManager.add(new vimperator.Option(["insertmode", "im"], "boolean", { shortHelp: "Use Insert mode as the default for text areas", defaultValue: true } )); optionManager.add(new vimperator.Option(["laststatus", "ls"], "number", { shortHelp: "Show the status line", defaultValue: 2, setter: function (value) { setLastStatus(value); }, validator: function (value) { return (value >= 0 && value <= 2); } } )); optionManager.add(new vimperator.Option(["linksearch", "lks"], "boolean", { shortHelp: "Limit the search to hyperlink text", defaultValue: false } )); optionManager.add(new vimperator.Option(["more"], "boolean", { shortHelp: "Pause the message list window when more than one screen of listings is displayed", defaultValue: true } )); optionManager.add(new vimperator.Option(["nextpattern"], "stringlist", { shortHelp: "Patterns to use when guessing the 'next' page in a document sequence", defaultValue: "\\bnext,^>$,^(>>|»)$,^(>|»),(>|»)$" } )); optionManager.add(new vimperator.Option(["pageinfo", "pa"], "charlist", { shortHelp: "Desired info on :pa[geinfo]", defaultValue: "gfm", validator: function (value) { return !(/[^gfm]/.test(value) || value.length > 3 || value.length < 1); } } )); optionManager.add(new vimperator.Option(["popups", "pps"], "number", { shortHelp: "Where to show requested popup windows", defaultValue: 1, setter: function (value) { setPopups(value); }, validator: function (value) { return (value >= 0 && value <= 3); } } )); optionManager.add(new vimperator.Option(["preload"], "boolean", { shortHelp: "Speed up first time history/bookmark completion", defaultValue: true } )); optionManager.add(new vimperator.Option(["previewheight", "pvh"], "number", { shortHelp: "Default height for preview window", defaultValue: 10, validator: function (value) { return (value >= 1 && value <= 50); } } )); optionManager.add(new vimperator.Option(["previouspattern"], "stringlist", { shortHelp: "Patterns to use when guessing the 'previous' page in a document sequence", defaultValue: "\\bprev|previous\\b,^<$,^(<<|«)$,^(<|«),(<|«)$" } )); optionManager.add(new vimperator.Option(["scroll", "scr"], "number", { shortHelp: "Number of lines to scroll with C-u and C-d commands", defaultValue: 0, validator: function (value) { return value >= 0; } } )); optionManager.add(new vimperator.Option(["showmode", "smd"], "boolean", { shortHelp: "Show the current mode in the command line", defaultValue: true } )); optionManager.add(new vimperator.Option(["showstatuslinks", "ssli"], "number", { shortHelp: "Show the destination of the link under the cursor in the status bar", defaultValue: 1, validator: function (value) { return (value >= 0 && value <= 2); } } )); optionManager.add(new vimperator.Option(["showtabline", "stal"], "number", { shortHelp: "Control when to show the tab bar of opened web pages", setter: function (value) { setShowTabline(value); }, defaultValue: 2, validator: function (value) { return (value >= 0 && value <= 2); } } )); optionManager.add(new vimperator.Option(["smartcase", "scs"], "boolean", { shortHelp: "Override the 'ignorecase' option if the pattern contains uppercase characters", defaultValue: true } )); optionManager.add(new vimperator.Option(["titlestring"], "string", { shortHelp: "Change the title of the browser window", setter: function (value) { setTitleString(value); }, defaultValue: "Vimperator" } )); optionManager.add(new vimperator.Option(["usermode", "um"], "boolean", { shortHelp: "Show current website with a minimal style sheet to make it easily accessible", setter: function (value) { getMarkupDocumentViewer().authorStyleDisabled = value; }, getter: function () { return getMarkupDocumentViewer().authorStyleDisabled; }, defaultValue: false } )); optionManager.add(new vimperator.Option(["verbose", "vbs"], "number", { shortHelp: "Define which type of messages are logged", defaultValue: 0, validator: function (value) { return (value >= 0 && value <= 9); } } )); optionManager.add(new vimperator.Option(["visualbell", "vb"], "boolean", { shortHelp: "Use visual bell instead of beeping on errors", setter: function (value) { vimperator.options.setFirefoxPref("accessibility.typeaheadfind.enablesound", !value); }, defaultValue: false } )); optionManager.add(new vimperator.Option(["wildmode", "wim"], "stringlist", { shortHelp: "Define how command line completion works", defaultValue: "list:full", validator: function (value) { return value.split(",").every(function (item) { return /^(full|longest|list|list:full|list:longest|)$/.test(item); }); } } )); optionManager.add(new vimperator.Option(["wildoptions", "wop"], "stringlist", { shortHelp: "Change how command line completion is done", defaultValue: "", validator: function (value) { return /^(sort|)$/.test(value); } } )); //}}} // we start with an "empty" GUI so that no toolbars or tabbar is shown if the user // sets them to empty in the .vimperatorrc, which is sourced asynchronously if (vimperator.config.name != "Vimperator") alert("mooh"); else alert("maeh"); if (vimperator.config.name != "Vimperator") return optionManager; setShowTabline(0); setGuiOptions(""); setLastStatus(0); guioptionsDone = showtablineDone = laststatusDone = false; setTitleString(optionManager.titlestring); setPopups(optionManager.popups); return optionManager; }; //}}} // vim: set fdm=marker sw=4 ts=4 et: