/[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 404 - (hide annotations)
Fri Feb 14 17:40:26 2003 UTC (22 years ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 28598 byte(s)
(MapCanvas.OnLeftUp): Make sure that the
dragging flag is always set to 0 even when the tool implementation
raises an exception

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