1 # doodle.py
   2 
   3 '''
   4 This module contains the DoodleWindow class which is a window that you
   5 can do simple drawings upon.
   6 '''
   7 
   8 import wx
   9 
  10 
  11 class DoodleWindow(wx.Window):
  12     colours = ['Black', 'Yellow', 'Red', 'Green', 'Blue', 'Purple',
  13         'Brown', 'Aquamarine', 'Forest Green', 'Light Blue', 'Goldenrod',
  14         'Cyan', 'Orange', 'Navy', 'Dark Grey', 'Light Grey']
  15 
  16     thicknesses = [1, 2, 3, 4, 6, 8, 12, 16, 24, 32, 48, 64, 96, 128]
  17 
  18     def __init__(self, parent):
  19         super(DoodleWindow, self).__init__(parent,
  20             style=wx.NO_FULL_REPAINT_ON_RESIZE)
  21         self.initDrawing()
  22         self.makeMenu()
  23         self.bindEvents()
  24         self.initBuffer()
  25 
  26     def initDrawing(self):
  27         self.SetBackgroundColour('WHITE')
  28         self.currentThickness = self.thicknesses[0]
  29         self.currentColour = self.colours[0]
  30         self.lines = []
  31         self.previousPosition = (0, 0)
  32 
  33     def bindEvents(self):
  34         for event, handler in [ \
                (wx.EVT_LEFT_DOWN, self.onLeftDown), # Start drawing
  35                 (wx.EVT_LEFT_UP, self.onLeftUp),     # Stop drawing 
  36                 (wx.EVT_MOTION, self.onMotion),      # Draw
  37                 (wx.EVT_RIGHT_UP, self.onRightUp),   # Popup menu
  38                 (wx.EVT_SIZE, self.onSize),          # Prepare for redraw
  39                 (wx.EVT_IDLE, self.onIdle),          # Redraw
  40                 (wx.EVT_PAINT, self.onPaint),        # Refresh
  41                 (wx.EVT_WINDOW_DESTROY, self.cleanup)]:
  42             self.Bind(event, handler)
  43 
  44     def initBuffer(self):
  45         ''' Initialize the bitmap used for buffering the display. '''
  46         size = self.GetClientSize()
  47         self.buffer = wx.EmptyBitmap(size.width, size.height)
  48         dc = wx.BufferedDC(None, self.buffer)
  49         dc.SetBackground(wx.Brush(self.GetBackgroundColour()))
  50         dc.Clear()
  51         self.drawLines(dc, *self.lines)
  52         self.reInitBuffer = False
  53 
  54     def makeMenu(self):
  55         ''' Make a menu that can be popped up later. '''
  56         self.menu = wx.Menu()
  57         self.idToColourMap = self.addCheckableMenuItems(self.menu,
  58             self.colours)
  59         self.bindMenuEvents(menuHandler=self.onMenuSetColour,
  60             updateUIHandler=self.onCheckMenuColours,
  61             ids=self.idToColourMap.keys())
  62         self.menu.Break() # Next menu items go in a new column of the menu
  63         self.idToThicknessMap = self.addCheckableMenuItems(self.menu,
  64             self.thicknesses)
  65         self.bindMenuEvents(menuHandler=self.onMenuSetThickness,
  66             updateUIHandler=self.onCheckMenuThickness,
  67             ids=self.idToThicknessMap.keys())
  68 
  69     @staticmethod
  70     def addCheckableMenuItems(menu, items):
  71         ''' Add a checkable menu entry to menu for each item in items. This
  72             method returns a dictionary that maps the menuIds to the
  73             items. '''
  74         idToItemMapping = {}
  75         for item in items:
  76             menuId = wx.NewId()
  77             idToItemMapping[menuId] = item
  78             menu.Append(menuId, str(item), kind=wx.ITEM_CHECK)
  79         return idToItemMapping
  80 
  81     def bindMenuEvents(self, menuHandler, updateUIHandler, ids):
  82         ''' Bind the menu id's in the list ids to menuHandler and
  83             updateUIHandler. '''
  84         sortedIds = sorted(ids)
  85         firstId, lastId = sortedIds[0], sortedIds[-1]
  86         for event, handler in \
                [(wx.EVT_MENU_RANGE, menuHandler),
  87                  (wx.EVT_UPDATE_UI_RANGE, updateUIHandler)]:
  88             self.Bind(event, handler, id=firstId, id2=lastId)
  89 
  90     # Event handlers:
  91 
  92     def onLeftDown(self, event):
  93         ''' Called when the left mouse button is pressed. '''
  94         self.currentLine = []
  95         self.previousPosition = event.GetPositionTuple()
  96         self.CaptureMouse()
  97 
  98     def onLeftUp(self, event):
  99         ''' Called when the left mouse button is released. '''
 100         if self.HasCapture():
 101             self.lines.append((self.currentColour, self.currentThickness,
 102                 self.currentLine))
 103             self.currentLine = []
 104             self.ReleaseMouse()
 105 
 106     def onRightUp(self, event):
 107         ''' Called when the right mouse button is released, will popup
 108             the menu. '''
 109         self.PopupMenu(self.menu)
 110 
 111     def onMotion(self, event):
 112         ''' Called when the mouse is in motion. If the left button is
 113             dragging then draw a line from the last event position to the
 114             current one. Save the coordinants for redraws. '''
 115         if event.Dragging() and event.LeftIsDown():
 116             dc = wx.BufferedDC(wx.ClientDC(self), self.buffer)
 117             currentPosition = event.GetPositionTuple()
 118             lineSegment = self.previousPosition + currentPosition
 119             self.drawLines(dc, (self.currentColour, self.currentThickness,
 120                 [lineSegment]))
 121             self.currentLine.append(lineSegment)
 122             self.previousPosition = currentPosition
 123 
 124     def onSize(self, event):
 125         ''' Called when the window is resized. We set a flag so the idle
 126             handler will resize the buffer. '''
 127         self.reInitBuffer = True
 128 
 129     def onIdle(self, event):
 130         ''' If the size was changed then resize the bitmap used for double
 131             buffering to match the window size.  We do it in Idle time so
 132             there is only one refresh after resizing is done, not lots while
 133             it is happening. '''
 134         if self.reInitBuffer:
 135             self.initBuffer()
 136             self.Refresh(False)
 137 
 138     def onPaint(self, event):
 139         ''' Called when the window is exposed. '''
 140         # Create a buffered paint DC.  It will create the real
 141         # wx.PaintDC and then blit the bitmap to it when dc is
 142         # deleted.  Since we don't need to draw anything else
 143         # here that's all there is to it.
 144         dc = wx.BufferedPaintDC(self, self.buffer)
 145 
 146     def cleanup(self, event):
 147         if hasattr(self, "menu"):
 148             self.menu.Destroy()
 149             del self.menu
 150 
 151     # These two event handlers are called before the menu is displayed
 152     # to determine which items should be checked.
 153     def onCheckMenuColours(self, event):
 154         colour = self.idToColourMap[event.GetId()]
 155         event.Check(colour == self.currentColour)
 156 
 157     def onCheckMenuThickness(self, event):
 158         thickness = self.idToThicknessMap[event.GetId()]
 159         event.Check(thickness == self.currentThickness)
 160 
 161     # Event handlers for the popup menu, uses the event ID to determine
 162     # the colour or the thickness to set.
 163     def onMenuSetColour(self, event):
 164         self.currentColour = self.idToColourMap[event.GetId()]
 165 
 166     def onMenuSetThickness(self, event):
 167         self.currentThickness = self.idToThicknessMap[event.GetId()]
 168 
 169     # Other methods
 170     @staticmethod
 171     def drawLines(dc, *lines):
 172         ''' drawLines takes a device context (dc) and a list of lines
 173         as arguments. Each line is a three-tuple: (colour, thickness,
 174         linesegments). linesegments is a list of coordinates: (x1, y1,
 175         x2, y2). '''
 176         dc.BeginDrawing()
 177         for colour, thickness, lineSegments in lines:
 178             pen = wx.Pen(wx.NamedColour(colour), thickness, wx.SOLID)
 179             dc.SetPen(pen)
 180             for lineSegment in lineSegments:
 181                 dc.DrawLine(*lineSegment)
 182         dc.EndDrawing()
 183 
 184 
 185 class DoodleFrame(wx.Frame):
 186     def __init__(self, parent=None):
 187         super(DoodleFrame, self).__init__(parent, title="Doodle Frame",
 188             size=(800,600),
 189             style=wx.DEFAULT_FRAME_STYLE|wx.NO_FULL_REPAINT_ON_RESIZE)
 190         doodle = DoodleWindow(self)
 191 
 192 
 193 if __name__ == '__main__':
 194     app = wx.App()
 195     frame = DoodleFrame()
 196     frame.Show()
 197     app.MainLoop()

WxHowtoDrawing (last edited 2008-03-11 10:50:38 by localhost)