| 1 | |
| 2 | import wx |
| 3 | import images |
| 4 | |
| 5 | #---------------------------------------------------------------------- |
| 6 | |
| 7 | class DragShape: |
| 8 | def __init__(self, bmp): |
| 9 | self.bmp = bmp |
| 10 | self.pos = (0,0) |
| 11 | self.shown = True |
| 12 | self.text = None |
| 13 | self.fullscreen = False |
| 14 | |
| 15 | def HitTest(self, pt): |
| 16 | rect = self.GetRect() |
| 17 | return rect.InsideXY(pt.x, pt.y) |
| 18 | |
| 19 | def GetRect(self): |
| 20 | return wx.Rect(self.pos[0], self.pos[1], |
| 21 | self.bmp.GetWidth(), self.bmp.GetHeight()) |
| 22 | |
| 23 | def Draw(self, dc, op = wx.COPY): |
| 24 | if self.bmp.Ok(): |
| 25 | memDC = wx.MemoryDC() |
| 26 | memDC.SelectObject(self.bmp) |
| 27 | |
| 28 | dc.Blit(self.pos[0], self.pos[1], |
| 29 | self.bmp.GetWidth(), self.bmp.GetHeight(), |
| 30 | memDC, 0, 0, op, True) |
| 31 | |
| 32 | return True |
| 33 | else: |
| 34 | return False |
| 35 | |
| 36 | |
| 37 | |
| 38 | #---------------------------------------------------------------------- |
| 39 | |
| 40 | class DragCanvas(wx.ScrolledWindow): |
| 41 | def __init__(self, parent, ID): |
| 42 | wx.ScrolledWindow.__init__(self, parent, ID) |
| 43 | self.shapes = [] |
| 44 | self.dragImage = None |
| 45 | self.dragShape = None |
| 46 | self.hiliteShape = None |
| 47 | |
| 48 | self.SetCursor(wx.StockCursor(wx.CURSOR_ARROW)) |
| 49 | self.bg_bmp = images.getBackgroundBitmap() |
| 50 | |
| 51 | # Make a shape from an image and mask. This one will demo |
| 52 | # dragging outside the window |
| 53 | bmp = images.getTestStarBitmap() |
| 54 | ##bmp = wx.Bitmap('bitmaps/toucan.png') |
| 55 | shape = DragShape(bmp) |
| 56 | shape.pos = (5, 5) |
| 57 | shape.fullscreen = True |
| 58 | self.shapes.append(shape) |
| 59 | |
| 60 | # Make a shape from some text |
| 61 | text = "Some Text" |
| 62 | bg_colour = wx.Colour(57, 115, 57) # matches the bg image |
| 63 | font = wx.Font(15, wx.ROMAN, wx.NORMAL, wx.BOLD) |
| 64 | textExtent = self.GetFullTextExtent(text, font) |
| 65 | |
| 66 | # create a bitmap the same size as our text |
| 67 | bmp = wx.EmptyBitmap(textExtent[0], textExtent[1]) |
| 68 | |
| 69 | # 'draw' the text onto the bitmap |
| 70 | dc = wx.MemoryDC() |
| 71 | dc.SelectObject(bmp) |
| 72 | dc.SetBackground(wx.Brush(bg_colour, wx.SOLID)) |
| 73 | dc.Clear() |
| 74 | dc.SetTextForeground(wx.RED) |
| 75 | dc.SetFont(font) |
| 76 | dc.DrawText(text, 0, 0) |
| 77 | dc.SelectObject(wx.NullBitmap) |
| 78 | mask = wx.Mask(bmp, bg_colour) |
| 79 | bmp.SetMask(mask) |
| 80 | shape = DragShape(bmp) |
| 81 | shape.pos = (5, 100) |
| 82 | shape.text = "Some dragging text" |
| 83 | self.shapes.append(shape) |
| 84 | |
| 85 | |
| 86 | # Make some shapes from some playing card images. |
| 87 | x = 200 |
| 88 | |
| 89 | for card in ['_01c_', '_12h_', '_13d_', '_10s_']: |
| 90 | bmpFunc = getattr(images, "get%sBitmap" % card) |
| 91 | bmp = bmpFunc() |
| 92 | shape = DragShape(bmp) |
| 93 | shape.pos = (x, 5) |
| 94 | self.shapes.append(shape) |
| 95 | x = x + 80 |
| 96 | |
| 97 | |
| 98 | self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground) |
| 99 | self.Bind(wx.EVT_PAINT, self.OnPaint) |
| 100 | self.Bind(wx.EVT_LEFT_DOWN, self.OnLeftDown) |
| 101 | self.Bind(wx.EVT_LEFT_UP, self.OnLeftUp) |
| 102 | self.Bind(wx.EVT_MOTION, self.OnMotion) |
| 103 | self.Bind(wx.EVT_LEAVE_WINDOW, self.OnLeaveWindow) |
| 104 | |
| 105 | |
| 106 | # We're not doing anything here, but you might have reason to. |
| 107 | # for example, if you were dragging something, you might elect to |
| 108 | # 'drop it' when the cursor left the window. |
| 109 | def OnLeaveWindow(self, evt): |
| 110 | pass |
| 111 | |
| 112 | |
| 113 | # tile the background bitmap |
| 114 | def TileBackground(self, dc): |
| 115 | sz = self.GetClientSize() |
| 116 | w = self.bg_bmp.GetWidth() |
| 117 | h = self.bg_bmp.GetHeight() |
| 118 | |
| 119 | x = 0 |
| 120 | |
| 121 | while x < sz.width: |
| 122 | y = 0 |
| 123 | |
| 124 | while y < sz.height: |
| 125 | dc.DrawBitmap(self.bg_bmp, x, y) |
| 126 | y = y + h |
| 127 | |
| 128 | x = x + w |
| 129 | |
| 130 | |
| 131 | # Go through our list of shapes and draw them in whatever place they are. |
| 132 | def DrawShapes(self, dc): |
| 133 | for shape in self.shapes: |
| 134 | if shape.shown: |
| 135 | shape.Draw(dc) |
| 136 | |
| 137 | # This is actually a sophisticated 'hit test', but in this |
| 138 | # case we're also determining which shape, if any, was 'hit'. |
| 139 | def FindShape(self, pt): |
| 140 | for shape in self.shapes: |
| 141 | if shape.HitTest(pt): |
| 142 | return shape |
| 143 | return None |
| 144 | |
| 145 | # Remove a shape from the display |
| 146 | def EraseShape(self, shape, dc): |
| 147 | r = shape.GetRect() |
| 148 | dc.SetClippingRect(r) |
| 149 | self.TileBackground(dc) |
| 150 | self.DrawShapes(dc) |
| 151 | dc.DestroyClippingRegion() |
| 152 | |
| 153 | # Clears the background, then redraws it. If the DC is passed, then |
| 154 | # we only do so in the area so designated. Otherwise, it's the whole thing. |
| 155 | def OnEraseBackground(self, evt): |
| 156 | dc = evt.GetDC() |
| 157 | |
| 158 | if not dc: |
| 159 | dc = wxClientDC(self) |
| 160 | rect = self.GetUpdateRegion().GetBox() |
| 161 | dc.SetClippingRect(rect) |
| 162 | self.TileBackground(dc) |
| 163 | |
| 164 | # Fired whenever a paint event occurs |
| 165 | def OnPaint(self, evt): |
| 166 | dc = wx.PaintDC(self) |
| 167 | self.PrepareDC(dc) |
| 168 | self.DrawShapes(dc) |
| 169 | |
| 170 | # Left mouse button is down. |
| 171 | def OnLeftDown(self, evt): |
| 172 | # Did the mouse go down on one of our shapes? |
| 173 | shape = self.FindShape(evt.GetPosition()) |
| 174 | |
| 175 | # If a shape was 'hit', then set that as the shape we're going to |
| 176 | # drag around. Get our start position. Dragging has not yet started. |
| 177 | # That will happen once the mouse moves, OR the mouse is released. |
| 178 | if shape: |
| 179 | self.dragShape = shape |
| 180 | self.dragStartPos = evt.GetPosition() |
| 181 | |
| 182 | # Left mouse button up. |
| 183 | def OnLeftUp(self, evt): |
| 184 | if not self.dragImage or not self.dragShape: |
| 185 | self.dragImage = None |
| 186 | self.dragShape = None |
| 187 | return |
| 188 | |
| 189 | # Hide the image, end dragging, and nuke out the drag image. |
| 190 | self.dragImage.Hide() |
| 191 | self.dragImage.EndDrag() |
| 192 | self.dragImage = None |
| 193 | |
| 194 | dc = wx.ClientDC(self) |
| 195 | |
| 196 | if self.hiliteShape: |
| 197 | self.hiliteShape.Draw(dc) |
| 198 | self.hiliteShape = None |
| 199 | |
| 200 | # reposition and draw the shape |
| 201 | |
| 202 | # Note by jmg 11/28/03 |
| 203 | # Here's the original: |
| 204 | # |
| 205 | # self.dragShape.pos = self.dragShape.pos + evt.GetPosition() - self.dragStartPos |
| 206 | # |
| 207 | # So if there are any problems associated with this, use that as |
| 208 | # a starting place in your investigation. I've tried to simulate the |
| 209 | # wx.Point __add__ method here -- it won't work for tuples as we |
| 210 | # have now from the various methods |
| 211 | # |
| 212 | # There must be a better way to do this :-) |
| 213 | # |
| 214 | |
| 215 | self.dragShape.pos = ( |
| 216 | self.dragShape.pos[0] + evt.GetPosition()[0] - self.dragStartPos[0], |
| 217 | self.dragShape.pos[1] + evt.GetPosition()[1] - self.dragStartPos[1] |
| 218 | ) |
| 219 | |
| 220 | self.dragShape.shown = True |
| 221 | self.dragShape.Draw(dc) |
| 222 | self.dragShape = None |
| 223 | |
| 224 | # The mouse is moving |
| 225 | def OnMotion(self, evt): |
| 226 | # Ignore mouse movement if we're not dragging. |
| 227 | if not self.dragShape or not evt.Dragging() or not evt.LeftIsDown(): |
| 228 | return |
| 229 | |
| 230 | # if we have a shape, but haven't started dragging yet |
| 231 | if self.dragShape and not self.dragImage: |
| 232 | |
| 233 | # only start the drag after having moved a couple pixels |
| 234 | tolerance = 2 |
| 235 | pt = evt.GetPosition() |
| 236 | dx = abs(pt.x - self.dragStartPos.x) |
| 237 | dy = abs(pt.y - self.dragStartPos.y) |
| 238 | if dx <= tolerance and dy <= tolerance: |
| 239 | return |
| 240 | |
| 241 | # erase the shape since it will be drawn independently now |
| 242 | dc = wx.ClientDC(self) |
| 243 | self.dragShape.shown = False |
| 244 | self.EraseShape(self.dragShape, dc) |
| 245 | |
| 246 | |
| 247 | if self.dragShape.text: |
| 248 | self.dragImage = wx.DragString(self.dragShape.text, |
| 249 | wx.StockCursor(wx.CURSOR_HAND)) |
| 250 | else: |
| 251 | self.dragImage = wx.DragImage(self.dragShape.bmp, |
| 252 | wx.StockCursor(wx.CURSOR_HAND)) |
| 253 | |
| 254 | hotspot = self.dragStartPos - self.dragShape.pos |
| 255 | self.dragImage.BeginDrag(hotspot, self, self.dragShape.fullscreen) |
| 256 | |
| 257 | self.dragImage.Move(pt) |
| 258 | self.dragImage.Show() |
| 259 | |
| 260 | |
| 261 | # if we have shape and image then move it, posibly highlighting another shape. |
| 262 | elif self.dragShape and self.dragImage: |
| 263 | onShape = self.FindShape(evt.GetPosition()) |
| 264 | unhiliteOld = False |
| 265 | hiliteNew = False |
| 266 | |
| 267 | # figure out what to hilite and what to unhilite |
| 268 | if self.hiliteShape: |
| 269 | if onShape is None or self.hiliteShape is not onShape: |
| 270 | unhiliteOld = True |
| 271 | |
| 272 | if onShape and onShape is not self.hiliteShape and onShape.shown: |
| 273 | hiliteNew = True |
| 274 | |
| 275 | # if needed, hide the drag image so we can update the window |
| 276 | if unhiliteOld or hiliteNew: |
| 277 | self.dragImage.Hide() |
| 278 | |
| 279 | if unhiliteOld: |
| 280 | dc = wx.ClientDC(self) |
| 281 | self.hiliteShape.Draw(dc) |
| 282 | self.hiliteShape = None |
| 283 | |
| 284 | if hiliteNew: |
| 285 | dc = wx.ClientDC(self) |
| 286 | self.hiliteShape = onShape |
| 287 | self.hiliteShape.Draw(dc, wx.INVERT) |
| 288 | |
| 289 | # now move it and show it again if needed |
| 290 | self.dragImage.Move(evt.GetPosition()) |
| 291 | if unhiliteOld or hiliteNew: |
| 292 | self.dragImage.Show() |
| 293 | |
| 294 | |
| 295 | #---------------------------------------------------------------------- |
| 296 | |
| 297 | def runTest(frame, nb, log): |
| 298 | |
| 299 | win = wx.Panel(nb, -1) |
| 300 | canvas = DragCanvas(win, -1) |
| 301 | |
| 302 | def onSize(evt, panel=win, canvas=canvas): |
| 303 | canvas.SetSize(panel.GetSize()) |
| 304 | |
| 305 | win.Bind(wx.EVT_SIZE, onSize) |
| 306 | return win |
| 307 | |
| 308 | #---------------------------------------------------------------------- |
| 309 | |
| 310 | |
| 311 | |
| 312 | overview = """\ |
| 313 | DragImage is used when you wish to drag an object on the screen, and a simple |
| 314 | cursor is not enough. |
| 315 | |
| 316 | On Windows, the WIN32 API is used to do achieve smooth dragging. On other |
| 317 | platforms, <code>GenericDragImage</code> is used. Applications may also prefer to use |
| 318 | <code>GenericDragImage</code> on Windows, too. |
| 319 | |
| 320 | <b>wxPython note</b>: wxPython uses <code>GenericDragImage</code> on all |
| 321 | platforms, but uses the <code>DragImage</code> name. |
| 322 | |
| 323 | To use this class, when you wish to start dragging an image, create a |
| 324 | <code>DragImage</code> object and store it somewhere you can access it as the |
| 325 | drag progresses. Call BeginDrag to start, and EndDrag to stop the drag. To move |
| 326 | the image, initially call Show and then Move. If you wish to update the screen |
| 327 | contents during the drag (for example, highlight an item as in the example), first |
| 328 | call Hide, update the screen, call Move, and then call Show. |
| 329 | |
| 330 | You can drag within one window, or you can use full-screen dragging either across |
| 331 | the whole screen, or just restricted to one area of the screen to save resources. |
| 332 | If you want the user to drag between two windows, then you will need to use |
| 333 | full-screen dragging. |
| 334 | |
| 335 | """ |
| 336 | |
| 337 | |
| 338 | if __name__ == '__main__': |
| 339 | import sys,os |
| 340 | import run |
| 341 | run.main(['', os.path.basename(sys.argv[0])] + sys.argv[1:]) |
| 342 | |