Title: [commits] (jeffrey) [15924] [APP]
Revision
15924
Author
jeffrey
Date
2007-11-27 13:18:38 -0800 (Tue, 27 Nov 2007)

Log Message

[APP]
- Add a pure-wxPython calendar drawing widget. Still need to do
one-line lozenges, swatches, import the full range of colors, etc.

Modified Paths

Added Paths

Diff

Added: branches/rearchitecture/Chandler-Platform/ocap/wxui/calendar.py (15923 => 15924)

--- branches/rearchitecture/Chandler-Platform/ocap/wxui/calendar.py	2007-11-27 19:56:22 UTC (rev 15923)
+++ branches/rearchitecture/Chandler-Platform/ocap/wxui/calendar.py	2007-11-27 21:18:38 UTC (rev 15924)
@@ -0,0 +1,601 @@
+#   Copyright (c) 2007 Open Source Applications Foundation
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+
+import wx
+import bisect
+from math import radians
+from datetime import datetime, timedelta
+from colorsys import hsv_to_rgb
+from drawing import DrawWrappedText
+
+SWATCH_BUFFER = (2,2)
+TEXT_BUFFER = (2,2)
+OUTLINE_WIDTH = 1
+HALO_WIDTH    = 3
+DEFAULT_TEXT_SIZE = 10
+ARC_RADIUS = 5.0
+
+LEGEND_BORDER_WIDTH = 3.0
+LEGEND_TEXT_MARGIN = 2.0
+
+MARGIN_LEFT = 40.0
+
+TEXT_X_MARGIN = 3.5
+TEXT_Y_MARGIN = 3
+
+TIME_BOTTOM_MARGIN = 2
+
+# arbitrary base size to use as a reference point for calculating calendar sizes 
+BASE_WIDTH  = 701.0
+BASE_HEIGHT = 721.0
+
+BOTTOM_EDGE = BASE_HEIGHT - 1
+RIGHT_EDGE  = BASE_WIDTH  - 1
+
+BASE_HOUR_HEIGHT = BOTTOM_EDGE / 24
+BASE_DAY_WIDTH   = RIGHT_EDGE / 7
+BASE_HOUR_Y_LIST = [i * BASE_HOUR_HEIGHT for i in xrange(25)]
+BASE_DAY_X_LIST  = [i * BASE_DAY_WIDTH   for i in xrange(8)]
+
+MINOR_LINE_COLOR = wx.Color(217, 217, 217)
+MAJOR_LINE_COLOR = wx.Color(204, 204, 204)
+LEGEND_COLOR     = wx.Color(128, 128, 128)
+
+class InitializeSlots(object):
+    def __init__(self, **kw):
+        for slot in self.__slots__:
+            setattr(self, slot, kw.get(slot))
+
+class LozengeColors(InitializeSlots):
+    __slots__ = ('text', 'outline', 'halo', 'gradient_left', 'gradient_right')
+    text = wx.WHITE
+    outline = wx.Color(0, 102, 204)
+    halo = wx.Color(255,255,255, 128)
+    gradient_left = wx.Color(0, 63, 127)
+    gradient_right = wx.Color(0, 102, 204)
+    
+class Event(InitializeSlots):
+    __slots__ = ('id', 'title', 'start', 'duration', 'color', 'swatch_colors')
+    def __cmp__(self, other):
+        """
+        Compare events.  Put earlier events first, then longer events.
+        """
+        if other.start == self.start:
+            if other.duration == self.duration:
+                return cmp(self.id, other.id)
+            else:
+                return cmp(other.duration, self.duration)
+        else:
+            return cmp(self.start, other.start)
+
+    @property
+    def end(self):
+        return self.start + self.duration
+
+    def overlaps(self, other):
+        earlier, later = (self, other) if self <= other else (other, self)
+        if earlier.start == earlier.end:
+            # special case zero-duration events so they overlap with anything
+            # that starts at the same time
+            return later.start <= earlier.end
+        else:
+            return later.start < earlier.end
+
+class OverlapCluster(object):
+    __slots__ = ('event_list', 'front_event', 'start', 'end', 'levels')
+    def __init__(self):
+        self.start = self.end = self.front_event = None
+        self.levels = []
+        self.event_list = []
+
+    def add(self, event):
+        if self.start is None or event.start < self.start:
+            self.start = event.start
+        if self.end is None or event.end > self.end:
+            self.end = event.end
+        
+        bisect.insort(self.event_list, event)
+        
+        for level in self.levels:
+            for level_event in level:
+                if event.overlaps(level_event):
+                    break
+            else:
+                bisect.insort(level, event)
+                return
+        self.levels.append([event])
+
+class EventsForDay(object):
+    __slots__ = ("start", "event_list", "clusters")
+    def __init__(self, start, event_list=None):
+        self.start = start
+        if event_list is None:
+            event_list = []
+        self.reset(event_list)
+
+    def change(self, event):
+        if event in self.event_list:
+            self.reset(self.event_list)
+        else:
+            bisect.insort(self.event_list, event)
+            first_cluster = None
+            clusters_merged = set()
+            for cluster in self.clusters:
+                if event.overlaps(cluster):
+                    if not first_cluster:
+                        first_cluster = cluster
+                        first_cluster.add(event)
+                    else:
+                        clusters_merged.add(cluster)
+            if first_cluster:
+                for cluster in clusters_merged:
+                    for event in cluster.event_list:
+                        first_cluster.add(event)
+                    self.clusters.remove(cluster)
+            else:
+                new_cluster = OverlapCluster()
+                new_cluster.add(event)
+                self.clusters.append(new_cluster)
+
+    def reset(self, event_list):
+        self.event_list = []
+        self.clusters = []
+        for event in sorted(event_list):
+            self.change(event)
+
+    def remove(self, event):
+        if event in self.event_list:
+            self.event_list.remove(event)
+            self.reset(self.event_list)
+
+    @property
+    def end(self):
+        return self.start + timedelta(1)
+
+
+class Calendar(wx.ScrolledWindow):
+    def __init__(self, time_formatter=None, *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.events = {}
+        self.days = []
+
+        self.matrix = None
+        self.hour_height = BASE_HOUR_HEIGHT
+        self.margin_right = wx.SystemSettings_GetMetric(wx.SYS_VSCROLL_X)
+                
+        self.business_start = 9
+        self.business_end   = 17
+        self.legend_font = wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL)
+        self.legend_strings = [str(i) for i in range(1,13)] * 2
+
+        self.lozenge_font = wx.Font(8, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD)
+        self.title_font = self.legend_font
+
+        self.range_start = None
+        self.range_mode = 'week'
+        
+        if time_formatter is None:
+            self.time_formatter = default_time_formatter
+        else:
+            self.time_formatter = time_formatter
+        
+        # caching
+        self.draw_from_cache = False
+        self._buffer = None
+
+
+    def Refresh(self, *args):
+        """
+        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)
+
+    def OnPaint(self, event):
+        # look more carefully at the dirty area
+
+        updateRegion = self.GetUpdateRegion()
+        if updateRegion.IsEmpty():
+            return
+
+        updateRect = updateRegion.Box
+
+        dc = wx.BufferedPaintDC(self)
+        
+        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)
+            mdc.SetBackground(wx.WHITE_BRUSH)
+            mdc.Clear()
+            self.DrawCanvas(mdc)
+            self.draw_from_cache = True
+
+        self.PrepareDC(dc)
+        dx = dc.DeviceToLogicalX(updateRect.x)
+        dy = dc.DeviceToLogicalY(updateRect.y)
+        dc.Blit(dx, dy, updateRect.width, updateRect.height, mdc, dx, dy)
+
+    def DrawCanvas(self, dc):
+        dc.SetBackground(wx.WHITE_BRUSH)
+        dc.Clear()
+        gc = wx.GraphicsContext.Create(dc)
+
+        width, height = self.GetSize()
+        gc.PushState()
+        gc.Translate(MARGIN_LEFT, 0)
+        gc.Scale((width - MARGIN_LEFT - self.margin_right)/BASE_WIDTH,
+                 self.hour_height/BASE_HOUR_HEIGHT)
+        self.matrix = gc.GetTransform()
+        gc.PopState()
+        
+        self.DrawBackground(gc)
+
+        for day in self.days:
+            for cluster in day.clusters:
+                max_depth = len(cluster.levels)
+                for depth, level in enumerate(cluster.levels):
+                    for event in level:
+                        self.DrawLozenge(gc, event, day, depth, max_depth)
+
+    def DrawLozenge(self, gc, event, range, depth, max_depth):
+        start = max(range.start, event.start)
+        hour_start = start.hour + start.minute/60.0
+        end = min(range.end, event.end)
+        duration = end - start
+        hours = duration.seconds / 3600.0
+        column = (start - self.range_start).days
+                
+        rect = wx.Rect2D(BASE_DAY_WIDTH*column, BASE_HOUR_HEIGHT*hour_start,
+                         BASE_DAY_WIDTH,        BASE_HOUR_HEIGHT*hours)
+
+        if max_depth > 1:
+            if self.range_mode == 'day':
+                rect.Inset(depth*BASE_DAY_WIDTH/max_depth, 0, 0, 0)
+                rect.width = BASE_DAY_WIDTH/max_depth
+            else:
+                rect.Inset(depth/(max_depth - 1)*BASE_DAY_WIDTH*0.2, 0, 0, 0)
+                rect.width = BASE_DAY_WIDTH * 0.8
+        
+        rect = TransformRect(self.matrix, rect)
+        rect.Inset(1, 1)
+        if column == 0 and depth == 0:
+            # don't let the halo overlap the legend border by too much
+            rect.Inset(.5, 0, 0, 0)
+
+        truncate = 0
+        if event.start < start:
+            truncate |= wx.TOP
+        if event.end > end:
+            truncate |= wx.BOTTOM
+        
+        DrawFilledLozenge(gc, rect, truncate = truncate)
+        
+        # draw one line?  Draw time?  Draw title on first line?
+        # Room for how many lozenges (min 2)?
+        
+        if event.start >= start:
+            rect.Inset(TEXT_X_MARGIN - 1, TEXT_Y_MARGIN, TEXT_X_MARGIN - 1, 0)
+            gc.Clip(*rect)
+            rect.Inset(1, 0)
+            time_text = self.time_formatter(start)
+            width, height = gc.GetTextExtent(time_text)
+
+            gc.SetFont(self.lozenge_font, LozengeColors.text)
+            gc.DrawText(time_text, rect.x, rect.y)
+            rect.Inset(0, TIME_BOTTOM_MARGIN + height, 0, 0)
+            gc.SetFont(self.title_font, LozengeColors.text)
+            # need to wrap text
+            DrawWrappedText(gc, event.title, rect)
+            gc.ResetClip()
+
+
+    def DrawBackground(self, gc):
+        gc.SetPen(wx.Pen(MINOR_LINE_COLOR))
+        path = self.GetDayLinePath(gc)
+        path.AddPath(self.GetHalfHourLinePath(gc))
+        gc.StrokePath(path)
+        
+        gc.SetPen(wx.Pen(MAJOR_LINE_COLOR))
+        path = (self.GetHourLinePath(gc))
+        gc.StrokePath(path)
+        
+        self.DrawLegend(gc)
+
+    def GetDayLinePath(self, gc):
+        """Get path associated with day lines."""
+        vertical = gc.CreatePath()
+        for x in BASE_DAY_X_LIST:
+            vertical.MoveToPoint(x, 0)
+            vertical.AddLineToPoint(x, BOTTOM_EDGE)
+        vertical.Transform(self.matrix)
+        return vertical
+
+    def GetHourLinePath(self, gc):
+        """Get path associated with hour lines."""
+        horizontal = gc.CreatePath()
+        for y in BASE_HOUR_Y_LIST:
+            horizontal.MoveToPoint(0, y)
+            horizontal.AddLineToPoint(RIGHT_EDGE, y)
+        horizontal.Transform(self.matrix)
+        return horizontal
+
+    def GetHalfHourLinePath(self, gc):
+        """Get path associated with hour lines."""
+        horizontal = gc.CreatePath()
+        for y in BASE_HOUR_Y_LIST[:-1]:
+            y += BASE_HOUR_HEIGHT / 2
+            horizontal.MoveToPoint(0, y)
+            horizontal.AddLineToPoint(RIGHT_EDGE, y)
+        horizontal.Transform(self.matrix)
+        return horizontal
+
+    def DrawLegend(self, gc):
+        """
+        Draw legend hours for a timed canvas.  Draw a thick line separating the
+        legend from the calendar, with business hours shaded differently.
+        
+        """
+        light_pen = wx.Pen(MAJOR_LINE_COLOR, LEGEND_BORDER_WIDTH)
+        dark_pen  = wx.Pen(LEGEND_COLOR,     LEGEND_BORDER_WIDTH)
+        noon_pen  = wx.Pen(MINOR_LINE_COLOR)
+
+        # CAP_BUTT would reach line end points exactly, but it draws the
+        # endpoints in a half tone which looks bad in this case.  Using
+        # projecting caps extends the line length by a pixel on either side but
+        # doesn't draw the half tone, so end points are adjusted up and down by
+        # a pixel
+        light_pen.SetCap(wx.CAP_PROJECTING)
+        dark_pen.SetCap( wx.CAP_PROJECTING)
+        
+        # adjust matrix for the extra right hand side of the pen
+        matrix = gc.GetTransform()
+        matrix.Translate( (1 - LEGEND_BORDER_WIDTH) / 2, 0)
+        matrix.Concat(self.matrix)
+        
+        main_line_start = matrix.TransformPoint(0, 1)
+        main_line_end   = matrix.TransformPoint(0, BOTTOM_EDGE - 1)
+        
+        business_line_start = 0, self.business_start*BASE_HOUR_HEIGHT + 1
+        business_line_start = matrix.TransformPoint(*business_line_start)
+
+        business_line_end = 0, self.business_end*BASE_HOUR_HEIGHT - 1
+        business_line_end = matrix.TransformPoint(*business_line_end)
+
+        gc.SetPen(light_pen)
+        gc.StrokeLines((main_line_start, main_line_end))
+
+        gc.SetPen(dark_pen)
+        gc.StrokeLines((business_line_start, business_line_end))
+
+        # draw the legend text
+        gc.SetFont(self.legend_font, LEGEND_COLOR)
+        x, real_hour_height = matrix.TransformPoint(0, BASE_HOUR_HEIGHT)
+        
+        for hour, text in enumerate(self.legend_strings[:-1]):
+            hour += 1
+            width, height = gc.GetTextExtent(text)
+            left = x - (LEGEND_BORDER_WIDTH - 1)/2 - LEGEND_TEXT_MARGIN - width
+            down = hour*real_hour_height
+            gc.DrawText(text, left, down - height/2)
+            if hour == 12:
+                gc.SetPen(noon_pen)
+                gc.StrokeLine(10, down, left - 3, down)
+
+    def OnSize(self, event):
+        width, height = self.GetSize()
+        self.SetVirtualSize((width, self.hour_height * 24))
+
+        self.SetScrollRate(0, self.hour_height / 3)
+        
+        self.Refresh(False)
+
+    def GetDays(self):
+        return 7 if self.range_mode == 'week' else 1
+
+    @property
+    def range_end(self):
+        return self.range_start + timedelta(self.GetDays())
+
+    def SetRange(self, start):
+        self.range_start = start
+        self.days = [EventsForDay(start + timedelta(day))
+                     for day in xrange(self.GetDays())]
+
+    def SetRangeMode(self, range_mode):
+        self.range_mode = range_mode
+        if self.range_start is not None:
+            self.SetRange(self, self.range_start)
+        
+    def ChangeEvent(self, id, **kw):
+        """
+        Create an event for id or adjust an existing one, adjust ordering of
+        lozenge clusters the event appears in.
+        
+        """
+        if not self.events.has_key(id):
+            self.events[id] = Event(id=id)
+        event = self.events[id]
+        for key, value in kw.iteritems():
+            setattr(event, key, value)
+        
+        for day in self.days:
+            if event.overlaps(day):
+                day.change(event)
+                self.Refresh(False)
+            elif event in day.event_list:
+                day.remove(event)
+                self.Refresh(False)
+    
+    def RemoveEvent(self, id):
+        """Remove an event from the events displayed."""
+        pass
+
+############## Utility functions ###############################################
+def GetGradientBrush(gc, offset, width, gradient_left, gradient_right):
+    return gc.CreateLinearGradientBrush(offset, 0, offset + width, 0,
+                                        gradient_left, gradient_right)
+
+def DrawFilledLozenge(gc, rect, colors=LozengeColors, truncate = 0):
+    """Draw a lozenge, truncated edges won't be rounded."""
+    path = GetLozengePath(gc, rect, truncate)
+    if HALO_WIDTH:
+        halo_pen = wx.Pen(colors.halo, HALO_WIDTH)
+        gc.SetPen(halo_pen)
+        gc.StrokePath(path)
+
+    brush = GetGradientBrush(gc, rect.x, rect.width,
+                             colors.gradient_left, colors.gradient_right)
+    gc.SetBrush(brush)
+    gc.SetPen(wx.Pen(colors.outline, OUTLINE_WIDTH))
+    gc.DrawPath(path)
+
+def GetLozengePath(gc, rect, truncate):
+    """
+    Return a rounded rectangle path with certain edges truncated (not rounded).
+    
+    truncate should be a bitfield of any combination of wx.RIGHT, wx.TOP,
+    wx.LEFT, and wx.BOTTOM.
+    
+    """
+    path = gc.CreatePath()
+    x, y, width, height = rect
+    if not truncate:
+        path.AddRoundedRectangle(x, y, width, height, ARC_RADIUS)
+        return path
+        
+    corners = wx.Rect2D(*rect)
+    adjust = wx.Point2D(0, ARC_RADIUS)
+    
+    start = corners.GetRightTop()
+    if not (truncate & (wx.RIGHT | wx.TOP)):
+        start += adjust
+    path.MoveToPoint(start)
+        
+    edge_vectors = {wx.LEFT  | wx.TOP    : corners.GetLeftTop(),
+                    wx.RIGHT | wx.TOP    : corners.GetRightTop(),
+                    wx.LEFT  | wx.BOTTOM : corners.GetLeftBottom(),
+                    wx.RIGHT | wx.BOTTOM : corners.GetRightBottom(),
+               }
+
+    edge_order = (wx.BOTTOM, wx.LEFT, wx.TOP, wx.RIGHT)
+
+    for i, edge in enumerate(edge_order):
+        last_edge = edge_order[i - 1]
+        point = edge_vectors[last_edge | edge]
+        if truncate & (last_edge | edge):
+            path.AddLineToPoint(point)
+        else:
+            adjust.SetPolarCoordinates(90*(i - 1), ARC_RADIUS)
+            point += adjust
+            path.AddLineToPoint(point)
+            adjust.SetPolarCoordinates(90*(i - 2), ARC_RADIUS)
+            point += adjust
+            path.AddArc(point, ARC_RADIUS, radians(i*90), radians((i + 1)*90))
+        
+    return path
+
+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)
+
+def shortTZ(dt, tzinfo=None):
+    """
+    Return an empty string or the short timezone string for dt if dt.tzinfo
+    doesn't match tzinfo and dt.tzinfo is a PyICU.ICUtzinfo instance.
+
+    """
+    try:
+        import PyICU
+        default = PyICU.ICUtzinfo.default
+        floating = PyICU.ICUtzinfo.floating
+    except ImportError:
+        floating = None
+        default = None
+
+    if tzinfo is None:
+        tzinfo = default
+
+    if dt.tzinfo is None or dt.tzinfo == floating:
+        return u''
+    elif dt.tzinfo != tzinfo:
+        try:
+            icu_timezone = dt.tzinfo.timezone
+            # make sure they aren't equivalent
+            if icu_timezone.getRawOffset() == tzinfo.timezone.getRawOffset():
+                numEquivalents = PyICU.TimeZone.countEquivalentIDs(tzinfo.tzid)
+                for index in xrange(numEquivalents):
+                    tzid = PyICU.TimeZone.getEquivalentID(tzinfo.tzid, index)
+                    if dt.tzinfo.tzid == tzid:
+                        return u''
+    
+            name = icu_timezone.getDisplayName(dt.dst(), icu_timezone.SHORT)
+            if not name:
+                return u''
+            else:
+                return name
+        except:
+            pass
+    return u''
+
+def default_time_formatter(dt):
+    return dt.strftime("%I:%M %p")
+
+lorem = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt uty laboreq et dolore magna aliqua. Ut enimy qad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
+
+def Demo():
+    wxApp = wx.PySimpleApp()
+    frame = wx.Frame(None, title="Calendar Mockup", size=wx.Size(200, 450))
+    frame.CenterOnParent()
+    calendar = Calendar(parent=frame, time_formatter=None)
+    calendar.SetRange(datetime(2007,11,25))
+    calendar.ChangeEvent(id="foo0", title=lorem,
+                         start=datetime(2007,11,25,10), 
+                         duration=timedelta(hours=4.97))
+    calendar.ChangeEvent(id="foo1", title=lorem,
+                         start=datetime(2007,11,26,10), 
+                         duration=timedelta(hours=18))
+    calendar.ChangeEvent(id="foo2", title=lorem,
+                         start=datetime(2007,11,27,15), 
+                         duration=timedelta(hours=3))
+    calendar.ChangeEvent(id="foo3", title=lorem,
+                         start=datetime(2007,11,27,16), 
+                         duration=timedelta(hours=3))
+    calendar.ChangeEvent(id="foo4", title=lorem,
+                         start=datetime(2007,11,27,18), 
+                         duration=timedelta(hours=3))
+    frame.Show()
+    wxApp.MainLoop()
+
+if __name__ == '__main__':
+    Demo()
Property changes on: branches/rearchitecture/Chandler-Platform/ocap/wxui/calendar.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: branches/rearchitecture/Chandler-Platform/ocap/wxui/drawing.py (15923 => 15924)

--- branches/rearchitecture/Chandler-Platform/ocap/wxui/drawing.py	2007-11-27 19:56:22 UTC (rev 15923)
+++ branches/rearchitecture/Chandler-Platform/ocap/wxui/drawing.py	2007-11-27 21:18:38 UTC (rev 15924)
@@ -85,8 +85,8 @@
         
     return lineWidth
 
-'''
-def DrawWrappedText(dc, text, rect, measurements=None):
+
+def DrawWrappedText(dc, text, rect):
     """
     Simple wordwrap - draws the text into the current DC
     
@@ -96,12 +96,9 @@
     Styles.getMeasurements()
     """
 
-    if measurements is None:
-        measurements = Styles.getMeasurements(dc.GetFont())
+    lineHeight = dc.GetTextExtent("M")[1]
+    spaceWidth = dc.GetTextExtent(" ")[0]
 
-    lineHeight = measurements.height
-    spaceWidth = measurements.spaceWidth
-        
     (rectX, rectY, rectWidth, rectHeight) = rect
     y = rectY
     rectRight = rectX + rectWidth
@@ -157,8 +154,8 @@
             dc.DrawText(thisLine, rectX, y)        
         y += lineHeight
     #return y - rectY # total height
-'''
 
+
 def DrawClippedText(dc, word, x, y, maxWidth, wordWidth = -1):
     """
     Draw the text, clipping at letter boundaries. This is optimized to
@@ -182,7 +179,7 @@
         return
 
     # take a guess at how long the word should be
-    testLength = (maxWidth*100/wordWidth)*len(word)/100
+    testLength = int((maxWidth*100/wordWidth)*len(word)/100)
     wordWidth = dc.GetTextExtent(word[0:testLength])[0]
 
     # now check if the guessed length actually fits
@@ -473,17 +470,25 @@
         def __init__(self, *args, **kwds):
             super(TestFrame, self).__init__(*args, **kwds)
             self.Bind(wx.EVT_PAINT, self.OnPaint)
+            self.Bind(wx.EVT_SIZE, self.OnSize)
+            
         def OnPaint(self, event):
             dc = wx.PaintDC(self)
             dc.Clear()
             
-            padding = 10
-            r = wx.Rect(padding, padding, self.GetRect().width - padding*2, self.GetRect().height-padding*2)
+            padding = 20
+            w, h = self.GetSize()
+            r = wx.Rect(0, 0, w, h)
+            r.Deflate(padding, padding)
+            dc.DrawRectangle(*r)
             
-            dc.DrawRectangle(*iter(r))
-            DrawWrappedText(dc, "Resize this window!\n\n  Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", r)
-            
+            DrawWrappedText(dc, "Resize this window!\n\n  Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
+                            r)
         
+        def OnSize(self, event):
+            self.Refresh(False)
+    
+        
     class TestApp(wx.App):
         def OnInit(self):
             frame = TestFrame(None, -1, "Test frame -- resize me!")




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

Reply via email to