allan%beaufour.dk 73014eb021 [XForms] range undefined @step should be "1" for xsd:integer. Bug 332221, r=doronr+smaug
git-svn-id: svn://10.0.0.236/trunk@198624 18797224-902f-48f8-a5cc-f745e15eee43
2006-05-30 09:30:31 +00:00

512 lines
17 KiB
XML

<?xml version="1.0" encoding="utf-8"?>
<!-- ***** BEGIN LICENSE BLOCK *****
- Version: MPL 1.1/GPL 2.0/LGPL 2.1
-
- The contents of this file are subject to the Mozilla Public License Version
- 1.1 (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
- http://www.mozilla.org/MPL/
-
- Software distributed under the License is distributed on an "AS IS" basis,
- WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
- for the specific language governing rights and limitations under the
- License.
-
- The Original Code is Mozilla XForms support.
-
- The Initial Developer of the Original Code is
- Novell, Inc.
- Portions created by the Initial Developer are Copyright (C) 2005
- the Initial Developer. All Rights Reserved.
-
- Contributor(s):
- Allan Beaufour <abeaufour@novell.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
- the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
- in which case the provisions of the GPL or the LGPL are applicable instead
- of those above. If you wish to allow use of your version of this file only
- under the terms of either the GPL or the LGPL, and not to allow others to
- use your version of this file under the terms of the MPL, indicate your
- decision by deleting the provisions above and replace them with the notice
- and other provisions required by the GPL or the LGPL. If you do not delete
- the provisions above, a recipient may use your version of this file under
- the terms of any one of the MPL, the GPL or the LGPL.
-
- ***** END LICENSE BLOCK ***** -->
<!--
ASSUMPTIONS:
*> @begin is valid, @end and @init value might not be
this means that steps and ticks are calculated with begin as starting point
*> Takes integers and floats
TODO: XXX
*> limit amount of ticks
*> handle undefined begin / end
*> handle end < begin (including negative steps)
*> @incremental should round if it is bound to integer
BUGS: XXX
*> leaves a trace behind, hor.bar gets darker, etc... fix transparency
-->
<bindings xmlns="http://www.mozilla.org/xbl"
xmlns:html="http://www.w3.org/1999/xhtml">
<binding id="xformswidget-range"
extends="chrome://xforms/content/xforms.xml#xformswidget-base">
<content>
<children includes="label"/>
<html:span anonid="labelBegin" style="margin-right: 3px;"></html:span>
<!-- width and height set by CSS? -->
<html:canvas tabindex="0" anonid="canvas" width="260" height="40"
class="xf-value"
onkeydown="this.parentNode.handleKey(event)"
onmousedown="this.parentNode.handleMouseDown(event)"
onmouseup="this.parentNode.handleMouseUp(event)"
onmouseout="this.parentNode.handleMouseOut(event)"
onmousemove="this.parentNode.handleMouseMove(event)">
</html:canvas>
<html:span anonid="labelEnd" style="margin-left: 3px;"> </html:span>
<children/>
</content>
<implementation implements="nsIXFormsUIWidget">
<!-- The "skin", should be set via CSS -->
<field name="strokeStyle" readonly="true">"#8f9ca4"</field>
<field name="strokeStyleMove" readonly="true">"red"</field>
<field name="fillStyle" readonly="true">"#eff3f1"</field>
<!-- Is the range initialized -->
<field name="isInitialized">false</field>
<!-- out of range -->
<field name="outOfRange">false</field>
<!-- are we currently moving the slider? -->
<field name="isMoving">false</field>
<!-- creates the sliderpath -->
<method name="sliderPath">
<parameter name="aPos"/>
<body>
this.ctx.beginPath();
var h = this.height - this.tickheight;
this.ctx.moveTo(aPos, h);
this.ctx.lineTo(aPos - this.sliderwidth, h - this.slidertip);
this.ctx.lineTo(aPos - this.sliderwidth, this.tickheight);
this.ctx.lineTo(aPos + this.sliderwidth, this.tickheight);
this.ctx.lineTo(aPos + this.sliderwidth, h - this.slidertip);
this.ctx.closePath();
</body>
</method>
<!-- creates the sliderpath -->
<method name="drawSlider">
<parameter name="aPos"/>
<parameter name="aMove"/>
<body>
this.ctx.save();
// do path
this.ctx.lineWidth = 1;
this.sliderPath(aPos);
this.ctx.strokeStyle = aMove ? this.strokeStyleMove : this.strokeStyle;
this.ctx.stroke();
this.sliderPath(aPos);
this.ctx.fillStyle = this.fillStyle;
this.ctx.fill();
this.ctx.restore();
</body>
</method>
<!-- takes a value and calculates the x position -->
<method name="calcPos">
<parameter name="val"/>
<body>
var pos = val - this.rBegin;
if (this.rStep) {
pos = (pos / this.rStep) * this.stepsp;
} else {
pos = (pos / (this.rEnd - this.rBegin)) * this.barwidth;
}
return Math.round(pos) + this.margin;
</body>
</method>
<!-- sets the slider to a new value -->
<method name="setSlider">
<!-- The new value -->
<parameter name="aVal"/>
<!-- The mode:
- move: just moving the slider around, not setting the value
- set: enforce slider position from instance data,
ie. do not correct it to fit a step, etc.
- [default]: set the slider to the given value, adjusting it
to fit inside the allowed range
-->
<parameter name="aMode"/>
<body>
<![CDATA[
aVal = parseFloat(aVal);
if (aMode != "set" && isNaN(aVal)) {
return this.delegate.reportError("rangeSetSliderNaN");
}
if (this.isIncremental && aMode == "move") {
// Incremental moves are actually sets.
aMode = "set";
}
var outOfRange = false;
if (aMode != "move") {
if (aMode == "set" &&
(isNaN(aVal) || aVal > this.adjEnd || aVal < this.rBegin)) {
outOfRange = true;
} else {
if (this.rStep) {
// adjust aVal to limits
valmod = (aVal - this.rBegin) % this.rStep;
if (valmod) {
if (aMode == "set") {
outOfRange = true;
} else if (valmod < (this.rStep / 2)) {
aVal -= valmod;
} else {
aVal += this.rStep - valmod;
}
}
}
if (aVal > this.adjEnd) {
aVal = this.adjEnd;
} else if (aVal < this.rBegin) {
aVal = this.rBegin;
}
}
}
if (!outOfRange && (aMode != "move" || this.isIncremental)) {
// Store new value
this.accessors.setValue(aVal);
}
if (this.rVal == aVal && !this.justMoved) {
// slider is already drawn at correct position
return;
}
this.ctx.save();
// clear old slider
this.ctx.clearRect(this.calcPos(this.rVal) - this.sliderwidth - 1, this.tickheight - 1,
this.sliderwidth * 2 + 2, this.tickheight * 3 + 2);
// (re)draw horisontal bar
this.ctx.lineWidth = 1;
this.ctx.fillStyle = this.fillStyle;
this.ctx.strokeStyle = this.strokeStyle;
mid = Math.round(this.height / 2);
// XXX only needs to be redrawn for old slider pos
this.ctx.fillRect(this.margin, mid - 1, this.barwidth, 3);
this.ctx.strokeRect(this.margin, mid - 1, this.barwidth, 3);
// Let the accessor know the outOfRange state. The accessor will
// take care of the control styling and dispatching the appropriate
// events if the in/out of range condition has changed from its
// previous state.
if (outOfRange != this.outOfRange) {
this.outOfRange = outOfRange;
this.accessors.setInRange(!outOfRange);
}
// if out-of-range, we cannot represent the value
if (outOfRange) {
this.rVal = null;
return null;
}
// draw slider at new position
this.justMoved = (aMode == "move");
this.drawSlider(this.calcPos(aVal), this.justMoved);
this.ctx.restore();
// Store new value
return this.rVal = aVal;
]]>
</body>
</method>
<!-- get x,y offset for mouse events -->
<method name="getOffset">
<parameter name="event"/>
<body>
var obj;
if (document.getBoxObjectFor) {
obj = document.getBoxObjectFor(this.canvas);
} else {
obj = { x: event.target.offsetLeft, y: event.target.offsetTop };
}
return obj;
</body>
</method>
<!-- calculate slider position from mouse position -->
<method name="calcMousePos">
<parameter name="obj"/>
<parameter name="x"/>
<body>
x -= obj.margin;
if (obj.rStep) {
x = (x / obj.stepsp) * obj.rStep;
} else {
x = (x / this.barwidth) * (this.rEnd - this.rBegin);
}
return x + obj.rBegin;
</body>
</method>
<!-- handle mouse down -->
<method name="handleMouseDown">
<parameter name="event"/>
<body>
<![CDATA[
if (event.button == 0) {
this.currentOffset = this.getOffset(event);
this.originalVal = this.rVal;
this.isMoving = true;
var xpos = event.clientX - this.currentOffset.x;
if (xpos < this.margin) {
xpos = this.margin;
}
if (xpos > (this.barwidth + this.margin)) {
xpos = this.barwidth;
}
var mode = this.isIncremental ? null : "move"
this.setSlider(this.calcMousePos(this, xpos), mode);
return;
}
]]>
</body>
</method>
<!-- handle mouse up -->
<method name="handleMouseUp">
<parameter name="event"/>
<body>
if (event.button != 0 || !this.isMoving) {
return;
}
var x = event.clientX - this.currentOffset.x;
this.setSlider(this.calcMousePos(this, x));
this.isMoving = false;
</body>
</method>
<!-- handle mouse moves -->
<method name="handleMouseMove">
<parameter name="event"/>
<body>
<![CDATA[
if (!this.isMoving) {
return;
}
var xpos = event.clientX - this.currentOffset.x;
if (xpos < this.margin) {
xpos = this.margin;
}
if (xpos > (this.barwidth + this.margin)) {
xpos = this.barwidth + this.margin;
}
var mode = this.isIncremental ? null : "move"
this.setSlider(this.calcMousePos(this, xpos), mode);
]]>
</body>
</method>
<!-- handle mouse out -->
<method name="handleMouseOut">
<parameter name="event"/>
<body>
if (!this.isMoving) {
return;
}
this.isMoving = false;
this.setSlider(this.originalVal);
</body>
</method>
<method name="handleKey">
<parameter name="event"/>
<body>
var move;
if (this.rStep) {
move = this.rStep;
} else {
move = (this.rEnd - this.rBegin) / 20;
}
if (event.keyCode == event.DOM_VK_LEFT) {
this.setSlider(this.rVal - move);
} else if (event.keyCode == event.DOM_VK_RIGHT) {
this.setSlider(this.rVal + move);
} else if (event.keyCode == event.DOM_VK_PAGE_DOWN) {
this.setSlider(this.rVal - move * 2);
} else if (event.keyCode == event.DOM_VK_PAGE_UP) {
this.setSlider(this.rVal + move * 2);
} else if (event.keyCode == event.DOM_VK_HOME) {
this.setSlider(this.rBegin);
} else if (event.keyCode == event.DOM_VK_END) {
this.setSlider(this.rEnd);
}
</body>
</method>
<method name="refresh">
<body>
<![CDATA[
if (!this.isInitialized) {
if (!this.delegate) {
return;
}
var labelBegin = document.getAnonymousElementByAttribute(this, "anonid", "labelBegin");
var labelEnd = document.getAnonymousElementByAttribute(this, "anonid", "labelEnd");
var canvas = document.getAnonymousElementByAttribute(this, "anonid", "canvas");
this.isInitialized = this.createRange(canvas, labelBegin, labelEnd,
this.accessors.getRangeStart(),
this.accessors.getRangeEnd(),
this.accessors.getRangeStep());
}
// XXX: does not clear range if bound node "disappears"
if (this.isInitialized && this.accessors.hasBoundNode()) {
this.setSlider(this.accessors.getValue(), "set");
}
]]>
</body>
</method>
<method name="focus">
<body>
this.canvas.focus();
return true;
</body>
</method>
<!-- create new range object -->
<method name="createRange">
<parameter name="aCanvas"/>
<parameter name="aLabelBegin"/>
<parameter name="aLabelEnd"/>
<parameter name="aBegin"/>
<parameter name="aEnd"/>
<parameter name="aStep"/>
<body>
<![CDATA[
if (!(aCanvas && aLabelBegin && aLabelEnd)) {
this.delegate.reportError("rangeNullObjects");
return false;
}
this.rBegin = parseFloat(aBegin);
this.rEnd = parseFloat(aEnd);
this.rStep = parseFloat(aStep);
this.rVal = this.rBegin;
this.isIncremental = this.getAttribute("incremental") == "true";
this.justMoved = false;
if (isNaN(this.rBegin) || isNaN(this.rEnd)) {
this.delegate.reportError("rangeNullInit");
return false;
}
// XXX should we handle this?
if (this.rBegin >= this.rEnd) {
this.delegate.reportError("rangeBeginEndError");
return false;
}
if (isNaN(this.rStep)) {
this.rStep = null;
} else if (this.rStep < 0) {
// XXX better handling
this.rStep = -this.rStep;
}
// Sanitize @step for xsd:integer
// XXX: we need a schemavalidator.isDerivedFrom(type, "xsd:integer");
if (this.getAttribute("type") == "http://www.w3.org/2001/XMLSchema#integer") {
if (!this.rStep) {
this.rStep = 1;
} else {
this.rStep = Math.round(this.rStep);
}
}
// set labels
aLabelBegin.appendChild(document.createTextNode(this.rBegin));
aLabelEnd.appendChild(document.createTextNode(this.rEnd));
// get canvas
this.canvas = aCanvas;
this.height = this.canvas.height;
// get and set context
this.ctx = this.canvas.getContext("2d");
this.ctx.globalAlpha = 1.0;
this.ctx.lineWidth = 1;
// size of horisontal bar
this.margin = Math.round(this.canvas.width / 45);
if (this.margin < 4) {
this.margin = 4;
}
this.barwidth = this.canvas.width - (2 * this.margin);
// slider size
this.sliderwidth = this.margin - 1;
if (this.sliderwidth < 4) {
this.sliderwidth = 4;
}
this.slidertip = Math.round(this.height / 10);
this.tickheight = this.slidertip * 2;
if (!this.rStep) {
this.adjEnd = this.rEnd;
return true;
}
// begin and end might not be a step
this.adjEnd = this.rEnd - ((this.rEnd - this.rBegin) % this.rStep);
this.steps = (this.adjEnd - this.rBegin) / this.rStep;
this.stepsp = (this.barwidth * (this.adjEnd / this.rEnd)) / this.steps;
this.width = this.steps * this.stepsp;
// ticks (== steps for the moment)
this.ticks = this.steps;
this.ticksp = this.stepsp;
for (var i = 0; i <= this.ticks; ++i) {
var pos = Math.round(this.margin + i * this.ticksp);
this.ctx.moveTo(pos, this.height - this.tickheight + 1);
this.ctx.lineTo(pos, this.height);
this.ctx.closePath();
this.ctx.stroke();
}
return true;
]]>
</body>
</method>
</implementation>
</binding>
</bindings>