1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/devtools/shared/widgets/BreadcrumbsWidget.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,258 @@ 1.4 +/* -*- Mode: javascript; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 1.5 +/* vim: set ft=javascript ts=2 et sw=2 tw=80: */ 1.6 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 +"use strict"; 1.10 + 1.11 +const Ci = Components.interfaces; 1.12 +const Cu = Components.utils; 1.13 + 1.14 +const ENSURE_SELECTION_VISIBLE_DELAY = 50; // ms 1.15 + 1.16 +Cu.import("resource:///modules/devtools/ViewHelpers.jsm"); 1.17 +Cu.import("resource://gre/modules/devtools/event-emitter.js"); 1.18 + 1.19 +this.EXPORTED_SYMBOLS = ["BreadcrumbsWidget"]; 1.20 + 1.21 +/** 1.22 + * A breadcrumb-like list of items. 1.23 + * 1.24 + * Note: this widget should be used in tandem with the WidgetMethods in 1.25 + * ViewHelpers.jsm. 1.26 + * 1.27 + * @param nsIDOMNode aNode 1.28 + * The element associated with the widget. 1.29 + */ 1.30 +this.BreadcrumbsWidget = function BreadcrumbsWidget(aNode) { 1.31 + this.document = aNode.ownerDocument; 1.32 + this.window = this.document.defaultView; 1.33 + this._parent = aNode; 1.34 + 1.35 + // Create an internal arrowscrollbox container. 1.36 + this._list = this.document.createElement("arrowscrollbox"); 1.37 + this._list.className = "breadcrumbs-widget-container"; 1.38 + this._list.setAttribute("flex", "1"); 1.39 + this._list.setAttribute("orient", "horizontal"); 1.40 + this._list.setAttribute("clicktoscroll", "true") 1.41 + this._list.addEventListener("keypress", e => this.emit("keyPress", e), false); 1.42 + this._list.addEventListener("mousedown", e => this.emit("mousePress", e), false); 1.43 + this._parent.appendChild(this._list); 1.44 + 1.45 + // By default, hide the arrows. We let the arrowscrollbox show them 1.46 + // in case of overflow. 1.47 + this._list._scrollButtonUp.collapsed = true; 1.48 + this._list._scrollButtonDown.collapsed = true; 1.49 + this._list.addEventListener("underflow", this._onUnderflow.bind(this), false); 1.50 + this._list.addEventListener("overflow", this._onOverflow.bind(this), false); 1.51 + 1.52 + 1.53 + // These separators are used for CSS purposes only, and are positioned 1.54 + // off screen, but displayed with -moz-element. 1.55 + this._separators = this.document.createElement("box"); 1.56 + this._separators.className = "breadcrumb-separator-container"; 1.57 + this._separators.innerHTML = 1.58 + "<box id='breadcrumb-separator-before'></box>" + 1.59 + "<box id='breadcrumb-separator-after'></box>" + 1.60 + "<box id='breadcrumb-separator-normal'></box>"; 1.61 + this._parent.appendChild(this._separators); 1.62 + 1.63 + // This widget emits events that can be handled in a MenuContainer. 1.64 + EventEmitter.decorate(this); 1.65 + 1.66 + // Delegate some of the associated node's methods to satisfy the interface 1.67 + // required by MenuContainer instances. 1.68 + ViewHelpers.delegateWidgetAttributeMethods(this, aNode); 1.69 + ViewHelpers.delegateWidgetEventMethods(this, aNode); 1.70 +}; 1.71 + 1.72 +BreadcrumbsWidget.prototype = { 1.73 + /** 1.74 + * Inserts an item in this container at the specified index. 1.75 + * 1.76 + * @param number aIndex 1.77 + * The position in the container intended for this item. 1.78 + * @param nsIDOMNode aContents 1.79 + * The node displayed in the container. 1.80 + * @return nsIDOMNode 1.81 + * The element associated with the displayed item. 1.82 + */ 1.83 + insertItemAt: function(aIndex, aContents) { 1.84 + let list = this._list; 1.85 + let breadcrumb = new Breadcrumb(this, aContents); 1.86 + return list.insertBefore(breadcrumb._target, list.childNodes[aIndex]); 1.87 + }, 1.88 + 1.89 + /** 1.90 + * Returns the child node in this container situated at the specified index. 1.91 + * 1.92 + * @param number aIndex 1.93 + * The position in the container intended for this item. 1.94 + * @return nsIDOMNode 1.95 + * The element associated with the displayed item. 1.96 + */ 1.97 + getItemAtIndex: function(aIndex) { 1.98 + return this._list.childNodes[aIndex]; 1.99 + }, 1.100 + 1.101 + /** 1.102 + * Removes the specified child node from this container. 1.103 + * 1.104 + * @param nsIDOMNode aChild 1.105 + * The element associated with the displayed item. 1.106 + */ 1.107 + removeChild: function(aChild) { 1.108 + this._list.removeChild(aChild); 1.109 + 1.110 + if (this._selectedItem == aChild) { 1.111 + this._selectedItem = null; 1.112 + } 1.113 + }, 1.114 + 1.115 + /** 1.116 + * Removes all of the child nodes from this container. 1.117 + */ 1.118 + removeAllItems: function() { 1.119 + let list = this._list; 1.120 + 1.121 + while (list.hasChildNodes()) { 1.122 + list.firstChild.remove(); 1.123 + } 1.124 + 1.125 + this._selectedItem = null; 1.126 + }, 1.127 + 1.128 + /** 1.129 + * Gets the currently selected child node in this container. 1.130 + * @return nsIDOMNode 1.131 + */ 1.132 + get selectedItem() { 1.133 + return this._selectedItem; 1.134 + }, 1.135 + 1.136 + /** 1.137 + * Sets the currently selected child node in this container. 1.138 + * @param nsIDOMNode aChild 1.139 + */ 1.140 + set selectedItem(aChild) { 1.141 + let childNodes = this._list.childNodes; 1.142 + 1.143 + if (!aChild) { 1.144 + this._selectedItem = null; 1.145 + } 1.146 + for (let node of childNodes) { 1.147 + if (node == aChild) { 1.148 + node.setAttribute("checked", ""); 1.149 + this._selectedItem = node; 1.150 + } else { 1.151 + node.removeAttribute("checked"); 1.152 + } 1.153 + } 1.154 + }, 1.155 + 1.156 + /** 1.157 + * Returns the value of the named attribute on this container. 1.158 + * 1.159 + * @param string aName 1.160 + * The name of the attribute. 1.161 + * @return string 1.162 + * The current attribute value. 1.163 + */ 1.164 + getAttribute: function(aName) { 1.165 + if (aName == "scrollPosition") return this._list.scrollPosition; 1.166 + if (aName == "scrollWidth") return this._list.scrollWidth; 1.167 + return this._parent.getAttribute(aName); 1.168 + }, 1.169 + 1.170 + /** 1.171 + * Ensures the specified element is visible. 1.172 + * 1.173 + * @param nsIDOMNode aElement 1.174 + * The element to make visible. 1.175 + */ 1.176 + ensureElementIsVisible: function(aElement) { 1.177 + if (!aElement) { 1.178 + return; 1.179 + } 1.180 + 1.181 + // Repeated calls to ensureElementIsVisible would interfere with each other 1.182 + // and may sometimes result in incorrect scroll positions. 1.183 + setNamedTimeout("breadcrumb-select", ENSURE_SELECTION_VISIBLE_DELAY, () => { 1.184 + if (this._list.ensureElementIsVisible) { 1.185 + this._list.ensureElementIsVisible(aElement); 1.186 + } 1.187 + }); 1.188 + }, 1.189 + 1.190 + /** 1.191 + * The underflow and overflow listener for the arrowscrollbox container. 1.192 + */ 1.193 + _onUnderflow: function({ target }) { 1.194 + if (target != this._list) { 1.195 + return; 1.196 + } 1.197 + target._scrollButtonUp.collapsed = true; 1.198 + target._scrollButtonDown.collapsed = true; 1.199 + target.removeAttribute("overflows"); 1.200 + }, 1.201 + 1.202 + /** 1.203 + * The underflow and overflow listener for the arrowscrollbox container. 1.204 + */ 1.205 + _onOverflow: function({ target }) { 1.206 + if (target != this._list) { 1.207 + return; 1.208 + } 1.209 + target._scrollButtonUp.collapsed = false; 1.210 + target._scrollButtonDown.collapsed = false; 1.211 + target.setAttribute("overflows", ""); 1.212 + }, 1.213 + 1.214 + window: null, 1.215 + document: null, 1.216 + _parent: null, 1.217 + _list: null, 1.218 + _selectedItem: null 1.219 +}; 1.220 + 1.221 +/** 1.222 + * A Breadcrumb constructor for the BreadcrumbsWidget. 1.223 + * 1.224 + * @param BreadcrumbsWidget aWidget 1.225 + * The widget to contain this breadcrumb. 1.226 + * @param nsIDOMNode aContents 1.227 + * The node displayed in the container. 1.228 + */ 1.229 +function Breadcrumb(aWidget, aContents) { 1.230 + this.document = aWidget.document; 1.231 + this.window = aWidget.window; 1.232 + this.ownerView = aWidget; 1.233 + 1.234 + this._target = this.document.createElement("hbox"); 1.235 + this._target.className = "breadcrumbs-widget-item"; 1.236 + this._target.setAttribute("align", "center"); 1.237 + this.contents = aContents; 1.238 +} 1.239 + 1.240 +Breadcrumb.prototype = { 1.241 + /** 1.242 + * Sets the contents displayed in this item's view. 1.243 + * 1.244 + * @param string | nsIDOMNode aContents 1.245 + * The string or node displayed in the container. 1.246 + */ 1.247 + set contents(aContents) { 1.248 + // If there are already some contents displayed, replace them. 1.249 + if (this._target.hasChildNodes()) { 1.250 + this._target.replaceChild(aContents, this._target.firstChild); 1.251 + return; 1.252 + } 1.253 + // These are the first contents ever displayed. 1.254 + this._target.appendChild(aContents); 1.255 + }, 1.256 + 1.257 + window: null, 1.258 + document: null, 1.259 + ownerView: null, 1.260 + _target: null 1.261 +};