- 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
- 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
- branches/rearchitecture/Chandler-App/ocap/chandler/calendar_main.py
- branches/rearchitecture/Chandler-Platform/ocap/wxui/calendar_canvas.py
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
