예제 #1
0
 def updateVisableRectsAndReturnUpdateRegion(self):
     """
     Updates the self.visibleWidgets list if necessary based on the current scroll position.
     :return: The update region that would need to be redrawn
     """
     # Determine visible passages
     updateRect = self.GetUpdateRegion().GetBox()
     scrollPos = (self.GetScrollPos(wx.HORIZONTAL),
                  self.GetScrollPos(wx.VERTICAL))
     if self.visibleWidgets is None or scrollPos != self.lastScrollPos:
         self.lastScrollPos = scrollPos
         updateRect = self.GetClientRect()
         displayArrows = self.app.config.ReadBool('displayArrows')
         imageArrows = self.app.config.ReadBool('imageArrows')
         self.visibleWidgets = [
             widget for widget in self.widgetDict.itervalues()
             # It's visible if it's in the client rect, or is being moved.
             if
             (widget.dimmed or updateRect.Intersects(widget.getPixelRect())
              # It's also visible if an arrow FROM it intersects with the Client Rect
              or [
                  w2 for w2 in widget.getConnectedWidgets(
                      displayArrows, imageArrows)
                  if geometry.lineRectIntersection(
                      widget.getConnectorLine(w2, clipped=False),
                      updateRect)
              ])
         ]
     return updateRect
예제 #2
0
    def paintConnectorTo(self, otherWidget, arrowheads, color, width, gc, updateRect = None):
        """
        Paints a connecting line between this widget and another,
        with optional arrowheads. You may pass either a wx.GraphicsContext
        (anti-aliased drawing) or a wx.PaintDC.
        """
        start, end = self.getConnectorLine(otherWidget)

        # does it actually need to be drawn?

        if otherWidget == self:
            return

        if updateRect and not geometry.lineRectIntersection([start, end], updateRect):
            return

        # ok, really draw the line

        lineWidth = max(self.parent.toPixels((width, 0), scaleOnly = True)[0], 1)
        gc.SetPen(wx.Pen(color, lineWidth))

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(start[0], start[1], end[0], end[1])
        else:
            gc.DrawLine(start[0], start[1], end[0], end[1])

        # arrowheads at end

        if not arrowheads: return

        flat = self.app.config.ReadBool('flatDesign')
        
        arrowheadLength = max(self.parent.toPixels((PassageWidget.ARROWHEAD_LENGTH, 0), scaleOnly = True)[0], 1)
        arrowhead = geometry.endPointProjectedFrom((start, end), angle = PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if flat:
            pass
        elif isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])

        arrowhead2 = geometry.endPointProjectedFrom((start, end), angle = 0 - PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        
        if flat:
            gc.SetBrush(wx.Brush(color))
            if isinstance(gc, wx.GraphicsContext):
                gc.DrawLines([wx.Point2D(*arrowhead2), wx.Point2D(*end), wx.Point2D(*arrowhead) ])
            else:
                gc.DrawPolygon([wx.Point(*arrowhead2), wx.Point(*end), wx.Point(*arrowhead)])
        elif isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead2[0], arrowhead2[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead2[0], arrowhead2[1])
예제 #3
0
    def paintConnectorTo(self, otherWidget, arrowheads, gc, updateRect=None):
        """
        Paints a connecting line between this widget and another,
        with optional arrowheads. You may pass either a wx.GraphicsContext
        (anti-aliased drawing) or a wx.PaintDC.
        """
        start = self.parent.toPixels(self.getCenter())
        end = self.parent.toPixels(otherWidget.getCenter())
        start, end = geometry.clipLineByRects([start, end],
                                              otherWidget.getPixelRect())

        # does it actually need to be drawn?

        if updateRect and not geometry.lineRectIntersection([start, end],
                                                            updateRect):
            return

        if otherWidget == self:
            return

        # ok, really draw the line

        lineWidth = max(
            self.parent.toPixels((PassageWidget.CONNECTOR_WIDTH, 0),
                                 scaleOnly=True)[0], 1)
        gc.SetPen(wx.Pen(PassageWidget.CONNECTOR_COLOR, lineWidth))

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(start[0], start[1], end[0], end[1])
        else:
            gc.DrawLine(start[0], start[1], end[0], end[1])

        # arrowheads at end

        if not arrowheads: return

        arrowheadLength = max(
            self.parent.toPixels((PassageWidget.ARROWHEAD_LENGTH, 0),
                                 scaleOnly=True)[0], 1)
        arrowhead = geometry.endPointProjectedFrom((start, end), angle = PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])

        arrowhead = geometry.endPointProjectedFrom((start, end), angle = 0 - PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])
예제 #4
0
    def paintConnectorTo (self, otherWidget, arrowheads, color, gc, updateRect = None):
        """
        Paints a connecting line between this widget and another,
        with optional arrowheads. You may pass either a wx.GraphicsContext
        (anti-aliased drawing) or a wx.PaintDC.
        """
        start = self.parent.toPixels(self.getCenter())
        end = self.parent.toPixels(otherWidget.getCenter())
        start, end = geometry.clipLineByRects([start, end], otherWidget.getPixelRect())
                    
        # does it actually need to be drawn?
        
        if updateRect and not geometry.lineRectIntersection([start, end], updateRect):
            return
        
        if otherWidget == self:
            return
            
        # ok, really draw the line
        
        lineWidth = max(self.parent.toPixels((PassageWidget.CONNECTOR_WIDTH, 0), scaleOnly = True)[0], 1)
        gc.SetPen(wx.Pen(color, lineWidth))
        
        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(start[0], start[1], end[0], end[1])
        else:
            gc.DrawLine(start[0], start[1], end[0], end[1])
        
        # arrowheads at end

        if not arrowheads: return
         
        arrowheadLength = max(self.parent.toPixels((PassageWidget.ARROWHEAD_LENGTH, 0), scaleOnly = True)[0], 1)
        arrowhead = geometry.endPointProjectedFrom((start, end), angle = PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)
        
        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])
            
        arrowhead = geometry.endPointProjectedFrom((start, end), angle = 0 - PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1]) 
예제 #5
0
    def getConnectorTo(self, otherWidget, arrowheads=False, updateRect=None):
        # does it actually need to be drawn?
        if otherWidget == self:
            return [], []
        start, end = self.getConnectorLine(otherWidget)
        if updateRect and not geometry.lineRectIntersection([start, end], updateRect):
            return [], []

        line = [[start[0], start[1]], [end[0], end[1]]]

        if not arrowheads:
            return line, []
        else:
            length = max(self.parent.toPixels((PassageWidget.ARROWHEAD_LENGTH, 0), scaleOnly=True)[0], 1)
            arrowheadr = geometry.endPointProjectedFrom((start, end), PassageWidget.ARROWHEAD_ANGLE,  length)
            arrowheadl = geometry.endPointProjectedFrom((start, end), 0 - PassageWidget.ARROWHEAD_ANGLE, length)
        return line, [(arrowheadl, end, arrowheadr)]
예제 #6
0
파일: storypanel.py 프로젝트: jju/twine
 def updateVisableRectsAndReturnUpdateRegion(self):
     """
     Updates the self.visibleWidgets list if necessary based on the current scroll position.
     :return: The update region that would need to be redrawn
     """
     # Determine visible passages
     updateRect = self.GetUpdateRegion().GetBox()
     scrollPos = (self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL))
     if self.visibleWidgets == None or scrollPos != self.lastScrollPos:
         self.lastScrollPos = scrollPos
         updateRect = self.GetClientRect()
         self.visibleWidgets = [widget for widget in self.widgetDict.itervalues()
                                # It's visible if it's in the client rect, or is being moved.
                                if (widget.dimmed
                                    or updateRect.Intersects(widget.getPixelRect())
                                    # It's also visible if an arrow FROM it intersects with the Client Rect
                                    or [w2 for w2 in widget.getConnectedWidgets()
                                        if geometry.lineRectIntersection(w2.getConnectorLine(widget), updateRect)])]
     return updateRect
예제 #7
0
    def getConnectorTo(self, otherWidget, arrowheads=False, updateRect=None):
        # does it actually need to be drawn?
        if otherWidget == self:
            return [], []
        start, end = self.getConnectorLine(otherWidget)
        if updateRect and not geometry.lineRectIntersection([start, end],
                                                            updateRect):
            return [], []

        line = [[start[0], start[1]], [end[0], end[1]]]

        if not arrowheads:
            return line, []
        else:
            length = max(
                self.parent.toPixels((PassageWidget.ARROWHEAD_LENGTH, 0),
                                     scaleOnly=True)[0], 1)
            arrowheadr = geometry.endPointProjectedFrom(
                (start, end), PassageWidget.ARROWHEAD_ANGLE, length)
            arrowheadl = geometry.endPointProjectedFrom(
                (start, end), 0 - PassageWidget.ARROWHEAD_ANGLE, length)
        return line, [(arrowheadl, end, arrowheadr)]
예제 #8
0
    def paintConnectorTo(self, otherWidget, arrowheads, color, width, gc, updateRect = None):
        """
        Paints a connecting line between this widget and another,
        with optional arrowheads. You may pass either a wx.GraphicsContext
        (anti-aliased drawing) or a wx.PaintDC.
        """
        start = self.parent.toPixels(self.getCenter())
        end = self.parent.toPixels(otherWidget.getCenter())

        # Additional tweak to make overlapping arrows more visible

        length = min(math.sqrt((start[0]-end[0])**2 + (start[1]-end[1])**2)/32, 16)

        if start[1] != end[1]:
            start[0] += length * math.copysign(1, start[1] - end[1]);
            end[0] += length * math.copysign(1, start[1] - end[1]);
        if start[0] != end[0]:
            start[1] += length * math.copysign(1, start[0] - end[0]);
            end[1] += length * math.copysign(1, start[0] - end[0]);

        # Clip the end of the arrow

        start, end = geometry.clipLineByRects([start, end], otherWidget.getPixelRect())

        # does it actually need to be drawn?

        if otherWidget == self:
            return

        if updateRect and not geometry.lineRectIntersection([start, end], updateRect):
            return

        # ok, really draw the line

        lineWidth = max(self.parent.toPixels((width, 0), scaleOnly = True)[0], 1)
        gc.SetPen(wx.Pen(color, lineWidth))

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(start[0], start[1], end[0], end[1])
        else:
            gc.DrawLine(start[0], start[1], end[0], end[1])

        # arrowheads at end

        if not arrowheads: return

        arrowheadLength = max(self.parent.toPixels((PassageWidget.ARROWHEAD_LENGTH, 0), scaleOnly = True)[0], 1)
        arrowhead = geometry.endPointProjectedFrom((start, end), angle = PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])

        arrowhead = geometry.endPointProjectedFrom((start, end), angle = 0 - PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])
예제 #9
0
파일: storypanel.py 프로젝트: fourks/twine
    def paint(self, event):
        """Paints marquee selection, widget connectors, and widgets onscreen."""
        # do NOT call self.DoPrepareDC() no matter what the docs may say
        # we already take into account our scroll origin in our
        # toPixels() method

        # in fast drawing, we ask for a standard paint context
        # in slow drawing, we ask for a anti-aliased one
        #
        # OS X already double buffers drawing for us; if we try to do it
        # ourselves, performance is horrendous

        if (sys.platform == 'darwin'):
            gc = wx.PaintDC(self)
        else:
            gc = wx.BufferedPaintDC(self)

        updateRect = self.GetUpdateRegion().GetBox()
        
        # Determine visible passages
        scrollPos = (self.GetScrollPos(wx.HORIZONTAL), self.GetScrollPos(wx.VERTICAL))
        if self.visibleWidgets == None or scrollPos != self.lastScrollPos:
            self.lastScrollPos = scrollPos
            updateRect = self.GetClientRect()
            self.visibleWidgets = [widget for widget in self.widgets
                                   # It's visible if it's in the client rect, or is being moved.
                                   if (widget.dimmed
                                       or updateRect.Intersects(widget.getPixelRect())
                                       # It's also visible if an arrow FROM it intersects with the Client Rect
                                       or [w2 for w2 in widget.getConnectedWidgets()
                                           if geometry.lineRectIntersection(w2.getConnectorLine(widget), updateRect)])]
        
        # background

        gc.SetBrush(wx.Brush(StoryPanel.FLAT_BG_COLOR if self.app.config.ReadBool('flatDesign') else StoryPanel.BACKGROUND_COLOR ))

        gc.DrawRectangle(updateRect.x - 1, updateRect.y - 1, updateRect.width + 2, updateRect.height + 2)

        # connectors

        arrowheads = (self.scale > StoryPanel.ARROWHEAD_THRESHOLD)

        for widget in self.visibleWidgets:
            if not widget.dimmed:
                widget.paintConnectors(gc, arrowheads, updateRect)
        
        for widget in self.visibleWidgets:
            # Could be "visible" only insofar as its arrow is visible
            if updateRect.Intersects(widget.getPixelRect()):
                widget.paint(gc)

        # marquee selection
        # with slow drawing, use alpha blending for interior

        if self.draggingMarquee:
            if self.app.config.ReadBool('fastStoryPanel'):
                gc.SetPen(wx.Pen('#ffffff', 1, wx.DOT))
                gc.SetBrush(wx.Brush(wx.WHITE, wx.TRANSPARENT))
            else:
                gc = wx.GraphicsContext.Create(gc)
                marqueeColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
                gc.SetPen(wx.Pen(marqueeColor))
                r, g, b = marqueeColor.Get(False)
                marqueeColor = wx.Colour(r, g, b, StoryPanel.MARQUEE_ALPHA)
                gc.SetBrush(wx.Brush(marqueeColor))

            gc.DrawRectangle(self.dragRect.x, self.dragRect.y, self.dragRect.width, self.dragRect.height)
예제 #10
0
    def paintConnectorTo(self,
                         otherWidget,
                         arrowheads,
                         color,
                         width,
                         gc,
                         updateRect=None):
        """
        Paints a connecting line between this widget and another,
        with optional arrowheads. You may pass either a wx.GraphicsContext
        (anti-aliased drawing) or a wx.PaintDC.
        """
        start, end = self.getConnectorLine(otherWidget)

        # does it actually need to be drawn?

        if otherWidget == self:
            return

        if updateRect and not geometry.lineRectIntersection([start, end],
                                                            updateRect):
            return

        # ok, really draw the line

        lineWidth = max(self.parent.toPixels((width, 0), scaleOnly=True)[0], 1)
        gc.SetPen(wx.Pen(color, lineWidth))

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(start[0], start[1], end[0], end[1])
        else:
            gc.DrawLine(start[0], start[1], end[0], end[1])

        # arrowheads at end

        if not arrowheads: return

        flat = self.app.config.ReadBool('flatDesign')

        arrowheadLength = max(
            self.parent.toPixels((PassageWidget.ARROWHEAD_LENGTH, 0),
                                 scaleOnly=True)[0], 1)
        arrowhead = geometry.endPointProjectedFrom((start, end), angle = PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if flat:
            pass
        elif isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])

        arrowhead2 = geometry.endPointProjectedFrom((start, end), angle = 0 - PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if flat:
            gc.SetBrush(wx.Brush(color))
            if isinstance(gc, wx.GraphicsContext):
                gc.DrawLines([
                    wx.Point2D(*arrowhead2),
                    wx.Point2D(*end),
                    wx.Point2D(*arrowhead)
                ])
            else:
                gc.DrawPolygon([
                    wx.Point(*arrowhead2),
                    wx.Point(*end),
                    wx.Point(*arrowhead)
                ])
        elif isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead2[0], arrowhead2[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead2[0], arrowhead2[1])
예제 #11
0
    def paintConnectorTo(self,
                         otherWidget,
                         arrowheads,
                         color,
                         width,
                         gc,
                         updateRect=None):
        """
        Paints a connecting line between this widget and another,
        with optional arrowheads. You may pass either a wx.GraphicsContext
        (anti-aliased drawing) or a wx.PaintDC.
        """
        start = self.parent.toPixels(self.getCenter())
        end = self.parent.toPixels(otherWidget.getCenter())

        # Additional tweak to make overlapping arrows more visible

        length = min(
            math.sqrt((start[0] - end[0])**2 + (start[1] - end[1])**2) / 32,
            16)

        if start[1] != end[1]:
            start[0] += length * math.copysign(1, start[1] - end[1])
            end[0] += length * math.copysign(1, start[1] - end[1])
        if start[0] != end[0]:
            start[1] += length * math.copysign(1, start[0] - end[0])
            end[1] += length * math.copysign(1, start[0] - end[0])

        # Clip the end of the arrow

        start, end = geometry.clipLineByRects([start, end],
                                              otherWidget.getPixelRect())

        # does it actually need to be drawn?

        if otherWidget == self:
            return

        if updateRect and not geometry.lineRectIntersection([start, end],
                                                            updateRect):
            return

        # ok, really draw the line

        lineWidth = max(self.parent.toPixels((width, 0), scaleOnly=True)[0], 1)
        gc.SetPen(wx.Pen(color, lineWidth))

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(start[0], start[1], end[0], end[1])
        else:
            gc.DrawLine(start[0], start[1], end[0], end[1])

        # arrowheads at end

        if not arrowheads: return

        arrowheadLength = max(
            self.parent.toPixels((PassageWidget.ARROWHEAD_LENGTH, 0),
                                 scaleOnly=True)[0], 1)
        arrowhead = geometry.endPointProjectedFrom((start, end), angle = PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])

        arrowhead = geometry.endPointProjectedFrom((start, end), angle = 0 - PassageWidget.ARROWHEAD_ANGLE, \
                                                   distance = arrowheadLength)

        if isinstance(gc, wx.GraphicsContext):
            gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
        else:
            gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])
예제 #12
0
    def paint(self, event):
        """Paints marquee selection, widget connectors, and widgets onscreen."""
        # do NOT call self.DoPrepareDC() no matter what the docs may say
        # we already take into account our scroll origin in our
        # toPixels() method

        # in fast drawing, we ask for a standard paint context
        # in slow drawing, we ask for a anti-aliased one
        #
        # OS X already double buffers drawing for us; if we try to do it
        # ourselves, performance is horrendous

        if (sys.platform == 'darwin'):
            gc = wx.PaintDC(self)
        else:
            gc = wx.BufferedPaintDC(self)

        updateRect = self.GetUpdateRegion().GetBox()

        # Determine visible passages
        scrollPos = (self.GetScrollPos(wx.HORIZONTAL),
                     self.GetScrollPos(wx.VERTICAL))
        if self.visibleWidgets == None or scrollPos != self.lastScrollPos:
            self.lastScrollPos = scrollPos
            updateRect = self.GetClientRect()
            self.visibleWidgets = [
                widget for widget in self.widgets
                # It's visible if it's in the client rect, or is being moved.
                if
                (widget.dimmed or updateRect.Intersects(widget.getPixelRect())
                 # It's also visible if an arrow FROM it intersects with the Client Rect
                 or [
                     w2 for w2 in widget.getConnectedWidgets()
                     if geometry.lineRectIntersection(
                         w2.getConnectorLine(widget), updateRect)
                 ])
            ]

        # background

        gc.SetBrush(
            wx.Brush(StoryPanel.FLAT_BG_COLOR if self.app.config.
                     ReadBool('flatDesign') else StoryPanel.BACKGROUND_COLOR))

        gc.DrawRectangle(updateRect.x - 1, updateRect.y - 1,
                         updateRect.width + 2, updateRect.height + 2)

        # connectors

        arrowheads = (self.scale > StoryPanel.ARROWHEAD_THRESHOLD)

        for widget in self.visibleWidgets:
            if not widget.dimmed:
                widget.paintConnectors(gc, arrowheads, updateRect)

        for widget in self.visibleWidgets:
            # Could be "visible" only insofar as its arrow is visible
            if updateRect.Intersects(widget.getPixelRect()):
                widget.paint(gc)

        # marquee selection
        # with slow drawing, use alpha blending for interior

        if self.draggingMarquee:
            if self.app.config.ReadBool('fastStoryPanel'):
                gc.SetPen(wx.Pen('#ffffff', 1, wx.DOT))
                gc.SetBrush(wx.Brush(wx.WHITE, wx.TRANSPARENT))
            else:
                gc = wx.GraphicsContext.Create(gc)
                marqueeColor = wx.SystemSettings.GetColour(
                    wx.SYS_COLOUR_HIGHLIGHT)
                gc.SetPen(wx.Pen(marqueeColor))
                r, g, b = marqueeColor.Get(False)
                marqueeColor = wx.Colour(r, g, b, StoryPanel.MARQUEE_ALPHA)
                gc.SetBrush(wx.Brush(marqueeColor))

            gc.DrawRectangle(self.dragRect.x, self.dragRect.y,
                             self.dragRect.width, self.dragRect.height)
예제 #13
0
    def paint (self, event):
        """Paints marquee selection, widget connectors, and widgets onscreen."""
        # do NOT call self.DoPrepareDC() no matter what the docs may say
        # we already take into account our scroll origin in our
        # toPixels() method
        
        # in fast drawing, we ask for a standard paint context
        # in slow drawing, we ask for a anti-aliased one
        #
        # OS X already double buffers drawing for us; if we try to do it
        # ourselves, performance is horrendous

        if (sys.platform == 'darwin'):
            gc = wx.PaintDC(self)
        else:
            gc = wx.BufferedPaintDC(self)
        
        if not self.app.config.ReadBool('fastStoryPanel'):
            gc = wx.GraphicsContext.Create(gc)            
                       
        # background
        
        updateRect = self.GetUpdateRegion().GetBox()
        gc.SetBrush(wx.Brush(StoryPanel.BACKGROUND_COLOR))      
        gc.DrawRectangle(updateRect.x - 1, updateRect.y - 1, updateRect.width + 2, updateRect.height + 2)
                
        # connectors
        
        arrowheadLength = max(self.toPixels((StoryPanel.ARROWHEAD_LENGTH, 0), scaleOnly = True)[0], \
                              StoryPanel.MIN_ARROWHEAD_LENGTH)
        
        gc.SetPen(wx.Pen(StoryPanel.CONNECTOR_COLOR, max(self.toPixels((StoryPanel.CONNECTOR_WIDTH, 0), \
                                                                        scaleOnly = True)[0], 1)))
        
        # cache bad links so we don't have to keep doing worst-case lookups
        
        badLinks = []

        for widget in self.widgets:
            if widget.dimmed: continue
            start = self.toPixels(widget.getCenter())
            for link in widget.passage.links():
                if link in badLinks: continue                
                otherWidget = self.findWidget(link)
                if not otherWidget: badLinks.append(link)
                
                if otherWidget and not otherWidget.dimmed:
                    # connector line
                    
                    end = self.toPixels(otherWidget.getCenter())
                    
                    # does it actually need to be drawn?
                    
                    if not geometry.lineRectIntersection([start, end], updateRect):
                        continue
                        
                    # ok, really draw the line
                    
                    if self.scale > StoryPanel.ARROWHEAD_THRESHOLD:
                        start, end = geometry.clipLineByRects([start, end], otherWidget.getPixelRect())
                    
                    if isinstance(gc, wx.GraphicsContext):
                        gc.StrokeLine(start[0], start[1], end[0], end[1])
                    else:
                        gc.DrawLine(start[0], start[1], end[0], end[1])
                    
                    # arrowheads at end
        
                    if self.scale < StoryPanel.ARROWHEAD_THRESHOLD: continue
                        
                    arrowhead = geometry.endPointProjectedFrom((start, end), angle = StoryPanel.ARROWHEAD_ANGLE, \
                                                               distance = arrowheadLength)
                    
                    if isinstance(gc, wx.GraphicsContext):
                        gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
                    else:
                        gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])
                        
                    arrowhead = geometry.endPointProjectedFrom((start, end), angle = 0 - StoryPanel.ARROWHEAD_ANGLE, \
                                                               distance = arrowheadLength)

                    if isinstance(gc, wx.GraphicsContext):
                        gc.StrokeLine(end[0], end[1], arrowhead[0], arrowhead[1])
                    else:
                        gc.DrawLine(end[0], end[1], arrowhead[0], arrowhead[1])                    
                     
        # widgets
                
        for widget in self.widgets:
            if updateRect.Intersects(widget.getPixelRect()): widget.paint(gc)
        
        # marquee selection
        # with slow drawing, use alpha blending for interior
        
        if self.draggingMarquee:
            if self.app.config.ReadBool('fastStoryPanel'):
                gc.SetPen(wx.Pen('#ffffff', 1, wx.DOT))
            else:
                marqueeColor = wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT)
                gc.SetPen(wx.Pen(marqueeColor))
                r, g, b = marqueeColor.Get()
                marqueeColor = wx.Color(r, g, b, StoryPanel.MARQUEE_ALPHA)            
                gc.SetBrush(wx.Brush(marqueeColor))
                
            gc.DrawRectangle(self.dragRect.x, self.dragRect.y, self.dragRect.width, self.dragRect.height)