[Commits] (alecf) - Rudimentary support for indenting of calendar items (bug 2579) based on conflicts between events

commits at osafoundation.org commits at osafoundation.org
Wed Apr 6 10:59:50 PDT 2005


Commit by: alecf
Modified files:
chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py 1.52 1.53

Log message:
- Rudimentary support for indenting of calendar items (bug 2579) based on conflicts between events
- simplify some of the rect-generating loop with enumerate()
- break up drawing loop into a few loops because you have to generate conflict data before drawing
- add a boundsrect to ColumnarCanvasItem so that we don't try to draw rectangles that aren't on screen. A nice side effect is that we get event titles on the 2nd day of multi-day events when in the day view


Bugzilla links:
http://bugzilla.osafoundation.org/show_bug.cgi?id=2579

ViewCVS links:
http://cvs.osafoundation.org/index.cgi/chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py.diff?r1=text&tr1=1.52&r2=text&tr2=1.53

Index: chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py
diff -u chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py:1.52 chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py:1.53
--- chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py:1.52	Tue Apr  5 12:07:12 2005
+++ chandler/parcels/osaf/framework/blocks/calendar/CalendarCanvas.py	Wed Apr  6 10:59:49 2005
@@ -1,8 +1,8 @@
 """ Canvas for calendaring blocks
 """
 
-__version__ = "$Revision: 1.52 $"
-__date__ = "$Date: 2005/04/05 19:07:12 $"
+__version__ = "$Revision: 1.53 $"
+__date__ = "$Date: 2005/04/06 17:59:49 $"
 __copyright__ = "Copyright (c) 2004 Open Source Applications Foundation"
 __license__ = "http://osafoundation.org/Chandler_0.1_license_terms.htm"
 
@@ -21,7 +21,19 @@
     Base class for calendar items. Covers:
     - editor position & size
     - text wrapping
+    - conflict management
     """
+    
+    def __init__(self, *args, **keywords):
+        super(CalendarCanvasItem, self).__init__(*args, **keywords)
+        self._parentConflicts = []
+        # the rating of conflicts - i.e. how far to indent this
+        self._conflictDepth = 0
+                
+        # the total depth of all conflicts - i.e. the maximum simultaneous 
+        # conflicts with this item, including this one
+        self._totalConflictDepth = 1
+        
     def GetEditorPosition(self):
         """
         This returns a location to show the editor. By default it is the same
@@ -97,6 +109,44 @@
             if width <= maxWidth:
                 dc.DrawText(smallWord, x, y)
                 return
+                
+    def AddConflict(self, child):
+        # we might want to keep track of the inverse conflict as well,
+        # for conflict bars
+        child._parentConflicts.append(self)
+        
+    def FindFirstGapInSequence(self, seq):
+        """
+        Look for the first gap in a sequence - for instance
+         0,2,3: choose 1
+         1,2,3: choose 0
+         0,1,2: choose 3        
+        """
+        for index, value in enumerate(seq):
+            if index != value:
+                return index
+                
+        # didn't find any gaps, so just put it one higher
+        return index+1
+        
+    def CalculateConflictDepth(self):
+        if not self._parentConflicts:
+            return 0
+        
+        # We'll find out the depth of all our parents, and then
+        # see if there's an empty gap we can fill
+        # this relies on parentDepths being sorted, which 
+        # is true because the conflicts are added in 
+        # the same order as the they appear in the calendar
+        parentDepths = [parent._conflictDepth for parent in self._parentConflicts]
+        self._conflictDepth = self.FindGapInSequence(parentDepths)
+        return self._conflictDepth
+        
+    def GetIndentLevel(self):
+        # this isn't right. but its a start
+        # it should be some wierd combination of 
+        # maximum indent level of all children + 1
+        return self._conflictDepth
         
 
 class ColumnarCanvasItem(CalendarCanvasItem):
@@ -107,8 +157,13 @@
     def __init__(self, item, calendarCanvas, *arguments, **keywords):
         super(ColumnarCanvasItem, self).__init__(None, item)
         
-        
-        self._boundsRects = list(self.GenerateBoundsRects(calendarCanvas))
+        # this is really annoying that we need to keep a reference back to 
+        # the calendar canvas in every single ColumnarCanvasItem, but we
+        # need it for drawing hints.. is there a better way?
+        self._calendarCanvas = calendarCanvas
+
+    def UpdateDrawingRects(self):
+        self._boundsRects = list(self.GenerateBoundsRects(self._calendarCanvas))
         self._bounds = self._boundsRects[0]
 
         r = self._boundsRects[-1]
@@ -183,33 +238,42 @@
             boundsStartTime = max(item.startTime, absDayStart)
             boundsEndTime = min(item.endTime, absDayEnd)
             
-            yield self.MakeRectForRange(calendarCanvas, boundsStartTime, boundsEndTime)
+            try:
+                yield self.MakeRectForRange(calendarCanvas, boundsStartTime, boundsEndTime)
+            except ValueError:
+                pass
         
     def MakeRectForRange(self, calendarCanvas, startTime, endTime):
         """
         Turn a datetime range into a rectangle that can be drawn on the screen
         """
-        
         startPosition = calendarCanvas.getPositionFromDateTime(startTime)
         
-        # need to factor this one out, but I'll wait until we've
-        # worked out a means to split the canvas item into multiple rectangles
+        # ultimately, I'm not sure that we should be asking the calendarCanvas
+        # directly for dayWidth and hourHeight, we probably need some system 
+        # instead similar to getPositionFromDateTime where we pass in a duration
         duration = (endTime - startTime).hours
         (cellWidth, cellHeight) = (calendarCanvas.dayWidth, int(duration * calendarCanvas.hourHeight))
+        
+        # Now handle indentation based on conflicts -
+        # we really need a way to proportionally size
+        # items, so that the right side of the rectangle
+        # shrinks as well
+        startPosition.x += self.GetIndentLevel() * 5
+        cellWidth -= self.GetIndentLevel() * 5
         return wx.Rect(startPosition.x, startPosition.y, cellWidth, cellHeight)
 
-    def Draw(self, dc, brushContainer):
+    def Draw(self, dc, boundingRect, brushContainer):
         item = self._item
 
         time = item.startTime
 
         # Draw one event - an event consists of one or more bounds
-        rectCount = len(self._boundsRects)
-        rectIndex = 0
+        lastRect = len(self._boundsRects) - 1
         radius = 10
         diameter = radius * 2
-        oldLogicalFunction = dc.GetLogicalFunction()
-        for itemRect in self._boundsRects:
+                       
+        for rectIndex, itemRect in enumerate(self._boundsRects):        
             
             dc.SetPen(brushContainer.selectionPen)
             dc.DrawRectangleRect(itemRect)
@@ -220,7 +284,7 @@
             hasTopRightRounded = hasBottomRightRounded = False
             if rectIndex == 0:
                 hasTopRightRounded = True
-            if rectIndex == rectCount - 1:
+            if rectIndex == lastRect:
                 hasBottomRightRounded = True
 
             # For now, we'll manually erase each corner, and then draw an arc
@@ -285,8 +349,6 @@
                 dc.SetFont(brushContainer.smallFont)
                 self.DrawWrappedText(dc, item.displayName, textRect)
 
-            rectIndex += 1
-
 class HeaderCanvasItem(CalendarCanvasItem):
     def Draw(self, dc, brushContainer):
         item = self._item
@@ -978,41 +1040,74 @@
 
         if self.parent.blockItem.dayMode:
             startDay = self.parent.blockItem.selectedDate
-            days = 1
+            endDay = startDay + DateTime.RelativeDateTime(days = 1)
         else:
             startDay = self.parent.blockItem.rangeStart
-            days = 7
+            endDay = startDay + self.parent.blockItem.rangeIncrement
         
         # Set up fonts and brushes for drawing the events
         dc.SetTextForeground(wx.BLACK)
         dc.SetBrush(wx.WHITE_BRUSH)
 
-        selectedBox = None
-        endDay = startDay + DateTime.RelativeDateTime(days = days)
-        
         # we sort the items so that when drawn, the later events are drawn last
         # so that we get proper stacking
         visibleItems = list(self.parent.blockItem.getItemsInRange(startDay, endDay))
         visibleItems.sort(self.sortByStartTime)
+                
+        boundingRect = wx.Rect(self.xOffset, 0, self.size.width, self.size.height)
+        
+        # First generate a sorted list of ColumnarCanvasItems
         for item in visibleItems:
                                                
             canvasItem = ColumnarCanvasItem(item, self)
             self.canvasItemList.append(canvasItem)
-            
+
             if self._currentDragBox and self._currentDragBox.GetItem() == item:
                 self._currentDragBox = canvasItem                
-                
+
+        # now generate conflict info
+        self.CheckConflicts()
+        
+        selectedBox = None        
+        # finally, draw the items
+        for canvasItem in self.canvasItemList:    
+
+            # drawing rects should be updated to reflect conflicts
+            canvasItem.UpdateDrawingRects()
+            
             # save the selected box to be drawn last
-            if self.parent.blockItem.selection is item:
+            if self.parent.blockItem.selection is canvasItem.GetItem():
                 selectedBox = canvasItem
             else:
-                canvasItem.Draw(dc, self)
+                canvasItem.Draw(dc, boundingRect, self)
             
         # now draw the current item on top of everything else
         if selectedBox:
             dc.SetBrush(self.selectionBrush)
-            selectedBox.Draw(dc, self)
+            selectedBox.Draw(dc, boundingRect, self)
             
+
+    def CheckConflicts(self):
+        for itemIndex, canvasItem in enumerate(self.canvasItemList):
+            # since these are sorted, we only have to check the items 
+            # that come after the current one
+            for innerItem in self.canvasItemList[itemIndex+1:]:
+                # we know we're done when we stop hitting conflicts
+                # 
+                # have a guarantee that innerItem.startTime >= item.endTime
+                # Since item.endTime < item.startTime, we know we're
+                # done
+                if innerItem.GetItem().startTime >= canvasItem.GetItem().endTime: break
+                
+                # item and innerItem MUST conflict now
+                canvasItem.AddConflict(innerItem)
+            
+            # we've now found all conflicts for item, do we need to calculate
+            # depth or anything?
+            # first theory: leaf children have a maximum conflict depth?
+            canvasItem.CalculateConflictDepth()
+
+
             
     # handle mouse related actions: move, resize, create, select
 
@@ -1163,9 +1258,15 @@
     def getPositionFromDateTime(self, datetime):
         if self.parent.blockItem.dayMode:
             startDay = self.parent.blockItem.selectedDate
+            endDay = startDay + DateTime.RelativeDateTime(days=1)
         else:
             startDay = self.parent.blockItem.rangeStart
+            endDay = startDay + self.parent.blockItem.rangeIncrement
         
+        if datetime < startDay or \
+           datetime > endDay:
+            raise ValueError, "Must be visible on the calendar"
+            
         delta = datetime - startDay
         x = (self.dayWidth * delta.day) + self.xOffset
         y = int(self.hourHeight * (datetime.hour + datetime.minute/float(60)))



More information about the Commits mailing list