| 1 | |
| 2 | import wx |
| 3 | import images |
| 4 | import random |
| 5 | |
| 6 | #--------------------------------------------------------------------------- |
| 7 | |
| 8 | W = 2000 |
| 9 | H = 2000 |
| 10 | SW = 150 |
| 11 | SH = 150 |
| 12 | SHAPE_COUNT = 2500 |
| 13 | hitradius = 5 |
| 14 | |
| 15 | #--------------------------------------------------------------------------- |
| 16 | |
| 17 | colours = [ |
| 18 | "BLACK", |
| 19 | "BLUE", |
| 20 | "BLUE VIOLET", |
| 21 | "BROWN", |
| 22 | "CYAN", |
| 23 | "DARK GREY", |
| 24 | "DARK GREEN", |
| 25 | "GOLD", |
| 26 | "GREY", |
| 27 | "GREEN", |
| 28 | "MAGENTA", |
| 29 | "NAVY", |
| 30 | "PINK", |
| 31 | "RED", |
| 32 | "SKY BLUE", |
| 33 | "VIOLET", |
| 34 | "YELLOW", |
| 35 | ] |
| 36 | |
| 37 | |
| 38 | |
| 39 | class MyCanvas(wx.ScrolledWindow): |
| 40 | def __init__(self, parent, id, log, size = wx.DefaultSize): |
| 41 | wx.ScrolledWindow.__init__(self, parent, id, (0, 0), size=size, style=wx.SUNKEN_BORDER) |
| 42 | |
| 43 | self.lines = [] |
| 44 | self.maxWidth = W |
| 45 | self.maxHeight = H |
| 46 | self.x = self.y = 0 |
| 47 | self.curLine = [] |
| 48 | self.drawing = False |
| 49 | |
| 50 | self.SetBackgroundColour("WHITE") |
| 51 | bmp = images.getTest2Bitmap() |
| 52 | mask = wx.Mask(bmp, wx.BLUE) |
| 53 | bmp.SetMask(mask) |
| 54 | self.bmp = bmp |
| 55 | |
| 56 | self.SetVirtualSize((self.maxWidth, self.maxHeight)) |
| 57 | self.SetScrollRate(20,20) |
| 58 | |
| 59 | # create a PseudoDC to record our drawing |
| 60 | self.pdc = wx.PseudoDC() |
| 61 | self.pen_cache = {} |
| 62 | self.brush_cache = {} |
| 63 | self.DoDrawing(self.pdc) |
| 64 | log.write('Created PseudoDC draw list with %d operations!'%self.pdc.GetLen()) |
| 65 | |
| 66 | self.Bind(wx.EVT_PAINT, self.OnPaint) |
| 67 | self.Bind(wx.EVT_ERASE_BACKGROUND, lambda x:None) |
| 68 | self.Bind(wx.EVT_MOUSE_EVENTS, self.OnMouse) |
| 69 | |
| 70 | # vars for handling mouse clicks |
| 71 | self.dragid = -1 |
| 72 | self.lastpos = (0,0) |
| 73 | |
| 74 | def ConvertEventCoords(self, event): |
| 75 | xView, yView = self.GetViewStart() |
| 76 | xDelta, yDelta = self.GetScrollPixelsPerUnit() |
| 77 | return (event.GetX() + (xView * xDelta), |
| 78 | event.GetY() + (yView * yDelta)) |
| 79 | |
| 80 | def OffsetRect(self, r): |
| 81 | xView, yView = self.GetViewStart() |
| 82 | xDelta, yDelta = self.GetScrollPixelsPerUnit() |
| 83 | r.OffsetXY(-(xView*xDelta),-(yView*yDelta)) |
| 84 | |
| 85 | def OnMouse(self, event): |
| 86 | global hitradius |
| 87 | if event.LeftDown(): |
| 88 | x,y = self.ConvertEventCoords(event) |
| 89 | #l = self.pdc.FindObjectsByBBox(x, y) |
| 90 | l = self.pdc.FindObjects(x, y, hitradius) |
| 91 | for id in l: |
| 92 | if not self.pdc.GetIdGreyedOut(id): |
| 93 | self.dragid = id |
| 94 | self.lastpos = (event.GetX(),event.GetY()) |
| 95 | break |
| 96 | elif event.RightDown(): |
| 97 | x,y = self.ConvertEventCoords(event) |
| 98 | #l = self.pdc.FindObjectsByBBox(x, y) |
| 99 | l = self.pdc.FindObjects(x, y, hitradius) |
| 100 | if l: |
| 101 | self.pdc.SetIdGreyedOut(l[0], not self.pdc.GetIdGreyedOut(l[0])) |
| 102 | r = self.pdc.GetIdBounds(l[0]) |
| 103 | r.Inflate(4,4) |
| 104 | self.OffsetRect(r) |
| 105 | self.RefreshRect(r, False) |
| 106 | elif event.Dragging() or event.LeftUp(): |
| 107 | if self.dragid != -1: |
| 108 | x,y = self.lastpos |
| 109 | dx = event.GetX() - x |
| 110 | dy = event.GetY() - y |
| 111 | r = self.pdc.GetIdBounds(self.dragid) |
| 112 | self.pdc.TranslateId(self.dragid, dx, dy) |
| 113 | r2 = self.pdc.GetIdBounds(self.dragid) |
| 114 | r = r.Union(r2) |
| 115 | r.Inflate(4,4) |
| 116 | self.OffsetRect(r) |
| 117 | self.RefreshRect(r, False) |
| 118 | self.lastpos = (event.GetX(),event.GetY()) |
| 119 | if event.LeftUp(): |
| 120 | self.dragid = -1 |
| 121 | |
| 122 | def RandomPen(self): |
| 123 | c = random.choice(colours) |
| 124 | t = random.randint(1, 4) |
| 125 | if not self.pen_cache.has_key( (c, t) ): |
| 126 | self.pen_cache[(c, t)] = wx.Pen(c, t) |
| 127 | return self.pen_cache[(c, t)] |
| 128 | |
| 129 | |
| 130 | def RandomBrush(self): |
| 131 | c = random.choice(colours) |
| 132 | if not self.brush_cache.has_key(c): |
| 133 | self.brush_cache[c] = wx.Brush(c) |
| 134 | |
| 135 | return self.brush_cache[c] |
| 136 | |
| 137 | def RandomColor(self): |
| 138 | return random.choice(colours) |
| 139 | |
| 140 | |
| 141 | def OnPaint(self, event): |
| 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. |
| 145 | dc = wx.BufferedPaintDC(self) |
| 146 | # use PrepateDC to set position correctly |
| 147 | self.PrepareDC(dc) |
| 148 | # we need to clear the dc BEFORE calling PrepareDC |
| 149 | bg = wx.Brush(self.GetBackgroundColour()) |
| 150 | dc.SetBackground(bg) |
| 151 | dc.Clear() |
| 152 | # create a clipping rect from our position and size |
| 153 | # and the Update Region |
| 154 | xv, yv = self.GetViewStart() |
| 155 | dx, dy = self.GetScrollPixelsPerUnit() |
| 156 | x, y = (xv * dx, yv * dy) |
| 157 | rgn = self.GetUpdateRegion() |
| 158 | rgn.Offset(x,y) |
| 159 | r = rgn.GetBox() |
| 160 | # draw to the dc using the calculated clipping rect |
| 161 | self.pdc.DrawToDCClipped(dc,r) |
| 162 | |
| 163 | def DoDrawing(self, dc): |
| 164 | random.seed() |
| 165 | self.objids = [] |
| 166 | self.boundsdict = {} |
| 167 | dc.BeginDrawing() |
| 168 | for i in range(SHAPE_COUNT): |
| 169 | id = wx.NewId() |
| 170 | dc.SetId(id) |
| 171 | choice = random.randint(0,8) |
| 172 | if choice in (0,1): |
| 173 | x = random.randint(0, W) |
| 174 | y = random.randint(0, H) |
| 175 | pen = self.RandomPen() |
| 176 | dc.SetPen(pen) |
| 177 | dc.DrawPoint(x,y) |
| 178 | r = wx.Rect(x,y,1,1) |
| 179 | r.Inflate(pen.GetWidth(),pen.GetWidth()) |
| 180 | dc.SetIdBounds(id,r) |
| 181 | elif choice in (2,3): |
| 182 | x1 = random.randint(0, W-SW) |
| 183 | y1 = random.randint(0, H-SH) |
| 184 | x2 = random.randint(x1, x1+SW) |
| 185 | y2 = random.randint(y1, y1+SH) |
| 186 | pen = self.RandomPen() |
| 187 | dc.SetPen(pen) |
| 188 | dc.DrawLine(x1,y1,x2,y2) |
| 189 | r = wx.Rect(x1,y1,x2-x1,y2-y1) |
| 190 | r.Inflate(pen.GetWidth(),pen.GetWidth()) |
| 191 | dc.SetIdBounds(id,r) |
| 192 | elif choice in (4,5): |
| 193 | w = random.randint(10, SW) |
| 194 | h = random.randint(10, SH) |
| 195 | x = random.randint(0, W - w) |
| 196 | y = random.randint(0, H - h) |
| 197 | pen = self.RandomPen() |
| 198 | dc.SetPen(pen) |
| 199 | dc.SetBrush(self.RandomBrush()) |
| 200 | dc.DrawRectangle(x,y,w,h) |
| 201 | r = wx.Rect(x,y,w,h) |
| 202 | r.Inflate(pen.GetWidth(),pen.GetWidth()) |
| 203 | dc.SetIdBounds(id,r) |
| 204 | self.objids.append(id) |
| 205 | elif choice == 6: |
| 206 | Np = 8 # number of characters in text |
| 207 | word = [] |
| 208 | for i in range(Np): |
| 209 | c = chr( random.randint(48, 122) ) |
| 210 | word.append( c ) |
| 211 | word = "".join(word) |
| 212 | w,h = self.GetFullTextExtent(word)[0:2] |
| 213 | x = random.randint(0, W-w) |
| 214 | y = random.randint(0, H-h) |
| 215 | dc.SetFont(self.GetFont()) |
| 216 | dc.SetTextForeground(self.RandomColor()) |
| 217 | dc.SetTextBackground(self.RandomColor()) |
| 218 | dc.DrawText(word, x, y) |
| 219 | r = wx.Rect(x,y,w,h) |
| 220 | r.Inflate(2,2) |
| 221 | dc.SetIdBounds(id, r) |
| 222 | self.objids.append(id) |
| 223 | elif choice == 7: |
| 224 | Np = 8 # number of points per polygon |
| 225 | poly = [] |
| 226 | minx = SW |
| 227 | miny = SH |
| 228 | maxx = 0 |
| 229 | maxy = 0 |
| 230 | for i in range(Np): |
| 231 | x = random.randint(0, SW) |
| 232 | y = random.randint(0, SH) |
| 233 | if x < minx: minx = x |
| 234 | if x > maxx: maxx = x |
| 235 | if y < miny: miny = y |
| 236 | if y > maxy: maxy = y |
| 237 | poly.append(wx.Point(x,y)) |
| 238 | x = random.randint(0, W-SW) |
| 239 | y = random.randint(0, H-SH) |
| 240 | pen = self.RandomPen() |
| 241 | dc.SetPen(pen) |
| 242 | dc.SetBrush(self.RandomBrush()) |
| 243 | dc.DrawPolygon(poly, x,y) |
| 244 | r = wx.Rect(minx+x,miny+y,maxx-minx,maxy-miny) |
| 245 | r.Inflate(pen.GetWidth(),pen.GetWidth()) |
| 246 | dc.SetIdBounds(id,r) |
| 247 | self.objids.append(id) |
| 248 | elif choice == 8: |
| 249 | w,h = self.bmp.GetSize() |
| 250 | x = random.randint(0, W-w) |
| 251 | y = random.randint(0, H-h) |
| 252 | dc.DrawBitmap(self.bmp,x,y,True) |
| 253 | dc.SetIdBounds(id,wx.Rect(x,y,w,h)) |
| 254 | self.objids.append(id) |
| 255 | dc.EndDrawing() |
| 256 | |
| 257 | class ControlPanel(wx.Panel): |
| 258 | def __init__(self, parent, id, pos=wx.DefaultPosition, |
| 259 | size=wx.DefaultSize, style = wx.TAB_TRAVERSAL): |
| 260 | wx.Panel.__init__(self,parent,id,pos,size,style) |
| 261 | lbl = wx.StaticText(self, wx.ID_ANY, "Hit Test Radius: ") |
| 262 | lbl2 = wx.StaticText(self, wx.ID_ANY, "Left Click to drag, Right Click to enable/disable") |
| 263 | sc = wx.SpinCtrl(self, wx.ID_ANY, "5") |
| 264 | sc.SetRange(0,100) |
| 265 | global hitradius |
| 266 | sc.SetValue(hitradius) |
| 267 | self.sc = sc |
| 268 | sz = wx.BoxSizer(wx.HORIZONTAL) |
| 269 | sz.Add(lbl,0,wx.EXPAND) |
| 270 | sz.Add(sc,0) |
| 271 | sz.Add(lbl2,0,wx.LEFT,5) |
| 272 | sz.Add((10,10),1,wx.EXPAND) |
| 273 | self.SetSizerAndFit(sz) |
| 274 | sc.Bind(wx.EVT_SPINCTRL,self.OnChange) |
| 275 | sc.Bind(wx.EVT_TEXT,self.OnChange) |
| 276 | |
| 277 | def OnChange(self, event): |
| 278 | global hitradius |
| 279 | hitradius = self.sc.GetValue() |
| 280 | |
| 281 | |
| 282 | #--------------------------------------------------------------------------- |
| 283 | |
| 284 | def runTest(frame, nb, log): |
| 285 | pnl = wx.Panel(nb, wx.ID_ANY,size=(200,30)) |
| 286 | pnl2 = ControlPanel(pnl,wx.ID_ANY) |
| 287 | win = MyCanvas(pnl, wx.ID_ANY, log) |
| 288 | sz = wx.BoxSizer(wx.VERTICAL) |
| 289 | sz.Add(pnl2,0,wx.EXPAND|wx.ALL,5) |
| 290 | sz.Add(win,1,wx.EXPAND) |
| 291 | pnl.SetSizerAndFit(sz) |
| 292 | return pnl |
| 293 | |
| 294 | #--------------------------------------------------------------------------- |
| 295 | |
| 296 | |
| 297 | |
| 298 | overview = """ |
| 299 | <html> |
| 300 | <body> |
| 301 | <h2>wx.PseudoDC</h2> |
| 302 | |
| 303 | The wx.PseudoDC class provides a way to record operations on a DC and then |
| 304 | play them back later. The PseudoDC can be passed to a drawing routine as |
| 305 | if it were a real DC. All Drawing methods are supported except Blit but |
| 306 | GetXXX methods are not supported and none of the drawing methods return |
| 307 | a value. The PseudoDC records the drawing to an operation |
| 308 | list. The operations can be played back to a real DC using:<pre> |
| 309 | DrawToDC(dc) |
| 310 | </pre> |
| 311 | The operations can be tagged with an id in order to associated them with a |
| 312 | specific object. To do this use:<pre> |
| 313 | SetId(id) |
| 314 | </pre> |
| 315 | Every operation after this will be associated with id until SetId is called |
| 316 | again. The PseudoDC also supports object level clipping. To enable this use:<pre> |
| 317 | SetIdBounds(id,rect) |
| 318 | </pre> |
| 319 | for each object that should be clipped. Then use:<pre> |
| 320 | DrawToDCClipped(dc, clippingRect) |
| 321 | </pre> |
| 322 | To draw the PseudoDC to a real dc. This is useful for large scrolled windows |
| 323 | where many objects are offscreen. |
| 324 | |
| 325 | Objects can be moved around without re-drawing using:<pre> |
| 326 | TranslateId(id, dx, dy) |
| 327 | </pre> |
| 328 | |
| 329 | To re-draw an object use:<pre> |
| 330 | ClearId(id) |
| 331 | SetId(id) |
| 332 | </pre> |
| 333 | and then re-draw the object. |
| 334 | </body> |
| 335 | </html> |
| 336 | """ |
| 337 | |
| 338 | |
| 339 | if __name__ == '__main__': |
| 340 | import sys,os |
| 341 | import run |
| 342 | run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) |
| 343 | |