/[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 1384 by jonathan, Tue Jul 1 16:11:26 2003 UTC revision 1385 by jonathan, Thu Jul 10 14:52:39 2003 UTC
# Line 22  from math import hypot Line 22  from math import hypot
22  from wxPython.wx import wxWindow, \  from wxPython.wx import wxWindow, \
23       wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\       wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
24       EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \       EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
25       wxBITMAP_TYPE_XPM, wxCursor, wxImageFromBitmap, wxPlatform, \       wxBITMAP_TYPE_XPM, wxCursor, wxPlatform, \
26       wxBeginBusyCursor, wxEndBusyCursor       wxBeginBusyCursor, wxEndBusyCursor, wxFileDialog, wxSAVE, \
27         wxOVERWRITE_PROMPT, wxID_OK
28    
29  # Export related stuff  # Export related stuff
30  if wxPlatform == '__WXMSW__':  if wxPlatform == '__WXMSW__':
31      from wxPython.wx import wxMetaFileDC      from wxPython.wx import wxMetaFileDC
 from wxPython.wx import wxFileDialog, wxSAVE, wxOVERWRITE_PROMPT, wxID_OK  
32    
33  from wxPython import wx  from wxPython import wx
34    
35  from wxproj import point_in_polygon_shape, shape_centroid  from wxproj import point_in_polygon_shape, shape_centroid
36    
37  from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \  from Thuban.Model.messages import \
38       LAYER_PROJECTION_CHANGED, \       MAP_PROJECTION_CHANGED, MAP_LAYERS_CHANGED, \
39       MAP_LAYERS_CHANGED, LAYER_CHANGED, LAYER_VISIBILITY_CHANGED       LAYER_PROJECTION_CHANGED, LAYER_CHANGED, LAYER_VISIBILITY_CHANGED
 from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \  
      SHAPETYPE_POINT  
 from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \  
      ALIGN_LEFT, ALIGN_RIGHT  
 from Thuban.Lib.connector import Publisher  
 from Thuban.Model.color import Color, Transparent  
   
 import resource  
40    
 from selection import Selection  
41  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer  from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
42    
43  import labeldialog  import labeldialog
44    
45  from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \  from viewport import ViewPort, PanTool
                      SCALE_CHANGED  
   
 #from common import ThubanBeginBusyCursor, ThubanEndBusyCursor  
   
 #  
 #   The tools  
 #  
   
 class Tool:  
   
     """  
     Base class for the interactive tools  
     """  
   
     def __init__(self, view):  
         """Intitialize the tool. The view is the canvas displaying the map"""  
         self.view = view  
         self.start = self.current = None  
         self.dragging = 0  
         self.drawn = 0  
   
     def Name(self):  
         """Return the tool's name"""  
         return ''  
   
     def drag_start(self, x, y):  
         self.start = self.current = x, y  
         self.dragging = 1  
   
     def drag_move(self, x, y):  
         self.current = x, y  
   
     def drag_stop(self, x, y):  
         self.current = x, y  
         self.dragging = 0  
   
     def Show(self, dc):  
         if not self.drawn:  
             self.draw(dc)  
         self.drawn = 1  
   
     def Hide(self, dc):  
         if self.drawn:  
             self.draw(dc)  
         self.drawn = 0  
   
     def draw(self, dc):  
         pass  
   
     def MouseDown(self, event):  
         self.drag_start(event.m_x, event.m_y)  
   
     def MouseMove(self, event):  
         if self.dragging:  
             self.drag_move(event.m_x, event.m_y)  
   
     def MouseUp(self, event):  
         if self.dragging:  
             self.drag_move(event.m_x, event.m_y)  
   
     def Cancel(self):  
         self.dragging = 0  
   
   
 class RectTool(Tool):  
   
     """Base class for tools that draw rectangles while dragging"""  
   
     def draw(self, dc):  
         sx, sy = self.start  
         cx, cy = self.current  
         dc.DrawRectangle(sx, sy, cx - sx, cy - sy)  
46    
47  class ZoomInTool(RectTool):  class CanvasPanTool(PanTool):
48    
49      """The Zoom-In Tool"""      """The Canvas Pan Tool"""
   
     def Name(self):  
         return "ZoomInTool"  
   
     def proj_rect(self):  
         """return the rectangle given by start and current in projected  
         coordinates"""  
         sx, sy = self.start  
         cx, cy = self.current  
         left, top = self.view.win_to_proj(sx, sy)  
         right, bottom = self.view.win_to_proj(cx, cy)  
         return (min(left, right), min(top, bottom),  
                 max(left, right), max(top, bottom))  
   
     def MouseUp(self, event):  
         if self.dragging:  
             Tool.MouseUp(self, event)  
             sx, sy = self.start  
             cx, cy = self.current  
             if sx == cx or sy == cy:  
                 # Just a mouse click or a degenerate rectangle. Simply  
                 # zoom in by a factor of two  
                 # FIXME: For a click this is the desired behavior but should we  
                 # really do this for degenrate rectagles as well or  
                 # should we ignore them?  
                 self.view.ZoomFactor(2, center = (cx, cy))  
             else:  
                 # A drag. Zoom in to the rectangle  
                 self.view.FitRectToWindow(self.proj_rect())  
   
   
 class ZoomOutTool(RectTool):  
   
     """The Zoom-Out Tool"""  
   
     def Name(self):  
         return "ZoomOutTool"  
   
     def MouseUp(self, event):  
         if self.dragging:  
             Tool.MouseUp(self, event)  
             sx, sy = self.start  
             cx, cy = self.current  
             if sx == cx or sy == cy:  
                 # Just a mouse click or a degenerate rectangle. Simply  
                 # zoom out by a factor of two.  
                 # FIXME: For a click this is the desired behavior but should we  
                 # really do this for degenrate rectagles as well or  
                 # should we ignore them?  
                 self.view.ZoomFactor(0.5, center = (cx, cy))  
             else:  
                 # A drag. Zoom out to the rectangle  
                 self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),  
                                          max(sx, cx), max(sy, cy)))  
   
   
 class PanTool(Tool):  
   
     """The Pan Tool"""  
   
     def Name(self):  
         return "PanTool"  
50    
51      def MouseMove(self, event):      def MouseMove(self, event):
52          if self.dragging:          if self.dragging:
53              Tool.MouseMove(self, event)              PanTool.MouseMove(self, event)
54              sx, sy = self.start              sx, sy = self.start
55              x, y = self.current              x, y = self.current
56              width, height = self.view.GetSizeTuple()              width, height = self.view.GetSizeTuple()
# Line 205  class PanTool(Tool): Line 61  class PanTool(Tool):
61              dc = self.view.drag_dc              dc = self.view.drag_dc
62              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)              dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
63    
     def MouseUp(self, event):  
         if self.dragging:  
             Tool.MouseUp(self, event)  
             sx, sy = self.start  
             cx, cy = self.current  
             self.view.Translate(cx - sx, cy - sy)  
   
 class IdentifyTool(Tool):  
   
     """The "Identify" Tool"""  
   
     def Name(self):  
         return "IdentifyTool"  
   
     def MouseUp(self, event):  
         self.view.SelectShapeAt(event.m_x, event.m_y)  
   
   
 class LabelTool(Tool):  
   
     """The "Label" Tool"""  
   
     def Name(self):  
         return "LabelTool"  
   
     def MouseUp(self, event):  
         self.view.LabelShapeAt(event.m_x, event.m_y)  
   
   
64  class MapPrintout(wx.wxPrintout):  class MapPrintout(wx.wxPrintout):
65    
66      """      """
# Line 276  class MapPrintout(wx.wxPrintout): Line 103  class MapPrintout(wx.wxPrintout):
103                             self.selected_layer, self.selected_shapes)                             self.selected_layer, self.selected_shapes)
104          return True          return True
105    
106  class MapCanvas(wxWindow, Publisher):  class MapCanvas(wxWindow, ViewPort):
107    
108      """A widget that displays a map and offers some interaction"""      """A widget that displays a map and offers some interaction"""
109    
     # Some messages that can be subscribed/unsubscribed directly through  
     # the MapCanvas come in fact from other objects. This is a dict  
     # mapping those messages to the names of the instance variables they  
     # actually come from. The delegation is implemented in the Subscribe  
     # and Unsubscribe methods  
     delegated_messages = {LAYER_SELECTED: "selection",  
                           SHAPES_SELECTED: "selection"}  
   
     # Methods delegated to some instance variables. The delegation is  
     # implemented in the __getattr__ method.  
     delegated_methods = {"SelectLayer": "selection",  
                          "SelectShapes": "selection",  
                          "SelectedLayer": "selection",  
                          "HasSelectedLayer": "selection",  
                          "HasSelectedShapes": "selection",  
                          "SelectedShapes": "selection"}  
   
110      def __init__(self, parent, winid):      def __init__(self, parent, winid):
111          wxWindow.__init__(self, parent, winid)          wxWindow.__init__(self, parent, winid)
112          self.SetBackgroundColour(wxColour(255, 255, 255))          ViewPort.__init__(self)
   
         # the map displayed in this canvas. Set with SetMap()  
         self.map = None  
113    
114          # current map projection. should only differ from map.projection          self.SetBackgroundColour(wxColour(255, 255, 255))
         # when the map's projection is changing and we need access to the  
         # old projection.  
         self.current_map_proj = None  
   
         # scale and offset describe the transformation from projected  
         # coordinates to window coordinates.  
         self.scale = 1.0  
         self.offset = (0, 0)  
   
         # whether the user is currently dragging the mouse, i.e. moving  
         # the mouse while pressing a mouse button  
         self.dragging = 0  
   
         # the currently active tool  
         self.tool = None  
   
         # The current mouse position of the last OnMotion event or None  
         # if the mouse is outside the window.  
         self.current_position = None  
115    
116          # the bitmap serving as backing store          # the bitmap serving as backing store
117          self.bitmap = None          self.bitmap = None
118    
         # the selection  
         self.selection = Selection()  
         self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)  
   
         # keep track of which layers/shapes are selected to make sure we  
         # only redraw when necessary  
         self.last_selected_layer = None  
         self.last_selected_shape = None  
   
119          self.backgroundColor = wx.wxWHITE_BRUSH          self.backgroundColor = wx.wxWHITE_BRUSH
120    
121          # subscribe the WX events we're interested in          # subscribe the WX events we're interested in
# Line 350  class MapCanvas(wxWindow, Publisher): Line 129  class MapCanvas(wxWindow, Publisher):
129    
130      def __del__(self):      def __del__(self):
131          wxWindow.__del__(self)          wxWindow.__del__(self)
132          Publisher.__del__(self)          ViewPort.__del__(self)
   
     def Subscribe(self, channel, *args):  
         """Extend the inherited method to handle delegated messages.  
133    
134          If channel is one of the delegated messages call the appropriate      def PanTool(self):
135          object's Subscribe method. Otherwise just call the inherited          """Start the canvas pan tool"""
136          method.          self.SelectTool(CanvasPanTool(self))
137          """          
138          if channel in self.delegated_messages:      def SetMap(self, map):
139              object = getattr(self, self.delegated_messages[channel])          redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
140              object.Subscribe(channel, *args)                             LAYER_VISIBILITY_CHANGED)
141          else:          if self.Map() is not None:
142              Publisher.Subscribe(self, channel, *args)              for channel in redraw_channels:
143                    self.Map().Unsubscribe(channel, self.full_redraw)
144    
145      def Unsubscribe(self, channel, *args):          ViewPort.SetMap(self, map)
         """Extend the inherited method to handle delegated messages.  
146    
147          If channel is one of the delegated messages call the appropriate          if self.Map() is not None:
148          object's Unsubscribe method. Otherwise just call the inherited              for channel in redraw_channels:
149          method.                  self.Map().Subscribe(channel, self.full_redraw)
         """  
         if channel in self.delegated_messages:  
             object = getattr(self, self.delegated_messages[channel])  
             object.Unsubscribe(channel, *args)  
         else:  
             Publisher.Unsubscribe(self, channel, *args)  
150    
151      def __getattr__(self, attr):          # force a redraw. If map is not empty, it's already been called
152          if attr in self.delegated_methods:          # by FitMapToWindow but if map is empty it hasn't been called
153              return getattr(getattr(self, self.delegated_methods[attr]), attr)          # yet so we have to explicitly call it.
154          raise AttributeError(attr)          self.full_redraw()
155    
156      def OnPaint(self, event):      def OnPaint(self, event):
157          dc = wxPaintDC(self)          dc = wxPaintDC(self)
158    
159          if self.map is not None and self.map.HasLayers():          if self.Map() is not None and self.Map().HasLayers():
160              if self.bitmap in (None, -1):              if self.bitmap in (None, -1):
161                  # set the flag that we should redraw the                  # set the flag that we should redraw the
162                  # bitmap in idle time                  # bitmap in idle time
163                  self.bitmap = -1                  self.bitmap = -1
164                  return              else:
165                    # blit the bitmap to the screen
166              # blit the bitmap to the screen                  dc.BeginDrawing()
167              dc.BeginDrawing()                  dc.DrawBitmap(self.bitmap, 0, 0)
168              dc.DrawBitmap(self.bitmap, 0, 0)                  dc.EndDrawing()
             dc.EndDrawing()  
169          else:          else:
170              # 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
171              # the screen.              # the screen.
# Line 436  class MapCanvas(wxWindow, Publisher): Line 205  class MapCanvas(wxWindow, Publisher):
205    
206              # Pass the entire bitmap as update region to the renderer.              # Pass the entire bitmap as update region to the renderer.
207              # We're redrawing the whole bitmap, after all.              # We're redrawing the whole bitmap, after all.
208              renderer.RenderMap(self.map, (0, 0, width, height),              renderer.RenderMap(self.Map(), (0, 0, width, height),
209                                 selected_layer, selected_shapes)                                 selected_layer, selected_shapes)
210    
211              dc.EndDrawing()              dc.EndDrawing()
# Line 451  class MapCanvas(wxWindow, Publisher): Line 220  class MapCanvas(wxWindow, Publisher):
220          self.redraw()          self.redraw()
221    
222      def Export(self):      def Export(self):
         if self.scale == 0:  
             return  
223    
224          if hasattr(self, "export_path"):          if hasattr(self, "export_path"):
225              export_path = self.export_path              export_path = self.export_path
# Line 478  class MapCanvas(wxWindow, Publisher): Line 245  class MapCanvas(wxWindow, Publisher):
245              # Pass the entire bitmap as update region to the renderer.              # Pass the entire bitmap as update region to the renderer.
246              # We're redrawing the whole bitmap, after all.              # We're redrawing the whole bitmap, after all.
247              width, height = self.GetSizeTuple()              width, height = self.GetSizeTuple()
248              renderer.RenderMap(self.map,              renderer.RenderMap(self.Map(),
249                                  (0,0,                                  (0,0,
250                                      (width/self.scale)*scale,                                      (width/self.scale)*scale,
251                                      (height/self.scale)*scale),                                      (height/self.scale)*scale),
# Line 494  class MapCanvas(wxWindow, Publisher): Line 261  class MapCanvas(wxWindow, Publisher):
261          selected_layer = self.selection.SelectedLayer()          selected_layer = self.selection.SelectedLayer()
262          selected_shapes = self.selection.SelectedShapes()          selected_shapes = self.selection.SelectedShapes()
263                    
264          printout = MapPrintout(self, self.map, (0, 0, width, height),          printout = MapPrintout(self, self.Map(), (0, 0, width, height),
265                                 selected_layer, selected_shapes)                                 selected_layer, selected_shapes)
266          printer.Print(self, printout, True)          printer.Print(self, printout, True)
267          printout.Destroy()          printout.Destroy()
268    
     def SetMap(self, map):  
         redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,  
                            LAYER_VISIBILITY_CHANGED)  
         if self.map is not None:  
             for channel in redraw_channels:  
                 self.map.Unsubscribe(channel, self.full_redraw)  
             self.map.Unsubscribe(MAP_PROJECTION_CHANGED,  
                                  self.map_projection_changed)  
             self.map.Unsubscribe(LAYER_PROJECTION_CHANGED,  
                                  self.layer_projection_changed)  
         self.map = map  
         self.current_map_proj = self.map.GetProjection()  
         self.selection.ClearSelection()  
         if self.map is not None:  
             for channel in redraw_channels:  
                 self.map.Subscribe(channel, self.full_redraw)  
             self.map.Subscribe(MAP_PROJECTION_CHANGED, self.map_projection_changed)  
             self.map.Subscribe(LAYER_PROJECTION_CHANGED, self.layer_projection_changed)  
         self.FitMapToWindow()  
         # force a redraw. If map is not empty, it's already been called  
         # by FitMapToWindow but if map is empty it hasn't been called  
         # yet so we have to explicitly call it.  
         self.full_redraw()  
   
     def Map(self):  
         """Return the map displayed by this canvas"""  
         return self.map  
   
269      def redraw(self, *args):      def redraw(self, *args):
270          self.Refresh(False)          self.Refresh(False)
271    
# Line 534  class MapCanvas(wxWindow, Publisher): Line 273  class MapCanvas(wxWindow, Publisher):
273          self.bitmap = None          self.bitmap = None
274          self.redraw()          self.redraw()
275    
276      def map_projection_changed(self, *args):      def map_projection_changed(self, map, old_proj):
277            ViewPort.map_projection_changed(self, map, old_proj)
         proj = self.current_map_proj  
         self.current_map_proj = self.map.GetProjection()  
   
         bbox = None  
   
         if proj is not None and self.current_map_proj is not None:  
             width, height = self.GetSizeTuple()  
             llx, lly = self.win_to_proj(0, height)  
             urx, ury = self.win_to_proj(width, 0)  
             bbox = proj.Inverse(llx, lly) + proj.Inverse(urx, ury)  
             bbox = self.current_map_proj.ForwardBBox(bbox)  
   
         if bbox is not None:  
             self.FitRectToWindow(bbox)  
         else:  
             self.FitMapToWindow()  
   
278          self.full_redraw()          self.full_redraw()
279    
280      def layer_projection_changed(self, *args):      def layer_projection_changed(self, *args):
281            ViewPort.layer_projection_changed(self, args)
282          self.full_redraw()          self.full_redraw()
283    
284      def set_view_transform(self, scale, offset):      def set_view_transform(self, scale, offset):
285          # width/height of the projected bbox          ViewPort.set_view_transform(self, scale, offset)
         llx, lly, urx, ury = bbox = self.map.ProjectedBoundingBox()  
         pwidth = float(urx - llx)  
         pheight = float(ury - lly)  
   
         # width/height of the window  
         wwidth, wheight = self.GetSizeTuple()  
   
         # The window's center in projected coordinates assuming the new  
         # scale/offset  
         pcenterx = (wwidth/2 - offset[0]) / scale  
         pcentery = (offset[1] - wheight/2) / scale  
   
         # The window coordinates used when drawing the shapes must fit  
         # into 16bit signed integers.  
         max_len = max(pwidth, pheight)  
         if max_len:  
             max_scale = 32000.0 / max_len  
         else:  
             # FIXME: What to do in this case? The bbox is effectively  
             # empty so any scale should work.  
             max_scale = scale  
   
         # The minimal scale is somewhat arbitrarily set to half that of  
         # the bbox fit into the window  
         scales = []  
         if pwidth:  
             scales.append(wwidth / pwidth)  
         if pheight:  
             scales.append(wheight / pheight)  
         if scales:  
             min_scale = 0.5 * min(scales)  
         else:  
             min_scale = scale  
   
         if scale > max_scale:  
             scale = max_scale  
         elif scale < min_scale:  
             scale = min_scale  
   
         self.scale = scale  
   
         # determine new offset to preserve the center  
         self.offset = (wwidth/2 - scale * pcenterx,  
                        wheight/2 + scale * pcentery)  
286          self.full_redraw()          self.full_redraw()
         self.issue(SCALE_CHANGED, scale)  
   
     def proj_to_win(self, x, y):  
         """\  
         Return the point in  window coords given by projected coordinates x y  
         """  
         if self.scale == 0:  
             return (0, 0)  
   
         offx, offy = self.offset  
         return (self.scale * x + offx, -self.scale * y + offy)  
   
     def win_to_proj(self, x, y):  
         """\  
         Return the point in projected coordinates given by window coords x y  
         """  
         if self.scale == 0:  
             return (0, 0)  
   
         offx, offy = self.offset  
         return ((x - offx) / self.scale, (offy - y) / self.scale)  
   
     def FitRectToWindow(self, rect):  
         """Fit the rectangular region given by rect into the window.  
   
         Set scale so that rect (in projected coordinates) just fits into  
         the window and center it.  
         """  
         width, height = self.GetSizeTuple()  
         llx, lly, urx, ury = rect  
         if llx == urx or lly == ury:  
             # zero width or zero height. Do Nothing  
             return  
         scalex = width / (urx - llx)  
         scaley = height / (ury - lly)  
         scale = min(scalex, scaley)  
         offx = 0.5 * (width - (urx + llx) * scale)  
         offy = 0.5 * (height + (ury + lly) * scale)  
         self.set_view_transform(scale, (offx, offy))  
   
     def FitMapToWindow(self):  
         """Fit the map to the window  
   
         Set the scale so that the map fits exactly into the window and  
         center it in the window.  
         """  
         if self.map is not None:  
             bbox = self.map.ProjectedBoundingBox()  
             if bbox is not None:  
                 self.FitRectToWindow(bbox)  
   
     def FitLayerToWindow(self, layer):  
         """Fit the given layer to the window.  
   
         Set the scale so that the layer fits exactly into the window and  
         center it in the window.  
         """  
           
         bbox = layer.LatLongBoundingBox()  
         if bbox is not None:  
             proj = self.map.GetProjection()  
             if proj is not None:  
                 bbox = proj.ForwardBBox(bbox)  
   
             if bbox is not None:  
                 self.FitRectToWindow(bbox)  
   
     def FitSelectedToWindow(self):  
         layer = self.selection.SelectedLayer()  
         shapes = self.selection.SelectedShapes()  
   
         bbox = layer.ShapesBoundingBox(shapes)  
         if bbox is not None:  
             proj = self.map.GetProjection()  
             if proj is not None:  
                 bbox = proj.ForwardBBox(bbox)  
   
             if bbox is not None:  
                 if len(shapes) == 1 and layer.ShapeType() == SHAPETYPE_POINT:  
                     self.ZoomFactor(1, self.proj_to_win(bbox[0], bbox[1]))  
                 else:  
                     self.FitRectToWindow(bbox)  
   
     def ZoomFactor(self, factor, center = None):  
         """Multiply the zoom by factor and center on center.  
   
         The optional parameter center is a point in window coordinates  
         that should be centered. If it is omitted, it defaults to the  
         center of the window  
         """  
         if self.scale > 0:  
             width, height = self.GetSizeTuple()  
             scale = self.scale * factor  
             offx, offy = self.offset  
             if center is not None:  
                 cx, cy = center  
             else:  
                 cx = width / 2  
                 cy = height / 2  
             offset = (factor * (offx - cx) + width / 2,  
                     factor * (offy - cy) + height / 2)  
             self.set_view_transform(scale, offset)  
   
     def ZoomOutToRect(self, rect):  
         """Zoom out to fit the currently visible region into rect.  
   
         The rect parameter is given in window coordinates  
         """  
         # determine the bbox of the displayed region in projected  
         # coordinates  
         width, height = self.GetSizeTuple()  
         llx, lly = self.win_to_proj(0, height - 1)  
         urx, ury = self.win_to_proj(width - 1, 0)  
   
         sx, sy, ex, ey = rect  
         scalex = (ex - sx) / (urx - llx)  
         scaley = (ey - sy) / (ury - lly)  
         scale = min(scalex, scaley)  
   
         offx = 0.5 * ((ex + sx) - (urx + llx) * scale)  
         offy = 0.5 * ((ey + sy) + (ury + lly) * scale)  
         self.set_view_transform(scale, (offx, offy))  
   
     def Translate(self, dx, dy):  
         """Move the map by dx, dy pixels"""  
         offx, offy = self.offset  
         self.set_view_transform(self.scale, (offx + dx, offy + dy))  
   
     def SelectTool(self, tool):  
         """Make tool the active tool.  
287    
288          The parameter should be an instance of Tool or None to indicate      def GetPortSizeTuple(self):
289          that no tool is active.          return self.GetSizeTuple()
         """  
         self.tool = tool  
   
     def ZoomInTool(self):  
         """Start the zoom in tool"""  
         self.SelectTool(ZoomInTool(self))  
   
     def ZoomOutTool(self):  
         """Start the zoom out tool"""  
         self.SelectTool(ZoomOutTool(self))  
   
     def PanTool(self):  
         """Start the pan tool"""  
         self.SelectTool(PanTool(self))  
         #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)  
         #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)  
         #print bmp  
         #img = wxImageFromBitmap(bmp)  
         #print img  
         #cur = wxCursor(img)  
         #print cur  
         #self.SetCursor(cur)  
   
     def IdentifyTool(self):  
         """Start the identify tool"""  
         self.SelectTool(IdentifyTool(self))  
   
     def LabelTool(self):  
         """Start the label tool"""  
         self.SelectTool(LabelTool(self))  
   
     def CurrentTool(self):  
         """Return the name of the current tool or None if no tool is active"""  
         return self.tool and self.tool.Name() or None  
   
     def CurrentPosition(self):  
         """Return current position of the mouse in projected coordinates.  
   
         The result is a 2-tuple of floats with the coordinates. If the  
         mouse is not in the window, the result is None.  
         """  
         if self.current_position is not None:  
             x, y = self.current_position  
             return self.win_to_proj(x, y)  
         else:  
             return None  
   
     def set_current_position(self, event):  
         """Set the current position from event  
   
         Should be called by all events that contain mouse positions  
         especially EVT_MOTION. The event paramete may be None to  
         indicate the the pointer left the window.  
         """  
         if event is not None:  
             self.current_position = (event.m_x, event.m_y)  
         else:  
             self.current_position = None  
         self.issue(VIEW_POSITION)  
290    
291      def OnLeftDown(self, event):      def OnLeftDown(self, event):
292          self.set_current_position(event)          self.MouseLeftDown(event)
293          if self.tool is not None:          if self.tool is not None:
294              self.drag_dc = wxClientDC(self)              self.drag_dc = wxClientDC(self)
295              self.drag_dc.SetLogicalFunction(wxINVERT)              self.drag_dc.SetLogicalFunction(wxINVERT)
296              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)              self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
             self.CaptureMouse()  
             self.tool.MouseDown(event)  
297              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
298              self.dragging = 1              self.dragging = 1
299    
300      def OnLeftUp(self, event):      def OnLeftUp(self, event):
301          self.set_current_position(event)          self.MouseLeftUp(event)
302          if self.dragging:          if self.dragging:
303              self.ReleaseMouse()              self.ReleaseMouse()
304              try:              try:
305                  self.tool.Hide(self.drag_dc)                  self.tool.Hide(self.drag_dc)
                 self.tool.MouseUp(event)  
306              finally:              finally:
307                  self.drag_dc = None                  self.drag_dc = None
308                  self.dragging = 0                  self.dragging = 0
309    
310      def OnMotion(self, event):      def OnMotion(self, event):
         self.set_current_position(event)  
311          if self.dragging:          if self.dragging:
312              self.tool.Hide(self.drag_dc)              self.tool.Hide(self.drag_dc)
313              self.tool.MouseMove(event)  
314            self.MouseMove(event)
315    
316            if self.dragging:
317              self.tool.Show(self.drag_dc)              self.tool.Show(self.drag_dc)
318    
319      def OnLeaveWindow(self, event):      def OnLeaveWindow(self, event):
# Line 838  class MapCanvas(wxWindow, Publisher): Line 327  class MapCanvas(wxWindow, Publisher):
327          # Even when the window becomes larger some parts of the bitmap          # Even when the window becomes larger some parts of the bitmap
328          # could be reused.          # could be reused.
329          self.full_redraw()          self.full_redraw()
         pass  
330    
331      def shape_selected(self, layer, shape):      def shape_selected(self, layer, shape):
332          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""          """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
# Line 848  class MapCanvas(wxWindow, Publisher): Line 336  class MapCanvas(wxWindow, Publisher):
336          # FIXME: We should perhaps try to limit the redraw to the are          # FIXME: We should perhaps try to limit the redraw to the are
337          # actually covered by the shapes before and after the selection          # actually covered by the shapes before and after the selection
338          # change.          # change.
339            ViewPort.shape_selected(self, layer, shape)
340          self.full_redraw()          self.full_redraw()
341    
342      def unprojected_rect_around_point(self, x, y, dist):      def GetTextExtent(self, text):
343          """return a rect dist pixels around (x, y) in unprojected corrdinates          dc = wxClientDC(self)
344            font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
345          The return value is a tuple (minx, miny, maxx, maxy) suitable a          dc.SetFont(font)
346          parameter to a layer's ShapesInRegion method.          return dc.GetTextExtent(text)
         """  
         map_proj = self.map.projection  
         if map_proj is not None:  
             inverse = map_proj.Inverse  
         else:  
             inverse = None  
   
         xs = []  
         ys = []  
         for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):  
             px, py = self.win_to_proj(x + dist * dx, y + dist * dy)  
             if inverse:  
                 px, py = inverse(px, py)  
             xs.append(px)  
             ys.append(py)  
         return (min(xs), min(ys), max(xs), max(ys))  
   
     def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):  
         """Determine the shape at point px, py in window coords  
   
         Return the shape and the corresponding layer as a tuple (layer,  
         shape).  
   
         If the optional parameter select_labels is true (default false)  
         search through the labels. If a label is found return it's index  
         as the shape and None as the layer.  
   
         If the optional parameter searched_layer is given (or not None  
         which it defaults to), only search in that layer.  
         """  
         map_proj = self.map.projection  
         if map_proj is not None:  
             forward = map_proj.Forward  
         else:  
             forward = None  
   
         scale = self.scale  
   
         if scale == 0:  
             return None, None  
   
         offx, offy = self.offset  
   
         if select_labels:  
             labels = self.map.LabelLayer().Labels()  
   
             if labels:  
                 dc = wxClientDC(self)  
                 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)  
                 dc.SetFont(font)  
                 for i in range(len(labels) - 1, -1, -1):  
                     label = labels[i]  
                     x = label.x  
                     y = label.y  
                     text = label.text  
                     if forward:  
                         x, y = forward(x, y)  
                     x = x * scale + offx  
                     y = -y * scale + offy  
                     width, height = dc.GetTextExtent(text)  
                     if label.halign == ALIGN_LEFT:  
                         # nothing to be done  
                         pass  
                     elif label.halign == ALIGN_RIGHT:  
                         x = x - width  
                     elif label.halign == ALIGN_CENTER:  
                         x = x - width/2  
                     if label.valign == ALIGN_TOP:  
                         # nothing to be done  
                         pass  
                     elif label.valign == ALIGN_BOTTOM:  
                         y = y - height  
                     elif label.valign == ALIGN_CENTER:  
                         y = y - height/2  
                     if x <= px < x + width and y <= py <= y + height:  
                         return None, i  
   
         if searched_layer:  
             layers = [searched_layer]  
         else:  
             layers = self.map.Layers()  
   
         for layer_index in range(len(layers) - 1, -1, -1):  
             layer = layers[layer_index]  
   
             # search only in visible layers  
             if not layer.Visible() or not layer.HasShapes():  
                 continue  
   
             filled = layer.GetClassification().GetDefaultFill() \  
                      is not Transparent  
             stroked = layer.GetClassification().GetDefaultLineColor() \  
                       is not Transparent  
   
             layer_proj = layer.projection  
             if layer_proj is not None:  
                 inverse = layer_proj.Inverse  
             else:  
                 inverse = None  
   
             shapetype = layer.ShapeType()  
347    
348              select_shape = -1      def LabelShapeAt(self, x, y, text=None):
   
             # Determine the ids of the shapes that overlap a tiny area  
             # around the point. For layers containing points we have to  
             # choose a larger size of the box we're testing agains so  
             # that we take the size of the markers into account  
             # FIXME: Once the markers are more flexible this part has to  
             # become more flexible too, of course  
             if shapetype == SHAPETYPE_POINT:  
                 box = self.unprojected_rect_around_point(px, py, 5)  
             else:  
                 box = self.unprojected_rect_around_point(px, py, 1)  
             shape_ids = layer.ShapesInRegion(box)  
             shape_ids.reverse()  
   
             if shapetype == SHAPETYPE_POLYGON:  
                 for i in shape_ids:  
                     shapefile = layer.ShapeStore().Shapefile().cobject()  
                     result = point_in_polygon_shape(shapefile, i,  
                                                     filled, stroked,  
                                                     map_proj, layer_proj,  
                                                     scale, -scale, offx, offy,  
                                                     px, py)  
                     if result:  
                         select_shape = i  
                         break  
             elif shapetype == SHAPETYPE_ARC:  
                 for i in shape_ids:  
                     shapefile = layer.ShapeStore().Shapefile().cobject()  
                     result = point_in_polygon_shape(shapefile,  
                                                     i, 0, 1,  
                                                     map_proj, layer_proj,  
                                                     scale, -scale, offx, offy,  
                                                     px, py)  
                     if result < 0:  
                         select_shape = i  
                         break  
             elif shapetype == SHAPETYPE_POINT:  
                 for i in shape_ids:  
                     shape = layer.Shape(i)  
                     x, y = shape.Points()[0]  
                     if inverse:  
                         x, y = inverse(x, y)  
                     if forward:  
                         x, y = forward(x, y)  
                     x = x * scale + offx  
                     y = -y * scale + offy  
                     if hypot(px - x, py - y) < 5:  
                         select_shape = i  
                         break  
   
             if select_shape >= 0:  
                 return layer, select_shape  
         return None, None  
   
     def SelectShapeAt(self, x, y, layer = None):  
         """\  
         Select and return the shape and its layer at window position (x, y)  
   
         If layer is given, only search in that layer. If no layer is  
         given, search through all layers.  
   
         Return a tuple (layer, shapeid). If no shape is found, return  
         (None, None).  
         """  
         layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)  
         # If layer is None, then shape will also be None. We don't want  
         # to deselect the currently selected layer, so we simply select  
         # the already selected layer again.  
         if layer is None:  
             layer = self.selection.SelectedLayer()  
             shapes = []  
         else:  
             shapes = [shape]  
         self.selection.SelectShapes(layer, shapes)  
         return result  
   
     def LabelShapeAt(self, x, y):  
349          """Add or remove a label at window position x, y.          """Add or remove a label at window position x, y.
350                                                                                    
351          If there's a label at the given position, remove it. Otherwise          If there's a label at the given position, remove it. Otherwise
352          determine the shape at the position, run the label dialog and          determine the shape at the position, run the label dialog and
353          unless the user cancels the dialog, add a laber.          unless the user cancels the dialog, add a label.
354          """          """
         ox = x; oy = y  
355          label_layer = self.map.LabelLayer()          label_layer = self.map.LabelLayer()
356          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)          layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
357          if layer is None and shape_index is not None:          if layer is None and shape_index is not None:
358              # a label was selected              ViewPort.LabelShapeAt(self, x, y)
             label_layer.RemoveLabel(shape_index)  
359          elif layer is not None:          elif layer is not None:
360              text = labeldialog.run_label_dialog(self,              text = labeldialog.run_label_dialog(self,
361                                                  layer.ShapeStore().Table(),                                                  layer.ShapeStore().Table(),
362                                                  shape_index)                                                  shape_index)
363              if text:              ViewPort.LabelShapeAt(self, x, y, text)
364                  proj = self.map.projection          
                 if proj is not None:  
                     map_proj = proj  
                 else:  
                     map_proj = None  
                 proj = layer.projection  
                 if proj is not None:  
                     layer_proj = proj  
                 else:  
                     layer_proj = None  
   
                 shapetype = layer.ShapeType()  
                 if shapetype == SHAPETYPE_POLYGON:  
                     shapefile = layer.ShapeStore().Shapefile().cobject()  
                     x, y = shape_centroid(shapefile, shape_index,  
                                           map_proj, layer_proj, 1, 1, 0, 0)  
                     if map_proj is not None:  
                         x, y = map_proj.Inverse(x, y)  
                 else:  
                     shape = layer.Shape(shape_index)  
                     if shapetype == SHAPETYPE_POINT:  
                         x, y = shape.Points()[0]  
                     else:  
                         # assume SHAPETYPE_ARC  
                         points = shape.Points()  
                         x, y = points[len(points) / 2]  
                     if layer_proj is not None:  
                         x, y = layer_proj.Inverse(x, y)  
                 if shapetype == SHAPETYPE_POINT:  
                     halign = ALIGN_LEFT  
                     valign = ALIGN_CENTER  
                 elif shapetype == SHAPETYPE_POLYGON:  
                     halign = ALIGN_CENTER  
                     valign = ALIGN_CENTER  
                 elif shapetype == SHAPETYPE_ARC:  
                     halign = ALIGN_LEFT  
                     valign = ALIGN_CENTER  
                 label_layer.AddLabel(x, y, text,  
                                      halign = halign, valign = valign)  
   
365  def OutputTransform(canvas_scale, canvas_offset, canvas_size, device_extend):  def OutputTransform(canvas_scale, canvas_offset, canvas_size, device_extend):
366      """Calculate dimensions to transform canvas content to output device."""      """Calculate dimensions to transform canvas content to output device."""
367      width, height = device_extend      width, height = device_extend

Legend:
Removed from v.1384  
changed lines
  Added in v.1385

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26