/[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 831 - (hide annotations)
Tue May 6 12:07:21 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: 31973 byte(s)
(MapCanvas): Added delegated method HasSelectedShapes.
(MapCanvas.FitSelectedToWindow): New. Centers and scales any selected
        shapes on the canvas using the map projection (if any).

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