1""" 2@package mapwin.mapwindow 3 4@brief Map display canvas basic functionality - base class and properties. 5 6Classes: 7 - mapwindow::MapWindowProperties 8 - mapwindow::MapWindowBase 9 10(C) 2006-2012 by the GRASS Development Team 11 12This program is free software under the GNU General Public License 13(>=v2). Read the file COPYING that comes with GRASS for details. 14 15@author Martin Landa <landa.martin gmail.com> 16@author Michael Barton 17@author Jachym Cepicky 18@author Vaclav Petras <wenzeslaus gmail.com> (handlers support) 19@author Stepan Turek <stepan.turek seznam.cz> (handlers support) 20""" 21 22import wx 23import six 24 25from core.settings import UserSettings 26from core.gcmd import GError 27from gui_core.wrap import StockCursor 28 29from grass.script import core as grass 30from grass.pydispatch.signal import Signal 31 32 33class MapWindowProperties(object): 34 35 def __init__(self): 36 self._resolution = None 37 self.resolutionChanged = Signal( 38 'MapWindowProperties.resolutionChanged') 39 self._autoRender = None 40 self.autoRenderChanged = Signal( 41 'MapWindowProperties.autoRenderChanged') 42 self._showRegion = None 43 self.showRegionChanged = Signal( 44 'MapWindowProperties.showRegionChanged') 45 self._alignExtent = None 46 self.alignExtentChanged = Signal( 47 'MapWindowProperties.alignExtentChanged') 48 49 def setValuesFromUserSettings(self): 50 """Convenient function to get values from user settings into this object.""" 51 self._resolution = UserSettings.Get(group='display', 52 key='compResolution', 53 subkey='enabled') 54 self._autoRender = UserSettings.Get(group='display', 55 key='autoRendering', 56 subkey='enabled') 57 self._showRegion = False # in statusbar.py was not from settings 58 self._alignExtent = UserSettings.Get(group='display', 59 key='alignExtent', 60 subkey='enabled') 61 62 @property 63 def resolution(self): 64 return self._resolution 65 66 @resolution.setter 67 def resolution(self, value): 68 if value != self._resolution: 69 self._resolution = value 70 self.resolutionChanged.emit(value=value) 71 72 @property 73 def autoRender(self): 74 return self._autoRender 75 76 @autoRender.setter 77 def autoRender(self, value): 78 if value != self._autoRender: 79 self._autoRender = value 80 self.autoRenderChanged.emit(value=value) 81 82 @property 83 def showRegion(self): 84 return self._showRegion 85 86 @showRegion.setter 87 def showRegion(self, value): 88 if value != self._showRegion: 89 self._showRegion = value 90 self.showRegionChanged.emit(value=value) 91 92 @property 93 def alignExtent(self): 94 return self._alignExtent 95 96 @alignExtent.setter 97 def alignExtent(self, value): 98 if value != self._alignExtent: 99 self._alignExtent = value 100 self.alignExtentChanged.emit(value=value) 101 102 103class MapWindowBase(object): 104 """Abstract map display window class 105 106 Superclass for BufferedWindow class (2D display mode), and GLWindow 107 (3D display mode). 108 109 Subclasses have to define 110 - _bindMouseEvents method which binds MouseEvent handlers 111 - Pixel2Cell 112 - Cell2Pixel (if it is possible) 113 """ 114 115 def __init__(self, parent, giface, Map): 116 self.parent = parent 117 self.Map = Map 118 self._giface = giface 119 120 # Emitted when someone registers as mouse event handler 121 self.mouseHandlerRegistered = Signal( 122 'MapWindow.mouseHandlerRegistered') 123 # Emitted when mouse event handler is unregistered 124 self.mouseHandlerUnregistered = Signal( 125 'MapWindow.mouseHandlerUnregistered') 126 # emitted after double click in pointer mode on legend, text, scalebar 127 self.overlayActivated = Signal('MapWindow.overlayActivated') 128 # emitted when overlay should be hidden 129 self.overlayRemoved = Signal('MapWindow.overlayRemoved') 130 131 # mouse attributes -- position on the screen, begin and end of 132 # dragging, and type of drawing 133 self.mouse = { 134 'begin': [0, 0], # screen coordinates 135 'end': [0, 0], 136 'use': "pointer", 137 'box': "point" 138 } 139 # last east, north coordinates, changes on mouse motion 140 self.lastEN = None 141 142 # stores overridden cursor 143 self._overriddenCursor = None 144 145 # dictionary where event types are stored as keys and lists of 146 # handlers for these types as values 147 self.handlersContainer = { 148 wx.EVT_LEFT_DOWN: [], 149 wx.EVT_LEFT_UP: [], 150 wx.EVT_LEFT_DCLICK: [], 151 wx.EVT_MIDDLE_DOWN: [], 152 wx.EVT_MIDDLE_UP: [], 153 wx.EVT_MIDDLE_DCLICK: [], 154 wx.EVT_RIGHT_DOWN: [], 155 wx.EVT_RIGHT_UP: [], 156 wx.EVT_RIGHT_DCLICK: [], 157 wx.EVT_MOTION: [], 158 wx.EVT_ENTER_WINDOW: [], 159 wx.EVT_LEAVE_WINDOW: [], 160 wx.EVT_MOUSEWHEEL: [], 161 wx.EVT_MOUSE_EVENTS: [] 162 } 163 164 # available cursors 165 self._cursors = { 166 "default": StockCursor(cursorId=wx.CURSOR_ARROW), 167 "cross": StockCursor(cursorId=wx.CURSOR_CROSS), 168 "hand": StockCursor(cursorId=wx.CURSOR_HAND), 169 "pencil": StockCursor(cursorId=wx.CURSOR_PENCIL), 170 "sizenwse": StockCursor(cursorId=wx.CURSOR_SIZENWSE) 171 } 172 173 # default cursor for window is arrow (at least we rely on it here) 174 # but we need to define attribute here 175 # cannot call SetNamedCursor since it expects the instance 176 # to be a wx window, so setting only the attribute 177 self._cursor = 'default' 178 179 wx.CallAfter(self.InitBinding) 180 181 def __del__(self): 182 self.UnregisterAllHandlers() 183 184 def InitBinding(self): 185 """Binds helper functions, which calls all handlers 186 registered to events with the events 187 """ 188 for ev, handlers in six.iteritems(self.handlersContainer): 189 self.Bind(ev, self.EventTypeHandler(handlers)) 190 191 def EventTypeHandler(self, evHandlers): 192 return lambda event: self.HandlersCaller(event, evHandlers) 193 194 def HandlersCaller(self, event, handlers): 195 """Hepler function which calls all handlers registered for 196 event 197 """ 198 for handler in handlers: 199 try: 200 handler(event) 201 except: 202 handlers.remove(handler) 203 GError( 204 parent=self, message=_( 205 "Error occurred during calling of handler: %s \n" 206 "Handler was unregistered.") % 207 handler.__name__) 208 209 event.Skip() 210 211 def RegisterMouseEventHandler(self, event, handler, cursor=None): 212 """Binds event handler 213 214 @depreciated This method is depreciated. Use Signals or drawing API 215 instead. Signals do not cover all events but new Signals can be added 216 when needed consider also adding generic signal. However, more 217 interesing and useful is higher level API to create objects, graphics etc. 218 219 Call event.Skip() in handler to allow default processing in MapWindow. 220 221 If any error occurs inside of handler, the handler is removed. 222 223 Before handler is unregistered it is called with 224 string value "unregistered" of event parameter. 225 226 :: 227 228 # your class methods 229 def OnButton(self, event): 230 # current map display's map window 231 # expects LayerManager to be the parent 232 self.mapwin = self.parent.GetLayerTree().GetMapDisplay().GetWindow() 233 if self.mapwin.RegisterEventHandler(wx.EVT_LEFT_DOWN, self.OnMouseAction, 234 'cross'): 235 self.parent.GetLayerTree().GetMapDisplay().Raise() 236 else: 237 # handle that you cannot get coordinates 238 239 def OnMouseAction(self, event): 240 # get real world coordinates of mouse click 241 coor = self.mapwin.Pixel2Cell(event.GetPositionTuple()[:]) 242 self.text.SetLabel('Coor: ' + str(coor)) 243 self.mapwin.UnregisterMouseEventHandler(wx.EVT_LEFT_DOWN, self.OnMouseAction) 244 event.Skip() 245 246 247 Emits mouseHandlerRegistered signal before handler is registered. 248 249 :param event: one of mouse events 250 :param handler: function to handle event 251 :param cursor: cursor which temporary overrides current cursor 252 253 :return: True if successful 254 :return: False if event cannot be bind 255 """ 256 self.mouseHandlerRegistered.emit() 257 # inserts handler into list 258 for containerEv, handlers in six.iteritems(self.handlersContainer): 259 if event == containerEv: 260 handlers.append(handler) 261 262 self.mouse['useBeforeGenericEvent'] = self.mouse['use'] 263 self.mouse['use'] = 'genericEvent' 264 265 if cursor: 266 self._overriddenCursor = self.GetNamedCursor() 267 self.SetNamedCursor(cursor) 268 269 return True 270 271 def UnregisterAllHandlers(self): 272 """Unregisters all registered handlers 273 274 @depreciated This method is depreciated. Use Signals or drawing API instead. 275 276 Before each handler is unregistered it is called with string 277 value "unregistered" of event parameter. 278 """ 279 for containerEv, handlers in six.iteritems(self.handlersContainer): 280 for handler in handlers: 281 try: 282 handler("unregistered") 283 handlers.remove(handler) 284 except: 285 GError(parent=self, 286 message=_("Error occurred during unregistration of handler: %s \n \ 287 Handler was unregistered.") % handler.__name__) 288 handlers.remove(handler) 289 290 def UnregisterMouseEventHandler(self, event, handler): 291 """Unbinds event handler for event 292 293 @depreciated This method is depreciated. Use Signals or drawing API instead. 294 295 Before handler is unregistered it is called with string value 296 "unregistered" of event parameter. 297 298 Emits mouseHandlerUnregistered signal after handler is unregistered. 299 300 :param handler: handler to unbind 301 :param event: event from which handler will be unbinded 302 303 :return: True if successful 304 :return: False if event cannot be unbind 305 """ 306 # removes handler from list 307 for containerEv, handlers in six.iteritems(self.handlersContainer): 308 if event != containerEv: 309 continue 310 try: 311 handler("unregistered") 312 if handler in handlers: 313 handlers.remove(handler) 314 else: 315 grass.warning(_("Handler: %s was not registered") 316 % handler.__name__) 317 except: 318 GError(parent=self, 319 message=_("Error occurred during unregistration of handler: %s \n \ 320 Handler was unregistered") % handler.__name__) 321 handlers.remove(handler) 322 323 # restore mouse use (previous state) 324 self.mouse['use'] = self.mouse['useBeforeGenericEvent'] 325 326 # restore overridden cursor 327 if self._overriddenCursor: 328 self.SetNamedCursor(self._overriddenCursor) 329 330 self.mouseHandlerUnregistered.emit() 331 return True 332 333 def Pixel2Cell(self, xyCoords): 334 raise NotImplementedError() 335 336 def Cell2Pixel(self, enCoords): 337 raise NotImplementedError() 338 339 def OnMotion(self, event): 340 """Tracks mouse motion and update statusbar 341 342 .. todo:: 343 remove this method when lastEN is not used 344 345 :func:`GetLastEN` 346 """ 347 try: 348 self.lastEN = self.Pixel2Cell(event.GetPosition()) 349 except (ValueError): 350 self.lastEN = None 351 352 event.Skip() 353 354 def GetLastEN(self): 355 """Returns last coordinates of mouse cursor. 356 357 @depreciated This method is depreciated. Use Signal with coordinates as parameters. 358 359 :func:`OnMotion` 360 """ 361 return self.lastEN 362 363 def SetNamedCursor(self, cursorName): 364 """Sets cursor defined by name.""" 365 cursor = self._cursors[cursorName] 366 self.SetCursor(cursor) 367 self._cursor = cursorName 368 369 def GetNamedCursor(self): 370 """Returns current cursor name.""" 371 return self._cursor 372 373 cursor = property(fget=GetNamedCursor, fset=SetNamedCursor) 374 375 def SetModePointer(self): 376 """Sets mouse mode to pointer.""" 377 self.mouse['use'] = 'pointer' 378 self.mouse['box'] = 'point' 379 self.SetNamedCursor('default') 380 381 def SetModePan(self): 382 """Sets mouse mode to pan.""" 383 self.mouse['use'] = "pan" 384 self.mouse['box'] = "box" 385 self.zoomtype = 0 386 self.SetNamedCursor('hand') 387 388 def SetModeZoomIn(self): 389 self._setModeZoom(zoomType=1) 390 391 def SetModeZoomOut(self): 392 self._setModeZoom(zoomType=-1) 393 394 def _setModeZoom(self, zoomType): 395 self.zoomtype = zoomType 396 self.mouse['use'] = "zoom" 397 self.mouse['box'] = "box" 398 self.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) 399 self.SetNamedCursor('cross') 400 401 def SetModeDrawRegion(self): 402 self.mouse['use'] = 'drawRegion' 403 self.mouse['box'] = "box" 404 self.pen = wx.Pen(colour='Red', width=2, style=wx.SHORT_DASH) 405 self.SetNamedCursor('cross') 406 407 def SetModeQuery(self): 408 """Query mode on""" 409 self.mouse['use'] = "query" 410 self.mouse['box'] = "point" 411 self.zoomtype = 0 412 self.SetNamedCursor('cross') 413 414 def DisactivateWin(self): 415 """Use when the class instance is hidden in MapFrame.""" 416 raise NotImplementedError() 417 418 def ActivateWin(self): 419 """Used when the class instance is activated in MapFrame.""" 420 raise NotImplementedError() 421