Fix bug 373251 - Can open multiple windows of a single event/task from Agenda, Task lists and Unifinder. r=philipp,a=philipp
authorPhilippe Martinak <philippe.martinak@i-carre.net>
Wed, 10 Aug 2011 06:40:00 +0200
changeset 8553 a7f24c946128
parent 8552 cd938bbe1d04
child 8554 b12547a628cf
push id150
push usermozilla@kewis.ch
push date2011-09-17 21:41 +0000
treeherdercomm-aurora@a7f24c946128 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersphilipp, philipp
bugs373251
Fix bug 373251 - Can open multiple windows of a single event/task from Agenda, Task lists and Unifinder. r=philipp,a=philipp
calendar/base/content/calendar-item-editing.js
calendar/base/content/calendar-views.js
calendar/base/src/calUtils.js
--- 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;
+}