Fix
bug 373251 - Can open multiple windows of a single event/task from Agenda, Task lists and Unifinder. r=philipp,a=philipp
--- a/calendar/base/content/calendar-item-editing.js
+++ b/calendar/base/content/calendar-item-editing.js
@@ -35,16 +35,28 @@
* 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 ***** */
Components.utils.import("resource://calendar/modules/calAlarmUtils.jsm");
/**
+ * Takes a job and makes sure the dispose function on it is called. If there is
+ * no dispose function or the job is null, ignore it.
+ *
+ * @param job The job to dispose.
+ */
+function disposeJob(job) {
+ if (job && job.dispose) {
+ job.dispose();
+ }
+}
+
+/**
* Creates an event with the calendar event dialog.
*
* @param calendar (optional) The calendar to create the event in
* @param startDate (optional) The event's start date.
* @param endDate (optional) The event's end date.
* @param summary (optional) The event's title.
* @param event (optional) A template event to show in the dialog
* @param aForceAllDay (optional) Make sure the event shown in the dialog is an
@@ -186,65 +198,78 @@ function createTodoWithDialog(calendar,
if (dueDate)
todo.dueDate = dueDate;
if (cal.getPrefSafe("calendar.alarms.onfortodos", 0) == 1 &&
!todo.entryDate) {
// the todo must have an entry date if we want to set an alarm
todo.entryDate = initialDate;
}
-
+
cal.alarms.setDefaultValues(todo);
}
openEventDialog(todo, calendar, "new", onNewItem, null, initialDate);
}
/**
* Modifies the passed event in the event dialog.
*
* @param aItem The item to modify.
* @param job (optional) The job object that controls this
* modification.
* @param aPromptOccurrence If the user should be prompted to select if the
* parent item or occurrence should be modified.
- * @param initialDate (optional) The initial date for new task datepickers
+ * @param initialDate (optional) The initial date for new task datepickers
*/
function modifyEventWithDialog(aItem, job, aPromptOccurrence, initialDate) {
- var onModifyItem = function(item, calendar, originalItem, listener) {
+ let dlg = cal.findItemWindow(aItem);
+ if (dlg) {
+ dlg.focus();
+ disposeJob(job);
+ return;
+ }
+
+ let onModifyItem = function(item, calendar, originalItem, listener) {
doTransaction('modify', item, calendar, originalItem, listener);
};
- var item = aItem;
- var futureItem, response;
+ let item = aItem;
+ let futureItem, response;
if (aPromptOccurrence !== false) {
[item, futureItem, response] = promptOccurrenceModification(aItem, true, "edit");
}
if (item && (response || response === undefined)) {
openEventDialog(item, item.calendar, "modify", onModifyItem, job, initialDate);
- } else if (job && job.dispose) {
- // If the action was canceled and there is a job, dispose it directly.
- job.dispose();
+ } else {
+ disposeJob(job);
}
}
/**
* Opens the event dialog with the given item (task OR event)
*
* @param calendarItem The item to open the dialog with
* @param calendar The calendar to open the dialog with.
* @param mode The operation the dialog should do ("new", "modify")
* @param callback The callback to call when the dialog has completed.
* @param job (optional) The job object for the modification.
- * @param initialDate (optional) The initial date for new task datepickers
+ * @param initialDate (optional) The initial date for new task datepickers
*/
function openEventDialog(calendarItem, calendar, mode, callback, job, initialDate) {
+ let dlg = cal.findItemWindow(calendarItem);
+ if (dlg) {
+ dlg.focus();
+ disposeJob(job);
+ return;
+ }
+
// Set up some defaults
mode = mode || "new";
calendar = calendar || getSelectedCalendar();
var calendars = getCalendarManager().getCalendars({});
calendars = calendars.filter(isCalendarWritable);
var isItemSupported;
if (isToDo(calendarItem)) {
@@ -259,16 +284,17 @@ function openEventDialog(calendarItem, c
// Filter out calendars that don't support the given calendar item
calendars = calendars.filter(isItemSupported);
if (mode == "new" && calendars.length < 1 &&
(!isCalendarWritable(calendar) || !isItemSupported(calendar))) {
// There are no writable calendars or no calendar supports the given
// item. Don't show the dialog.
+ disposeJob(job);
return;
} else if (mode == "new" &&
(!isCalendarWritable(calendar) || !isItemSupported(calendar))) {
// Pick the first calendar that supports the item and is writable
calendar = calendars[0];
if (calendarItem) {
// XXX The dialog currently uses the items calendar as a first
// choice. Since we are shortly before a release to keep regression
--- a/calendar/base/content/calendar-views.js
+++ b/calendar/base/content/calendar-views.js
@@ -146,23 +146,28 @@ var calendarViewController = {
return aOccurrence;
},
/**
* Modifies the given occurrence
* @see calICalendarViewController
*/
modifyOccurrence: function (aOccurrence, aNewStartTime, aNewEndTime, aNewTitle) {
+ let dlg = cal.findItemWindow(aOccurrence);
+ if (dlg) {
+ dlg.focus();
+ return;
+ }
aOccurrence = this.finalizePendingModification(aOccurrence);
// if modifying this item directly (e.g. just dragged to new time),
// then do so; otherwise pop up the dialog
if (aNewStartTime || aNewEndTime || aNewTitle) {
- var instance = aOccurrence.clone();
+ let instance = aOccurrence.clone();
if (aNewTitle) {
instance.title = aNewTitle;
}
// When we made the executive decision (in bug 352862) that
// dragging an occurrence of a recurring event would _only_ act
// upon _that_ occurrence, we removed a bunch of code from this
--- a/calendar/base/src/calUtils.js
+++ b/calendar/base/src/calUtils.js
@@ -38,16 +38,18 @@
*
* ***** END LICENSE BLOCK ***** */
/* This file contains commonly used functions in a centralized place so that
* various components (and other js scopes) don't need to replicate them. Note
* that loading this file twice in the same scope will throw errors.
*/
+Components.utils.import("resource:///modules/Services.jsm");
+
/**
* Returns a clean new calIEvent
*
* @param aIcalString (optional) The icalString to read event details from.
*/
function createEvent(aIcalString) {
let event = Components.classes["@mozilla.org/calendar/event;1"]
.createInstance(Components.interfaces.calIEvent);
@@ -128,17 +130,17 @@ function createAlarm() {
createInstance(Components.interfaces.calIAlarm);
}
/* Returns a clean new calIRelation */
function createRelation() {
return Components.classes["@mozilla.org/calendar/relation;1"].
createInstance(Components.interfaces.calIRelation);
}
-
+
/* Shortcut to the console service */
function getConsoleService() {
return Components.classes["@mozilla.org/consoleservice;1"]
.getService(Components.interfaces.nsIConsoleService);
}
/* Shortcut to the account manager service */
function getAccountManager() {
@@ -230,18 +232,18 @@ function calendarDefaultTimezone() {
* Ensures: result only contains ascii digits, letters,'-', and '_'.
* Ensures: result is invertible, so (f(a) = f(b)) implies (a = b).
* also means f is not idempotent, so (a != f(a)) implies (f(a) != f(f(a))).
* Ensures: result must be lowercase.
* Rationale: preference keys require 8bit chars, and ascii chars are legible
* in most fonts (in case user edits PROFILE/prefs.js).
* CSS class names in Gecko 1.8 seem to require lowercase,
* no punctuation, and of course no spaces.
- * nmchar [_a-zA-Z0-9-]|{nonascii}|{escape}
- * name {nmchar}+
+ * nmchar [_a-zA-Z0-9-]|{nonascii}|{escape}
+ * name {nmchar}+
* http://www.w3.org/TR/CSS21/grammar.html#scanner
*
* @param aString The unicode string to format
* @return The formatted string using only chars [_a-zA-Z0-9-]
*/
function formatStringForCSSRule(aString) {
function toReplacement(ch) {
// char code is natural number (positive integer)
@@ -1666,17 +1668,17 @@ calPropertyBag.prototype = {
return (this.mData[aName] = aValue);
},
getProperty_: function cpb_getProperty(aName) {
// avoid strict undefined property warning
return (aName in this.mData ? this.mData[aName] : undefined);
},
getProperty: function cpb_getProperty(aName) {
// avoid strict undefined property warning
- return (aName in this.mData ? this.mData[aName] : null);
+ return (aName in this.mData ? this.mData[aName] : null);
},
getAllProperties: function cpb_getAllProperties(aOutKeys, aOutValues) {
var keys = [];
var values = [];
for (var key in this.mData) {
keys.push(key);
values.push(this.mData[key]);
}
@@ -1897,8 +1899,26 @@ function getCompositeCalendar() {
let chromeWindow = window.QueryInterface(Components.interfaces.nsIDOMChromeWindow);
getCompositeCalendar.mObject.setStatusObserver(gCalendarStatusFeedback, chromeWindow);
}
} catch (exc) { // catch errors in case we run in contexts without status feedback
}
}
return getCompositeCalendar.mObject;
}
+
+/**
+ * Search for already open item dialog.
+ *
+ * @param aItem The item of the dialog to search for.
+ */
+function findItemWindow(aItem){
+ let list = Services.wm.getEnumerator("Calendar:EventDialog");
+ while (list.hasMoreElements()) {
+ let dlg = list.getNext();
+ if (dlg.calendarItem &&
+ dlg.mode == "modify" &&
+ dlg.calendarItem.hashId == aItem.hashId) {
+ return dlg;
+ }
+ }
+ return null;
+}