Bug 454543 - Calendar Multiday-View: Performance improvement is required;r=dbo
authorBerend Cornelius [:berend] <berend.cornelius@sun.com>
Tue, 21 Oct 2008 15:21:52 +0200
changeset 667 588651b7b68098858af041040e30a90404127abf
parent 666 5bf762b71c0d6a3f798c3f5e9fedc60978181984
child 668 7eabd4ca2ea517476b92bdc8fda535049e27e308
push idunknown
push userunknown
push dateunknown
reviewersdbo
bugs454543
Bug 454543 - Calendar Multiday-View: Performance improvement is required;r=dbo
calendar/base/content/calendar-multiday-view.xml
--- a/calendar/base/content/calendar-multiday-view.xml
+++ b/calendar/base/content/calendar-multiday-view.xml
@@ -271,32 +271,40 @@
           <xul:box anonid="fgdragspacer" style="display: inherit; overflow: hidden;">
             <xul:spacer flex="1"/>
             <xul:label anonid="fgdragbox-startlabel" class="fgdragbox-label"/>
           </xul:box>
           <xul:box anonid="fgdragbox" class="fgdragbox" />
           <xul:label anonid="fgdragbox-endlabel" class="fgdragbox-label"/>
         </xul:box>
       </xul:stack>
-      <xul:calendar-event-box anonid="config-box" hidden="true" xbl:inherits="orient"/>      
+      <xul:calendar-event-box anonid="config-box" hidden="true" xbl:inherits="orient"/>
     </content>
 
     <implementation>
       <constructor><![CDATA[
-        this.mEvents = Array();
+        this.mEventInfos = Array();
         this.mTimezone = UTC();
       ]]></constructor>
 
       <!-- fields -->
       <field name="mPixPerMin">0.6</field>
       <field name="mStartMin">0*60</field>
       <field name="mEndMin">24*60</field>
       <field name="mDayStartMin">8*60</field>
       <field name="mDayEndMin">17*60</field>
-      <field name="mEvents">new Array()</field>
+      an Array of objects that contain information about the events that are to be
+      displayed. The contained fields are:
+      event:        The event that is to be displayed in a 'calendar-event-box'
+      layoutStart:  The 'start'-datetime object of the event in the timezone of the view
+      layoutEnd:    The 'end'-datetime object of the event in the timezone of the view.
+                    The 'layoutEnd' may be different from the real 'end' time of the
+                    event because it considers a certain minimum duration of the event
+                    that is basically dependent of the font-size of the event-box label
+      <field name="mEventInfos">new Array()</field>
       <field name="mEventMap">null</field>
       <field name="mCalendarView">null</field>
       <field name="mDate">null</field>
       <field name="mTimezone">null</field>
       <field name="mDragState">null</field>
       <field name="mLayoutBatchCount">0</field>
       <!-- Since we'll often be getting many events in rapid succession, this
            timer helps ensure that we don't re-compute the event map too many
@@ -420,17 +428,17 @@
           return this.mDayOff;
         ]]></getter>
         <setter><![CDATA[
           this.mDayOff = val;
           return val;
         ]]></setter>
       </property>
 
-      <!-- mEvents -->
+      <!-- mEventInfos -->
       <field name="mSelectedChunks">[]</field>
 
       <method name="selectOccurrence">
         <parameter name="aOccurrence"/>
         <body><![CDATA[
           if (aOccurrence) {
               this.mSelectedItemIds[aOccurrence.hashId] = true;
               var chunk = this.findChunkForOccurrence(aOccurrence);
@@ -502,43 +510,43 @@
         ]]></body>
       </method>
 
       <method name="internalDeleteEvent">
         <parameter name="aOccurrence"/>
         <body><![CDATA[
            var itemIndex = -1;
            var occ;
-           for (var i in this.mEvents) {
-               occ = this.mEvents[i].event;
+           for (var i in this.mEventInfos) {
+               occ = this.mEventInfos[i].event;
                if (occ.hashId == aOccurrence.hashId)
                {
                    itemIndex = i;
                    break;
                }
            }
 
            if (itemIndex != -1) {
                delete this.mSelectedItemIds[occ.hashId];
                function isNotItem(a) {
                   return a.occurrence.hashId != aOccurrence.hashId;
                }
                this.mSelectedChunks = this.mSelectedChunks.filter(isNotItem);
 
-               this.mEvents.splice(itemIndex, 1);
+               this.mEventInfos.splice(itemIndex, 1);
                return true;
            } else {
                return false;
            }
         ]]></body>
       </method>
 
       <method name="recalculateStartEndMinutes">
         <body><![CDATA[
-          for each (var chunk in this.mEvents) {
+          for each (var chunk in this.mEventInfos) {
               var mins = this.getStartEndMinutesForOccurrence(chunk.event);
               chunk.startMinute = mins.start;
               chunk.endMinute = mins.end;
           }
 
           this.relayout();
         ]]></body>
       </method>
@@ -595,17 +603,17 @@
       </method>
 
       <method name="addEvent">
         <parameter name="aOccurrence"/>
         <body><![CDATA[
            this.internalDeleteEvent(aOccurrence);
 
            var chunk = this.createChunk(aOccurrence);
-           this.mEvents.push(chunk);
+           this.mEventInfos.push(chunk);
            if (this.mEventMapTimeout) {
                clearTimeout(this.mEventMapTimeout);
            }
            var column = this;
 
            if (this.mCreatedNewEvent) {
                this.mEventToEdit = aOccurrence;
            }
@@ -643,19 +651,19 @@
 
           var orient = this.getAttribute("orient");
           this.bgbox.setAttribute("orient", orient);
 
           // bgbox is used mainly for drawing the grid.  at some point it may
           // also be used for all-day events.
           var otherorient = getOtherOrientation(orient);
           var configBox = document.getAnonymousElementByAttribute(this, "anonid", "config-box");
-          configBox.removeAttribute("hidden");          
+          configBox.removeAttribute("hidden");
           var minSize = configBox.getOptimalMinSize();
-          configBox.setAttribute("hidden", "true");          
+          configBox.setAttribute("hidden", "true");
           this.mMinDuration = Components.classes["@mozilla.org/calendar/duration;1"]
                                             .createInstance(Components.interfaces.calIDuration);
           this.mMinDuration.minutes = parseInt(minSize/this.mPixPerMin);
 
           var theMin = this.mStartMin;
           while (theMin < this.mEndMin) {
               var dur = theMin % 60;
               theMin += dur;
@@ -865,44 +873,46 @@
            *    item:     the event/task
            *    startCol: the starting column to display the event in (0-indexed)
            *    colSpan:  the number of columns the item spans
            *
            * An item with no conflicts will have startCol: 0 and colSpan: 1.
            */
           var blobs = new Array();
           var currentBlob = new Array();
-          function sortByStart(a, b) {
-              function assureFloating(aDateTime) {
-                  if (aDateTime.timezone.isFloating) {
-                      aDateTime = aDateTime.getInTimezone(self.mTimezone);
-                  }
-                  return aDateTime;
-              }
+          function sortByStart(aEventInfo, bEventInfo) {
               // If you pass in tasks without both entry and due dates, I will
               // kill you
-              var aStart = a.event.startDate || a.event.entryDate;
-              aStart = assureFloating(aStart);
-              var bStart = b.event.startDate || b.event.entryDate;
-              bStart = assureFloating(bStart);
-              var startComparison = aStart.compare(bStart);
+              var startComparison = aEventInfo.layoutStart.compare(bEventInfo.layoutStart);
               if (startComparison != 0) {
                   return startComparison;
               } else {
-                  var aEnd = a.event.endDate || a.event.dueDate;
-                  aEnd = assureFloating(aEnd);
-                  var bEnd = b.event.endDate || b.event.dueDate;
-                  bEnd = assureFloating(bEnd);
                   // If the items start at the same time, return the longer one
                   // first
-                  return bEnd.compare(aEnd);
+                  return bEventInfo.layoutEnd.compare(aEventInfo.layoutEnd);
               }
           }
           var self = this;
-          this.mEvents.sort(sortByStart);
+          this.mEventInfos.forEach(function(aEventInfo) {
+              let item = aEventInfo.event.clone();
+              let start = item.startDate || item.entryDate;
+              start = start.getInTimezone(self.mTimezone);
+              aEventInfo.layoutStart = start;
+              let end = item.endDate || item.dueDate
+              end = end.getInTimezone(self.mTimezone);
+              let secEnd = start.clone();
+              secEnd.addDuration(self.mMinDuration);
+              if (secEnd.nativeTime > end.nativeTime) {
+                   aEventInfo.layoutEnd = secEnd;
+              } else {
+                   aEventInfo.layoutEnd = end;
+              }
+              return aEventInfo;
+          });
+          this.mEventInfos.sort(sortByStart);
 
           // The end time of the last ending event in the entire blob
           var latestItemEnd;
 
           // This array keeps track of the last (latest ending) item in each of
           // the columns of the current blob. We could reconstruct this data at
           // any time by looking at the items in the blob, but that would hurt
           // perf.
@@ -911,25 +921,25 @@
           /* Go through a 3 step process to try and place each item.
            * Step 1: Look for an existing column with room for the item.
            * Step 2: Look for a previously placed item that can be shrunk in
            *         width to make room for the item.
            * Step 3: Give up and create a new column for the item.
            *
            * (The steps are explained in more detail as we come to them)
            */
-          for (var i in this.mEvents) {
-              var item = this.mEvents[i].event;
-              var itemStart, itemEnd;
-              [itemStart, itemEnd] = this.getLayoutEnd(item);
+          for (var i in this.mEventInfos) {
+              var curItemInfo = {event: this.mEventInfos[i].event,
+                                 layoutStart: this.mEventInfos[i].layoutStart,
+                                 layoutEnd: this.mEventInfos[i].layoutEnd};
               if (!latestItemEnd) {
-                 latestItemEnd = itemEnd;
+                 latestItemEnd = curItemInfo.layoutEnd;
               }
               if (currentBlob.length && latestItemEnd &&
-                  itemStart.compare(latestItemEnd) != -1) {
+                  curItemInfo.layoutStart.compare(latestItemEnd) != -1) {
                   // We're done with this current blob because item starts
                   // after the last event in the current blob ended.
                   blobs.push({blob: currentBlob, totalCols: colEndArray.length});
 
                   // Reset our variables
                   currentBlob = new Array();
                   colEndArray = new Array();
               }
@@ -949,45 +959,45 @@
               //       |      |      |
               //       |OPEN! |      |<--Our item's start time might be here
               //       |      |______|
               //       |      |      |
               //
               // Remember that any time we're starting a new blob, colEndArray
               // will be empty, but that's ok.
               for (var ii = 0; ii < colEndArray.length; ++ii) {
-                  var colStart, colEnd;
-                  [colStart, colEnd] = this.getLayoutEnd(colEndArray[ii]);
-                  if (colEnd.compare(itemStart) != 1) {
+                  var colStart = colEndArray[ii].layoutStart;
+                  var colEnd = colEndArray[ii].layoutEnd;
+                  if (colEnd.compare(curItemInfo.layoutStart) != 1) {
                       // Yay, we can jump into this column
-                      colEndArray[ii] = item;
+                      colEndArray[ii] = curItemInfo;
 
                       // Check and see if there are any adjacent columns we can
                       // jump into as well.
                       var lastCol = Number(ii) + 1;
                       while (lastCol < colEndArray.length) {
-                          var nextColStart, nextColEnd;
-                          [nextColStart, nextColEnd] = this.getLayoutEnd(colEndArray[lastCol]);
+                          var nextColStart = colEndArray[lastCol].layoutStart;
+                          var nextColEnd = colEndArray[lastCol].layoutEnd;
                           // If the next column's item ends after we start, we
                           // can't expand any further
-                          if (nextColEnd.compare(itemStart) == 1) {
+                          if (nextColEnd.compare(curItemInfo.layoutStart) == 1) {
                               break;
                           }
-                          colEndArray[lastCol] = item;
+                          colEndArray[lastCol] = curItemInfo;
                           lastCol++;
                       }
                       // Now construct the info we need to push into the blob
-                      currentBlob.push({item: item,
+                      currentBlob.push({itemInfo: curItemInfo,
                                         startCol: ii,
                                         colSpan: lastCol - ii});
 
                       // Update latestItemEnd
                       if (latestItemEnd &&
-                          itemEnd.compare(latestItemEnd) == 1) {
-                          latestItemEnd = itemEnd;
+                          curItemInfo.layoutEnd.compare(latestItemEnd) == 1) {
+                          latestItemEnd = curItemInfo.layoutEnd;
                       }
                       placedItem = true;
                       break; // Stop iterating through colEndArray
                   }
               }
 
               if (placedItem) {
                   // Go get the next item
@@ -1006,40 +1016,40 @@
               //       |      |      |______|
               //       |      |_____________|
               //       |      |ev2          |
               //       |______|             |<--If our item's start time is
               //       |      |_____________|   here, we can shrink ev2 and jump
               //       |      |      |      |   in column #3
               //
               for (var jj=1; jj<colEndArray.length; ++jj) {
-                  if (colEndArray[jj].hashId == colEndArray[jj-1].hashId) {
+                  if (colEndArray[jj].event.hashId == colEndArray[jj-1].event.hashId) {
                       // Good we found a item that spanned multiple columns.
                       // Find it in the blob so we can modify its properties
                       for (var kk in currentBlob) {
-                          if (currentBlob[kk].item.hashId == colEndArray[jj].hashId) {
+                          if (currentBlob[kk].itemInfo.event.hashId == colEndArray[jj].event.hashId) {
                               // Take all but the first spot that the item spanned
                               var spanOfShrunkItem = currentBlob[kk].colSpan;
-                              currentBlob.push({item: item,
+                              currentBlob.push({itemInfo: curItemInfo,
                                                 startCol: Number(currentBlob[kk].startCol) + 1,
                                                 colSpan: spanOfShrunkItem - 1});
 
                               // Update colEndArray
                               for (var ll = jj; ll < jj + spanOfShrunkItem - 1; ll++) {
-                                  colEndArray[ll] = item;
+                                  colEndArray[ll] = curItemInfo;
                               }
 
                               // Modify the data on the old item
-                              currentBlob[kk] = {item: currentBlob[kk].item,
+                              currentBlob[kk] = {itemInfo: currentBlob[kk].itemInfo,
                                                  startCol: currentBlob[kk].startCol,
                                                  colSpan: 1};
                               // Update latestItemEnd
                               if (latestItemEnd &&
-                                  itemEnd.compare(latestItemEnd) == 1) {
-                                  latestItemEnd = itemEnd;
+                                  curItemInfo.layoutEnd.compare(latestItemEnd) == 1) {
+                                  latestItemEnd = curItemInfo.layoutEnd;
                               }
                               break; // Stop iterating through currentBlob
                           }
                       }
                       placedItem = true;
                       break; // Stop iterating through colEndArray
                   }
               }
@@ -1057,66 +1067,42 @@
               // conflicts with the item we're trying to place, need to have
               // their span extended by 1, since we're adding the new column
               //
               // * Note that there can only be one, because we sorted our
               //   events by start time, so this event must start later than
               //   the start of any possible conflicts.
               var lastColNum = colEndArray.length;
               for (var mm in currentBlob) {
-                  var mmStart, mmEnd;
-                  [mmStart, mmEnd] = this.getLayoutEnd(currentBlob[mm].item);
+                  var mmStart = currentBlob[mm].itemInfo.layoutStart;
+                  var mmEnd = currentBlob[mm].itemInfo.layoutEnd;
                   if (currentBlob[mm].startCol + currentBlob[mm].colSpan == lastColNum &&
-                      mmEnd.compare(itemStart) != 1) {
-                      currentBlob[mm] = {item: currentBlob[mm].item,
+                      mmEnd.compare(curItemInfo.layoutStart) != 1) {
+                      currentBlob[mm] = {itemInfo: currentBlob[mm].itemInfo,
                                          startCol: currentBlob[mm].startCol,
                                          colSpan: currentBlob[mm].colSpan + 1};
                   }
               }
-              currentBlob.push({item: item,
+              currentBlob.push({itemInfo: curItemInfo,
                                 startCol: colEndArray.length,
                                 colSpan: 1});
-              colEndArray.push(item);
+              colEndArray.push(curItemInfo);
 
               // Update latestItemEnd
-              if (latestItemEnd && itemEnd.compare(latestItemEnd) == 1) {
-                  latestItemEnd = itemEnd;
+              if (latestItemEnd && curItemInfo.layoutEnd.compare(latestItemEnd) == 1) {
+                  latestItemEnd = curItemInfo.layoutEnd;
               }
               // Go get the next item
           }
           // Add the last blob
           blobs.push({blob: currentBlob,
                       totalCols: colEndArray.length});
-
           return this.setupBoxStructure(blobs);
         ]]></body>
       </method>
-
-      <method name="getLayoutEnd">
-        <parameter name="aItem"/>
-        <body><![CDATA[
-          var item = aItem.clone();
-          var start = item.startDate || item.entryDate;
-          if (!compareObjects(start.timezone, this.mTimezone)) {
-              start = start.getInTimezone(this.mTimezone);
-          }
-          var end = item.endDate || item.dueDate
-          if (!compareObjects(end.timezone, this.mTimezone)) {
-              end = end.getInTimezone(this.mTimezone);
-          }
-          var secEnd = start.clone();
-          secEnd.addDuration(this.mMinDuration);
-          if (secEnd.nativeTime > end.nativeTime) {
-               return [start, secEnd];
-          } else {
-               return [start, end];
-          }
-        ]]></body>
-      </method>
-
       <method name="setupBoxStructure">
         <parameter name="aBlobs"/>
         <body><![CDATA[
           // This is actually going to end up being a 3-d array
           // 1st dimension: "layers", sets of columns of events that all
           //                should have equal width*
           // 2nd dimension: "columns", individual columns of non-conflicting
           //                items
@@ -1184,18 +1170,18 @@
                   var col = layers[layerIndex][data.startCol];
                   if (specialSpan) {
                       col.specialSpan = specialSpan;
                   }
 
                   // take into account that items can span several days.
                   // that's why i'm clipping the start- and end-time to the
                   // timespan of this column.
-                  var start, end;
-                  [start, end] = this.getLayoutEnd(data.item);
+                  var start = data.itemInfo.layoutStart;
+                  var end = data.itemInfo.layoutEnd;
                   if (start.year  != this.date.year ||
                       start.month != this.date.month ||
                       start.day   != this.date.day) {
                       start = start.clone();
                       start.resetTo(this.date.year,
                                     this.date.month,
                                     this.date.day,
                                     0,this.mStartMin,0,
@@ -1232,17 +1218,17 @@
                   var floatstart = start.clone();
                   floatstart.timezone = floating();
                   var dur = floatstart.subtractDate(prevEnd);
                   if (dur.inSeconds) {
                       col.push({duration: dur});
                   }
                   var floatend = end.clone();
                   floatend.timezone = floating();
-                  col.push({event: data.item,
+                  col.push({event: data.itemInfo.event,
                             endDate: end,
                             duration: floatend.subtractDate(floatstart)});
               }
               layerOffset = layers.length;
           }
           return layers;
         ]]></body>
       </method>
@@ -3144,17 +3130,17 @@
         <parameter name="aVal"/>
         <body><![CDATA[
           var needsreorient = false;
           var needsrelayout = false;
           if (aAttr == "orient") {
               if (this.getAttribute("orient") != aVal)
                   needsreorient = true;
           }
-          
+
           if (aAttr == "context" || aAttr == "item-context")
               needsrelayout = true;
 
           // this should be done using lookupMethod(), see bug 286629
           var ret = XULElement.prototype.setAttribute.call (this, aAttr, aVal);
 
           if (needsrelayout && !needsreorient)
               this.relayout();
@@ -3344,17 +3330,17 @@
           var headerboxkids = headerdaybox.childNodes;
           var labelboxkids = labeldaybox.childNodes;
 
           for each (var d in computedDateList) {
               var dayEventsBox;
               if (counter < dayboxkids.length) {
                   dayEventsBox = dayboxkids[counter];
                   dayEventsBox.removeAttribute("relation");
-                  dayEventsBox.mEvents = new Array();
+                  dayEventsBox.mEventInfos = new Array();
               } else {
                   dayEventsBox = createXULElement("calendar-event-column");
                   dayEventsBox.setAttribute("flex", "1");
                   daybox.appendChild(dayEventsBox);
               }
               setUpDayEventsBox(dayEventsBox);
 
               var dayHeaderBox;