1 <?xml version="1.0"?>
2
3 <!DOCTYPE bindings [
4 <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
5 %globalDTD;
6 ]>
7
8 <bindings id="arrowscrollboxBindings"
9 xmlns="http://www.mozilla.org/xbl"
10 xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
11 xmlns:xbl="http://www.mozilla.org/xbl">
12
13 <binding id="scrollbox-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
14 <resources>
15 <stylesheet src="chrome://global/skin/scrollbox.css"/>
16 </resources>
17 </binding>
18
19 <binding id="scrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
20 <content>
21 <xul:box class="box-inherit scrollbox-innerbox" xbl:inherits="orient,align,pack,dir" flex="1">
22 <children/>
23 </xul:box>
24 </content>
25 </binding>
26
27 <binding id="arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
28 <content>
29 <xul:autorepeatbutton class="autorepeatbutton-up"
30 anonid="scrollbutton-up"
31 collapsed="true"
32 xbl:inherits="orient"
33 oncommand="_autorepeatbuttonScroll(event);"/>
34 <xul:scrollbox xbl:inherits="orient,align,pack,dir" flex="1" anonid="scrollbox">
35 <children/>
36 </xul:scrollbox>
37 <xul:autorepeatbutton class="autorepeatbutton-down"
38 anonid="scrollbutton-down"
39 collapsed="true"
40 xbl:inherits="orient"
41 oncommand="_autorepeatbuttonScroll(event);"/>
42 </content>
43
44 <implementation>
45 <field name="_scrollbox">
46 document.getAnonymousElementByAttribute(this, "anonid", "scrollbox");
47 </field>
48 <field name="_scrollButtonUp">
49 document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-up");
50 </field>
51 <field name="_scrollButtonDown">
52 document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-down");
53 </field>
54
55 <field name="__prefBranch">null</field>
56 <property name="_prefBranch" readonly="true">
57 <getter><![CDATA[
58 if (this.__prefBranch === null) {
59 this.__prefBranch = Components.classes['@mozilla.org/preferences-service;1']
60 .getService(Components.interfaces.nsIPrefBranch2);
61 }
62 return this.__prefBranch;
63 ]]></getter>
64 </property>
65
66 <field name="_scrollIncrement">null</field>
67 <property name="scrollIncrement" readonly="true">
68 <getter><![CDATA[
69 if (this._scrollIncrement === null) {
70 try {
71 this._scrollIncrement = this._prefBranch
72 .getIntPref("toolkit.scrollbox.scrollIncrement");
73 }
74 catch (ex) {
75 this._scrollIncrement = 20;
76 }
77 }
78 return this._scrollIncrement;
79 ]]></getter>
80 </property>
81
82 <field name="_smoothScroll">null</field>
83 <property name="smoothScroll">
84 <getter><![CDATA[
85 if (this._smoothScroll === null) {
86 if (this.hasAttribute("smoothscroll")) {
87 this._smoothScroll = (this.getAttribute("smoothscroll") == "true");
88 } else {
89 try {
90 this._smoothScroll = this._prefBranch
91 .getBoolPref("toolkit.scrollbox.smoothScroll");
92 }
93 catch (ex) {
94 this._smoothScroll = true;
95 }
96 }
97 }
98 return this._smoothScroll;
99 ]]></getter>
100 <setter><![CDATA[
101 this._smoothScroll = val;
102 return val;
103 ]]></setter>
104 </property>
105
106 <field name="_scrollBoxObject">null</field>
107 <property name="scrollBoxObject" readonly="true">
108 <getter><![CDATA[
109 if (!this._scrollBoxObject) {
110 this._scrollBoxObject =
111 this._scrollbox.boxObject
112 .QueryInterface(Components.interfaces.nsIScrollBoxObject);
113 }
114 return this._scrollBoxObject;
115 ]]></getter>
116 </property>
117
118 <field name="_isLTRScrollbox">
119 document.defaultView.getComputedStyle(this._scrollbox, "").direction == "ltr";
120 </field>
121
122 <method name="ensureElementIsVisible">
123 <parameter name="element"/>
124 <body><![CDATA[
125 if (!this.smoothScroll || this.getAttribute("orient") == "vertical") {
126 this.scrollBoxObject.ensureElementIsVisible(element);
127 return;
128 }
129
130 var containerStart = this._scrollbox.boxObject.screenX;
131 var containerEnd = containerStart + this._scrollbox.boxObject.width;
132 var elementStart = element.boxObject.screenX;
133 var elementEnd = elementStart + element.boxObject.width;
134 var amountToScroll;
135
136 if (elementStart < containerStart) {
137 amountToScroll = elementStart - containerStart;
138 } else if (containerEnd < elementEnd) {
139 amountToScroll = elementEnd - containerEnd;
140 } else if (this._isScrolling) {
141 // decelerate if a currently-visible element is selected during the scroll
142 const STOP_DISTANCE = 15;
143 if (this._isScrolling == -1 && elementStart - STOP_DISTANCE < containerStart)
144 amountToScroll = elementStart - containerStart;
145 else if (this._isScrolling == 1 && containerEnd - STOP_DISTANCE < elementEnd)
146 amountToScroll = elementEnd - containerEnd;
147 else
148 amountToScroll = this._isScrolling * STOP_DISTANCE;
149 } else {
150 return;
151 }
152
153 this._smoothScrollByPixels(amountToScroll);
154 ]]></body>
155 </method>
156
157 <method name="_smoothScrollByPixels">
158 <parameter name="amountToScroll"/>
159 <body><![CDATA[
160 this._stopSmoothScroll();
161
162 // Positive amountToScroll makes us scroll right (elements fly left), negative scrolls left.
163 var round;
164 if (amountToScroll < 0) {
165 this._isScrolling = -1;
166 round = Math.floor;
167 } else {
168 this._isScrolling = 1;
169 round = Math.ceil;
170 }
171
172 function processFrame(self, scrollAmounts) {
173 self.scrollBoxObject.scrollBy(scrollAmounts.shift(), 0);
174 if (!scrollAmounts.length)
175 self._stopSmoothScroll();
176 }
177
178 // amountToScroll: total distance to scroll
179 // scrollAmount: distance to move during the particular effect frame (60ms)
180 var scrollAmount, scrollAmounts = [];
181 if (amountToScroll > 2 || amountToScroll < -2) {
182 scrollAmount = round(amountToScroll * 0.2);
183 scrollAmounts.push (scrollAmount, scrollAmount, scrollAmount);
184 amountToScroll -= 3 * scrollAmount;
185 }
186 while (this._isScrolling < 0 && amountToScroll < 0 ||
187 this._isScrolling > 0 && amountToScroll > 0) {
188 amountToScroll -= (scrollAmount = round(amountToScroll * 0.5));
189 scrollAmounts.push(scrollAmount);
190 }
191 this._smoothScrollTimer = setInterval(processFrame, 60, this, scrollAmounts);
192 processFrame(this, scrollAmounts);
193 ]]></body>
194 </method>
195
196 <method name="scrollByIndex">
197 <parameter name="index"/>
198 <body><![CDATA[
199 if (index == 0)
200 return;
201 if (this.getAttribute("orient") == "vertical") {
202 this.scrollBoxObject.scrollByIndex(index);
203 return;
204 }
205
206 var scrollBox = this.scrollBoxObject;
207 var edge = scrollBox.screenX;
208 if (index > 0)
209 edge += scrollBox.width;
210 else
211 edge--;
212 var nextElement = this._elementFromPoint(edge);
213 if (!nextElement)
214 return;
215
216 var targetElement;
217 if (!this._isLTRScrollbox)
218 index *= -1;
219
220 while (index < 0 && nextElement) {
221 targetElement = nextElement;
222 nextElement = nextElement.previousSibling;
223 index++;
224 }
225 while (index > 0 && nextElement) {
226 targetElement = nextElement;
227 nextElement = nextElement.nextSibling;
228 index--;
229 }
230
231 this.ensureElementIsVisible(targetElement);
232 ]]></body>
233 </method>
234
235 <method name="_elementFromPoint">
236 <parameter name="aX"/>
237 <body><![CDATA[
238 var elements = this.hasChildNodes() ?
239 this.childNodes :
240 document.getBindingParent(this).childNodes;
241 if (!this._isLTRScrollbox) {
242 elements = Array.slice(elements);
243 elements.reverse();
244 }
245 var low = 0;
246 var high = elements.length - 1;
247
248 while (low <= high) {
249 var mid = Math.floor((low + high) / 2);
250 var element = elements[mid];
251 var bO = element.boxObject;
252 var x = bO.screenX;
253 if (x > aX)
254 high = mid - 1;
255 else if (x + bO.width < aX)
256 low = mid + 1;
257 else
258 return element;
259 }
260
261 return null;
262 ]]></body>
263 </method>
264
265 <method name="_autorepeatbuttonScroll">
266 <parameter name="event"/>
267 <body><![CDATA[
268 var dir = event.originalTarget == this._scrollButtonUp ? -1 : 1;
269 if (this.getAttribute("orient") == "horizontal" && !this._isLTRScrollbox)
270 dir *= -1;
271
272 this.scrollByPixels(this.scrollIncrement * dir);
273
274 event.stopPropagation();
275 ]]></body>
276 </method>
277
278 <method name="scrollByPixels">
279 <parameter name="px"/>
280 <body><![CDATA[
281 if (this.getAttribute("orient") == "horizontal")
282 this.scrollBoxObject.scrollBy(px, 0);
283 else
284 this.scrollBoxObject.scrollBy(0, px);
285 ]]></body>
286 </method>
287
288 <!-- 0: idle
289 1: scrolling right
290 -1: scrolling left -->
291 <field name="_isScrolling">0</field>
292 <field name="_smoothScrollTimer">0</field>
293
294 <method name="_stopSmoothScroll">
295 <body><![CDATA[
296 clearInterval(this._smoothScrollTimer);
297 this._isScrolling = 0;
298 ]]></body>
299 </method>
300
301 <method name="_updateScrollButtonsDisabledState">
302 <body><![CDATA[
303 var disableUpButton = false;
304 var disableDownButton = false;
305
306 if (this.getAttribute("orient") == "horizontal") {
307 var width = {};
308 this.scrollBoxObject.getScrolledSize(width, {});
309 var xPos = {};
310 this.scrollBoxObject.getPosition(xPos, {});
311 if (xPos.value == 0) {
312 // In the RTL case, this means the _last_ element in the
313 // scrollbox is visible
314 if (this._isLTRScrollbox)
315 disableUpButton = true;
316 else
317 disableDownButton = true;
318 }
319 else if (this._scrollbox.boxObject.width + xPos.value == width.value) {
320 // In the RTL case, this means the _first_ element in the
321 // scrollbox is visible
322 if (this._isLTRScrollbox)
323 disableDownButton = true;
324 else
325 disableUpButton = true;
326 }
327 }
328 else { // vertical scrollbox
329 var height = {};
330 this.scrollBoxObject.getScrolledSize({}, height);
331 var yPos = {};
332 this.scrollBoxObject.getPosition({}, yPos);
333 if (yPos.value == 0)
334 disableUpButton = true;
335 else if (this._scrollbox.boxObject.height + yPos.value == height.value)
336 disableDownButton = true;
337 }
338
339 if (this._scrollButtonUp.disabled != disableUpButton ||
340 this._scrollButtonDown.disabled != disableDownButton) {
341 this._scrollButtonUp.disabled = disableUpButton;
342 this._scrollButtonDown.disabled = disableDownButton;
343
344 var event = document.createEvent("Events");
345 event.initEvent("UpdatedScrollButtonsDisabledState", true, false);
346 this.dispatchEvent(event);
347 }
348 ]]></body>
349 </method>
350 </implementation>
351
352 <handlers>
353 <handler event="DOMMouseScroll" action="this.scrollByIndex(event.detail); event.stopPropagation();"/>
354
355 <handler event="underflow"><![CDATA[
356 // filter underflow events which were dispatched on nested scrollboxes
357 if (event.target != this)
358 return;
359
360 // Ignore events that doesn't match our orientation.
361 // Scrollport event orientation:
362 // 0: vertical
363 // 1: horizontal
364 // 2: both
365 if (this.getAttribute("orient") == "horizontal") {
366 if (event.detail == 0) {
367 return;
368 }
369 }
370 else { // vertical scrollbox
371 if (event.detail == 1) {
372 return;
373 }
374 }
375
376 this._scrollButtonUp.collapsed = true;
377 this._scrollButtonDown.collapsed = true;
378 try {
379 // See bug 341047 and comments in overflow handler as to why
380 // try..catch is needed here
381 var childNodes = document.getAnonymousNodes(this._scrollbox);
382 if (childNodes && childNodes.length)
383 this.scrollBoxObject.ensureElementIsVisible(childNodes[0]);
384 }
385 catch(e) {
386 this._scrollButtonUp.collapsed = false;
387 this._scrollButtonDown.collapsed = false;
388 }
389 ]]></handler>
390
391 <handler event="overflow"><![CDATA[
392 // filter underflow events which were dispatched on nested scrollboxes
393 if (event.target != this)
394 return;
395
396 // Ignore events that doesn't match our orientation.
397 // Scrollport event orientation:
398 // 0: vertical
399 // 1: horizontal
400 // 2: both
401 if (this.getAttribute("orient") == "horizontal") {
402 if (event.detail == 0) {
403 return;
404 }
405 }
406 else { // vertical scrollbox
407 if (event.detail == 1) {
408 return;
409 }
410 }
411
412 this._scrollButtonUp.collapsed = false;
413 this._scrollButtonDown.collapsed = false;
414 try {
415 // See bug 341047, the overflow event is dispatched when the
416 // scrollbox already is mostly destroyed. This causes some code in
417 // _updateScrollButtonsDisabledState() to throw an error. It also
418 // means that the scrollbarbuttons were uncollapsed when that should
419 // not be happening, because the whole overflow event should not be
420 // happening in that case.
421 this._updateScrollButtonsDisabledState();
422 }
423 catch(e) {
424 this._scrollButtonUp.collapsed = true;
425 this._scrollButtonDown.collapsed = true;
426 }
427 ]]></handler>
428
429 <handler event="scroll" action="this._updateScrollButtonsDisabledState()"/>
430 </handlers>
431 </binding>
432
433 <binding id="autorepeatbutton" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
434 <content repeat="hover" chromedir="&locale.dir;">
435 <xul:image class="autorepeatbutton-icon"/>
436 </content>
437 </binding>
438
439 <binding id="arrowscrollbox-clicktoscroll" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox">
440 <content>
441 <xul:toolbarbutton class="scrollbutton-up" collapsed="true"
442 xbl:inherits="orient"
443 anonid="scrollbutton-up"
444 onclick="_distanceScroll(event);"
445 onmousedown="_startScroll(-1);"
446 onmouseover="_continueScroll(-1);"
447 onmouseup="_stopScroll();"
448 onmouseout="_pauseScroll();"
449 chromedir="&locale.dir;"/>
450 <xul:scrollbox xbl:inherits="orient,align,pack,dir" flex="1" anonid="scrollbox">
451 <children/>
452 </xul:scrollbox>
453 <xul:toolbarbutton class="scrollbutton-down" collapsed="true"
454 xbl:inherits="orient"
455 anonid="scrollbutton-down"
456 onclick="_distanceScroll(event);"
457 onmousedown="_startScroll(1);"
458 onmouseover="_continueScroll(1);"
459 onmouseup="_stopScroll();"
460 onmouseout="_pauseScroll();"
461 chromedir="&locale.dir;"/>
462 </content>
463 <implementation implements="nsITimerCallback, nsIDOMEventListener">
464 <constructor><![CDATA[
465 try {
466 this._scrollDelay = this._prefBranch
467 .getIntPref("toolkit.scrollbox.clickToScroll.scrollDelay");
468 }
469 catch (ex) {
470 }
471 ]]></constructor>
472
473 <destructor><![CDATA[
474 // Release timer to avoid reference cycles.
475 if (this._scrollTimer) {
476 this._scrollTimer.cancel();
477 this._scrollTimer = null;
478 }
479 ]]></destructor>
480
481 <field name="_scrollIndex">0</field>
482 <field name="_scrollDelay">150</field>
483
484 <method name="notify">
485 <parameter name="aTimer"/>
486 <body>
487 <![CDATA[
488 if (!document)
489 aTimer.cancel();
490
491 if (this.smoothScroll)
492 this.scrollByPixels(25 * this._scrollIndex);
493 else
494 this.scrollBoxObject.scrollByIndex(this._scrollIndex);
495 ]]>
496 </body>
497 </method>
498
499 <method name="_startScroll">
500 <parameter name="index"/>
501 <body><![CDATA[
502 if (this.getAttribute("orient") == "horizontal" && !this._isLTRScrollbox)
503 index *= -1;
504 this._scrollIndex = index;
505 var scrollDelay = this.smoothScroll ? 60 : this._scrollDelay;
506 if (!this._scrollTimer)
507 this._scrollTimer =
508 Components.classes["@mozilla.org/timer;1"]
509 .createInstance(Components.interfaces.nsITimer);
510 else
511 this._scrollTimer.cancel();
512
513 this._scrollTimer.initWithCallback(this, scrollDelay,
514 this._scrollTimer.TYPE_REPEATING_SLACK);
515 this.notify(this._scrollTimer);
516 this._mousedown = true;
517 ]]>
518 </body>
519 </method>
520
521 <method name="_stopScroll">
522 <body><![CDATA[
523 if (this._scrollTimer)
524 this._scrollTimer.cancel();
525 this._mousedown = false;
526 if (!this._scrollIndex || !this.smoothScroll)
527 return;
528
529 this.scrollByIndex(this._scrollIndex);
530 this._scrollIndex = 0;
531 ]]></body>
532 </method>
533
534 <method name="_pauseScroll">
535 <body><![CDATA[
536 if (this._mousedown) {
537 this._stopScroll();
538 this._mousedown = true;
539 document.addEventListener("mouseup", this, false);
540 document.addEventListener("blur", this, true);
541 }
542 ]]></body>
543 </method>
544
545 <method name="_continueScroll">
546 <parameter name="index"/>
547 <body><![CDATA[
548 if (this._mousedown)
549 this._startScroll(index);
550 ]]></body>
551 </method>
552
553 <method name="handleEvent">
554 <parameter name="aEvent"/>
555 <body><![CDATA[
556 if (aEvent.type == "mouseup" ||
557 aEvent.type == "blur" && aEvent.target == document) {
558 this._mousedown = false;
559 document.removeEventListener("mouseup", this, false);
560 document.removeEventListener("blur", this, true);
561 }
562 ]]></body>
563 </method>
564
565 <method name="_distanceScroll">
566 <parameter name="aEvent"/>
567 <body><![CDATA[
568 if (this.getAttribute("orient") == "vertical" ||
569 aEvent.detail < 2 || aEvent.detail > 3)
570 return;
571
572 var scrollLeft = (aEvent.originalTarget == this._scrollButtonUp);
573 if (!this._isLTRScrollbox)
574 scrollLeft = !scrollLeft;
575 var targetElement;
576
577 if (aEvent.detail == 2) {
578 // scroll by the width of the scrollbox; make sure that the next
579 // partly-hidden element will become fully visible.
580 var scrollBox = this.scrollBoxObject;
581 var edge = scrollBox.screenX;
582 if (scrollLeft)
583 edge -= scrollBox.width;
584 else
585 edge += scrollBox.width * 2;
586 targetElement = this._elementFromPoint(edge);
587
588 if (targetElement)
589 targetElement = scrollLeft ?
590 targetElement.nextSibling :
591 targetElement.previousSibling;
592 }
593
594 if (!targetElement) {
595 // scroll to the first resp. last element
596 var container = this.hasChildNodes() ? this : document.getBindingParent(this);
597 targetElement = (this._isLTRScrollbox ? scrollLeft : !scrollLeft) ?
598 container.firstChild :
599 container.lastChild;
600 }
601
602 this.ensureElementIsVisible(targetElement);
603 ]]></body>
604 </method>
605
606 </implementation>
607 </binding>
608 </bindings>