gecko/toolkit/content/widgets/scrollbox.xml

602 lines
21 KiB
XML

<?xml version="1.0"?>
<!DOCTYPE bindings [
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
]>
<bindings id="arrowscrollboxBindings"
xmlns="http://www.mozilla.org/xbl"
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
xmlns:xbl="http://www.mozilla.org/xbl">
<binding id="scrollbox-base" extends="chrome://global/content/bindings/general.xml#basecontrol">
<resources>
<stylesheet src="chrome://global/skin/scrollbox.css"/>
</resources>
</binding>
<binding id="scrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
<content>
<xul:box class="box-inherit scrollbox-innerbox" xbl:inherits="orient,align,pack,dir" flex="1">
<children/>
</xul:box>
</content>
</binding>
<binding id="arrowscrollbox" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
<content>
<xul:autorepeatbutton class="autorepeatbutton-up"
anonid="scrollbutton-up"
collapsed="true"
xbl:inherits="orient"
oncommand="_autorepeatbuttonScroll(event);"/>
<xul:scrollbox xbl:inherits="orient,align,pack,dir" flex="1" anonid="scrollbox">
<children/>
</xul:scrollbox>
<xul:autorepeatbutton class="autorepeatbutton-down"
anonid="scrollbutton-down"
collapsed="true"
xbl:inherits="orient"
oncommand="_autorepeatbuttonScroll(event);"/>
</content>
<implementation>
<field name="_scrollbox">
document.getAnonymousElementByAttribute(this, "anonid", "scrollbox");
</field>
<field name="_scrollButtonUp">
document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-up");
</field>
<field name="_scrollButtonDown">
document.getAnonymousElementByAttribute(this, "anonid", "scrollbutton-down");
</field>
<field name="__prefBranch">null</field>
<property name="_prefBranch" readonly="true">
<getter><![CDATA[
if (this.__prefBranch === null) {
this.__prefBranch = Components.classes['@mozilla.org/preferences-service;1']
.getService(Components.interfaces.nsIPrefBranch2);
}
return this.__prefBranch;
]]></getter>
</property>
<field name="_scrollIncrement">null</field>
<property name="scrollIncrement" readonly="true">
<getter><![CDATA[
if (this._scrollIncrement === null) {
try {
this._scrollIncrement = this._prefBranch
.getIntPref("toolkit.scrollbox.scrollIncrement");
}
catch (ex) {
this._scrollIncrement = 20;
}
}
return this._scrollIncrement;
]]></getter>
</property>
<field name="_smoothScroll">null</field>
<property name="smoothScroll">
<getter><![CDATA[
if (this._smoothScroll === null) {
if (this.hasAttribute("smoothscroll")) {
this._smoothScroll = (this.getAttribute("smoothscroll") == "true");
} else {
try {
this._smoothScroll = this._prefBranch
.getBoolPref("toolkit.scrollbox.smoothScroll");
}
catch (ex) {
this._smoothScroll = true;
}
}
}
return this._smoothScroll;
]]></getter>
<setter><![CDATA[
this._smoothScroll = val;
return val;
]]></setter>
</property>
<field name="_scrollBoxObject">null</field>
<property name="scrollBoxObject" readonly="true">
<getter><![CDATA[
if (!this._scrollBoxObject) {
this._scrollBoxObject =
this._scrollbox.boxObject
.QueryInterface(Components.interfaces.nsIScrollBoxObject);
}
return this._scrollBoxObject;
]]></getter>
</property>
<field name="_isLTRScrollbox">
document.defaultView.getComputedStyle(this._scrollbox, "").direction == "ltr";
</field>
<method name="ensureElementIsVisible">
<parameter name="element"/>
<body><![CDATA[
if (!this.smoothScroll || this.getAttribute("orient") == "vertical") {
this.scrollBoxObject.ensureElementIsVisible(element);
return;
}
var containerStart = this._scrollbox.boxObject.screenX;
var containerEnd = containerStart + this._scrollbox.boxObject.width;
var elementStart = element.boxObject.screenX;
var elementEnd = elementStart + element.boxObject.width;
var amountToScroll;
if (elementStart < containerStart) {
amountToScroll = elementStart - containerStart;
} else if (containerEnd < elementEnd) {
amountToScroll = elementEnd - containerEnd;
} else if (this._isScrolling) {
// decelerate if a currently-visible element is selected during the scroll
const STOP_DISTANCE = 15;
if (this._isScrolling == -1 && elementStart - STOP_DISTANCE < containerStart)
amountToScroll = elementStart - containerStart;
else if (this._isScrolling == 1 && containerEnd - STOP_DISTANCE < elementEnd)
amountToScroll = elementEnd - containerEnd;
else
amountToScroll = this._isScrolling * STOP_DISTANCE;
} else {
return;
}
this._stopSmoothScroll();
// Positive amountToScroll makes us scroll right (elements fly left), negative scrolls left.
var round;
if (amountToScroll < 0) {
this._isScrolling = -1;
round = Math.floor;
} else {
this._isScrolling = 1;
round = Math.ceil;
}
function processFrame(self, scrollAmounts) {
self.scrollBoxObject.scrollBy(scrollAmounts.shift(), 0);
if (!scrollAmounts.length)
self._stopSmoothScroll();
}
// amountToScroll: total distance to scroll
// scrollAmount: distance to move during the particular effect frame (60ms)
var scrollAmount, scrollAmounts = [];
if (amountToScroll > 2 || amountToScroll < -2) {
scrollAmount = round(amountToScroll * 0.2);
scrollAmounts.push (scrollAmount, scrollAmount, scrollAmount);
amountToScroll -= 3 * scrollAmount;
}
while (this._isScrolling < 0 && amountToScroll < 0 ||
this._isScrolling > 0 && amountToScroll > 0) {
amountToScroll -= (scrollAmount = round(amountToScroll * 0.5));
scrollAmounts.push(scrollAmount);
}
this._smoothScrollTimer = setInterval(processFrame, 60, this, scrollAmounts);
processFrame(this, scrollAmounts);
]]></body>
</method>
<method name="scrollByIndex">
<parameter name="index"/>
<body><![CDATA[
if (index == 0)
return;
if (this.getAttribute("orient") == "vertical") {
this.scrollBoxObject.scrollByIndex(index);
return;
}
var scrollBox = this.scrollBoxObject;
var edge = scrollBox.screenX;
if (index > 0)
edge += scrollBox.width;
else
edge--;
var nextElement = this._elementFromPoint(edge);
if (!nextElement)
return;
var targetElement;
if (!this._isLTRScrollbox)
index *= -1;
while (index < 0 && nextElement) {
targetElement = nextElement;
nextElement = nextElement.previousSibling;
index++;
}
while (index > 0 && nextElement) {
targetElement = nextElement;
nextElement = nextElement.nextSibling;
index--;
}
this.ensureElementIsVisible(targetElement);
]]></body>
</method>
<method name="_elementFromPoint">
<parameter name="aX"/>
<body><![CDATA[
var elements = this.hasChildNodes() ?
this.childNodes :
document.getBindingParent(this).childNodes;
if (!this._isLTRScrollbox) {
elements = Array.slice(elements);
elements.reverse();
}
var low = 0;
var high = elements.length - 1;
while (low <= high) {
var mid = Math.floor((low + high) / 2);
var element = elements[mid];
var bO = element.boxObject;
var x = bO.screenX;
if (x > aX)
high = mid - 1;
else if (x + bO.width < aX)
low = mid + 1;
else
return element;
}
return null;
]]></body>
</method>
<method name="_autorepeatbuttonScroll">
<parameter name="event"/>
<body><![CDATA[
var dir = event.originalTarget == this._scrollButtonUp ? -1 : 1;
if (this.getAttribute("orient") == "horizontal" && !this._isLTRScrollbox)
dir *= -1;
this.scrollByPixels(this.scrollIncrement * dir);
event.stopPropagation();
]]></body>
</method>
<method name="scrollByPixels">
<parameter name="px"/>
<body><![CDATA[
if (this.getAttribute("orient") == "horizontal")
this.scrollBoxObject.scrollBy(px, 0);
else
this.scrollBoxObject.scrollBy(0, px);
]]></body>
</method>
<!-- 0: idle
1: scrolling right
-1: scrolling left -->
<field name="_isScrolling">0</field>
<field name="_smoothScrollTimer">0</field>
<method name="_stopSmoothScroll">
<body><![CDATA[
clearInterval(this._smoothScrollTimer);
this._isScrolling = 0;
]]></body>
</method>
<method name="_updateScrollButtonsDisabledState">
<body><![CDATA[
var disableUpButton = false;
var disableDownButton = false;
if (this.getAttribute("orient") == "horizontal") {
var width = {};
this.scrollBoxObject.getScrolledSize(width, {});
var xPos = {};
this.scrollBoxObject.getPosition(xPos, {});
if (xPos.value == 0) {
// In the RTL case, this means the _last_ element in the
// scrollbox is visible
if (this._isLTRScrollbox)
disableUpButton = true;
else
disableDownButton = true;
}
else if (this._scrollbox.boxObject.width + xPos.value == width.value) {
// In the RTL case, this means the _first_ element in the
// scrollbox is visible
if (this._isLTRScrollbox)
disableDownButton = true;
else
disableUpButton = true;
}
}
else { // vertical scrollbox
var height = {};
this.scrollBoxObject.getScrolledSize({}, height);
var yPos = {};
this.scrollBoxObject.getPosition({}, yPos);
if (yPos.value == 0)
disableUpButton = true;
else if (this._scrollbox.boxObject.height + yPos.value == height.value)
disableDownButton = true;
}
if (this._scrollButtonUp.disabled != disableUpButton ||
this._scrollButtonDown.disabled != disableDownButton) {
this._scrollButtonUp.disabled = disableUpButton;
this._scrollButtonDown.disabled = disableDownButton;
var event = document.createEvent("Events");
event.initEvent("UpdatedScrollButtonsDisabledState", true, false);
this.dispatchEvent(event);
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="DOMMouseScroll" action="this.scrollByIndex(event.detail); event.stopPropagation();"/>
<handler event="underflow"><![CDATA[
// filter underflow events which were dispatched on nested scrollboxes
if (event.target != this)
return;
// Ignore events that doesn't match our orientation.
// Scrollport event orientation:
// 0: vertical
// 1: horizontal
// 2: both
if (this.getAttribute("orient") == "horizontal") {
if (event.detail == 0) {
return;
}
}
else { // vertical scrollbox
if (event.detail == 1) {
return;
}
}
this._scrollButtonUp.collapsed = true;
this._scrollButtonDown.collapsed = true;
try {
// See bug 341047 and comments in overflow handler as to why
// try..catch is needed here
var childNodes = document.getAnonymousNodes(this._scrollbox);
if (childNodes && childNodes.length)
this.scrollBoxObject.ensureElementIsVisible(childNodes[0]);
}
catch(e) {
this._scrollButtonUp.collapsed = false;
this._scrollButtonDown.collapsed = false;
}
]]></handler>
<handler event="overflow"><![CDATA[
// filter underflow events which were dispatched on nested scrollboxes
if (event.target != this)
return;
// Ignore events that doesn't match our orientation.
// Scrollport event orientation:
// 0: vertical
// 1: horizontal
// 2: both
if (this.getAttribute("orient") == "horizontal") {
if (event.detail == 0) {
return;
}
}
else { // vertical scrollbox
if (event.detail == 1) {
return;
}
}
this._scrollButtonUp.collapsed = false;
this._scrollButtonDown.collapsed = false;
try {
// See bug 341047, the overflow event is dispatched when the
// scrollbox already is mostly destroyed. This causes some code in
// _updateScrollButtonsDisabledState() to throw an error. It also
// means that the scrollbarbuttons were uncollapsed when that should
// not be happening, because the whole overflow event should not be
// happening in that case.
this._updateScrollButtonsDisabledState();
}
catch(e) {
this._scrollButtonUp.collapsed = true;
this._scrollButtonDown.collapsed = true;
}
]]></handler>
<handler event="scroll" action="this._updateScrollButtonsDisabledState()"/>
</handlers>
</binding>
<binding id="autorepeatbutton" extends="chrome://global/content/bindings/scrollbox.xml#scrollbox-base">
<content repeat="hover" chromedir="&locale.dir;">
<xul:image class="autorepeatbutton-icon"/>
</content>
</binding>
<binding id="arrowscrollbox-clicktoscroll" extends="chrome://global/content/bindings/scrollbox.xml#arrowscrollbox">
<content>
<xul:toolbarbutton class="scrollbutton-up" collapsed="true"
xbl:inherits="orient"
anonid="scrollbutton-up"
onclick="_distanceScroll(event);"
onmousedown="_startScroll(-1);"
onmouseover="_continueScroll(-1);"
onmouseup="_stopScroll();"
onmouseout="_pauseScroll();"
chromedir="&locale.dir;"/>
<xul:scrollbox xbl:inherits="orient,align,pack,dir" flex="1" anonid="scrollbox">
<children/>
</xul:scrollbox>
<xul:toolbarbutton class="scrollbutton-down" collapsed="true"
xbl:inherits="orient"
anonid="scrollbutton-down"
onclick="_distanceScroll(event);"
onmousedown="_startScroll(1);"
onmouseover="_continueScroll(1);"
onmouseup="_stopScroll();"
onmouseout="_pauseScroll();"
chromedir="&locale.dir;"/>
</content>
<implementation implements="nsITimerCallback, nsIDOMEventListener">
<constructor><![CDATA[
try {
this._scrollDelay = this._prefBranch
.getIntPref("toolkit.scrollbox.clickToScroll.scrollDelay");
}
catch (ex) {
}
]]></constructor>
<destructor><![CDATA[
// Release timer to avoid reference cycles.
if (this._scrollTimer) {
this._scrollTimer.cancel();
this._scrollTimer = null;
}
]]></destructor>
<field name="_scrollIndex">0</field>
<field name="_scrollDelay">150</field>
<method name="notify">
<parameter name="aTimer"/>
<body>
<![CDATA[
if (!document)
aTimer.cancel();
if (this.smoothScroll)
this.scrollByPixels(25 * this._scrollIndex);
else
this.scrollBoxObject.scrollByIndex(this._scrollIndex);
]]>
</body>
</method>
<method name="_startScroll">
<parameter name="index"/>
<body><![CDATA[
if (this.getAttribute("orient") == "horizontal" && !this._isLTRScrollbox)
index *= -1;
this._scrollIndex = index;
var scrollDelay = this.smoothScroll ? 60 : this._scrollDelay;
if (!this._scrollTimer)
this._scrollTimer =
Components.classes["@mozilla.org/timer;1"]
.createInstance(Components.interfaces.nsITimer);
else
this._scrollTimer.cancel();
this._scrollTimer.initWithCallback(this, scrollDelay,
this._scrollTimer.TYPE_REPEATING_SLACK);
this.notify(this._scrollTimer);
this._mousedown = true;
]]>
</body>
</method>
<method name="_stopScroll">
<body><![CDATA[
if (this._scrollTimer)
this._scrollTimer.cancel();
this._mousedown = false;
if (!this._scrollIndex || !this.smoothScroll)
return;
this.scrollByIndex(this._scrollIndex);
this._scrollIndex = 0;
]]></body>
</method>
<method name="_pauseScroll">
<body><![CDATA[
if (this._mousedown) {
this._stopScroll();
this._mousedown = true;
document.addEventListener("mouseup", this, false);
document.addEventListener("blur", this, true);
}
]]></body>
</method>
<method name="_continueScroll">
<parameter name="index"/>
<body><![CDATA[
if (this._mousedown)
this._startScroll(index);
]]></body>
</method>
<method name="handleEvent">
<parameter name="aEvent"/>
<body><![CDATA[
if (aEvent.type == "mouseup" ||
aEvent.type == "blur" && aEvent.target == document) {
this._mousedown = false;
document.removeEventListener("mouseup", this, false);
document.removeEventListener("blur", this, true);
}
]]></body>
</method>
<method name="_distanceScroll">
<parameter name="aEvent"/>
<body><![CDATA[
if (this.getAttribute("orient") == "vertical" ||
aEvent.detail < 2 || aEvent.detail > 3)
return;
var scrollLeft = (aEvent.originalTarget == this._scrollButtonUp);
if (!this._isLTRScrollbox)
scrollLeft = !scrollLeft;
var targetElement;
if (aEvent.detail == 2) {
// scroll by the width of the scrollbox; make sure that the next
// partly-hidden element will become fully visible.
var scrollBox = this.scrollBoxObject;
var edge = scrollBox.screenX;
if (scrollLeft)
edge -= scrollBox.width;
else
edge += scrollBox.width * 2;
targetElement = this._elementFromPoint(edge);
if (targetElement)
targetElement = scrollLeft ?
targetElement.nextSibling :
targetElement.previousSibling;
}
if (!targetElement) {
// scroll to the first resp. last element
var container = this.hasChildNodes() ? this : document.getBindingParent(this);
targetElement = (this._isLTRScrollbox ? scrollLeft : !scrollLeft) ?
container.firstChild :
container.lastChild;
}
this.ensureElementIsVisible(targetElement);
]]></body>
</method>
</implementation>
</binding>
</bindings>