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

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

NOTE: To edit pages in this wiki you must be a member of the TrustedEditorsGroup.