/[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 535 - (hide annotations)
Fri Mar 14 20:42:18 2003 UTC (21 years, 11 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 30286 byte(s)
Implement multiple selected shapes

* Thuban/UI/selection.py: New module with a class to represent the
selection.

* Thuban/UI/messages.py (SELECTED_TABLE, SELECTED_MAP): Remove
these unused messages

* Thuban/UI/application.py (ThubanApplication.OnInit)
(ThubanApplication.OnExit, ThubanApplication.SetSession): The
interactor is gone now.
(ThubanApplication.CreateMainWindow): There is no interactor
anymore so we pass None as the interactor argument for now for
compatibility.

* Thuban/UI/view.py (MapCanvas.delegated_messages)
(MapCanvas.Subscribe, MapCanvas.Unsubscribe): In Subscribe and
Unsubscribe, delegate messages according to the delegated_messages
class variable.
(MapCanvas.__getattr__, MapCanvas.delegated_methods): Get some
attributes from instance variables as described with the
delegated_methods class variable.
(MapCanvas.__init__): New instance variable selection holding the
current selection
(MapCanvas.do_redraw): Deal with multiple selected shapes. Simply
pass them on to the renderer
(MapCanvas.SetMap): Clear the selection when a different map is
selected.
(MapCanvas.shape_selected): Simple force a complete redraw. The
selection class now takes care of only issueing SHAPES_SELECTED
messages when the set of selected shapes actually does change.
(MapCanvas.SelectShapeAt): The selection is now managed in
self.selection

* Thuban/UI/mainwindow.py (MainWindow.delegated_messages)
(MainWindow.Subscribe, MainWindow.Unsubscribe): In Subscribe and
Unsubscribe, delegate messages according to the delegated_messages
class variable.
(MainWindow.delegated_methods, MainWindow.__getattr__): Get some
attributes from instance variables as described with the
delegated_methods class variable.
(MainWindow.__init__): The interactor as ivar is gone. The
parameter is still there for compatibility. The selection messages
now come from the canvas.
(MainWindow.current_layer, MainWindow.has_selected_layer):
Delegate to the the canvas.
(MainWindow.LayerShowTable, MainWindow.Classify)
(MainWindow.identify_view_on_demand): The dialogs don't need the
interactor parameter anymore.

* Thuban/UI/tableview.py (TableFrame.__init__)
(LayerTableFrame.__init__, LayerTableFrame.OnClose)
(LayerTableFrame.row_selected): The interactor is gone. It's job
from the dialog's point of view is now done by the mainwindow,
i.e. the parent. Subscribe to SHAPES_SELECTED instead
of SELECTED_SHAPE

* Thuban/UI/dialogs.py (NonModalDialog.__init__): The interactor
is gone. It's job from the dialog's point of view is now done by
the mainwindow, i.e. the parent.

* Thuban/UI/classifier.py (Classifier.__init__): The interactor is
gone. It's job from the dialog's point of view is now done by the
mainwindow, i.e. the parent.

* Thuban/UI/tree.py (SessionTreeView.__init__): The interactor is
gone. It's job from the dialog's point of view is now done by the
mainwindow, i.e. the parent.
(SessionTreeCtrl.__init__): New parameter mainwindow which is
stored as self.mainwindow. The mainwindow is need so that the tree
can still subscribe to the selection messages.
(SessionTreeCtrl.__init__, SessionTreeCtrl.unsubscribe_all)
(SessionTreeCtrl.update_tree, SessionTreeCtrl.OnSelChanged): The
selection is now accessible through the mainwindow. Subscribe to
SHAPES_SELECTED instead of SELECTED_SHAPE

* Thuban/UI/identifyview.py (IdentifyView.__init__): Use the
SHAPES_SELECTED message now.
(IdentifyView.selected_shape): Now subscribed to SHAPES_SELECTED,
so deal with multiple shapes
(IdentifyView.__init__, IdentifyView.OnClose): The interactor is
gone. It's job from the dialog's point of view is now done by the
mainwindow, i.e. the parent.

* Thuban/UI/renderer.py (ScreenRenderer.RenderMap): Rename the
selected_shape parameter and ivar to selected_shapes. It's now a
list of shape ids.
(MapRenderer.draw_label_layer): Deal with multiple selected
shapes. Rearrange the code a bit so that the setup and shape type
distinctions are only executed once.

1 bh 404 # Copyright (c) 2001, 2002, 2003 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 jonathan 433 from Thuban.Model.color import Color
34 bh 6
35 bh 535 from selection import Selection
36 bh 6 from renderer import ScreenRenderer, PrinterRender
37    
38     import labeldialog
39    
40 bh 535 from messages import LAYER_SELECTED, SHAPES_SELECTED, VIEW_POSITION
41 bh 6
42    
43     #
44     # The tools
45     #
46    
47     class Tool:
48    
49     """
50     Base class for the interactive tools
51     """
52    
53     def __init__(self, view):
54     """Intitialize the tool. The view is the canvas displaying the map"""
55     self.view = view
56     self.start = self.current = None
57     self.dragging = 0
58     self.drawn = 0
59    
60     def Name(self):
61     """Return the tool's name"""
62     return ''
63    
64     def drag_start(self, x, y):
65     self.start = self.current = x, y
66     self.dragging = 1
67    
68     def drag_move(self, x, y):
69     self.current = x, y
70    
71     def drag_stop(self, x, y):
72     self.current = x, y
73     self.dragging = 0
74    
75     def Show(self, dc):
76     if not self.drawn:
77     self.draw(dc)
78     self.drawn = 1
79    
80     def Hide(self, dc):
81     if self.drawn:
82     self.draw(dc)
83     self.drawn = 0
84    
85     def draw(self, dc):
86     pass
87    
88     def MouseDown(self, event):
89     self.drag_start(event.m_x, event.m_y)
90    
91     def MouseMove(self, event):
92     if self.dragging:
93     self.drag_move(event.m_x, event.m_y)
94    
95     def MouseUp(self, event):
96     if self.dragging:
97     self.drag_move(event.m_x, event.m_y)
98    
99     def Cancel(self):
100     self.dragging = 0
101    
102    
103     class RectTool(Tool):
104    
105     """Base class for tools that draw rectangles while dragging"""
106    
107     def draw(self, dc):
108     sx, sy = self.start
109     cx, cy = self.current
110     dc.DrawRectangle(sx, sy, cx - sx, cy - sy)
111    
112     class ZoomInTool(RectTool):
113    
114     """The Zoom-In Tool"""
115    
116     def Name(self):
117     return "ZoomInTool"
118    
119     def proj_rect(self):
120     """return the rectangle given by start and current in projected
121     coordinates"""
122     sx, sy = self.start
123     cx, cy = self.current
124     left, top = self.view.win_to_proj(sx, sy)
125     right, bottom = self.view.win_to_proj(cx, cy)
126     return (min(left, right), min(top, bottom),
127     max(left, right), max(top, bottom))
128    
129     def MouseUp(self, event):
130     if self.dragging:
131     Tool.MouseUp(self, event)
132 bh 57 sx, sy = self.start
133     cx, cy = self.current
134 bh 288 if sx == cx or sy == cy:
135     # Just a mouse click or a degenerate rectangle. Simply
136     # zoom in by a factor of two
137     # FIXME: For a click this is the desired behavior but should we
138     # really do this for degenrate rectagles as well or
139     # should we ignore them?
140 bh 57 self.view.ZoomFactor(2, center = (cx, cy))
141     else:
142     # A drag. Zoom in to the rectangle
143     self.view.FitRectToWindow(self.proj_rect())
144 bh 6
145    
146     class ZoomOutTool(RectTool):
147    
148     """The Zoom-Out Tool"""
149 bh 239
150 bh 6 def Name(self):
151     return "ZoomOutTool"
152    
153     def MouseUp(self, event):
154     if self.dragging:
155     Tool.MouseUp(self, event)
156     sx, sy = self.start
157     cx, cy = self.current
158 bh 288 if sx == cx or sy == cy:
159     # Just a mouse click or a degenerate rectangle. Simply
160     # zoom out by a factor of two.
161     # FIXME: For a click this is the desired behavior but should we
162     # really do this for degenrate rectagles as well or
163     # should we ignore them?
164 bh 239 self.view.ZoomFactor(0.5, center = (cx, cy))
165 bh 57 else:
166     # A drag. Zoom out to the rectangle
167     self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
168     max(sx, cx), max(sy, cy)))
169 bh 6
170    
171     class PanTool(Tool):
172    
173     """The Pan Tool"""
174    
175     def Name(self):
176     return "PanTool"
177    
178     def MouseMove(self, event):
179     if self.dragging:
180     Tool.MouseMove(self, event)
181 bh 159 sx, sy = self.start
182 bh 6 x, y = self.current
183     width, height = self.view.GetSizeTuple()
184 bh 159
185     bitmapdc = wx.wxMemoryDC()
186     bitmapdc.SelectObject(self.view.bitmap)
187    
188 bh 6 dc = self.view.drag_dc
189 bh 159 dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
190 bh 6
191     def MouseUp(self, event):
192     if self.dragging:
193     Tool.MouseUp(self, event)
194     sx, sy = self.start
195     cx, cy = self.current
196     self.view.Translate(cx - sx, cy - sy)
197 bh 246
198 bh 6 class IdentifyTool(Tool):
199    
200     """The "Identify" Tool"""
201 bh 246
202 bh 6 def Name(self):
203     return "IdentifyTool"
204    
205     def MouseUp(self, event):
206     self.view.SelectShapeAt(event.m_x, event.m_y)
207    
208    
209     class LabelTool(Tool):
210    
211     """The "Label" Tool"""
212    
213     def Name(self):
214     return "LabelTool"
215    
216     def MouseUp(self, event):
217     self.view.LabelShapeAt(event.m_x, event.m_y)
218    
219    
220    
221    
222     class MapPrintout(wx.wxPrintout):
223    
224     """
225     wxPrintout class for printing Thuban maps
226     """
227    
228     def __init__(self, map):
229     wx.wxPrintout.__init__(self)
230     self.map = map
231    
232     def GetPageInfo(self):
233     return (1, 1, 1, 1)
234    
235     def HasPage(self, pagenum):
236     return pagenum == 1
237    
238     def OnPrintPage(self, pagenum):
239     if pagenum == 1:
240     self.draw_on_dc(self.GetDC())
241    
242     def draw_on_dc(self, dc):
243     width, height = self.GetPageSizePixels()
244     llx, lly, urx, ury = self.map.ProjectedBoundingBox()
245     scalex = width / (urx - llx)
246     scaley = height / (ury - lly)
247     scale = min(scalex, scaley)
248     offx = 0.5 * (width - (urx + llx) * scale)
249     offy = 0.5 * (height + (ury + lly) * scale)
250    
251     resx, resy = self.GetPPIPrinter()
252     renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)
253     renderer.RenderMap(self.map)
254     return wx.true
255    
256 bh 246
257 bh 122 class MapCanvas(wxWindow, Publisher):
258 bh 6
259     """A widget that displays a map and offers some interaction"""
260    
261 bh 535 # Some messages that can be subscribed/unsubscribed directly through
262     # the MapCanvas come in fact from other objects. This is a map to
263     # map those messages to the names of the instance variables they
264     # actually come from. This delegation is implemented in the
265     # Subscribe and unsubscribed methods
266     delegated_messages = {LAYER_SELECTED: "selection",
267     SHAPES_SELECTED: "selection"}
268    
269     # Methods delegated to some instance variables. The delegation is
270     # implemented in the __getattr__ method.
271     delegated_methods = {"SelectLayer": "selection",
272     "SelectShapes": "selection",
273     "SelectedLayer": "selection",
274     "HasSelectedLayer": "selection"}
275    
276     def __init__(self, parent, winid):
277 bh 6 wxWindow.__init__(self, parent, winid)
278     self.SetBackgroundColour(wxColour(255, 255, 255))
279 bh 125
280     # the map displayed in this canvas. Set with SetMap()
281 bh 6 self.map = None
282 bh 125
283     # scale and offset describe the transformation from projected
284     # coordinates to window coordinates.
285 bh 6 self.scale = 1.0
286     self.offset = (0, 0)
287 bh 125
288     # whether the user is currently dragging the mouse, i.e. moving
289     # the mouse while pressing a mouse button
290 bh 6 self.dragging = 0
291 bh 125
292     # the currently active tool
293 bh 6 self.tool = None
294 bh 125
295     # The current mouse position of the last OnMotion event or None
296     # if the mouse is outside the window.
297     self.current_position = None
298    
299     # the bitmap serving as backing store
300     self.bitmap = None
301    
302 bh 535 # the selection
303     self.selection = Selection()
304     self.selection.Subscribe(SHAPES_SELECTED , self.shape_selected)
305 bh 125
306 bh 174 # keep track of which layers/shapes are selected to make sure we
307     # only redraw when necessary
308     self.last_selected_layer = None
309     self.last_selected_shape = None
310    
311 bh 125 # subscribe the WX events we're interested in
312 bh 6 EVT_PAINT(self, self.OnPaint)
313     EVT_LEFT_DOWN(self, self.OnLeftDown)
314     EVT_LEFT_UP(self, self.OnLeftUp)
315     EVT_MOTION(self, self.OnMotion)
316 bh 122 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
317 bh 125 wx.EVT_SIZE(self, self.OnSize)
318 bh 6
319 bh 122 def __del__(self):
320     wxWindow.__del__(self)
321     Publisher.__del__(self)
322    
323 bh 535 def Subscribe(self, channel, *args):
324     """Extend the inherited method to handle delegated messages.
325    
326     If channel is one of the delegated messages call the appropriate
327     object's Subscribe method. Otherwise just call the inherited
328     method.
329     """
330     if channel in self.delegated_messages:
331     object = getattr(self, self.delegated_messages[channel])
332     object.Subscribe(channel, *args)
333     else:
334     Publisher.Subscribe(self, channel, *args)
335    
336     def Unsubscribe(self, channel, *args):
337     """Extend the inherited method to handle delegated messages.
338    
339     If channel is one of the delegated messages call the appropriate
340     object's Unsubscribe method. Otherwise just call the inherited
341     method.
342     """
343     if channel in self.delegated_messages:
344     object = getattr(self, self.delegated_messages[channel])
345     object.Unsubscribe(channel, *args)
346     else:
347     Publisher.Unsubscribe(self, channel, *args)
348    
349     def __getattr__(self, attr):
350     if attr in self.delegated_methods:
351     return getattr(getattr(self, self.delegated_methods[attr]), attr)
352     raise AttributeError(attr)
353    
354 bh 6 def OnPaint(self, event):
355     dc = wxPaintDC(self)
356 bh 57 if self.map is not None and self.map.HasLayers():
357 bh 303 self.do_redraw()
358 bh 57 else:
359     # If we've got no map or if the map is empty, simply clear
360     # the screen.
361 bh 246
362 bh 57 # XXX it's probably possible to get rid of this. The
363     # background color of the window is already white and the
364     # only thing we may have to do is to call self.Refresh()
365     # with a true argument in the right places.
366     dc.BeginDrawing()
367 bh 246 dc.Clear()
368 bh 57 dc.EndDrawing()
369 bh 6
370     def do_redraw(self):
371 bh 145 # This should only be called if we have a non-empty map.
372 bh 125
373 bh 145 # Get the window size.
374 bh 6 width, height = self.GetSizeTuple()
375    
376 bh 125 # If self.bitmap's still there, reuse it. Otherwise redraw it
377     if self.bitmap is not None:
378     bitmap = self.bitmap
379 bh 6 else:
380 bh 125 bitmap = wx.wxEmptyBitmap(width, height)
381     dc = wx.wxMemoryDC()
382     dc.SelectObject(bitmap)
383     dc.BeginDrawing()
384 bh 57
385 bh 125 # clear the background
386     dc.SetBrush(wx.wxWHITE_BRUSH)
387     dc.SetPen(wx.wxTRANSPARENT_PEN)
388     dc.DrawRectangle(0, 0, width, height)
389 bh 6
390 bh 535 selected_layer = self.selection.SelectedLayer()
391     selected_shapes = self.selection.SelectedShapes()
392 bh 57
393 bh 125 # draw the map into the bitmap
394     renderer = ScreenRenderer(dc, self.scale, self.offset)
395 bh 149
396 bh 296 # Pass the entire bitmap as update region to the renderer.
397 bh 149 # We're redrawing the whole bitmap, after all.
398     renderer.RenderMap(self.map, (0, 0, width, height),
399 bh 535 selected_layer, selected_shapes)
400 bh 125
401     dc.EndDrawing()
402     dc.SelectObject(wx.wxNullBitmap)
403     self.bitmap = bitmap
404    
405 bh 57 # blit the bitmap to the screen
406 bh 125 dc = wx.wxMemoryDC()
407     dc.SelectObject(bitmap)
408 bh 6 clientdc = wxClientDC(self)
409     clientdc.BeginDrawing()
410     clientdc.Blit(0, 0, width, height, dc, 0, 0)
411     clientdc.EndDrawing()
412    
413     def Print(self):
414     printer = wx.wxPrinter()
415     printout = MapPrintout(self.map)
416     printer.Print(self, printout, wx.true)
417     printout.Destroy()
418 bh 246
419 bh 6 def SetMap(self, map):
420     redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,
421     LAYER_VISIBILITY_CHANGED)
422     if self.map is not None:
423     for channel in redraw_channels:
424 bh 125 self.map.Unsubscribe(channel, self.full_redraw)
425 bh 6 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
426     self.projection_changed)
427     self.map = map
428 bh 535 self.selection.ClearSelection()
429 bh 6 if self.map is not None:
430     for channel in redraw_channels:
431 bh 125 self.map.Subscribe(channel, self.full_redraw)
432 bh 6 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
433     self.FitMapToWindow()
434 bh 57 # force a redraw. If map is not empty, it's already been called
435     # by FitMapToWindow but if map is empty it hasn't been called
436     # yet so we have to explicitly call it.
437 bh 125 self.full_redraw()
438 bh 6
439     def Map(self):
440 bh 295 """Return the map displayed by this canvas"""
441 bh 6 return self.map
442    
443     def redraw(self, *args):
444     self.Refresh(0)
445    
446 bh 125 def full_redraw(self, *args):
447     self.bitmap = None
448     self.redraw()
449    
450 bh 6 def projection_changed(self, *args):
451     self.FitMapToWindow()
452 bh 125 self.full_redraw()
453 bh 6
454     def set_view_transform(self, scale, offset):
455     self.scale = scale
456     self.offset = offset
457 bh 125 self.full_redraw()
458 bh 6
459     def proj_to_win(self, x, y):
460     """\
461     Return the point in window coords given by projected coordinates x y
462     """
463     offx, offy = self.offset
464     return (self.scale * x + offx, -self.scale * y + offy)
465    
466     def win_to_proj(self, x, y):
467     """\
468     Return the point in projected coordinates given by window coords x y
469     """
470     offx, offy = self.offset
471     return ((x - offx) / self.scale, (offy - y) / self.scale)
472    
473     def FitRectToWindow(self, rect):
474 bh 293 """Fit the rectangular region given by rect into the window.
475 bh 535
476 bh 293 Set scale so that rect (in projected coordinates) just fits into
477     the window and center it.
478     """
479 bh 6 width, height = self.GetSizeTuple()
480     llx, lly, urx, ury = rect
481 bh 45 if llx == urx or lly == ury:
482     # zero with or zero height. Do Nothing
483     return
484 bh 6 scalex = width / (urx - llx)
485     scaley = height / (ury - lly)
486     scale = min(scalex, scaley)
487     offx = 0.5 * (width - (urx + llx) * scale)
488     offy = 0.5 * (height + (ury + lly) * scale)
489     self.set_view_transform(scale, (offx, offy))
490    
491     def FitMapToWindow(self):
492 bh 293 """Fit the map to the window
493 bh 535
494 bh 293 Set the scale so that the map fits exactly into the window and
495     center it in the window.
496 bh 6 """
497     bbox = self.map.ProjectedBoundingBox()
498     if bbox is not None:
499     self.FitRectToWindow(bbox)
500    
501 bh 57 def ZoomFactor(self, factor, center = None):
502     """Multiply the zoom by factor and center on center.
503    
504     The optional parameter center is a point in window coordinates
505     that should be centered. If it is omitted, it defaults to the
506     center of the window
507     """
508 bh 6 width, height = self.GetSizeTuple()
509     scale = self.scale * factor
510     offx, offy = self.offset
511 bh 57 if center is not None:
512     cx, cy = center
513     else:
514     cx = width / 2
515     cy = height / 2
516     offset = (factor * (offx - cx) + width / 2,
517     factor * (offy - cy) + height / 2)
518 bh 6 self.set_view_transform(scale, offset)
519    
520     def ZoomOutToRect(self, rect):
521 bh 293 """Zoom out to fit the currently visible region into rect.
522 bh 6
523 bh 293 The rect parameter is given in window coordinates
524     """
525 bh 6 # determine the bbox of the displayed region in projected
526     # coordinates
527     width, height = self.GetSizeTuple()
528     llx, lly = self.win_to_proj(0, height - 1)
529     urx, ury = self.win_to_proj(width - 1, 0)
530    
531     sx, sy, ex, ey = rect
532     scalex = (ex - sx) / (urx - llx)
533     scaley = (ey - sy) / (ury - lly)
534     scale = min(scalex, scaley)
535    
536     offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
537     offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
538     self.set_view_transform(scale, (offx, offy))
539    
540     def Translate(self, dx, dy):
541 bh 295 """Move the map by dx, dy pixels"""
542 bh 6 offx, offy = self.offset
543     self.set_view_transform(self.scale, (offx + dx, offy + dy))
544    
545 bh 356 def SelectTool(self, tool):
546     """Make tool the active tool.
547    
548     The parameter should be an instance of Tool or None to indicate
549     that no tool is active.
550     """
551     self.tool = tool
552    
553 bh 6 def ZoomInTool(self):
554 bh 295 """Start the zoom in tool"""
555 bh 356 self.SelectTool(ZoomInTool(self))
556 bh 6
557     def ZoomOutTool(self):
558 bh 295 """Start the zoom out tool"""
559 bh 356 self.SelectTool(ZoomOutTool(self))
560 bh 6
561     def PanTool(self):
562 bh 295 """Start the pan tool"""
563 bh 356 self.SelectTool(PanTool(self))
564 bh 6
565     def IdentifyTool(self):
566 bh 295 """Start the identify tool"""
567 bh 356 self.SelectTool(IdentifyTool(self))
568 bh 6
569     def LabelTool(self):
570 bh 295 """Start the label tool"""
571 bh 356 self.SelectTool(LabelTool(self))
572 bh 6
573     def CurrentTool(self):
574 bh 295 """Return the name of the current tool or None if no tool is active"""
575 bh 6 return self.tool and self.tool.Name() or None
576    
577 bh 122 def CurrentPosition(self):
578     """Return current position of the mouse in projected coordinates.
579    
580     The result is a 2-tuple of floats with the coordinates. If the
581     mouse is not in the window, the result is None.
582     """
583     if self.current_position is not None:
584     x, y = self.current_position
585     return self.win_to_proj(x, y)
586     else:
587     return None
588    
589     def set_current_position(self, event):
590     """Set the current position from event
591    
592     Should be called by all events that contain mouse positions
593     especially EVT_MOTION. The event paramete may be None to
594     indicate the the pointer left the window.
595     """
596     if event is not None:
597     self.current_position = (event.m_x, event.m_y)
598     else:
599     self.current_position = None
600     self.issue(VIEW_POSITION)
601    
602 bh 6 def OnLeftDown(self, event):
603 bh 122 self.set_current_position(event)
604 bh 6 if self.tool is not None:
605     self.drag_dc = wxClientDC(self)
606     self.drag_dc.SetLogicalFunction(wxINVERT)
607     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
608     self.CaptureMouse()
609     self.tool.MouseDown(event)
610     self.tool.Show(self.drag_dc)
611     self.dragging = 1
612 bh 246
613 bh 6 def OnLeftUp(self, event):
614 bh 122 self.set_current_position(event)
615 bh 6 if self.dragging:
616 bh 261 self.ReleaseMouse()
617 bh 404 try:
618     self.tool.Hide(self.drag_dc)
619     self.tool.MouseUp(event)
620     finally:
621     self.drag_dc = None
622     self.dragging = 0
623 bh 6
624     def OnMotion(self, event):
625 bh 122 self.set_current_position(event)
626 bh 6 if self.dragging:
627     self.tool.Hide(self.drag_dc)
628     self.tool.MouseMove(event)
629     self.tool.Show(self.drag_dc)
630    
631 bh 122 def OnLeaveWindow(self, event):
632     self.set_current_position(None)
633    
634 bh 125 def OnSize(self, event):
635     # the window's size has changed. We have to get a new bitmap. If
636     # we want to be clever we could try to get by without throwing
637     # everything away. E.g. when the window gets smaller, we could
638     # either keep the bitmap or create the new one from the old one.
639     # Even when the window becomes larger some parts of the bitmap
640     # could be reused.
641     self.full_redraw()
642    
643 bh 6 def shape_selected(self, layer, shape):
644 bh 535 """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
645     # The selection object takes care that it only issues
646     # SHAPES_SELECTED messages when the set of selected shapes has
647     # actually changed, so we can do a full redraw unconditionally.
648     # FIXME: We should perhaps try to limit the redraw to the are
649     # actually covered by the shapes before and after the selection
650     # change.
651     self.full_redraw()
652 bh 6
653 bh 301 def unprojected_rect_around_point(self, x, y, dist):
654     """return a rect dist pixels around (x, y) in unprojected corrdinates
655 bh 159
656     The return value is a tuple (minx, miny, maxx, maxy) suitable a
657     parameter to a layer's ShapesInRegion method.
658     """
659     map_proj = self.map.projection
660     if map_proj is not None:
661     inverse = map_proj.Inverse
662     else:
663     inverse = None
664    
665     xs = []
666     ys = []
667     for dx, dy in ((-1, -1), (1, -1), (1, 1), (-1, 1)):
668 bh 301 px, py = self.win_to_proj(x + dist * dx, y + dist * dy)
669 bh 159 if inverse:
670     px, py = inverse(px, py)
671     xs.append(px)
672     ys.append(py)
673     return (min(xs), min(ys), max(xs), max(ys))
674    
675 bh 246 def find_shape_at(self, px, py, select_labels = 0, searched_layer = None):
676 bh 43 """Determine the shape at point px, py in window coords
677    
678     Return the shape and the corresponding layer as a tuple (layer,
679     shape).
680    
681     If the optional parameter select_labels is true (default false)
682     search through the labels. If a label is found return it's index
683     as the shape and None as the layer.
684    
685 bh 246 If the optional parameter searched_layer is given (or not None
686     which it defaults to), only search in that layer.
687 bh 43 """
688 bh 6 map_proj = self.map.projection
689     if map_proj is not None:
690     forward = map_proj.Forward
691     else:
692     forward = None
693    
694     scale = self.scale
695     offx, offy = self.offset
696    
697     if select_labels:
698     labels = self.map.LabelLayer().Labels()
699 bh 246
700 bh 6 if labels:
701     dc = wxClientDC(self)
702     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
703     dc.SetFont(font)
704 bh 60 for i in range(len(labels) - 1, -1, -1):
705 bh 6 label = labels[i]
706     x = label.x
707     y = label.y
708     text = label.text
709     if forward:
710     x, y = forward(x, y)
711     x = x * scale + offx
712     y = -y * scale + offy
713     width, height = dc.GetTextExtent(text)
714     if label.halign == ALIGN_LEFT:
715     # nothing to be done
716     pass
717     elif label.halign == ALIGN_RIGHT:
718     x = x - width
719     elif label.halign == ALIGN_CENTER:
720     x = x - width/2
721     if label.valign == ALIGN_TOP:
722     # nothing to be done
723     pass
724     elif label.valign == ALIGN_BOTTOM:
725     y = y - height
726     elif label.valign == ALIGN_CENTER:
727     y = y - height/2
728     if x <= px < x + width and y <= py <= y + height:
729     return None, i
730 bh 43
731 bh 246 if searched_layer:
732     layers = [searched_layer]
733 bh 43 else:
734     layers = self.map.Layers()
735    
736 bh 6 for layer_index in range(len(layers) - 1, -1, -1):
737     layer = layers[layer_index]
738    
739     # search only in visible layers
740     if not layer.Visible():
741     continue
742    
743 jonathan 433 filled = layer.GetClassification().GetDefaultFill() \
744     is not Color.None
745 jonathan 469 stroked = layer.GetClassification().GetDefaultLineColor() \
746 jonathan 433 is not Color.None
747 bh 246
748 bh 6 layer_proj = layer.projection
749     if layer_proj is not None:
750     inverse = layer_proj.Inverse
751     else:
752     inverse = None
753 bh 246
754 bh 6 shapetype = layer.ShapeType()
755    
756     select_shape = -1
757 bh 159
758 bh 301 # Determine the ids of the shapes that overlap a tiny area
759     # around the point. For layers containing points we have to
760     # choose a larger size of the box we're testing agains so
761     # that we take the size of the markers into account
762     # FIXME: Once the markers are more flexible this part has to
763     # become more flexible too, of course
764     if shapetype == SHAPETYPE_POINT:
765     box = self.unprojected_rect_around_point(px, py, 5)
766     else:
767     box = self.unprojected_rect_around_point(px, py, 1)
768 bh 159 shape_ids = layer.ShapesInRegion(box)
769     shape_ids.reverse()
770    
771 bh 6 if shapetype == SHAPETYPE_POLYGON:
772 bh 159 for i in shape_ids:
773 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
774     i,
775     filled, stroked,
776     map_proj, layer_proj,
777     scale, -scale, offx, offy,
778     px, py)
779     if result:
780     select_shape = i
781     break
782     elif shapetype == SHAPETYPE_ARC:
783 bh 159 for i in shape_ids:
784 bh 6 result = point_in_polygon_shape(layer.shapefile.cobject(),
785     i, 0, 1,
786     map_proj, layer_proj,
787     scale, -scale, offx, offy,
788     px, py)
789     if result < 0:
790     select_shape = i
791     break
792     elif shapetype == SHAPETYPE_POINT:
793 bh 159 for i in shape_ids:
794 bh 6 shape = layer.Shape(i)
795     x, y = shape.Points()[0]
796     if inverse:
797     x, y = inverse(x, y)
798     if forward:
799     x, y = forward(x, y)
800     x = x * scale + offx
801     y = -y * scale + offy
802     if hypot(px - x, py - y) < 5:
803     select_shape = i
804     break
805    
806     if select_shape >= 0:
807     return layer, select_shape
808     return None, None
809    
810 bh 246 def SelectShapeAt(self, x, y, layer = None):
811     """\
812     Select and return the shape and its layer at window position (x, y)
813    
814     If layer is given, only search in that layer. If no layer is
815     given, search through all layers.
816    
817     Return a tuple (layer, shapeid). If no shape is found, return
818     (None, None).
819     """
820     layer, shape = result = self.find_shape_at(x, y, searched_layer=layer)
821 bh 43 # If layer is None, then shape will also be None. We don't want
822     # to deselect the currently selected layer, so we simply select
823     # the already selected layer again.
824     if layer is None:
825 bh 535 layer = self.selection.SelectedLayer()
826     shapes = []
827     else:
828     shapes = [shape]
829     self.selection.SelectShapes(layer, shapes)
830 bh 246 return result
831 bh 6
832     def LabelShapeAt(self, x, y):
833 bh 295 """Add or remove a label at window position x, y.
834    
835     If there's a label at the given position, remove it. Otherwise
836     determine the shape at the position, run the label dialog and
837     unless the user cancels the dialog, add a laber.
838     """
839 bh 6 ox = x; oy = y
840     label_layer = self.map.LabelLayer()
841     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
842     if layer is None and shape_index is not None:
843     # a label was selected
844     label_layer.RemoveLabel(shape_index)
845     elif layer is not None:
846     text = labeldialog.run_label_dialog(self, layer.table, shape_index)
847     if text:
848     proj = self.map.projection
849     if proj is not None:
850     map_proj = proj
851     else:
852     map_proj = None
853     proj = layer.projection
854     if proj is not None:
855     layer_proj = proj
856     else:
857     layer_proj = None
858    
859     shapetype = layer.ShapeType()
860     if shapetype == SHAPETYPE_POLYGON:
861     x, y = shape_centroid(layer.shapefile.cobject(),
862     shape_index,
863     map_proj, layer_proj, 1, 1, 0, 0)
864     if map_proj is not None:
865     x, y = map_proj.Inverse(x, y)
866     else:
867     shape = layer.Shape(shape_index)
868     if shapetype == SHAPETYPE_POINT:
869     x, y = shape.Points()[0]
870     else:
871     # assume SHAPETYPE_ARC
872     points = shape.Points()
873     x, y = points[len(points) / 2]
874     if layer_proj is not None:
875     x, y = layer_proj.Inverse(x, y)
876     if shapetype == SHAPETYPE_POINT:
877     halign = ALIGN_LEFT
878     valign = ALIGN_CENTER
879     elif shapetype == SHAPETYPE_POLYGON:
880     halign = ALIGN_CENTER
881     valign = ALIGN_CENTER
882     elif shapetype == SHAPETYPE_ARC:
883     halign = ALIGN_LEFT
884     valign = ALIGN_CENTER
885     label_layer.AddLabel(x, y, text,
886     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