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 [ \
35 (wx.EVT_LEFT_DOWN, self.onLeftDown),
36 (wx.EVT_LEFT_UP, self.onLeftUp),
37 (wx.EVT_MOTION, self.onMotion),
38 (wx.EVT_RIGHT_UP, self.onRightUp),
39 (wx.EVT_SIZE, self.onSize),
40 (wx.EVT_IDLE, self.onIdle),
41 (wx.EVT_PAINT, self.onPaint),
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()
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
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
143
144
145
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
154
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
164
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
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()