summaryrefslogtreecommitdiffstats
path: root/comm/calendar/base/content/widgets/mouseoverPreviews.js
diff options
context:
space:
mode:
Diffstat (limited to 'comm/calendar/base/content/widgets/mouseoverPreviews.js')
-rw-r--r--comm/calendar/base/content/widgets/mouseoverPreviews.js439
1 files changed, 439 insertions, 0 deletions
diff --git a/comm/calendar/base/content/widgets/mouseoverPreviews.js b/comm/calendar/base/content/widgets/mouseoverPreviews.js
new file mode 100644
index 0000000000..38e5c1e24f
--- /dev/null
+++ b/comm/calendar/base/content/widgets/mouseoverPreviews.js
@@ -0,0 +1,439 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * 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.)
+ */
+
+/* exported onMouseOverItem, showToolTip, getPreviewForItem,
+ getEventStatusString, getToDoStatusString */
+
+/* import-globals-from ../calendar-ui-utils.js */
+
+/**
+ * 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.
+ */
+
+var { cal } = ChromeUtils.import("resource:///modules/calendar/calUtils.jsm");
+
+/**
+ * PUBLIC: Displays a tooltip with details when hovering over an item in the views
+ *
+ * @param {DOMEvent} occurrenceBoxMouseEvent the triggering event
+ * @returns {boolean} true, if the tooltip is displayed
+ */
+function onMouseOverItem(occurrenceBoxMouseEvent) {
+ if ("occurrence" in occurrenceBoxMouseEvent.currentTarget) {
+ // occurrence of repeating event or todo
+ let occurrence = occurrenceBoxMouseEvent.currentTarget.occurrence;
+ const toolTip = document.getElementById("itemTooltip");
+ return showToolTip(toolTip, occurrence);
+ }
+ return false;
+}
+
+/**
+ * PUBLIC: Displays a tooltip for a given item
+ *
+ * @param {Node} aTooltip the node to hold the tooltip
+ * @param {CalIEvent|calIToDo} aItem the item to create the tooltip for
+ * @returns {boolean} true, if the tooltip is displayed
+ */
+function showToolTip(aToolTip, aItem) {
+ if (aItem) {
+ let holderBox = getPreviewForItem(aItem);
+ if (holderBox) {
+ while (aToolTip.lastChild) {
+ aToolTip.lastChild.remove();
+ }
+ aToolTip.appendChild(holderBox);
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * PUBLIC: Called when a user hovers over a todo element and the text for the
+ * mouse over is changed.
+ *
+ * @param {calIToDo} toDoItem - the item to create the preview for
+ * @param {boolean} aIsTooltip enabled if used for tooltip composition (default)
+ */
+function getPreviewForItem(aItem, aIsTooltip = true) {
+ if (aItem.isEvent()) {
+ return getPreviewForEvent(aItem, aIsTooltip);
+ } else if (aItem.isTodo()) {
+ return getPreviewForTask(aItem, aIsTooltip);
+ }
+ return null;
+}
+
+/**
+ * PUBLIC: Returns the string for status (none), Tentative, Confirmed, or
+ * Cancelled for a given event
+ *
+ * @param {calIEvent} aEvent The event
+ * @returns {string} The string for the status property of the event
+ */
+function getEventStatusString(aEvent) {
+ switch (aEvent.status) {
+ // Event status value keywords are specified in RFC2445sec4.8.1.11
+ case "TENTATIVE":
+ return cal.l10n.getCalString("statusTentative");
+ case "CONFIRMED":
+ return cal.l10n.getCalString("statusConfirmed");
+ case "CANCELLED":
+ return cal.l10n.getCalString("eventStatusCancelled");
+ default:
+ return "";
+ }
+}
+
+/**
+ * PUBLIC: Returns the string for status (none), NeedsAction, InProcess,
+ * Cancelled, orCompleted for a given ToDo
+ *
+ * @param {calIToDo} aToDo The ToDo
+ * @returns {string} The string for the status property of the event
+ */
+function getToDoStatusString(aToDo) {
+ switch (aToDo.status) {
+ // Todo status keywords are specified in RFC2445sec4.8.1.11
+ case "NEEDS-ACTION":
+ return cal.l10n.getCalString("statusNeedsAction");
+ case "IN-PROCESS":
+ return cal.l10n.getCalString("statusInProcess");
+ case "CANCELLED":
+ return cal.l10n.getCalString("todoStatusCancelled");
+ case "COMPLETED":
+ return cal.l10n.getCalString("statusCompleted");
+ default:
+ return "";
+ }
+}
+
+/**
+ * PRIVATE: Called when a user hovers over a todo element and the text for the
+ * mouse overis changed.
+ *
+ * @param {calIToDo} toDoItem - the item to create the preview for
+ * @param {boolean} aIsTooltip enabled if used for tooltip composition (default)
+ */
+function getPreviewForTask(toDoItem, aIsTooltip = true) {
+ if (toDoItem) {
+ const vbox = document.createXULElement("vbox");
+ vbox.setAttribute("class", "tooltipBox");
+ if (aIsTooltip) {
+ // tooltip appears above or below pointer, so may have as little as
+ // one half the screen height available (avoid top going off screen).
+ vbox.style.maxHeight = Math.floor(screen.height / 2);
+ } else {
+ vbox.setAttribute("flex", "1");
+ }
+ boxInitializeHeaderTable(vbox);
+
+ let hasHeader = false;
+
+ if (toDoItem.title) {
+ boxAppendLabeledText(vbox, "tooltipTitle", toDoItem.title);
+ hasHeader = true;
+ }
+
+ let location = toDoItem.getProperty("LOCATION");
+ if (location) {
+ boxAppendLabeledText(vbox, "tooltipLocation", location);
+ hasHeader = true;
+ }
+
+ // First try to get calendar name appearing in tooltip
+ if (toDoItem.calendar.name) {
+ let calendarNameString = toDoItem.calendar.name;
+ boxAppendLabeledText(vbox, "tooltipCalName", calendarNameString);
+ }
+
+ 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) {
+ let priorityInteger = parseInt(toDoItem.priority, 10);
+ let priorityString;
+
+ // These cut-offs should match calendar-event-dialog.js
+ if (priorityInteger >= 1 && priorityInteger <= 4) {
+ priorityString = cal.l10n.getCalString("highPriority");
+ } else if (priorityInteger == 5) {
+ priorityString = cal.l10n.getCalString("normalPriority");
+ } else {
+ priorityString = cal.l10n.getCalString("lowPriority");
+ }
+ boxAppendLabeledText(vbox, "tooltipPriority", priorityString);
+ hasHeader = true;
+ }
+
+ if (toDoItem.status && toDoItem.status != "NONE") {
+ let status = getToDoStatusString(toDoItem);
+ boxAppendLabeledText(vbox, "tooltipStatus", status);
+ hasHeader = true;
+ }
+
+ if (
+ toDoItem.status != null &&
+ 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;
+ }
+
+ let description = toDoItem.descriptionText;
+ if (description) {
+ // display wrapped description lines like body of message below headers
+ if (hasHeader) {
+ boxAppendBodySeparator(vbox);
+ }
+ boxAppendBody(vbox, description, aIsTooltip);
+ }
+
+ return vbox;
+ }
+ return null;
+}
+
+/**
+ * PRIVATE: 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.
+ *
+ * @param {calIEvent} aEvent - the item to create the preview for
+ * @param {boolean} aIsTooltip enabled if used for tooltip composition (default)
+ */
+function getPreviewForEvent(aEvent, aIsTooltip = true) {
+ let event = aEvent;
+ const vbox = document.createXULElement("vbox");
+ vbox.setAttribute("class", "tooltipBox");
+ if (aIsTooltip) {
+ // 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);
+ } else {
+ vbox.setAttribute("flex", "1");
+ }
+ boxInitializeHeaderTable(vbox);
+
+ if (event) {
+ if (event.title) {
+ boxAppendLabeledText(vbox, "tooltipTitle", aEvent.title);
+ }
+
+ let location = event.getProperty("LOCATION");
+ if (location) {
+ boxAppendLabeledText(vbox, "tooltipLocation", location);
+ }
+ if (!(event.startDate && event.endDate)) {
+ // Event may be recurrent event. If no displayed instance specified,
+ // use next instance, or previous instance if no next instance.
+ event = getCurrentNextOrPreviousRecurrence(event);
+ }
+ boxAppendLabeledDateTimeInterval(vbox, "tooltipDate", event);
+
+ // First try to get calendar name appearing in tooltip
+ if (event.calendar.name) {
+ let calendarNameString = event.calendar.name;
+ boxAppendLabeledText(vbox, "tooltipCalName", calendarNameString);
+ }
+
+ if (event.status && event.status != "NONE") {
+ let statusString = getEventStatusString(event);
+ boxAppendLabeledText(vbox, "tooltipStatus", statusString);
+ }
+
+ if (event.organizer && event.getAttendees().length > 0) {
+ let organizer = event.organizer;
+ boxAppendLabeledText(vbox, "tooltipOrganizer", organizer);
+ }
+
+ let description = event.descriptionText;
+ if (description) {
+ boxAppendBodySeparator(vbox);
+ // display wrapped description lines, like body of message below headers
+ boxAppendBody(vbox, description, aIsTooltip);
+ }
+ return vbox;
+ }
+ return null;
+}
+
+/**
+ * PRIVATE: Append a separator, a thin space between header and body.
+ *
+ * @param {Node} vbox box to which to append separator.
+ */
+function boxAppendBodySeparator(vbox) {
+ const separator = document.createXULElement("separator");
+ separator.setAttribute("class", "tooltipBodySeparator");
+ vbox.appendChild(separator);
+}
+
+/**
+ * PRIVATE: Append description to box for body text. Rendered as HTML.
+ * Indentation and line breaks are preserved.
+ *
+ * @param {Node} box - Box to which to append the body.
+ * @param {string} textString - Text of the body.
+ * @param {boolean} aIsTooltip - True for "tooltip" and false for "conflict-dialog" case.
+ */
+function boxAppendBody(box, textString, aIsTooltip) {
+ let type = aIsTooltip ? "description" : "vbox";
+ let xulDescription = document.createXULElement(type);
+ xulDescription.setAttribute("class", "tooltipBody");
+ if (!aIsTooltip) {
+ xulDescription.setAttribute("flex", "1");
+ }
+ let docFragment = cal.view.textToHtmlDocumentFragment(textString, document);
+ xulDescription.appendChild(docFragment);
+ box.appendChild(xulDescription);
+}
+
+/**
+ * PRIVATE: Use dateFormatter to format date and time,
+ * and to header table append a row containing localized Label: date.
+ *
+ * @param {Node} box The node to add the date label to
+ * @param {string} labelProperty The label
+ * @param {calIDateTime} date - The datetime object to format and add
+ */
+function boxAppendLabeledDateTime(box, labelProperty, date) {
+ date = date.getInTimezone(cal.dtz.defaultTimezone);
+ let formattedDateTime = cal.dtz.formatter.formatDateTime(date);
+ boxAppendLabeledText(box, labelProperty, formattedDateTime);
+}
+
+/**
+ * PRIVATE: Use dateFormatter to format date and time interval,
+ * and to header table append a row containing localized Label: interval.
+ *
+ * @param box contains header table.
+ * @param labelProperty name of property for localized field label.
+ * @param item the event or task
+ */
+function boxAppendLabeledDateTimeInterval(box, labelProperty, item) {
+ let dateString = cal.dtz.formatter.formatItemInterval(item);
+ boxAppendLabeledText(box, labelProperty, dateString);
+}
+
+/**
+ * PRIVATE: create empty 2-column table for header fields, and append it to box.
+ *
+ * @param {Node} box The node to create a column table for
+ */
+function boxInitializeHeaderTable(box) {
+ let table = document.createElementNS("http://www.w3.org/1999/xhtml", "table");
+ table.setAttribute("class", "tooltipHeaderTable");
+ box.appendChild(table);
+}
+
+/**
+ * PRIVATE: To headers table, append a row containing Label: value, where label
+ * is localized text for labelProperty.
+ *
+ * @param box box containing headers table
+ * @param labelProperty name of property for localized name of header
+ * @param textString value of header field.
+ */
+function boxAppendLabeledText(box, labelProperty, textString) {
+ let labelText = cal.l10n.getCalString(labelProperty);
+ let table = box.querySelector("table");
+ let row = document.createElementNS("http://www.w3.org/1999/xhtml", "tr");
+
+ row.appendChild(createTooltipHeaderLabel(labelText));
+ row.appendChild(createTooltipHeaderDescription(textString));
+
+ table.appendChild(row);
+}
+
+/**
+ * PRIVATE: Creates an element for field label (for header table)
+ *
+ * @param {string} text The text to display in the node
+ * @returns {Node} The node
+ */
+function createTooltipHeaderLabel(text) {
+ let labelCell = document.createElementNS("http://www.w3.org/1999/xhtml", "th");
+ labelCell.setAttribute("class", "tooltipHeaderLabel");
+ labelCell.textContent = text;
+ return labelCell;
+}
+
+/**
+ * PRIVATE: Creates an element for field value (for header table)
+ *
+ * @param {string} text The text to display in the node
+ * @returns {Node} The node
+ */
+function createTooltipHeaderDescription(text) {
+ let descriptionCell = document.createElementNS("http://www.w3.org/1999/xhtml", "td");
+ descriptionCell.setAttribute("class", "tooltipHeaderDescription");
+ descriptionCell.textContent = text;
+ return descriptionCell;
+}
+
+/**
+ * PRIVATE: If now is during an occurrence, return the occurrence. If now is
+ * before an occurrence, return the next occurrence or otherwise the previous
+ * occurrence.
+ *
+ * @param {calIEvent} calendarEvent The text to display in the node
+ * @returns {mixed} Returns a calIDateTime for the detected
+ * occurrence or calIEvent, if this is a
+ * non-recurring event
+ */
+function getCurrentNextOrPreviousRecurrence(calendarEvent) {
+ if (!calendarEvent.recurrenceInfo) {
+ return calendarEvent;
+ }
+
+ let dur = calendarEvent.duration.clone();
+ dur.isNegative = true;
+
+ // To find current event when now is during event, look for occurrence
+ // starting duration ago.
+ let probeTime = cal.dtz.now();
+ probeTime.addDuration(dur);
+
+ let occ = calendarEvent.recurrenceInfo.getNextOccurrence(probeTime);
+
+ if (!occ) {
+ let occs = calendarEvent.recurrenceInfo.getOccurrences(
+ calendarEvent.startDate,
+ probeTime,
+ 0,
+ {}
+ );
+ occ = occs[occs.length - 1];
+ }
+ return occ;
+}