/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/view.py
ViewVC logotype

Diff of /branches/WIP-pyshapelib-bramz/Thuban/UI/view.py

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 57 by bh, Thu Sep 13 13:55:33 2001 UTC revision 610 by jonathan, Fri Apr 4 13:56:59 2003 UTC
# Line 1  Line 1 
1  # Copyright (c) 2001 by Intevation GmbH  # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2  # Authors:  # Authors:
3  # Bernhard Herzog <[email protected]>  # Bernhard Herzog <[email protected]>
4  #  #
# Line 15  from math import hypot Line 15  from math import hypot
15    
16  from wxPython.wx import wxWindow,\  from wxPython.wx import wxWindow,\
17       wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\       wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
18       EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION       EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW
19    
20    
21  from wxPython import wx  from wxPython import wx
# Line 24  from wxproj import point_in_polygon_shap Line 24  from wxproj import point_in_polygon_shap
24    
25    
26  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \
27       LAYERS_CHANGED, LAYER_LEGEND_CHANGED, LAYER_VISIBILITY_CHANGED       MAP_LAYERS_CHANGED, LAYER_CHANGED, LAYER_VISIBILITY_CHANGED
28  from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \  from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
29       SHAPETYPE_POINT       SHAPETYPE_POINT
30  from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \  from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
31       ALIGN_LEFT, ALIGN_RIGHT       ALIGN_LEFT, ALIGN_RIGHT
32    from Thuban.Lib.connector import Publisher
33    from Thuban.Model.color import Color
34    
35    from selection import Selection
36  from renderer import ScreenRenderer, PrinterRender  from renderer import ScreenRenderer, PrinterRender
37    
38  import labeldialog  import labeldialog
39    
40  from messages import SELECTED_SHAPE  from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION
41    
42    
43  #  #
# Line 129  class ZoomInTool(RectTool): Line 131  class ZoomInTool(RectTool):
131              Tool.MouseUp(self, event)              Tool.MouseUp(self, event)
132              sx, sy = self.start              sx, sy = self.start
133              cx, cy = self.current              cx, cy = self.current
134              if sx == cx and sy == cy:              if sx == cx or sy == cy:
135                  # Just a mouse click. Simply zoom in by a factor of two                  # Just a mouse click or a degenerate rectangle. Simply
136                    # zoom in by a factor of two
137                    # FIXME: For a click this is the desired behavior but should we
138                    # really do this for degenrate rectagles as well or
139                    # should we ignore them?
140                  self.view.ZoomFactor(2, center = (cx, cy))                  self.view.ZoomFactor(2, center = (cx, cy))
141              else:              else:
142                  # A drag. Zoom in to the rectangle                  # A drag. Zoom in to the rectangle
# Line 140  class ZoomInTool(RectTool): Line 146  class ZoomInTool(RectTool):
146  class ZoomOutTool(RectTool):  class ZoomOutTool(RectTool):
147    
148      """The Zoom-Out Tool"""      """The Zoom-Out Tool"""
149        
150      def Name(self):      def Name(self):
151          return "ZoomOutTool"          return "ZoomOutTool"
152    
# Line 149  class ZoomOutTool(RectTool): Line 155  class ZoomOutTool(RectTool):
155              Tool.MouseUp(self, event)              Tool.MouseUp(self, event)
156              sx, sy = self.start              sx, sy = self.start
157              cx, cy = self.current              cx, cy = self.current
158              if sx == cx and sy == cy:              if sx == cx or sy == cy:
159                  # Just a mouse click. Simply zoom out by a factor of two                  # Just a mouse click or a degenerate rectangle. Simply
160                  self.view.ZoomFactor(0.5, center = (cy, cy))                  # zoom out by a factor of two.
161                    # FIXME: For a click this is the desired behavior but should we
162                    # really do this for degenrate rectagles as well or
163                    # should we ignore them?
164                    self.view.ZoomFactor(0.5, center = (cx, cy))
165              else:              else:
166                  # A drag. Zoom out to the rectangle                  # A drag. Zoom out to the rectangle
167                  self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),                  self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
# Line 167  class PanTool(Tool): Line 177  class PanTool(Tool):
177    
178      def MouseMove(self, event):      def MouseMove(self, event):
179          if self.dragging:          if self.dragging:
             x0, y0 = self.current  
180              Tool.MouseMove(self, event)              Tool.MouseMove(self, event)
181                sx, sy = self.start
182              x, y = self.current              x, y = self.current
183              width, height = self.view.GetSizeTuple()              width, height = self.view.GetSizeTuple()
184    
185                bitmapdc = wx.wxMemoryDC()
186                bitmapdc.SelectObject(self.view.bitmap)
187    
188              dc = self.view.drag_dc              dc = self.view.drag_dc
189              dc.Blit(0, 0, width, height, dc, x0 - x, y0 - y)              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
190    
191      def MouseUp(self, event):      def MouseUp(self, event):
192          if self.dragging:          if self.dragging:
# Line 180  class PanTool(Tool): Line 194  class PanTool(Tool):
194              sx, sy = self.start              sx, sy = self.start
195              cx, cy = self.current              cx, cy = self.current
196              self.view.Translate(cx - sx, cy - sy)              self.view.Translate(cx - sx, cy - sy)
197            
198  class IdentifyTool(Tool):  class IdentifyTool(Tool):
199    
200      """The "Identify" Tool"""      """The "Identify" Tool"""
201        
202      def Name(self):      def Name(self):
203          return "IdentifyTool"          return "IdentifyTool"
204    
# Line 238  class MapPrintout(wx.wxPrintout): Line 252  class MapPrintout(wx.wxPrintout):
252          renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)          renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)
253          renderer.RenderMap(self.map)          renderer.RenderMap(self.map)
254          return wx.true          return wx.true
           
255    
256  class MapCanvas(wxWindow):  
257    class MapCanvas(wxWindow, Publisher):
258    
259      """A widget that displays a map and offers some interaction"""      """A widget that displays a map and offers some interaction"""
260    
261      def __init__(self, parent, winid, interactor):      # Some messages that can be subscribed/unsubscribed directly through
262        # the MapCanvas come in fact from other objects. This is a map to
263        # map those messages to the names of the instance variables they
264        # actually come from. This delegation is implemented in the
265        # Subscribe and unsubscribed methods
266        delegated_messages = {LAYER_SELECTED: "selection",
267                              SHAPES_SELECTED: "selection"}
268    
269        # Methods delegated to some instance variables. The delegation is
270        # implemented in the __getattr__ method.
271        delegated_methods = {"SelectLayer": "selection",
272                             "SelectShapes": "selection",
273                             "SelectedLayer": "selection",
274                             "HasSelectedLayer": "selection"}
275    
276        def __init__(self, parent, winid):
277          wxWindow.__init__(self, parent, winid)          wxWindow.__init__(self, parent, winid)
278          self.SetBackgroundColour(wxColour(255, 255, 255))          self.SetBackgroundColour(wxColour(255, 255, 255))
279    
280            # the map displayed in this canvas. Set with SetMap()
281          self.map = None          self.map = None
282    
283            # scale and offset describe the transformation from projected
284            # coordinates to window coordinates.
285          self.scale = 1.0          self.scale = 1.0
286          self.offset = (0, 0)          self.offset = (0, 0)
287    
288            # whether the user is currently dragging the mouse, i.e. moving
289            # the mouse while pressing a mouse button
290          self.dragging = 0          self.dragging = 0
291    
292            # the currently active tool
293          self.tool = None          self.tool = None
294          self.redraw_on_idle = 0  
295            # The current mouse position of the last OnMotion event or None
296            # if the mouse is outside the window.
297            self.current_position = None
298    
299            # the bitmap serving as backing store
300            self.bitmap = None
301    
302            # the selection
303            self.selection = Selection()
304            self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
305    
306            # keep track of which layers/shapes are selected to make sure we
307            # only redraw when necessary
308            self.last_selected_layer = None
309            self.last_selected_shape = None
310    
311            # subscribe the WX events we're interested in
312          EVT_PAINT(self, self.OnPaint)          EVT_PAINT(self, self.OnPaint)
313          EVT_LEFT_DOWN(self, self.OnLeftDown)          EVT_LEFT_DOWN(self, self.OnLeftDown)
314          EVT_LEFT_UP(self, self.OnLeftUp)          EVT_LEFT_UP(self, self.OnLeftUp)
315          EVT_MOTION(self, self.OnMotion)          EVT_MOTION(self, self.OnMotion)
316          wx.EVT_IDLE(self, self.OnIdle)          EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
317          self.interactor = interactor          wx.EVT_SIZE(self, self.OnSize)
318          self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)  
319        def __del__(self):
320            wxWindow.__del__(self)
321            Publisher.__del__(self)
322    
323        def Subscribe(self, channel, *args):
324            """Extend the inherited method to handle delegated messages.
325    
326            If channel is one of the delegated messages call the appropriate
327            object's Subscribe method. Otherwise just call the inherited
328            method.
329            """
330            if channel in self.delegated_messages:
331                object = getattr(self, self.delegated_messages[channel])
332                object.Subscribe(channel, *args)
333            else:
334                Publisher.Subscribe(self, channel, *args)
335    
336        def Unsubscribe(self, channel, *args):
337            """Extend the inherited method to handle delegated messages.
338    
339            If channel is one of the delegated messages call the appropriate
340            object's Unsubscribe method. Otherwise just call the inherited
341            method.
342            """
343            if channel in self.delegated_messages:
344                object = getattr(self, self.delegated_messages[channel])
345                object.Unsubscribe(channel, *args)
346            else:
347                Publisher.Unsubscribe(self, channel, *args)
348    
349        def __getattr__(self, attr):
350            if attr in self.delegated_methods:
351                return getattr(getattr(self, self.delegated_methods[attr]), attr)
352            raise AttributeError(attr)
353    
354      def OnPaint(self, event):      def OnPaint(self, event):
355          dc = wxPaintDC(self)          dc = wxPaintDC(self)
356          if self.map is not None and self.map.HasLayers():          if self.map is not None and self.map.HasLayers():
357              # We have a non-empty map. Redraw it in idle time              self.do_redraw()
             self.redraw_on_idle = 1  
358          else:          else:
359              # If we've got no map or if the map is empty, simply clear              # If we've got no map or if the map is empty, simply clear
360              # the screen.              # the screen.
361                
362              # XXX it's probably possible to get rid of this. The              # XXX it's probably possible to get rid of this. The
363              # background color of the window is already white and the              # background color of the window is already white and the
364              # only thing we may have to do is to call self.Refresh()              # only thing we may have to do is to call self.Refresh()
365              # with a true argument in the right places.              # with a true argument in the right places.
366              dc.BeginDrawing()              dc.BeginDrawing()
367              dc.Clear()                          dc.Clear()
368              dc.EndDrawing()              dc.EndDrawing()
369    
370      def do_redraw(self):      def do_redraw(self):
371          # This should only be called if we have a non-empty map. We draw          # This should only be called if we have a non-empty map.
         # it into a memory DC and then blit it to the screen.  
         width, height = self.GetSizeTuple()  
         bitmap = wx.wxEmptyBitmap(width, height)  
         dc = wx.wxMemoryDC()  
         dc.SelectObject(bitmap)  
         dc.BeginDrawing()  
372    
373          # clear the background          # Get the window size.
374          dc.SetBrush(wx.wxWHITE_BRUSH)          width, height = self.GetSizeTuple()
         dc.SetPen(wx.wxTRANSPARENT_PEN)  
         dc.DrawRectangle(0, 0, width, height)  
375    
376          if 1: #self.interactor.selected_map is self.map:          # If self.bitmap's still there, reuse it. Otherwise redraw it
377              selected_layer = self.interactor.selected_layer          if self.bitmap is not None:
378              selected_shape = self.interactor.selected_shape              bitmap = self.bitmap
379          else:          else:
380              selected_layer = None              bitmap = wx.wxEmptyBitmap(width, height)
381              selected_shape = None              dc = wx.wxMemoryDC()
382                dc.SelectObject(bitmap)
383                dc.BeginDrawing()
384    
385          # draw the map into the bitmap              # clear the background
386          renderer = ScreenRenderer(dc, self.scale, self.offset)              dc.SetBrush(wx.wxWHITE_BRUSH)
387          renderer.RenderMap(self.map, selected_layer, selected_shape)              dc.SetPen(wx.wxTRANSPARENT_PEN)
388                dc.DrawRectangle(0, 0, width, height)
389    
390                selected_layer = self.selection.SelectedLayer()
391                selected_shapes = self.selection.SelectedShapes()
392    
393                # draw the map into the bitmap
394                renderer = ScreenRenderer(dc, self.scale, self.offset)
395    
396                # Pass the entire bitmap as update region to the renderer.
397                # We're redrawing the whole bitmap, after all.
398                renderer.RenderMap(self.map, (0, 0, width, height),
399                                   selected_layer, selected_shapes)
400    
401          dc.EndDrawing()              dc.EndDrawing()
402                dc.SelectObject(wx.wxNullBitmap)
403                self.bitmap = bitmap
404    
405          # blit the bitmap to the screen          # blit the bitmap to the screen
406            dc = wx.wxMemoryDC()
407            dc.SelectObject(bitmap)
408          clientdc = wxClientDC(self)          clientdc = wxClientDC(self)
409          clientdc.BeginDrawing()          clientdc.BeginDrawing()
410          clientdc.Blit(0, 0, width, height, dc, 0, 0)          clientdc.Blit(0, 0, width, height, dc, 0, 0)
# Line 316  class MapCanvas(wxWindow): Line 415  class MapCanvas(wxWindow):
415          printout = MapPrintout(self.map)          printout = MapPrintout(self.map)
416          printer.Print(self, printout, wx.true)          printer.Print(self, printout, wx.true)
417          printout.Destroy()          printout.Destroy()
418            
419      def SetMap(self, map):      def SetMap(self, map):
420          redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,          redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
421                             LAYER_VISIBILITY_CHANGED)                             LAYER_VISIBILITY_CHANGED)
422          if self.map is not None:          if self.map is not None:
423              for channel in redraw_channels:              for channel in redraw_channels:
424                  self.map.Unsubscribe(channel, self.redraw)                  self.map.Unsubscribe(channel, self.full_redraw)
425              self.map.Unsubscribe(MAP_PROJECTION_CHANGED,              self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
426                                   self.projection_changed)                                   self.projection_changed)
427          self.map = map          self.map = map
428            self.selection.ClearSelection()
429          if self.map is not None:          if self.map is not None:
430              for channel in redraw_channels:              for channel in redraw_channels:
431                  self.map.Subscribe(channel, self.redraw)                  self.map.Subscribe(channel, self.full_redraw)
432              self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)              self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
433          self.FitMapToWindow()          self.FitMapToWindow()
434          # force a redraw. If map is not empty, it's already been called          # force a redraw. If map is not empty, it's already been called
435          # by FitMapToWindow but if map is empty it hasn't been called          # by FitMapToWindow but if map is empty it hasn't been called
436          # yet so we have to explicitly call it.          # yet so we have to explicitly call it.
437          self.redraw()          self.full_redraw()
438    
439      def Map(self):      def Map(self):
440            """Return the map displayed by this canvas"""
441          return self.map          return self.map
442    
443      def redraw(self, *args):      def redraw(self, *args):
444          self.Refresh(0)          self.Refresh(0)
445    
446        def full_redraw(self, *args):
447            self.bitmap = None
448            self.redraw()
449    
450      def projection_changed(self, *args):      def projection_changed(self, *args):
451          self.FitMapToWindow()          self.FitMapToWindow()
452          self.redraw()          self.full_redraw()
453    
454      def set_view_transform(self, scale, offset):      def set_view_transform(self, scale, offset):
455          self.scale = scale          self.scale = scale
456          self.offset = offset          self.offset = offset
457          self.redraw()          self.full_redraw()
458    
459      def proj_to_win(self, x, y):      def proj_to_win(self, x, y):
460          """\          """\
# Line 366  class MapCanvas(wxWindow): Line 471  class MapCanvas(wxWindow):
471          return ((x - offx) / self.scale, (offy - y) / self.scale)          return ((x - offx) / self.scale, (offy - y) / self.scale)
472    
473      def FitRectToWindow(self, rect):      def FitRectToWindow(self, rect):
474            """Fit the rectangular region given by rect into the window.
475    
476            Set scale so that rect (in projected coordinates) just fits into
477            the window and center it.
478            """
479          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
480          llx, lly, urx, ury = rect          llx, lly, urx, ury = rect
481          if llx == urx or lly == ury:          if llx == urx or lly == ury:
# Line 379  class MapCanvas(wxWindow): Line 489  class MapCanvas(wxWindow):
489          self.set_view_transform(scale, (offx, offy))          self.set_view_transform(scale, (offx, offy))
490    
491      def FitMapToWindow(self):      def FitMapToWindow(self):
492          """\          """Fit the map to the window
493          Set the scale and offset so that the map is centered in the  
494          window          Set the scale so that the map fits exactly into the window and
495            center it in the window.
496          """          """
497          bbox = self.map.ProjectedBoundingBox()          bbox = self.map.ProjectedBoundingBox()
498          if bbox is not None:          if bbox is not None:
# Line 407  class MapCanvas(wxWindow): Line 518  class MapCanvas(wxWindow):
518          self.set_view_transform(scale, offset)          self.set_view_transform(scale, offset)
519    
520      def ZoomOutToRect(self, rect):      def ZoomOutToRect(self, rect):
521          # rect is given in window coordinates          """Zoom out to fit the currently visible region into rect.
522    
523            The rect parameter is given in window coordinates
524            """
525          # determine the bbox of the displayed region in projected          # determine the bbox of the displayed region in projected
526          # coordinates          # coordinates
527          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
# Line 425  class MapCanvas(wxWindow): Line 538  class MapCanvas(wxWindow):
538          self.set_view_transform(scale, (offx, offy))          self.set_view_transform(scale, (offx, offy))
539    
540      def Translate(self, dx, dy):      def Translate(self, dx, dy):
541            """Move the map by dx, dy pixels"""
542          offx, offy = self.offset          offx, offy = self.offset
543          self.set_view_transform(self.scale, (offx + dx, offy + dy))          self.set_view_transform(self.scale, (offx + dx, offy + dy))
544    
545        def SelectTool(self, tool):
546            """Make tool the active tool.
547    
548            The parameter should be an instance of Tool or None to indicate
549            that no tool is active.
550            """
551            self.tool = tool
552    
553      def ZoomInTool(self):      def ZoomInTool(self):
554          self.tool = ZoomInTool(self)          """Start the zoom in tool"""
555            self.SelectTool(ZoomInTool(self))
556    
557      def ZoomOutTool(self):      def ZoomOutTool(self):
558          self.tool = ZoomOutTool(self)          """Start the zoom out tool"""
559            self.SelectTool(ZoomOutTool(self))
560    
561      def PanTool(self):      def PanTool(self):
562          self.tool = PanTool(self)          """Start the pan tool"""
563            self.SelectTool(PanTool(self))
564    
565      def IdentifyTool(self):      def IdentifyTool(self):
566          self.tool = IdentifyTool(self)          """Start the identify tool"""
567            self.SelectTool(IdentifyTool(self))
568    
569      def LabelTool(self):      def LabelTool(self):
570          self.tool = LabelTool(self)          """Start the label tool"""
571            self.SelectTool(LabelTool(self))
572    
573      def CurrentTool(self):      def CurrentTool(self):
574            """Return the name of the current tool or None if no tool is active"""
575          return self.tool and self.tool.Name() or None          return self.tool and self.tool.Name() or None
576    
577        def CurrentPosition(self):
578            """Return current position of the mouse in projected coordinates.
579    
580            The result is a 2-tuple of floats with the coordinates. If the
581            mouse is not in the window, the result is None.
582            """
583            if self.current_position is not None:
584                x, y = self.current_position
585                return self.win_to_proj(x, y)
586            else:
587                return None
588    
589        def set_current_position(self, event):
590            """Set the current position from event
591    
592            Should be called by all events that contain mouse positions
593            especially EVT_MOTION. The event paramete may be None to
594            indicate the the pointer left the window.
595            """
596            if event is not None:
597                self.current_position = (event.m_x, event.m_y)
598            else:
599                self.current_position = None
600            self.issue(VIEW_POSITION)
601    
602      def OnLeftDown(self, event):      def OnLeftDown(self, event):
603            self.set_current_position(event)
604          if self.tool is not None:          if self.tool is not None:
605              self.drag_dc = wxClientDC(self)              self.drag_dc = wxClientDC(self)
606              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wxINVERT)
# Line 455  class MapCanvas(wxWindow): Line 609  class MapCanvas(wxWindow):
609              self.tool.MouseDown(event)              self.tool.MouseDown(event)
610              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
611              self.dragging = 1              self.dragging = 1
612            
613      def OnLeftUp(self, event):      def OnLeftUp(self, event):
614          self.ReleaseMouse()          self.set_current_position(event)
615          if self.dragging:          if self.dragging:
616              self.tool.Hide(self.drag_dc)              self.ReleaseMouse()
617              self.tool.MouseUp(event)              try:
618              self.drag_dc = None                  self.tool.Hide(self.drag_dc)
619          self.dragging = 0                  self.tool.MouseUp(event)
620                finally:
621                    self.drag_dc = None
622                    self.dragging = 0
623    
624      def OnMotion(self, event):      def OnMotion(self, event):
625            self.set_current_position(event)
626          if self.dragging:          if self.dragging:
627              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
628              self.tool.MouseMove(event)              self.tool.MouseMove(event)
629              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
630    
631      def OnIdle(self, event):      def OnLeaveWindow(self, event):
632          if self.redraw_on_idle:          self.set_current_position(None)
633              self.do_redraw()  
634          self.redraw_on_idle = 0      def OnSize(self, event):
635            # the window's size has changed. We have to get a new bitmap. If
636            # we want to be clever we could try to get by without throwing
637            # everything away. E.g. when the window gets smaller, we could
638            # either keep the bitmap or create the new one from the old one.
639            # Even when the window becomes larger some parts of the bitmap
640            # could be reused.
641            self.full_redraw()
642    
643      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
644          self.redraw()          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
645            # The selection object takes care that it only issues
646            # SHAPES_SELECTED messages when the set of selected shapes has
647            # actually changed, so we can do a full redraw unconditionally.
648            # FIXME: We should perhaps try to limit the redraw to the are
649            # actually covered by the shapes before and after the selection
650            # change.
651            self.full_redraw()
652    
653        def unprojected_rect_around_point(self, x, y, dist):
654            """return a rect dist pixels around (x, y) in unprojected corrdinates
655    
656      def find_shape_at(self, px, py, select_labels = 0, selected_layer = 1):          The return value is a tuple (minx, miny, maxx, maxy) suitable a
657            parameter to a layer's ShapesInRegion method.
658            """
659            map_proj = self.map.projection
660            if map_proj is not None:
661                inverse = map_proj.Inverse
662            else:
663                inverse = None
664    
665            xs = []
666            ys = []
667            for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
668                px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
669                if inverse:
670                    px, py = inverse(px, py)
671                xs.append(px)
672                ys.append(py)
673            return (min(xs), min(ys), max(xs), max(ys))
674    
675        def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
676          """Determine the shape at point px, py in window coords          """Determine the shape at point px, py in window coords
677    
678          Return the shape and the corresponding layer as a tuple (layer,          Return the shape and the corresponding layer as a tuple (layer,
# Line 488  class MapCanvas(wxWindow): Line 682  class MapCanvas(wxWindow):
682          search through the labels. If a label is found return it's index          search through the labels. If a label is found return it's index
683          as the shape and None as the layer.          as the shape and None as the layer.
684    
685          If the optional parameter selected_layer is true (default), only          If the optional parameter searched_layer is given (or not None
686          search in the currently selected layer.          which it defaults to), only search in that layer.
687          """          """
688          map_proj = self.map.projection          map_proj = self.map.projection
689          if map_proj is not None:          if map_proj is not None:
# Line 502  class MapCanvas(wxWindow): Line 696  class MapCanvas(wxWindow):
696    
697          if select_labels:          if select_labels:
698              labels = self.map.LabelLayer().Labels()              labels = self.map.LabelLayer().Labels()
699                
700              if labels:              if labels:
701                  dc = wxClientDC(self)                  dc = wxClientDC(self)
702                  font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)                  font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
703                  dc.SetFont(font)                  dc.SetFont(font)
704                  for i in range(len(labels)):                  for i in range(len(labels) - 1, -1, -1):
705                      label = labels[i]                      label = labels[i]
706                      x = label.x                      x = label.x
707                      y = label.y                      y = label.y
# Line 534  class MapCanvas(wxWindow): Line 728  class MapCanvas(wxWindow):
728                      if x <= px < x + width and y <= py <= y + height:                      if x <= px < x + width and y <= py <= y + height:
729                          return None, i                          return None, i
730    
731          if selected_layer:          if searched_layer:
732              layer = self.interactor.SelectedLayer()              layers = [searched_layer]
             if layer is not None:  
                 layers = [layer]  
             else:  
                 # no layer selected. Use an empty list to effectively  
                 # ignore all layers.  
                 layers = []  
733          else:          else:
734              layers = self.map.Layers()              layers = self.map.Layers()
735    
# Line 552  class MapCanvas(wxWindow): Line 740  class MapCanvas(wxWindow):
740              if not layer.Visible():              if not layer.Visible():
741                  continue                  continue
742    
743              filled = layer.fill is not None              filled = layer.GetClassification().GetDefaultFill() \
744              stroked = layer.stroke is not None                       is not Color.Transparent
745                                stroked = layer.GetClassification().GetDefaultLineColor() \
746                          is not Color.Transparent
747    
748              layer_proj = layer.projection              layer_proj = layer.projection
749              if layer_proj is not None:              if layer_proj is not None:
750                  inverse = layer_proj.Inverse                  inverse = layer_proj.Inverse
751              else:              else:
752                  inverse = None                  inverse = None
753                    
754              shapetype = layer.ShapeType()              shapetype = layer.ShapeType()
755    
756              select_shape = -1              select_shape = -1
757    
758                # Determine the ids of the shapes that overlap a tiny area
759                # around the point. For layers containing points we have to
760                # choose a larger size of the box we're testing agains so
761                # that we take the size of the markers into account
762                # FIXME: Once the markers are more flexible this part has to
763                # become more flexible too, of course
764                if shapetype == SHAPETYPE_POINT:
765                    box = self.unprojected_rect_around_point(px, py, 5)
766                else:
767                    box = self.unprojected_rect_around_point(px, py, 1)
768                shape_ids = layer.ShapesInRegion(box)
769                shape_ids.reverse()
770    
771              if shapetype == SHAPETYPE_POLYGON:              if shapetype == SHAPETYPE_POLYGON:
772                  for i in range(layer.NumShapes()):                  for i in shape_ids:
773                      result = point_in_polygon_shape(layer.shapefile.cobject(),                      result = point_in_polygon_shape(layer.shapefile.cobject(),
774                                                      i,                                                      i,
775                                                      filled, stroked,                                                      filled, stroked,
# Line 576  class MapCanvas(wxWindow): Line 780  class MapCanvas(wxWindow):
780                          select_shape = i                          select_shape = i
781                          break                          break
782              elif shapetype == SHAPETYPE_ARC:              elif shapetype == SHAPETYPE_ARC:
783                  for i in range(layer.NumShapes()):                  for i in shape_ids:
784                      result = point_in_polygon_shape(layer.shapefile.cobject(),                      result = point_in_polygon_shape(layer.shapefile.cobject(),
785                                                      i, 0, 1,                                                      i, 0, 1,
786                                                      map_proj, layer_proj,                                                      map_proj, layer_proj,
# Line 586  class MapCanvas(wxWindow): Line 790  class MapCanvas(wxWindow):
790                          select_shape = i                          select_shape = i
791                          break                          break
792              elif shapetype == SHAPETYPE_POINT:              elif shapetype == SHAPETYPE_POINT:
793                  for i in range(layer.NumShapes()):                  for i in shape_ids:
794                      shape = layer.Shape(i)                      shape = layer.Shape(i)
795                      x, y = shape.Points()[0]                      x, y = shape.Points()[0]
796                      if inverse:                      if inverse:
# Line 603  class MapCanvas(wxWindow): Line 807  class MapCanvas(wxWindow):
807                  return layer, select_shape                  return layer, select_shape
808          return None, None          return None, None
809    
810      def SelectShapeAt(self, x, y):      def SelectShapeAt(self, x, y, layer = None):
811          layer, shape = self.find_shape_at(x, y)          """\
812            Select and return the shape and its layer at window position (x, y)
813    
814            If layer is given, only search in that layer. If no layer is
815            given, search through all layers.
816    
817            Return a tuple (layer, shapeid). If no shape is found, return
818            (None, None).
819            """
820            layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
821          # If layer is None, then shape will also be None. We don't want          # If layer is None, then shape will also be None. We don't want
822          # to deselect the currently selected layer, so we simply select          # to deselect the currently selected layer, so we simply select
823          # the already selected layer again.          # the already selected layer again.
824          if layer is None:          if layer is None:
825              layer = self.interactor.SelectedLayer()              layer = self.selection.SelectedLayer()
826          self.interactor.SelectLayerAndShape(layer, shape)              shapes = []
827            else:
828                shapes = [shape]
829            self.selection.SelectShapes(layer, shapes)
830            return result
831    
832      def LabelShapeAt(self, x, y):      def LabelShapeAt(self, x, y):
833            """Add or remove a label at window position x, y.
834    
835            If there's a label at the given position, remove it. Otherwise
836            determine the shape at the position, run the label dialog and
837            unless the user cancels the dialog, add a laber.
838            """
839          ox = x; oy = y          ox = x; oy = y
840          label_layer = self.map.LabelLayer()          label_layer = self.map.LabelLayer()
841          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)

Legend:
Removed from v.57  
changed lines
  Added in v.610

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26