/[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 288 - (show 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 # 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 width, height = self.GetSizeTuple()
449 llx, lly, urx, ury = rect
450 if llx == urx or lly == ury:
451 # zero with or zero height. Do Nothing
452 return
453 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 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 width, height = self.GetSizeTuple()
477 scale = self.scale * factor
478 offx, offy = self.offset
479 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 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 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 def OnLeftDown(self, event):
554 self.set_current_position(event)
555 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
564 def OnLeftUp(self, event):
565 self.set_current_position(event)
566 if self.dragging:
567 self.ReleaseMouse()
568 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 self.set_current_position(event)
575 if self.dragging:
576 self.tool.Hide(self.drag_dc)
577 self.tool.MouseMove(event)
578 self.tool.Show(self.drag_dc)
579
580 def OnLeaveWindow(self, event):
581 self.set_current_position(None)
582
583 def OnIdle(self, event):
584 if self.redraw_on_idle:
585 self.do_redraw()
586 self.redraw_on_idle = 0
587
588 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 def shape_selected(self, layer, shape):
598 """Redraw the map.
599
600 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
614 # remember the selection so we can compare when it changes again.
615 self.last_selected_layer = layer
616 self.last_selected_shape = shape
617
618 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 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
641 """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 If the optional parameter searched_layer is given (or not None
651 which it defaults to), only search in that layer.
652 """
653 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 box = self.unprojected_rect_around_point(px, py)
663
664 if select_labels:
665 labels = self.map.LabelLayer().Labels()
666
667 if labels:
668 dc = wxClientDC(self)
669 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
670 dc.SetFont(font)
671 for i in range(len(labels) - 1, -1, -1):
672 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
698 if searched_layer:
699 layers = [searched_layer]
700 else:
701 layers = self.map.Layers()
702
703 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
713 layer_proj = layer.projection
714 if layer_proj is not None:
715 inverse = layer_proj.Inverse
716 else:
717 inverse = None
718
719 shapetype = layer.ShapeType()
720
721 select_shape = -1
722
723 shape_ids = layer.ShapesInRegion(box)
724 shape_ids.reverse()
725
726 if shapetype == SHAPETYPE_POLYGON:
727 for i in shape_ids:
728 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 for i in shape_ids:
739 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 for i in shape_ids:
749 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 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 # 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 self.interactor.SelectLayerAndShape(layer, shape)
782 return result
783
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