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

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

Parent Directory Parent Directory | Revision Log Revision Log


Revision 883 - (hide annotations)
Fri May 9 16:34:39 2003 UTC (21 years, 10 months ago) by jonathan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 32108 byte(s)
(MapCanvas): Delegate "SelectedShapes" so the table view can call it.

1 bh 404 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 bh 6 # Authors:
3     # Bernhard Herzog <[email protected]>
4     #
5     # This program is free software under the GPL (>=v2)
6     # Read the file COPYING coming with Thuban for details.
7    
8     """
9     Classes for display of a map and interaction with it
10     """
11    
12     __version__ = "$Revision$"
13    
14 jonathan 799 import sys
15    
16 bh 6 from math import hypot
17    
18     from wxPython.wx import wxWindow,\
19     wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
20 jonathan 822 EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
21     wxBITMAP_TYPE_XPM, wxBeginBusyCursor, wxEndBusyCursor, wxCursor, \
22     wxImageFromBitmap
23 bh 6
24    
25     from wxPython import wx
26    
27     from wxproj import point_in_polygon_shape, shape_centroid
28    
29    
30     from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \
31 jonathan 565 MAP_LAYERS_CHANGED, LAYER_CHANGED, LAYER_VISIBILITY_CHANGED
32 bh 6 from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
33     SHAPETYPE_POINT
34     from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
35     ALIGN_LEFT, ALIGN_RIGHT
36 bh 122 from Thuban.Lib.connector import Publisher
37 jonathan 433 from Thuban.Model.color import Color
38 bh 6
39 jonathan 822 import resource
40    
41 bh 535 from selection import Selection
42 bh 6 from renderer import ScreenRenderer, PrinterRender
43    
44     import labeldialog
45    
46 frank 855 from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION, \
47     SCALE_CHANGED
48 bh 6
49    
50     #
51     # The tools
52     #
53    
54     class Tool:
55    
56     """
57     Base class for the interactive tools
58     """
59    
60     def __init__(self, view):
61     """Intitialize the tool. The view is the canvas displaying the map"""
62     self.view = view
63     self.start = self.current = None
64     self.dragging = 0
65     self.drawn = 0
66    
67     def Name(self):
68     """Return the tool's name"""
69     return ''
70    
71     def drag_start(self, x, y):
72     self.start = self.current = x, y
73     self.dragging = 1
74    
75     def drag_move(self, x, y):
76     self.current = x, y
77    
78     def drag_stop(self, x, y):
79     self.current = x, y
80     self.dragging = 0
81    
82     def Show(self, dc):
83     if not self.drawn:
84     self.draw(dc)
85     self.drawn = 1
86    
87     def Hide(self, dc):
88     if self.drawn:
89     self.draw(dc)
90     self.drawn = 0
91    
92     def draw(self, dc):
93     pass
94    
95     def MouseDown(self, event):
96     self.drag_start(event.m_x, event.m_y)
97    
98     def MouseMove(self, event):
99     if self.dragging:
100     self.drag_move(event.m_x, event.m_y)
101    
102     def MouseUp(self, event):
103     if self.dragging:
104     self.drag_move(event.m_x, event.m_y)
105    
106     def Cancel(self):
107     self.dragging = 0
108    
109    
110     class RectTool(Tool):
111    
112     """Base class for tools that draw rectangles while dragging"""
113    
114     def draw(self, dc):
115     sx, sy = self.start
116     cx, cy = self.current
117     dc.DrawRectangle(sx, sy, cx - sx, cy - sy)
118    
119     class ZoomInTool(RectTool):
120    
121     """The Zoom-In Tool"""
122    
123     def Name(self):
124     return "ZoomInTool"
125    
126     def proj_rect(self):
127     """return the rectangle given by start and current in projected
128     coordinates"""
129     sx, sy = self.start
130     cx, cy = self.current
131     left, top = self.view.win_to_proj(sx, sy)
132     right, bottom = self.view.win_to_proj(cx, cy)
133     return (min(left, right), min(top, bottom),
134     max(left, right), max(top, bottom))
135    
136     def MouseUp(self, event):
137     if self.dragging:
138     Tool.MouseUp(self, event)
139 bh 57 sx, sy = self.start
140     cx, cy = self.current
141 bh 288 if sx == cx or sy == cy:
142     # Just a mouse click or a degenerate rectangle. Simply
143     # zoom in by a factor of two
144     # FIXME: For a click this is the desired behavior but should we
145     # really do this for degenrate rectagles as well or
146     # should we ignore them?
147 bh 57 self.view.ZoomFactor(2, center = (cx, cy))
148     else:
149     # A drag. Zoom in to the rectangle
150     self.view.FitRectToWindow(self.proj_rect())
151 bh 6
152    
153     class ZoomOutTool(RectTool):
154    
155     """The Zoom-Out Tool"""
156 bh 239
157 bh 6 def Name(self):
158     return "ZoomOutTool"
159    
160     def MouseUp(self, event):
161     if self.dragging:
162     Tool.MouseUp(self, event)
163     sx, sy = self.start
164     cx, cy = self.current
165 bh 288 if sx == cx or sy == cy:
166     # Just a mouse click or a degenerate rectangle. Simply
167     # zoom out by a factor of two.
168     # FIXME: For a click this is the desired behavior but should we
169     # really do this for degenrate rectagles as well or
170     # should we ignore them?
171 bh 239 self.view.ZoomFactor(0.5, center = (cx, cy))
172 bh 57 else:
173     # A drag. Zoom out to the rectangle
174     self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
175     max(sx, cx), max(sy, cy)))
176 bh 6
177    
178     class PanTool(Tool):
179    
180     """The Pan Tool"""
181    
182     def Name(self):
183     return "PanTool"
184    
185     def MouseMove(self, event):
186     if self.dragging:
187     Tool.MouseMove(self, event)
188 bh 159 sx, sy = self.start
189 bh 6 x, y = self.current
190     width, height = self.view.GetSizeTuple()
191 bh 159
192     bitmapdc = wx.wxMemoryDC()
193     bitmapdc.SelectObject(self.view.bitmap)
194    
195 bh 6 dc = self.view.drag_dc
196 bh 159 dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
197 bh 6
198     def MouseUp(self, event):
199     if self.dragging:
200     Tool.MouseUp(self, event)
201     sx, sy = self.start
202     cx, cy = self.current
203     self.view.Translate(cx - sx, cy - sy)
204 bh 246
205 bh 6 class IdentifyTool(Tool):
206    
207     """The "Identify" Tool"""
208 bh 246
209 bh 6 def Name(self):
210     return "IdentifyTool"
211    
212     def MouseUp(self, event):
213     self.view.SelectShapeAt(event.m_x, event.m_y)
214    
215    
216     class LabelTool(Tool):
217    
218     """The "Label" Tool"""
219    
220     def Name(self):
221     return "LabelTool"
222    
223     def MouseUp(self, event):
224     self.view.LabelShapeAt(event.m_x, event.m_y)
225    
226    
227    
228    
229     class MapPrintout(wx.wxPrintout):
230    
231     """
232     wxPrintout class for printing Thuban maps
233     """
234    
235     def __init__(self, map):
236     wx.wxPrintout.__init__(self)
237     self.map = map
238    
239     def GetPageInfo(self):
240     return (1, 1, 1, 1)
241    
242     def HasPage(self, pagenum):
243     return pagenum == 1
244    
245     def OnPrintPage(self, pagenum):
246     if pagenum == 1:
247     self.draw_on_dc(self.GetDC())
248    
249     def draw_on_dc(self, dc):
250     width, height = self.GetPageSizePixels()
251     llx, lly, urx, ury = self.map.ProjectedBoundingBox()
252     scalex = width / (urx - llx)
253     scaley = height / (ury - lly)
254     scale = min(scalex, scaley)
255     offx = 0.5 * (width - (urx + llx) * scale)
256     offy = 0.5 * (height + (ury + lly) * scale)
257    
258     resx, resy = self.GetPPIPrinter()
259     renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)
260     renderer.RenderMap(self.map)
261     return wx.true
262    
263 bh 246
264 bh 122 class MapCanvas(wxWindow, Publisher):
265 bh 6
266     """A widget that displays a map and offers some interaction"""
267    
268 bh 535 # Some messages that can be subscribed/unsubscribed directly through
269 bh 789 # the MapCanvas come in fact from other objects. This is a dict
270     # mapping those messages to the names of the instance variables they
271     # actually come from. The delegation is implemented in the Subscribe
272     # and Unsubscribe methods
273 bh 535 delegated_messages = {LAYER_SELECTED: "selection",
274     SHAPES_SELECTED: "selection"}
275    
276     # Methods delegated to some instance variables. The delegation is
277     # implemented in the __getattr__ method.
278     delegated_methods = {"SelectLayer": "selection",
279     "SelectShapes": "selection",
280     "SelectedLayer": "selection",
281 jonathan 831 "HasSelectedLayer": "selection",
282 jonathan 883 "HasSelectedShapes": "selection",
283     "SelectedShapes": "selection"}
284 bh 535
285     def __init__(self, parent, winid):
286 bh 6 wxWindow.__init__(self, parent, winid)
287     self.SetBackgroundColour(wxColour(255, 255, 255))
288 bh 125
289     # the map displayed in this canvas. Set with SetMap()
290 bh 6 self.map = None
291 bh 125
292     # scale and offset describe the transformation from projected
293     # coordinates to window coordinates.
294 bh 6 self.scale = 1.0
295     self.offset = (0, 0)
296 bh 125
297     # whether the user is currently dragging the mouse, i.e. moving
298     # the mouse while pressing a mouse button
299 bh 6 self.dragging = 0
300 bh 125
301     # the currently active tool
302 bh 6 self.tool = None
303 bh 125
304     # The current mouse position of the last OnMotion event or None
305     # if the mouse is outside the window.
306     self.current_position = None
307    
308     # the bitmap serving as backing store
309     self.bitmap = None
310    
311 bh 535 # the selection
312     self.selection = Selection()
313     self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
314 bh 125
315 bh 174 # keep track of which layers/shapes are selected to make sure we
316     # only redraw when necessary
317     self.last_selected_layer = None
318     self.last_selected_shape = None
319    
320 bh 125 # subscribe the WX events we're interested in
321 bh 6 EVT_PAINT(self, self.OnPaint)
322     EVT_LEFT_DOWN(self, self.OnLeftDown)
323     EVT_LEFT_UP(self, self.OnLeftUp)
324     EVT_MOTION(self, self.OnMotion)
325 bh 122 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
326 bh 125 wx.EVT_SIZE(self, self.OnSize)
327 bh 6
328 bh 122 def __del__(self):
329     wxWindow.__del__(self)
330     Publisher.__del__(self)
331    
332 bh 535 def Subscribe(self, channel, *args):
333     """Extend the inherited method to handle delegated messages.
334    
335     If channel is one of the delegated messages call the appropriate
336     object's Subscribe method. Otherwise just call the inherited
337     method.
338     """
339     if channel in self.delegated_messages:
340     object = getattr(self, self.delegated_messages[channel])
341     object.Subscribe(channel, *args)
342     else:
343     Publisher.Subscribe(self, channel, *args)
344    
345     def Unsubscribe(self, channel, *args):
346     """Extend the inherited method to handle delegated messages.
347    
348     If channel is one of the delegated messages call the appropriate
349     object's Unsubscribe method. Otherwise just call the inherited
350     method.
351     """
352     if channel in self.delegated_messages:
353     object = getattr(self, self.delegated_messages[channel])
354     object.Unsubscribe(channel, *args)
355     else:
356     Publisher.Unsubscribe(self, channel, *args)
357    
358     def __getattr__(self, attr):
359     if attr in self.delegated_methods:
360     return getattr(getattr(self, self.delegated_methods[attr]), attr)
361     raise AttributeError(attr)
362    
363 bh 6 def OnPaint(self, event):
364     dc = wxPaintDC(self)
365 jonathan 799 clear = self.map is None or not self.map.HasLayers()
366    
367 jonathan 822 #wxBeginBusyCursor()
368    
369 jonathan 799 if not clear:
370     try:
371     self.do_redraw()
372     except:
373     print "Error during drawing:", sys.exc_info()[0]
374     clear = True
375    
376     if clear:
377 bh 57 # If we've got no map or if the map is empty, simply clear
378     # the screen.
379 bh 246
380 bh 57 # XXX it's probably possible to get rid of this. The
381     # background color of the window is already white and the
382     # only thing we may have to do is to call self.Refresh()
383     # with a true argument in the right places.
384     dc.BeginDrawing()
385 bh 246 dc.Clear()
386 bh 57 dc.EndDrawing()
387 bh 6
388 jonathan 822 #wxEndBusyCursor()
389    
390 bh 6 def do_redraw(self):
391 bh 145 # This should only be called if we have a non-empty map.
392 bh 125
393 bh 145 # Get the window size.
394 bh 6 width, height = self.GetSizeTuple()
395    
396 bh 125 # If self.bitmap's still there, reuse it. Otherwise redraw it
397     if self.bitmap is not None:
398     bitmap = self.bitmap
399 bh 6 else:
400 bh 125 bitmap = wx.wxEmptyBitmap(width, height)
401     dc = wx.wxMemoryDC()
402     dc.SelectObject(bitmap)
403     dc.BeginDrawing()
404 bh 57
405 bh 125 # clear the background
406 jonathan 799 #dc.SetBrush(wx.wxWHITE_BRUSH)
407     #dc.SetPen(wx.wxTRANSPARENT_PEN)
408     #dc.DrawRectangle(0, 0, width, height)
409     dc.SetBackground(wx.wxWHITE_BRUSH)
410     dc.Clear()
411 bh 6
412 bh 535 selected_layer = self.selection.SelectedLayer()
413     selected_shapes = self.selection.SelectedShapes()
414 bh 57
415 bh 125 # draw the map into the bitmap
416     renderer = ScreenRenderer(dc, self.scale, self.offset)
417 bh 149
418 bh 296 # Pass the entire bitmap as update region to the renderer.
419 bh 149 # We're redrawing the whole bitmap, after all.
420     renderer.RenderMap(self.map, (0, 0, width, height),
421 bh 535 selected_layer, selected_shapes)
422 bh 125
423     dc.EndDrawing()
424     dc.SelectObject(wx.wxNullBitmap)
425     self.bitmap = bitmap
426    
427 bh 57 # blit the bitmap to the screen
428 bh 125 dc = wx.wxMemoryDC()
429     dc.SelectObject(bitmap)
430 bh 6 clientdc = wxClientDC(self)
431     clientdc.BeginDrawing()
432     clientdc.Blit(0, 0, width, height, dc, 0, 0)
433     clientdc.EndDrawing()
434    
435     def Print(self):
436     printer = wx.wxPrinter()
437     printout = MapPrintout(self.map)
438     printer.Print(self, printout, wx.true)
439     printout.Destroy()
440 bh 246
441 bh 6 def SetMap(self, map):
442 jonathan 565 redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
443 bh 6 LAYER_VISIBILITY_CHANGED)
444     if self.map is not None:
445     for channel in redraw_channels:
446 bh 125 self.map.Unsubscribe(channel, self.full_redraw)
447 bh 6 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
448     self.projection_changed)
449     self.map = map
450 bh 535 self.selection.ClearSelection()
451 bh 6 if self.map is not None:
452     for channel in redraw_channels:
453 bh 125 self.map.Subscribe(channel, self.full_redraw)
454 bh 6 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
455     self.FitMapToWindow()
456 bh 57 # force a redraw. If map is not empty, it's already been called
457     # by FitMapToWindow but if map is empty it hasn't been called
458     # yet so we have to explicitly call it.
459 bh 125 self.full_redraw()
460 bh 6
461     def Map(self):
462 bh 295 """Return the map displayed by this canvas"""
463 bh 6 return self.map
464    
465     def redraw(self, *args):
466     self.Refresh(0)
467    
468 bh 125 def full_redraw(self, *args):
469     self.bitmap = None
470     self.redraw()
471    
472 bh 6 def projection_changed(self, *args):
473     self.FitMapToWindow()
474 bh 125 self.full_redraw()
475 bh 6
476     def set_view_transform(self, scale, offset):
477     self.scale = scale
478     self.offset = offset
479 bh 125 self.full_redraw()
480 frank 855 self.issue(SCALE_CHANGED, scale)
481 bh 6
482     def proj_to_win(self, x, y):
483     """\
484     Return the point in window coords given by projected coordinates x y
485     """
486     offx, offy = self.offset
487     return (self.scale * x + offx, -self.scale * y + offy)
488    
489     def win_to_proj(self, x, y):
490     """\
491     Return the point in projected coordinates given by window coords x y
492     """
493     offx, offy = self.offset
494     return ((x - offx) / self.scale, (offy - y) / self.scale)
495    
496     def FitRectToWindow(self, rect):
497 bh 293 """Fit the rectangular region given by rect into the window.
498 bh 535
499 bh 293 Set scale so that rect (in projected coordinates) just fits into
500     the window and center it.
501     """
502 bh 6 width, height = self.GetSizeTuple()
503     llx, lly, urx, ury = rect
504 bh 45 if llx == urx or lly == ury:
505 jonathan 799 # zero width or zero height. Do Nothing
506 bh 45 return
507 bh 6 scalex = width / (urx - llx)
508     scaley = height / (ury - lly)
509     scale = min(scalex, scaley)
510     offx = 0.5 * (width - (urx + llx) * scale)
511     offy = 0.5 * (height + (ury + lly) * scale)
512     self.set_view_transform(scale, (offx, offy))
513    
514     def FitMapToWindow(self):
515 bh 293 """Fit the map to the window
516 bh 535
517 bh 293 Set the scale so that the map fits exactly into the window and
518     center it in the window.
519 bh 6 """
520     bbox = self.map.ProjectedBoundingBox()
521     if bbox is not None:
522     self.FitRectToWindow(bbox)
523    
524 jonathan 822 def FitLayerToWindow(self, layer):
525     """Fit the given layer to the window.
526    
527     Set the scale so that the layer fits exactly into the window and
528     center it in the window.
529     """
530    
531     bbox = layer.LatLongBoundingBox()
532     if bbox is not None:
533     proj = self.map.GetProjection()
534     if proj is not None:
535     bbox = proj.ForwardBBox(bbox)
536    
537 jonathan 824 if bbox is not None:
538     self.FitRectToWindow(bbox)
539    
540 jonathan 831 def FitSelectedToWindow(self):
541     layer = self.selection.SelectedLayer()
542     shapes = self.selection.SelectedShapes()
543    
544     bbox = layer.ShapesBoundingBox(shapes)
545     if bbox is not None:
546     proj = self.map.GetProjection()
547     if proj is not None:
548     bbox = proj.ForwardBBox(bbox)
549    
550     if bbox is not None:
551     self.FitRectToWindow(bbox)
552    
553 bh 57 def ZoomFactor(self, factor, center = None):
554     """Multiply the zoom by factor and center on center.
555    
556     The optional parameter center is a point in window coordinates
557     that should be centered. If it is omitted, it defaults to the
558     center of the window
559     """
560 bh 6 width, height = self.GetSizeTuple()
561     scale = self.scale * factor
562     offx, offy = self.offset
563 bh 57 if center is not None:
564     cx, cy = center
565     else:
566     cx = width / 2
567     cy = height / 2
568     offset = (factor * (offx - cx) + width / 2,
569     factor * (offy - cy) + height / 2)
570 bh 6 self.set_view_transform(scale, offset)
571    
572     def ZoomOutToRect(self, rect):
573 bh 293 """Zoom out to fit the currently visible region into rect.
574 bh 6
575 bh 293 The rect parameter is given in window coordinates
576     """
577 bh 6 # determine the bbox of the displayed region in projected
578     # coordinates
579     width, height = self.GetSizeTuple()
580     llx, lly = self.win_to_proj(0, height - 1)
581     urx, ury = self.win_to_proj(width - 1, 0)
582    
583     sx, sy, ex, ey = rect
584     scalex = (ex - sx) / (urx - llx)
585     scaley = (ey - sy) / (ury - lly)
586     scale = min(scalex, scaley)
587    
588     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
589     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
590     self.set_view_transform(scale, (offx, offy))
591    
592     def Translate(self, dx, dy):
593 bh 295 """Move the map by dx, dy pixels"""
594 bh 6 offx, offy = self.offset
595     self.set_view_transform(self.scale, (offx + dx, offy + dy))
596    
597 bh 356 def SelectTool(self, tool):
598     """Make tool the active tool.
599    
600     The parameter should be an instance of Tool or None to indicate
601     that no tool is active.
602     """
603     self.tool = tool
604    
605 bh 6 def ZoomInTool(self):
606 bh 295 """Start the zoom in tool"""
607 bh 356 self.SelectTool(ZoomInTool(self))
608 bh 6
609     def ZoomOutTool(self):
610 bh 295 """Start the zoom out tool"""
611 bh 356 self.SelectTool(ZoomOutTool(self))
612 bh 6
613     def PanTool(self):
614 bh 295 """Start the pan tool"""
615 bh 356 self.SelectTool(PanTool(self))
616 jonathan 822 #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
617     #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
618     #print bmp
619     #img = wxImageFromBitmap(bmp)
620     #print img
621     #cur = wxCursor(img)
622     #print cur
623     #self.SetCursor(cur)
624 bh 6
625     def IdentifyTool(self):
626 bh 295 """Start the identify tool"""
627 bh 356 self.SelectTool(IdentifyTool(self))
628 bh 6
629     def LabelTool(self):
630 bh 295 """Start the label tool"""
631 bh 356 self.SelectTool(LabelTool(self))
632 bh 6
633     def CurrentTool(self):
634 bh 295 """Return the name of the current tool or None if no tool is active"""
635 bh 6 return self.tool and self.tool.Name() or None
636    
637 bh 122 def CurrentPosition(self):
638     """Return current position of the mouse in projected coordinates.
639    
640     The result is a 2-tuple of floats with the coordinates. If the
641     mouse is not in the window, the result is None.
642     """
643     if self.current_position is not None:
644     x, y = self.current_position
645     return self.win_to_proj(x, y)
646     else:
647     return None
648    
649     def set_current_position(self, event):
650     """Set the current position from event
651    
652     Should be called by all events that contain mouse positions
653     especially EVT_MOTION. The event paramete may be None to
654     indicate the the pointer left the window.
655     """
656     if event is not None:
657     self.current_position = (event.m_x, event.m_y)
658     else:
659     self.current_position = None
660     self.issue(VIEW_POSITION)
661    
662 bh 6 def OnLeftDown(self, event):
663 bh 122 self.set_current_position(event)
664 bh 6 if self.tool is not None:
665     self.drag_dc = wxClientDC(self)
666     self.drag_dc.SetLogicalFunction(wxINVERT)
667     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
668     self.CaptureMouse()
669     self.tool.MouseDown(event)
670     self.tool.Show(self.drag_dc)
671     self.dragging = 1
672 bh 246
673 bh 6 def OnLeftUp(self, event):
674 bh 122 self.set_current_position(event)
675 bh 6 if self.dragging:
676 bh 261 self.ReleaseMouse()
677 bh 404 try:
678     self.tool.Hide(self.drag_dc)
679     self.tool.MouseUp(event)
680     finally:
681     self.drag_dc = None
682     self.dragging = 0
683 bh 6
684     def OnMotion(self, event):
685 bh 122 self.set_current_position(event)
686 bh 6 if self.dragging:
687     self.tool.Hide(self.drag_dc)
688     self.tool.MouseMove(event)
689     self.tool.Show(self.drag_dc)
690    
691 bh 122 def OnLeaveWindow(self, event):
692     self.set_current_position(None)
693    
694 bh 125 def OnSize(self, event):
695     # the window's size has changed. We have to get a new bitmap. If
696     # we want to be clever we could try to get by without throwing
697     # everything away. E.g. when the window gets smaller, we could
698     # either keep the bitmap or create the new one from the old one.
699     # Even when the window becomes larger some parts of the bitmap
700     # could be reused.
701     self.full_redraw()
702 jonathan 799 pass
703 bh 125
704 bh 6 def shape_selected(self, layer, shape):
705 bh 535 """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
706     # The selection object takes care that it only issues
707     # SHAPES_SELECTED messages when the set of selected shapes has
708     # actually changed, so we can do a full redraw unconditionally.
709     # FIXME: We should perhaps try to limit the redraw to the are
710     # actually covered by the shapes before and after the selection
711     # change.
712     self.full_redraw()
713 bh 6
714 bh 301 def unprojected_rect_around_point(self, x, y, dist):
715     """return a rect dist pixels around (x, y) in unprojected corrdinates
716 bh 159
717     The return value is a tuple (minx, miny, maxx, maxy) suitable a
718     parameter to a layer's ShapesInRegion method.
719     """
720     map_proj = self.map.projection
721     if map_proj is not None:
722     inverse = map_proj.Inverse
723     else:
724     inverse = None
725    
726     xs = []
727     ys = []
728     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
729 bh 301 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
730 bh 159 if inverse:
731     px, py = inverse(px, py)
732     xs.append(px)
733     ys.append(py)
734     return (min(xs), min(ys), max(xs), max(ys))
735    
736 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
737 bh 43 """Determine the shape at point px, py in window coords
738    
739     Return the shape and the corresponding layer as a tuple (layer,
740     shape).
741    
742     If the optional parameter select_labels is true (default false)
743     search through the labels. If a label is found return it's index
744     as the shape and None as the layer.
745    
746 bh 246 If the optional parameter searched_layer is given (or not None
747     which it defaults to), only search in that layer.
748 bh 43 """
749 bh 6 map_proj = self.map.projection
750     if map_proj is not None:
751     forward = map_proj.Forward
752     else:
753     forward = None
754    
755     scale = self.scale
756     offx, offy = self.offset
757    
758     if select_labels:
759     labels = self.map.LabelLayer().Labels()
760 bh 246
761 bh 6 if labels:
762     dc = wxClientDC(self)
763     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
764     dc.SetFont(font)
765 bh 60 for i in range(len(labels) - 1, -1, -1):
766 bh 6 label = labels[i]
767     x = label.x
768     y = label.y
769     text = label.text
770     if forward:
771     x, y = forward(x, y)
772     x = x * scale + offx
773     y = -y * scale + offy
774     width, height = dc.GetTextExtent(text)
775     if label.halign == ALIGN_LEFT:
776     # nothing to be done
777     pass
778     elif label.halign == ALIGN_RIGHT:
779     x = x - width
780     elif label.halign == ALIGN_CENTER:
781     x = x - width/2
782     if label.valign == ALIGN_TOP:
783     # nothing to be done
784     pass
785     elif label.valign == ALIGN_BOTTOM:
786     y = y - height
787     elif label.valign == ALIGN_CENTER:
788     y = y - height/2
789     if x <= px < x + width and y <= py <= y + height:
790     return None, i
791 bh 43
792 bh 246 if searched_layer:
793     layers = [searched_layer]
794 bh 43 else:
795     layers = self.map.Layers()
796    
797 bh 6 for layer_index in range(len(layers) - 1, -1, -1):
798     layer = layers[layer_index]
799    
800     # search only in visible layers
801     if not layer.Visible():
802     continue
803    
804 jonathan 433 filled = layer.GetClassification().GetDefaultFill() \
805 jonathan 610 is not Color.Transparent
806 jonathan 469 stroked = layer.GetClassification().GetDefaultLineColor() \
807 jonathan 610 is not Color.Transparent
808 bh 246
809 bh 6 layer_proj = layer.projection
810     if layer_proj is not None:
811     inverse = layer_proj.Inverse
812     else:
813     inverse = None
814 bh 246
815 bh 6 shapetype = layer.ShapeType()
816    
817     select_shape = -1
818 bh 159
819 bh 301 # Determine the ids of the shapes that overlap a tiny area
820     # around the point. For layers containing points we have to
821     # choose a larger size of the box we're testing agains so
822     # that we take the size of the markers into account
823     # FIXME: Once the markers are more flexible this part has to
824     # become more flexible too, of course
825     if shapetype == SHAPETYPE_POINT:
826     box = self.unprojected_rect_around_point(px, py, 5)
827     else:
828     box = self.unprojected_rect_around_point(px, py, 1)
829 bh 159 shape_ids = layer.ShapesInRegion(box)
830     shape_ids.reverse()
831    
832 bh 6 if shapetype == SHAPETYPE_POLYGON:
833 bh 159 for i in shape_ids:
834 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
835     i,
836     filled, stroked,
837     map_proj, layer_proj,
838     scale, -scale, offx, offy,
839     px, py)
840     if result:
841     select_shape = i
842     break
843     elif shapetype == SHAPETYPE_ARC:
844 bh 159 for i in shape_ids:
845 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
846     i, 0, 1,
847     map_proj, layer_proj,
848     scale, -scale, offx, offy,
849     px, py)
850     if result < 0:
851     select_shape = i
852     break
853     elif shapetype == SHAPETYPE_POINT:
854 bh 159 for i in shape_ids:
855 bh 6 shape = layer.Shape(i)
856     x, y = shape.Points()[0]
857     if inverse:
858     x, y = inverse(x, y)
859     if forward:
860     x, y = forward(x, y)
861     x = x * scale + offx
862     y = -y * scale + offy
863     if hypot(px - x, py - y) < 5:
864     select_shape = i
865     break
866    
867     if select_shape >= 0:
868     return layer, select_shape
869     return None, None
870    
871 bh 246 def SelectShapeAt(self, x, y, layer = None):
872     """\
873     Select and return the shape and its layer at window position (x, y)
874    
875     If layer is given, only search in that layer. If no layer is
876     given, search through all layers.
877    
878     Return a tuple (layer, shapeid). If no shape is found, return
879     (None, None).
880     """
881     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
882 bh 43 # If layer is None, then shape will also be None. We don't want
883     # to deselect the currently selected layer, so we simply select
884     # the already selected layer again.
885     if layer is None:
886 bh 535 layer = self.selection.SelectedLayer()
887     shapes = []
888     else:
889     shapes = [shape]
890     self.selection.SelectShapes(layer, shapes)
891 bh 246 return result
892 bh 6
893     def LabelShapeAt(self, x, y):
894 bh 295 """Add or remove a label at window position x, y.
895    
896     If there's a label at the given position, remove it. Otherwise
897     determine the shape at the position, run the label dialog and
898     unless the user cancels the dialog, add a laber.
899     """
900 bh 6 ox = x; oy = y
901     label_layer = self.map.LabelLayer()
902     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
903     if layer is None and shape_index is not None:
904     # a label was selected
905     label_layer.RemoveLabel(shape_index)
906     elif layer is not None:
907     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
908     if text:
909     proj = self.map.projection
910     if proj is not None:
911     map_proj = proj
912     else:
913     map_proj = None
914     proj = layer.projection
915     if proj is not None:
916     layer_proj = proj
917     else:
918     layer_proj = None
919    
920     shapetype = layer.ShapeType()
921     if shapetype == SHAPETYPE_POLYGON:
922     x, y = shape_centroid(layer.shapefile.cobject(),
923     shape_index,
924     map_proj, layer_proj, 1, 1, 0, 0)
925     if map_proj is not None:
926     x, y = map_proj.Inverse(x, y)
927     else:
928     shape = layer.Shape(shape_index)
929     if shapetype == SHAPETYPE_POINT:
930     x, y = shape.Points()[0]
931     else:
932     # assume SHAPETYPE_ARC
933     points = shape.Points()
934     x, y = points[len(points) / 2]
935     if layer_proj is not None:
936     x, y = layer_proj.Inverse(x, y)
937     if shapetype == SHAPETYPE_POINT:
938     halign = ALIGN_LEFT
939     valign = ALIGN_CENTER
940     elif shapetype == SHAPETYPE_POLYGON:
941     halign = ALIGN_CENTER
942     valign = ALIGN_CENTER
943     elif shapetype == SHAPETYPE_ARC:
944     halign = ALIGN_LEFT
945     valign = ALIGN_CENTER
946     label_layer.AddLabel(x, y, text,
947     halign = halign, valign = valign)

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26