releases-comm-central/calendar/base/content/calendar-task-tree.xml

1171 строка
47 KiB
XML

<?xml version="1.0"?>
<!-- ***** 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>
- Chris Charabaruk <coldacid@meldstar.com>
- Colin Phillips <colinp@oeone.com>
- ArentJan Banck <ajbanck@planet.nl>
- Curtis Jewell <csjewell@mail.freeshell.org>
- Eric Belhaire <eric.belhaire@ief.u-psud.fr>
- Mark Swaffer <swaff@fudo.org>
- Michael Buettner <michael.buettner@sun.com>
- Philipp Kewisch <mozilla@kewis.ch>
- Lars Wohlfahrt <thetux.moz@googlemail.com>
- Fred Jendrzejewski <fred.jen@web.de>
-
- 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 ***** -->
<!DOCTYPE dialog [
<!ENTITY % dtd1 SYSTEM "chrome://calendar/locale/global.dtd" > %dtd1;
<!ENTITY % dtd2 SYSTEM "chrome://calendar/locale/calendar.dtd" > %dtd2;
]>
<bindings id="calendar-task-tree-bindings"
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="calendar-task-tree">
<resources>
<stylesheet src="chrome://calendar/skin/calendar-task-tree.css"/>
</resources>
<content>
<xul:tree anonid="calendar-task-tree"
class="calendar-task-tree"
flex="1"
enableColumnDrag="false">
<xul:treecols anonid="calendar-task-tree-cols">
<xul:treecol anonid="calendar-task-tree-col-completed"
class="calendar-task-tree-col-completed"
minwidth="19"
fixed="true"
cycler="true"
sortKey="completedDate"
itemproperty="completed"
label="&calendar.unifinder.tree.done.label;">
<xul:image anonid="checkboximg" />
</xul:treecol>
<xul:splitter class="tree-splitter" ordinal="2"/>
<xul:treecol anonid="calendar-task-tree-col-priority"
class="calendar-task-tree-col-priority"
minwidth="17"
fixed="true"
itemproperty="priority"
label="&calendar.unifinder.tree.priority.label;">
<xul:image anonid="priorityimg"/>
</xul:treecol>
<xul:splitter class="tree-splitter" ordinal="4"/>
<xul:treecol anonid="calendar-task-tree-col-title"
flex="1"
itemproperty="title"
label="&calendar.unifinder.tree.title.label;"/>
<xul:splitter class="tree-splitter" ordinal="6"/>
<xul:treecol anonid="calendar-task-tree-col-entrydate"
itemproperty="entryDate"
flex="1" label="&calendar.unifinder.tree.startdate.label;"/>
<xul:splitter class="tree-splitter" ordinal="8"/>
<xul:treecol anonid="calendar-task-tree-col-duedate"
itemproperty="dueDate"
flex="1" label="&calendar.unifinder.tree.duedate.label;"/>
<xul:splitter class="tree-splitter" ordinal="10"/>
<xul:treecol anonid="calendar-task-tree-col-duration"
sortKey="dueDate"
itemproperty="duration"
flex="1" label="&calendar.unifinder.tree.duration.label;"/>
<xul:splitter class="tree-splitter" ordinal="12"/>
<xul:treecol anonid="calendar-task-tree-col-completeddate"
itemproperty="completedDate"
flex="1" label="&calendar.unifinder.tree.completeddate.label;"/>
<xul:splitter class="tree-splitter" ordinal="14"/>
<xul:treecol anonid="calendar-task-tree-col-percentcomplete"
flex="1"
type="progressmeter"
minwidth="19"
itemproperty="percentComplete"
label="&calendar.unifinder.tree.percentcomplete.label;"/>
<xul:splitter class="tree-splitter" ordinal="16"/>
<xul:treecol anonid="calendar-task-tree-col-categories"
itemproperty="categories"
flex="1" label="&calendar.unifinder.tree.categories.label;"/>
<xul:splitter class="tree-splitter" ordinal="18"/>
<xul:treecol anonid="calendar-task-tree-col-location"
itemproperty="location"
label="&calendar.unifinder.tree.location.label;"/>
<xul:splitter class="tree-splitter" ordinal="20"/>
<xul:treecol anonid="calendar-task-tree-col-status"
flex="1"
itemproperty="status"
label="&calendar.unifinder.tree.status.label;"/>
<xul:splitter class="tree-splitter" ordinal="22"/>
<xul:treecol anonid="calendar-task-tree-col-calendarname"
flex="1"
itemproperty="calendar"
label="&calendar.unifinder.tree.calendarname.label;"/>
</xul:treecols>
<xul:treechildren tooltip="taskTreeTooltip"/>
</xul:tree>
</content>
<implementation implements="nsIObserver">
<constructor><![CDATA[
Components.utils.import("resource://gre/modules/PluralForm.jsm");
let self = this;
// set up the tree filter
this.mFilter = new calFilter();
// set up the custom tree view
let tree = document.getAnonymousElementByAttribute(this, "anonid", "calendar-task-tree");
this.mTreeView.tree = tree;
tree.view = this.mTreeView;
// set up our calendar event observer
let composite = getCompositeCalendar();
composite.addObserver(this.mTaskTreeObserver);
// set up the preference observer
let prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
let branch = prefService.getBranch("")
.QueryInterface(Components.interfaces.nsIPrefBranch2);
branch.addObserver("calendar.", this, false);
// we want to make several attributes on the column
// elements persistent, but unfortunately there's no
// relyable way with the 'persist' feature.
// that's why we need to store the necessary bits and
// pieces at the element this binding is attached to.
let names = this.getAttribute("visible-columns").split(' ');
let ordinals = this.getAttribute("ordinals").split(' ');
let widths = this.getAttribute("widths").split(' ');
let sorted = this.getAttribute("sort-active");
let sortDirection = this.getAttribute("sort-direction") || "ascending";
let tree = document.getAnonymousNodes(this)[0];
let treecols = tree.getElementsByTagNameNS(tree.namespaceURI, "treecol");
for (let i = 0; i < treecols.length; i++) {
let content = treecols[i].getAttribute("itemproperty");
if (names.some(
function(element) {
return (element == content);
})) {
treecols[i].removeAttribute("hidden");
} else {
treecols[i].setAttribute("hidden","true");
}
if (ordinals && ordinals.length > 0) {
treecols[i].ordinal = Number(ordinals.shift());
}
if (widths && widths.length > 0) {
treecols[i].width = Number(widths.shift());
}
if (sorted && sorted.length > 0) {
if (sorted == content) {
this.mTreeView.sortDirection = sortDirection;
this.mTreeView.selectedColumn = treecols[i];
}
}
}
]]></constructor>
<destructor><![CDATA[
// remove composite calendar observer
let composite = getCompositeCalendar();
composite.removeObserver(this.mTaskTreeObserver);
// remove the preference observer
let prefService = Components.classes["@mozilla.org/preferences-service;1"]
.getService(Components.interfaces.nsIPrefService);
let branch = prefService.getBranch("")
.QueryInterface(Components.interfaces.nsIPrefBranch2);
branch.removeObserver("calendar.", this, false);
let widths = "";
let ordinals = "";
let visible = "";
let sorted = this.mTreeView.selectedColumn;
let tree = document.getAnonymousNodes(this)[0];
let treecols = tree.getElementsByTagNameNS(tree.namespaceURI, "treecol");
for (let i = 0; i < treecols.length; i++) {
if (treecols[i].getAttribute("hidden") != "true") {
let content = treecols[i].getAttribute("itemproperty");
visible += (visible.length > 0) ? " "+content : content;
}
if(ordinals.length > 0)
ordinals += " ";
ordinals += treecols[i].ordinal;
if(widths.length > 0)
widths += " ";
widths += treecols[i].width || 0;
}
this.setAttribute("visible-columns",visible);
this.setAttribute("ordinals",ordinals);
this.setAttribute("widths",widths);
if (sorted) {
this.setAttribute("sort-active",sorted.getAttribute("itemproperty"));
this.setAttribute("sort-direction",this.mTreeView.sortDirection);
} else {
this.removeAttribute("sort-active");
this.removeAttribute("sort-direction");
}
]]></destructor>
<field name="mTaskArray">[]</field>
<field name="mHash2Index"><![CDATA[({})]]></field>
<field name="mRefreshQueue">[]</field>
<field name="mPendingRefresh">null</field>
<field name="mShowCompletedTasks">true</field>
<field name="mFilter">null</field>
<field name="mStartDate">null</field>
<field name="mEndDate">null</field>
<property name="currentIndex">
<getter><![CDATA[
var tree = document.getAnonymousElementByAttribute(
this, "anonid", "calendar-task-tree");
return tree.currentIndex;
]]></getter>
</property>
<property name="currentTask">
<getter><![CDATA[
let tree = document.getAnonymousElementByAttribute(
this, "anonid", "calendar-task-tree");
let index = tree.currentIndex;
if (tree.view && tree.view.selection) {
// If the current index is not selected, then ignore
index = (tree.view.selection.isSelected(index) ? index : -1);
}
return (index < 0) ? null : this.mTaskArray[index];
]]></getter>
</property>
<property name="selectedTasks" readonly="true">
<getter><![CDATA[
var tasks = [];
var start = {};
var end = {};
if (!this.mTreeView.selection) {
return tasks;
}
var rangeCount = this.mTreeView.selection.getRangeCount();
for (var range = 0; range < rangeCount; range++) {
this.mTreeView.selection.getRangeAt(range, start, end);
for (var i = start.value; i <= end.value; i++) {
var task = this.getTaskAtRow(i);
if (task) {
tasks.push(this.getTaskAtRow(i));
}
}
}
return tasks;
]]></getter>
</property>
<property name="showCompleted">
<getter><![CDATA[
return this.mShowCompletedTasks;
]]></getter>
<setter><![CDATA[
this.mShowCompletedTasks = val;
return val;
]]></setter>
</property>
<method name="duration">
<parameter name="aTask"/>
<body><![CDATA[
if (aTask && aTask.dueDate && aTask.dueDate.isValid){
var dur = aTask.dueDate.subtractDate(now());
if (!dur.isNegative) {
var minutes = Math.ceil(dur.inSeconds / 60);
if (minutes >= 1440) { // 1 day or more
let dueIn = PluralForm.get(dur.days, calGetString("calendar", "dueInDays"));
return dueIn.replace("#1", dur.days);
} else if (minutes >= 60) { // 1 hour or more
let dueIn = PluralForm.get(dur.hours, calGetString("calendar", "dueInHours"));
return dueIn.replace("#1", dur.hours);
} else {
// Less than one hour
return calGetString("calendar", "dueInLessThanOneHour");
}
}
}
return null;
]]></body>
</method>
<method name="getTaskAtRow">
<parameter name="aRow"/>
<body><![CDATA[
return (aRow > -1 ? this.mTaskArray[aRow] : null);
]]></body>
</method>
<method name="getTaskFromEvent">
<parameter name="aEvent"/>
<body><![CDATA[
return this.mTreeView._getItemFromEvent(aEvent);
]]></body>
</method>
<field name="mTreeView"><![CDATA[
({
/**
* Attributes
*/
// back reference to the binding
binding: this,
tree: null,
treebox: null,
mSelectedColumn: null,
sortDirection: null,
get selectedColumn tTV_getSelectedColumn() {
return this.mSelectedColumn;
},
set selectedColumn tTV_setSelectedColumn(aCol) {
var tree = document.getAnonymousNodes(this.binding)[0];
var treecols = tree.getElementsByTagNameNS(tree.namespaceURI, "treecol");
for (var i = 0; i < treecols.length; i++) {
var col = treecols[i];
if (col.getAttribute("sortActive")) {
col.removeAttribute("sortActive");
col.removeAttribute("sortDirection");
}
if (aCol.getAttribute("itemproperty") == col.getAttribute("itemproperty")) {
col.setAttribute("sortActive", "true");
col.setAttribute("sortDirection", this.sortDirection);
}
}
return (this.mSelectedColumn = aCol);
},
/**
* High-level task tree manipulation
*/
addItem: function tTV_addItem(aItem, aDontSort) {
if (aItem.isCompleted && !this.binding.showCompleted) {
return;
}
var index = this.binding.mHash2Index[aItem.hashId];
if (index === undefined) {
var index = this.binding.mTaskArray.length;
this.binding.mTaskArray.push(aItem);
this.binding.mHash2Index[aItem.hashId] = index;
// The rowCountChanged function takes two arguments, the index where the
// first row was inserted and the number of rows to insert.
this.treebox.rowCountChanged(index, 1);
this.tree.view.selection.select(index);
}
this.treebox.ensureRowIsVisible(this.rowCount - 1);
if(aDontSort) {
this.binding.recreateHashTable();
} else {
this.binding.sortItems();
}
},
removeItem: function tTV_removeItem(aItem, aDontSort) {
var index = this.binding.mHash2Index[aItem.hashId];
if (index != undefined) {
delete this.binding.mHash2Index[aItem.hashId];
this.binding.mTaskArray.splice(index, 1);
this.treebox.rowCountChanged(index, -1);
if (index == this.rowCount) {
index--;
}
this.tree.view.selection.select(index);
this.binding.recreateHashTable();
}
},
modifyItem: function tTV_modifyItem(aNewItem, aOldItem, aDontSort) {
var index = this.binding.mHash2Index[aOldItem.hashId];
if (index != undefined) {
// if a filter is installed we need to make sure that
// the item still belongs to the set of valid items before
// moving forward. if the filter cuts this item off, we
// need to act accordingly.
if (!this.binding.mFilter.isItemInFilters(aNewItem)) {
this.removeItem(aNewItem);
return;
}
// same holds true for the completed filter, which is
// currently modeled as an explicit boolean.
if (aNewItem.isCompleted != aOldItem.isCompleted) {
if (aNewItem.isCompleted && !this.binding.showCompleted) {
this.removeItem(aNewItem);
return;
}
}
delete this.binding.mHash2Index[aOldItem.hashId];
this.binding.mHash2Index[aNewItem.hashId] = index;
this.binding.mTaskArray[index] = aNewItem;
this.tree.view.selection.select(index);
if(aDontSort) {
this.treebox.invalidateRow(index);
} else {
this.binding.sortItems();
}
}
},
clear: function tTV_clear() {
var count = this.binding.mTaskArray.length;
if (count > 0) {
this.binding.mTaskArray = [];
this.binding.mHash2Index = {};
this.treebox.rowCountChanged(0, -count);
this.tree.view.selection.clearSelection();
}
},
updateItem: function tTV_updateItem(aItem) {
var index = this.binding.mHash2Index[aItem.hashId];
if (index) {
this.treebox.invalidateRow(index);
}
},
/**
* nsITreeView methods and properties
*/
get rowCount() {
return this.binding.mTaskArray.length;
},
// TODO this code is currently identical to the unifinder. We should
// create an itemTreeView that these tree views can inherit, that
// contains this code, and possibly other code related to sorting and
// storing items. See bug 432582 for more details.
getCellProperties: function mTV_getCellProperties(aRow, aCol, aProps) {
this.getRowProperties(aRow, aProps);
this.getColumnProperties(aCol, aProps);
},
// Called to get properties to paint a column background.
// For shading the sort column, etc.
getColumnProperties: function mTV_getColumnProperties(aCol, aProps) {
if (aCol.element.hasAttribute("anonid")) {
aProps.AppendElement(getAtomFromService(aCol.element.getAttribute("anonid")));
}
},
getRowProperties: function mTV_getRowProperties(aRow, aProps) {
var item = this.binding.mTaskArray[aRow];
if (item.priority > 0 && item.priority < 5) {
aProps.AppendElement(getAtomFromService("highpriority"));
} else if (item.priority > 5 && item.priority < 10) {
aProps.AppendElement(getAtomFromService("lowpriority"));
}
aProps.AppendElement(getAtomFromService(getProgressAtom(item)));
// Add calendar name and id atom
var calendarNameAtom = "calendar-" + formatStringForCSSRule(item.calendar.name);
var calendarIdAtom = "calendarid-" + item.calendar.id;
aProps.AppendElement(getAtomFromService(calendarNameAtom));
aProps.AppendElement(getAtomFromService(calendarIdAtom));
// Add item status atom
if (item.status) {
aProps.AppendElement(getAtomFromService("status-" + item.status.toLowerCase()));
}
// Alarm status atom
if (item.getAlarms({}).length) {
aProps.AppendElement(getAtomFromService("alarm"));
}
// Task categories
var categories = item.getCategories({});
categories.map(formatStringForCSSRule)
.map(getAtomFromService)
.forEach(aProps.AppendElement, aProps);
},
// Called on the view when a cell in a non-selectable cycling
// column (e.g., unread/flag/etc.) is clicked.
cycleCell: function mTV_cycleCell(aRow, aCol) {
var task = this.binding.mTaskArray[aRow];
if(!task)
return;
if (aCol != null) {
var content = aCol.element.getAttribute("itemproperty");
if (content == "completed") {
var newTask = task.clone().QueryInterface(Components.interfaces.calITodo);
newTask.isCompleted = !task.completedDate;
doTransaction('modify', newTask, newTask.calendar, task, null);
}
}
},
// Called on the view when a header is clicked.
cycleHeader: function mTV_cycleHeader(aCol) {
if (!this.selectedColumn) {
this.sortDirection = "ascending";
}
else {
if(!this.sortDirection || this.sortDirection == "descending") {
this.sortDirection = "ascending";
} else {
this.sortDirection = "descending";
}
}
this.selectedColumn = aCol.element;
let selectedItems = this.binding.selectedTasks;
this.binding.sortItems();
if (selectedItems != undefined) {
this.tree.view.selection.clearSelection();
for each (item in selectedItems){
let index = this.binding.mHash2Index[item.hashId];
this.tree.view.selection.toggleSelect(index);
}
}
},
// The text for a given cell. If a column consists only of an
// image, then the empty string is returned.
getCellText: function mTV_getCellText(aRow, aCol) {
var task = this.binding.mTaskArray[aRow];
if (!task)
return false;
switch (aCol.element.getAttribute("itemproperty")) {
case "title":
// return title, or "Untitled" if empty/null
return task.title || calGetString("calendar", "eventUntitled");
case "entryDate":
return this._formatDateTime(task.entryDate);
case "dueDate":
return this._formatDateTime(task.dueDate);
case "completedDate":
return this._formatDateTime(task.completedDate);
case "percentComplete":
return (task.percentComplete > 0 ? task.percentComplete + "%" : "");
case "categories":
return task.getCategories({}).join(", "); // TODO l10n-unfriendly
case "location":
return task.getProperty("LOCATION");
case "status":
return getToDoStatusString(task);
case "calendar":
return task.calendar.name;
case "duration":
return this.binding.duration(task);
case "completed":
case "priority":
default:
return "";
}
},
// This method is only called for columns of type other than text.
getCellValue: function mTV_getCellValue(aRow, aCol) {
var task = this.binding.mTaskArray[aRow];
if (!task) {
return null;
}
switch (aCol.element.getAttribute("itemproperty")) {
case "percentComplete":
return task.percentComplete;
}
return null;
},
// SetCellValue is called when the value of the cell has been set by the user.
// This method is only called for columns of type other than text.
setCellValue: function mTV_setCellValue(aRow, aCol, aValue) {
return null;
},
// The image path for a given cell. For defining an icon for a cell.
// If the empty string is returned, the :moz-tree-image pseudoelement will be used.
getImageSrc: function mTV_getImageSrc(aRow, aCol) {
// Return the empty string in order
// to use moz-tree-image pseudoelement :
// it is mandatory to return "" and not false :-(
return("");
},
// IsEditable is called to ask the view if the cell contents are editable.
// A value of true will result in the tree popping up a text field when the user
// tries to inline edit the cell.
isEditable: function mTV_isEditable(aRow, aCol) {
return true;
},
// Called during initialization to link the view to the front end box object.
setTree: function mTV_setTree(aTreeBox) {
this.treebox = aTreeBox;
},
// Methods that can be used to test whether or not a twisty should
// be drawn, and if so, whether an open or closed twisty should be used.
isContainer: function mTV_isContainer(aRow) {
return false;
},
isContainerOpen: function mTV_isContainerOpen(aRow) {
return false;
},
isContainerEmpty: function mTV_isContainerEmpty(aRow) {
return false;
},
// IsSeparator is used to determine if the row at index is a separator.
// A value of true will result in the tree drawing a horizontal separator.
// The tree uses the ::moz-tree-separator pseudoclass to draw the separator.
isSeparator: function mTV_isSeparator(aRow) {
return false;
},
// Specifies if there is currently a sort on any column.
// Used mostly by dragdrop to affect drop feedback.
isSorted: function mTV_isSorted(aRow) {
return false;
},
canDrop: function mTV_canDrop() { return false; },
drop: function mTV_drop(aRow, aOrientation) {},
getParentIndex: function mTV_getParentIndex(aRow) {
return -1;
},
// The level is an integer value that represents the level of indentation.
// It is multiplied by the width specified in the :moz-tree-indentation
// pseudoelement to compute the exact indendation.
getLevel: function mTV_getLevel(aRow) {
return 0;
},
// The image path for a given cell. For defining an icon for a cell.
// If the empty string is returned, the :moz-tree-image pseudoelement
// will be used.
getImgSrc: function mTV_getImgSrc(aRow, aCol) {
return null;
},
// The progress mode for a given cell. This method is only called for
// columns of type |progressmeter|.
getProgressMode: function mTV_getProgressMode(aRow, aCol) {
switch(aCol.element.getAttribute("itemproperty")) {
case "percentcomplete":
var task = this.binding.mTaskArray[aRow];
if (aCol.element.boxObject.width > 75 &&
task.percentComplete > 0 ) {
// XXX Would be nice if we could use relative widths,
// i.e "15ex", but there is no scriptable interface.
return Components.interfaces.nsITreeView.PROGRESS_NORMAL;
}
break;
}
return Components.interfaces.nsITreeView.PROGRESS_NONE;
},
/**
* Task Tree Events
*/
onSelect: function tTV_onSelect(event) {},
onDoubleClick: function tTV_onDoubleClick(event) {
if (event.button == 0) {
var col = {};
var item = this._getItemFromEvent(event, col);
if (item) {
var colAnonId = col.value.element.getAttribute("itemproperty");
if (colAnonId == "completed") {
// item holds checkbox state toggled by first click,
// so don't call modifyEventWithDialog
// to make sure user notices state changed.
} else {
modifyEventWithDialog(item, null, true);
}
} else {
createTodoWithDialog();
}
}
},
onKeyPress: function tTV_onKeyPress(event) {
const kKE = Components.interfaces.nsIDOMKeyEvent;
switch (event.keyCode || event.which) {
case kKE.DOM_VK_DELETE:
document.popupNode = this.binding;
document.getElementById('calendar_delete_todo_command').doCommand();
event.preventDefault();
event.stopPropagation();
break;
case kKE.DOM_VK_SPACE:
if (this.tree.currentIndex > -1) {
var col = document.getAnonymousElementByAttribute(
this.binding, "itemproperty", "completed");
this.cycleCell(
this.tree.currentIndex,
{ element: col });
}
break;
case kKE.DOM_VK_RETURN:
var index = this.tree.currentIndex;
if (index > -1) {
modifyEventWithDialog(this.binding.mTaskArray[index]);
}
break;
}
},
// Set the context menu on mousedown to change it before it is opened
onMouseDown: function tTV_onMouseDown(event) {
let tree = document.getAnonymousElementByAttribute(this.binding,
"anonid",
"calendar-task-tree");
if (!this._getItemFromEvent(event)) {
tree.view.selection.invalidateSelection();
}
},
/**
* Private methods and attributes
*/
_getItemFromEvent: function tTV_getItemFromEvent(event, aCol, aRow) {
aRow = aRow || {};
var childElt = {};
this.treebox.getCellAt(event.clientX, event.clientY, aRow, aCol || {}, childElt);
if (!childElt.value) {
return false;
}
return aRow && aRow.value > -1 && this.binding.mTaskArray[aRow.value];
},
// Helper function to display datetimes
_formatDateTime: function tTV_formatDateTime(aDateTime) {
var dateFormatter = Components.classes["@mozilla.org/calendar/datetime-formatter;1"]
.getService(Components.interfaces.calIDateTimeFormatter);
// datetime is from todo object, it is not a javascript date
if (aDateTime && aDateTime.isValid) {
var dateTime = aDateTime.getInTimezone(calendarDefaultTimezone());
return dateFormatter.formatDateTime(dateTime);
}
return "";
}
})
]]></field>
<!--
Observer for the calendar event data source. This keeps the unifinder
display up to date when the calendar event data is changed
-->
<field name="mTaskTreeObserver"><![CDATA[
({
binding: this,
mInBatch: false,
QueryInterface: function tTO_QueryInterface(aIID) {
return doQueryInterface(this, null, aIID,
[Components.interfaces.calICompositeObserver,
Components.interfaces.calIObserver]);
},
/**
* calIObserver methods and properties
*/
onStartBatch: function tTO_onStartBatch() {
this.mInBatch = true;
},
onEndBatch: function tTO_onEndBatch() {
if (this.mInBatch) {
this.mInBatch = false;
this.binding.refresh();
}
},
onLoad: function tTO_onLoad() {
if (!this.mInBatch) {
this.binding.refresh();
}
},
onAddItem: function tTO_onAddItem(aItem) {
// XXX: We have to filter here
if (isToDo(aItem) &&
!this.mInBatch &&
this.binding.mFilter.isItemInFilters(aItem)) {
this.binding.mTreeView.addItem(aItem);
}
},
onModifyItem: function tTO_onModifyItem(aNewItem, aOldItem) {
if ((isToDo(aNewItem) || isToDo(aOldItem)) &&
!this.mInBatch) {
// forward the call to the view which will in turn
// update the internal reference and the view.
this.binding.mTreeView.modifyItem(aNewItem, aOldItem);
// we also need to notify potential listeners.
var event = document.createEvent('Events');
event.initEvent('select', true, false);
this.binding.dispatchEvent(event);
}
},
onDeleteItem: function tTO_onDeleteItem(aDeletedItem) {
if (isToDo(aDeletedItem) &&
!this.mInBatch) {
this.binding.mTreeView.removeItem(aDeletedItem);
}
},
onError: function tTO_onError(aCalendar, aErrNo, aMessage) {},
onPropertyChanged: function tTO_onPropertyChanged(aCalendar, aName, aValue, aOldValue) {
switch (aName) {
case "disabled":
if (aValue) {
this.binding.onCalendarRemoved(aCalendar);
} else {
this.binding.onCalendarAdded(aCalendar);
}
break;
}
},
onPropertyDeleting: function tTO_onPropertyDeleting(aCalendar, aName) {
this.onPropertyChanged(aCalendar, aName, null, null);
},
/**
* calICompositeObserver methods and properties
*/
onCalendarAdded: function tTO_onCalendarAdded(aCalendar) {
if (!this.mInBatch && !aCalendar.getProperty("disabled")) {
this.binding.onCalendarAdded(aCalendar);
}
},
onCalendarRemoved: function tTO_onCalendarRemoved(aCalendar) {
if (!this.mInBatch && !aCalendar.getProperty("disabled")) {
this.binding.onCalendarRemoved(aCalendar);
}
},
onDefaultCalendarChanged: function tTO_onDefaultCalendarChanged(aNewDefaultCalendar) {}
})
]]></field>
<method name="observe">
<parameter name="aSubject"/>
<parameter name="aTopic"/>
<parameter name="aPrefName"/>
<body><![CDATA[
switch (aPrefName) {
case "calendar.date.format":
case "calendar.timezone.local":
this.refresh();
break;
}
]]></body>
</method>
<!-- Called by event observers to update the display -->
<method name="refresh">
<parameter name="aFilter"/>
<body><![CDATA[
// XXX: why do I need this ?
if(!this.mFilter) {
this.mFilter = new calFilter();
}
var savedThis = this;
var refreshListener = {
mTaskArray: [],
onOperationComplete: function(aCalendar, aStatus, aOperationType, aId, aDateTime) {
savedThis.mTaskArray = this.mTaskArray;
savedThis.onOperationComplete();
},
onGetResult: function(aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
for (var i=0; i<aCount; i++) {
if (savedThis.mFilter.isItemInFilters(aItems[i])) {
refreshListener.mTaskArray.push(aItems[i]);
}
}
}
};
var refreshJob = {
execute: function() {
var composite = getCompositeCalendar();
var filter = aFilter || !savedThis.mShowCompletedTasks ?
composite.ITEM_FILTER_COMPLETED_NO :
composite.ITEM_FILTER_COMPLETED_ALL;
filter |= composite.ITEM_FILTER_TYPE_TODO;
if (savedThis.mFilter.startDate && savedThis.mFilter.endDate) {
filter |= composite.ITEM_FILTER_CLASS_OCCURRENCES;
}
composite.getItems(filter, 0, savedThis.mFilter.startDate, savedThis.mFilter.endDate, refreshListener);
}
};
this.mRefreshQueue.push(refreshJob);
this.popRefreshQueue();
]]></body>
</method>
<method name="onCalendarAdded">
<parameter name="aCalendar"/>
<parameter name="aFilter"/>
<body><![CDATA[
var savedThis = this;
var refreshListener = {
onOperationComplete: function (aCalendar, aStatus, aOperationType, aId, aDateTime) {
savedThis.onOperationComplete();
},
onGetResult: function (aCalendar, aStatus, aItemType, aDetail, aCount, aItems) {
for (var i=0; i<aCount; i++) {
if (savedThis.mFilter.isItemInFilters(aItems[i])) {
savedThis.mTaskArray.push(aItems[i]);
}
}
}
};
var refreshJob = {
execute: function() {
var composite = getCompositeCalendar();
var filter = aFilter || !savedThis.mShowCompletedTasks ?
composite.ITEM_FILTER_COMPLETED_NO :
composite.ITEM_FILTER_COMPLETED_ALL;
filter |= composite.ITEM_FILTER_TYPE_TODO;
if (savedThis.mFilter.startDate && savedThis.mFilter.endDate) {
filter |= composite.ITEM_FILTER_CLASS_OCCURRENCES;
}
aCalendar.getItems(filter, 0, savedThis.mFilter.startDate, savedThis.mFilter.endDate, refreshListener);
}
};
this.mRefreshQueue.push(refreshJob);
this.popRefreshQueue();
]]></body>
</method>
<method name="onCalendarRemoved">
<parameter name="aCalendar"/>
<body><![CDATA[
if (this.mTaskArray.length > 0) {
var index = this.mTaskArray.length - 1;
while(index >= 0) {
var item = this.mTaskArray[index];
if (item.calendar == aCalendar) {
this.mTreeView.removeItem(item);
}
--index;
}
}
// we also need to notify potential listeners.
var event = document.createEvent('Events');
event.initEvent('select', true, false);
this.dispatchEvent(event);
]]></body>
</method>
<method name="popRefreshQueue">
<body><![CDATA[
var pendingRefresh = this.mPendingRefresh;
if (pendingRefresh) {
if (calInstanceOf(pendingRefresh, Components.interfaces.calIOperation)) {
this.mPendingRefresh = null;
pendingRefresh.cancel(null);
} else {
return;
}
}
var refreshJob = this.mRefreshQueue.pop();
if (!refreshJob) {
return;
}
this.mPendingRefresh = true;
pendingRefresh = refreshJob.execute();
if (pendingRefresh && pendingRefresh.isPending) {
this.mPendingRefresh = pendingRefresh;
}
]]></body>
</method>
<method name="onOperationComplete">
<body><![CDATA[
// signal that the current operation finished.
this.mPendingRefresh = null;
// immediately start the next job on the queue.
this.popRefreshQueue();
var tree = document.getAnonymousNodes(this)[0];
if(this.mTreeView.selectedColumn) {
this.sortItems();
} else {
this.recreateHashTable();
}
var tree = document.getAnonymousElementByAttribute(
this, "anonid", "calendar-task-tree");
tree.view = this.mTreeView;
// we also need to notify potential listeners.
var event = document.createEvent('Events');
event.initEvent('select', true, false);
this.dispatchEvent(event);
]]></body>
</method>
<method name="sortItems">
<body><![CDATA[
if (this.mTreeView.selectedColumn) {
var modifier = (this.mTreeView.sortDirection == "descending" ? -1 : 1);
var sortKey = cal.sortEntry.mSortKey = this.mTreeView.selectedColumn.getAttribute("sortKey") ?
this.mTreeView.selectedColumn.getAttribute("sortKey") :
this.mTreeView.selectedColumn.getAttribute("itemproperty");
var sortType = cal.getSortTypeForSortKey(sortKey);
// sort (key,item) entries
cal.sortEntry.mSortStartedDate = now();
var entries = this.mTaskArray.map(cal.sortEntry, cal.sortEntry);
entries.sort(cal.sortEntryComparer(sortType, modifier));
this.mTaskArray = entries.map(cal.sortEntryItem);
}
this.recreateHashTable();
]]></body>
</method>
<method name="recreateHashTable">
<body><![CDATA[
this.mHash2Index = {};
for (var i=0; i<this.mTaskArray.length; i++) {
var item = this.mTaskArray[i];
this.mHash2Index[item.hashId] = i;
}
if (this.mTreeView.treebox) {
this.mTreeView.treebox.invalidate();
}
]]></body>
</method>
</implementation>
<handlers>
<handler event="select"><![CDATA[
this.mTreeView.onSelect(event);
calendarController.onSelectionChanged({detail:this.selectedTasks});
]]></handler>
<handler event="dblclick" button="0"><![CDATA[
this.mTreeView.onDoubleClick(event);
]]></handler>
<handler event="focus"><![CDATA[
calendarController.onSelectionChanged({detail:this.selectedTasks});
calendarController.todo_tasktree_focused = true;
]]></handler>
<handler event="blur"><![CDATA[
calendarController.onSelectionChanged({detail:[]});
calendarController.todo_tasktree_focused = false;
]]></handler>
<handler event="keypress"><![CDATA[
this.mTreeView.onKeyPress(event);
]]></handler>
<handler event="mousedown"><![CDATA[
this.mTreeView.onMouseDown(event);
]]></handler>
<handler event="draggesture"><![CDATA[
if (event.originalTarget.localName != "treechildren") {
// We should only drag treechildren, not for example the scrollbar.
return;
}
var item = this.mTreeView._getItemFromEvent(event);
if (!item || item.calendar.readOnly) {
return;
}
var tree = document.getAnonymousElementByAttribute(this, "anonid", "calendar-task-tree");
// let's build the drag region
var region = null;
try {
region = Components.classes["@mozilla.org/gfx/region;1"].createInstance(Components.interfaces.nsIScriptableRegion);
region.init();
var obo = tree.treeBoxObject;
var bo = obo.treeBody.boxObject;
var sel= tree.view.selection;
var rowX = bo.x;
var rowY = bo.y;
var rowHeight = obo.rowHeight;
var rowWidth = bo.width;
//add a rectangle for each visible selected row
for (var i = obo.getFirstVisibleRow(); i <= obo.getLastVisibleRow(); i ++) {
if (sel.isSelected(i))
region.unionRect(rowX, rowY, rowWidth, rowHeight);
rowY = rowY + rowHeight;
}
//and finally, clip the result to be sure we don't spill over...
if(!region.isEmpty())
region.intersectRect(bo.x, bo.y, bo.width, bo.height);
} catch(ex) {
ASSERT(false, "Error while building selection region: " + ex + "\n");
region = null;
}
invokeEventDragSession(item, event.target);
]]></handler>
</handlers>
</binding>
</bindings>