/[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 288 - (hide annotations)
Thu Aug 29 13:31:43 2002 UTC (22 years, 6 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 27693 byte(s)
(ZoomInTool.MouseUp, ZoomOutTool.MouseUp):
Handle degenrate rectangles.

1 bh 78 # Copyright (c) 2001, 2002 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     # If true, OnIdle will call do_redraw to do the actual
283     # redrawing. Set by OnPaint to avoid some unnecessary redraws.
284     # To force a redraw call full_redraw().
285 bh 6 self.redraw_on_idle = 0
286 bh 125
287 bh 145 # The region to update when idle
288     self.update_region = wx.wxRegion()
289    
290 bh 125 # the bitmap serving as backing store
291     self.bitmap = None
292    
293     # the interactor
294     self.interactor = interactor
295     self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)
296    
297 bh 174 # keep track of which layers/shapes are selected to make sure we
298     # only redraw when necessary
299     self.last_selected_layer = None
300     self.last_selected_shape = None
301    
302 bh 125 # subscribe the WX events we're interested in
303 bh 6 EVT_PAINT(self, self.OnPaint)
304     EVT_LEFT_DOWN(self, self.OnLeftDown)
305     EVT_LEFT_UP(self, self.OnLeftUp)
306     EVT_MOTION(self, self.OnMotion)
307 bh 122 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
308 bh 125 wx.EVT_SIZE(self, self.OnSize)
309 bh 6 wx.EVT_IDLE(self, self.OnIdle)
310    
311 bh 122 def __del__(self):
312     wxWindow.__del__(self)
313     Publisher.__del__(self)
314    
315 bh 6 def OnPaint(self, event):
316     dc = wxPaintDC(self)
317 bh 57 if self.map is not None and self.map.HasLayers():
318     # We have a non-empty map. Redraw it in idle time
319     self.redraw_on_idle = 1
320 bh 145 # update the region that has to be redrawn
321     self.update_region.UnionRegion(self.GetUpdateRegion())
322 bh 57 else:
323     # If we've got no map or if the map is empty, simply clear
324     # the screen.
325 bh 246
326 bh 57 # XXX it's probably possible to get rid of this. The
327     # background color of the window is already white and the
328     # only thing we may have to do is to call self.Refresh()
329     # with a true argument in the right places.
330     dc.BeginDrawing()
331 bh 246 dc.Clear()
332 bh 57 dc.EndDrawing()
333 bh 6
334 bh 145 # clear the region
335     self.update_region = wx.wxRegion()
336    
337 bh 6 def do_redraw(self):
338 bh 145 # This should only be called if we have a non-empty map.
339 bh 125
340 bh 149 # get the update region and reset it. We're not actually using
341     # it anymore, though.
342 bh 145 update_box = self.update_region.GetBox()
343     self.update_region = wx.wxRegion()
344    
345     # Get the window size.
346 bh 6 width, height = self.GetSizeTuple()
347    
348 bh 125 # If self.bitmap's still there, reuse it. Otherwise redraw it
349     if self.bitmap is not None:
350     bitmap = self.bitmap
351 bh 6 else:
352 bh 125 bitmap = wx.wxEmptyBitmap(width, height)
353     dc = wx.wxMemoryDC()
354     dc.SelectObject(bitmap)
355     dc.BeginDrawing()
356 bh 57
357 bh 125 # clear the background
358     dc.SetBrush(wx.wxWHITE_BRUSH)
359     dc.SetPen(wx.wxTRANSPARENT_PEN)
360     dc.DrawRectangle(0, 0, width, height)
361 bh 6
362 bh 125 if 1: #self.interactor.selected_map is self.map:
363     selected_layer = self.interactor.selected_layer
364     selected_shape = self.interactor.selected_shape
365     else:
366     selected_layer = None
367     selected_shape = None
368 bh 57
369 bh 125 # draw the map into the bitmap
370     renderer = ScreenRenderer(dc, self.scale, self.offset)
371 bh 149
372     # Pass the entire bitmap as update_region to the renderer.
373     # We're redrawing the whole bitmap, after all.
374     renderer.RenderMap(self.map, (0, 0, width, height),
375 bh 145 selected_layer, selected_shape)
376 bh 125
377     dc.EndDrawing()
378     dc.SelectObject(wx.wxNullBitmap)
379     self.bitmap = bitmap
380    
381 bh 57 # blit the bitmap to the screen
382 bh 125 dc = wx.wxMemoryDC()
383     dc.SelectObject(bitmap)
384 bh 6 clientdc = wxClientDC(self)
385     clientdc.BeginDrawing()
386     clientdc.Blit(0, 0, width, height, dc, 0, 0)
387     clientdc.EndDrawing()
388    
389     def Print(self):
390     printer = wx.wxPrinter()
391     printout = MapPrintout(self.map)
392     printer.Print(self, printout, wx.true)
393     printout.Destroy()
394 bh 246
395 bh 6 def SetMap(self, map):
396     redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,
397     LAYER_VISIBILITY_CHANGED)
398     if self.map is not None:
399     for channel in redraw_channels:
400 bh 125 self.map.Unsubscribe(channel, self.full_redraw)
401 bh 6 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
402     self.projection_changed)
403     self.map = map
404     if self.map is not None:
405     for channel in redraw_channels:
406 bh 125 self.map.Subscribe(channel, self.full_redraw)
407 bh 6 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
408     self.FitMapToWindow()
409 bh 57 # force a redraw. If map is not empty, it's already been called
410     # by FitMapToWindow but if map is empty it hasn't been called
411     # yet so we have to explicitly call it.
412 bh 125 self.full_redraw()
413 bh 6
414     def Map(self):
415     return self.map
416    
417     def redraw(self, *args):
418     self.Refresh(0)
419    
420 bh 125 def full_redraw(self, *args):
421     self.bitmap = None
422     self.redraw()
423    
424 bh 6 def projection_changed(self, *args):
425     self.FitMapToWindow()
426 bh 125 self.full_redraw()
427 bh 6
428     def set_view_transform(self, scale, offset):
429     self.scale = scale
430     self.offset = offset
431 bh 125 self.full_redraw()
432 bh 6
433     def proj_to_win(self, x, y):
434     """\
435     Return the point in window coords given by projected coordinates x y
436     """
437     offx, offy = self.offset
438     return (self.scale * x + offx, -self.scale * y + offy)
439    
440     def win_to_proj(self, x, y):
441     """\
442     Return the point in projected coordinates given by window coords x y
443     """
444     offx, offy = self.offset
445     return ((x - offx) / self.scale, (offy - y) / self.scale)
446    
447     def FitRectToWindow(self, rect):
448     width, height = self.GetSizeTuple()
449     llx, lly, urx, ury = rect
450 bh 45 if llx == urx or lly == ury:
451     # zero with or zero height. Do Nothing
452     return
453 bh 6 scalex = width / (urx - llx)
454     scaley = height / (ury - lly)
455     scale = min(scalex, scaley)
456     offx = 0.5 * (width - (urx + llx) * scale)
457     offy = 0.5 * (height + (ury + lly) * scale)
458     self.set_view_transform(scale, (offx, offy))
459    
460     def FitMapToWindow(self):
461     """\
462     Set the scale and offset so that the map is centered in the
463     window
464     """
465     bbox = self.map.ProjectedBoundingBox()
466     if bbox is not None:
467     self.FitRectToWindow(bbox)
468    
469 bh 57 def ZoomFactor(self, factor, center = None):
470     """Multiply the zoom by factor and center on center.
471    
472     The optional parameter center is a point in window coordinates
473     that should be centered. If it is omitted, it defaults to the
474     center of the window
475     """
476 bh 6 width, height = self.GetSizeTuple()
477     scale = self.scale * factor
478     offx, offy = self.offset
479 bh 57 if center is not None:
480     cx, cy = center
481     else:
482     cx = width / 2
483     cy = height / 2
484     offset = (factor * (offx - cx) + width / 2,
485     factor * (offy - cy) + height / 2)
486 bh 6 self.set_view_transform(scale, offset)
487    
488     def ZoomOutToRect(self, rect):
489     # rect is given in window coordinates
490    
491     # determine the bbox of the displayed region in projected
492     # coordinates
493     width, height = self.GetSizeTuple()
494     llx, lly = self.win_to_proj(0, height - 1)
495     urx, ury = self.win_to_proj(width - 1, 0)
496    
497     sx, sy, ex, ey = rect
498     scalex = (ex - sx) / (urx - llx)
499     scaley = (ey - sy) / (ury - lly)
500     scale = min(scalex, scaley)
501    
502     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
503     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
504     self.set_view_transform(scale, (offx, offy))
505    
506     def Translate(self, dx, dy):
507     offx, offy = self.offset
508     self.set_view_transform(self.scale, (offx + dx, offy + dy))
509    
510     def ZoomInTool(self):
511     self.tool = ZoomInTool(self)
512    
513     def ZoomOutTool(self):
514     self.tool = ZoomOutTool(self)
515    
516     def PanTool(self):
517     self.tool = PanTool(self)
518    
519     def IdentifyTool(self):
520     self.tool = IdentifyTool(self)
521    
522     def LabelTool(self):
523     self.tool = LabelTool(self)
524    
525     def CurrentTool(self):
526     return self.tool and self.tool.Name() or None
527    
528 bh 122 def CurrentPosition(self):
529     """Return current position of the mouse in projected coordinates.
530    
531     The result is a 2-tuple of floats with the coordinates. If the
532     mouse is not in the window, the result is None.
533     """
534     if self.current_position is not None:
535     x, y = self.current_position
536     return self.win_to_proj(x, y)
537     else:
538     return None
539    
540     def set_current_position(self, event):
541     """Set the current position from event
542    
543     Should be called by all events that contain mouse positions
544     especially EVT_MOTION. The event paramete may be None to
545     indicate the the pointer left the window.
546     """
547     if event is not None:
548     self.current_position = (event.m_x, event.m_y)
549     else:
550     self.current_position = None
551     self.issue(VIEW_POSITION)
552    
553 bh 6 def OnLeftDown(self, event):
554 bh 122 self.set_current_position(event)
555 bh 6 if self.tool is not None:
556     self.drag_dc = wxClientDC(self)
557     self.drag_dc.SetLogicalFunction(wxINVERT)
558     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
559     self.CaptureMouse()
560     self.tool.MouseDown(event)
561     self.tool.Show(self.drag_dc)
562     self.dragging = 1
563 bh 246
564 bh 6 def OnLeftUp(self, event):
565 bh 122 self.set_current_position(event)
566 bh 6 if self.dragging:
567 bh 261 self.ReleaseMouse()
568 bh 6 self.tool.Hide(self.drag_dc)
569     self.tool.MouseUp(event)
570     self.drag_dc = None
571     self.dragging = 0
572    
573     def OnMotion(self, event):
574 bh 122 self.set_current_position(event)
575 bh 6 if self.dragging:
576     self.tool.Hide(self.drag_dc)
577     self.tool.MouseMove(event)
578     self.tool.Show(self.drag_dc)
579    
580 bh 122 def OnLeaveWindow(self, event):
581     self.set_current_position(None)
582    
583 bh 6 def OnIdle(self, event):
584     if self.redraw_on_idle:
585     self.do_redraw()
586     self.redraw_on_idle = 0
587    
588 bh 125 def OnSize(self, event):
589     # the window's size has changed. We have to get a new bitmap. If
590     # we want to be clever we could try to get by without throwing
591     # everything away. E.g. when the window gets smaller, we could
592     # either keep the bitmap or create the new one from the old one.
593     # Even when the window becomes larger some parts of the bitmap
594     # could be reused.
595     self.full_redraw()
596    
597 bh 6 def shape_selected(self, layer, shape):
598 bh 174 """Redraw the map.
599 bh 6
600 bh 174 Receiver for the SELECTED_SHAPE messages. Try to redraw only
601     when necessary.
602     """
603     # A redraw is necessary when the display has to change, which
604     # means that either the status changes from having no selection
605     # to having a selection shape or vice versa, or when the fact
606     # whether there is a selection at all doesn't change, when the
607     # shape which is selected has changed (which means that layer or
608     # shapeid changes).
609     if ((shape is not None or self.last_selected_shape is not None)
610     and (shape != self.last_selected_shape
611     or layer != self.last_selected_layer)):
612     self.full_redraw()
613 bh 176
614     # remember the selection so we can compare when it changes again.
615 bh 174 self.last_selected_layer = layer
616     self.last_selected_shape = shape
617    
618 bh 159 def unprojected_rect_around_point(self, x, y):
619     """return a rect a few pixels around (x, y) in unprojected corrdinates
620    
621     The return value is a tuple (minx, miny, maxx, maxy) suitable a
622     parameter to a layer's ShapesInRegion method.
623     """
624     map_proj = self.map.projection
625     if map_proj is not None:
626     inverse = map_proj.Inverse
627     else:
628     inverse = None
629    
630     xs = []
631     ys = []
632     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
633     px, py = self.win_to_proj(x + dx, y + dy)
634     if inverse:
635     px, py = inverse(px, py)
636     xs.append(px)
637     ys.append(py)
638     return (min(xs), min(ys), max(xs), max(ys))
639    
640 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
641 bh 43 """Determine the shape at point px, py in window coords
642    
643     Return the shape and the corresponding layer as a tuple (layer,
644     shape).
645    
646     If the optional parameter select_labels is true (default false)
647     search through the labels. If a label is found return it's index
648     as the shape and None as the layer.
649    
650 bh 246 If the optional parameter searched_layer is given (or not None
651     which it defaults to), only search in that layer.
652 bh 43 """
653 bh 6 map_proj = self.map.projection
654     if map_proj is not None:
655     forward = map_proj.Forward
656     else:
657     forward = None
658    
659     scale = self.scale
660     offx, offy = self.offset
661    
662 bh 159 box = self.unprojected_rect_around_point(px, py)
663    
664 bh 6 if select_labels:
665     labels = self.map.LabelLayer().Labels()
666 bh 246
667 bh 6 if labels:
668     dc = wxClientDC(self)
669     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
670     dc.SetFont(font)
671 bh 60 for i in range(len(labels) - 1, -1, -1):
672 bh 6 label = labels[i]
673     x = label.x
674     y = label.y
675     text = label.text
676     if forward:
677     x, y = forward(x, y)
678     x = x * scale + offx
679     y = -y * scale + offy
680     width, height = dc.GetTextExtent(text)
681     if label.halign == ALIGN_LEFT:
682     # nothing to be done
683     pass
684     elif label.halign == ALIGN_RIGHT:
685     x = x - width
686     elif label.halign == ALIGN_CENTER:
687     x = x - width/2
688     if label.valign == ALIGN_TOP:
689     # nothing to be done
690     pass
691     elif label.valign == ALIGN_BOTTOM:
692     y = y - height
693     elif label.valign == ALIGN_CENTER:
694     y = y - height/2
695     if x <= px < x + width and y <= py <= y + height:
696     return None, i
697 bh 43
698 bh 246 if searched_layer:
699     layers = [searched_layer]
700 bh 43 else:
701     layers = self.map.Layers()
702    
703 bh 6 for layer_index in range(len(layers) - 1, -1, -1):
704     layer = layers[layer_index]
705    
706     # search only in visible layers
707     if not layer.Visible():
708     continue
709    
710     filled = layer.fill is not None
711     stroked = layer.stroke is not None
712 bh 246
713 bh 6 layer_proj = layer.projection
714     if layer_proj is not None:
715     inverse = layer_proj.Inverse
716     else:
717     inverse = None
718 bh 246
719 bh 6 shapetype = layer.ShapeType()
720    
721     select_shape = -1
722 bh 159
723     shape_ids = layer.ShapesInRegion(box)
724     shape_ids.reverse()
725    
726 bh 6 if shapetype == SHAPETYPE_POLYGON:
727 bh 159 for i in shape_ids:
728 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
729     i,
730     filled, stroked,
731     map_proj, layer_proj,
732     scale, -scale, offx, offy,
733     px, py)
734     if result:
735     select_shape = i
736     break
737     elif shapetype == SHAPETYPE_ARC:
738 bh 159 for i in shape_ids:
739 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
740     i, 0, 1,
741     map_proj, layer_proj,
742     scale, -scale, offx, offy,
743     px, py)
744     if result < 0:
745     select_shape = i
746     break
747     elif shapetype == SHAPETYPE_POINT:
748 bh 159 for i in shape_ids:
749 bh 6 shape = layer.Shape(i)
750     x, y = shape.Points()[0]
751     if inverse:
752     x, y = inverse(x, y)
753     if forward:
754     x, y = forward(x, y)
755     x = x * scale + offx
756     y = -y * scale + offy
757     if hypot(px - x, py - y) < 5:
758     select_shape = i
759     break
760    
761     if select_shape >= 0:
762     return layer, select_shape
763     return None, None
764    
765 bh 246 def SelectShapeAt(self, x, y, layer = None):
766     """\
767     Select and return the shape and its layer at window position (x, y)
768    
769     If layer is given, only search in that layer. If no layer is
770     given, search through all layers.
771    
772     Return a tuple (layer, shapeid). If no shape is found, return
773     (None, None).
774     """
775     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
776 bh 43 # If layer is None, then shape will also be None. We don't want
777     # to deselect the currently selected layer, so we simply select
778     # the already selected layer again.
779     if layer is None:
780     layer = self.interactor.SelectedLayer()
781 bh 6 self.interactor.SelectLayerAndShape(layer, shape)
782 bh 246 return result
783 bh 6
784     def LabelShapeAt(self, x, y):
785     ox = x; oy = y
786     label_layer = self.map.LabelLayer()
787     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
788     if layer is None and shape_index is not None:
789     # a label was selected
790     label_layer.RemoveLabel(shape_index)
791     elif layer is not None:
792     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
793     if text:
794     proj = self.map.projection
795     if proj is not None:
796     map_proj = proj
797     else:
798     map_proj = None
799     proj = layer.projection
800     if proj is not None:
801     layer_proj = proj
802     else:
803     layer_proj = None
804    
805     shapetype = layer.ShapeType()
806     if shapetype == SHAPETYPE_POLYGON:
807     x, y = shape_centroid(layer.shapefile.cobject(),
808     shape_index,
809     map_proj, layer_proj, 1, 1, 0, 0)
810     if map_proj is not None:
811     x, y = map_proj.Inverse(x, y)
812     else:
813     shape = layer.Shape(shape_index)
814     if shapetype == SHAPETYPE_POINT:
815     x, y = shape.Points()[0]
816     else:
817     # assume SHAPETYPE_ARC
818     points = shape.Points()
819     x, y = points[len(points) / 2]
820     if layer_proj is not None:
821     x, y = layer_proj.Inverse(x, y)
822     if shapetype == SHAPETYPE_POINT:
823     halign = ALIGN_LEFT
824     valign = ALIGN_CENTER
825     elif shapetype == SHAPETYPE_POLYGON:
826     halign = ALIGN_CENTER
827     valign = ALIGN_CENTER
828     elif shapetype == SHAPETYPE_ARC:
829     halign = ALIGN_LEFT
830     valign = ALIGN_CENTER
831     label_layer.AddLabel(x, y, text,
832     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