]> git.donarmstrong.com Git - dactyl.git/blob - common/content/statusline.js
7b0cd48d59b65da6d38df8b1de4d8e08f1198f96
[dactyl.git] / common / content / statusline.js
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-2013 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 /** @scope modules */
10
11 var StatusLine = Module("statusline", {
12     init: function init() {
13         this._statusLine = document.getElementById("status-bar");
14         this.statusBar = document.getElementById("addon-bar") || this._statusLine;
15         this.baseGroup = this.statusBar == this._statusLine ? "StatusLine " : "";
16
17         if (config.haveGecko("25"))
18             config.tabbrowser.getStatusPanel().hidden = true;
19
20         if (this.statusBar.localName == "toolbar") {
21             styles.system.add("addon-bar", config.styleableChrome, literal(/*
22                 #status-bar { margin-top: 0 !important; }
23                 #addon-bar > statusbar { -moz-box-flex: 1 }
24                 #addon-bar > #addonbar-closebutton { visibility: collapse; }
25                 #addon-bar > xul|toolbarspring { visibility: collapse; }
26             */));
27
28             overlay.overlayWindow(window, {
29                 append: [
30                     ["statusbar", { id: "status-bar", ordinal: "0" }]]
31             });
32
33             highlight.loadCSS(util.compileMacro(literal(/*
34                 !AddonBar;#addon-bar {
35                     padding-left: 0 !important;
36                     min-height: 18px !important;
37                     -moz-appearance: none !important;
38                     <padding>
39                 }
40                 !AddonButton;#addon-bar xul|toolbarbutton {
41                     -moz-appearance: none !important;
42                     padding: 0 !important;
43                     border-width: 0px !important;
44                     min-width: 0 !important;
45                     color: inherit !important;
46                 }
47                 AddonButton:not(:hover)  background: transparent;
48             */))({ padding: config.OS.isMacOSX ? "padding-right: 10px !important;" : "" }));
49
50             if (document.getElementById("appmenu-button"))
51                 highlight.loadCSS(literal(/*
52                     AppmenuButton       min-width: 0 !important; padding: 0 .5em !important;
53                 */));
54         }
55
56         let _commandline = "if (window.dactyl) return dactyl.modules.commandline";
57         let prepend = [
58             ["button", { id: "appmenu-button", label: "", image: "chrome://branding/content/icon16.png", highlight: "AppmenuButton", xmlns: "xul" }],
59             ["toolbarbutton", { id: "appmenu-toolbar-button", label: "", image: "chrome://branding/content/icon16.png" }],
60             ["statusbar", { id: "status-bar", highlight: "StatusLine", xmlns: "xul" },
61                 // <!-- insertbefore="dactyl.statusBefore;" insertafter="dactyl.statusAfter;" -->
62                 ["hbox", { key: "container", hidden: "false", align: "center",  flex: "1" },
63                     ["stack", { orient: "horizontal",       align: "stretch", flex: "1", highlight: "CmdLine StatusCmdLine", class: "dactyl-container" },
64                         ["hbox", {                                                       highlight: "CmdLine StatusCmdLine", class: "dactyl-container" },
65                             ["label", { key: "mode",          crop: "end",                                                   class: "plain", collapsed: "true" }],
66                             ["stack", {  id: "dactyl-statusline-stack",       flex: "1", highlight: "CmdLine StatusCmdLine", class: "dactyl-container" },
67                                 ["textbox", { key: "url",     crop: "end",    flex: "1", style: "background: transparent;",  class: "plain dactyl-status-field-url",
68                                               readonly: "true" }],
69                                 ["hbox", { key: "message-box" },
70                                     ["label", { key: "message-pre", highlight: "WarningMsg StatusWarningMsg", class: "plain", readonly: "true" }],
71                                     ["textbox", { key: "message", crop: "end",    flex: "1", highlight: "Normal StatusNormal",   class: "plain",
72                                                   readonly: "true" }]]]]],
73                     ["label", { class: "plain", key: "inputbuffer",    flex: "0" }],
74                     ["label", { class: "plain", key: "progress",       flex: "0" }],
75                     ["label", { class: "plain", key: "tabcount",       flex: "0" }],
76                     ["label", { class: "plain", key: "bufferposition", flex: "0" }],
77                     ["label", { class: "plain", key: "zoomlevel",      flex: "0" }]],
78                 // just hide them since other elements expect them
79                 ["statusbarpanel", { id: "statusbar-display",       hidden: "true" }],
80                 ["statusbarpanel", { id: "statusbar-progresspanel", hidden: "true" }]]];
81
82         (function rec(ary) {
83             ary.forEach(function (elem) {
84                 if ("key" in elem[1])
85                     elem[1].id = "dactyl-statusline-field-" + elem[1].key;
86                 if (elem.length > 2)
87                     rec(elem.slice(2));
88             });
89         })(prepend);
90
91         overlay.overlayWindow(window, {
92             objects: this.widgets = { get status() this.container },
93             prepend: prepend
94         });
95
96         try {
97             this.security = content.document.dactylSecurity || "insecure";
98         }
99         catch (e) {}
100     },
101
102     get visible() !this.statusBar.collapsed && !this.statusBar.hidden,
103
104     signals: {
105         "browser.locationChange": function (webProgress, request, uri) {
106             let win = webProgress.DOMWindow;
107             this.status = uri;
108             this.progress = uri && win && win.dactylProgress || "";
109
110             // if this is not delayed we get the position of the old buffer
111             this.timeout(function () {
112                 this.updateBufferPosition();
113                 this.updateZoomLevel();
114             }, 500);
115         },
116         "browser.overLink": function (link) {
117             switch (options["showstatuslinks"]) {
118             case "status":
119                 this.overLink = link ? _("status.link", link) : null;
120                 this.status = link ? _("status.link", link) : buffer.uri;
121                 break;
122             case "command":
123                 this.overLink = null;
124                 if (link)
125                     dactyl.echo(_("status.link", link), commandline.FORCE_SINGLELINE);
126                 else
127                     commandline.clear();
128                 break;
129             }
130         },
131         "browser.progressChange": function onProgressChange(webProgress, request, curSelfProgress, maxSelfProgress, curTotalProgress, maxTotalProgress) {
132             if (webProgress && webProgress.DOMWindow)
133                 webProgress.DOMWindow.dactylProgress = curTotalProgress / maxTotalProgress;
134             this.progress = curTotalProgress / maxTotalProgress;
135         },
136         "browser.securityChange": function onSecurityChange(webProgress, request, state) {
137
138             if (state & Ci.nsIWebProgressListener.STATE_IS_BROKEN)
139                 this.security = "broken";
140             else if (state & Ci.nsIWebProgressListener.STATE_IDENTITY_EV_TOPLEVEL)
141                 this.security = "extended";
142             else if (state & Ci.nsIWebProgressListener.STATE_SECURE_HIGH)
143                 this.security = "secure";
144             else // if (state & Ci.nsIWebProgressListener.STATE_IS_INSECURE)
145                 this.security = "insecure";
146
147             if (webProgress && webProgress.DOMWindow)
148                 webProgress.DOMWindow.document.dactylSecurity = this.security;
149         },
150         "browser.stateChange": function onStateChange(webProgress, request, flags, status) {
151             const L = Ci.nsIWebProgressListener;
152
153             if (flags & (L.STATE_IS_DOCUMENT | L.STATE_IS_WINDOW))
154                 if (flags & L.STATE_START)
155                     this.progress = 0;
156                 else if (flags & L.STATE_STOP)
157                     this.progress = "";
158
159             if (flags & L.STATE_STOP)
160                 this.updateStatus();
161         },
162         "browser.statusChange": function onStatusChange(webProgress, request, status, message) {
163             this.timeout(function () {
164                 this.status = message || buffer.uri;
165             });
166         },
167         "fullscreen": function onFullscreen(fullscreen) {
168             let go = options.get("guioptions");
169             if (fullscreen) {
170                 this.wasVisible = go.has("s");
171                 go.op("-", "s");
172             }
173             else if (this.wasVisible) {
174                 go.op("+", "s");
175             }
176         }
177     },
178
179     /**
180      * Update the status bar to indicate how secure the website is:
181      * extended - Secure connection with Extended Validation(EV) certificate.
182      * secure -   Secure connection with valid certificate.
183      * broken -   Secure connection with invalid certificate, or
184      *            mixed content.
185      * insecure - Insecure connection.
186      *
187      * @param {'extended'|'secure'|'broken'|'insecure'} type
188      */
189     set security(type) {
190         this._security = type;
191         const highlightGroup = {
192             extended: "StatusLineExtended",
193             secure:   "StatusLineSecure",
194             broken:   "StatusLineBroken",
195             insecure: "StatusLineNormal"
196         };
197
198         highlight.highlightNode(this.statusBar, this.baseGroup + highlightGroup[type]);
199     },
200     get security() this._security,
201
202     // update all fields of the statusline
203     update: function update() {
204         this.updateStatus();
205         this.inputBuffer = "";
206         this.progress = "";
207         this.updateTabCount();
208         this.updateBufferPosition();
209         this.updateZoomLevel();
210     },
211
212     unsafeURI: deprecated("util.unsafeURI", { get: function unsafeURI() util.unsafeURI }),
213     losslessDecodeURI: deprecated("util.losslessDecodeURI", function losslessDecodeURI() util.losslessDecodeURI.apply(util, arguments)),
214
215     /**
216      * Update the URL displayed in the status line. Also displays status
217      * icons, [+-♥], when there are next and previous pages in the
218      * current tab's history, and when the current URL is bookmarked,
219      * respectively.
220      *
221      * @param {string} url The URL to display.
222      */
223     get status() this._uri,
224     set status(uri) {
225         let modified = "";
226         let url = uri;
227         if (isinstance(uri, Ci.nsIURI)) {
228             // when session information is available, add [+] when we can go
229             // backwards, [-] when we can go forwards
230             if (uri.equals(buffer.uri) && window.getWebNavigation) {
231                 let sh = window.getWebNavigation().sessionHistory;
232                 if (sh && sh.index > 0)
233                     modified += "-";
234                 if (sh && sh.index < sh.count - 1)
235                     modified += "+";
236                 if (this.bookmarked)
237                     modified += UTF8("❤");
238             }
239
240             if (modules.quickmarks)
241                 modified += quickmarks.find(uri.spec.replace(/#.*/, "")).join("");
242
243             url = util.losslessDecodeURI(uri.spec);
244         }
245
246         if (url == "about:blank") {
247             if (!buffer.title)
248                 url = _("buffer.noName");
249         }
250         else {
251             url = url.replace(RegExp("^dactyl://help/(\\S+)#(.*)"), (m, n1, n2) => n1 + " " + decodeURIComponent(n2) + " " + _("buffer.help"))
252                      .replace(RegExp("^dactyl://help/(\\S+)"), "$1 " + _("buffer.help"));
253         }
254
255         if (modified)
256             url += " [" + modified + "]";
257
258         this.widgets.url.value = url;
259         this._status = uri;
260     },
261
262     get bookmarked() this._bookmarked,
263     set bookmarked(val) {
264         this._bookmarked = val;
265         if (this.status)
266             this.status = this.status;
267     },
268
269     updateStatus: function updateStatus() {
270         this.timeout(function () {
271             this.status = this.overLink || buffer.uri;
272         });
273     },
274
275     updateUrl: deprecated("statusline.status", function updateUrl(url) { this.status = url || buffer.uri; }),
276
277     /**
278      * Set the contents of the status line's input buffer to the given
279      * string. Used primarily when a key press requires further input
280      * before being processed, including mapping counts and arguments,
281      * along with multi-key mappings.
282      *
283      * @param {string} buffer
284      * @optional
285      */
286     get inputBuffer() this.widgets.inputbuffer.value,
287     set inputBuffer(val) this.widgets.inputbuffer.value = val == null ? "" : val,
288     updateInputBuffer: deprecated("statusline.inputBuffer", function updateInputBuffer(val) { this.inputBuffer = val; }),
289
290     /**
291      * Update the page load progress bar.
292      *
293      * @param {string|number} progress The current progress, as follows:
294      *    A string          - Displayed literally.
295      *    A ratio 0 < n < 1 - Displayed as a progress bar.
296      *    A number n <= 0   - Displayed as a "Loading" message.
297      *    Any other number  - The progress is cleared.
298      */
299     progress: Modes.boundProperty({
300         get: function progress() this._progress,
301         set: function progress(progress) {
302             this._progress = progress || "";
303
304             if (isinstance(progress, ["String", _]))
305                 this.widgets.progress.value = this._progress;
306             else if (typeof progress == "number") {
307                 let progressStr = "";
308                 if (this._progress <= 0)
309                     progressStr = /*L*/"[ Loading...         ]";
310                 else if (this._progress < 1) {
311                     let progress = Math.round(this._progress * 20);
312                     progressStr = "["
313                         + "===================>                    "
314                             .substr(20 - progress, 20)
315                         + "]";
316                 }
317                 this.widgets.progress.value = progressStr;
318             }
319         }
320     }),
321     updateProgress: deprecated("statusline.progress", function updateProgress(progress) {
322         this.progress = progress;
323     }),
324
325     /**
326      * Display the correct tabcount (e.g., [1/5]) on the status bar.
327      *
328      * @param {boolean} delayed When true, update count after a brief timeout.
329      *     Useful in the many cases when an event that triggers an update is
330      *     broadcast before the tab state is fully updated.
331      * @optional
332      */
333     updateTabCount: function updateTabCount(delayed) {
334         if (dactyl.has("tabs")) {
335             if (delayed) {
336                 this.timeout(() => { this.updateTabCount(false); }, 0);
337                 return;
338             }
339
340             this.widgets.tabcount.value = "[" + (tabs.index(null, true) + 1) + "/" + tabs.visibleTabs.length + "]";
341         }
342     },
343
344     /**
345      * Display the main content's vertical scroll position in the status
346      * bar.
347      *
348      * @param {number} percent The position, as a percentage.
349      * @optional
350      */
351     updateBufferPosition: function updateBufferPosition(percent) {
352         if (percent == null) {
353             let win = document.commandDispatcher.focusedWindow;
354             if (!win)
355                 return;
356             win.scrollY; // intentional - see Kris
357             percent = win.scrollY    == 0 ?  0 : // This prevents a forced rendering
358                       win.scrollMaxY == 0 ? -1 : win.scrollY / win.scrollMaxY;
359         }
360
361         percent = Math.round(percent * 100);
362
363         if (percent < 0)
364             var position = "All";
365         else if (percent == 0)
366             position = "Top";
367         else if (percent >= 100)
368             position = "Bot";
369         else if (percent < 10)
370             position = " " + percent + "%";
371         else
372             position = percent + "%";
373
374         this.widgets.bufferposition.value = position;
375     },
376
377     /**
378      * Display the main content's zoom level.
379      *
380      * @param {number} percent The zoom level, as a percentage. @optional
381      * @param {boolean} full True if full zoom is in operation. @optional
382      */
383     updateZoomLevel: function updateZoomLevel(percent=buffer.zoomLevel, full=buffer.fullZoom) {
384         if (percent == 100)
385             this.widgets.zoomlevel.value = "";
386         else {
387             percent = ("  " + Math.round(percent)).substr(-3);
388             if (full)
389                 this.widgets.zoomlevel.value = " [" + percent + "%]";
390             else
391                 this.widgets.zoomlevel.value = " (" + percent + "%)";
392         }
393     }
394 });
395
396 // vim: set fdm=marker sw=4 sts=4 ts=8 et: