/[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 296 - (hide annotations)
Fri Aug 30 16:10:45 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: 28182 byte(s)
* Thuban/UI/view.py (MapCanvas.__init__, MapCanvas.OnPaint)
(MapCanvas.do_redraw): Get rid of the unused update_region
instance variable

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