/[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 303 - (hide annotations)
Mon Sep 2 16:47:53 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: 28296 byte(s)
* Thuban/UI/view.py: Get rid of the idle redraw. This is done by
wxWindows already and our implementation doesn't work correctly
with wxGTK 2.3:
(MapCanvas.__init__): Remove the instance variable
(MapCanvas.OnPaint): Always call do_redraw when there's a map to
be drawin
(MapCanvas.OnIdle): Removed.

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     # 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     def ZoomInTool(self):
501 bh 295 """Start the zoom in tool"""
502 bh 6 self.tool = ZoomInTool(self)
503    
504     def ZoomOutTool(self):
505 bh 295 """Start the zoom out tool"""
506 bh 6 self.tool = ZoomOutTool(self)
507    
508     def PanTool(self):
509 bh 295 """Start the pan tool"""
510 bh 6 self.tool = PanTool(self)
511    
512     def IdentifyTool(self):
513 bh 295 """Start the identify tool"""
514 bh 6 self.tool = IdentifyTool(self)
515    
516     def LabelTool(self):
517 bh 295 """Start the label tool"""
518 bh 6 self.tool = LabelTool(self)
519    
520     def CurrentTool(self):
521 bh 295 """Return the name of the current tool or None if no tool is active"""
522 bh 6 return self.tool and self.tool.Name() or None
523    
524 bh 122 def CurrentPosition(self):
525     """Return current position of the mouse in projected coordinates.
526    
527     The result is a 2-tuple of floats with the coordinates. If the
528     mouse is not in the window, the result is None.
529     """
530     if self.current_position is not None:
531     x, y = self.current_position
532     return self.win_to_proj(x, y)
533     else:
534     return None
535    
536     def set_current_position(self, event):
537     """Set the current position from event
538    
539     Should be called by all events that contain mouse positions
540     especially EVT_MOTION. The event paramete may be None to
541     indicate the the pointer left the window.
542     """
543     if event is not None:
544     self.current_position = (event.m_x, event.m_y)
545     else:
546     self.current_position = None
547     self.issue(VIEW_POSITION)
548    
549 bh 6 def OnLeftDown(self, event):
550 bh 122 self.set_current_position(event)
551 bh 6 if self.tool is not None:
552     self.drag_dc = wxClientDC(self)
553     self.drag_dc.SetLogicalFunction(wxINVERT)
554     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
555     self.CaptureMouse()
556     self.tool.MouseDown(event)
557     self.tool.Show(self.drag_dc)
558     self.dragging = 1
559 bh 246
560 bh 6 def OnLeftUp(self, event):
561 bh 122 self.set_current_position(event)
562 bh 6 if self.dragging:
563 bh 261 self.ReleaseMouse()
564 bh 6 self.tool.Hide(self.drag_dc)
565     self.tool.MouseUp(event)
566     self.drag_dc = None
567     self.dragging = 0
568    
569     def OnMotion(self, event):
570 bh 122 self.set_current_position(event)
571 bh 6 if self.dragging:
572     self.tool.Hide(self.drag_dc)
573     self.tool.MouseMove(event)
574     self.tool.Show(self.drag_dc)
575    
576 bh 122 def OnLeaveWindow(self, event):
577     self.set_current_position(None)
578    
579 bh 125 def OnSize(self, event):
580     # the window's size has changed. We have to get a new bitmap. If
581     # we want to be clever we could try to get by without throwing
582     # everything away. E.g. when the window gets smaller, we could
583     # either keep the bitmap or create the new one from the old one.
584     # Even when the window becomes larger some parts of the bitmap
585     # could be reused.
586     self.full_redraw()
587    
588 bh 6 def shape_selected(self, layer, shape):
589 bh 174 """Redraw the map.
590 bh 6
591 bh 174 Receiver for the SELECTED_SHAPE messages. Try to redraw only
592     when necessary.
593     """
594     # A redraw is necessary when the display has to change, which
595     # means that either the status changes from having no selection
596     # to having a selection shape or vice versa, or when the fact
597     # whether there is a selection at all doesn't change, when the
598     # shape which is selected has changed (which means that layer or
599     # shapeid changes).
600     if ((shape is not None or self.last_selected_shape is not None)
601     and (shape != self.last_selected_shape
602     or layer != self.last_selected_layer)):
603     self.full_redraw()
604 bh 176
605     # remember the selection so we can compare when it changes again.
606 bh 174 self.last_selected_layer = layer
607     self.last_selected_shape = shape
608    
609 bh 301 def unprojected_rect_around_point(self, x, y, dist):
610     """return a rect dist pixels around (x, y) in unprojected corrdinates
611 bh 159
612     The return value is a tuple (minx, miny, maxx, maxy) suitable a
613     parameter to a layer's ShapesInRegion method.
614     """
615     map_proj = self.map.projection
616     if map_proj is not None:
617     inverse = map_proj.Inverse
618     else:
619     inverse = None
620    
621     xs = []
622     ys = []
623     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
624 bh 301 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
625 bh 159 if inverse:
626     px, py = inverse(px, py)
627     xs.append(px)
628     ys.append(py)
629     return (min(xs), min(ys), max(xs), max(ys))
630    
631 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
632 bh 43 """Determine the shape at point px, py in window coords
633    
634     Return the shape and the corresponding layer as a tuple (layer,
635     shape).
636    
637     If the optional parameter select_labels is true (default false)
638     search through the labels. If a label is found return it's index
639     as the shape and None as the layer.
640    
641 bh 246 If the optional parameter searched_layer is given (or not None
642     which it defaults to), only search in that layer.
643 bh 43 """
644 bh 6 map_proj = self.map.projection
645     if map_proj is not None:
646     forward = map_proj.Forward
647     else:
648     forward = None
649    
650     scale = self.scale
651     offx, offy = self.offset
652    
653     if select_labels:
654     labels = self.map.LabelLayer().Labels()
655 bh 246
656 bh 6 if labels:
657     dc = wxClientDC(self)
658     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
659     dc.SetFont(font)
660 bh 60 for i in range(len(labels) - 1, -1, -1):
661 bh 6 label = labels[i]
662     x = label.x
663     y = label.y
664     text = label.text
665     if forward:
666     x, y = forward(x, y)
667     x = x * scale + offx
668     y = -y * scale + offy
669     width, height = dc.GetTextExtent(text)
670     if label.halign == ALIGN_LEFT:
671     # nothing to be done
672     pass
673     elif label.halign == ALIGN_RIGHT:
674     x = x - width
675     elif label.halign == ALIGN_CENTER:
676     x = x - width/2
677     if label.valign == ALIGN_TOP:
678     # nothing to be done
679     pass
680     elif label.valign == ALIGN_BOTTOM:
681     y = y - height
682     elif label.valign == ALIGN_CENTER:
683     y = y - height/2
684     if x <= px < x + width and y <= py <= y + height:
685     return None, i
686 bh 43
687 bh 246 if searched_layer:
688     layers = [searched_layer]
689 bh 43 else:
690     layers = self.map.Layers()
691    
692 bh 6 for layer_index in range(len(layers) - 1, -1, -1):
693     layer = layers[layer_index]
694    
695     # search only in visible layers
696     if not layer.Visible():
697     continue
698    
699     filled = layer.fill is not None
700     stroked = layer.stroke is not None
701 bh 246
702 bh 6 layer_proj = layer.projection
703     if layer_proj is not None:
704     inverse = layer_proj.Inverse
705     else:
706     inverse = None
707 bh 246
708 bh 6 shapetype = layer.ShapeType()
709    
710     select_shape = -1
711 bh 159
712 bh 301 # Determine the ids of the shapes that overlap a tiny area
713     # around the point. For layers containing points we have to
714     # choose a larger size of the box we're testing agains so
715     # that we take the size of the markers into account
716     # FIXME: Once the markers are more flexible this part has to
717     # become more flexible too, of course
718     if shapetype == SHAPETYPE_POINT:
719     box = self.unprojected_rect_around_point(px, py, 5)
720     else:
721     box = self.unprojected_rect_around_point(px, py, 1)
722 bh 159 shape_ids = layer.ShapesInRegion(box)
723     shape_ids.reverse()
724    
725 bh 6 if shapetype == SHAPETYPE_POLYGON:
726 bh 159 for i in shape_ids:
727 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
728     i,
729     filled, stroked,
730     map_proj, layer_proj,
731     scale, -scale, offx, offy,
732     px, py)
733     if result:
734     select_shape = i
735     break
736     elif shapetype == SHAPETYPE_ARC:
737 bh 159 for i in shape_ids:
738 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
739     i, 0, 1,
740     map_proj, layer_proj,
741     scale, -scale, offx, offy,
742     px, py)
743     if result < 0:
744     select_shape = i
745     break
746     elif shapetype == SHAPETYPE_POINT:
747 bh 159 for i in shape_ids:
748 bh 6 shape = layer.Shape(i)
749     x, y = shape.Points()[0]
750     if inverse:
751     x, y = inverse(x, y)
752     if forward:
753     x, y = forward(x, y)
754     x = x * scale + offx
755     y = -y * scale + offy
756     if hypot(px - x, py - y) < 5:
757     select_shape = i
758     break
759    
760     if select_shape >= 0:
761     return layer, select_shape
762     return None, None
763    
764 bh 246 def SelectShapeAt(self, x, y, layer = None):
765     """\
766     Select and return the shape and its layer at window position (x, y)
767    
768     If layer is given, only search in that layer. If no layer is
769     given, search through all layers.
770    
771     Return a tuple (layer, shapeid). If no shape is found, return
772     (None, None).
773     """
774     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
775 bh 43 # If layer is None, then shape will also be None. We don't want
776     # to deselect the currently selected layer, so we simply select
777     # the already selected layer again.
778     if layer is None:
779     layer = self.interactor.SelectedLayer()
780 bh 6 self.interactor.SelectLayerAndShape(layer, shape)
781 bh 246 return result
782 bh 6
783     def LabelShapeAt(self, x, y):
784 bh 295 """Add or remove a label at window position x, y.
785    
786     If there's a label at the given position, remove it. Otherwise
787     determine the shape at the position, run the label dialog and
788     unless the user cancels the dialog, add a laber.
789     """
790 bh 6 ox = x; oy = y
791     label_layer = self.map.LabelLayer()
792     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
793     if layer is None and shape_index is not None:
794     # a label was selected
795     label_layer.RemoveLabel(shape_index)
796     elif layer is not None:
797     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
798     if text:
799     proj = self.map.projection
800     if proj is not None:
801     map_proj = proj
802     else:
803     map_proj = None
804     proj = layer.projection
805     if proj is not None:
806     layer_proj = proj
807     else:
808     layer_proj = None
809    
810     shapetype = layer.ShapeType()
811     if shapetype == SHAPETYPE_POLYGON:
812     x, y = shape_centroid(layer.shapefile.cobject(),
813     shape_index,
814     map_proj, layer_proj, 1, 1, 0, 0)
815     if map_proj is not None:
816     x, y = map_proj.Inverse(x, y)
817     else:
818     shape = layer.Shape(shape_index)
819     if shapetype == SHAPETYPE_POINT:
820     x, y = shape.Points()[0]
821     else:
822     # assume SHAPETYPE_ARC
823     points = shape.Points()
824     x, y = points[len(points) / 2]
825     if layer_proj is not None:
826     x, y = layer_proj.Inverse(x, y)
827     if shapetype == SHAPETYPE_POINT:
828     halign = ALIGN_LEFT
829     valign = ALIGN_CENTER
830     elif shapetype == SHAPETYPE_POLYGON:
831     halign = ALIGN_CENTER
832     valign = ALIGN_CENTER
833     elif shapetype == SHAPETYPE_ARC:
834     halign = ALIGN_LEFT
835     valign = ALIGN_CENTER
836     label_layer.AddLabel(x, y, text,
837     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