pjs/calendar/resources/content/mouseoverPreviews.js

494 строки
16 KiB
JavaScript

/* ***** 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>
* Karl Guertin <grayrest@grayrest.com>
* Mike Norton <xor@ivwnet.com>
* ArentJan Banck <ajbanck@planet.nl>
* Eric Belhaire <belhaire@ief.u-psud.fr>
*
* 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 ***** */
/**
* Code which generates event and task (todo) preview tooltips/titletips
* when the mouse hovers over either the event list, the task list, or
* an event or task box in one of the grid views.
*
* (Portions of this code were previously in calendar.js and unifinder.js,
* some of it duplicated.)
*/
/** PUBLIC
*
* This changes the mouseover preview based on the start and end dates
* of an occurrence of a (one-time or recurring) calEvent or calToDo.
* Used by all grid views.
*/
function onMouseOverItem( occurrenceBoxMouseEvent )
{
if ("occurrence" in occurrenceBoxMouseEvent.currentTarget) {
// occurrence of repeating event or todo
var occurrence = occurrenceBoxMouseEvent.currentTarget.occurrence;
const toolTip = document.getElementById("itemTooltip");
var holderBox;
if (isEvent(occurrence)) {
holderBox = getPreviewForEvent(occurrence, occurrence.startDate, occurrence.endDate);
} else if (isToDo(occurrence)) {
holderBox = getPreviewForTask(occurrence);
}
if (holderBox) {
setToolTipContent(toolTip, holderBox);
return true;
}
}
return false;
}
/** For all instances of an event, as displayed by unifinder. **/
function onMouseOverEventTree( toolTip, mouseEvent )
{
var item = getCalendarEventFromEvent( mouseEvent );
if (isEvent(item)) {
var holderBox = getPreviewForEvent(item);
if (holderBox) {
setToolTipContent(toolTip, holderBox);
return true;
}
}
return false;
}
/** For all instances of a task, as displayed by unifinderToDo. **/
function onMouseOverTaskTree( toolTip, mouseEvent )
{
var item = getToDoFromEvent( mouseEvent );
if (isToDo(item)) {
var holderBox = getPreviewForTask(item);
if (holderBox) {
setToolTipContent(toolTip, holderBox);
return true;
}
}
return false;
}
/**
* Removes old content from tooltip, adds new content box to tooltip,
* then resizes the tooltip to the size of the new content box.
*
* @param tooltip The tooltip to modify.
* @param holderBox The box element containing the new content.
*/
function setToolTipContent(toolTip, holderBox)
{
while (toolTip.hasChildNodes()) {
toolTip.removeChild( toolTip.firstChild );
}
toolTip.appendChild( holderBox );
var width = holderBox.boxObject.width;
var height = holderBox.boxObject.height;
// workaround bug 369225 (aspect: tooltip may not shrink height)
toolTip.sizeTo(0,0);
// workaround bug 369225 (aspect: tooltip height too short)
// Add top and bottom border and padding to workaround bug where bottom
// tooltip border disappears if wrapped description below header grid.
height += 1 + 2 + 2 + 1;
toolTip.sizeTo(width, height);
}
/**
* Called when a user hovers over a todo element and the text for the mouse over is changed.
*/
function getPreviewForTask( toDoItem )
{
if( toDoItem )
{
const vbox = document.createElement( "vbox" );
vbox.setAttribute("class", "tooltipBox");
// tooltip appears above or below pointer, so may have as little as
// one half the screen height available (avoid top going off screen).
vbox.maxHeight = Math.floor(screen.height / 2);
boxInitializeHeaderGrid(vbox);
var hasHeader = false;
if (toDoItem.title)
{
boxAppendLabeledText(vbox, "tooltipTitle", toDoItem.title);
hasHeader = true;
}
var location = toDoItem.getProperty("LOCATION");
if (location)
{
boxAppendLabeledText(vbox, "tooltipLocation", location);
hasHeader = true;
}
if (toDoItem.entryDate && toDoItem.entryDate.isValid)
{
boxAppendLabeledDateTime(vbox, "tooltipStart", toDoItem.entryDate);
hasHeader = true;
}
if (toDoItem.dueDate && toDoItem.dueDate.isValid)
{
boxAppendLabeledDateTime(vbox, "tooltipDue", toDoItem.dueDate);
hasHeader = true;
}
if (toDoItem.priority && toDoItem.priority != 0)
{
var priorityInteger = parseInt(toDoItem.priority);
var priorityString;
// These cut-offs should match calendar-event-dialog.js
if (priorityInteger >= 1 && priorityInteger <= 4) {
priorityString = calGetString('calendar', 'highPriority'); // high priority
} else if (priorityInteger == 5) {
priorityString = calGetString('calendar', 'mediumPriority'); // medium priority
} else {
priorityString = calGetString('calendar', 'lowPriority'); // low priority
}
boxAppendLabeledText(vbox, "tooltipPriority", priorityString);
hasHeader = true;
}
if (toDoItem.status && toDoItem.status != "NONE")
{
var status = getToDoStatusString(toDoItem);
boxAppendLabeledText(vbox, "tooltipStatus", status);
hasHeader = true;
}
if (toDoItem.percentComplete != 0 && toDoItem.percentComplete != 100)
{
boxAppendLabeledText(vbox, "tooltipPercent", String(toDoItem.percentComplete)+"%");
hasHeader = true;
} else if (toDoItem.percentComplete == 100)
{
if (toDoItem.completedDate == null) {
boxAppendLabeledText(vbox, "tooltipPercent", "100%");
} else {
boxAppendLabeledDateTime(vbox, "tooltipCompleted", toDoItem.completedDate);
}
hasHeader = true;
}
var description = toDoItem.getProperty("DESCRIPTION");
if (description)
{
// display wrapped description lines like body of message below headers
if (hasHeader) {
boxAppendBodySeparator(vbox);
}
boxAppendBody(vbox, description);
}
return ( vbox );
}
else
{
return null;
}
}
/**
* Called when mouse moves over a different, or
* when mouse moves over event in event list.
* The instStartDate is date of instance displayed at event box
* (recurring or multiday events may be displayed by more than one event box
* for different days), or null if should compute next instance from now.
*/
function getPreviewForEvent( event, instStartDate, instEndDate )
{
const vbox = document.createElement( "vbox" );
vbox.setAttribute("class", "tooltipBox");
// tooltip appears above or below pointer, so may have as little as
// one half the screen height available (avoid top going off screen).
vbox.maxHeight = Math.floor(screen.height / 2);
boxInitializeHeaderGrid(vbox);
if (event)
{
if (event.title)
{
boxAppendLabeledText(vbox, "tooltipTitle", event.title);
}
var location = event.getProperty("LOCATION");
if (location)
{
boxAppendLabeledText(vbox, "tooltipLocation", location);
}
if (event.startDate || instStartDate)
{
var startDate, endDate;
if (instStartDate && instEndDate) {
startDate = instStartDate;
endDate = instEndDate;
} else {
// Event may be recurrent event. If no displayed instance specified,
// use next instance, or previous instance if no next instance.
var occ = getCurrentNextOrPreviousRecurrence(event);
startDate = instStartDate || occ.startDate;
endDate = occ.endDate;
}
boxAppendLabeledDateTimeInterval(vbox, "tooltipDate", startDate, endDate);
}
if (event.status && event.status != "NONE")
{
var statusString = getEventStatusString(event);
boxAppendLabeledText(vbox, "tooltipStatus", statusString);
}
var description = event.getProperty("DESCRIPTION");
if (description)
{
boxAppendBodySeparator(vbox);
// display wrapped description lines, like body of message below headers
boxAppendBody(vbox, description);
}
return ( vbox );
}
else
{
return null;
}
}
/** String for event status: (none), Tentative, Confirmed, or Cancelled **/
function getEventStatusString(calendarEvent)
{
switch( calendarEvent.status )
{
// Event status value keywords are specified in RFC2445sec4.8.1.11
case "TENTATIVE":
return calGetString('calendar', "statusTentative");
case "CONFIRMED":
return calGetString('calendar', "statusConfirmed");
case "CANCELLED":
return calGetString('calendar', "statusCancelled");
default:
return "";
}
}
/** String for todo status: (none), NeedsAction, InProcess, Cancelled, or Completed **/
function getToDoStatusString(iCalToDo)
{
switch( iCalToDo.status )
{
// Todo status keywords are specified in RFC2445sec4.8.1.11
case "NEEDS-ACTION":
return calGetString('calendar', "statusNeedsAction");
case "IN-PROCESS":
return calGetString('calendar', "statusInProcess");
case "CANCELLED":
return calGetString('calendar', "statusCancelled");
case "COMPLETED":
return calGetString('calendar', "statusCompleted");
default:
return "";
}
}
/**
* PRIVATE: Append a separator, a thin space between header and body.
*
* @param vbox box to which to append separator.
*/
function boxAppendBodySeparator(vbox) {
const separator = document.createElement("separator");
separator.setAttribute("class", "tooltipBodySeparator");
vbox.appendChild(separator);
}
/**
* PRIVATE: Append description to box for body text. Text may contain
* paragraphs; line indent and line breaks will be preserved by CSS.
* @param box box to which to append body
* @param textString text of body
*/
function boxAppendBody(box, textString)
{
var textNode = document.createTextNode(textString);
var xulDescription = document.createElement("description");
xulDescription.setAttribute("class", "tooltipBody");
xulDescription.appendChild(textNode);
box.appendChild(xulDescription);
}
/**
* PRIVATE: Use dateFormatter to format date and time,
* and to header grid append a row containing localized Label: date.
*/
function boxAppendLabeledDateTime(box, labelProperty, date)
{
var dateFormatter = Components.classes["@mozilla.org/calendar/datetime-formatter;1"]
.getService(Components.interfaces.calIDateTimeFormatter);
date = date.getInTimezone(calendarDefaultTimezone());
var formattedDateTime = dateFormatter.formatDateTime(date);
boxAppendLabeledText(box, labelProperty, formattedDateTime);
}
/**
* PRIVATE: Use dateFormatter to format date and time interval,
* and to header grid append a row containing localized Label: interval.
* @param box contains header grid.
* @param labelProperty name of property for localized field label.
* @param start calDateTime of start of time interval.
* @param end calDateTime of end of time interval.
*/
function boxAppendLabeledDateTimeInterval(box, labelProperty, start, end)
{
var dateFormatter = Components.classes["@mozilla.org/calendar/datetime-formatter;1"]
.getService(Components.interfaces.calIDateTimeFormatter);
var startString = new Object();
var endString = new Object();
start = start.getInTimezone(calendarDefaultTimezone());
end = end.getInTimezone(calendarDefaultTimezone());
dateFormatter.formatInterval(start, end, startString, endString);
if (endString.value != "") {
boxAppendLabeledText(box, labelProperty, startString.value + ' - ' + endString.value);
} else {
boxAppendLabeledText(box, labelProperty, startString.value);
}
}
/**
* PRIVATE: create empty 2-column grid for header fields,
* and append it to box.
*/
function boxInitializeHeaderGrid(box)
{
var grid = document.createElement("grid");
grid.setAttribute("class", "tooltipHeaderGrid");
var rows;
{
var columns = document.createElement("columns");
{
var labelColumn = document.createElement("column");
labelColumn.setAttribute("class", "tooltipLabelColumn");
columns.appendChild(labelColumn);
var valueColumn = document.createElement("column");
valueColumn.setAttribute("class", "tooltipValueColumn");
columns.appendChild(valueColumn);
}
grid.appendChild(columns);
rows = document.createElement("rows");
grid.appendChild(rows);
}
box.appendChild(grid);
}
/**
* PRIVATE: To headers grid, append a row containing Label: value,
* where label is localized text for labelProperty.
* @param box box containing headers grid
* @param labelProperty name of property for localized name of header
* @param textString value of header field.
*/
function boxAppendLabeledText(box, labelProperty, textString)
{
var labelText = calGetString('calendar', labelProperty);
var rows = box.getElementsByTagName("rows")[0];
{
var row = document.createElement("row");
{
row.appendChild(createTooltipHeaderLabel(labelText));
row.appendChild(createTooltipHeaderDescription(textString));
}
rows.appendChild(row);
}
}
/** PRIVATE: create element for field label (for header grid). **/
function createTooltipHeaderLabel(text)
{
var label = document.createElement("label");
label.setAttribute("class", "tooltipHeaderLabel");
label.appendChild(document.createTextNode(text));
return label;
}
/** PRIVATE: create element for field value (for header grid). **/
function createTooltipHeaderDescription(text)
{
var label = document.createElement("description");
label.setAttribute("class", "tooltipHeaderDescription");
label.appendChild(document.createTextNode(text));
return label;
}
/**
* If now is during an occurrence, return the occurrence.
* Else if now is before an occurrence, return the next occurrence.
* Otherwise return the previous occurrence.
*/
function getCurrentNextOrPreviousRecurrence(calendarEvent)
{
if (!calendarEvent.recurrenceInfo) {
return calendarEvent;
}
var dur = calendarEvent.duration.clone();
dur.isNegative = true;
// To find current event when now is during event, look for occurrence
// starting duration ago.
var probeTime = now();
probeTime.addDuration(dur);
var occ = calendarEvent.recurrenceInfo.getNextOccurrence(probeTime);
if (!occ) {
var occs = calendarEvent.recurrenceInfo.getOccurrences(calendarEvent.startDate, probeTime, 0, {});
occ = occs[occs.length -1];
}
return occ;
}