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