/[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 1866 - (show 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 # Copyright (c) 2001, 2002, 2003 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 # Frank Koormann <[email protected]>
5 #
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 from __future__ import generators
14
15 __version__ = "$Revision$"
16 # $Source$
17 # $Id$
18
19 import os.path
20 import time
21 import traceback
22
23 from wxPython.wx import wxWindow, \
24 wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
25 EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW, \
26 wxPlatform, wxBeginBusyCursor, wxEndBusyCursor, wxFileDialog, wxSAVE, \
27 wxOVERWRITE_PROMPT, wxID_OK
28
29 # Export related stuff
30 if wxPlatform == '__WXMSW__':
31 from wxPython.wx import wxMetaFileDC
32
33 from wxPython import wx
34
35 from Thuban import _
36
37 from Thuban.Model.messages import MAP_LAYERS_CHANGED, LAYER_CHANGED, \
38 LAYER_VISIBILITY_CHANGED
39
40 from renderer import ScreenRenderer, ExportRenderer, PrinterRenderer
41
42 import labeldialog
43
44 from viewport import ViewPort, PanTool, output_transform
45
46 class CanvasPanTool(PanTool):
47
48 """The Canvas Pan Tool"""
49
50 def MouseMove(self, event):
51 if self.dragging:
52 PanTool.MouseMove(self, event)
53 sx, sy = self.start
54 x, y = self.current
55 width, height = self.view.GetSizeTuple()
56
57 bitmapdc = wx.wxMemoryDC()
58 bitmapdc.SelectObject(self.view.PreviewBitmap())
59
60 dc = self.view.drag_dc
61 dc.Blit(0, 0, width, height, bitmapdc, sx - x, sy - y)
62
63 class MapPrintout(wx.wxPrintout):
64
65 """
66 wxPrintout class for printing Thuban maps
67 """
68
69 def __init__(self, canvas, map, region, selected_layer, selected_shapes):
70 wx.wxPrintout.__init__(self)
71 self.canvas = canvas
72 self.map = map
73 self.region = region
74 self.selected_layer = selected_layer
75 self.selected_shapes = selected_shapes
76
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 scale, offset, mapregion = output_transform(self.canvas.scale,
90 self.canvas.offset,
91 self.canvas.GetSizeTuple(),
92 self.GetPageSizePixels())
93 resx, resy = self.GetPPIPrinter()
94 canvas_scale = self.canvas.scale
95 x, y, width, height = self.region
96 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 return True
104
105
106 class MapCanvas(wxWindow, ViewPort):
107
108 """A widget that displays a map and offers some interaction"""
109
110 def __init__(self, parent, winid):
111 wxWindow.__init__(self, parent, winid)
112 ViewPort.__init__(self)
113
114 self.SetBackgroundColour(wxColour(255, 255, 255))
115
116 # the bitmap serving as backing store
117 self.bitmap = None
118 # the monochrome bitmap with the selection if any
119 self.selection_bitmap = None
120
121 self.backgroundColor = wx.wxWHITE_BRUSH
122
123 # The rendering iterator object. Used when rendering
124 # incrementally
125 self.render_iter = None
126
127 # subscribe the WX events we're interested in
128 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 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
133 wx.EVT_SIZE(self, self.OnSize)
134 wx.EVT_IDLE(self, self.OnIdle)
135
136 def __del__(self):
137 wxWindow.__del__(self)
138 ViewPort.__del__(self)
139
140 def PreviewBitmap(self):
141 return self.bitmap
142
143 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
154 ViewPort.SetMap(self, map)
155
156 if self.Map() is not None:
157 for channel in redraw_channels:
158 self.Map().Subscribe(channel, self.full_redraw)
159
160 # 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
165 def OnPaint(self, event):
166 dc = wxPaintDC(self)
167 if self.Map() is not None and self.Map().HasLayers():
168 if self.bitmap is not None:
169 dc.BeginDrawing()
170 dc.DrawBitmap(self.bitmap, 0, 0)
171 if self.selection_bitmap is not None:
172 dc.DrawBitmap(self.selection_bitmap, 0, 0, True)
173 dc.EndDrawing()
174 else:
175 # If we've got no map or if the map is empty, simply clear
176 # the screen.
177
178 # 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
187 def OnIdle(self, event):
188 """Idle handler. Redraw the bitmap if necessary"""
189 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
195 def _do_redraw(self):
196 """Redraw a bit and return whether this method has to be called again.
197
198 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 try:
205 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 except:
229 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
237 def _render_iterator(self):
238 width, height = self.GetSizeTuple()
239 dc = wx.wxMemoryDC()
240
241 render_start = time.time()
242
243 if self.bitmap is None:
244 self.bitmap = wx.wxEmptyBitmap(width, height)
245 dc.SelectObject(self.bitmap)
246 dc.BeginDrawing()
247
248 dc.SetBackground(self.backgroundColor)
249 dc.Clear()
250
251 # 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
257 dc.EndDrawing()
258 dc.SelectObject(wx.wxNullBitmap)
259
260 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
267 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 def Export(self):
283
284 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 scale, offset, mapregion = output_transform(self.scale,
296 self.offset,
297 self.GetSizeTuple(),
298 dc.GetSizeTuple())
299
300 selected_layer = self.selection.SelectedLayer()
301 selected_shapes = self.selection.SelectedShapes()
302
303 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
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 renderer.RenderMap(selected_layer, selected_shapes)
313 dc.EndDrawing()
314 dc.Close()
315 dlg.Destroy()
316
317 def Print(self):
318 printer = wx.wxPrinter()
319 width, height = self.GetSizeTuple()
320 selected_layer = self.selection.SelectedLayer()
321 selected_shapes = self.selection.SelectedShapes()
322
323 printout = MapPrintout(self, self.Map(), (0, 0, width, height),
324 selected_layer, selected_shapes)
325 printer.Print(self, printout, True)
326 printout.Destroy()
327
328 def redraw(self, *args):
329 self.Refresh(False)
330
331 def full_redraw(self, *args):
332 self.bitmap = None
333 self.selection_bitmap = None
334 self.render_iter = None
335 self.redraw()
336
337 def redraw_selection(self, *args):
338 self.selection_bitmap = None
339 self.render_iter = None
340 self.redraw()
341
342 def map_projection_changed(self, map, old_proj):
343 ViewPort.map_projection_changed(self, map, old_proj)
344 self.full_redraw()
345
346 def layer_projection_changed(self, *args):
347 ViewPort.layer_projection_changed(self, args)
348 self.full_redraw()
349
350 def set_view_transform(self, scale, offset):
351 ViewPort.set_view_transform(self, scale, offset)
352 self.full_redraw()
353
354 def GetPortSizeTuple(self):
355 return self.GetSizeTuple()
356
357 def OnLeftDown(self, event):
358 self.MouseLeftDown(event)
359 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 self.CaptureMouse()
365 self.dragging = 1
366
367 def OnLeftUp(self, event):
368 """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 if self.dragging:
379 if self.HasCapture():
380 self.ReleaseMouse()
381 try:
382 self.tool.Hide(self.drag_dc)
383 finally:
384 self.drag_dc = None
385 self.dragging = 0
386 self.MouseLeftUp(event)
387
388 def OnMotion(self, event):
389 if self.dragging:
390 self.tool.Hide(self.drag_dc)
391
392 self.MouseMove(event)
393
394 if self.dragging:
395 self.tool.Show(self.drag_dc)
396
397 def OnLeaveWindow(self, event):
398 self.set_current_position(None)
399
400 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 def shape_selected(self, layer, shape):
410 """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 # actually changed, so we can do a full redraw of the
414 # selection_bitmap unconditionally.
415 ViewPort.shape_selected(self, layer, shape)
416 self.redraw_selection()
417
418 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
424 def LabelShapeAt(self, x, y, text=None):
425 """Add or remove a label at window position x, y.
426
427 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 unless the user cancels the dialog, add a label.
430 """
431 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 ViewPort.LabelShapeAt(self, x, y)
435 elif layer is not None:
436 text = labeldialog.run_label_dialog(self,
437 layer.ShapeStore().Table(),
438 shape_index)
439 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