/[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 855 - (hide annotations)
Wed May 7 18:24:27 2003 UTC (21 years, 10 months ago) by frank
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 32052 byte(s)
(MapCanvas.set_view_transform): Issue SCALE_CHANGED.

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     "HasSelectedShapes": "selection"}
283 bh 535
284     def __init__(self, parent, winid):
285 bh 6 wxWindow.__init__(self, parent, winid)
286     self.SetBackgroundColour(wxColour(255, 255, 255))
287 bh 125
288     # the map displayed in this canvas. Set with SetMap()
289 bh 6 self.map = None
290 bh 125
291     # scale and offset describe the transformation from projected
292     # coordinates to window coordinates.
293 bh 6 self.scale = 1.0
294     self.offset = (0, 0)
295 bh 125
296     # whether the user is currently dragging the mouse, i.e. moving
297     # the mouse while pressing a mouse button
298 bh 6 self.dragging = 0
299 bh 125
300     # the currently active tool
301 bh 6 self.tool = None
302 bh 125
303     # The current mouse position of the last OnMotion event or None
304     # if the mouse is outside the window.
305     self.current_position = None
306    
307     # the bitmap serving as backing store
308     self.bitmap = None
309    
310 bh 535 # the selection
311     self.selection = Selection()
312     self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
313 bh 125
314 bh 174 # keep track of which layers/shapes are selected to make sure we
315     # only redraw when necessary
316     self.last_selected_layer = None
317     self.last_selected_shape = None
318    
319 bh 125 # subscribe the WX events we're interested in
320 bh 6 EVT_PAINT(self, self.OnPaint)
321     EVT_LEFT_DOWN(self, self.OnLeftDown)
322     EVT_LEFT_UP(self, self.OnLeftUp)
323     EVT_MOTION(self, self.OnMotion)
324 bh 122 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
325 bh 125 wx.EVT_SIZE(self, self.OnSize)
326 bh 6
327 bh 122 def __del__(self):
328     wxWindow.__del__(self)
329     Publisher.__del__(self)
330    
331 bh 535 def Subscribe(self, channel, *args):
332     """Extend the inherited method to handle delegated messages.
333    
334     If channel is one of the delegated messages call the appropriate
335     object's Subscribe method. Otherwise just call the inherited
336     method.
337     """
338     if channel in self.delegated_messages:
339     object = getattr(self, self.delegated_messages[channel])
340     object.Subscribe(channel, *args)
341     else:
342     Publisher.Subscribe(self, channel, *args)
343    
344     def Unsubscribe(self, channel, *args):
345     """Extend the inherited method to handle delegated messages.
346    
347     If channel is one of the delegated messages call the appropriate
348     object's Unsubscribe method. Otherwise just call the inherited
349     method.
350     """
351     if channel in self.delegated_messages:
352     object = getattr(self, self.delegated_messages[channel])
353     object.Unsubscribe(channel, *args)
354     else:
355     Publisher.Unsubscribe(self, channel, *args)
356    
357     def __getattr__(self, attr):
358     if attr in self.delegated_methods:
359     return getattr(getattr(self, self.delegated_methods[attr]), attr)
360     raise AttributeError(attr)
361    
362 bh 6 def OnPaint(self, event):
363     dc = wxPaintDC(self)
364 jonathan 799 clear = self.map is None or not self.map.HasLayers()
365    
366 jonathan 822 #wxBeginBusyCursor()
367    
368 jonathan 799 if not clear:
369     try:
370     self.do_redraw()
371     except:
372     print "Error during drawing:", sys.exc_info()[0]
373     clear = True
374    
375     if clear:
376 bh 57 # If we've got no map or if the map is empty, simply clear
377     # the screen.
378 bh 246
379 bh 57 # XXX it's probably possible to get rid of this. The
380     # background color of the window is already white and the
381     # only thing we may have to do is to call self.Refresh()
382     # with a true argument in the right places.
383     dc.BeginDrawing()
384 bh 246 dc.Clear()
385 bh 57 dc.EndDrawing()
386 bh 6
387 jonathan 822 #wxEndBusyCursor()
388    
389 bh 6 def do_redraw(self):
390 bh 145 # This should only be called if we have a non-empty map.
391 bh 125
392 bh 145 # Get the window size.
393 bh 6 width, height = self.GetSizeTuple()
394    
395 bh 125 # If self.bitmap's still there, reuse it. Otherwise redraw it
396     if self.bitmap is not None:
397     bitmap = self.bitmap
398 bh 6 else:
399 bh 125 bitmap = wx.wxEmptyBitmap(width, height)
400     dc = wx.wxMemoryDC()
401     dc.SelectObject(bitmap)
402     dc.BeginDrawing()
403 bh 57
404 bh 125 # clear the background
405 jonathan 799 #dc.SetBrush(wx.wxWHITE_BRUSH)
406     #dc.SetPen(wx.wxTRANSPARENT_PEN)
407     #dc.DrawRectangle(0, 0, width, height)
408     dc.SetBackground(wx.wxWHITE_BRUSH)
409     dc.Clear()
410 bh 6
411 bh 535 selected_layer = self.selection.SelectedLayer()
412     selected_shapes = self.selection.SelectedShapes()
413 bh 57
414 bh 125 # draw the map into the bitmap
415     renderer = ScreenRenderer(dc, self.scale, self.offset)
416 bh 149
417 bh 296 # Pass the entire bitmap as update region to the renderer.
418 bh 149 # We're redrawing the whole bitmap, after all.
419     renderer.RenderMap(self.map, (0, 0, width, height),
420 bh 535 selected_layer, selected_shapes)
421 bh 125
422     dc.EndDrawing()
423     dc.SelectObject(wx.wxNullBitmap)
424     self.bitmap = bitmap
425    
426 bh 57 # blit the bitmap to the screen
427 bh 125 dc = wx.wxMemoryDC()
428     dc.SelectObject(bitmap)
429 bh 6 clientdc = wxClientDC(self)
430     clientdc.BeginDrawing()
431     clientdc.Blit(0, 0, width, height, dc, 0, 0)
432     clientdc.EndDrawing()
433    
434     def Print(self):
435     printer = wx.wxPrinter()
436     printout = MapPrintout(self.map)
437     printer.Print(self, printout, wx.true)
438     printout.Destroy()
439 bh 246
440 bh 6 def SetMap(self, map):
441 jonathan 565 redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
442 bh 6 LAYER_VISIBILITY_CHANGED)
443     if self.map is not None:
444     for channel in redraw_channels:
445 bh 125 self.map.Unsubscribe(channel, self.full_redraw)
446 bh 6 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
447     self.projection_changed)
448     self.map = map
449 bh 535 self.selection.ClearSelection()
450 bh 6 if self.map is not None:
451     for channel in redraw_channels:
452 bh 125 self.map.Subscribe(channel, self.full_redraw)
453 bh 6 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
454     self.FitMapToWindow()
455 bh 57 # force a redraw. If map is not empty, it's already been called
456     # by FitMapToWindow but if map is empty it hasn't been called
457     # yet so we have to explicitly call it.
458 bh 125 self.full_redraw()
459 bh 6
460     def Map(self):
461 bh 295 """Return the map displayed by this canvas"""
462 bh 6 return self.map
463    
464     def redraw(self, *args):
465     self.Refresh(0)
466    
467 bh 125 def full_redraw(self, *args):
468     self.bitmap = None
469     self.redraw()
470    
471 bh 6 def projection_changed(self, *args):
472     self.FitMapToWindow()
473 bh 125 self.full_redraw()
474 bh 6
475     def set_view_transform(self, scale, offset):
476     self.scale = scale
477     self.offset = offset
478 bh 125 self.full_redraw()
479 frank 855 self.issue(SCALE_CHANGED, scale)
480 bh 6
481     def proj_to_win(self, x, y):
482     """\
483     Return the point in window coords given by projected coordinates x y
484     """
485     offx, offy = self.offset
486     return (self.scale * x + offx, -self.scale * y + offy)
487    
488     def win_to_proj(self, x, y):
489     """\
490     Return the point in projected coordinates given by window coords x y
491     """
492     offx, offy = self.offset
493     return ((x - offx) / self.scale, (offy - y) / self.scale)
494    
495     def FitRectToWindow(self, rect):
496 bh 293 """Fit the rectangular region given by rect into the window.
497 bh 535
498 bh 293 Set scale so that rect (in projected coordinates) just fits into
499     the window and center it.
500     """
501 bh 6 width, height = self.GetSizeTuple()
502     llx, lly, urx, ury = rect
503 bh 45 if llx == urx or lly == ury:
504 jonathan 799 # zero width or zero height. Do Nothing
505 bh 45 return
506 bh 6 scalex = width / (urx - llx)
507     scaley = height / (ury - lly)
508     scale = min(scalex, scaley)
509     offx = 0.5 * (width - (urx + llx) * scale)
510     offy = 0.5 * (height + (ury + lly) * scale)
511     self.set_view_transform(scale, (offx, offy))
512    
513     def FitMapToWindow(self):
514 bh 293 """Fit the map to the window
515 bh 535
516 bh 293 Set the scale so that the map fits exactly into the window and
517     center it in the window.
518 bh 6 """
519     bbox = self.map.ProjectedBoundingBox()
520     if bbox is not None:
521     self.FitRectToWindow(bbox)
522    
523 jonathan 822 def FitLayerToWindow(self, layer):
524     """Fit the given layer to the window.
525    
526     Set the scale so that the layer fits exactly into the window and
527     center it in the window.
528     """
529    
530     bbox = layer.LatLongBoundingBox()
531     if bbox is not None:
532     proj = self.map.GetProjection()
533     if proj is not None:
534     bbox = proj.ForwardBBox(bbox)
535    
536 jonathan 824 if bbox is not None:
537     self.FitRectToWindow(bbox)
538    
539 jonathan 831 def FitSelectedToWindow(self):
540     layer = self.selection.SelectedLayer()
541     shapes = self.selection.SelectedShapes()
542    
543     bbox = layer.ShapesBoundingBox(shapes)
544     if bbox is not None:
545     proj = self.map.GetProjection()
546     if proj is not None:
547     bbox = proj.ForwardBBox(bbox)
548    
549     if bbox is not None:
550     self.FitRectToWindow(bbox)
551    
552 bh 57 def ZoomFactor(self, factor, center = None):
553     """Multiply the zoom by factor and center on center.
554    
555     The optional parameter center is a point in window coordinates
556     that should be centered. If it is omitted, it defaults to the
557     center of the window
558     """
559 bh 6 width, height = self.GetSizeTuple()
560     scale = self.scale * factor
561     offx, offy = self.offset
562 bh 57 if center is not None:
563     cx, cy = center
564     else:
565     cx = width / 2
566     cy = height / 2
567     offset = (factor * (offx - cx) + width / 2,
568     factor * (offy - cy) + height / 2)
569 bh 6 self.set_view_transform(scale, offset)
570    
571     def ZoomOutToRect(self, rect):
572 bh 293 """Zoom out to fit the currently visible region into rect.
573 bh 6
574 bh 293 The rect parameter is given in window coordinates
575     """
576 bh 6 # determine the bbox of the displayed region in projected
577     # coordinates
578     width, height = self.GetSizeTuple()
579     llx, lly = self.win_to_proj(0, height - 1)
580     urx, ury = self.win_to_proj(width - 1, 0)
581    
582     sx, sy, ex, ey = rect
583     scalex = (ex - sx) / (urx - llx)
584     scaley = (ey - sy) / (ury - lly)
585     scale = min(scalex, scaley)
586    
587     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
588     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
589     self.set_view_transform(scale, (offx, offy))
590    
591     def Translate(self, dx, dy):
592 bh 295 """Move the map by dx, dy pixels"""
593 bh 6 offx, offy = self.offset
594     self.set_view_transform(self.scale, (offx + dx, offy + dy))
595    
596 bh 356 def SelectTool(self, tool):
597     """Make tool the active tool.
598    
599     The parameter should be an instance of Tool or None to indicate
600     that no tool is active.
601     """
602     self.tool = tool
603    
604 bh 6 def ZoomInTool(self):
605 bh 295 """Start the zoom in tool"""
606 bh 356 self.SelectTool(ZoomInTool(self))
607 bh 6
608     def ZoomOutTool(self):
609 bh 295 """Start the zoom out tool"""
610 bh 356 self.SelectTool(ZoomOutTool(self))
611 bh 6
612     def PanTool(self):
613 bh 295 """Start the pan tool"""
614 bh 356 self.SelectTool(PanTool(self))
615 jonathan 822 #img = resource.GetImageResource("pan", wxBITMAP_TYPE_XPM)
616     #bmp = resource.GetBitmapResource("pan", wxBITMAP_TYPE_XPM)
617     #print bmp
618     #img = wxImageFromBitmap(bmp)
619     #print img
620     #cur = wxCursor(img)
621     #print cur
622     #self.SetCursor(cur)
623 bh 6
624     def IdentifyTool(self):
625 bh 295 """Start the identify tool"""
626 bh 356 self.SelectTool(IdentifyTool(self))
627 bh 6
628     def LabelTool(self):
629 bh 295 """Start the label tool"""
630 bh 356 self.SelectTool(LabelTool(self))
631 bh 6
632     def CurrentTool(self):
633 bh 295 """Return the name of the current tool or None if no tool is active"""
634 bh 6 return self.tool and self.tool.Name() or None
635    
636 bh 122 def CurrentPosition(self):
637     """Return current position of the mouse in projected coordinates.
638    
639     The result is a 2-tuple of floats with the coordinates. If the
640     mouse is not in the window, the result is None.
641     """
642     if self.current_position is not None:
643     x, y = self.current_position
644     return self.win_to_proj(x, y)
645     else:
646     return None
647    
648     def set_current_position(self, event):
649     """Set the current position from event
650    
651     Should be called by all events that contain mouse positions
652     especially EVT_MOTION. The event paramete may be None to
653     indicate the the pointer left the window.
654     """
655     if event is not None:
656     self.current_position = (event.m_x, event.m_y)
657     else:
658     self.current_position = None
659     self.issue(VIEW_POSITION)
660    
661 bh 6 def OnLeftDown(self, event):
662 bh 122 self.set_current_position(event)
663 bh 6 if self.tool is not None:
664     self.drag_dc = wxClientDC(self)
665     self.drag_dc.SetLogicalFunction(wxINVERT)
666     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
667     self.CaptureMouse()
668     self.tool.MouseDown(event)
669     self.tool.Show(self.drag_dc)
670     self.dragging = 1
671 bh 246
672 bh 6 def OnLeftUp(self, event):
673 bh 122 self.set_current_position(event)
674 bh 6 if self.dragging:
675 bh 261 self.ReleaseMouse()
676 bh 404 try:
677     self.tool.Hide(self.drag_dc)
678     self.tool.MouseUp(event)
679     finally:
680     self.drag_dc = None
681     self.dragging = 0
682 bh 6
683     def OnMotion(self, event):
684 bh 122 self.set_current_position(event)
685 bh 6 if self.dragging:
686     self.tool.Hide(self.drag_dc)
687     self.tool.MouseMove(event)
688     self.tool.Show(self.drag_dc)
689    
690 bh 122 def OnLeaveWindow(self, event):
691     self.set_current_position(None)
692    
693 bh 125 def OnSize(self, event):
694     # the window's size has changed. We have to get a new bitmap. If
695     # we want to be clever we could try to get by without throwing
696     # everything away. E.g. when the window gets smaller, we could
697     # either keep the bitmap or create the new one from the old one.
698     # Even when the window becomes larger some parts of the bitmap
699     # could be reused.
700     self.full_redraw()
701 jonathan 799 pass
702 bh 125
703 bh 6 def shape_selected(self, layer, shape):
704 bh 535 """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
705     # The selection object takes care that it only issues
706     # SHAPES_SELECTED messages when the set of selected shapes has
707     # actually changed, so we can do a full redraw unconditionally.
708     # FIXME: We should perhaps try to limit the redraw to the are
709     # actually covered by the shapes before and after the selection
710     # change.
711     self.full_redraw()
712 bh 6
713 bh 301 def unprojected_rect_around_point(self, x, y, dist):
714     """return a rect dist pixels around (x, y) in unprojected corrdinates
715 bh 159
716     The return value is a tuple (minx, miny, maxx, maxy) suitable a
717     parameter to a layer's ShapesInRegion method.
718     """
719     map_proj = self.map.projection
720     if map_proj is not None:
721     inverse = map_proj.Inverse
722     else:
723     inverse = None
724    
725     xs = []
726     ys = []
727     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
728 bh 301 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
729 bh 159 if inverse:
730     px, py = inverse(px, py)
731     xs.append(px)
732     ys.append(py)
733     return (min(xs), min(ys), max(xs), max(ys))
734    
735 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
736 bh 43 """Determine the shape at point px, py in window coords
737    
738     Return the shape and the corresponding layer as a tuple (layer,
739     shape).
740    
741     If the optional parameter select_labels is true (default false)
742     search through the labels. If a label is found return it's index
743     as the shape and None as the layer.
744    
745 bh 246 If the optional parameter searched_layer is given (or not None
746     which it defaults to), only search in that layer.
747 bh 43 """
748 bh 6 map_proj = self.map.projection
749     if map_proj is not None:
750     forward = map_proj.Forward
751     else:
752     forward = None
753    
754     scale = self.scale
755     offx, offy = self.offset
756    
757     if select_labels:
758     labels = self.map.LabelLayer().Labels()
759 bh 246
760 bh 6 if labels:
761     dc = wxClientDC(self)
762     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
763     dc.SetFont(font)
764 bh 60 for i in range(len(labels) - 1, -1, -1):
765 bh 6 label = labels[i]
766     x = label.x
767     y = label.y
768     text = label.text
769     if forward:
770     x, y = forward(x, y)
771     x = x * scale + offx
772     y = -y * scale + offy
773     width, height = dc.GetTextExtent(text)
774     if label.halign == ALIGN_LEFT:
775     # nothing to be done
776     pass
777     elif label.halign == ALIGN_RIGHT:
778     x = x - width
779     elif label.halign == ALIGN_CENTER:
780     x = x - width/2
781     if label.valign == ALIGN_TOP:
782     # nothing to be done
783     pass
784     elif label.valign == ALIGN_BOTTOM:
785     y = y - height
786     elif label.valign == ALIGN_CENTER:
787     y = y - height/2
788     if x <= px < x + width and y <= py <= y + height:
789     return None, i
790 bh 43
791 bh 246 if searched_layer:
792     layers = [searched_layer]
793 bh 43 else:
794     layers = self.map.Layers()
795    
796 bh 6 for layer_index in range(len(layers) - 1, -1, -1):
797     layer = layers[layer_index]
798    
799     # search only in visible layers
800     if not layer.Visible():
801     continue
802    
803 jonathan 433 filled = layer.GetClassification().GetDefaultFill() \
804 jonathan 610 is not Color.Transparent
805 jonathan 469 stroked = layer.GetClassification().GetDefaultLineColor() \
806 jonathan 610 is not Color.Transparent
807 bh 246
808 bh 6 layer_proj = layer.projection
809     if layer_proj is not None:
810     inverse = layer_proj.Inverse
811     else:
812     inverse = None
813 bh 246
814 bh 6 shapetype = layer.ShapeType()
815    
816     select_shape = -1
817 bh 159
818 bh 301 # Determine the ids of the shapes that overlap a tiny area
819     # around the point. For layers containing points we have to
820     # choose a larger size of the box we're testing agains so
821     # that we take the size of the markers into account
822     # FIXME: Once the markers are more flexible this part has to
823     # become more flexible too, of course
824     if shapetype == SHAPETYPE_POINT:
825     box = self.unprojected_rect_around_point(px, py, 5)
826     else:
827     box = self.unprojected_rect_around_point(px, py, 1)
828 bh 159 shape_ids = layer.ShapesInRegion(box)
829     shape_ids.reverse()
830    
831 bh 6 if shapetype == SHAPETYPE_POLYGON:
832 bh 159 for i in shape_ids:
833 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
834     i,
835     filled, stroked,
836     map_proj, layer_proj,
837     scale, -scale, offx, offy,
838     px, py)
839     if result:
840     select_shape = i
841     break
842     elif shapetype == SHAPETYPE_ARC:
843 bh 159 for i in shape_ids:
844 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
845     i, 0, 1,
846     map_proj, layer_proj,
847     scale, -scale, offx, offy,
848     px, py)
849     if result < 0:
850     select_shape = i
851     break
852     elif shapetype == SHAPETYPE_POINT:
853 bh 159 for i in shape_ids:
854 bh 6 shape = layer.Shape(i)
855     x, y = shape.Points()[0]
856     if inverse:
857     x, y = inverse(x, y)
858     if forward:
859     x, y = forward(x, y)
860     x = x * scale + offx
861     y = -y * scale + offy
862     if hypot(px - x, py - y) < 5:
863     select_shape = i
864     break
865    
866     if select_shape >= 0:
867     return layer, select_shape
868     return None, None
869    
870 bh 246 def SelectShapeAt(self, x, y, layer = None):
871     """\
872     Select and return the shape and its layer at window position (x, y)
873    
874     If layer is given, only search in that layer. If no layer is
875     given, search through all layers.
876    
877     Return a tuple (layer, shapeid). If no shape is found, return
878     (None, None).
879     """
880     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
881 bh 43 # If layer is None, then shape will also be None. We don't want
882     # to deselect the currently selected layer, so we simply select
883     # the already selected layer again.
884     if layer is None:
885 bh 535 layer = self.selection.SelectedLayer()
886     shapes = []
887     else:
888     shapes = [shape]
889     self.selection.SelectShapes(layer, shapes)
890 bh 246 return result
891 bh 6
892     def LabelShapeAt(self, x, y):
893 bh 295 """Add or remove a label at window position x, y.
894    
895     If there's a label at the given position, remove it. Otherwise
896     determine the shape at the position, run the label dialog and
897     unless the user cancels the dialog, add a laber.
898     """
899 bh 6 ox = x; oy = y
900     label_layer = self.map.LabelLayer()
901     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
902     if layer is None and shape_index is not None:
903     # a label was selected
904     label_layer.RemoveLabel(shape_index)
905     elif layer is not None:
906     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
907     if text:
908     proj = self.map.projection
909     if proj is not None:
910     map_proj = proj
911     else:
912     map_proj = None
913     proj = layer.projection
914     if proj is not None:
915     layer_proj = proj
916     else:
917     layer_proj = None
918    
919     shapetype = layer.ShapeType()
920     if shapetype == SHAPETYPE_POLYGON:
921     x, y = shape_centroid(layer.shapefile.cobject(),
922     shape_index,
923     map_proj, layer_proj, 1, 1, 0, 0)
924     if map_proj is not None:
925     x, y = map_proj.Inverse(x, y)
926     else:
927     shape = layer.Shape(shape_index)
928     if shapetype == SHAPETYPE_POINT:
929     x, y = shape.Points()[0]
930     else:
931     # assume SHAPETYPE_ARC
932     points = shape.Points()
933     x, y = points[len(points) / 2]
934     if layer_proj is not None:
935     x, y = layer_proj.Inverse(x, y)
936     if shapetype == SHAPETYPE_POINT:
937     halign = ALIGN_LEFT
938     valign = ALIGN_CENTER
939     elif shapetype == SHAPETYPE_POLYGON:
940     halign = ALIGN_CENTER
941     valign = ALIGN_CENTER
942     elif shapetype == SHAPETYPE_ARC:
943     halign = ALIGN_LEFT
944     valign = ALIGN_CENTER
945     label_layer.AddLabel(x, y, text,
946     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