]> git.donarmstrong.com Git - dactyl.git/blob - common/modules/prefs.jsm
981a6beb0f609ed1c751e6d284879b836f54c280
[dactyl.git] / common / modules / prefs.jsm
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>
4 //
5 // This work is licensed for reuse under an MIT license. Details are
6 // given in the LICENSE.txt file included with this file.
7 "use strict";
8
9 try {
10
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"]
16 }, this);
17
18 var Prefs = Module("prefs", XPCOM([Ci.nsIObserver, Ci.nsISupportsWeakReference]), {
19     SAVED: "extensions.dactyl.saved.",
20     RESTORE: "extensions.dactyl.restore.",
21     INIT: {},
22
23     init: function (branch, defaults) {
24         this._prefContexts = [];
25
26         this.branch = services.pref[defaults ? "getDefaultBranch" : "getBranch"](branch || "");
27         if (this.branch instanceof Ci.nsIPrefBranch2)
28             this.branch.QueryInterface(Ci.nsIPrefBranch2);
29
30         this.defaults = defaults ? this : this.constructor(branch, true);
31
32         if (!defaults)
33             this.restore();
34
35         this._observers = {};
36     },
37
38     cleanup: function cleanup() {
39         if (this.defaults != this)
40             this.defaults.cleanup();
41         this._observers = {};
42         if (this.observe) {
43             this.branch.removeObserver("", this);
44             this.observe.unregister();
45             delete this.observe;
46         }
47     },
48
49     observe: null,
50     observers: {
51         "nsPref:changed": function (subject, data) {
52             let observers = this._observers[data];
53             if (observers) {
54                 let value = this.get(data, false);
55                 this._observers[data] = observers.filter(function (callback) {
56                     if (!callback.get())
57                         return false;
58                     util.trapErrors(callback.get(), null, value);
59                     return true;
60                 });
61             }
62         }
63     },
64
65     /**
66      * Adds a new preference observer for the given preference.
67      *
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.
71      */
72     watch: function (pref, callback, strong) {
73         if (!this.observe) {
74             util.addObserver(this);
75             this.branch.addObserver("", this, false);
76         }
77
78         if (!this._observers[pref])
79             this._observers[pref] = [];
80         this._observers[pref].push(!strong ? Cu.getWeakReference(callback) : { get: function () callback });
81     },
82
83     /**
84      * Lists all preferences matching *filter* or only those with changed
85      * values if *onlyNonDefault* is specified.
86      *
87      * @param {boolean} onlyNonDefault Limit the list to prefs with a
88      *     non-default value.
89      * @param {string} filter The list filter. A null filter lists all
90      *     prefs.
91      * @optional
92      */
93     list: function list(onlyNonDefault, filter) {
94         if (!filter)
95             filter = "";
96
97         let prefArray = this.getNames();
98         prefArray.sort();
99         function prefs() {
100             for (let [, pref] in Iterator(prefArray)) {
101                 let userValue = services.pref.prefHasUserValue(pref);
102                 if (onlyNonDefault && !userValue || pref.indexOf(filter) == -1)
103                     continue;
104
105                 let value = this.get(pref);
106
107                 let option = {
108                     isDefault: !userValue,
109                     default:   this.defaults.get(pref, null),
110                     value:     <>={template.highlight(value, true, 100)}</>,
111                     name:      pref,
112                     pre:       "\u00a0\u00a0" // Unicode nonbreaking space.
113                 };
114
115                 yield option;
116             }
117         };
118
119         return template.options(config.host + " Preferences", prefs.call(this));
120     },
121
122     /**
123      * Returns the value of a preference.
124      *
125      * @param {string} name The preference name.
126      * @param {value} defaultValue The value to return if the preference
127      *     is unset.
128      */
129     get: function get(name, defaultValue) {
130         if (defaultValue == null)
131             defaultValue = null;
132
133         let type = this.branch.getPrefType(name);
134         try {
135             switch (type) {
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;
142                 return value;
143             case Ci.nsIPrefBranch.PREF_INT:
144                 return this.branch.getIntPref(name);
145             case Ci.nsIPrefBranch.PREF_BOOL:
146                 return this.branch.getBoolPref(name);
147             default:
148                 return defaultValue;
149             }
150         }
151         catch (e) {
152             return defaultValue;
153         }
154     },
155
156     getDefault: deprecated("Prefs#defaults.get", function getDefault(name, defaultValue) this.defaults.get(name, defaultValue)),
157
158     /**
159      * Returns the names of all preferences.
160      *
161      * @param {string} branch The branch in which to search preferences.
162      *     @default ""
163      */
164     getNames: function (branch) this.branch.getChildList(branch || "", { value: 0 }),
165
166     _checkSafe: function (name, message, value) {
167         let curval = this.get(name, null);
168         if (arguments.length > 2 && curval === value)
169             return;
170         let defval = this.defaults.get(name, null);
171         let saved  = this.get(this.SAVED + name);
172
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.";
175             if (message)
176                 msg = template.linkifyHelp(msg + " " + message);
177             util.dactyl.warn(msg);
178         }
179     },
180
181     /**
182      * Resets the preference *name* to *value* but warns the user if the value
183      * is changed from its default.
184      *
185      * @param {string} name The preference name.
186      * @param {value} value The new preference value.
187      */
188     safeReset: function (name, message) {
189         this._checkSafe(name, message);
190         this.reset(name);
191         this.reset(this.SAVED + name);
192     },
193
194     /**
195      * Sets the preference *name* to *value* but warns the user if the value is
196      * changed from its default.
197      *
198      * @param {string} name The preference name.
199      * @param {value} value The new preference value.
200      */
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);
205     },
206
207     /**
208      * Sets the preference *name* to *value*.
209      *
210      * @param {string} name The preference name.
211      * @param {value} value The new preference value.
212      */
213     set: function (name, value) {
214         if (this._prefContexts.length) {
215             let val = this.get(name, null);
216             if (val != null)
217                 this._prefContexts[this._prefContexts.length - 1][name] = val;
218         }
219
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);
225
226         let type = this.branch.getPrefType(name);
227         switch (typeof value) {
228         case "string":
229             assertType(Ci.nsIPrefBranch.PREF_STRING);
230
231             this.branch.setComplexValue(name, Ci.nsISupportsString, services.String(value));
232             break;
233         case "number":
234             assertType(Ci.nsIPrefBranch.PREF_INT);
235
236             this.branch.setIntPref(name, value);
237             break;
238         case "boolean":
239             assertType(Ci.nsIPrefBranch.PREF_BOOL);
240
241             this.branch.setBoolPref(name, value);
242             break;
243         default:
244             throw FailedAssertion("Unknown preference type: " + typeof value + " (" + name + "=" + value + ")");
245         }
246     },
247
248     /**
249      * Saves the current value of a preference to be restored at next
250      * startup.
251      *
252      * @param {string} name The preference to save.
253      */
254     save: function (name) {
255         let val = this.get(name);
256         this.set(this.RESTORE + name, val);
257         this.safeSet(name, val);
258     },
259
260     /**
261      * Restores saved preferences in the given branch.
262      *
263      * @param {string} branch The branch from which to restore
264      *      preferences. @optional
265      */
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);
269             this.reset(pref);
270         }, this);
271     },
272
273     /**
274      * Resets the preference *name* to its default value.
275      *
276      * @param {string} name The preference name.
277      */
278     reset: function (name) {
279         try {
280             this.branch.clearUserPref(name);
281         }
282         catch (e) {} // ignore - thrown if not a user set value
283     },
284
285     /**
286      * Toggles the value of the boolean preference *name*.
287      *
288      * @param {string} name The preference name.
289      */
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));
294     },
295
296     /**
297      * Pushes a new preference context onto the context stack.
298      *
299      * @see #withContext
300      */
301     pushContext: function () {
302         this._prefContexts.push({});
303     },
304
305     /**
306      * Pops the top preference context from the stack.
307      *
308      * @see #withContext
309      */
310     popContext: function () {
311         for (let [k, v] in Iterator(this._prefContexts.pop()))
312             this.set(k, v);
313     },
314
315     /**
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.
319      *
320      * @param {function} func The function to call.
321      * @param {Object} func The 'this' object with which to call *func*
322      * @see #pushContext
323      * @see #popContext
324      */
325     withContext: function (func, self) {
326         try {
327             this.pushContext();
328             return func.call(self);
329         }
330         finally {
331             this.popContext();
332         }
333     }
334 }, {
335 }, {
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();
342         };
343     },
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, ""]))]);
347     }
348 });
349
350 var localPrefs = Prefs("extensions.dactyl.");
351 defineModule.modules.push(localPrefs);
352
353 endModule();
354
355 } catch(e){ if (!e.stack) e = Error(e); dump(e.fileName+":"+e.lineNumber+": "+e+"\n" + e.stack); }
356
357 // vim: set fdm=marker sw=4 ts=4 et ft=javascript: