Title: [commits] (jeffrey) [16100] [APP] Add more mouse handling
Revision
16100
Author
jeffrey
Date
2007-12-10 15:15:20 -0800 (Mon, 10 Dec 2007)

Log Message

[APP] Add more mouse handling
- Cache background seperately from drawn lozenges
- Add dragged-lozenge drawing, which currently works
for resized lozenges
- Add drag and drop mixin classes in anticipation of doing
drag and drop between all-day and timed canvases

Modified Paths

Added Paths

Diff

Modified: branches/rearchitecture/Chandler-App/ocap/chandler/calendar_main.py (16099 => 16100)

--- branches/rearchitecture/Chandler-App/ocap/chandler/calendar_main.py	2007-12-10 22:12:17 UTC (rev 16099)
+++ branches/rearchitecture/Chandler-App/ocap/chandler/calendar_main.py	2007-12-10 23:15:20 UTC (rev 16100)
@@ -75,7 +75,7 @@
         # available to PyCrust.
         from ocap.debugging import Debugger
         debugger = Debugger(app)
-        debugger.variables['miniCalendarPresentation'] = minicalendar
+        debugger.variables['CalendarPresentation'] = calendar
         debugger.variables['interactionApp'] = iApp
 
 if __name__ == '__main__':

Modified: branches/rearchitecture/Chandler-Platform/ocap/wxui/calendar_canvas.py (16099 => 16100)

--- branches/rearchitecture/Chandler-Platform/ocap/wxui/calendar_canvas.py	2007-12-10 22:12:17 UTC (rev 16099)
+++ branches/rearchitecture/Chandler-Platform/ocap/wxui/calendar_canvas.py	2007-12-10 23:15:20 UTC (rev 16100)
@@ -15,10 +15,11 @@
 import wx
 import bisect
 from math import radians
-from datetime import datetime, timedelta
+from datetime import datetime, timedelta, time
 from colorsys import hsv_to_rgb
 from drawing import DrawWrappedText, DrawClippedText, getFont
 from button import BitmapButton
+import drag_and_drop
 from wx.lib.stattext import GenStaticText
 import wx.lib.newevent
 from wx import colheader
@@ -44,7 +45,7 @@
 
 MARGIN_LEFT = 40.0
 
-TEXT_X_MARGIN = 3.5
+TEXT_X_MARGIN = 1.5
 TEXT_Y_MARGIN = 1
 
 TIME_Y_MARGIN = 2
@@ -225,16 +226,17 @@
 
 lozenge_cache = {}
 
-class Calendar(wx.ScrolledWindow):
+class Calendar(drag_and_drop.DropReceiveWidget, wx.ScrolledWindow):
     def __init__(self, time_formatter=None, *arguments, **keywords):
-        super(Calendar, self).__init__ (*arguments, **keywords)
+        super(Calendar, self).__init__(*arguments, **keywords)
 
         self.Bind(wx.EVT_PAINT, self.OnPaint)
         self.Bind(wx.EVT_SIZE,  self.OnSize)
         self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x: None)
-        self.Bind(wx.EVT_LEFT_DOWN, self.OnClick)
         self.Bind(EVT_SELECT_ITEMS, self.OnSelectItems)
-
+        
+        self.InitDragging()
+        
         self.events = {}
         self.selected_ids = []
         # EventsForDay or EventsForWeek associated with the current range
@@ -243,6 +245,7 @@
         self.matrix = None
         self.hour_height = BASE_HOUR_HEIGHT
         self.margin_right = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
+        self.width = 0
 
         self.business_start = 9
         self.business_end   = 17
@@ -270,18 +273,21 @@
             self.time_formatter = time_formatter
 
         # caching
-        self.draw_from_cache = False
-        self._buffer = None
+        self._combined_buffer = None
+        self.draw_lozenges_from_cache = False
+        self._lozenge_buffer = None
+        self._background_buffer = None
 
-    def Refresh(self, *args):
+    def Refresh(self, eraseBackground=False, keep_cache=False):
         """
         Set a flag so we know if the paint event is happening because of a
         programatic call to Refresh, or if it is a 'natural' refresh due to
         things like scrolling or the window being damaged by another window. 
 
         """
-        self.draw_from_cache = False
-        super(Calendar, self).Refresh(*args)
+        if not keep_cache:
+            self.draw_lozenges_from_cache = False
+        super(Calendar, self).Refresh(eraseBackground)
 
     def OnPaint(self, event):
         # look more carefully at the dirty area
@@ -292,57 +298,109 @@
 
         updateRect = updateRegion.Box
 
-        dc = wx.BufferedPaintDC(self)
+        # figure out what size of bitmap we need, plus a little
+        # extra space for scrolling overflow
+        sz = self.GetVirtualSize()
+        ppux, ppuy = self.GetScrollPixelsPerUnit()
+        sz.height += ppuy + 1
+        width, height = sz
+        # make a new bitmap of that size if needed
+        if not self._combined_buffer or sz != self._combined_buffer.Size:
+            self._combined_buffer = wx.EmptyBitmap(*sz)
+        combined_dc = wx.MemoryDC(self._combined_buffer)
         
-        if self.draw_from_cache:
-            mdc = wx.MemoryDC(self._buffer)
-        else:
-            # figure out what size of bitmap we need, plus a little
-            # extra space for scrolling overflow
-            sz = self.GetVirtualSize()
-            ppux, ppuy = self.GetScrollPixelsPerUnit()
-            #sz.width += ppux + 1
-            sz.height += ppuy + 1
-            # make a new bitmap of that size if needed
-            if not self._buffer or sz != self._buffer.GetSize():
-                self._buffer = wx.EmptyBitmap(*sz)
-            # draw the canvas to that bitmap
-            mdc = wx.MemoryDC(self._buffer)
+        if not self.matrix:
+            gc = wx.GraphicsContext.Create(combined_dc)
+            self.matrix = gc.GetTransform()
+            # create a matrix to transform rectangles with. The Mac default
+            # transform is a y-flip, so explicitly set the initial matrix
+            self.matrix.Set(1,0,0,1,0,0)
+            self.matrix.Translate(MARGIN_LEFT, 0)
+            self.matrix.Scale((width - MARGIN_LEFT - self.margin_right)/BASE_WIDTH,
+                              self.hour_height/BASE_HOUR_HEIGHT)
+
+        if not self._background_buffer or sz != self._background_buffer.Size:
+            self._background_buffer = wx.EmptyBitmap(*sz)
+            mdc = wx.MemoryDC(self._background_buffer)
             mdc.SetBackground(wx.WHITE_BRUSH)
             mdc.Clear()
-            self.DrawCanvas(mdc)
-            self.draw_from_cache = True
+            self.DrawBackground(wx.GraphicsContext.Create(mdc))
+            combined_dc.Blit(0, 0, width, height, mdc, 0, 0)
+        else:
+            combined_dc.DrawBitmap(self._background_buffer, 0, 0)
 
+        if self.draw_lozenges_from_cache:
+            combined_dc.DrawBitmap(self._lozenge_buffer, 0, 0)
+        else:
+            self._lozenge_buffer = transparent_bitmap(width, height)
+            # draw the canvas to that bitmap
+            mdc = wx.MemoryDC(self._lozenge_buffer)
+            self.DrawNotDraggedLozenges(mdc)
+            combined_dc.DrawBitmap(self._lozenge_buffer, 0, 0)
+            self.draw_lozenges_from_cache = True
+
+        self.DrawDraggedLozenge(combined_dc)
+
+        dc = wx.BufferedPaintDC(self)
         self.PrepareDC(dc)
         dx = dc.DeviceToLogicalX(updateRect.x)
         dy = dc.DeviceToLogicalY(updateRect.y)
-        dc.Blit(dx, dy, updateRect.width, updateRect.height, mdc, dx, dy)
+        dc.Blit(dx, dy, updateRect.width, updateRect.height, combined_dc, dx, dy)
 
-    def DrawCanvas(self, dc):
-        dc.SetBackground(wx.WHITE_BRUSH)
-        dc.Clear()
+
+    def DrawNotDraggedLozenges(self, dc):
         gc = wx.GraphicsContext.Create(dc)
-
-        width, height = self.GetSize()
-
-        self.matrix = gc.GetTransform()
-        # create a matrix to transform rectangles with. The Mac default
-        # transform is a y-flip, so explicitly set the initial matrix
-        self.matrix.Set(1,0,0,1,0,0)
-        self.matrix.Translate(MARGIN_LEFT, 0)
-        self.matrix.Scale((width - MARGIN_LEFT - self.margin_right)/BASE_WIDTH,
-                          self.hour_height/BASE_HOUR_HEIGHT)
-
-        self.DrawBackground(gc)
-
         for interval in self.intervals:
             for cluster in interval.clusters:
                 max_depth = len(cluster.levels)
                 for depth, event in cluster:
-                    args = event, interval, depth, max_depth
-                    rect, truncate, draw_text = self.LozengeGeometry(*args)
-                    self.DrawLozenge(gc, event, rect, truncate, draw_text)
+                    if event.id != self.drag_id:
+                        args = event, interval, depth, max_depth
+                        rect, truncate, draw_text = self.LozengeGeometry(*args)
+                        self.DrawLozenge(gc, event, rect, truncate, draw_text)
 
+    def DrawDraggedLozenge(self, dc):
+        if self.drag_id:
+            gc = wx.GraphicsContext.Create(dc)
+            drag_time = self.TimeFromPosition(self.drag_position)
+            if self.drag_type == 'resize' and drag_time:
+                real_event = self.events.get(self.drag_id)
+                drag_event = Event(id=None)
+                for slot in drag_event.__slots__:
+                    if slot != 'id':
+                        setattr(drag_event, slot, getattr(real_event, slot))
+                drag_time = self.TimeFromPosition(self.drag_position)
+                if self.drag_edge in (wx.TOP, wx.LEFT):
+                    drag_event.duration = max(timedelta(0), drag_event.end - drag_time)
+                    max_start = real_event.end
+                    if self.drag_edge == wx.LEFT:
+                        max_start -= timedelta(1)
+                    drag_event.start = min(drag_time, max_start)
+                else:
+                    drag_event.duration = max(timedelta(0),
+                                              drag_time - drag_event.start)
+                    if self.drag_edge == wx.RIGHT:
+                        drag_event.duration += timedelta(1)
+                depth, max_depth = self.EventDepth(self.drag_id)
+                for interval in self.intervals:
+                    if drag_event.overlaps(interval):                            
+                        args = drag_event, interval, depth, max_depth
+                        rect, truncate, draw_text = self.LozengeGeometry(*args)
+                        self.DrawLozenge(gc, drag_event, rect, truncate,
+                                         draw_text, dragging=True)
+
+    def EventDepth(self, event_id):
+        """Return the depth, max_depth tuple for the event's first lozenge."""
+        event = self.events.get(event_id)
+        if event:
+            for interval in self.intervals:
+                if event.overlaps(interval):
+                    for cluster in interval.clusters:
+                        max_depth = len(cluster.levels)
+                        for depth, events in enumerate(cluster.levels):
+                            if event_id in (e.id for e in events):
+                                return depth, max_depth
+
     def RectFromTime(self, start, end, depth=1, max_depth=1):
         duration = end - start
         hours = duration.seconds / 3600.0 + 24.0*duration.days
@@ -371,6 +429,20 @@
 
         return TransformRect(self.matrix, rect)
 
+    def TimeFromPosition(self, point):
+        self.matrix.Invert()
+        x, y = self.matrix.TransformPoint(*point)
+        self.matrix.Invert()
+
+        if x < 0 or x > BASE_DAY_WIDTH*7:
+            # outside the week range
+            return None
+        
+        day = self.range_start
+        if self.range_mode == 'week':
+            day += timedelta(1) * int(x / BASE_DAY_WIDTH)
+        return day + timedelta(hours=(y / self.hour_height))
+
     def SidesToTruncate(self, real_start, real_end, lozenge_start, lozenge_end):
         truncate = 0 if real_start >= lozenge_start else wx.TOP
         return truncate | (0 if real_end <= lozenge_end else wx.BOTTOM)
@@ -390,8 +462,9 @@
 
         truncate = self.SidesToTruncate(event.start, event.end, start, end)
         return tuple(rect), truncate, event.start >= start or column == 0
-    
-    def DrawLozenge(self, real_gc, event, rect_tup, truncate, draw_text):
+
+    def DrawLozenge(self, gc, event, rect_tup, truncate, draw_text,
+                    dragging=False):
         rect = wx.Rect2D(*rect_tup)
         bitmap_margin = int(HALO_WIDTH)
         w = int(rect.width) + 2*(bitmap_margin + 1)
@@ -400,59 +473,114 @@
         draw_rect = wx.Rect2D(int(rect.x) - bitmap_margin,
                               int(rect.y) - bitmap_margin, w, h)
         
-        colors = (DefaultSelectedColors if event.id in self.selected_ids
-                                        else DefaultColors)
+        selected = not event.id or event.id in self.selected_ids
+        colors = DefaultSelectedColors if selected else DefaultColors
 
-        cache_key = (event.id, rect_tup, truncate, draw_text, colors)
+        cache_key = (event.id, rect_tup, truncate, draw_text, colors, dragging)
         bitmap = lozenge_cache.get(cache_key)
-        if bitmap:
-            real_gc.DrawBitmap(bitmap, *draw_rect)
-            return
-        
-        bitmap = lozenge_cache[cache_key] = transparent_bitmap(w, h)
-        rect.x = rect.x - int(rect.x) + bitmap_margin
-        rect.y = rect.y - int(rect.y) + bitmap_margin
-        
-        mdc = wx.MemoryDC(bitmap)
-        gc = wx.GraphicsContext.Create(mdc)
-        
+        if bitmap and event.id:
+            gc.DrawBitmap(bitmap, *draw_rect)
+        else:
+            bitmap = transparent_bitmap(w, h)
+            if event.id:
+                lozenge_cache[cache_key] = bitmap
+
+            rect.x = rect.x - int(rect.x) + bitmap_margin
+            rect.y = rect.y - int(rect.y) + bitmap_margin
+            
+            mdc = wx.MemoryDC(bitmap)
+            bitmap_gc = wx.GraphicsContext.Create(mdc)
+            
+            self.DrawLozengeNoTimes(bitmap_gc, event, colors, rect, truncate,
+                                    draw_text, dragging)
+            gc.DrawBitmap(bitmap, *draw_rect)
+
+        if draw_text:
+            self.DrawTimesInLozenge(gc, rect_tup, colors, event.start,
+                                    event.end, dragging)
+
+    def DrawLozengeNoTimes(self, gc, event, colors, rect, truncate, draw_text,
+                           dragging):
         # Room for how many swatches (min 2)?  Draw swatches
         if not draw_text:
             # continuation lozenge, just draw a small-radius lozenge
             DrawFilledLozenge(gc, rect, SMALL_RADIUS, colors, truncate)
         else:
-            line_height = self.title_font_height
-            min_height = 2*TEXT_Y_MARGIN + line_height
+            radius = self.RadiusFromLozengeHeight(rect.height)
+            DrawFilledLozenge(gc, rect, radius, colors, truncate)
             
-            radius = SMALL_RADIUS if rect.height > min_height else rect.height/2
-            DrawFilledLozenge(gc, rect, radius, colors, truncate)
-
-            time_height = 2*TIME_Y_MARGIN + self.time_font_height
-
-            rect.Inset(TEXT_X_MARGIN - 1, 0)
-            gc.Clip(*rect)
-            rect.Inset(1, 0)
-            if rect.height <= time_height + line_height:
+            start_rect, text_rect, end_rect = self.GetTextRects(rect, dragging)
+            gc.SetFont(self.title_font, colors.text)
+            gc.Clip(*larger_rect(text_rect))
+            if not start_rect:
                 # room for just one line of text
-                rect.Inset(radius/3, 0, 0, 0)
-                text_top = rect.GetCentre()[1] - line_height/2
-                gc.SetFont(self.title_font, DefaultColors.text)
-                DrawClippedText(gc, event.title, rect.x, text_top, rect.width)
+                text_top = rect.GetCentre()[1] - self.title_font_height/2
+                DrawClippedText(gc, event.title, text_rect.x, text_top, text_rect.width)
             else:
-                rect.Inset(radius/3, TIME_Y_MARGIN, 0, 0)
-                time_text = self.time_formatter(event.start)
-                gc.SetFont(self.time_font, DefaultColors.text)
-                gc.DrawText(time_text, rect.x, rect.y)
-                width, height = gc.GetTextExtent(time_text)
-                rect.Inset(0, TIME_Y_MARGIN + height, 0, 0)
-                gc.SetFont(self.title_font, DefaultColors.text)
-                # need to wrap text
-                DrawWrappedText(gc, event.title, rect)
+                DrawWrappedText(gc, event.title, text_rect)
+            gc.ResetClip()
 
+    def DrawTimesInLozenge(self, gc, rect, colors, start, end, dragging):
+        start_rect, text_rect, end_rect = self.GetTextRects(rect, dragging)
+        gc.SetFont(self.time_font, colors.text)
+        if dragging:
+            if self.drag_edge == wx.TOP:
+                start = roundTime(start)
+            elif self.drag_edge == wx.BOTTOM:
+                end = roundTime(end)
+            else:
+                old_start = start
+                start = roundTime(start)
+                end = start + (end - old_start)
+
+        if start_rect:
+            gc.Clip(*start_rect)
+            start_rect.Inset(1, 0, 0, 0)
+            gc.SetFont(self.time_font, DefaultColors.text)
+            time_text = self.time_formatter(start)
+            gc.DrawText(time_text, start_rect.x, start_rect.y + TIME_Y_MARGIN)
             gc.ResetClip()
+        if end_rect:
+            end_rect.Inset(1, 0, 0, 0)
+            gc.Clip(*end_rect)
+            time_text = self.time_formatter(end)
+            width, height = gc.GetTextExtent(time_text)
+            end_rect.Inset(max(0, text_rect.width - width), 0, 0, 0)
+            gc.DrawText(time_text, end_rect.x, end_rect.y)
+            gc.ResetClip()
 
-        real_gc.DrawBitmap(bitmap, *draw_rect)
+    def RadiusFromLozengeHeight(self, height):
+        min_height = 2*TEXT_Y_MARGIN + self.title_font_height        
+        return SMALL_RADIUS if height > min_height else height/2
 
+    def GetTextRects(self, rect, dragging):
+        """Split a lozenge rectangle into (start_rect, text_rect, end_rect).
+        
+        start_rect may be None if there isn't room to draw it, end_rect will be
+        None if dragging is False or there isn't room to draw it.
+        
+        """
+        rect = wx.Rect2D(*rect)
+        h = rect.height
+        time_height = 2*TIME_Y_MARGIN + self.time_font_height
+        rect.Inset(TEXT_X_MARGIN + self.RadiusFromLozengeHeight(h)/2, 0)
+        start_rect = end_rect = None
+        if h > time_height + self.title_font_height:
+            start_rect = wx.Rect2D(*rect)
+            start_rect.height = time_height
+            rect.SetTop(start_rect.GetBottom())
+            if dragging:
+                end_rect = wx.Rect2D(*rect)
+                top = max(rect.GetBottom() - self.time_font_height,
+                          start_rect.GetBottom())
+                end_rect.SetTop(top)
+                rect.SetBottom(top)
+        else:
+            start_rect = end_rect = None
+            text_rect = rect
+        return (start_rect, rect, end_rect)
+
+
     def DrawBackground(self, gc):
         gc.SetPen(wx.Pen(MINOR_LINE_COLOR))
         gc.StrokePath(self.GetHalfHourLinePath(gc))
@@ -545,11 +673,16 @@
                 gc.StrokeLine(10, down, left - 3, down)
 
     def OnSize(self, event):
-        lozenge_cache.clear()
-        width, height = self.GetSize()
-        self.SetVirtualSize((width, self.hour_height * 24))
+        old_width = self.width
+        self.width, height = self.GetSize()
+        self.SetVirtualSize((self.width, self.hour_height * 24))
         self.SetScrollRate(0, self.hour_height / 3)
-        self.Refresh(False)
+        if old_width != self.width:
+            lozenge_cache.clear()
+            self.matrix = None
+            self.Refresh()
+        else:
+            self.Refresh(keep_cache=True)
 
     def GetDays(self):
         return 7 if self.range_mode == 'week' else 1
@@ -584,40 +717,16 @@
                                 if event.id in new_local:
                                     cluster.front_event = event
                                     break
-            self.Refresh(False)
+            self.Refresh()
 
-    def TopItemFromPosition(self, pos):
-        def match(obj, depth, constrain, max_depth=0):
-            start, end = obj.start, obj.end
-            if constrain:
-                start = max(constrain.start, start)
-                end = min(constrain.end, end)
-            return self.RectFromTime(start, end, depth, max_depth).Contains(pos)
-            
-        def matches(iterable, depth=0, constrain=None):
-            for obj in iterable:
-                if match(obj, depth, constrain):
-                    yield obj
-                    break
-
-        for interval in matches(self.intervals, -1):
-            for cluster in matches(interval.clusters, -1, constrain=interval):
-                for depth, event in reversed(cluster):
-                    if match(event, depth, interval, len(cluster.levels)):
-                        return event
-
-    def OnClick(self, wx_evt):
-        position = wx.Point2D(*self.CalcUnscrolledPosition(wx_evt.Position))
-        event = self.TopItemFromPosition(position)
-        selected_id = None if not event else event.id
-        wx.PostEvent(self, SelectItemsEvent(selected_ids=[selected_id]))
-
     def SetRange(self, start):
         self.range_start = start
         self.Reset()
 
     def SetRangeMode(self, range_mode, start=None):
-        self.range_mode = range_mode
+        if self.range_mode != range_mode:
+            self.range_mode = range_mode
+            self._background_buffer = None
         if start is None:
             start = self.range_start
         if start is not None:
@@ -636,7 +745,7 @@
         self.intervals = [EventsForDay(self.range_start + timedelta(day))
                           for day in xrange(self.GetDays())]
         self.FillIntervals()
-        self.Refresh(False)
+        self.Refresh()
 
     def SetEvents(self, events):
         self.events = events
@@ -658,16 +767,154 @@
         for interval in self.intervals:
             if event.overlaps(interval):
                 interval.change(event)
-                self.Refresh(False)
+                self.Refresh()
             elif event in interval.event_list:
                 interval.remove(event)
-                self.Refresh(False)
+                self.Refresh()
 
     def RemoveEvent(self, id):
         """Remove an event from the events displayed."""
         pass
 
+    ######### Mouse and dragging methods ###########
+    draggable_edges = wx.TOP, wx.BOTTOM    
+
+    def InitDragging(self):
+        self.Bind(wx.EVT_LEFT_DOWN, self.OnClick)
+        self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse)
+        self.default_cursor = wx.StockCursor(wx.CURSOR_DEFAULT)
+        self.top_bottom_cursor = wx.StockCursor(wx.CURSOR_SIZENS)
+        self.left_right_cursor = wx.StockCursor(wx.CURSOR_SIZEWE)
+        self.cursor = self.default_cursor
+        
+        self.ResetDrag()
+
+    def ResetDrag(self):
+        self.drag_id = None
+        self.drag_type = None
+        self.drag_edge = None
+        self.dragging = False
+        self.click_position = None
+        self.drag_position = None
+
+    def OnMouse(self, event):
+        threshold = 5
+        position = wx.Point2D(*self.CalcUnscrolledPosition(event.GetPosition()))
+        if event.LeftDown():
+            self.click_position = position
+            print "left down", self.TimeFromPosition(position)
+        elif event.LeftUp() or event.Entering() or event.Leaving():
+            self.ResetDrag()
+            self.Refresh()
+        if event.Dragging():
+            if not self.click_position:
+                print "dragging, but nothing to do"
+            elif (not self.dragging and
+                  self.click_position.GetDistance(position) < 10):
+                print "dragging within threshold"
+            elif not self.drag_type:
+                edge = self.DragEdge(self.click_position)
+                if edge:
+                    self.drag_type = 'resize'
+                    ev, depth, max_depth, range, rect = self.TopInfoFromPosition(self.click_position)
+                    self.drag_id = ev.id
+                    self.drag_edge = edge
+                    self.dragging = True
+                    self.Refresh()
+
+            if self.drag_type == 'resize':
+                self.drag_position = position
+                self.Refresh(keep_cache=True)
+            
+            else:
+                pass
+                #drag_and_drop.DoDragAndDrop(self, 'phantom_lozenge')
+                #print "done with DragAndDrop"
+                
+        elif event.Moving():
+            self.UpdateCursor(position)
+        event.Skip()
+
+    def OnHover(self, x, y, drag_result):
+        if self.CurrentDragFormat() == 'phantom_lozenge':
+            if self.GetDraggedFromWidget() == self:
+                print "hovering in the right window"
+                return wx.DragMove
+            else:
+                print "hovering in the wrong window"
+                return wx.DragNone
+        return drag_result
+
+    def OnDragResize(self, x, y):
+        if not self.dragging: return
+        
+
+    def TopInfoFromPosition(self, pos):
+        """Return (event, depth, max_depth, range, rect) or None."""
+        if not self.matrix: return
+        pos = tuple(pos)
+        def get_rect(obj, depth, constrain, max_depth=0):
+            start, end = obj.start, obj.end
+            if constrain:
+                start = max(constrain.start, start)
+                end = min(constrain.end, end)
+            return self.RectFromTime(start, end, depth, max_depth)
+            
+        def matches(iterable, depth=0, constrain=None):
+            for obj in iterable:
+                if get_rect(obj, depth, constrain).Contains(pos):
+                    yield obj
+                    break
+
+        for interval in matches(self.intervals, -1):
+            for cluster in matches(interval.clusters, -1, constrain=interval):
+                for depth, event in reversed(cluster):
+                    rect = get_rect(event, depth, interval, len(cluster.levels))
+                    if rect.Contains(pos):
+                        return event, depth, len(cluster.levels), interval, rect
+
+    def OnClick(self, wx_evt):
+        position = wx.Point2D(*self.CalcUnscrolledPosition(wx_evt.Position))
+        info = self.TopInfoFromPosition(position)
+        selected_id = info[0].id if info else None
+        wx.PostEvent(self, SelectItemsEvent(selected_ids=[selected_id]))
+
+    def DragEdge(self, pos):
+        """Return the edge for the position's associated lozenge, or 0."""
+        m = 5 # margin within the rectangle to consider near enough to edge
+        info = self.TopInfoFromPosition(pos)
+        if info:
+            e, depth, max_depth, range, rect = info
+            mask = self.SidesToTruncate(e.start, e.end, range.start, range.end)
+            n_s = wx.TOP in self.draggable_edges
+            if n_s:
+                if not mask & wx.BOTTOM and abs(pos.y - rect.GetBottom()) <= m:
+                    return wx.BOTTOM
+                elif not mask & wx.TOP and abs(pos.y - rect.GetTop()) <= m:
+                    return wx.TOP
+            else:
+                if not mask & wx.RIGHT and abs(pos.x - rect.GetRight()) <= m:
+                    return wx.RIGHT
+                elif not mask & wx.LEFT and abs(pos.x - rect.GetLeft()) <= m:
+                    return wx.LEFT
+        return 0
+
+    @property
+    def resize_cursor(self):
+        n_s = wx.TOP in self.draggable_edges
+        return self.top_bottom_cursor if n_s else self.left_right_cursor
+
+    def UpdateCursor(self, pos):
+        """Show the resize cursor if appropriate."""
+        c = self.resize_cursor if self.DragEdge(pos) else self.default_cursor 
+        if c != self.cursor:
+            self.SetCursor(c)
+            self.cursor = c
+
 class AllDayCalendar(Calendar):
+    draggable_edges = wx.LEFT, wx.RIGHT
+    scroll=False
+
     def DrawBackground(self, gc):
         gc.SetPen(wx.Pen(MINOR_LINE_COLOR))
         gc.StrokePath(self.GetDayLinePath(gc))
@@ -685,7 +932,12 @@
         return vertical
 
     def OnSize(self, event):
-        self.Refresh(False)
+        old_width = self.width
+        self.width, height = self.GetSize()
+        if old_width != self.width:
+            lozenge_cache.clear()
+            self.matrix = None
+        self.Refresh()
 
     def Reset(self):
         """Empty out events."""
@@ -694,7 +946,7 @@
         else:
             self.intervals = [EventsForWeek(self.range_start)]
         self.FillIntervals()
-        self.Refresh(False)
+        self.Refresh()
 
     def RectFromTime(self, start, end, depth=-1, max_depth=0):
         rect = super(AllDayCalendar, self).RectFromTime(start, end)
@@ -707,6 +959,12 @@
             rect.height = height
         return rect
 
+    def TimeFromPosition(self, position):
+        dt = super(AllDayCalendar, self).TimeFromPosition(position)
+        if dt:
+            return dt.replace(hour=0, minute=0, second=0, microsecond=0)
+
+
     def SidesToTruncate(self, real_start, real_end, lozenge_start, lozenge_end):
         truncate = 0 if real_start >= lozenge_start else wx.LEFT
         return truncate | (0 if real_end <= lozenge_end else wx.RIGHT)
@@ -975,13 +1233,13 @@
 def TransformRect(matrix, rect):
     x, y = matrix.TransformPoint(rect.x, rect.y)
     width, height = matrix.TransformDistance(rect.width, rect.height)
-    return wx.Rect2D(x, y, width, height)
+    return wx.Rect2D(x, y, width, height)    
 
 def font_height(gc, font):
     gc.SetFont(font)
     return gc.GetTextExtent('M')[1]
 
-empty = '\x00' * 300**2
+empty = '\x00' * 1200**2
 
 def transparent_bitmap(w, h):
     image = wx.EmptyImage(w, h)
@@ -995,6 +1253,18 @@
 
     return wx.BitmapFromImage(image)
 
+def larger_rect(rect, outset=1):
+    rect = wx.Rect2D(*rect)
+    rect.Inset(-outset,-outset)
+    return rect
+
+def roundTime(dt, nearest_minutes=15):
+    day_start = dt.combine(dt, time(0))
+    delta = dt - day_start
+    nearest_seconds = 60.0 * nearest_minutes
+    seconds = round(delta.seconds / nearest_seconds) * nearest_seconds
+    return day_start + timedelta(seconds=seconds)
+
 def shortTZ(dt, tzinfo=None):
     """
     Return an empty string or the short timezone string for dt if dt.tzinfo

Added: branches/rearchitecture/Chandler-Platform/ocap/wxui/drag_and_drop.py (16099 => 16100)

--- branches/rearchitecture/Chandler-Platform/ocap/wxui/drag_and_drop.py	2007-12-10 22:12:17 UTC (rev 16099)
+++ branches/rearchitecture/Chandler-Platform/ocap/wxui/drag_and_drop.py	2007-12-10 23:15:20 UTC (rev 16100)
@@ -0,0 +1,146 @@
+import wx
+
+# wx doesn't provide a mechanism for determining the format that's being dragged
+# until it's dropped, but since we render hover differently depending on the
+# type of drag, the source item, and the source window, use a global to track
+# what the drag source window was.
+DraggedFromWidget = None
+
+def DoDragAndDrop(source_window, format_label, data_object=None):
+    """
+    Do a Drag And Drop operation, given the data in the selection.
+
+    @return: C{wx.DragResult} - e.g. wx.DragNone if the drag was refused
+    """
+    global DraggedFromWidget
+
+    flags = wx.Drag_AllowMove
+
+    if data_object is None:
+        data_object = wx.CustomDataObject(wx.CustomDataFormat(format_label))
+        data_object.SetData('')
+
+    # create the drop source, set up its data
+    drop_source = wx.DropSource(source_window)
+    drop_source.SetData(data_object)
+
+    DraggedFromWidget = source_window
+    DraggedFromWidget._drag_format_label = format_label
+    result = drop_source.DoDragDrop(flags=flags)
+    del DraggedFromWidget._drag_format_label
+    DraggedFromWidget = None
+    return result
+
+class DropReceiveWidget(object):
+    custom_drag_names = ['chandler_item_ids']
+    """Mixin class for widgets that want to receive drag and drop events."""
+    def __init__(self, *arguments, **keywords):
+        super(DropReceiveWidget, self).__init__(*arguments, **keywords)
+        self.SetDropTarget(CustomDropTarget(self, self.custom_drag_names))
+
+    def OnRequestDrop(self, x, y):
+        """
+        Override this to decide whether or not to accept a dropped item.
+        """
+        print "OnRequestDrop", x, y, self.GetDraggedFromWidget()
+        # default - don't allow drop onto ourself
+        return self.GetDraggedFromWidget() is not self
+        
+    def OnEnter(self, x, y, dragResult):
+        """
+        Override this to perform an action when a drag enters your widget.
+        
+        @return: A wxDragResult other than dragResult if you want to change
+                 the drag operation
+        """
+        # default - don't allow dragging to ourself
+        if self.GetDraggedFromWidget() is self:
+            return wx.DragNone
+        return dragResult
+
+    def OnHover(self, x, y, dragResult):
+        """
+        Override this to perform an action when a drag cursor is
+        hovering over the widget.
+        
+        @return: A wxDragResult other than dragResult if you want to change
+                 the drag operation
+        """
+        #print "OnHover", dragResult, x, y, self.GetDraggedFromWidget()
+        # default - don't allow dragging to ourself
+        if self.GetDraggedFromWidget() is self:
+            return wx.DragNone
+        return dragResult
+
+    def OnLeave(self):
+        """
+        Override this to perform an action when hovering terminates.
+        """
+        pass
+
+    def GetDraggedFromWidget(self):
+        # return the widget that initiated the drag
+        global DraggedFromWidget
+        return DraggedFromWidget
+
+    def CurrentDragFormat(self):
+        return self.GetDraggedFromWidget()._drag_format_label
+
+class CustomDropTarget(wx.DropTarget):
+    """
+    An instance of this class is a helper object that associates
+    a wx.DropTarget with a receiver - a DropReceiveWidget.
+    """
+    def __init__(self, receiver, custom_format_labels):
+        super (CustomDropTarget, self).__init__ ()
+        self.receiver = receiver
+        # create a data object for the kind of data we'll accept
+        self.composite_data_object = self.MakeDataObject(custom_format_labels)
+        self.SetDataObject(self.composite_data_object)
+    
+    def OnDrop(self, x, y):
+        return self.receiver.OnRequestDrop(x, y)
+    
+    def OnData(self, x, y, drag_result):
+        if self.GetData():
+            data_format = None
+            for format in data.GetAllFormats():
+                if data.GetDataSize(format) > 0:
+                    data_format = format
+                    break
+            if data_format is not None:
+                for format_name, format_obj in self.data_formats.iteritems():
+                    if format_obj == data_format:
+                        data_string = self.data_objects[format_name].GetData()
+                        # composite data objects don't empty their last
+                        # dragged item, so their data needs to be set to '' by
+                        # hand.
+                        self.composite_data_object.SetData(data_format, '')
+                        break
+
+            self.receiver.EndDrag(format_name, x, y, data_string,
+                                  DraggedFromWidget)
+        return drag_result
+    
+    def OnDragOver(self, x, y, dragResult):
+        return self.receiver.OnHover(x, y, dragResult)
+
+    def MakeDataObject(self, custom_format_labels):
+        composite_data_object = wx.DataObjectComposite()
+        
+        self.data_formats = {}
+        self.data_objects = {}
+
+        for name in custom_format_labels:
+            format = self.data_formats[name] = wx.CustomDataFormat(name)       
+            obj = self.data_objects[name] = wx.CustomDataObject(format)
+            composite_data_object.Add(obj)
+            composite_data_object.SetData(format, '')
+
+        return composite_data_object
+
+    def OnEnter(self, x, y, dragResult):
+        return self.receiver.OnEnter(x, y, dragResult)
+
+    def OnLeave(self):
+        self.receiver.OnLeave()
Property changes on: branches/rearchitecture/Chandler-Platform/ocap/wxui/drag_and_drop.py
___________________________________________________________________
Name: svn:eol-style
   + native




_______________________________________________
Commits mailing list
[email protected]
http://lists.osafoundation.org/mailman/listinfo/commits

Reply via email to