1 // Copyright (c) 2006-2008 by Martin Stubenschrott <stubenschrott@vimperator.org>
2 // Copyright (c) 2007-2011 by Doug Kearns <dougkearns@gmail.com>
3 // Copyright (c) 2008-2011 by Kris Maglione <maglione.k@gmail.com>
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
11 Components.utils.import("resource://dactyl/bootstrap.jsm");
12 defineModule("prefs", {
13 exports: ["Prefs", "localPrefs", "prefs"],
14 require: ["services", "util"],
15 use: ["config", "messages", "template"]
18 var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), {
19 SAVED: "extensions.dactyl.saved.",
20 RESTORE: "extensions.dactyl.restore.",
23 init: function (branch, defaults) {
24 this._prefContexts = [];
26 this.branch = services.pref[defaults ? "getDefaultBranch" : "getBranch"](branch || "");
27 if (this.branch instanceof Ci.nsIPrefBranch2)
28 this.branch.QueryInterface(Ci.nsIPrefBranch2);
30 this.defaults = defaults ? this : this.constructor(branch, true);
38 cleanup: function cleanup() {
39 if (this.defaults != this)
40 this.defaults.cleanup();
43 this.branch.removeObserver("", this);
44 this.observe.unregister();
51 "nsPref:changed": function (subject, data) {
52 let observers = this._observers[data];
54 let value = this.get(data, false);
55 this._observers[data] = observers.filter(function (callback) {
58 util.trapErrors(callback.get(), null, value);
66 * Adds a new preference observer for the given preference.
68 * @param {string} pref The preference to observe.
69 * @param {function(object)} callback The callback, called with the
70 * new value of the preference whenever it changes.
72 watch: function (pref, callback, strong) {
74 util.addObserver(this);
75 this.branch.addObserver("", this, false);
78 if (!this._observers[pref])
79 this._observers[pref] = [];
80 this._observers[pref].push(!strong ? Cu.getWeakReference(callback) : { get: function () callback });
84 * Lists all preferences matching *filter* or only those with changed
85 * values if *onlyNonDefault* is specified.
87 * @param {boolean} onlyNonDefault Limit the list to prefs with a
89 * @param {string} filter The list filter. A null filter lists all
93 list: function list(onlyNonDefault, filter) {
97 let prefArray = this.getNames();
100 for (let [, pref] in Iterator(prefArray)) {
101 let userValue = services.pref.prefHasUserValue(pref);
102 if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
105 let value = this.get(pref);
108 isDefault: !userValue,
109 default: this.defaults.get(pref, null),
110 value: <>={template.highlight(value, true, 100)}</>,
112 pre: "\u00a0\u00a0" // Unicode nonbreaking space.
119 return template.options(config.host + " Preferences", prefs.call(this));
123 * Returns the value of a preference.
125 * @param {string} name The preference name.
126 * @param {value} defaultValue The value to return if the preference
129 get: function get(name, defaultValue) {
130 if (defaultValue == null)
133 let type = this.branch.getPrefType(name);
136 case Ci.nsIPrefBranch.PREF_STRING:
137 let value = this.branch.getComplexValue(name, Ci.nsISupportsString).data;
138 // try in case it's a localized string (will throw an exception if not)
139 if (!this.branch.prefIsLocked(name) && !this.branch.prefHasUserValue(name) &&
140 RegExp("chrome://.+/locale/.+\\.properties").test(value))
141 value = this.branch.getComplexValue(name, Ci.nsIPrefLocalizedString).data;
143 case Ci.nsIPrefBranch.PREF_INT:
144 return this.branch.getIntPref(name);
145 case Ci.nsIPrefBranch.PREF_BOOL:
146 return this.branch.getBoolPref(name);
156 getDefault: deprecated("Prefs#defaults.get", function getDefault(name, defaultValue) this.defaults.get(name, defaultValue)),
159 * Returns the names of all preferences.
161 * @param {string} branch The branch in which to search preferences.
164 getNames: function (branch) this.branch.getChildList(branch || "", { value: 0 }),
166 _checkSafe: function (name, message, value) {
167 let curval = this.get(name, null);
168 if (arguments.length > 2 && curval === value)
170 let defval = this.defaults.get(name, null);
171 let saved = this.get(this.SAVED + name);
173 if (saved == null && curval != defval || saved != null && curval != saved) {
174 let msg = "Warning: setting preference " + name + ", but it's changed from its default value.";
176 msg = template.linkifyHelp(msg + " " + message);
177 util.dactyl.warn(msg);
182 * Resets the preference *name* to *value* but warns the user if the value
183 * is changed from its default.
185 * @param {string} name The preference name.
186 * @param {value} value The new preference value.
188 safeReset: function (name, message) {
189 this._checkSafe(name, message);
191 this.reset(this.SAVED + name);
195 * Sets the preference *name* to *value* but warns the user if the value is
196 * changed from its default.
198 * @param {string} name The preference name.
199 * @param {value} value The new preference value.
201 safeSet: function (name, value, message, skipSave) {
202 this._checkSafe(name, message, value);
203 this.set(name, value);
204 this[skipSave ? "reset" : "set"](this.SAVED + name, value);
208 * Sets the preference *name* to *value*.
210 * @param {string} name The preference name.
211 * @param {value} value The new preference value.
213 set: function (name, value) {
214 if (this._prefContexts.length) {
215 let val = this.get(name, null);
217 this._prefContexts[this._prefContexts.length - 1][name] = val;
220 function assertType(needType)
221 util.assert(type === Ci.nsIPrefBranch.PREF_INVALID || type === needType,
222 type === Ci.nsIPrefBranch.PREF_INT
223 ? "E521: Number required after =: " + name + "=" + value
224 : "E474: Invalid argument: " + name + "=" + value);
226 let type = this.branch.getPrefType(name);
227 switch (typeof value) {
229 assertType(Ci.nsIPrefBranch.PREF_STRING);
231 this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value));
234 assertType(Ci.nsIPrefBranch.PREF_INT);
236 this.branch.setIntPref(name, value);
239 assertType(Ci.nsIPrefBranch.PREF_BOOL);
241 this.branch.setBoolPref(name, value);
244 throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
249 * Saves the current value of a preference to be restored at next
252 * @param {string} name The preference to save.
254 save: function (name) {
255 let val = this.get(name);
256 this.set(this.RESTORE + name, val);
257 this.safeSet(name, val);
261 * Restores saved preferences in the given branch.
263 * @param {string} branch The branch from which to restore
264 * preferences. @optional
266 restore: function (branch) {
267 this.getNames(this.RESTORE + (branch || "")).forEach(function (pref) {
268 this.safeSet(pref.substr(this.RESTORE.length), this.get(pref), null, true);
274 * Resets the preference *name* to its default value.
276 * @param {string} name The preference name.
278 reset: function (name) {
280 this.branch.clearUserPref(name);
282 catch (e) {} // ignore - thrown if not a user set value
286 * Toggles the value of the boolean preference *name*.
288 * @param {string} name The preference name.
290 toggle: function (name) {
291 util.assert(this.branch.getPrefType(name) === Ci.nsIPrefBranch.PREF_BOOL,
292 _("error.trailing", name + "!"));
293 this.set(name, !this.get(name));
297 * Pushes a new preference context onto the context stack.
301 pushContext: function () {
302 this._prefContexts.push({});
306 * Pops the top preference context from the stack.
310 popContext: function () {
311 for (let [k, v] in Iterator(this._prefContexts.pop()))
316 * Executes *func* with a new preference context. When *func* returns, the
317 * context is popped and any preferences set via {@link #setPref} or
318 * {@link #invertPref} are restored to their previous values.
320 * @param {function} func The function to call.
321 * @param {Object} func The 'this' object with which to call *func*
325 withContext: function (func, self) {
328 return func.call(self);
336 completion: function (dactyl, modules) {
337 modules.completion.preference = function preference(context) {
338 context.anchored = false;
339 context.title = [config.host + " Preference", "Value"];
340 context.keys = { text: function (item) item, description: function (item) prefs.get(item) };
341 context.completions = prefs.getNames();
344 javascript: function (dactyl, modules) {
345 modules.JavaScript.setCompleter([this.get, this.safeSet, this.set, this.reset, this.toggle],
346 [function (context) (context.anchored=false, this.getNames().map(function (pref) [pref, ""]))]);
350 var localPrefs = Prefs("extensions.dactyl.");
351 defineModule.modules.push(localPrefs);
355 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
357 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: