1
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),
35 (wx.EVT_LEFT_UP, self.onLeftUp),
36 (wx.EVT_MOTION, self.onMotion),
37 (wx.EVT_RIGHT_UP, self.onRightUp),
38 (wx.EVT_SIZE, self.onSize),
39 (wx.EVT_IDLE, self.onIdle),
40 (wx.EVT_PAINT, self.onPaint),
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()
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
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
141
142
143
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
152
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
162
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
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()