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