/[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 45 by bh, Fri Sep 7 15:00:21 2001 UTC revision 535 by bh, Fri Mar 14 20:42:18 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 29  from Thuban.Model.layer import SHAPETYPE Line 29  from Thuban.Model.layer import SHAPETYPE
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 127  class ZoomInTool(RectTool): Line 129  class ZoomInTool(RectTool):
129      def MouseUp(self, event):      def MouseUp(self, event):
130          if self.dragging:          if self.dragging:
131              Tool.MouseUp(self, event)              Tool.MouseUp(self, event)
132              self.view.FitRectToWindow(self.proj_rect())              sx, sy = self.start
133                cx, cy = self.current
134                if sx == cx or sy == cy:
135                    # 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))
141                else:
142                    # A drag. Zoom in to the rectangle
143                    self.view.FitRectToWindow(self.proj_rect())
144    
145    
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 142  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              self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),              if sx == cx or sy == cy:
159                                       max(sx, cx), max(sy, cy)))                  # Just a mouse click or a degenerate rectangle. Simply
160                    # 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:
166                    # A drag. Zoom out to the rectangle
167                    self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
168                                             max(sx, cx), max(sy, cy)))
169    
170    
171  class PanTool(Tool):  class PanTool(Tool):
# Line 155  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 168  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 226  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 None or not self.map.HasLayers():          if self.map is not None and self.map.HasLayers():
357              return              self.do_redraw()
358          self.redraw_on_idle = 1          else:
359                # If we've got no map or if the map is empty, simply clear
360                # the screen.
361    
362                # XXX it's probably possible to get rid of this. The
363                # background color of the window is already white and the
364                # only thing we may have to do is to call self.Refresh()
365                # with a true argument in the right places.
366                dc.BeginDrawing()
367                dc.Clear()
368                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.
372    
373            # Get the window size.
374          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
         bitmap = wx.wxEmptyBitmap(width, height)  
375    
376            # If self.bitmap's still there, reuse it. Otherwise redraw it
377            if self.bitmap is not None:
378                bitmap = self.bitmap
379            else:
380                bitmap = wx.wxEmptyBitmap(width, height)
381                dc = wx.wxMemoryDC()
382                dc.SelectObject(bitmap)
383                dc.BeginDrawing()
384    
385                # clear the background
386                dc.SetBrush(wx.wxWHITE_BRUSH)
387                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()
402                dc.SelectObject(wx.wxNullBitmap)
403                self.bitmap = bitmap
404    
405            # blit the bitmap to the screen
406          dc = wx.wxMemoryDC()          dc = wx.wxMemoryDC()
407          dc.SelectObject(bitmap)          dc.SelectObject(bitmap)
   
         dc.BeginDrawing()  
   
         dc.SetBrush(wx.wxWHITE_BRUSH)  
         dc.SetPen(wx.wxTRANSPARENT_PEN)  
         dc.DrawRectangle(0, 0, width, height)  
   
         if 1: #self.interactor.selected_map is self.map:  
             selected_layer = self.interactor.selected_layer  
             selected_shape = self.interactor.selected_shape  
         else:  
             selected_layer = None  
             selected_shape = None  
               
         renderer = ScreenRenderer(dc, self.scale, self.offset)  
         renderer.RenderMap(self.map, selected_layer, selected_shape)  
   
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)
411          clientdc.EndDrawing()          clientdc.EndDrawing()
412    
   
413      def Print(self):      def Print(self):
414          printer = wx.wxPrinter()          printer = wx.wxPrinter()
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 = (LAYERS_CHANGED, LAYER_LEGEND_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
435            # by FitMapToWindow but if map is empty it hasn't been called
436            # yet so we have to explicitly call it.
437            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 335  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 348  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:
499              self.FitRectToWindow(bbox)              self.FitRectToWindow(bbox)
500    
501      def ZoomFactor(self, factor):      def ZoomFactor(self, factor, center = None):
502            """Multiply the zoom by factor and center on center.
503    
504            The optional parameter center is a point in window coordinates
505            that should be centered. If it is omitted, it defaults to the
506            center of the window
507            """
508          width, height = self.GetSizeTuple()          width, height = self.GetSizeTuple()
509          scale = self.scale * factor          scale = self.scale * factor
510          offx, offy = self.offset          offx, offy = self.offset
511          offset = (factor * (offx - width / 2) + width / 2,          if center is not None:
512                    factor * (offy - height / 2) + height / 2)              cx, cy = center
513            else:
514                cx = width / 2
515                cy = height / 2
516            offset = (factor * (offx - cx) + width / 2,
517                      factor * (offy - cy) + height / 2)
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 383  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 413  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 446  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 460  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 492  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 510  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.None
745                                stroked = layer.GetClassification().GetDefaultLineColor() \
746                          is not Color.None
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 534  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 544  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 561  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.45  
changed lines
  Added in v.535

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26