1795 lines
80 KiB
XML
1795 lines
80 KiB
XML
<?xml version="1.0"?>
|
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
|
|
|
<!-- ***** 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 OEone Calendar Code, released October 31st, 2001.
|
|
-
|
|
- The Initial Developer of the Original Code is
|
|
- OEone Corporation.
|
|
- Portions created by the Initial Developer are Copyright (C) 2001
|
|
- the Initial Developer. All Rights Reserved.
|
|
-
|
|
- Contributor(s):
|
|
- Garth Smedley <garths@oeone.com>
|
|
- Mike Potter <mikep@oeone.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 LGPL or the GPL. 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 ***** -->
|
|
|
|
<!--
|
|
/* This defines <datepicker/> <timepicker/> and <datetimepicker/>
|
|
which all descend from datetimepicker-base to get date/time parsing
|
|
and consistent behavior.
|
|
It relies on <minimonth/> for the date picker's drop down.
|
|
You can be notified of change event as follows:
|
|
<datepicker id="my-date-picker" onchange="myDatePick( this );"/>
|
|
May get/set value in javascript with
|
|
document.getElementById("my-date-picker").value = new Date();
|
|
May disable/enable in javascript with
|
|
document.getElementById("my-date-picker").disabled = true;
|
|
May also disable/enable a datetimepicker's component
|
|
datepicker or timepicker individually with
|
|
document.getElementById("my-datetimepicker").datepickerdisabled = true;
|
|
document.getElementById("my-datetimepicker").timepickerdisabled = true;
|
|
|
|
*/
|
|
-->
|
|
<bindings id="xulDatePicker"
|
|
xmlns="http://www.mozilla.org/xbl"
|
|
xmlns:xbl="http://www.mozilla.org/xbl"
|
|
xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
|
|
<binding id="datetextpicker"
|
|
extends="chrome://calendar/content/datetimepickers/datetimepickers.xml#datetimepicker-base">
|
|
<content>
|
|
<xul:hbox flex="1">
|
|
<xul:textbox anonid="date-textbox" flex="1"
|
|
onfocus="this.select();"
|
|
onkeypress="if (event.keyCode == Components.interfaces.nsIDOMKeyEvent.DOM_VK_RETURN) fireGoEvent();"/>
|
|
<xul:button anonid="date-go-button" oncommand="fireGoEvent()"/>
|
|
</xul:hbox>
|
|
</content>
|
|
<implementation>
|
|
<field name="mRelativeDates">[]</field>
|
|
<field name="mDayNames">[]</field>
|
|
<field name="mRelationWords">[]</field>
|
|
<field name="mMonthLongNames">[]</field>
|
|
<field name="mMonthShortNames">[]</field>
|
|
|
|
<constructor><![CDATA[
|
|
var goButton = document.getAnonymousElementByAttribute(this, "anonid", "date-go-button");
|
|
goButton.setAttribute("label", calGetString("calendar", "go"));
|
|
// Load the stuff we're going to use to parse written dates
|
|
this.mRelativeDates = [
|
|
{word:calGetString("calendar", "today").toLowerCase(), offset: 0},
|
|
{word:calGetString("calendar", "yesterday").toLowerCase(), offset: -1},
|
|
{word:calGetString("calendar", "tomorrow").toLowerCase(), offset: 1}];
|
|
var i;
|
|
for (i = 1; i <= 7; i++) {
|
|
this.mDayNames.push(calGetString("dateFormat", "day."+i+".name").toLowerCase());
|
|
}
|
|
|
|
for (i = 1; i <= 12; i++) {
|
|
this.mMonthLongNames.push(calGetString("dateFormat", "month."+i+".name").toLowerCase());
|
|
this.mMonthShortNames.push(calGetString("dateFormat", "month."+i+".Mmm").toLowerCase());
|
|
}
|
|
|
|
// note that some languages have different conjugations of
|
|
// next/last depending on the day
|
|
this.mRelationWords = [
|
|
{word:calGetString("calendar", "last1"), offset: -1},
|
|
{word:calGetString("calendar", "last2"), offset: -1},
|
|
{word:calGetString("calendar", "next1"), offset: 0},
|
|
{word:calGetString("calendar", "next2"), offset: 0}];
|
|
|
|
// Set the value to today
|
|
var text = document.getAnonymousElementByAttribute(this, "anonid", "date-textbox");
|
|
text.value = calGetString("calendar", "today");
|
|
]]></constructor>
|
|
|
|
<property name="value">
|
|
<getter><![CDATA[
|
|
return this.mValue;
|
|
]]></getter>
|
|
<setter><![CDATA[
|
|
var text = document.getAnonymousElementByAttribute(this, "anonid", "date-textbox");
|
|
try {
|
|
text.value = this.formatDate(val);
|
|
} catch(ex) {}
|
|
return val;
|
|
]]></setter>
|
|
</property>
|
|
|
|
<method name="fireGoEvent">
|
|
<body><![CDATA[
|
|
var text = document.getAnonymousElementByAttribute(this, "anonid", "date-textbox");
|
|
var date = this.parseLanguageDate(text.value);
|
|
if (!date) {
|
|
date = this.parseDateTime(text.value);
|
|
}
|
|
if (date) {
|
|
// format fails if year <= 1600 on win2k, so try format first.
|
|
var prettyDate;
|
|
try {
|
|
prettyDate = this.formatDate(date);
|
|
} catch (ex) {} // fall thru
|
|
}
|
|
if (date && prettyDate) {
|
|
this.mValue = date;
|
|
text.value = prettyDate;
|
|
this.fireEvent("command", date);
|
|
return;
|
|
}
|
|
]]></body>
|
|
</method>
|
|
|
|
<!-- This function will take written (with words) dates and, if
|
|
- possible, return a Date() object described by the words. Note
|
|
- that this function will not parse explicit dates, like 1/1/06,
|
|
- you should use parseDateTime for that.
|
|
-->
|
|
<method name="parseLanguageDate">
|
|
<parameter name="aValue"/>
|
|
<body><![CDATA[
|
|
if (!aValue) {
|
|
return null;
|
|
}
|
|
var val = aValue.toLowerCase();
|
|
// Look for the easy ones like today, tomorrow, etc
|
|
for each (var rel in this.mRelativeDates) {
|
|
if (val == rel.word) {
|
|
var now = new Date();
|
|
now.setDate(now.getDate()+rel.offset);
|
|
return now;
|
|
}
|
|
}
|
|
|
|
var parser = this;
|
|
|
|
// Takes a written day of the week and returns a js-date
|
|
// corresponding to the nearest day in the future that is
|
|
// that day of the week
|
|
function getDateForDay(aWord) {
|
|
for (var i in parser.mDayNames) {
|
|
if (aWord != parser.mDayNames[i]) {
|
|
continue;
|
|
}
|
|
// Figure out what day of the week today is.
|
|
var today = now();
|
|
|
|
// i-weekday gets the offset. Add 7 to ensure that the %
|
|
// operation stays positive.
|
|
var offset = (i - today.weekday + 7) % 7;
|
|
today.day = today.day + offset;
|
|
return today.jsDate;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Remove commas
|
|
val = val.replace(',', '');
|
|
|
|
if (val.indexOf(' ') == -1) {
|
|
// Just a single word, or a single date.
|
|
return getDateForDay(val);
|
|
}
|
|
|
|
// Replace month names with numbers
|
|
for (var i in this.mMonthLongNames) {
|
|
if (val.indexOf(this.mMonthLongNames[i]) != -1) {
|
|
var newVal = val.replace(this.mMonthLongNames[i], Number(i)+1);
|
|
newVal = newVal.replace(' ', '/');
|
|
return this.parseDateTime(newVal);
|
|
}
|
|
}
|
|
|
|
// Same for short month names
|
|
for (var i in this.mMonthShortNames) {
|
|
if (val.indexOf(this.mMonthShortNames[i]) != -1) {
|
|
var newVal = val.replace(this.mMonthShortNames[i], Number(i)+1);
|
|
newVal = newVal.replace(' ', '/');
|
|
return this.parseDateTime(newVal);
|
|
}
|
|
}
|
|
|
|
// Now for the cool 'next' and 'last'
|
|
var words = val.split(' ');
|
|
var offset, day;
|
|
for each (word in words) {
|
|
for each (rel in this.mRelationWords) {
|
|
if (word == rel.word) {
|
|
offset = rel.offset;
|
|
break;
|
|
}
|
|
}
|
|
for (var i in this.mDayNames) {
|
|
if (word == this.mDayNames[i]) {
|
|
day = getDateForDay(word);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (day && offset != undefined) {
|
|
day.setDate(day.getDate()+(7*offset));
|
|
return day;
|
|
}
|
|
return null;
|
|
]]></body>
|
|
</method>
|
|
</implementation>
|
|
</binding>
|
|
<binding id="datepicker" extends="chrome://calendar/content/datetimepickers/datetimepickers.xml#datetimepicker-base">
|
|
<!-- ::::::::::::::::: CONTENT ::::::::::::::::::::::::: -->
|
|
<!-- Desired behavior: when user is done editing the date field
|
|
and either leaves the field (onblur) or closes the dialog
|
|
(13 is enter/return key), parse the date and redisplay it
|
|
in the date field using the current format to verify
|
|
whether the date was parsed correctly.
|
|
This cannot be done with textbox oninput, so use a workaround.
|
|
This was done with textbox onkeypress="parseTextBoxDate(true)"
|
|
and onblur="parseTextBoxDate(true)" which worked in Moz1.6, but
|
|
no longer works in Moz1.7.
|
|
Therefore constructor stores attribute kDatePicker on the textbox,
|
|
and the onblur and onkeypress commands navigate to this kDatePicker.
|
|
xul:Textbox contains an xul:hbox which contains html:input.
|
|
Onkeypress and onblur are not documented attributes of xul:textbox,
|
|
but become attributes of the html:input.
|
|
Not clear how to navigate from the textbox to the input, otherwise
|
|
could put kDatePicker property on the input element.
|
|
[document.getAnonymousNodes(textBox) fails as of Moz1.7b]).
|
|
So navigate parents to textbox in order to call parseTextBoxDate.
|
|
[Note: minimonth onmonthchange reshows parent popup to fix title
|
|
month/year (bug 973914). minimonth onpopuplisthidden reshows parent
|
|
popup to avoid freeze (bug 278877).]
|
|
[this comment is outside the <content> so it won't become a
|
|
node that interferes with navigation to interior nodes.] -->
|
|
<content>
|
|
<xul:hbox flex="1" id="hbox" class="datepicker-box-class">
|
|
<xul:menulist editable="true" sizetopopup="false"
|
|
class="datepicker-text-class"
|
|
onkeypress="if (event.keyCode == 13) this.kDatePicker.parseTextBoxDate(true);"
|
|
xbl:inherits="disabled">
|
|
<xul:menupopup popupanchor="bottomright" popupalign="topright"
|
|
anonid="datepopup"
|
|
onpopupshowing="this.parentNode.kDatePicker.onPopup();"
|
|
onpopuphiding="this.firstChild.hidePopupList();">
|
|
<xul:minimonth/>
|
|
</xul:menupopup>
|
|
</xul:menulist>
|
|
</xul:hbox>
|
|
</content>
|
|
|
|
<!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
|
|
<implementation>
|
|
<constructor>
|
|
<![CDATA[
|
|
var hbox = document.getAnonymousNodes(this)[0];
|
|
this.kTextBox = hbox.firstChild;
|
|
this.kTextBox.kDatePicker = this; // enable call back to method in Moz1.7
|
|
this.kTextBox.menupopup.kDatePicker = this;
|
|
this.kMinimonth = this.kTextBox.menupopup.firstChild;
|
|
this.mInPopup = false;
|
|
this.mValue = null;
|
|
var val = this.getAttribute("value");
|
|
if (val) {
|
|
this.value = new Date(val); // setting value property calls update
|
|
} else {
|
|
this.value = new Date();
|
|
}
|
|
this.kMinimonth.addEventListener("select", this.clickDate, false);
|
|
this.kMinimonth.addEventListener("monthchange", this.reshowPopup, false);
|
|
this.kMinimonth.addEventListener("popuplisthidden", this.reshowPopup, false);
|
|
this.mIsReshowing = false;
|
|
]]>
|
|
</constructor>
|
|
|
|
<destructor>
|
|
<![CDATA[
|
|
this.kMinimonth.removeEventListener("select", this.clickDate, false);
|
|
this.kMinimonth.removeEventListener("monthchange", this.reshowPopup, false);
|
|
this.kMinimonth.removeEventListener("popuplisthidden", this.reshowPopup, false);
|
|
]]>
|
|
</destructor>
|
|
|
|
<method name="update">
|
|
<parameter name="aValue"/>
|
|
<parameter name="aRefresh"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aValue != null) {
|
|
// format fails if year <= 1600 on win2k, so try format first.
|
|
var formattedValue = null;
|
|
try {
|
|
formattedValue = this.formatDate(aValue);
|
|
} catch (ex) {} // fall thru
|
|
|
|
if (formattedValue) {
|
|
// format succeeded, safe to set value
|
|
var dateChanged = true;
|
|
if (this.mValue != null) {
|
|
dateChanged = (this.mValue.getDate() != aValue.getDate()) ||
|
|
(this.mValue.getMonth() != aValue.getMonth()) ||
|
|
(this.mValue.getFullYear() != aValue.getFullYear());
|
|
}
|
|
this.mValue = aValue;
|
|
this.kTextBox.value = formattedValue;
|
|
if (aRefresh && dateChanged) {
|
|
this.fireEvent("change");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
// invalid date, revert to previous date
|
|
// set textBox.value property, not attribute
|
|
if (this.mValue) {
|
|
this.kTextBox.value = this.formatDate(this.mValue);
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onPopup">
|
|
<body>
|
|
<![CDATA[
|
|
// avoid reinitializing during reshow, for bugs 273914 & 278877 workaround
|
|
if (! this.mIsReshowing) {
|
|
this.mInPopup = true;
|
|
this.kMinimonth.update( this.mValue );
|
|
this.mInPopup = false;
|
|
// select all to remove cursor since can't type while popped-up
|
|
this.select();
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Reshow hides and shows parent popup without reinitializing in onPopup
|
|
to workaround bugs 273914 (update title) & 278877 (avoid freeze) -->
|
|
<method name="reshowPopup">
|
|
<parameter name="aEvent"/>
|
|
<body>
|
|
<![CDATA[
|
|
var datepicker = aEvent.target.parentNode.parentNode.kDatePicker;
|
|
datepicker.mIsReshowing = true;
|
|
try {
|
|
aEvent.target.parentNode.hidePopup();
|
|
aEvent.target.parentNode.showPopup();
|
|
} finally {
|
|
datepicker.mIsReshowing = false;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="parseTextBoxDate">
|
|
<parameter name="aRefresh"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.update(this.parseDateTime(this.kTextBox.value), aRefresh);
|
|
this.lastDateParseIncludedTime = false;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="select">
|
|
<body>
|
|
<![CDATA[
|
|
// select all in text box
|
|
this.kTextBox.select();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="clickDate">
|
|
<parameter name="aEvent" />
|
|
<body>
|
|
<![CDATA[
|
|
var datepicker = aEvent.target.parentNode.parentNode.kDatePicker;
|
|
if (! datepicker.mInPopup)
|
|
{
|
|
// aEvent.target is the minimonth
|
|
datepicker.update(new Date(aEvent.target.value), true);
|
|
// select changed value so no cursor appears (can't type to it).
|
|
datepicker.select();
|
|
aEvent.target.parentNode.hidePopup();
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
</implementation>
|
|
|
|
<!-- ::::::::::::::::: HANDLERS ::::::::::::::::::::::::: -->
|
|
<handlers>
|
|
<handler event="bindingattached" action="this.initialize();"/>
|
|
|
|
<handler event="blur" phase="capturing">
|
|
<![CDATA[
|
|
this.parseTextBoxDate(true);
|
|
]]>
|
|
</handler>
|
|
</handlers>
|
|
|
|
</binding>
|
|
|
|
<binding id="timepicker" extends="chrome://calendar/content/datetimepickers/datetimepickers.xml#datetimepicker-base">
|
|
<!-- ::::::::::::::::: CONTENT ::::::::::::::::::::::::: -->
|
|
<!-- Desired behavior: when user is done editing the time field
|
|
and either leaves the field (onblur) or closes the dialog
|
|
(13 is enter/return key), parse the time and redisplay it
|
|
in the time field using the current format to verify
|
|
whether the time was parsed correctly.
|
|
This cannot be done with textbox oninput, so use a workaround.
|
|
This was done with textbox onkeypress="parseTextBoxTime(true)"
|
|
and onblur="parseTextBoxTime(true)" which worked in Moz1.6, but
|
|
no longer works in Moz1.7.
|
|
Therefore constructor stores attribute kTimePicker on the textbox,
|
|
and the onblur and onkeypress commands navigate to this kTimePicker.
|
|
xul:Textbox contains an xul:hbox which contains html:input.
|
|
Onkeypress and onblur are not documented attributes of xul:textbox,
|
|
but become attributes of the html:input.
|
|
Not clear how to navigate from the textbox to the input, otherwise
|
|
could put kTimePicker property on the input element.
|
|
[document.getAnonymousNodes(textBox) fails as of Moz1.7b]).
|
|
So navigate parents to textbox in order to call parseTextBoxTime.
|
|
[this comment is outside the <content> so it won't become a
|
|
node that interferes with navigation to interior nodes.] -->
|
|
<content>
|
|
<xul:hbox flex="1" id="hbox" class="timepicker-box-class">
|
|
<xul:menulist editable="true" sizetopopup="false"
|
|
id="timepicker-text"
|
|
class="timepicker-text-class"
|
|
onkeypress="if (event.keyCode == 13) this.kTimePicker.parseTextBoxTime(true);"
|
|
xbl:inherits="disabled">
|
|
<xul:menupopup popupalign="topright" popupanchor="bottomright"
|
|
onpopupshowing="onPopup(this)"
|
|
onpopuphiding="onHide(this)">
|
|
<xul:timepicker-grids />
|
|
</xul:menupopup>
|
|
</xul:menulist>
|
|
</xul:hbox>
|
|
</content>
|
|
|
|
<!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
|
|
<implementation>
|
|
<constructor>
|
|
<![CDATA[
|
|
var hbox = document.getAnonymousNodes(this)[0];
|
|
this.kTextBox = hbox.childNodes[0];
|
|
this.kTextBox.kTimePicker = this; // enable call back to method in Moz1.7
|
|
|
|
var val = this.getAttribute("value");
|
|
if (val)
|
|
this.value = (new Date(val));
|
|
else
|
|
this.value = (new Date());
|
|
]]>
|
|
</constructor>
|
|
|
|
<method name="update">
|
|
<parameter name="aValue"/>
|
|
<parameter name="aRefresh"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aValue != null) {
|
|
this.mValue = aValue;
|
|
}
|
|
// set textBox.value property, not attribute
|
|
this.kTextBox.value = this.formatTime(this.mValue);
|
|
|
|
if (aValue != null && aRefresh) {
|
|
var event = document.createEvent('Events');
|
|
event.initEvent("change", true, true);
|
|
this.dispatchEvent(event);;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="parseTextBoxTime">
|
|
<parameter name="aRefresh"/>
|
|
<body>
|
|
<![CDATA[
|
|
var time = this.parseTime(this.kTextBox.value);
|
|
this.update(time, aRefresh);
|
|
return time;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onPopup">
|
|
<parameter name="aPopup" />
|
|
<body>
|
|
<![CDATA[
|
|
// select all to remove cursor since can't type while popped-up
|
|
this.select();
|
|
var timePickerGrid = aPopup.childNodes[0];
|
|
timePickerGrid.onPopupShowing(this, aPopup);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onHide">
|
|
<parameter name="aPopup"/>
|
|
<body>
|
|
<![CDATA[
|
|
// This is the timepicker grid. Why aren't we using anonid?
|
|
aPopup.childNodes[0].onPopupHiding();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="select">
|
|
<body>
|
|
<![CDATA[
|
|
// select all in text box
|
|
this.kTextBox.select();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
</implementation>
|
|
|
|
<!-- ::::::::::::::::: HANDLERS ::::::::::::::::::::::::: -->
|
|
|
|
<handlers>
|
|
<handler event="bindingattached" action="this.initialize();"/>
|
|
<handler event="blur" phase="capturing">
|
|
<![CDATA[
|
|
this.parseTextBoxTime(true);
|
|
]]>
|
|
</handler>
|
|
</handlers>
|
|
|
|
</binding>
|
|
|
|
<binding id="timepicker-hour">
|
|
<content>
|
|
<xul:spacer flex="1"/>
|
|
<xul:vbox anonid="hourbox"
|
|
onclick="clickHour(this.parentNode, this.parentNode.getAttribute('value'))"
|
|
ondblclick="doubleClickHour(this.parentNode, this.parentNode.getAttribute('value'))">
|
|
<xul:box>
|
|
<xul:label class="time-picker-hour-label" anonid="hourlabel" xbl:inherits="value=label,selected"/>
|
|
</xul:box>
|
|
<xul:spacer flex="1"/>
|
|
</xul:vbox>
|
|
<xul:spacer flex="1"/>
|
|
</content>
|
|
<handlers>
|
|
<handler event="DOMMouseScroll">
|
|
<![CDATA[
|
|
var rows = event.detail;
|
|
if (rows == NSUIEvent.SCROLL_PAGE_UP) {
|
|
rows = -1;
|
|
} else if (rows == NSUIEvent.SCROLL_PAGE_DOWN) {
|
|
rows = 1;
|
|
} else {
|
|
// In this case event.detail contains the default number of lines
|
|
// to scroll. We always want to only scroll 1 hour though
|
|
rows = (rows > 0) ? 1 : -1;
|
|
}
|
|
moveHours(rows);
|
|
]]>
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="timepicker-minute">
|
|
<content>
|
|
<xul:spacer flex="1"/>
|
|
<xul:vbox anonid="minutebox"
|
|
onclick="clickMinute(this.parentNode, this.parentNode.getAttribute('value'))">
|
|
<xul:box>
|
|
<xul:label class="time-picker-minute-label" anonid="minutelabel" xbl:inherits="value=label,selected"/>
|
|
</xul:box>
|
|
</xul:vbox>
|
|
<xul:spacer flex="1"/>
|
|
</content>
|
|
<handlers>
|
|
<handler event="DOMMouseScroll">
|
|
<![CDATA[
|
|
var rows = event.detail;
|
|
if (rows == NSUIEvent.SCROLL_PAGE_UP) {
|
|
rows = -1;
|
|
} else if (rows == NSUIEvent.SCROLL_PAGE_DOWN) {
|
|
rows = 1;
|
|
} else {
|
|
// In this case event.detail contains the default number of lines
|
|
// to scroll. We always want to only scroll 1 minute though
|
|
rows = (rows > 0) ? 1 : -1;
|
|
}
|
|
moveMinutes(rows);
|
|
]]>
|
|
</handler>
|
|
</handlers>
|
|
</binding>
|
|
|
|
<binding id="timepicker-grids" extends="xul:box">
|
|
<resources>
|
|
<stylesheet src="chrome://calendar/skin/datetimepickers/datetimepickers.css"/>
|
|
</resources>
|
|
|
|
<!-- ::::::::::::::::: CONTENT ::::::::::::::::::::::::: -->
|
|
<content>
|
|
<!-- Box to hold time picker internals -->
|
|
<vbox anonid="time-picker-grids"
|
|
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
|
|
<!-- Hour Grid -->
|
|
<grid anonid="time-picker-hour-grid">
|
|
<columns>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
<column class="time-picker-hour-column-class" flex="1"/>
|
|
</columns>
|
|
<rows>
|
|
<row flex="1">
|
|
<timepicker-hour class="time-picker-hour-box-class" value="0"
|
|
anonid="time-picker-hour-box-0" label="0"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="1"
|
|
anonid="time-picker-hour-box-1" label="1"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="2"
|
|
anonid="time-picker-hour-box-2" label="2"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="3"
|
|
anonid="time-picker-hour-box-3" label="3"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="4"
|
|
anonid="time-picker-hour-box-4" label="4"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="5"
|
|
anonid="time-picker-hour-box-5" label="5"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="6"
|
|
anonid="time-picker-hour-box-6" label="6"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="7"
|
|
anonid="time-picker-hour-box-7" label="7"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="8"
|
|
anonid="time-picker-hour-box-8" label="8"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="9"
|
|
anonid="time-picker-hour-box-9" label="9"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="10"
|
|
anonid="time-picker-hour-box-10" label="10"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="11"
|
|
anonid="time-picker-hour-box-11" label="11"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-hour class="time-picker-hour-box-class" value="12"
|
|
anonid="time-picker-hour-box-12" label="12"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="13"
|
|
anonid="time-picker-hour-box-13" label="13"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="14"
|
|
anonid="time-picker-hour-box-14" label="14"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="15"
|
|
anonid="time-picker-hour-box-15" label="15"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="16"
|
|
anonid="time-picker-hour-box-16" label="16"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="17"
|
|
anonid="time-picker-hour-box-17" label="17"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="18"
|
|
anonid="time-picker-hour-box-18" label="18"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="19"
|
|
anonid="time-picker-hour-box-19" label="19"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="20"
|
|
anonid="time-picker-hour-box-20" label="20"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="21"
|
|
anonid="time-picker-hour-box-21" label="21"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="22"
|
|
anonid="time-picker-hour-box-22" label="22"/>
|
|
<timepicker-hour class="time-picker-hour-box-class" value="23"
|
|
anonid="time-picker-hour-box-23" label="23"/>
|
|
</row>
|
|
</rows>
|
|
</grid> <!-- Hour Grid -->
|
|
<!-- Five Minute Grid -->
|
|
<vbox anonid="time-picker-five-minute-grid-box" flex="1">
|
|
<grid anonid="time-picker-five-minute-grid" flex="1">
|
|
<columns>
|
|
<column class="time-picker-five-minute-column-class" flex="1"/>
|
|
<column class="time-picker-five-minute-column-class" flex="1"/>
|
|
<column class="time-picker-five-minute-column-class" flex="1"/>
|
|
<column class="time-picker-five-minute-column-class" flex="1"/>
|
|
<column class="time-picker-five-minute-column-class" flex="1"/>
|
|
<column class="time-picker-five-minute-column-class" flex="1"/>
|
|
</columns>
|
|
<rows>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-five-minute-class" value="0"
|
|
anonid="time-picker-five-minute-box-0" label=":00"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="5"
|
|
anonid="time-picker-five-minute-box-5" label=":05"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="10"
|
|
anonid="time-picker-five-minute-box-10" label=":10"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="15"
|
|
anonid="time-picker-five-minute-box-15" label=":15"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="20"
|
|
anonid="time-picker-five-minute-box-20" label=":20"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="25"
|
|
anonid="time-picker-five-minute-box-25" label=":25"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-five-minute-class" value="30"
|
|
anonid="time-picker-five-minute-box-30" label=":30"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="35"
|
|
anonid="time-picker-five-minute-box-35" label=":35"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="40"
|
|
anonid="time-picker-five-minute-box-40" label=":40"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="45"
|
|
anonid="time-picker-five-minute-box-45" label=":45"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="50"
|
|
anonid="time-picker-five-minute-box-50" label=":50"/>
|
|
<timepicker-minute class="time-picker-five-minute-class" value="55"
|
|
anonid="time-picker-five-minute-box-55" label=":55"/>
|
|
</row>
|
|
</rows>
|
|
</grid> <!-- Five Minute Grid -->
|
|
<hbox class="time-picker-minutes-bottom">
|
|
<spacer flex="1"/>
|
|
<label class="time-picker-more-control-label" value="»" onclick="clickMore()"/>
|
|
</hbox>
|
|
</vbox> <!-- Five Minute Grid Box -->
|
|
<!-- One Minute Grid -->
|
|
<vbox anonid="time-picker-one-minute-grid-box" flex="1" hidden="true">
|
|
<grid anonid="time-picker-one-minute-grid" flex="1">
|
|
<columns>
|
|
<column class="time-picker-one-minute-column-class" flex="1"/>
|
|
<column class="time-picker-one-minute-column-class" flex="1"/>
|
|
<column class="time-picker-one-minute-column-class" flex="1"/>
|
|
<column class="time-picker-one-minute-column-class" flex="1"/>
|
|
<column class="time-picker-one-minute-column-class" flex="1"/>
|
|
</columns>
|
|
<rows >
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="0"
|
|
anonid="time-picker-one-minute-box-0" label=":00"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="1"
|
|
anonid="time-picker-one-minute-box-1" label=":01"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="2"
|
|
anonid="time-picker-one-minute-box-2" label=":02"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="3"
|
|
anonid="time-picker-one-minute-box-3" label=":03"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="4"
|
|
anonid="time-picker-one-minute-box-4" label=":04"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="5"
|
|
anonid="time-picker-one-minute-box-5" label=":05"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="6"
|
|
anonid="time-picker-one-minute-box-6" label=":06"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="7"
|
|
anonid="time-picker-one-minute-box-7" label=":07"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="8"
|
|
anonid="time-picker-one-minute-box-8" label=":08"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="9"
|
|
anonid="time-picker-one-minute-box-9" label=":09"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="10"
|
|
anonid="time-picker-one-minute-box-10" label=":10"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="11"
|
|
anonid="time-picker-one-minute-box-11" label=":11"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="12"
|
|
anonid="time-picker-one-minute-box-12" label=":12"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="13"
|
|
anonid="time-picker-one-minute-box-13" label=":13"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="14"
|
|
anonid="time-picker-one-minute-box-14" label=":14"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="15"
|
|
anonid="time-picker-one-minute-box-15" label=":15"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="16"
|
|
anonid="time-picker-one-minute-box-16" label=":16"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="17"
|
|
anonid="time-picker-one-minute-box-17" label=":17"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="18"
|
|
anonid="time-picker-one-minute-box-18" label=":18"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="19"
|
|
anonid="time-picker-one-minute-box-19" label=":19"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="20"
|
|
anonid="time-picker-one-minute-box-20" label=":20"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="21"
|
|
anonid="time-picker-one-minute-box-21" label=":21"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="22"
|
|
anonid="time-picker-one-minute-box-22" label=":22"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="23"
|
|
anonid="time-picker-one-minute-box-23" label=":23"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="24"
|
|
anonid="time-picker-one-minute-box-24" label=":24"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="25"
|
|
anonid="time-picker-one-minute-box-25" label=":25"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="26"
|
|
anonid="time-picker-one-minute-box-26" label=":26"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="27"
|
|
anonid="time-picker-one-minute-box-27" label=":27"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="28"
|
|
anonid="time-picker-one-minute-box-28" label=":28"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="29"
|
|
anonid="time-picker-one-minute-box-29" label=":29"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="30"
|
|
anonid="time-picker-one-minute-box-30" label=":30"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="31"
|
|
anonid="time-picker-one-minute-box-31" label=":31"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="32"
|
|
anonid="time-picker-one-minute-box-32" label=":32"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="33"
|
|
anonid="time-picker-one-minute-box-33" label=":33"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="34"
|
|
anonid="time-picker-one-minute-box-34" label=":34"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="35"
|
|
anonid="time-picker-one-minute-box-35" label=":35"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="36"
|
|
anonid="time-picker-one-minute-box-36" label=":36"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="37"
|
|
anonid="time-picker-one-minute-box-37" label=":37"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="38"
|
|
anonid="time-picker-one-minute-box-38" label=":38"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="39"
|
|
anonid="time-picker-one-minute-box-39" label=":39"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="40"
|
|
anonid="time-picker-one-minute-box-40" label=":40"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="41"
|
|
anonid="time-picker-one-minute-box-41" label=":41"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="42"
|
|
anonid="time-picker-one-minute-box-42" label=":42"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="43"
|
|
anonid="time-picker-one-minute-box-43" label=":43"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="44"
|
|
anonid="time-picker-one-minute-box-44" label=":44"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="45"
|
|
anonid="time-picker-one-minute-box-45" label=":45"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="46"
|
|
anonid="time-picker-one-minute-box-46" label=":46"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="47"
|
|
anonid="time-picker-one-minute-box-47" label=":47"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="48"
|
|
anonid="time-picker-one-minute-box-48" label=":48"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="49"
|
|
anonid="time-picker-one-minute-box-49" label=":49"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="50"
|
|
anonid="time-picker-one-minute-box-50" label=":50"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="51"
|
|
anonid="time-picker-one-minute-box-51" label=":51"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="52"
|
|
anonid="time-picker-one-minute-box-52" label=":52"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="53"
|
|
anonid="time-picker-one-minute-box-53" label=":53"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="54"
|
|
anonid="time-picker-one-minute-box-54" label=":54"/>
|
|
</row>
|
|
<row flex="1">
|
|
<timepicker-minute class="time-picker-one-minute-class" value="55"
|
|
anonid="time-picker-one-minute-box-55" label=":55"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="56"
|
|
anonid="time-picker-one-minute-box-56" label=":56"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="57"
|
|
anonid="time-picker-one-minute-box-57" label=":57"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="58"
|
|
anonid="time-picker-one-minute-box-58" label=":58"/>
|
|
<timepicker-minute class="time-picker-one-minute-class" value="59"
|
|
anonid="time-picker-one-minute-box-59" label=":59"/>
|
|
</row>
|
|
</rows>
|
|
</grid> <!-- One Minute Grid -->
|
|
<hbox class="time-picker-minutes-bottom">
|
|
<spacer flex="1"/>
|
|
<label class="time-picker-more-control-label" value="«" onclick="clickLess()"/>
|
|
</hbox>
|
|
</vbox> <!-- One Minute Grid Box -->
|
|
</vbox> <!-- Box to hold time picker internals -->
|
|
</content>
|
|
|
|
<!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
|
|
<implementation>
|
|
<constructor>
|
|
<![CDATA[
|
|
// set by onPopupShowing
|
|
this.mPicker = null;
|
|
this.mPopup = null;
|
|
|
|
// The currently selected time
|
|
this.mSelectedTime = new Date();
|
|
// The selected hour and selected minute items
|
|
this.mSelectedHourItem = null;
|
|
this.mSelectedMinuteItem = null;
|
|
// constants use to specify one and five minute view
|
|
this.kMINUTE_VIEW_FIVE = 5;
|
|
this.kMINUTE_VIEW_ONE = 1;
|
|
]]>
|
|
</constructor>
|
|
|
|
<!-- Set up the picker, called when the popup pops -->
|
|
<method name="onPopupShowing">
|
|
<parameter name="aPicker"/>
|
|
<parameter name="aPopup"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.mPicker = aPicker;
|
|
this.mPopup = aPopup;
|
|
|
|
// if there is a Date object in the popup item's
|
|
// value attribute, use it, otherwise use Now.
|
|
var inputTime = this.mPicker.value;
|
|
if (inputTime) {
|
|
this.mSelectedTime = new Date(inputTime);
|
|
} else {
|
|
this.mSelectedTime = new Date();
|
|
}
|
|
|
|
// select the hour item
|
|
var hours24 = this.mSelectedTime.getHours();
|
|
var hourItemId = "time-picker-hour-box-" + hours24;
|
|
var hourItem = document.getAnonymousElementByAttribute(this, "anonid", hourItemId);
|
|
this.selectHourItem( hourItem );
|
|
|
|
// Show the five minute view if we are an even five minutes,
|
|
// otherwise one minute view
|
|
var minutesByFive = this.calcNearestFiveMinutes( this.mSelectedTime );
|
|
|
|
if (minutesByFive == this.mSelectedTime.getMinutes()) {
|
|
this.clickLess();
|
|
} else {
|
|
this.clickMore();
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="onPopupHiding">
|
|
<body>
|
|
<![CDATA[
|
|
if (this.hasChanged)
|
|
this.timeSelected();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Called when the more tab is clicked, and possibly at startup -->
|
|
<method name="clickMore">
|
|
<body>
|
|
<![CDATA[
|
|
// switch to one minute view
|
|
this.switchMinuteView( this.kMINUTE_VIEW_ONE );
|
|
|
|
// select minute box corresponding to the time
|
|
var minutes = this.mSelectedTime.getMinutes();
|
|
var oneMinuteItemId = "time-picker-one-minute-box-" + minutes;
|
|
var oneMinuteItem = document.getAnonymousElementByAttribute(this, "anonid", oneMinuteItemId);
|
|
this.selectMinuteItem(oneMinuteItem);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Called when the less tab is clicked, and possibly at startup -->
|
|
<method name="clickLess">
|
|
<body>
|
|
<![CDATA[
|
|
// switch to five minute view
|
|
this.switchMinuteView( this.kMINUTE_VIEW_FIVE );
|
|
|
|
// select closest five minute box,
|
|
// BUT leave the selected time at what may NOT be an even five minutes
|
|
// So that If they click more again the proper non-even-five minute
|
|
// box will be selected
|
|
var minutesByFive = this.calcNearestFiveMinutes(this.mSelectedTime);
|
|
var fiveMinuteItemId = "time-picker-five-minute-box-"+minutesByFive;
|
|
var fiveMinuteItem = document.getAnonymousElementByAttribute(this, "anonid", fiveMinuteItemId);
|
|
this.selectMinuteItem(fiveMinuteItem);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Called when one of the hour boxes is clicked -->
|
|
<method name="clickHour">
|
|
<parameter name="hourItem"/>
|
|
<parameter name="hourNumber"/>
|
|
<body>
|
|
<![CDATA[
|
|
// select the item
|
|
this.selectHourItem( hourItem );
|
|
|
|
// Change the hour in the selected time.
|
|
this.mSelectedTime.setHours( hourNumber );
|
|
|
|
this.hasChanged = true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Called when one of the hour boxes is double clicked
|
|
Sets the time to the selected hour, on the hour, and closes the popup -->
|
|
<method name="doubleClickHour">
|
|
<parameter name="hourItem"/>
|
|
<parameter name="hourNumber"/>
|
|
<body>
|
|
<![CDATA[
|
|
// set the minutes to :00
|
|
this.mSelectedTime.setMinutes(0);
|
|
|
|
// remove the popup grid
|
|
this.mPopup.hidePopup();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Called when one of the minute boxes is clicked,
|
|
Calls the client's onchange and Closes the popup -->
|
|
<method name="clickMinute">
|
|
<parameter name="minuteItem"/>
|
|
<parameter name="minuteNumber"/>
|
|
<body>
|
|
<![CDATA[
|
|
// set the minutes in the selected time
|
|
this.mSelectedTime.setMinutes(minuteNumber);
|
|
this.selectMinuteItem(minuteItem);
|
|
this.hasChanged = true;
|
|
// remove the popup grid
|
|
this.mPopup.hidePopup();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Called by clickMinute when one of the minute boxes is clicked,
|
|
Sets the value property and calls the client's onchange. -->
|
|
<method name="timeSelected">
|
|
<body>
|
|
<![CDATA[
|
|
// Copy picked time to avoid problems changing Date object in place
|
|
var pickedTime = new Date( this.mSelectedTime );
|
|
|
|
// put the picked time in the value property, and update
|
|
this.mPicker.update(pickedTime, true);
|
|
// select changed value so no cursor appears (can't type to it).
|
|
this.mPicker.select();
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Helper function to switch between "one" and "five" minute views -->
|
|
<method name="switchMinuteView">
|
|
<parameter name="view"/>
|
|
<body>
|
|
<![CDATA[
|
|
var fiveMinuteBox = document.getAnonymousElementByAttribute(this, "anonid", "time-picker-five-minute-grid-box");
|
|
var oneMinuteBox = document.getAnonymousElementByAttribute(this, "anonid", "time-picker-one-minute-grid-box");
|
|
|
|
if (view == this.kMINUTE_VIEW_ONE) {
|
|
fiveMinuteBox.setAttribute( "hidden", true );
|
|
oneMinuteBox.setAttribute( "hidden", false );
|
|
} else {
|
|
fiveMinuteBox.setAttribute( "hidden", false );
|
|
oneMinuteBox.setAttribute( "hidden", true );
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Helper function to select an hour item -->
|
|
<method name="selectHourItem">
|
|
<parameter name="hourItem"/>
|
|
<body>
|
|
<![CDATA[
|
|
// clear old selection, if there is one
|
|
if (this.mSelectedHourItem != null)
|
|
this.mSelectedHourItem.removeAttribute( "selected" );
|
|
// set selected attribute, to cause the selected style to apply
|
|
hourItem.setAttribute( "selected" , "true" );
|
|
// remember the selected item so we can deselect it
|
|
this.mSelectedHourItem = hourItem;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Helper function to select an minute item -->
|
|
<method name="selectMinuteItem">
|
|
<parameter name="minuteItem"/>
|
|
<body>
|
|
<![CDATA[
|
|
// clear old selection, if there is one
|
|
if (this.mSelectedMinuteItem != null)
|
|
this.mSelectedMinuteItem.removeAttribute( "selected" );
|
|
// set selected attribute, to cause the selected style to apply
|
|
minuteItem.setAttribute( "selected" , "true" );
|
|
// remember the selected item so we can deselect it
|
|
this.mSelectedMinuteItem = minuteItem;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
<method name="moveMinutes">
|
|
<parameter name="aNumber"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.mSelectedTime)
|
|
return;
|
|
|
|
var idPrefix = "time-picker-one-minute-box-";
|
|
|
|
// Everything above assumes that we are showing the one-minute-grid,
|
|
// If not, we need to do these corrections;
|
|
var fiveMinuteBox = document.getAnonymousElementByAttribute(
|
|
this, "anonid", "time-picker-five-minute-grid-box");
|
|
|
|
if (fiveMinuteBox.getAttribute("hidden") == 'false') {
|
|
aNumber *= 5;
|
|
idPrefix = "time-picker-five-minute-box-";
|
|
|
|
// If the detailed view was shown before, then mSelectedTime.getMinutes
|
|
// might not be a multiple of 5.
|
|
this.mSelectedTime.setMinutes(this.calcNearestFiveMinutes(this.mSelectedTime));
|
|
}
|
|
|
|
var newMinutes = this.mSelectedTime.getMinutes() + aNumber;
|
|
|
|
// Handle rollover cases
|
|
if (newMinutes < 0)
|
|
newMinutes += 60;
|
|
if (newMinutes > 59)
|
|
newMinutes -= 60;
|
|
|
|
this.mSelectedTime.setMinutes(newMinutes);
|
|
|
|
var minuteItemId = idPrefix + this.mSelectedTime.getMinutes();
|
|
var minuteItem = document.getAnonymousElementByAttribute(this, "anonid", minuteItemId);
|
|
|
|
this.selectMinuteItem(minuteItem);
|
|
this.mPicker.kTextBox.value = this.mPicker.formatTime(this.mSelectedTime);
|
|
this.hasChanged = true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
<method name="moveHours">
|
|
<parameter name="aNumber"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (!this.mSelectedTime)
|
|
return;
|
|
|
|
var newHours = this.mSelectedTime.getHours() + aNumber;
|
|
|
|
// Handle rollover cases
|
|
if (newHours < 0)
|
|
newHours += 24;
|
|
if (newHours > 23)
|
|
newHours -= 24;
|
|
|
|
this.mSelectedTime.setHours(newHours);
|
|
|
|
var hourItemId = "time-picker-hour-box-" + this.mSelectedTime.getHours();
|
|
var hourItem = document.getAnonymousElementByAttribute(this, "anonid", hourItemId);
|
|
|
|
this.selectHourItem(hourItem);
|
|
this.mPicker.kTextBox.value = this.mPicker.formatTime(this.mSelectedTime);
|
|
this.hasChanged = true;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Helper function to calulate the nearest even five minutes -->
|
|
<method name="calcNearestFiveMinutes">
|
|
<parameter name="time"/>
|
|
<body>
|
|
<![CDATA[
|
|
var minutes = time.getMinutes();
|
|
var minutesByFive = Math.round( minutes / 5 ) * 5;
|
|
|
|
if (minutesByFive > 59)
|
|
minutesByFive = 55;
|
|
return minutesByFive;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
|
|
<!-- ::::::::::::::::: HANDLERS ::::::::::::::::::::::::: -->
|
|
|
|
<handlers>
|
|
<handler event="bindingattached" action="this.initialize();"/>
|
|
</handlers>
|
|
|
|
</binding>
|
|
|
|
<binding id="datetimepicker" extends="chrome://calendar/content/datetimepickers/datetimepickers.xml#datetimepicker-base"
|
|
inherits="value,onchange,disabled,datepickerdisabled,timepickerdisabled">
|
|
<!-- ::::::::::::::::: CONTENT ::::::::::::::::::::::::: -->
|
|
<!-- onchange was simply "onDatePick()" in Moz1.6, but stopped working in Moz1.7
|
|
so had to add navigation by parents. -->
|
|
<content>
|
|
<xul:hbox flex="1" anonid="hbox">
|
|
<xul:datepicker anonid="date-picker"
|
|
onchange="this.parentNode.parentNode.onDatePick();"
|
|
xbl:inherits="value,disabled,disabled=datepickerdisabled"/>
|
|
<xul:timepicker anonid="time-picker"
|
|
onchange="this.parentNode.parentNode.onTimePick();"
|
|
xbl:inherits="value,disabled,disabled=timepickerdisabled"/>
|
|
</xul:hbox>
|
|
</content>
|
|
|
|
<!-- ::::::::::::::::: INTERFACE ::::::::::::::::::::::::: -->
|
|
<implementation>
|
|
<property name="datepickerdisabled"
|
|
onset="if (val) this.kDatePicker.setAttribute('disabled', 'true');
|
|
else this.kDatePicker.removeAttribute('disabled');"
|
|
onget="return this.kDatePicker.getAttribute('disabled')=='true';"/>
|
|
|
|
<!-- timepicker may be disabled alone for all day events -->
|
|
<property name="timepickerdisabled"
|
|
onset="if (val) this.kTimePicker.setAttribute('disabled', 'true');
|
|
else this.kTimePicker.removeAttribute('disabled');"
|
|
onget="return this.kTimePicker.getAttribute('disabled')=='true';"/>
|
|
|
|
<constructor>
|
|
<![CDATA[
|
|
this.kDatePicker =
|
|
document.getAnonymousElementByAttribute(this, "anonid", "date-picker");
|
|
this.kTimePicker =
|
|
document.getAnonymousElementByAttribute(this, "anonid", "time-picker");
|
|
|
|
var val = this.getAttribute("value");
|
|
this.mValue = (val ? new Date(val)
|
|
: new Date());
|
|
]]>
|
|
</constructor>
|
|
|
|
<method name="update">
|
|
<parameter name="aValue"/>
|
|
<parameter name="aRefresh"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aValue != null) {
|
|
this.mValue = aValue;
|
|
}
|
|
// set textBox.value property, not attribute
|
|
this.kDatePicker.update(this.mValue, aRefresh);
|
|
this.kTimePicker.update(this.mValue, aRefresh);
|
|
if (aRefresh) {
|
|
this.fireEvent("change");
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Date was changed by gui: update value. -->
|
|
<method name="onDatePick">
|
|
<body>
|
|
<![CDATA[
|
|
var tempTime;
|
|
if (this.kDatePicker.lastDateParseIncludedTime)
|
|
tempTime = new Date(this.kDatePicker.value);
|
|
else
|
|
tempTime = new Date(this.mValue);
|
|
var newDate = new Date(this.kDatePicker.value);
|
|
// Note: create new date because setting month and date of month in
|
|
// either order can lead to unexpected results (month may be advanced
|
|
// automatically if day of month is temporarily out of range).
|
|
var dateTime = new Date(newDate.getFullYear(),
|
|
newDate.getMonth(),
|
|
newDate.getDate(),
|
|
tempTime.getHours(),
|
|
tempTime.getMinutes(),
|
|
tempTime.getSeconds());
|
|
this.mValue = dateTime;
|
|
this.kTimePicker.update(dateTime, false);
|
|
this.fireEvent("change");
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Time was changed by gui: update value -->
|
|
<method name="onTimePick">
|
|
<body>
|
|
<![CDATA[
|
|
var dateTime = new Date(this.mValue);
|
|
var newTime = this.kTimePicker.value;
|
|
dateTime.setHours(newTime.getHours());
|
|
dateTime.setMinutes(newTime.getMinutes());
|
|
dateTime.setSeconds(newTime.getSeconds());
|
|
this.mValue = dateTime;
|
|
this.kDatePicker.update(dateTime, false);
|
|
this.fireEvent("change");
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
</implementation>
|
|
|
|
<!-- ::::::::::::::::: HANDLERS ::::::::::::::::::::::::: -->
|
|
|
|
<handlers>
|
|
<handler event="bindingattached" action="this.initialize();"/>
|
|
</handlers>
|
|
|
|
</binding>
|
|
|
|
<binding id="datetimepicker-base" extends="chrome://global/content/bindings/general.xml#basecontrol"
|
|
inherits="value,onchange">
|
|
<resources>
|
|
<stylesheet src="chrome://calendar/skin/datetimepickers/datetimepickers.css"/>
|
|
</resources>
|
|
<implementation>
|
|
<constructor>
|
|
<![CDATA[
|
|
this.initDateFormat();
|
|
this.initTimeFormat();
|
|
]]>
|
|
</constructor>
|
|
|
|
<property name="value"
|
|
onget="return this.mValue"
|
|
onset="this.update(val, false)"/>
|
|
|
|
<property name="lastDateParseIncludedTime"
|
|
onget="return this.mLastDateParseIncludedTime;"
|
|
onset="this.mLastDateParseIncludedTime = val;" />
|
|
|
|
<method name="update">
|
|
<parameter name="aValue"/>
|
|
<parameter name="aRefresh"/>
|
|
<body>
|
|
<![CDATA[
|
|
if (aValue != null) {
|
|
this.mValue = aValue;
|
|
}
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="fireEvent">
|
|
<parameter name="aEventName"/>
|
|
<parameter name="aDetail"/>
|
|
<body>
|
|
<![CDATA[
|
|
var event = document.createEvent('Events');
|
|
event.initEvent(aEventName, true, true);
|
|
event.detail = aDetail;
|
|
this.dispatchEvent(event);
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Parameter aValue may be a date or a date time. Dates are
|
|
read according to locale/OS setting (d-m-y or m-d-y or ...).
|
|
(see initDateFormat). Uses parseTime() for times.
|
|
-->
|
|
<method name="parseDateTime">
|
|
<parameter name="aValue"/>
|
|
<body>
|
|
<![CDATA[
|
|
this.mLastDateParseIncludedTime = false;
|
|
var tempDate = null;
|
|
if (!this.probeSucceeded)
|
|
return null; // avoid errors accessing uninitialized data.
|
|
|
|
var year = Number.MIN_VALUE; var month = -1; var day = -1; var timeString = null;
|
|
if (this.alphaMonths == null) {
|
|
// SHORT NUMERIC DATE, such as 2002-03-04, 4/3/2002, or CE2002Y03M04D.
|
|
// Made of digits & nonDigits. (Nondigits may be unicode letters
|
|
// which do not match \w, esp. in CJK locales.)
|
|
// (.*)? binds to null if no suffix.
|
|
var parseNumShortDateRegex = /^\D*(\d+)\D+(\d+)\D+(\d+)(.*)?$/;
|
|
var dateNumbersArray = parseNumShortDateRegex.exec(aValue);
|
|
if (dateNumbersArray != null) {
|
|
year = Number(dateNumbersArray[this.yearIndex]);
|
|
month = Number(dateNumbersArray[this.monthIndex]) - 1; // 0-based
|
|
day = Number(dateNumbersArray[this.dayIndex]);
|
|
timeString = dateNumbersArray[4];
|
|
}
|
|
} else {
|
|
// SHORT DATE WITH ALPHABETIC MONTH, such as "dd MMM yy" or "MMMM dd, yyyy"
|
|
// (\d+|[^\d\W]) is digits or letters, not both together.
|
|
// Allows 31dec1999 (no delimiters between parts) if OS does (w2k does not).
|
|
// Allows Dec 31, 1999 (comma and space between parts)
|
|
// (Only accepts ASCII month names; JavaScript RegExp does not have an
|
|
// easy way to describe unicode letters short of a HUGE character range
|
|
// regexp derived from the Alphabetic ranges in
|
|
// http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt)
|
|
// (.*)? binds to null if no suffix.
|
|
var parseAlphShortDateRegex = /^\s*(\d+|[^\d\W]+)\W{0,2}(\d+|[^\d\W]+)\W{0,2}(\d+|[^\d\W]+)(.*)?$/;
|
|
var datePartsArray = parseAlphShortDateRegex.exec(aValue);
|
|
if (datePartsArray != null) {
|
|
year = Number(datePartsArray[this.yearIndex]);
|
|
var monthString = datePartsArray[this.monthIndex].toUpperCase();
|
|
for (var m = 0; m < this.alphaMonths.length; m++) {
|
|
if (monthString == this.alphaMonths[m]) {
|
|
month = m;
|
|
break;
|
|
}
|
|
}
|
|
day = Number(datePartsArray[this.dayIndex]);
|
|
timeString = datePartsArray[4];
|
|
}
|
|
}
|
|
if (year != Number.MIN_VALUE && month != -1 && day != -1) {
|
|
// year, month, day successfully parsed
|
|
if (0 <= year && year < 100) {
|
|
// If 0 <= year < 100, treat as 2-digit year (like formatDate):
|
|
// parse year as up to 30 years in future or 69 years in past.
|
|
// (Covers 30-year mortgage and most working people's birthdate.)
|
|
// otherwise will be treated as four digit year.
|
|
var currentYear = new Date().getFullYear();
|
|
var currentCentury = currentYear - currentYear % 100;
|
|
year = currentCentury + year;
|
|
if (year < currentYear - 69)
|
|
year += 100;
|
|
if (year > currentYear + 30)
|
|
year -= 100;
|
|
}
|
|
// if time is also present, parse it
|
|
var hours = 0; var minutes = 0; var seconds = 0;
|
|
if (timeString != null) {
|
|
var time = this.parseTime(dateNumbersArray[4]);
|
|
if (time != null) {
|
|
hours = time.getHours();
|
|
minutes = time.getMinutes();
|
|
seconds = time.getSeconds();
|
|
this.mLastDateParseIncludedTime = true;
|
|
}
|
|
}
|
|
tempDate = new Date(year, month, day, hours, minutes, seconds, 0);
|
|
} //else did not match regex, not a valid date
|
|
return tempDate;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Parse a variety of time formats so that cut and paste is likely to work.
|
|
separator: ':' '.' ' ' symbol none
|
|
"12:34:56" "12.34.56" "12 34 56" "12h34m56s" "123456"
|
|
seconds optional: "02:34" "02.34" "02 34" "02h34m" "0234"
|
|
minutes optional: "12" "12" "12" "12h" "12"
|
|
1st hr digit optional:"9:34" " 9.34" "9 34" "9H34M" "934am"
|
|
skip nondigit prefix " 12:34" "t12.34" " 12 34" "T12H34M" "T0234"
|
|
am/pm optional "02:34 a.m.""02.34pm" "02 34 A M" "02H34M P.M." "0234pm"
|
|
am/pm prefix "a.m. 02:34""pm02.34" "A M 02 34" "P.M. 02H34M" "pm0234"
|
|
am/pm cyrillic "02:34\u0430.\u043c." "02 34 \u0420 \u041c"
|
|
am/pm arabic "\u063502:34" (RTL 02:34a) "\u0645 02.34" (RTL 02.34 p)
|
|
above/below noon "\u4e0a\u534802:34" "\u4e0b\u5348 02 34"
|
|
noon before/after "\u5348\u524d02:34" "\u5348\u5f8c 02 34"
|
|
-->
|
|
<method name="parseTime">
|
|
<parameter name="aValue"/>
|
|
<body>
|
|
<![CDATA[
|
|
// Try/catch this, since some people are apparently
|
|
// interested in using the datepicker in remote apps, where
|
|
// calGetString wouldn't exist
|
|
try {
|
|
var noon = calGetString("dateFormat", "noon");
|
|
if (aValue.toLowerCase() == noon.toLowerCase()) {
|
|
return new Date(0, 0, 0, 12, 0, 0, 0);
|
|
}
|
|
|
|
var midnight = calGetString("dateFormat", "midnight");
|
|
if (aValue.toLowerCase() == midnight.toLowerCase()) {
|
|
return new Date(0, 0, 0, 0, 0, 0, 0);
|
|
}
|
|
} catch(ex) {
|
|
}
|
|
|
|
var time = null;
|
|
var timePartsArray = this.parseTimeRegExp.exec(aValue);
|
|
const PRE_INDEX=1, HR_INDEX=2, MIN_INDEX=4, SEC_INDEX=6, POST_INDEX=8;
|
|
|
|
if (timePartsArray != null) {
|
|
var hoursString = timePartsArray[HR_INDEX]
|
|
var hours = Number(hoursString);
|
|
var hoursSuffix = timePartsArray[HR_INDEX + 1];
|
|
if (!(0 <= hours && hours < 24)) return null;
|
|
|
|
var minutesString = timePartsArray[MIN_INDEX];
|
|
var minutes = (minutesString == null? 0 : Number(minutesString));
|
|
var minutesSuffix = timePartsArray[MIN_INDEX + 1];
|
|
if (!(0 <= minutes && minutes < 60)) return null;
|
|
|
|
var secondsString = timePartsArray[SEC_INDEX];
|
|
var seconds = (secondsString == null? 0 : Number(secondsString));
|
|
var secondsSuffix = timePartsArray[SEC_INDEX + 1];
|
|
if (!(0 <= seconds && seconds < 60)) return null;
|
|
|
|
var ampmCode = null;
|
|
if (timePartsArray[PRE_INDEX] || timePartsArray[POST_INDEX]) {
|
|
if (this.ampmIndex && timePartsArray[this.ampmIndex]) {
|
|
// try current format order first
|
|
var ampmString = timePartsArray[this.ampmIndex];
|
|
if (this.amRegExp.test(ampmString)) {
|
|
ampmCode = "AM";
|
|
} else if (this.pmRegExp.test(ampmString)) {
|
|
ampmCode = "PM";
|
|
}
|
|
}
|
|
if (ampmCode == null) { // not yet found
|
|
// try any format order
|
|
var preString = timePartsArray[PRE_INDEX];
|
|
var postString = timePartsArray[POST_INDEX];
|
|
if ((preString && this.amRegExp.test(preString)) ||
|
|
(postString && this.amRegExp.test(postString))) {
|
|
ampmCode = "AM";
|
|
} else if ((preString && this.pmRegExp.test(preString)) ||
|
|
(postString && this.pmRegExp.test(postString))) {
|
|
ampmCode = "PM";
|
|
} // else no match, ignore and treat as 24hour time.
|
|
}
|
|
}
|
|
if (ampmCode == "AM") {
|
|
if (hours == 12)
|
|
hours = 0;
|
|
} else if (ampmCode == "PM") {
|
|
if (hours < 12)
|
|
hours += 12;
|
|
}
|
|
time = new Date(0, 0, 0, hours, minutes, seconds, 0);
|
|
} // else did not match regex, not valid time
|
|
return time;
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="initDateFormat">
|
|
<body>
|
|
<![CDATA[
|
|
// probe the dateformat
|
|
this.yearIndex = -1;
|
|
this.monthIndex = -1;
|
|
this.dayIndex = -1;
|
|
this.twoDigitYear = false;
|
|
this.alphaMonths = null;
|
|
this.probeSucceeded = false;
|
|
this.mLastDateParseIncludedTime = false;
|
|
|
|
// SHORT NUMERIC DATE, such as 2002-03-04, 4/3/2002, or CE2002Y03M04D.
|
|
// Made of digits & nonDigits. (Nondigits may be unicode letters
|
|
// which do not match \w, esp. in CJK locales.)
|
|
this.parseShortDateRegex = /^\D*(\d+)\D+(\d+)\D+(\d+)\D?$/;
|
|
var probeDate = new Date(2002,3-1,4); // month is 0-based
|
|
var probeString = this.formatDate(probeDate);
|
|
var probeArray = this.parseShortDateRegex.exec(probeString);
|
|
if (probeArray != null) {
|
|
// Numeric month format
|
|
for (var i = 1; i <= 3; i++) {
|
|
switch (Number(probeArray[i])) {
|
|
case 02: this.twoDigitYear = true; // fall thru
|
|
case 2002: this.yearIndex = i; break;
|
|
case 3: this.monthIndex = i; break;
|
|
case 4: this.dayIndex = i; break;
|
|
}
|
|
}
|
|
// All three indexes are set (not -1) at this point.
|
|
this.probeSucceeded = true;
|
|
} else {
|
|
// SHORT DATE WITH ALPHABETIC MONTH, such as "dd MMM yy" or "MMMM dd, yyyy"
|
|
// (\d+|[^\d\W]) is digits or letters, not both together.
|
|
// Allows 31dec1999 (no delimiters between parts) if OS does (w2k does not).
|
|
// Allows Dec 31, 1999 (comma and space between parts)
|
|
// (Only accepts ASCII month names; JavaScript RegExp does not have an
|
|
// easy way to describe unicode letters short of a HUGE character range
|
|
// regexp derived from the Alphabetic ranges in
|
|
// http://www.unicode.org/Public/UNIDATA/DerivedCoreProperties.txt)
|
|
this.parseShortDateRegex = /^\s*(\d+|[^\d\W]+)\W{0,2}(\d+|[^\d\W]+)\W{0,2}(\d+|[^\d\W]+)\s*$/;
|
|
probeArray = this.parseShortDateRegex.exec(probeString);
|
|
if (probeArray != null) {
|
|
for (var j = 1; j <= 3; j++) {
|
|
switch (Number(probeArray[j])) {
|
|
case 02: this.twoDigitYear = true; // fall thru
|
|
case 2002: this.yearIndex = j; break;
|
|
case 4: this.dayIndex = j; break;
|
|
default: this.monthIndex = j; break;
|
|
}
|
|
}
|
|
if (this.yearIndex != -1 && this.dayIndex != -1 && this.monthIndex != -1) {
|
|
this.probeSucceeded = true;
|
|
// Fill this.alphaMonths with month names.
|
|
this.alphaMonths = new Array(12);
|
|
for (var m = 0; m < 12; m++) {
|
|
probeDate.setMonth(m);
|
|
probeString = this.formatDate(probeDate);
|
|
probeArray = this.parseShortDateRegex.exec(probeString);
|
|
if (probeArray != null)
|
|
this.alphaMonths[m] = probeArray[this.monthIndex].toUpperCase();
|
|
else
|
|
this.probeSucceeded = false;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (! this.probeSucceeded)
|
|
dump("\nOperating system short date format is not recognized: "+probeString+"\n");
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<!-- Time format in 24-hour format or 12-hour format with am/pm string.
|
|
Should match formats
|
|
HH:mm, H:mm, HH:mm:ss, H:mm:ss
|
|
hh:mm tt, h:mm tt, hh:mm:ss tt, h:mm:ss tt
|
|
tt hh:mm, tt h:mm, tt hh:mm:ss, tt h:mm:ss
|
|
where
|
|
HH is 24 hour digits, with leading 0. H is 24 hour digits, no leading 0.
|
|
hh is 12 hour digits, with leading 0. h is 12 hour digits, no leading 0.
|
|
mm and ss are is minutes and seconds digits, with leading 0.
|
|
tt is localized AM or PM string.
|
|
':' may be ':' or a units marker such as 'h', 'm', or 's' in 15h12m00s
|
|
or may be omitted as in 151200.
|
|
-->
|
|
<method name="initTimeFormat">
|
|
<body>
|
|
<![CDATA[
|
|
// probe the Time format
|
|
this.ampmIndex = null;
|
|
// Digits HR sep MIN sep SEC sep
|
|
// Index: 2 3 4 5 6 7
|
|
var digitsExpr = "(\\d?\\d)(\\D)?(?:(\\d\\d)(\\D)?(?:(\\d\\d)(\\D)?)?)?";
|
|
// any letters or '.': non-digit alphanumeric, period (a.m.), or space (P M)
|
|
var anyAmPmExpr = "(?:[^\\d\\W]|[. ])+";
|
|
// digitsExpr has 6 captures, so index of first ampmExpr is 1, of last is 8.
|
|
var probeTimeRegExp =
|
|
new RegExp("^("+anyAmPmExpr+")?\\s?"+digitsExpr+"("+anyAmPmExpr+")?\\s*$");
|
|
const PRE_INDEX=1, HR_INDEX=2, MIN_INDEX=4, SEC_INDEX=6, POST_INDEX=8;
|
|
var amProbeTime = new Date(2000,0,1,6,12,34);
|
|
var amProbeString = amProbeTime.toLocaleFormat("%X");
|
|
var pmProbeTime = new Date(2000,0,1,18,12,34);
|
|
var pmProbeString = pmProbeTime.toLocaleFormat("%X");
|
|
var amFormatExpr = null, pmFormatExpr = null;
|
|
if (amProbeString != pmProbeString) {
|
|
var amProbeArray = probeTimeRegExp.exec(amProbeString);
|
|
var pmProbeArray = probeTimeRegExp.exec(pmProbeString);
|
|
if (amProbeArray != null && pmProbeArray != null) {
|
|
if (amProbeArray[PRE_INDEX] && pmProbeArray[PRE_INDEX] &&
|
|
amProbeArray[PRE_INDEX] != pmProbeArray[PRE_INDEX]) {
|
|
this.ampmIndex = PRE_INDEX;
|
|
} else if (amProbeArray[POST_INDEX] && pmProbeArray[POST_INDEX]) {
|
|
if (amProbeArray[POST_INDEX] != pmProbeArray[POST_INDEX]) {
|
|
this.ampmIndex = POST_INDEX;
|
|
} else {
|
|
// check if need to append previous character,
|
|
// captured by the optional separator pattern after seconds digits,
|
|
// or after minutes if no seconds, or after hours if no minutes.
|
|
for (var k = SEC_INDEX; k >= HR_INDEX; k -= 2) {
|
|
var nextSepI = k + 1;
|
|
var nextDigitsI = k + 2;
|
|
if ((k == SEC_INDEX ||
|
|
(!amProbeArray[nextDigitsI] && !pmProbeArray[nextDigitsI]))
|
|
&& amProbeArray[nextSepI] && pmProbeArray[nextSepI]
|
|
&& amProbeArray[nextSepI] != pmProbeArray[nextSepI])
|
|
{
|
|
amProbeArray[POST_INDEX] =
|
|
amProbeArray[nextSepI] + amProbeArray[POST_INDEX];
|
|
pmProbeArray[POST_INDEX] =
|
|
pmProbeArray[nextSepI] + pmProbeArray[POST_INDEX];
|
|
this.ampmIndex = POST_INDEX;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (this.ampmIndex) {
|
|
var makeFormatRegExp = function(string) {
|
|
// make expr to accept either as provided, lowercased, or uppercased
|
|
var regExp = string.replace(/(\W)/g, "[$1]"); // escape punctuation
|
|
var lowercased = string.toLowerCase();
|
|
if (string != lowercased)
|
|
regExp += "|"+lowercased;
|
|
var uppercased = string.toUpperCase();
|
|
if (string != uppercased)
|
|
regExp += "|"+uppercased;
|
|
return regExp;
|
|
};
|
|
amFormatExpr = makeFormatRegExp(amProbeArray[this.ampmIndex]);
|
|
pmFormatExpr = makeFormatRegExp(pmProbeArray[this.ampmIndex]);
|
|
}
|
|
}
|
|
}
|
|
// International formats ([roman, cyrillic]|arabic|chinese/kanji characters)
|
|
// covering languages of U.N. (en,fr,sp,ru,ar,zh) and G8 (en,fr,de,it,ru,ja).
|
|
// See examples at parseTimeOfDay.
|
|
var amExpr =
|
|
"[Aa\u0410\u0430][. ]?[Mm\u041c\u043c][. ]?|\u0635|\u4e0a\u5348|\u5348\u524d";
|
|
var pmExpr =
|
|
"[Pp\u0420\u0440][. ]?[Mm\u041c\u043c][. ]?|\u0645|\u4e0b\u5348|\u5348\u5f8c";
|
|
if (this.ampmIndex){
|
|
amExpr = amFormatExpr+"|"+amExpr;
|
|
pmExpr = pmFormatExpr+"|"+pmExpr;
|
|
}
|
|
var ampmExpr = amExpr+"|"+pmExpr;
|
|
// Must build am/pm formats into parse time regexp so that it can
|
|
// match them without mistaking the initial char for an optional divider.
|
|
// (For example, want to be able to parse both "12:34pm" and
|
|
// "12H34M56Spm" for any characters H,M,S and any language's "pm".
|
|
// The character between the last digit and the "pm" is optional.
|
|
// Must recogize "pm" directly, otherwise in "12:34pm" the "S" pattern
|
|
// matches the "p" character so only "m" is matched as ampm suffix.)
|
|
//
|
|
// digitsExpr has 6 captures, so index of first ampmExpr is 1, of last is 8.
|
|
this.parseTimeRegExp =
|
|
new RegExp("("+ampmExpr+")?\\s?"+digitsExpr+"("+ampmExpr+")?\\s*$");
|
|
this.amRegExp = new RegExp("^(?:"+amExpr+")$");
|
|
this.pmRegExp = new RegExp("^(?:"+pmExpr+")$");
|
|
// build time display format that mimics "%x" format without seconds
|
|
var ampmSep = (pmProbeString.indexOf(' ') != -1? " " : "");
|
|
if (this.ampmIndex == PRE_INDEX)
|
|
this.kTimeFormatString = "%p" + ampmSep + "%I:%M";
|
|
else if (this.ampmIndex == POST_INDEX)
|
|
this.kTimeFormatString = "%I:%M" + ampmSep + "%p";
|
|
else
|
|
this.kTimeFormatString = "%H:%M";
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="formatDate">
|
|
<parameter name="aDate"/>
|
|
<body>
|
|
<![CDATA[
|
|
// workaround for bugs 354073/327869:
|
|
// aDate.toLocaleFormat("%x") exits application
|
|
// if year before 1900 (e.g., typo) on MSWindows (due to MS bug).
|
|
var nsIScriptableDateFormat =
|
|
Components.interfaces.nsIScriptableDateFormat;
|
|
var dateService =
|
|
Components.classes["@mozilla.org/intl/scriptabledateformat;1"]
|
|
.getService(nsIScriptableDateFormat);
|
|
return dateService.FormatDate("", dateService.dateFormatShort,
|
|
aDate.getFullYear(),
|
|
aDate.getMonth()+1,
|
|
aDate.getDate());
|
|
]]>
|
|
</body>
|
|
</method>
|
|
|
|
<method name="formatTime">
|
|
<parameter name="aValue"/>
|
|
<body>
|
|
<![CDATA[
|
|
// remove leading zero in hours before colon in xx01:23pmxx
|
|
return aValue.toLocaleFormat(this.kTimeFormatString).replace(/0(\d):/,"$1:");
|
|
]]>
|
|
</body>
|
|
</method>
|
|
</implementation>
|
|
</binding>
|
|
|
|
</bindings>
|
|
|