/[thuban]/branches/WIP-pyshapelib-bramz/Thuban/UI/view.py
ViewVC logotype

Contents of /branches/WIP-pyshapelib-bramz/Thuban/UI/view.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 293 - (show annotations)
Fri Aug 30 10:18:50 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: 28035 byte(s)
Add/update some doc-strings.

1 # Copyright (c) 2001, 2002 by Intevation GmbH
2 # 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 EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW
19
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 from Thuban.Lib.connector import Publisher
33
34 from renderer import ScreenRenderer, PrinterRender
35
36 import labeldialog
37
38 from messages import SELECTED_SHAPE, VIEW_POSITION
39
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 sx, sy = self.start
131 cx, cy = self.current
132 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 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
143
144 class ZoomOutTool(RectTool):
145
146 """The Zoom-Out Tool"""
147
148 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 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 self.view.ZoomFactor(0.5, center = (cx, cy))
163 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
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 sx, sy = self.start
180 x, y = self.current
181 width, height = self.view.GetSizeTuple()
182
183 bitmapdc = wx.wxMemoryDC()
184 bitmapdc.SelectObject(self.view.bitmap)
185
186 dc = self.view.drag_dc
187 dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
188
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
196 class IdentifyTool(Tool):
197
198 """The "Identify" Tool"""
199
200 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
255 class MapCanvas(wxWindow, Publisher):
256
257 """A widget that displays a map and offers some interaction"""
258
259 def __init__(self, parent, winid, interactor):
260 wxWindow.__init__(self, parent, winid)
261 self.SetBackgroundColour(wxColour(255, 255, 255))
262
263 # the map displayed in this canvas. Set with SetMap()
264 self.map = None
265
266 # scale and offset describe the transformation from projected
267 # coordinates to window coordinates.
268 self.scale = 1.0
269 self.offset = (0, 0)
270
271 # whether the user is currently dragging the mouse, i.e. moving
272 # the mouse while pressing a mouse button
273 self.dragging = 0
274
275 # the currently active tool
276 self.tool = None
277
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 self.redraw_on_idle = 0
286
287 # The region to update when idle
288 self.update_region = wx.wxRegion()
289
290 # 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 # 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 # subscribe the WX events we're interested in
303 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 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
308 wx.EVT_SIZE(self, self.OnSize)
309 wx.EVT_IDLE(self, self.OnIdle)
310
311 def __del__(self):
312 wxWindow.__del__(self)
313 Publisher.__del__(self)
314
315 def OnPaint(self, event):
316 dc = wxPaintDC(self)
317 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 # update the region that has to be redrawn
321 self.update_region.UnionRegion(self.GetUpdateRegion())
322 else:
323 # If we've got no map or if the map is empty, simply clear
324 # the screen.
325
326 # 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 dc.Clear()
332 dc.EndDrawing()
333
334 # clear the region
335 self.update_region = wx.wxRegion()
336
337 def do_redraw(self):
338 # This should only be called if we have a non-empty map.
339
340 # get the update region and reset it. We're not actually using
341 # it anymore, though.
342 update_box = self.update_region.GetBox()
343 self.update_region = wx.wxRegion()
344
345 # Get the window size.
346 width, height = self.GetSizeTuple()
347
348 # If self.bitmap's still there, reuse it. Otherwise redraw it
349 if self.bitmap is not None:
350 bitmap = self.bitmap
351 else:
352 bitmap = wx.wxEmptyBitmap(width, height)
353 dc = wx.wxMemoryDC()
354 dc.SelectObject(bitmap)
355 dc.BeginDrawing()
356
357 # clear the background
358 dc.SetBrush(wx.wxWHITE_BRUSH)
359 dc.SetPen(wx.wxTRANSPARENT_PEN)
360 dc.DrawRectangle(0, 0, width, height)
361
362 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
369 # draw the map into the bitmap
370 renderer = ScreenRenderer(dc, self.scale, self.offset)
371
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 selected_layer, selected_shape)
376
377 dc.EndDrawing()
378 dc.SelectObject(wx.wxNullBitmap)
379 self.bitmap = bitmap
380
381 # blit the bitmap to the screen
382 dc = wx.wxMemoryDC()
383 dc.SelectObject(bitmap)
384 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
395 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 self.map.Unsubscribe(channel, self.full_redraw)
401 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 self.map.Subscribe(channel, self.full_redraw)
407 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
408 self.FitMapToWindow()
409 # 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 self.full_redraw()
413
414 def Map(self):
415 return self.map
416
417 def redraw(self, *args):
418 self.Refresh(0)
419
420 def full_redraw(self, *args):
421 self.bitmap = None
422 self.redraw()
423
424 def projection_changed(self, *args):
425 self.FitMapToWindow()
426 self.full_redraw()
427
428 def set_view_transform(self, scale, offset):
429 self.scale = scale
430 self.offset = offset
431 self.full_redraw()
432
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 """Fit the rectangular region given by rect into the window.
449
450 Set scale so that rect (in projected coordinates) just fits into
451 the window and center it.
452 """
453 width, height = self.GetSizeTuple()
454 llx, lly, urx, ury = rect
455 if llx == urx or lly == ury:
456 # zero with or zero height. Do Nothing
457 return
458 scalex = width / (urx - llx)
459 scaley = height / (ury - lly)
460 scale = min(scalex, scaley)
461 offx = 0.5 * (width - (urx + llx) * scale)
462 offy = 0.5 * (height + (ury + lly) * scale)
463 self.set_view_transform(scale, (offx, offy))
464
465 def FitMapToWindow(self):
466 """Fit the map to the window
467
468 Set the scale so that the map fits exactly into the window and
469 center it in the window.
470 """
471 bbox = self.map.ProjectedBoundingBox()
472 if bbox is not None:
473 self.FitRectToWindow(bbox)
474
475 def ZoomFactor(self, factor, center = None):
476 """Multiply the zoom by factor and center on center.
477
478 The optional parameter center is a point in window coordinates
479 that should be centered. If it is omitted, it defaults to the
480 center of the window
481 """
482 width, height = self.GetSizeTuple()
483 scale = self.scale * factor
484 offx, offy = self.offset
485 if center is not None:
486 cx, cy = center
487 else:
488 cx = width / 2
489 cy = height / 2
490 offset = (factor * (offx - cx) + width / 2,
491 factor * (offy - cy) + height / 2)
492 self.set_view_transform(scale, offset)
493
494 def ZoomOutToRect(self, rect):
495 """Zoom out to fit the currently visible region into rect.
496
497 The rect parameter is given in window coordinates
498 """
499 # determine the bbox of the displayed region in projected
500 # coordinates
501 width, height = self.GetSizeTuple()
502 llx, lly = self.win_to_proj(0, height - 1)
503 urx, ury = self.win_to_proj(width - 1, 0)
504
505 sx, sy, ex, ey = rect
506 scalex = (ex - sx) / (urx - llx)
507 scaley = (ey - sy) / (ury - lly)
508 scale = min(scalex, scaley)
509
510 offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
511 offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
512 self.set_view_transform(scale, (offx, offy))
513
514 def Translate(self, dx, dy):
515 offx, offy = self.offset
516 self.set_view_transform(self.scale, (offx + dx, offy + dy))
517
518 def ZoomInTool(self):
519 self.tool = ZoomInTool(self)
520
521 def ZoomOutTool(self):
522 self.tool = ZoomOutTool(self)
523
524 def PanTool(self):
525 self.tool = PanTool(self)
526
527 def IdentifyTool(self):
528 self.tool = IdentifyTool(self)
529
530 def LabelTool(self):
531 self.tool = LabelTool(self)
532
533 def CurrentTool(self):
534 return self.tool and self.tool.Name() or None
535
536 def CurrentPosition(self):
537 """Return current position of the mouse in projected coordinates.
538
539 The result is a 2-tuple of floats with the coordinates. If the
540 mouse is not in the window, the result is None.
541 """
542 if self.current_position is not None:
543 x, y = self.current_position
544 return self.win_to_proj(x, y)
545 else:
546 return None
547
548 def set_current_position(self, event):
549 """Set the current position from event
550
551 Should be called by all events that contain mouse positions
552 especially EVT_MOTION. The event paramete may be None to
553 indicate the the pointer left the window.
554 """
555 if event is not None:
556 self.current_position = (event.m_x, event.m_y)
557 else:
558 self.current_position = None
559 self.issue(VIEW_POSITION)
560
561 def OnLeftDown(self, event):
562 self.set_current_position(event)
563 if self.tool is not None:
564 self.drag_dc = wxClientDC(self)
565 self.drag_dc.SetLogicalFunction(wxINVERT)
566 self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
567 self.CaptureMouse()
568 self.tool.MouseDown(event)
569 self.tool.Show(self.drag_dc)
570 self.dragging = 1
571
572 def OnLeftUp(self, event):
573 self.set_current_position(event)
574 if self.dragging:
575 self.ReleaseMouse()
576 self.tool.Hide(self.drag_dc)
577 self.tool.MouseUp(event)
578 self.drag_dc = None
579 self.dragging = 0
580
581 def OnMotion(self, event):
582 self.set_current_position(event)
583 if self.dragging:
584 self.tool.Hide(self.drag_dc)
585 self.tool.MouseMove(event)
586 self.tool.Show(self.drag_dc)
587
588 def OnLeaveWindow(self, event):
589 self.set_current_position(None)
590
591 def OnIdle(self, event):
592 if self.redraw_on_idle:
593 self.do_redraw()
594 self.redraw_on_idle = 0
595
596 def OnSize(self, event):
597 # the window's size has changed. We have to get a new bitmap. If
598 # we want to be clever we could try to get by without throwing
599 # everything away. E.g. when the window gets smaller, we could
600 # either keep the bitmap or create the new one from the old one.
601 # Even when the window becomes larger some parts of the bitmap
602 # could be reused.
603 self.full_redraw()
604
605 def shape_selected(self, layer, shape):
606 """Redraw the map.
607
608 Receiver for the SELECTED_SHAPE messages. Try to redraw only
609 when necessary.
610 """
611 # A redraw is necessary when the display has to change, which
612 # means that either the status changes from having no selection
613 # to having a selection shape or vice versa, or when the fact
614 # whether there is a selection at all doesn't change, when the
615 # shape which is selected has changed (which means that layer or
616 # shapeid changes).
617 if ((shape is not None or self.last_selected_shape is not None)
618 and (shape != self.last_selected_shape
619 or layer != self.last_selected_layer)):
620 self.full_redraw()
621
622 # remember the selection so we can compare when it changes again.
623 self.last_selected_layer = layer
624 self.last_selected_shape = shape
625
626 def unprojected_rect_around_point(self, x, y):
627 """return a rect a few pixels around (x, y) in unprojected corrdinates
628
629 The return value is a tuple (minx, miny, maxx, maxy) suitable a
630 parameter to a layer's ShapesInRegion method.
631 """
632 map_proj = self.map.projection
633 if map_proj is not None:
634 inverse = map_proj.Inverse
635 else:
636 inverse = None
637
638 xs = []
639 ys = []
640 for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
641 px, py = self.win_to_proj(x + dx, y + dy)
642 if inverse:
643 px, py = inverse(px, py)
644 xs.append(px)
645 ys.append(py)
646 return (min(xs), min(ys), max(xs), max(ys))
647
648 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
649 """Determine the shape at point px, py in window coords
650
651 Return the shape and the corresponding layer as a tuple (layer,
652 shape).
653
654 If the optional parameter select_labels is true (default false)
655 search through the labels. If a label is found return it's index
656 as the shape and None as the layer.
657
658 If the optional parameter searched_layer is given (or not None
659 which it defaults to), only search in that layer.
660 """
661 map_proj = self.map.projection
662 if map_proj is not None:
663 forward = map_proj.Forward
664 else:
665 forward = None
666
667 scale = self.scale
668 offx, offy = self.offset
669
670 box = self.unprojected_rect_around_point(px, py)
671
672 if select_labels:
673 labels = self.map.LabelLayer().Labels()
674
675 if labels:
676 dc = wxClientDC(self)
677 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
678 dc.SetFont(font)
679 for i in range(len(labels) - 1, -1, -1):
680 label = labels[i]
681 x = label.x
682 y = label.y
683 text = label.text
684 if forward:
685 x, y = forward(x, y)
686 x = x * scale + offx
687 y = -y * scale + offy
688 width, height = dc.GetTextExtent(text)
689 if label.halign == ALIGN_LEFT:
690 # nothing to be done
691 pass
692 elif label.halign == ALIGN_RIGHT:
693 x = x - width
694 elif label.halign == ALIGN_CENTER:
695 x = x - width/2
696 if label.valign == ALIGN_TOP:
697 # nothing to be done
698 pass
699 elif label.valign == ALIGN_BOTTOM:
700 y = y - height
701 elif label.valign == ALIGN_CENTER:
702 y = y - height/2
703 if x <= px < x + width and y <= py <= y + height:
704 return None, i
705
706 if searched_layer:
707 layers = [searched_layer]
708 else:
709 layers = self.map.Layers()
710
711 for layer_index in range(len(layers) - 1, -1, -1):
712 layer = layers[layer_index]
713
714 # search only in visible layers
715 if not layer.Visible():
716 continue
717
718 filled = layer.fill is not None
719 stroked = layer.stroke is not None
720
721 layer_proj = layer.projection
722 if layer_proj is not None:
723 inverse = layer_proj.Inverse
724 else:
725 inverse = None
726
727 shapetype = layer.ShapeType()
728
729 select_shape = -1
730
731 shape_ids = layer.ShapesInRegion(box)
732 shape_ids.reverse()
733
734 if shapetype == SHAPETYPE_POLYGON:
735 for i in shape_ids:
736 result = point_in_polygon_shape(layer.shapefile.cobject(),
737 i,
738 filled, stroked,
739 map_proj, layer_proj,
740 scale, -scale, offx, offy,
741 px, py)
742 if result:
743 select_shape = i
744 break
745 elif shapetype == SHAPETYPE_ARC:
746 for i in shape_ids:
747 result = point_in_polygon_shape(layer.shapefile.cobject(),
748 i, 0, 1,
749 map_proj, layer_proj,
750 scale, -scale, offx, offy,
751 px, py)
752 if result < 0:
753 select_shape = i
754 break
755 elif shapetype == SHAPETYPE_POINT:
756 for i in shape_ids:
757 shape = layer.Shape(i)
758 x, y = shape.Points()[0]
759 if inverse:
760 x, y = inverse(x, y)
761 if forward:
762 x, y = forward(x, y)
763 x = x * scale + offx
764 y = -y * scale + offy
765 if hypot(px - x, py - y) < 5:
766 select_shape = i
767 break
768
769 if select_shape >= 0:
770 return layer, select_shape
771 return None, None
772
773 def SelectShapeAt(self, x, y, layer = None):
774 """\
775 Select and return the shape and its layer at window position (x, y)
776
777 If layer is given, only search in that layer. If no layer is
778 given, search through all layers.
779
780 Return a tuple (layer, shapeid). If no shape is found, return
781 (None, None).
782 """
783 layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
784 # If layer is None, then shape will also be None. We don't want
785 # to deselect the currently selected layer, so we simply select
786 # the already selected layer again.
787 if layer is None:
788 layer = self.interactor.SelectedLayer()
789 self.interactor.SelectLayerAndShape(layer, shape)
790 return result
791
792 def LabelShapeAt(self, x, y):
793 ox = x; oy = y
794 label_layer = self.map.LabelLayer()
795 layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
796 if layer is None and shape_index is not None:
797 # a label was selected
798 label_layer.RemoveLabel(shape_index)
799 elif layer is not None:
800 text = labeldialog.run_label_dialog(self, layer.table, shape_index)
801 if text:
802 proj = self.map.projection
803 if proj is not None:
804 map_proj = proj
805 else:
806 map_proj = None
807 proj = layer.projection
808 if proj is not None:
809 layer_proj = proj
810 else:
811 layer_proj = None
812
813 shapetype = layer.ShapeType()
814 if shapetype == SHAPETYPE_POLYGON:
815 x, y = shape_centroid(layer.shapefile.cobject(),
816 shape_index,
817 map_proj, layer_proj, 1, 1, 0, 0)
818 if map_proj is not None:
819 x, y = map_proj.Inverse(x, y)
820 else:
821 shape = layer.Shape(shape_index)
822 if shapetype == SHAPETYPE_POINT:
823 x, y = shape.Points()[0]
824 else:
825 # assume SHAPETYPE_ARC
826 points = shape.Points()
827 x, y = points[len(points) / 2]
828 if layer_proj is not None:
829 x, y = layer_proj.Inverse(x, y)
830 if shapetype == SHAPETYPE_POINT:
831 halign = ALIGN_LEFT
832 valign = ALIGN_CENTER
833 elif shapetype == SHAPETYPE_POLYGON:
834 halign = ALIGN_CENTER
835 valign = ALIGN_CENTER
836 elif shapetype == SHAPETYPE_ARC:
837 halign = ALIGN_LEFT
838 valign = ALIGN_CENTER
839 label_layer.AddLabel(x, y, text,
840 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