/[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 1035 - (hide annotations)
Mon May 26 17:03:08 2003 UTC (21 years, 9 months ago) by jan
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 36144 byte(s)
Replace the true/false of wxWindows by True/False of Python 2.2.1.

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

Properties

Name Value
svn:eol-style native
svn:keywords Author Date Id Revision

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26