/[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 1866 - (hide annotations)
Mon Oct 27 13:01:58 2003 UTC (21 years, 4 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 15788 byte(s)
Several rendering changes:

 - Render the selection into a separate bitmap so that only that
   bitmap needs to be redrawn when the selection changes

 - Render incrementally showing previews and allowing interaction
   before rendering is complete

 - Update the renderer interface a bit. Most parameters of
   RenderMap are now parameters of the constructor

* Thuban/UI/baserenderer.py (BaseRenderer.__init__): Add the map
and the update region as parameters. Update the doc-string
(BaseRenderer.render_map_incrementally): New. Generator function
to renders the map incrementally
(BaseRenderer.render_map): Remove the map argument (it's now in
the constructor) and simply iterate over the
render_map_incrementally generator to draw the map.
(BaseRenderer.draw_shape_layer_incrementally)
(BaseRenderer.draw_shape_layer): Renamed to
draw_shape_layer_incrementally and changed into a generator that
yields True every 500 shapes. Used by render_map_incrementally to
render shape layers incrementally

* Thuban/UI/renderer.py (ScreenRenderer.RenderMap): Removed the
map and region parameters which are now in the constructor
(ScreenRenderer.RenderMapIncrementally): New. Public frontend for
the inherited render_map_incrementally.
(BaseRenderer.draw_shape_layer): Removed.
(ScreenRenderer.draw_selection_incrementally): New. The selection
drawing part of the removed draw_shape_layer as a generator
(ScreenRenderer.layer_shapes): Update because of the region
parameter change
(ExportRenderer.__init__): New. Extend the inherited constructor
with the destination region for the drawing
(ExportRenderer.RenderMap): Removed the map and region parameters
which are now in the constructor

* Thuban/UI/view.py (MapCanvas.PreviewBitmap): New. Return a
bitmap suitable for a preview in a tool
(CanvasPanTool.MouseMove): Use the PreviewBitmap method to get the
bitmap
(MapPrintout.draw_on_dc): Adapt to new renderer interface
(MapCanvas.OnPaint): Handle drawing the selection bitmap if it
exists
(MapCanvas.OnIdle): Update the logic to deal with incremental
rendering and the selection bitmap
(MapCanvas._do_redraw): Handle the instantiation of the render
iterator and the redraws during rendering
(MapCanvas._render_iterator): New. Generator to incrementally
redraw both bitmaps
(MapCanvas.Export): Adapt to new renderer interface.
(MapCanvas.full_redraw): Reset the selection bitmap and the
renderer iterator too
(MapCanvas.redraw_selection): New. Force a redraw of the selection
bitmap
(MapCanvas.shape_selected): Only redraw the selection bitmap

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 bh 1866 from __future__ import generators
14    
15 bh 6 __version__ = "$Revision$"
16 bh 1866 # $Source$
17     # $Id$
18 bh 6
19 frank 910 import os.path
20 bh 1866 import time
21     import traceback
22 jonathan 799
23 jonathan 1285 from wxPython.wx import wxWindow, \
24 bh 6 wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
25 jonathan 822 EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
26 bh 1456 wxPlatform, wxBeginBusyCursor, wxEndBusyCursor, wxFileDialog, wxSAVE, \
27 jonathan 1385 wxOVERWRITE_PROMPT, wxID_OK
28 bh 6
29 frank 910 # Export related stuff
30     if wxPlatform == '__WXMSW__':
31     from wxPython.wx import wxMetaFileDC
32 bh 6
33     from wxPython import wx
34    
35 bh 1866 from Thuban import _
36    
37 bh 1456 from Thuban.Model.messages import MAP_LAYERS_CHANGED, LAYER_CHANGED, \
38     LAYER_VISIBILITY_CHANGED
39 bh 6
40 frank 910 from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
41 bh 6
42     import labeldialog
43    
44 bh 1454 from viewport import ViewPort, PanTool, output_transform
45 bh 6
46 jonathan 1385 class CanvasPanTool(PanTool):
47 bh 6
48 jonathan 1385 """The Canvas Pan Tool"""
49 bh 6
50     def MouseMove(self, event):
51     if self.dragging:
52 jonathan 1385 PanTool.MouseMove(self, event)
53 bh 57 sx, sy = self.start
54 bh 6 x, y = self.current
55     width, height = self.view.GetSizeTuple()
56 bh 159
57     bitmapdc = wx.wxMemoryDC()
58 bh 1866 bitmapdc.SelectObject(self.view.PreviewBitmap())
59 bh 159
60 bh 6 dc = self.view.drag_dc
61 bh 159 dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
62 bh 6
63     class MapPrintout(wx.wxPrintout):
64    
65     """
66     wxPrintout class for printing Thuban maps
67     """
68    
69 frank 910 def __init__(self, canvas, map, region, selected_layer, selected_shapes):
70 bh 6 wx.wxPrintout.__init__(self)
71 frank 910 self.canvas = canvas
72 bh 6 self.map = map
73 frank 910 self.region = region
74     self.selected_layer = selected_layer
75     self.selected_shapes = selected_shapes
76 bh 6
77     def GetPageInfo(self):
78     return (1, 1, 1, 1)
79    
80     def HasPage(self, pagenum):
81     return pagenum == 1
82    
83     def OnPrintPage(self, pagenum):
84     if pagenum == 1:
85     self.draw_on_dc(self.GetDC())
86    
87     def draw_on_dc(self, dc):
88     width, height = self.GetPageSizePixels()
89 bh 1454 scale, offset, mapregion = output_transform(self.canvas.scale,
90     self.canvas.offset,
91     self.canvas.GetSizeTuple(),
92     self.GetPageSizePixels())
93 bh 6 resx, resy = self.GetPPIPrinter()
94 bh 1866 canvas_scale = self.canvas.scale
95 frank 910 x, y, width, height = self.region
96 bh 1866 renderer = PrinterRenderer(dc, self.map, scale, offset,
97     region = (0, 0,
98     (width/canvas_scale)*scale,
99     (height/canvas_scale)*scale),
100     resolution = resy,
101     destination_region = mapregion)
102     renderer.RenderMap(self.selected_layer, self.selected_shapes)
103 jan 1035 return True
104 bh 6
105 bh 1866
106 jonathan 1385 class MapCanvas(wxWindow, ViewPort):
107 bh 6
108     """A widget that displays a map and offers some interaction"""
109    
110 bh 535 def __init__(self, parent, winid):
111 bh 6 wxWindow.__init__(self, parent, winid)
112 jonathan 1385 ViewPort.__init__(self)
113    
114 bh 6 self.SetBackgroundColour(wxColour(255, 255, 255))
115 bh 125
116     # the bitmap serving as backing store
117     self.bitmap = None
118 bh 1866 # the monochrome bitmap with the selection if any
119     self.selection_bitmap = None
120 bh 125
121 jonathan 1344 self.backgroundColor = wx.wxWHITE_BRUSH
122    
123 bh 1866 # The rendering iterator object. Used when rendering
124     # incrementally
125     self.render_iter = None
126 bh 1552
127 bh 125 # subscribe the WX events we're interested in
128 bh 6 EVT_PAINT(self, self.OnPaint)
129     EVT_LEFT_DOWN(self, self.OnLeftDown)
130     EVT_LEFT_UP(self, self.OnLeftUp)
131     EVT_MOTION(self, self.OnMotion)
132 bh 122 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
133 bh 125 wx.EVT_SIZE(self, self.OnSize)
134 jonathan 1344 wx.EVT_IDLE(self, self.OnIdle)
135 bh 6
136 bh 122 def __del__(self):
137     wxWindow.__del__(self)
138 jonathan 1385 ViewPort.__del__(self)
139 bh 122
140 bh 1866 def PreviewBitmap(self):
141     return self.bitmap
142    
143 jonathan 1385 def PanTool(self):
144     """Start the canvas pan tool"""
145     self.SelectTool(CanvasPanTool(self))
146    
147     def SetMap(self, map):
148     redraw_channels = (MAP_LAYERS_CHANGED, LAYER_CHANGED,
149     LAYER_VISIBILITY_CHANGED)
150     if self.Map() is not None:
151     for channel in redraw_channels:
152     self.Map().Unsubscribe(channel, self.full_redraw)
153 bh 535
154 jonathan 1385 ViewPort.SetMap(self, map)
155 bh 535
156 jonathan 1385 if self.Map() is not None:
157     for channel in redraw_channels:
158     self.Map().Subscribe(channel, self.full_redraw)
159 bh 535
160 jonathan 1385 # force a redraw. If map is not empty, it's already been called
161     # by FitMapToWindow but if map is empty it hasn't been called
162     # yet so we have to explicitly call it.
163     self.full_redraw()
164 bh 535
165 bh 6 def OnPaint(self, event):
166     dc = wxPaintDC(self)
167 jonathan 1385 if self.Map() is not None and self.Map().HasLayers():
168 bh 1866 if self.bitmap is not None:
169 jonathan 1385 dc.BeginDrawing()
170     dc.DrawBitmap(self.bitmap, 0, 0)
171 bh 1866 if self.selection_bitmap is not None:
172     dc.DrawBitmap(self.selection_bitmap, 0, 0, True)
173 jonathan 1385 dc.EndDrawing()
174 jonathan 1344 else:
175     # If we've got no map or if the map is empty, simply clear
176     # the screen.
177 jonathan 799
178 jonathan 1344 # XXX it's probably possible to get rid of this. The
179     # background color of the window is already white and the
180     # only thing we may have to do is to call self.Refresh()
181     # with a true argument in the right places.
182     dc.BeginDrawing()
183     dc.SetBackground(self.backgroundColor)
184     dc.Clear()
185     dc.EndDrawing()
186 bh 246
187 jonathan 1344 def OnIdle(self, event):
188 bh 1552 """Idle handler. Redraw the bitmap if necessary"""
189 bh 1866 if (self.bitmap is None
190     or self.render_iter is not None
191     or (self.HasSelectedShapes()
192     and self.selection_bitmap is None)):
193     event.RequestMore(self._do_redraw())
194 bh 125
195 bh 1866 def _do_redraw(self):
196     """Redraw a bit and return whether this method has to be called again.
197 bh 6
198 bh 1866 Called by OnIdle to handle the actual redraw. Redraw is
199     incremental for both the bitmap with the normal layers and the
200     bitmap with the selection.
201     """
202     finished = False
203     if self.render_iter is not None:
204 bh 1552 try:
205 bh 1866 if self.render_iter.next():
206     # Redraw if the last preview redraw was some time
207     # ago and the user is not currently dragging the
208     # mouse because redrawing would interfere with what
209     # the current tool is drawing on the window.
210     if not self.dragging \
211     and time.time() - self.render_last_preview > 0.5:
212     client_dc = wxClientDC(self)
213     client_dc.BeginDrawing()
214     client_dc.DrawBitmap(self.bitmap, 0, 0)
215     client_dc.EndDrawing()
216     self.render_last_preview = time.time()
217     else:
218     self.render_iter = None
219     # Redraw if not dragging because redrawing would
220     # interfere with what the current tool is drawing on
221     # the window.
222     if not self.dragging:
223     self.redraw()
224     finished = True
225     except StopIteration:
226     finished = True
227     self.render_iter = None
228 bh 1552 except:
229 bh 1866 finished = True
230     self.render_iter = None
231     traceback.print_exc()
232     else:
233     self.render_iter = self._render_iterator()
234     self.render_last_preview = time.time()
235     return not finished
236 jonathan 1344
237 bh 1866 def _render_iterator(self):
238 bh 1552 width, height = self.GetSizeTuple()
239     dc = wx.wxMemoryDC()
240 bh 6
241 bh 1866 render_start = time.time()
242 bh 57
243 bh 1866 if self.bitmap is None:
244     self.bitmap = wx.wxEmptyBitmap(width, height)
245     dc.SelectObject(self.bitmap)
246     dc.BeginDrawing()
247 bh 149
248 bh 1866 dc.SetBackground(self.backgroundColor)
249     dc.Clear()
250 bh 125
251 bh 1866 # draw the map into the bitmap
252     renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
253     (0, 0, width, height))
254     for cont in renderer.RenderMapIncrementally():
255     yield True
256 jonathan 1344
257 bh 1866 dc.EndDrawing()
258     dc.SelectObject(wx.wxNullBitmap)
259 bh 125
260 bh 1866 if self.HasSelectedShapes() and self.selection_bitmap is None:
261     bitmap = wx.wxEmptyBitmap(width, height)
262     dc.SelectObject(bitmap)
263     dc.BeginDrawing()
264     dc.SetBackground(wx.wxWHITE_BRUSH)
265     dc.Clear()
266 bh 6
267 bh 1866 renderer = ScreenRenderer(dc, self.Map(), self.scale, self.offset,
268     (0, 0, width, height))
269     layer = self.SelectedLayer()
270     shapes = self.selection.SelectedShapes()
271     for cont in renderer.draw_selection_incrementally(layer, shapes):
272     yield True
273    
274     dc.EndDrawing()
275     dc.SelectObject(wx.wxNullBitmap)
276    
277     bitmap.SetMask(wx.wxMaskColour(bitmap, wx.wxWHITE))
278     self.selection_bitmap = bitmap
279    
280     yield False
281    
282 frank 910 def Export(self):
283 jonathan 967
284 frank 910 if hasattr(self, "export_path"):
285     export_path = self.export_path
286     else:
287     export_path="."
288     dlg = wxFileDialog(self, _("Export Map"), export_path, "",
289     "Enhanced Metafile (*.wmf)|*.wmf",
290     wxSAVE|wxOVERWRITE_PROMPT)
291     if dlg.ShowModal() == wxID_OK:
292     self.export_path = os.path.dirname(dlg.GetPath())
293     dc = wxMetaFileDC(dlg.GetPath())
294    
295 bh 1454 scale, offset, mapregion = output_transform(self.scale,
296     self.offset,
297     self.GetSizeTuple(),
298     dc.GetSizeTuple())
299 frank 910
300     selected_layer = self.selection.SelectedLayer()
301     selected_shapes = self.selection.SelectedShapes()
302    
303 bh 1866 renderer = ExportRenderer(dc, self.Map(), scale, offset,
304     region = (0, 0,
305     (width/self.scale)*scale,
306     (height/self.scale)*scale),
307     destination_region = mapregion)
308 frank 910
309     # Pass the entire bitmap as update region to the renderer.
310     # We're redrawing the whole bitmap, after all.
311     width, height = self.GetSizeTuple()
312 bh 1866 renderer.RenderMap(selected_layer, selected_shapes)
313 frank 910 dc.EndDrawing()
314     dc.Close()
315     dlg.Destroy()
316    
317 bh 6 def Print(self):
318     printer = wx.wxPrinter()
319 frank 910 width, height = self.GetSizeTuple()
320     selected_layer = self.selection.SelectedLayer()
321     selected_shapes = self.selection.SelectedShapes()
322    
323 jonathan 1385 printout = MapPrintout(self, self.Map(), (0, 0, width, height),
324 frank 910 selected_layer, selected_shapes)
325 jan 1035 printer.Print(self, printout, True)
326 bh 6 printout.Destroy()
327 bh 246
328 bh 6 def redraw(self, *args):
329 jonathan 1344 self.Refresh(False)
330 bh 6
331 bh 125 def full_redraw(self, *args):
332     self.bitmap = None
333 bh 1866 self.selection_bitmap = None
334     self.render_iter = None
335 bh 125 self.redraw()
336    
337 bh 1866 def redraw_selection(self, *args):
338     self.selection_bitmap = None
339     self.render_iter = None
340     self.redraw()
341    
342 jonathan 1385 def map_projection_changed(self, map, old_proj):
343     ViewPort.map_projection_changed(self, map, old_proj)
344 bh 125 self.full_redraw()
345 bh 6
346 jonathan 1221 def layer_projection_changed(self, *args):
347 jonathan 1385 ViewPort.layer_projection_changed(self, args)
348 jonathan 1221 self.full_redraw()
349    
350 bh 6 def set_view_transform(self, scale, offset):
351 jonathan 1385 ViewPort.set_view_transform(self, scale, offset)
352 bh 125 self.full_redraw()
353 bh 6
354 jonathan 1385 def GetPortSizeTuple(self):
355     return self.GetSizeTuple()
356 jonathan 967
357 bh 6 def OnLeftDown(self, event):
358 jonathan 1385 self.MouseLeftDown(event)
359 bh 6 if self.tool is not None:
360     self.drag_dc = wxClientDC(self)
361     self.drag_dc.SetLogicalFunction(wxINVERT)
362     self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
363     self.tool.Show(self.drag_dc)
364 bh 1460 self.CaptureMouse()
365 bh 6 self.dragging = 1
366 bh 246
367 bh 6 def OnLeftUp(self, event):
368 bh 1652 """Handle EVT_LEFT_UP
369    
370     Release the mouse if it was captured, if a tool is active call
371     its Hide method and call self.MouseLeftUp.
372     """
373     # It's important that ReleaseMouse is called before MouseLeftUp.
374     # MouseLeftUp may pop up modal dialogs which leads to an
375     # effectively frozen X session because the user can only
376     # interact with the dialog but the mouse is still grabbed by the
377     # canvas.
378 bh 6 if self.dragging:
379 bh 1652 if self.HasCapture():
380     self.ReleaseMouse()
381 bh 404 try:
382     self.tool.Hide(self.drag_dc)
383     finally:
384     self.drag_dc = None
385     self.dragging = 0
386 bh 1652 self.MouseLeftUp(event)
387 bh 6
388     def OnMotion(self, event):
389     if self.dragging:
390     self.tool.Hide(self.drag_dc)
391 jonathan 1385
392     self.MouseMove(event)
393    
394     if self.dragging:
395 bh 6 self.tool.Show(self.drag_dc)
396    
397 bh 122 def OnLeaveWindow(self, event):
398     self.set_current_position(None)
399    
400 bh 125 def OnSize(self, event):
401     # the window's size has changed. We have to get a new bitmap. If
402     # we want to be clever we could try to get by without throwing
403     # everything away. E.g. when the window gets smaller, we could
404     # either keep the bitmap or create the new one from the old one.
405     # Even when the window becomes larger some parts of the bitmap
406     # could be reused.
407     self.full_redraw()
408    
409 bh 6 def shape_selected(self, layer, shape):
410 bh 535 """Receiver for the SHAPES_SELECTED messages. Redraw the map."""
411     # The selection object takes care that it only issues
412     # SHAPES_SELECTED messages when the set of selected shapes has
413 bh 1866 # actually changed, so we can do a full redraw of the
414     # selection_bitmap unconditionally.
415 jonathan 1385 ViewPort.shape_selected(self, layer, shape)
416 bh 1866 self.redraw_selection()
417 bh 6
418 jonathan 1385 def GetTextExtent(self, text):
419     dc = wxClientDC(self)
420     font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
421     dc.SetFont(font)
422     return dc.GetTextExtent(text)
423 bh 159
424 jonathan 1385 def LabelShapeAt(self, x, y, text=None):
425 bh 295 """Add or remove a label at window position x, y.
426 bh 1454
427 bh 295 If there's a label at the given position, remove it. Otherwise
428     determine the shape at the position, run the label dialog and
429 jonathan 1385 unless the user cancels the dialog, add a label.
430 bh 295 """
431 bh 6 label_layer = self.map.LabelLayer()
432     layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
433     if layer is None and shape_index is not None:
434 jonathan 1385 ViewPort.LabelShapeAt(self, x, y)
435 bh 6 elif layer is not None:
436 bh 1219 text = labeldialog.run_label_dialog(self,
437     layer.ShapeStore().Table(),
438     shape_index)
439 jonathan 1385 ViewPort.LabelShapeAt(self, x, y, text)

Properties

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

[email protected]
ViewVC Help
Powered by ViewVC 1.1.26