1#---------------------------------------------------------------------------- 2# Name: GUIMode.py 3# Purpose: 4# 5# Author: 6# 7# Created: 8# Version: 9# Date: 10# Licence: 11# Tags: phoenix-port 12#---------------------------------------------------------------------------- 13""" 14 15Module that holds the GUI modes used by FloatCanvas 16 17Note that this can only be imported after a wx.App() has been created. 18 19This approach was inspired by Christian Blouin, who also wrote the initial 20version of the code. 21 22""" 23 24import wx 25import numpy as N 26 27from . import FCEvents, Resources 28from .Utilities import BBox 29 30 31class Cursors(object): 32 """ 33 Class to hold the standard Cursors 34 35 """ 36 def __init__(self): 37 if "wxMac" in wx.PlatformInfo: # use 16X16 cursors for wxMac 38 self.HandCursor = wx.Cursor(Resources.getHand16Image()) 39 self.GrabHandCursor = wx.Cursor(Resources.getGrabHand16Image()) 40 41 img = Resources.getMagPlus16Image() 42 img.SetOption(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6) 43 img.SetOption(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6) 44 self.MagPlusCursor = wx.Cursor(img) 45 46 img = Resources.getMagMinus16Image() 47 img.SetOption(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 6) 48 img.SetOption(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 6) 49 self.MagMinusCursor = wx.Cursor(img) 50 else: # use 24X24 cursors for GTK and Windows 51 self.HandCursor = wx.Cursor(Resources.getHandImage()) 52 self.GrabHandCursor = wx.Cursor(Resources.getGrabHandImage()) 53 54 img = Resources.getMagPlusImage() 55 img.SetOption(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9) 56 img.SetOption(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9) 57 self.MagPlusCursor = wx.Cursor(img) 58 59 img = Resources.getMagMinusImage() 60 img.SetOption(wx.IMAGE_OPTION_CUR_HOTSPOT_X, 9) 61 img.SetOption(wx.IMAGE_OPTION_CUR_HOTSPOT_Y, 9) 62 self.MagMinusCursor = wx.Cursor(img) 63 64 65class GUIBase(object): 66 """ 67 Basic Mouse mode and baseclass for other GUImode. 68 69 This one does nothing with any event 70 71 """ 72 def __init__(self, Canvas=None): 73 """ 74 Default class constructor. 75 76 :param `Canvas`: the canvas the GUI mode is attached too 77 78 """ 79 self.Canvas = Canvas # set the FloatCanvas for the mode 80 # it gets set when the Mode is set on the Canvas. 81 self.Cursors = Cursors() 82 83 Cursor = wx.NullCursor 84 def UnSet(self): 85 """ 86 this method gets called by FloatCanvas when a new mode is being set 87 on the Canvas 88 """ 89 pass 90 # Handlers 91 def OnLeftDown(self, event): 92 pass 93 def OnLeftUp(self, event): 94 pass 95 def OnLeftDouble(self, event): 96 pass 97 def OnRightDown(self, event): 98 pass 99 def OnRightUp(self, event): 100 pass 101 def OnRightDouble(self, event): 102 pass 103 def OnMiddleDown(self, event): 104 pass 105 def OnMiddleUp(self, event): 106 pass 107 def OnMiddleDouble(self, event): 108 pass 109 def OnWheel(self, event): 110 pass 111 def OnMove(self, event): 112 pass 113 def OnKeyDown(self, event): 114 pass 115 def OnKeyUp(self, event): 116 pass 117 def UpdateScreen(self): 118 """ 119 Update gets called if the screen has been repainted in the middle of a zoom in 120 so the Rubber Band Box can get updated. Other GUIModes may require something similar 121 """ 122 pass 123 124 125## some mix-ins for use with the other modes: 126class ZoomWithMouseWheel(): 127 def OnWheel(self, event): 128 point = event.Position 129 if event.GetWheelRotation() < 0: 130 self.Canvas.Zoom(0.9, point, centerCoords = "pixel", keepPointInPlace=True) 131 else: 132 self.Canvas.Zoom(1.1, point, centerCoords = "pixel", keepPointInPlace=True) 133 134 135class GUIMouse(GUIBase): 136 """ 137 138 Mouse mode checks for a hit test, and if nothing is hit, 139 raises a FloatCanvas mouse event for each event. 140 141 """ 142 143 Cursor = wx.NullCursor 144 145 # Handlers 146 def OnLeftDown(self, event): 147 EventType = FCEvents.EVT_FC_LEFT_DOWN 148 if not self.Canvas.HitTest(event, EventType): 149 self.Canvas._RaiseMouseEvent(event, EventType) 150 151 def OnLeftUp(self, event): 152 EventType = FCEvents.EVT_FC_LEFT_UP 153 if not self.Canvas.HitTest(event, EventType): 154 self.Canvas._RaiseMouseEvent(event, EventType) 155 156 def OnLeftDouble(self, event): 157 EventType = FCEvents.EVT_FC_LEFT_DCLICK 158 if not self.Canvas.HitTest(event, EventType): 159 self.Canvas._RaiseMouseEvent(event, EventType) 160 161 def OnMiddleDown(self, event): 162 EventType = FCEvents.EVT_FC_MIDDLE_DOWN 163 if not self.Canvas.HitTest(event, EventType): 164 self.Canvas._RaiseMouseEvent(event, EventType) 165 166 def OnMiddleUp(self, event): 167 EventType = FCEvents.EVT_FC_MIDDLE_UP 168 if not self.Canvas.HitTest(event, EventType): 169 self.Canvas._RaiseMouseEvent(event, EventType) 170 171 def OnMiddleDouble(self, event): 172 EventType = FCEvents.EVT_FC_MIDDLE_DCLICK 173 if not self.Canvas.HitTest(event, EventType): 174 self.Canvas._RaiseMouseEvent(event, EventType) 175 176 def OnRightDown(self, event): 177 EventType = FCEvents.EVT_FC_RIGHT_DOWN 178 if not self.Canvas.HitTest(event, EventType): 179 self.Canvas._RaiseMouseEvent(event, EventType) 180 181 def OnRightUp(self, event): 182 EventType = FCEvents.EVT_FC_RIGHT_UP 183 if not self.Canvas.HitTest(event, EventType): 184 self.Canvas._RaiseMouseEvent(event, EventType) 185 186 def OnRightDouble(self, event): 187 EventType = FCEvents.EVT_FC_RIGHT_DCLICK 188 if not self.Canvas.HitTest(event, EventType): 189 self.Canvas._RaiseMouseEvent(event, EventType) 190 191 def OnWheel(self, event): 192 EventType = FCEvents.EVT_FC_MOUSEWHEEL 193 self.Canvas._RaiseMouseEvent(event, EventType) 194 195 def OnMove(self, event): 196 ## The Move event always gets raised, even if there is a hit-test 197 EventType = FCEvents.EVT_FC_MOTION 198 # process the object hit test for EVT_MOTION bindings 199 self.Canvas.HitTest(event, EventType) 200 # process enter and leave events 201 self.Canvas.MouseOverTest(event) 202 # then raise the event on the canvas 203 self.Canvas._RaiseMouseEvent(event, EventType) 204 205 206class GUIMove(ZoomWithMouseWheel, GUIBase): 207 """ 208 Mode that moves the image (pans). 209 It doesn't change any coordinates, it only changes what the viewport is 210 """ 211 def __init__(self, canvas=None): 212 GUIBase.__init__(self, canvas) 213 self.Cursor = self.Cursors.HandCursor 214 self.GrabCursor = self.Cursors.GrabHandCursor 215 self.StartMove = None 216 self.MidMove = None 217 self.PrevMoveXY = None 218 219 ## timer to give a delay when moving so that buffers aren't re-built too many times. 220 self.MoveTimer = wx.PyTimer(self.OnMoveTimer) 221 222 def OnLeftDown(self, event): 223 self.Canvas.SetCursor(self.GrabCursor) 224 self.Canvas.CaptureMouse() 225 self.StartMove = N.array( event.GetPosition() ) 226 self.MidMove = self.StartMove 227 self.PrevMoveXY = (0,0) 228 229 def OnLeftUp(self, event): 230 self.Canvas.SetCursor(self.Cursor) 231 if self.StartMove is not None: 232 self.EndMove = N.array(event.GetPosition()) 233 DiffMove = self.MidMove-self.EndMove 234 self.Canvas.MoveImage(DiffMove, 'Pixel', ReDraw=True) 235 236 def OnMove(self, event): 237 # Always raise the Move event. 238 self.Canvas._RaiseMouseEvent(event, FCEvents.EVT_FC_MOTION) 239 if event.Dragging() and event.LeftIsDown() and not self.StartMove is None: 240 self.EndMove = N.array(event.GetPosition()) 241 self.MoveImage(event) 242 DiffMove = self.MidMove-self.EndMove 243 self.Canvas.MoveImage(DiffMove, 'Pixel', ReDraw=False)# reset the canvas without re-drawing 244 self.MidMove = self.EndMove 245 self.MoveTimer.Start(30, oneShot=True) 246 247 def OnMoveTimer(self, event=None): 248 self.Canvas.Draw() 249 250 def UpdateScreen(self): 251 ## The screen has been re-drawn, so StartMove needs to be reset. 252 self.StartMove = self.MidMove 253 254 def MoveImage(self, event ): 255 #xy1 = N.array( event.GetPosition() ) 256 xy1 = self.EndMove 257 wh = self.Canvas.PanelSize 258 xy_tl = xy1 - self.StartMove 259 dc = wx.ClientDC(self.Canvas) 260 x1,y1 = self.PrevMoveXY 261 x2,y2 = xy_tl 262 w,h = self.Canvas.PanelSize 263 ##fixme: This sure could be cleaner! 264 ## This is all to fill in the background with the background color 265 ## without flashing as the image moves. 266 if x2 > x1 and y2 > y1: 267 xa = xb = x1 268 ya = yb = y1 269 wa = w 270 ha = y2 - y1 271 wb = x2- x1 272 hb = h 273 elif x2 > x1 and y2 <= y1: 274 xa = x1 275 ya = y1 276 wa = x2 - x1 277 ha = h 278 xb = x1 279 yb = y2 + h 280 wb = w 281 hb = y1 - y2 282 elif x2 <= x1 and y2 > y1: 283 xa = x1 284 ya = y1 285 wa = w 286 ha = y2 - y1 287 xb = x2 + w 288 yb = y1 289 wb = x1 - x2 290 hb = h - y2 + y1 291 elif x2 <= x1 and y2 <= y1: 292 xa = x2 + w 293 ya = y1 294 wa = x1 - x2 295 ha = h 296 xb = x1 297 yb = y2 + h 298 wb = w 299 hb = y1 - y2 300 301 dc.SetPen(wx.TRANSPARENT_PEN) 302 dc.SetBrush(self.Canvas.BackgroundBrush) 303 dc.DrawRectangle(xa, ya, wa, ha) 304 dc.DrawRectangle(xb, yb, wb, hb) 305 self.PrevMoveXY = xy_tl 306 if self.Canvas._ForeDrawList: 307 dc.DrawBitmap(self.Canvas._ForegroundBuffer,xy_tl) 308 else: 309 dc.DrawBitmap(self.Canvas._Buffer,xy_tl) 310 #self.Canvas.Update() 311 312 313class GUIZoomIn(ZoomWithMouseWheel, GUIBase): 314 """ 315 Mode to zoom in. 316 """ 317 def __init__(self, canvas=None): 318 GUIBase.__init__(self, canvas) 319 self.StartRBBox = None 320 self.PrevRBBox = None 321 self.Cursor = self.Cursors.MagPlusCursor 322 323 def OnLeftDown(self, event): 324 self.StartRBBox = N.array( event.GetPosition() ) 325 self.PrevRBBox = None 326 self.Canvas.CaptureMouse() 327 328 def OnLeftUp(self, event): 329 if event.LeftUp() and not self.StartRBBox is None: 330 self.PrevRBBox = None 331 EndRBBox = event.GetPosition() 332 StartRBBox = self.StartRBBox 333 # if mouse has moved less that ten pixels, don't use the box. 334 if ( abs(StartRBBox[0] - EndRBBox[0]) > 10 335 and abs(StartRBBox[1] - EndRBBox[1]) > 10 ): 336 EndRBBox = self.Canvas.PixelToWorld(EndRBBox) 337 StartRBBox = self.Canvas.PixelToWorld(StartRBBox) 338 self.Canvas.ZoomToBB( BBox.fromPoints(N.r_[EndRBBox,StartRBBox]) ) 339 else: 340 Center = self.Canvas.PixelToWorld(StartRBBox) 341 self.Canvas.Zoom(1.5,Center) 342 self.StartRBBox = None 343 344 def OnMove(self, event): 345 # Always raise the Move event. 346 self.Canvas._RaiseMouseEvent(event,FCEvents.EVT_FC_MOTION) 347 if event.Dragging() and event.LeftIsDown() and not (self.StartRBBox is None): 348 xy0 = self.StartRBBox 349 xy1 = N.array( event.GetPosition() ) 350 wh = abs(xy1 - xy0) 351 wh[0] = max(wh[0], int(wh[1]*self.Canvas.AspectRatio)) 352 wh[1] = int(wh[0] / self.Canvas.AspectRatio) 353 xy_c = (xy0 + xy1) / 2 354 dc = wx.ClientDC(self.Canvas) 355 dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) 356 dc.SetBrush(wx.TRANSPARENT_BRUSH) 357 dc.SetLogicalFunction(wx.XOR) 358 if self.PrevRBBox: 359 dc.DrawRectangle(*self.PrevRBBox) 360 self.PrevRBBox = ( xy_c - wh/2, wh ) 361 dc.DrawRectangle( *self.PrevRBBox ) 362 363 def UpdateScreen(self): 364 """ 365 Update gets called if the screen has been repainted in the middle of a zoom in 366 so the Rubber Band Box can get updated 367 """ 368 #if False: 369 if self.PrevRBBox is not None: 370 dc = wx.ClientDC(self.Canvas) 371 dc.SetPen(wx.Pen('WHITE', 2, wx.SHORT_DASH)) 372 dc.SetBrush(wx.TRANSPARENT_BRUSH) 373 dc.SetLogicalFunction(wx.XOR) 374 dc.DrawRectangle(*self.PrevRBBox) 375 376 def OnRightDown(self, event): 377 self.Canvas.Zoom(1/1.5, event.GetPosition(), centerCoords="pixel") 378 379 380class GUIZoomOut(ZoomWithMouseWheel, GUIBase): 381 """ 382 Mode to zoom out. 383 """ 384 def __init__(self, Canvas=None): 385 GUIBase.__init__(self, Canvas) 386 self.Cursor = self.Cursors.MagMinusCursor 387 388 def OnLeftDown(self, event): 389 self.Canvas.Zoom(1/1.5, event.GetPosition(), centerCoords="pixel") 390 391 def OnRightDown(self, event): 392 self.Canvas.Zoom(1.5, event.GetPosition(), centerCoords="pixel") 393 394 def OnMove(self, event): 395 # Always raise the Move event. 396 self.Canvas._RaiseMouseEvent(event,FCEvents.EVT_FC_MOTION) 397