/[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 149 - (show annotations)
Tue May 7 16:41:07 2002 UTC (22 years, 10 months ago) by bh
Original Path: trunk/thuban/Thuban/UI/view.py
File MIME type: text/x-python
File size: 25257 byte(s)
	* Thuban/UI/view.py (MapCanvas.do_redraw): Pass the entire bitmap
	as update_region to the renderer.

1 # Copyright (c) 2001, 2002 by Intevation GmbH
2 # Authors:
3 # Bernhard Herzog <[email protected]>
4 #
5 # This program is free software under the GPL (>=v2)
6 # Read the file COPYING coming with Thuban for details.
7
8 """
9 Classes for display of a map and interaction with it
10 """
11
12 __version__ = "$Revision$"
13
14 from math import hypot
15
16 from wxPython.wx import wxWindow,\
17 wxPaintDC, wxColour, wxClientDC, wxINVERT, wxTRANSPARENT_BRUSH, wxFont,\
18 EVT_PAINT, EVT_LEFT_DOWN, EVT_LEFT_UP, EVT_MOTION, EVT_LEAVE_WINDOW
19
20
21 from wxPython import wx
22
23 from wxproj import point_in_polygon_shape, shape_centroid
24
25
26 from Thuban.Model.messages import MAP_PROJECTION_CHANGED, \
27 LAYERS_CHANGED, LAYER_LEGEND_CHANGED, LAYER_VISIBILITY_CHANGED
28 from Thuban.Model.layer import SHAPETYPE_POLYGON, SHAPETYPE_ARC, \
29 SHAPETYPE_POINT
30 from Thuban.Model.label import ALIGN_CENTER, ALIGN_TOP, ALIGN_BOTTOM, \
31 ALIGN_LEFT, ALIGN_RIGHT
32 from Thuban.Lib.connector import Publisher
33
34 from renderer import ScreenRenderer, PrinterRender
35
36 import labeldialog
37
38 from messages import SELECTED_SHAPE, VIEW_POSITION
39
40
41 #
42 # The tools
43 #
44
45 class Tool:
46
47 """
48 Base class for the interactive tools
49 """
50
51 def __init__(self, view):
52 """Intitialize the tool. The view is the canvas displaying the map"""
53 self.view = view
54 self.start = self.current = None
55 self.dragging = 0
56 self.drawn = 0
57
58 def Name(self):
59 """Return the tool's name"""
60 return ''
61
62 def drag_start(self, x, y):
63 self.start = self.current = x, y
64 self.dragging = 1
65
66 def drag_move(self, x, y):
67 self.current = x, y
68
69 def drag_stop(self, x, y):
70 self.current = x, y
71 self.dragging = 0
72
73 def Show(self, dc):
74 if not self.drawn:
75 self.draw(dc)
76 self.drawn = 1
77
78 def Hide(self, dc):
79 if self.drawn:
80 self.draw(dc)
81 self.drawn = 0
82
83 def draw(self, dc):
84 pass
85
86 def MouseDown(self, event):
87 self.drag_start(event.m_x, event.m_y)
88
89 def MouseMove(self, event):
90 if self.dragging:
91 self.drag_move(event.m_x, event.m_y)
92
93 def MouseUp(self, event):
94 if self.dragging:
95 self.drag_move(event.m_x, event.m_y)
96
97 def Cancel(self):
98 self.dragging = 0
99
100
101 class RectTool(Tool):
102
103 """Base class for tools that draw rectangles while dragging"""
104
105 def draw(self, dc):
106 sx, sy = self.start
107 cx, cy = self.current
108 dc.DrawRectangle(sx, sy, cx - sx, cy - sy)
109
110 class ZoomInTool(RectTool):
111
112 """The Zoom-In Tool"""
113
114 def Name(self):
115 return "ZoomInTool"
116
117 def proj_rect(self):
118 """return the rectangle given by start and current in projected
119 coordinates"""
120 sx, sy = self.start
121 cx, cy = self.current
122 left, top = self.view.win_to_proj(sx, sy)
123 right, bottom = self.view.win_to_proj(cx, cy)
124 return (min(left, right), min(top, bottom),
125 max(left, right), max(top, bottom))
126
127 def MouseUp(self, event):
128 if self.dragging:
129 Tool.MouseUp(self, event)
130 sx, sy = self.start
131 cx, cy = self.current
132 if sx == cx and sy == cy:
133 # Just a mouse click. Simply zoom in by a factor of two
134 self.view.ZoomFactor(2, center = (cx, cy))
135 else:
136 # A drag. Zoom in to the rectangle
137 self.view.FitRectToWindow(self.proj_rect())
138
139
140 class ZoomOutTool(RectTool):
141
142 """The Zoom-Out Tool"""
143
144 def Name(self):
145 return "ZoomOutTool"
146
147 def MouseUp(self, event):
148 if self.dragging:
149 Tool.MouseUp(self, event)
150 sx, sy = self.start
151 cx, cy = self.current
152 if sx == cx and sy == cy:
153 # Just a mouse click. Simply zoom out by a factor of two
154 self.view.ZoomFactor(0.5, center = (cy, cy))
155 else:
156 # A drag. Zoom out to the rectangle
157 self.view.ZoomOutToRect((min(sx, cx), min(sy, cy),
158 max(sx, cx), max(sy, cy)))
159
160
161 class PanTool(Tool):
162
163 """The Pan Tool"""
164
165 def Name(self):
166 return "PanTool"
167
168 def MouseMove(self, event):
169 if self.dragging:
170 x0, y0 = self.current
171 Tool.MouseMove(self, event)
172 x, y = self.current
173 width, height = self.view.GetSizeTuple()
174 dc = self.view.drag_dc
175 dc.Blit(0, 0, width, height, dc, x0 - x, y0 - y)
176
177 def MouseUp(self, event):
178 if self.dragging:
179 Tool.MouseUp(self, event)
180 sx, sy = self.start
181 cx, cy = self.current
182 self.view.Translate(cx - sx, cy - sy)
183
184 class IdentifyTool(Tool):
185
186 """The "Identify" Tool"""
187
188 def Name(self):
189 return "IdentifyTool"
190
191 def MouseUp(self, event):
192 self.view.SelectShapeAt(event.m_x, event.m_y)
193
194
195 class LabelTool(Tool):
196
197 """The "Label" Tool"""
198
199 def Name(self):
200 return "LabelTool"
201
202 def MouseUp(self, event):
203 self.view.LabelShapeAt(event.m_x, event.m_y)
204
205
206
207
208 class MapPrintout(wx.wxPrintout):
209
210 """
211 wxPrintout class for printing Thuban maps
212 """
213
214 def __init__(self, map):
215 wx.wxPrintout.__init__(self)
216 self.map = map
217
218 def GetPageInfo(self):
219 return (1, 1, 1, 1)
220
221 def HasPage(self, pagenum):
222 return pagenum == 1
223
224 def OnPrintPage(self, pagenum):
225 if pagenum == 1:
226 self.draw_on_dc(self.GetDC())
227
228 def draw_on_dc(self, dc):
229 width, height = self.GetPageSizePixels()
230 llx, lly, urx, ury = self.map.ProjectedBoundingBox()
231 scalex = width / (urx - llx)
232 scaley = height / (ury - lly)
233 scale = min(scalex, scaley)
234 offx = 0.5 * (width - (urx + llx) * scale)
235 offy = 0.5 * (height + (ury + lly) * scale)
236
237 resx, resy = self.GetPPIPrinter()
238 renderer = PrinterRender(dc, scale, (offx, offy), resolution = resx)
239 renderer.RenderMap(self.map)
240 return wx.true
241
242
243 class MapCanvas(wxWindow, Publisher):
244
245 """A widget that displays a map and offers some interaction"""
246
247 def __init__(self, parent, winid, interactor):
248 wxWindow.__init__(self, parent, winid)
249 self.SetBackgroundColour(wxColour(255, 255, 255))
250
251 # the map displayed in this canvas. Set with SetMap()
252 self.map = None
253
254 # scale and offset describe the transformation from projected
255 # coordinates to window coordinates.
256 self.scale = 1.0
257 self.offset = (0, 0)
258
259 # whether the user is currently dragging the mouse, i.e. moving
260 # the mouse while pressing a mouse button
261 self.dragging = 0
262
263 # the currently active tool
264 self.tool = None
265
266 # The current mouse position of the last OnMotion event or None
267 # if the mouse is outside the window.
268 self.current_position = None
269
270 # If true, OnIdle will call do_redraw to do the actual
271 # redrawing. Set by OnPaint to avoid some unnecessary redraws.
272 # To force a redraw call full_redraw().
273 self.redraw_on_idle = 0
274
275 # The region to update when idle
276 self.update_region = wx.wxRegion()
277
278 # the bitmap serving as backing store
279 self.bitmap = None
280
281 # the interactor
282 self.interactor = interactor
283 self.interactor.Subscribe(SELECTED_SHAPE, self.shape_selected)
284
285 # subscribe the WX events we're interested in
286 EVT_PAINT(self, self.OnPaint)
287 EVT_LEFT_DOWN(self, self.OnLeftDown)
288 EVT_LEFT_UP(self, self.OnLeftUp)
289 EVT_MOTION(self, self.OnMotion)
290 EVT_LEAVE_WINDOW(self, self.OnLeaveWindow)
291 wx.EVT_SIZE(self, self.OnSize)
292 wx.EVT_IDLE(self, self.OnIdle)
293
294 def __del__(self):
295 wxWindow.__del__(self)
296 Publisher.__del__(self)
297
298 def OnPaint(self, event):
299 dc = wxPaintDC(self)
300 if self.map is not None and self.map.HasLayers():
301 # We have a non-empty map. Redraw it in idle time
302 self.redraw_on_idle = 1
303 # update the region that has to be redrawn
304 self.update_region.UnionRegion(self.GetUpdateRegion())
305 else:
306 # If we've got no map or if the map is empty, simply clear
307 # the screen.
308
309 # XXX it's probably possible to get rid of this. The
310 # background color of the window is already white and the
311 # only thing we may have to do is to call self.Refresh()
312 # with a true argument in the right places.
313 dc.BeginDrawing()
314 dc.Clear()
315 dc.EndDrawing()
316
317 # clear the region
318 self.update_region = wx.wxRegion()
319
320 def do_redraw(self):
321 # This should only be called if we have a non-empty map.
322
323 # get the update region and reset it. We're not actually using
324 # it anymore, though.
325 update_box = self.update_region.GetBox()
326 self.update_region = wx.wxRegion()
327
328 # Get the window size.
329 width, height = self.GetSizeTuple()
330
331 # If self.bitmap's still there, reuse it. Otherwise redraw it
332 if self.bitmap is not None:
333 bitmap = self.bitmap
334 else:
335 bitmap = wx.wxEmptyBitmap(width, height)
336 dc = wx.wxMemoryDC()
337 dc.SelectObject(bitmap)
338 dc.BeginDrawing()
339
340 # clear the background
341 dc.SetBrush(wx.wxWHITE_BRUSH)
342 dc.SetPen(wx.wxTRANSPARENT_PEN)
343 dc.DrawRectangle(0, 0, width, height)
344
345 if 1: #self.interactor.selected_map is self.map:
346 selected_layer = self.interactor.selected_layer
347 selected_shape = self.interactor.selected_shape
348 else:
349 selected_layer = None
350 selected_shape = None
351
352 # draw the map into the bitmap
353 renderer = ScreenRenderer(dc, self.scale, self.offset)
354
355 # Pass the entire bitmap as update_region to the renderer.
356 # We're redrawing the whole bitmap, after all.
357 renderer.RenderMap(self.map, (0, 0, width, height),
358 selected_layer, selected_shape)
359
360 dc.EndDrawing()
361 dc.SelectObject(wx.wxNullBitmap)
362 self.bitmap = bitmap
363
364 # blit the bitmap to the screen
365 dc = wx.wxMemoryDC()
366 dc.SelectObject(bitmap)
367 clientdc = wxClientDC(self)
368 clientdc.BeginDrawing()
369 clientdc.Blit(0, 0, width, height, dc, 0, 0)
370 clientdc.EndDrawing()
371
372 def Print(self):
373 printer = wx.wxPrinter()
374 printout = MapPrintout(self.map)
375 printer.Print(self, printout, wx.true)
376 printout.Destroy()
377
378 def SetMap(self, map):
379 redraw_channels = (LAYERS_CHANGED, LAYER_LEGEND_CHANGED,
380 LAYER_VISIBILITY_CHANGED)
381 if self.map is not None:
382 for channel in redraw_channels:
383 self.map.Unsubscribe(channel, self.full_redraw)
384 self.map.Unsubscribe(MAP_PROJECTION_CHANGED,
385 self.projection_changed)
386 self.map = map
387 if self.map is not None:
388 for channel in redraw_channels:
389 self.map.Subscribe(channel, self.full_redraw)
390 self.map.Subscribe(MAP_PROJECTION_CHANGED, self.projection_changed)
391 self.FitMapToWindow()
392 # force a redraw. If map is not empty, it's already been called
393 # by FitMapToWindow but if map is empty it hasn't been called
394 # yet so we have to explicitly call it.
395 self.full_redraw()
396
397 def Map(self):
398 return self.map
399
400 def redraw(self, *args):
401 self.Refresh(0)
402
403 def full_redraw(self, *args):
404 self.bitmap = None
405 self.redraw()
406
407 def projection_changed(self, *args):
408 self.FitMapToWindow()
409 self.full_redraw()
410
411 def set_view_transform(self, scale, offset):
412 self.scale = scale
413 self.offset = offset
414 self.full_redraw()
415
416 def proj_to_win(self, x, y):
417 """\
418 Return the point in window coords given by projected coordinates x y
419 """
420 offx, offy = self.offset
421 return (self.scale * x + offx, -self.scale * y + offy)
422
423 def win_to_proj(self, x, y):
424 """\
425 Return the point in projected coordinates given by window coords x y
426 """
427 offx, offy = self.offset
428 return ((x - offx) / self.scale, (offy - y) / self.scale)
429
430 def FitRectToWindow(self, rect):
431 width, height = self.GetSizeTuple()
432 llx, lly, urx, ury = rect
433 if llx == urx or lly == ury:
434 # zero with or zero height. Do Nothing
435 return
436 scalex = width / (urx - llx)
437 scaley = height / (ury - lly)
438 scale = min(scalex, scaley)
439 offx = 0.5 * (width - (urx + llx) * scale)
440 offy = 0.5 * (height + (ury + lly) * scale)
441 self.set_view_transform(scale, (offx, offy))
442
443 def FitMapToWindow(self):
444 """\
445 Set the scale and offset so that the map is centered in the
446 window
447 """
448 bbox = self.map.ProjectedBoundingBox()
449 if bbox is not None:
450 self.FitRectToWindow(bbox)
451
452 def ZoomFactor(self, factor, center = None):
453 """Multiply the zoom by factor and center on center.
454
455 The optional parameter center is a point in window coordinates
456 that should be centered. If it is omitted, it defaults to the
457 center of the window
458 """
459 width, height = self.GetSizeTuple()
460 scale = self.scale * factor
461 offx, offy = self.offset
462 if center is not None:
463 cx, cy = center
464 else:
465 cx = width / 2
466 cy = height / 2
467 offset = (factor * (offx - cx) + width / 2,
468 factor * (offy - cy) + height / 2)
469 self.set_view_transform(scale, offset)
470
471 def ZoomOutToRect(self, rect):
472 # rect is given in window coordinates
473
474 # determine the bbox of the displayed region in projected
475 # coordinates
476 width, height = self.GetSizeTuple()
477 llx, lly = self.win_to_proj(0, height - 1)
478 urx, ury = self.win_to_proj(width - 1, 0)
479
480 sx, sy, ex, ey = rect
481 scalex = (ex - sx) / (urx - llx)
482 scaley = (ey - sy) / (ury - lly)
483 scale = min(scalex, scaley)
484
485 offx = 0.5 * ((ex + sx) - (urx + llx) * scale)
486 offy = 0.5 * ((ey + sy) + (ury + lly) * scale)
487 self.set_view_transform(scale, (offx, offy))
488
489 def Translate(self, dx, dy):
490 offx, offy = self.offset
491 self.set_view_transform(self.scale, (offx + dx, offy + dy))
492
493 def ZoomInTool(self):
494 self.tool = ZoomInTool(self)
495
496 def ZoomOutTool(self):
497 self.tool = ZoomOutTool(self)
498
499 def PanTool(self):
500 self.tool = PanTool(self)
501
502 def IdentifyTool(self):
503 self.tool = IdentifyTool(self)
504
505 def LabelTool(self):
506 self.tool = LabelTool(self)
507
508 def CurrentTool(self):
509 return self.tool and self.tool.Name() or None
510
511 def CurrentPosition(self):
512 """Return current position of the mouse in projected coordinates.
513
514 The result is a 2-tuple of floats with the coordinates. If the
515 mouse is not in the window, the result is None.
516 """
517 if self.current_position is not None:
518 x, y = self.current_position
519 return self.win_to_proj(x, y)
520 else:
521 return None
522
523 def set_current_position(self, event):
524 """Set the current position from event
525
526 Should be called by all events that contain mouse positions
527 especially EVT_MOTION. The event paramete may be None to
528 indicate the the pointer left the window.
529 """
530 if event is not None:
531 self.current_position = (event.m_x, event.m_y)
532 else:
533 self.current_position = None
534 self.issue(VIEW_POSITION)
535
536 def OnLeftDown(self, event):
537 self.set_current_position(event)
538 if self.tool is not None:
539 self.drag_dc = wxClientDC(self)
540 self.drag_dc.SetLogicalFunction(wxINVERT)
541 self.drag_dc.SetBrush(wxTRANSPARENT_BRUSH)
542 self.CaptureMouse()
543 self.tool.MouseDown(event)
544 self.tool.Show(self.drag_dc)
545 self.dragging = 1
546
547 def OnLeftUp(self, event):
548 self.ReleaseMouse()
549 self.set_current_position(event)
550 if self.dragging:
551 self.tool.Hide(self.drag_dc)
552 self.tool.MouseUp(event)
553 self.drag_dc = None
554 self.dragging = 0
555
556 def OnMotion(self, event):
557 self.set_current_position(event)
558 if self.dragging:
559 self.tool.Hide(self.drag_dc)
560 self.tool.MouseMove(event)
561 self.tool.Show(self.drag_dc)
562
563 def OnLeaveWindow(self, event):
564 self.set_current_position(None)
565
566 def OnIdle(self, event):
567 if self.redraw_on_idle:
568 self.do_redraw()
569 self.redraw_on_idle = 0
570
571 def OnSize(self, event):
572 # the window's size has changed. We have to get a new bitmap. If
573 # we want to be clever we could try to get by without throwing
574 # everything away. E.g. when the window gets smaller, we could
575 # either keep the bitmap or create the new one from the old one.
576 # Even when the window becomes larger some parts of the bitmap
577 # could be reused.
578 self.full_redraw()
579
580 def shape_selected(self, layer, shape):
581 self.full_redraw()
582
583 def find_shape_at(self, px, py, select_labels = 0, selected_layer = 1):
584 """Determine the shape at point px, py in window coords
585
586 Return the shape and the corresponding layer as a tuple (layer,
587 shape).
588
589 If the optional parameter select_labels is true (default false)
590 search through the labels. If a label is found return it's index
591 as the shape and None as the layer.
592
593 If the optional parameter selected_layer is true (default), only
594 search in the currently selected layer.
595 """
596 map_proj = self.map.projection
597 if map_proj is not None:
598 forward = map_proj.Forward
599 else:
600 forward = None
601
602 scale = self.scale
603 offx, offy = self.offset
604
605 if select_labels:
606 labels = self.map.LabelLayer().Labels()
607
608 if labels:
609 dc = wxClientDC(self)
610 font = wxFont(10, wx.wxSWISS, wx.wxNORMAL, wx.wxNORMAL)
611 dc.SetFont(font)
612 for i in range(len(labels) - 1, -1, -1):
613 label = labels[i]
614 x = label.x
615 y = label.y
616 text = label.text
617 if forward:
618 x, y = forward(x, y)
619 x = x * scale + offx
620 y = -y * scale + offy
621 width, height = dc.GetTextExtent(text)
622 if label.halign == ALIGN_LEFT:
623 # nothing to be done
624 pass
625 elif label.halign == ALIGN_RIGHT:
626 x = x - width
627 elif label.halign == ALIGN_CENTER:
628 x = x - width/2
629 if label.valign == ALIGN_TOP:
630 # nothing to be done
631 pass
632 elif label.valign == ALIGN_BOTTOM:
633 y = y - height
634 elif label.valign == ALIGN_CENTER:
635 y = y - height/2
636 if x <= px < x + width and y <= py <= y + height:
637 return None, i
638
639 if selected_layer:
640 layer = self.interactor.SelectedLayer()
641 if layer is not None:
642 layers = [layer]
643 else:
644 # no layer selected. Use an empty list to effectively
645 # ignore all layers.
646 layers = []
647 else:
648 layers = self.map.Layers()
649
650 for layer_index in range(len(layers) - 1, -1, -1):
651 layer = layers[layer_index]
652
653 # search only in visible layers
654 if not layer.Visible():
655 continue
656
657 filled = layer.fill is not None
658 stroked = layer.stroke is not None
659
660 layer_proj = layer.projection
661 if layer_proj is not None:
662 inverse = layer_proj.Inverse
663 else:
664 inverse = None
665
666 shapetype = layer.ShapeType()
667
668 select_shape = -1
669 if shapetype == SHAPETYPE_POLYGON:
670 for i in range(layer.NumShapes() - 1, -1, -1):
671 result = point_in_polygon_shape(layer.shapefile.cobject(),
672 i,
673 filled, stroked,
674 map_proj, layer_proj,
675 scale, -scale, offx, offy,
676 px, py)
677 if result:
678 select_shape = i
679 break
680 elif shapetype == SHAPETYPE_ARC:
681 for i in range(layer.NumShapes() - 1, -1, -1):
682 result = point_in_polygon_shape(layer.shapefile.cobject(),
683 i, 0, 1,
684 map_proj, layer_proj,
685 scale, -scale, offx, offy,
686 px, py)
687 if result < 0:
688 select_shape = i
689 break
690 elif shapetype == SHAPETYPE_POINT:
691 for i in range(layer.NumShapes() - 1, -1, -1):
692 shape = layer.Shape(i)
693 x, y = shape.Points()[0]
694 if inverse:
695 x, y = inverse(x, y)
696 if forward:
697 x, y = forward(x, y)
698 x = x * scale + offx
699 y = -y * scale + offy
700 if hypot(px - x, py - y) < 5:
701 select_shape = i
702 break
703
704 if select_shape >= 0:
705 return layer, select_shape
706 return None, None
707
708 def SelectShapeAt(self, x, y):
709 layer, shape = self.find_shape_at(x, y, selected_layer = 0)
710 # If layer is None, then shape will also be None. We don't want
711 # to deselect the currently selected layer, so we simply select
712 # the already selected layer again.
713 if layer is None:
714 layer = self.interactor.SelectedLayer()
715 self.interactor.SelectLayerAndShape(layer, shape)
716
717 def LabelShapeAt(self, x, y):
718 ox = x; oy = y
719 label_layer = self.map.LabelLayer()
720 layer, shape_index = self.find_shape_at(x, y, select_labels = 1)
721 if layer is None and shape_index is not None:
722 # a label was selected
723 label_layer.RemoveLabel(shape_index)
724 elif layer is not None:
725 text = labeldialog.run_label_dialog(self, layer.table, shape_index)
726 if text:
727 proj = self.map.projection
728 if proj is not None:
729 map_proj = proj
730 else:
731 map_proj = None
732 proj = layer.projection
733 if proj is not None:
734 layer_proj = proj
735 else:
736 layer_proj = None
737
738 shapetype = layer.ShapeType()
739 if shapetype == SHAPETYPE_POLYGON:
740 x, y = shape_centroid(layer.shapefile.cobject(),
741 shape_index,
742 map_proj, layer_proj, 1, 1, 0, 0)
743 if map_proj is not None:
744 x, y = map_proj.Inverse(x, y)
745 else:
746 shape = layer.Shape(shape_index)
747 if shapetype == SHAPETYPE_POINT:
748 x, y = shape.Points()[0]
749 else:
750 # assume SHAPETYPE_ARC
751 points = shape.Points()
752 x, y = points[len(points) / 2]
753 if layer_proj is not None:
754 x, y = layer_proj.Inverse(x, y)
755 if shapetype == SHAPETYPE_POINT:
756 halign = ALIGN_LEFT
757 valign = ALIGN_CENTER
758 elif shapetype == SHAPETYPE_POLYGON:
759 halign = ALIGN_CENTER
760 valign = ALIGN_CENTER
761 elif shapetype == SHAPETYPE_ARC:
762 halign = ALIGN_LEFT
763 valign = ALIGN_CENTER
764 label_layer.AddLabel(x, y, text,
765 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