Updating download.html page with Git information
[pythoncad.git] / PythonCAD / Interface / Gtk / gtkimage.py
blob96cd994ddb3fd937b1de79aa00dfbf98dcfcbafa
2 # Copyright (c) 2002, 2003, 2004, 2005, 2006, 2007 Art Haas
4 # This file is part of PythonCAD.
6 # PythonCAD is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
11 # PythonCAD is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with PythonCAD; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # the GTK code for displaying a drawing
24 from __future__ import division
26 import math
27 import types
28 import warnings
30 import pygtk
31 pygtk.require('2.0')
32 import gtk
33 import gobject
35 from PythonCAD.Interface.Gtk import gtkdimension
36 from PythonCAD.Interface.Gtk import gtktext
37 from PythonCAD.Interface.Gtk import gtkactions
39 from PythonCAD.Generic.image import Image
40 from PythonCAD.Generic.point import Point
41 from PythonCAD.Generic.conobject import ConstructionObject
42 from PythonCAD.Generic.color import Color
43 from PythonCAD.Generic.layer import Layer
44 from PythonCAD.Generic import tools
45 from PythonCAD.Generic import globals
46 from PythonCAD.Generic import keywords
47 from PythonCAD.Generic import prompt
49 from PythonCAD.Interface.Gtk import gtkshell
52 # Global variables
55 _debug = False ## SDB debug stuff
56 globals.gtkcolors = {}
57 globals.gtklinetypes = {}
59 class GTKImage(object):
60 """The GTK wrapping around an Image
62 The GTKImage class is derived from the Image class, so it shares all
63 the attributes and methods of that class. The GTKImage class has the
64 following addtional methods:
66 close(): Close a GTKImage.
67 getWindow(): Get the GTK Window used in the GTKImage.
68 getEntry(): Get the GTK Entry used in the GTKImage.
69 getDA(): Get the GTK Drawing Area used in the GTKImage.
70 {get/set}Pixmap(): Get/Set the GTK Pixmap used in the GTKImage.
71 {get/set}Prompt(): Get/Set the prompt.
72 {get/set}Tool(): Get/Set the tool used for working in the GTKImage.
73 {get/set}UnitsPerPixel(): Get/Set this display parameter.
74 {get/set}View(): Get/Set the current view seen in the GTKImage.
75 getTolerance(): Get the current drawing tolerance.
76 {get/set}GC(): Get/Set the graphic context used in the GTKImage.
77 {get/set}Point(): Get/Set the current coordinates of the tool.
78 {get/set}Size(): Get/Set the size of the drawing area.
79 pixToCoordTransform(): Convert pixels to x/y coordinates.
80 coordToPixTransform(): Convert x/y coordinates to pixels.
81 refresh(): Redraw the screen using the current pixmap.
82 redraw(): Recalculate the visible entities and redraw the screen.
83 addGroup(): Add a new ActionGroup to the GTKImage.
84 getGroup(): Retrieve an ActionGroup from the GTKImage.
85 deleteGroup(): Remove an ActionGroup from the GTKImage.
86 """
89 # class variables
92 from PythonCAD.Interface.Gtk import gtkentities
93 from PythonCAD.Interface.Gtk import gtkconobjs
94 from PythonCAD.Interface.Gtk import gtkmodify
95 from PythonCAD.Interface.Gtk import gtkmirror
96 from PythonCAD.Interface.Gtk import gtkprinting
97 from PythonCAD.Interface.Gtk import gtkedit
98 __inittool = {
99 tools.PasteTool : gtkedit.paste_mode_init,
100 tools.SelectTool : gtkedit.select_mode_init,
101 tools.DeselectTool : gtkedit.deselect_mode_init,
102 tools.PointTool : gtkentities.point_mode_init,
103 tools.SegmentTool : gtkentities.segment_mode_init,
104 tools.RectangleTool: gtkentities.rectangle_mode_init,
105 tools.CircleTool : gtkentities.circle_center_mode_init,
106 tools.TwoPointCircleTool : gtkentities.circle_tp_mode_init,
107 tools.ArcTool : gtkentities.arc_center_mode_init,
108 tools.ChamferTool : gtkentities.chamfer_mode_init,
109 tools.FilletTool: gtkentities.fillet_mode_init,
110 tools.LeaderTool : gtkentities.leader_mode_init,
111 tools.PolylineTool : gtkentities.polyline_mode_init,
112 tools.PolygonTool : gtkentities.polygon_mode_init,
113 tools.HCLineTool : gtkconobjs.hcline_mode_init,
114 tools.VCLineTool : gtkconobjs.vcline_mode_init,
115 tools.ACLineTool : gtkconobjs.acline_mode_init,
116 tools.CLineTool : gtkconobjs.cline_mode_init,
117 tools.PerpendicularCLineTool: gtkconobjs.perpendicular_cline_mode_init,
118 tools.TangentCLineTool : gtkconobjs.tangent_cline_mode_init,
119 tools.CCircleTangentLineTool : gtkconobjs.two_circle_tangent_line_mode_init,
120 tools.ParallelOffsetTool : gtkconobjs.parallel_offset_mode_init,
121 tools.CCircleTool : gtkconobjs.ccircle_cpmode_init,
122 tools.TwoPointCCircleTool : gtkconobjs.ccircle_tpmode_init,
123 tools.TangentCCircleTool : gtkconobjs.tangent_ccircle_mode_init,
124 tools.TwoPointTangentCCircleTool : gtkconobjs.two_cline_tancc_mode_init,
125 tools.TextTool : gtktext.text_add_init,
126 tools.HorizontalMoveTool : gtkmodify.move_horizontal_init,
127 tools.VerticalMoveTool : gtkmodify.move_vertical_init,
128 tools.MoveTool : gtkmodify.move_twopoint_init,
129 tools.HorizontalStretchTool : gtkmodify.stretch_horizontal_init,
130 tools.VerticalStretchTool : gtkmodify.stretch_vertical_init,
131 tools.StretchTool : gtkmodify.stretch_xy_init,
132 tools.TransferTool : gtkmodify.transfer_object_init,
133 tools.RotateTool : gtkmodify.rotate_init,
134 tools.SplitTool : gtkmodify.split_object_init,
135 tools.DeleteTool : gtkmodify.delete_mode_init,
136 tools.MirrorTool : gtkmirror.mirror_mode_init,
137 tools.ZoomTool : gtkmodify.zoom_init,
138 tools.LinearDimensionTool : gtkdimension.linear_mode_init,
139 tools.HorizontalDimensionTool : gtkdimension.horizontal_mode_init,
140 tools.VerticalDimensionTool : gtkdimension.vertical_mode_init,
141 tools.RadialDimensionTool : gtkdimension.radial_mode_init,
142 tools.AngularDimensionTool : gtkdimension.angular_mode_init,
143 tools.PlotTool : gtkprinting.plot_mode_init,
146 def __init__(self, image):
147 debug_print("Initialized another GTKImage class instance...")
148 if not isinstance(image, Image):
149 raise TypeError, "Invalid Image type: " + `type(image)`
150 self.__image = image
151 self.__window = gtk.Window()
152 self.__window.set_title("Untitled")
153 self.__window.connect("destroy", self.__destroyEvent)
154 self.__window.connect("event", self.__windowEvent)
155 self.__window.connect("key_press_event", self.__keyPressEvent)
156 _width = min(1024, int(0.8 * float(gtk.gdk.screen_width())))
157 _height = min(768, int(0.8 * float(gtk.gdk.screen_height())))
158 self.__window.set_default_size(_width, _height)
160 main_vbox = gtk.VBox(False, 2)
161 main_vbox.set_border_width(2)
162 self.__window.add(main_vbox)
165 # accelerators
168 self.__accel = gtk.AccelGroup()
169 self.__window.add_accel_group(self.__accel)
172 # menu bar
175 self.__mb = gtk.MenuBar()
176 main_vbox.pack_start(self.__mb, False, False)
179 # action group dictionary
182 self.__groups = {}
185 # fixme - try to rework code to avoid this import ...
187 from PythonCAD.Interface.Gtk.gtkmenus import fill_menubar
188 fill_menubar(self.__mb, self)
191 # drawing window has Horizontal Pane:
192 # left side: stuff for layer display
193 # right side: drawing area
196 pane = gtk.HPaned()
197 main_vbox.pack_start(pane)
199 frame1 = gtk.Frame()
200 pane.pack1(frame1, True, False)
201 pane.set_position(100)
204 # layer display stuff
207 _ld = gtkshell.LayerDisplay(self.__image, self.__window)
208 frame1.add(_ld.getWindow())
209 self.__layerdisplay = _ld
212 # drawing area
215 self.__disp_width = None
216 self.__disp_height = None
217 self.__units_per_pixel = 1.0
218 self.__da = gtk.DrawingArea()
220 black = gtk.gdk.color_parse('black')
221 self.__da.modify_fg(gtk.STATE_NORMAL, black)
222 self.__da.modify_bg(gtk.STATE_NORMAL, black)
223 pane.pack2(self.__da, True, False)
224 self.__da.set_flags(gtk.CAN_FOCUS)
226 self.__da.connect("event", self.__daEvent)
227 self.__da.connect("expose_event", self.__exposeEvent)
228 self.__da.connect("realize", self.__realizeEvent)
229 self.__da.connect("configure_event", self.__configureEvent)
230 # self.__da.connect("focus_in_event", self.__focusInEvent)
231 # self.__da.connect("focus_out_event", self.__focusOutEvent)
233 self.__da.set_events(gtk.gdk.EXPOSURE_MASK |
234 gtk.gdk.LEAVE_NOTIFY_MASK |
235 gtk.gdk.BUTTON_PRESS_MASK |
236 gtk.gdk.BUTTON_RELEASE_MASK |
237 gtk.gdk.ENTER_NOTIFY_MASK|
238 gtk.gdk.LEAVE_NOTIFY_MASK|
239 gtk.gdk.KEY_PRESS_MASK |
240 gtk.gdk.KEY_RELEASE_MASK |
241 gtk.gdk.FOCUS_CHANGE_MASK |
242 gtk.gdk.POINTER_MOTION_MASK)
244 lower_hbox = gtk.HBox(False, 2)
245 main_vbox.pack_start(lower_hbox, False, False)
247 self.__prompt = gtk.Label(_('Enter Command:'))
248 lower_hbox.pack_start(self.__prompt, False, False)
251 # where the action is taking place
254 self.__coords = gtk.Label('(0,0)')
255 lower_hbox.pack_end(self.__coords, False, False)
257 self.__image.setCurrentPoint(0.0, 0.0)
260 # command entry area
263 self.__entry = gtk.Entry()
264 main_vbox.pack_start(self.__entry, False, False)
265 self.__entry.connect("activate", self.__entryEvent)
268 # the Pixmap, GraphicContext, and CairoContext for the drawing
271 self.__pixmap = None
272 self.__gc = None
273 self.__ctx = None
274 self.__refresh = True
277 # the viewable region and tolerance in the drawing
280 self.__xmin = None
281 self.__ymin = None
282 self.__xmax = None
283 self.__ymax = None
284 self.__tolerance = 1e-10
287 # establish message connections
290 _image = self.__image
291 _image.connect('selected_object', self.__selectedObject)
292 _image.connect('deselected_object', self.__deselectedObject)
293 _image.connect('option_changed', self.__optionChanged)
294 _image.connect('current_point_changed', self.__currentPointChanged)
295 _image.connect('active_layer_changed', self.__activeLayerChanged)
296 _image.connect('added_child', self.__imageAddedChild)
297 _image.connect('removed_child', self.__imageRemovedChild)
298 _image.connect('group_action_started', self.__groupActionStarted)
299 _image.connect('group_action_ended', self.__groupActionEnded)
300 _image.connect('units_changed', self.__imageUnitsChanged)
301 _image.connect('tool_changed', self.__imageToolChanged)
303 _layers = [_image.getTopLayer()]
304 while len(_layers):
305 _layer = _layers.pop()
306 _layer.connect('added_child', self.__layerAddedChild)
307 _layer.connect('removed_child', self.__layerRemovedChild)
308 for _child in _layer.getChildren():
309 _child.connect('refresh', self.__refreshObject)
310 _child.connect('change_pending', self.__objChangePending)
311 _child.connect('change_complete', self.__objChangeComplete)
312 _layers.extend(_layer.getSublayers())
314 #------------------------------------------------------------------
315 def close(self):
316 """Release the entites stored in the drawing.
318 close()
320 self.__layerdisplay.close()
321 self.__layerdisplay = None
322 _image = self.__image
323 _image.close()
324 _image.disconnect(self)
325 _log = _image.getLog()
326 if _log is not None:
327 _log.detatch()
328 _image.setLog(None)
329 _image.finish()
330 self.__window.destroy()
331 self.__window = None
332 self.__da = None
333 self.__entry = None
334 self.__accel = None
335 self.__mb = None
336 self.__pixmap = None
337 self.__gc = None
339 #------------------------------------------------------------------
340 def __destroyEvent(self, widget, data=None):
341 self.close()
342 for _i in xrange(len(globals.imagelist)):
343 _gimage = globals.imagelist[_i]
344 if self.__image is _gimage:
345 del globals.imagelist[_i]
346 if not len(globals.imagelist):
347 gtk.main_quit()
348 break
349 return False
351 #------------------------------------------------------------------
352 def __keyPressEvent(self, widget, event, data=None):
353 # print "__keyPressEvent()"
354 _entry = self.__entry
355 if _entry.is_focus():
356 return False
357 _tool = self.__image.getTool()
358 if _tool is not None and _tool.hasHandler('entry_event'):
359 _entry.grab_focus()
360 return _entry.event(event)
361 return False
363 #------------------------------------------------------------------
364 def __windowEvent(self, widget, event, data=None):
365 _type = event.type
366 debug_print("__windowEvent: Event type: %d" % _type)
367 if _type == gtk.gdk.BUTTON_PRESS:
368 _button = event.button
369 debug_print("BUTTON_PRESS: %d" % _button)
370 elif _type == gtk.gdk.BUTTON_RELEASE:
371 _button = event.button
372 debug_print("BUTTON_RELEASE: %d" % _button)
373 elif _type == gtk.gdk.KEY_PRESS:
374 debug_print("KEY_PRESS")
375 if event.keyval == gtk.keysyms.Escape:
376 debug_print("Got escape key")
377 self.reset()
378 return True
379 elif _type == gtk.gdk.KEY_RELEASE:
380 debug_print("KEY_RELEASE")
381 else:
382 pass
383 return False
385 #------------------------------------------------------------------
386 def __entryEvent(self, widget, data=None):
388 # The error handling in this function needs work, and probably
389 # a rethink as how the commands are implemented is in order. Perhaps
390 # the command set should be stored in the image's global dictionary?
392 debug_print("__entryEvent()")
393 _entry = self.__entry
394 _text = _entry.get_text().strip()
395 if len(_text):
396 _text = _text.lower()
397 if _text == 'end' or _text == 'stop':
398 _entry.delete_text(0,-1)
399 self.reset()
400 else:
401 _tool = self.__image.getTool()
402 if _tool is not None and _tool.hasHandler("entry_event"):
403 _handler = _tool.getHandler("entry_event")
404 try:
405 _handler(self, widget, _tool)
406 except StandardError, e:
407 print "exception called: " + str(e)
408 else:
409 _cmds = keywords.defaultglobals
410 _entry.delete_text(0,-1)
411 # print "text is '%s'" % _text
412 if _text in _cmds:
413 # print "valid command"
414 _opt = _cmds[_text]
415 # print "opt: '%s'" % _opt
416 _tooltype = prompt.lookup(_opt)
417 # print "cmd: '%s'" % _cmd
418 if _tooltype is not None:
419 self.__image.setTool(_tooltype())
420 else:
421 # print "Calling exec for '%s'" % _text
422 # print "Command Error; See http://www.pythoncad.org/commands.html for reference page."
423 try:
424 exec _text in self.__image.getImageVariables()
425 except:
426 print "error executing '%s' " % _text
428 # set the focus back to the DisplayArea widget
430 self.__da.grab_focus()
431 return False
433 #------------------------------------------------------------------
434 def __exposeEvent(self, widget, event, data=None):
435 # print "__exposeEvent()"
436 _pixmap = self.__pixmap
437 _x, _y, _w, _h = event.area
438 _gc = widget.get_style().fg_gc[widget.state]
439 widget.window.draw_drawable(_gc, _pixmap, _x, _y, _x, _y, _w, _h)
440 return True
442 #------------------------------------------------------------------
443 def __realizeEvent(self, widget, data=None):
444 _win = widget.window
445 _width, _height = _win.get_size()
446 self.setSize(_width, _height)
447 widget.set_size_request(100,100)
448 _gc = _win.new_gc()
449 _gc.set_exposures(True)
450 self.setGC(_gc)
452 #------------------------------------------------------------------
453 def __configureEvent(self, widget, event, data=None):
454 _win = widget.window
455 _width, _height = _win.get_size()
456 _disp_width, _disp_height = self.getSize()
457 if _disp_width != _width or _disp_height != _height:
458 self.setSize(_width, _height)
459 _pixmap = gtk.gdk.Pixmap(_win, _width, _height)
460 _gc = widget.get_style().fg_gc[widget.state]
461 _pixmap.draw_rectangle(_gc, True, 0, 0, _width, _height)
462 self.setPixmap(_pixmap)
463 if hasattr(_pixmap, 'cairo_create'):
464 self.__ctx = _pixmap.cairo_create()
465 _xmin = self.__xmin
466 _ymin = self.__ymin
467 if _xmin is None or _ymin is None:
468 _xmin = 1.0
469 _ymin = 1.0
470 _upp = self.__units_per_pixel
471 self.setView(_xmin, _ymin, _upp)
472 return True
474 #------------------------------------------------------------------
475 def __daEvent(self, widget, event, data=None):
476 _rv = False
477 _type = event.type
478 # debug_print("__daEvent(): Event type: %d" % _type)
479 _tool = self.__image.getTool()
480 if _type == gtk.gdk.BUTTON_PRESS:
481 self.setToolpoint(event)
482 _button = event.button
483 if _button == 1:
484 if _tool is not None and _tool.hasHandler("button_press"):
485 _rv = _tool.getHandler("button_press")(self, widget,
486 event, _tool)
487 elif _type == gtk.gdk.BUTTON_RELEASE:
488 self.setToolpoint(event)
489 _button = event.button
490 if _button == 1:
491 if _tool is not None and _tool.hasHandler("button_release"):
492 _rv =_tool.getHandler("button_release")(self, widget,
493 event, _tool)
494 elif _type == gtk.gdk.MOTION_NOTIFY:
495 self.setToolpoint(event)
496 if _tool is not None and _tool.hasHandler("motion_notify"):
497 _rv = _tool.getHandler('motion_notify')(self, widget,
498 event, _tool)
499 elif _type == gtk.gdk.KEY_PRESS:
500 debug_print("In __daEvent(), got key press!")
501 _key = event.keyval
502 if (_key == gtk.keysyms.Page_Up or
503 _key == gtk.keysyms.Page_Down or
504 _key == gtk.keysyms.Left or
505 _key == gtk.keysyms.Right or
506 _key == gtk.keysyms.Up or
507 _key == gtk.keysyms.Down):
508 debug_print("Got Arrow/PageUp/PageDown key")
509 pass # handle moving the drawing in some fashion ...
510 elif _key == gtk.keysyms.Escape:
511 debug_print("Got escape key")
512 self.reset()
513 _rv = True
514 elif _tool is not None and _tool.hasHandler("key_press"):
515 _rv = _tool.getHandler("key_press")(self, widget,
516 event, _tool)
517 else:
518 _entry = self.__entry
519 _entry.grab_focus()
520 if _key == gtk.keysyms.Tab:
521 _rv = True
522 else:
523 _rv = _entry.event(event)
524 elif _type == gtk.gdk.ENTER_NOTIFY:
525 self.setToolpoint(event)
526 _rv = True
527 elif _type == gtk.gdk.LEAVE_NOTIFY:
528 self.setToolpoint(event)
529 _rv = True
530 else:
531 debug_print("Got type %d" % _type)
532 pass
533 return _rv
535 #------------------------------------------------------------------
536 def __focusInEvent(self, widget, event, data=None):
537 debug_print("in GTKImage::__focusInEvent()")
538 return False
540 #------------------------------------------------------------------
541 def __focusOutEvent(self, widget, event, data=None):
542 debug_print("in GTKImage::__focusOutEvent()")
543 return False
545 #------------------------------------------------------------------
546 def __selectedObject(self, img, *args):
547 _alen = len(args)
548 if _alen < 1:
549 raise ValueError, "Invalid argument count: %d" % _alen
550 _obj = args[0]
551 if _debug:
552 print "Selected object: " + `_obj`
553 _parent = _obj.getParent()
554 if _parent.isVisible() and _obj.isVisible():
555 _color = Color('#ff7733') # FIXME color should be adjustable
556 _obj.draw(self, _color)
557 self.__refresh = True
559 #------------------------------------------------------------------
560 def __deselectedObject(self, img, *args):
561 _alen = len(args)
562 if _alen < 1:
563 raise ValueError, "Invalid argument count: %d" % _alen
564 _obj = args[0]
565 if _debug:
566 print "Deselected object: " + `_obj`
567 _parent = _obj.getParent()
568 if _parent.isVisible() and _obj.isVisible():
569 _obj.draw(self)
570 self.__refresh = True
572 #------------------------------------------------------------------
573 def __optionChanged(self, img, *args):
574 _alen = len(args)
575 if _alen < 1:
576 raise ValueError, "Invalid argument count: %d" % _alen
577 _opt = args[0]
578 if _debug:
579 print "Option changed: '%s'" % _opt
580 if _opt == 'BACKGROUND_COLOR':
581 _bc = self.__image.getOption('BACKGROUND_COLOR')
582 _col = gtk.gdk.color_parse(str(_bc))
583 self.__da.modify_fg(gtk.STATE_NORMAL, _col)
584 self.__da.modify_bg(gtk.STATE_NORMAL, _col)
585 self.redraw()
586 elif (_opt == 'HIGHLIGHT_POINTS' or
587 _opt == 'INACTIVE_LAYER_COLOR' or
588 _opt == 'SINGLE_POINT_COLOR' or
589 _opt == 'MULTI_POINT_COLOR'):
590 self.redraw()
591 else:
592 pass
594 #------------------------------------------------------------------
595 def __currentPointChanged(self, img, *args):
596 _x, _y = self.__image.getCurrentPoint()
597 self.__coords.set_text("%.4f, %.4f" % (_x, _y))
600 #------------------------------------------------------------------
601 def __activeLayerChanged(self, img, *args):
602 _alen = len(args)
603 if _alen < 1:
604 raise ValueError, "Invalid argument count: %d" % _alen
605 _prev_layer = args[0]
606 self.drawLayer(_prev_layer)
607 _active_layer = self.__image.getActiveLayer()
608 self.drawLayer(_active_layer)
609 self.refresh()
612 def getImage(self):
613 """Return the Image being displayed.
615 getImage()
617 return self.__image
619 image = property(getImage, None, None, "Displayed Image")
621 #------------------------------------------------------------------
622 def getAccel(self):
623 """Return the gtk.AccelGroup in the GTKImage.
625 getAccel()
627 return self.__accel
629 accel = property(getAccel, None, None, "Accel group in the GTKImage.")
631 #------------------------------------------------------------------
632 def getWindow(self):
633 """Return a handle to the gtk.Window in the GTKImage.
635 getWindow()
637 return self.__window
639 window = property(getWindow, None, None, "Main GTK Window for a GTKImage.")
641 #------------------------------------------------------------------
642 def getEntry(self):
643 """Return a handle to the gtk.Entry in the GTKImage.
645 getEntry()
647 return self.__entry
649 entry = property(getEntry, None, None, "Entry box for a GTKImage.")
651 #------------------------------------------------------------------
652 def getDA(self):
653 """Return the gtk.DrawingArea in the GTKImage.
655 getDA()
657 return self.__da
659 da = property(getDA, None, None, "DrawingArea for a GTKImage.")
661 #------------------------------------------------------------------
662 def getPixmap(self):
663 """Return the Pixmap for the GTKImage.
665 getPixmap()
667 return self.__pixmap
669 #------------------------------------------------------------------
670 def setPixmap(self, pixmap):
671 """Set the Pixmap for the GTKImage.
673 setPixmap(pixmap)
675 self.__pixmap = pixmap
677 pixmap = property(getPixmap, setPixmap, None, "Pixmap for a GTKImage.")
679 #------------------------------------------------------------------
680 def getPrompt(self):
681 """Return the current prompt string.
683 getPrompt()
685 return self.__prompt.get_label()
687 #------------------------------------------------------------------
688 def setPrompt(self, p):
689 """Set the current prompt string.
691 setPrompt(p)
693 if not isinstance(p, types.StringTypes):
694 raise TypeError, "Invalid prompt type: " + `type(p)`
695 self.__prompt.set_text(p)
697 prompt = property(getPrompt, setPrompt, None, "Prompt string.")
699 #------------------------------------------------------------------
700 def setTool(self, tool):
701 """Replace the tool in the image with a new Tool.
703 setTool(tool)
705 The argument 'tool' should be an instance of a Tool object.
707 warnings.warn("Method setTool() is deprecated - use getImage().setTool()", stacklevel=2)
708 self.__image.setTool(tool)
710 #------------------------------------------------------------------
711 def getTool(self):
712 """Return the current Tool used in the drawing.
714 getTool()
716 warnings.warn("Method getTool() is deprecated - use getImage().getTool()", stacklevel=2)
717 return self.__image.getTool()
719 tool = property(getTool, None, None, "Tool for adding/modifying entities.")
721 #------------------------------------------------------------------
722 def getUnitsPerPixel(self):
723 """Return the current value of units/pixel.
725 getUnitsPerPixel()
727 return self.__units_per_pixel
729 #------------------------------------------------------------------
730 def setUnitsPerPixel(self, upp):
731 """Set the current value of units/pixel.
733 setUnitsPerPixel(upp)
735 The argument 'upp' should be a positive float value.
737 _upp = upp
738 if not isinstance(_upp, float):
739 _upp = float(upp)
740 if _upp < 1e-10:
741 raise ValueError, "Invalid scale value: %g" % _upp
742 self.__units_per_pixel = _upp
743 self.__tolerance = _upp * 5.0
745 #------------------------------------------------------------------
746 def setView(self, xmin, ymin, scale=None):
747 """Set the current visible area in a drawing.
749 setView(xmin, ymin[, scale])
751 xmin: Minimum visible x-coordinate
752 ymin: Minimum visible y-coordinate
754 The optional argument 'scale' defaults to the current
755 value of units/pixel (set with getUnitsPerPixel() method.)
756 This value must be a positive float.
758 _xmin = xmin
759 if not isinstance(_xmin, float):
760 _xmin = float(xmin)
761 _ymin = ymin
762 if not isinstance(_ymin, float):
763 _ymin = float(ymin)
764 _scale = scale
765 if _scale is None:
766 _scale = self.__units_per_pixel
767 if not isinstance(_scale, float):
768 _scale = float(scale)
769 if _scale < 1e-10:
770 raise ValueError, "Invalid scale value: %g" % _scale
771 _xmax = _xmin + (_scale * self.__disp_width)
772 _ymax = _ymin + (_scale * self.__disp_height)
773 _recalc = False
774 if abs(_scale - self.__units_per_pixel) > 1e-10:
775 self.__units_per_pixel = _scale
776 _recalc = True
777 self.__tolerance = self.__units_per_pixel * 5.0
778 self.__xmin = _xmin
779 self.__ymin = _ymin
780 self.__xmax = _xmax
781 self.__ymax = _ymax
782 if _recalc:
783 self.calcTextWidths()
784 self.redraw()
786 #------------------------------------------------------------------
787 def calcTextWidths(self):
788 """Calculate the width of the text strings in the Image.
790 calcTextWidths()
792 _layers = [self.__image.getTopLayer()]
793 while len(_layers):
794 _layer = _layers.pop()
795 for _tblock in _layer.getLayerEntities('text'):
796 gtktext.set_textblock_bounds(self, _tblock)
797 for _dim in _layer.getLayerEntities('linear_dimension'):
798 _ds1, _ds2 = _dim.getDimstrings()
799 gtktext.set_textblock_bounds(self, _ds1)
800 if _dim.getDualDimMode():
801 gtktext.set_textblock_bounds(self, _ds2)
802 for _dim in _layer.getLayerEntities('horizontal_dimension'):
803 _ds1, _ds2 = _dim.getDimstrings()
804 gtktext.set_textblock_bounds(self, _ds1)
805 if _dim.getDualDimMode():
806 gtktext.set_textblock_bounds(self, _ds2)
807 for _dim in _layer.getLayerEntities('vertical_dimension'):
808 _ds1, _ds2 = _dim.getDimstrings()
809 gtktext.set_textblock_bounds(self, _ds1)
810 if _dim.getDualDimMode():
811 gtktext.set_textblock_bounds(self, _ds2)
812 for _dim in _layer.getLayerEntities('radial_dimension'):
813 _ds1, _ds2 = _dim.getDimstrings()
814 gtktext.set_textblock_bounds(self, _ds1)
815 if _dim.getDualDimMode():
816 gtktext.set_textblock_bounds(self, _ds2)
817 for _dim in _layer.getLayerEntities('angular_dimension'):
818 _ds1, _ds2 = _dim.getDimstrings()
819 gtktext.set_textblock_bounds(self, _ds1)
820 if _dim.getDualDimMode():
821 gtktext.set_textblock_bounds(self, _ds2)
822 _layers.extend(_layer.getSublayers())
824 #------------------------------------------------------------------
825 def getView(self):
826 """Return the current visible area in a drawing.
828 getView()
830 This method returns a tuple with four float values:
832 (xmin, ymin, xmax, ymax)
834 If the view has never been set, each of these values
835 will be None.
837 return (self.__xmin, self.__ymin, self.__xmax, self.__ymax)
839 view = property(getView, setView, None, "The visible area in a drawing.")
841 #------------------------------------------------------------------
842 def getTolerance(self):
843 """Return the current drawing tolerance.
845 getTolerance()
847 return self.__tolerance
849 tolerance = property(getTolerance, None, None, "Drawing tolerance.")
851 def getGC(self):
852 """Return the GraphicContext allocated for the GTKImage.
854 getGC()
856 return self.__gc
858 #------------------------------------------------------------------
859 def setGC(self, gc):
860 """Set the GraphicContext for the GTKImage.
862 setGC(gc)
864 if not isinstance(gc, gtk.gdk.GC):
865 raise TypeError, "Invalid GC object: " + `gc`
866 if self.__gc is None:
867 self.__gc = gc
869 gc = property(getGC, None, None, "GraphicContext for the GTKImage.")
871 #------------------------------------------------------------------
872 def getCairoContext(self):
873 """Return the CairoContext allocated for the GTKImage.
875 getCairoContext()
877 return self.__ctx
879 ctx = property(getCairoContext, None, None, "CairoContext for the GTKImage.")
881 #------------------------------------------------------------------
882 def getSize(self):
883 """Return the size of the DrawingArea window.
885 getSize()
887 return (self.__disp_width, self.__disp_height)
889 #------------------------------------------------------------------
890 def setSize(self, width, height):
891 """Set the size of the DrawingArea window.
893 setSize(width, height)
895 _width = width
896 if not isinstance(_width, int):
897 _width = int(width)
898 if _width < 0:
899 raise ValueError, "Invalid drawing area width: %d" % _width
900 _height = height
901 if not isinstance(_height, int):
902 _height = int(height)
903 if _height < 0:
904 raise ValueError, "Invalid drawing area height: %d" % _height
905 self.__disp_width = _width
906 self.__disp_height = _height
908 #------------------------------------------------------------------
909 def setToolpoint(self, event):
910 _x = event.x
911 _y = event.y
912 _tx, _ty = self.pixToCoordTransform(_x, _y)
913 self.__image.setCurrentPoint(_tx, _ty)
915 #------------------------------------------------------------------
916 def addGroup(self, group):
917 """Add an ActionGroup to the GTKImage.
919 addGroup(group)
921 Argument 'group' must be either an instance of either
922 gtk.Action gtk.stdAction.
924 if not isinstance(group, gtk.ActionGroup):
925 if not isinstance(gtkactions.stdActionGroup):
926 raise TypeError, "Invalid group type: " + `type(group)`
927 self.__groups[group.get_name()] = group
929 #------------------------------------------------------------------
930 def getGroup(self, name):
931 """Return an ActionGroup stored in the GTKImage.
933 getGroup(name)
935 Argument 'name' should be the name of the ActionGroup. This method
936 will return None if no group by that name is stored.
938 return self.__groups.get(name)
940 #------------------------------------------------------------------
941 def deleteGroup(self, name):
942 """Remove an ActionGroup stored in the GTKImage.
944 deleteGroup(name)
946 Argument 'name' should be the name of the ActionGroup to be removed.
948 if name in self.__groups:
949 del self.__groups[name]
951 #------------------------------------------------------------------
952 def pixToCoordTransform(self, xp, yp):
953 """Convert from pixel coordinates to x-y coordinates.
955 pixToCoordTransform(xp, yp)
957 The function arguments are:
959 xp: pixel x value
960 yp: pixel y value
962 The function returns a tuple holding two float values
964 _upp = self.__units_per_pixel
965 _xc = self.__xmin + (xp * _upp)
966 _yc = self.__ymax - (yp * _upp)
967 return (_xc, _yc)
969 #------------------------------------------------------------------
970 def coordToPixTransform(self, xc, yc):
971 """Convert from x-y coordinates to pixel coordinates
973 coordToPixTransform(xc, yc)
975 The function arguments are:
977 xc: x coordinate
978 yp: y coordinate
980 The function returns a tuple holding two integer values
982 _upp = self.__units_per_pixel
983 _xp = int((xc - self.__xmin)/_upp)
984 _yp = int((self.__ymax - yc)/_upp)
985 return _xp, _yp
987 #------------------------------------------------------------------
988 def getColor(self, c):
989 """Return an allocated color for a given Color object.
991 getColor(c)
993 Argument 'c' must be a Color object. This method will return an
994 allocated color.
996 if not isinstance(c, Color):
997 raise TypeError, "Invalid Color object: " + `type(c)`
998 _color = globals.gtkcolors.get(c)
999 if _color is None:
1000 # _r = int(round(65535.0 * (c.r/255.0)))
1001 # _g = int(round(65535.0 * (c.g/255.0)))
1002 # _b = int(round(65535.0 * (c.b/255.0)))
1003 # _color = self.__da.get_colormap().alloc_color(_r, _g, _b)
1004 _color = self.__da.get_colormap().alloc_color(str(c))
1005 globals.gtkcolors[c] = _color
1006 return _color
1008 #------------------------------------------------------------------
1009 def fitImage(self):
1010 """Redraw the image so all entities are visible in the window.
1012 fitImage()
1014 _fw = float(self.__disp_width)
1015 _fh = float(self.__disp_height)
1016 _xmin, _ymin, _xmax, _ymax = self.__image.getExtents()
1017 _xdiff = abs(_xmax - _xmin)
1018 _ydiff = abs(_ymax - _ymin)
1019 _xmid = (_xmin + _xmax)/2.0
1020 _ymid = (_ymin + _ymax)/2.0
1021 _xs = _xdiff/_fw
1022 _ys = _ydiff/_fh
1023 if _xs > _ys:
1024 _scale = _xs * 1.05 # make it a little larger
1025 else:
1026 _scale = _ys * 1.05 # make it a little larger
1027 _xm = _xmid - (_fw/2.0) * _scale
1028 _ym = _ymid - (_fh/2.0) * _scale
1029 self.setView(_xm, _ym, _scale)
1031 #------------------------------------------------------------------
1032 def refresh(self):
1033 """This method does a screen refresh.
1035 refresh()
1037 If entities in the drawing have been added, removed, or
1038 modified, use the redraw() method.
1040 _da = self.__da
1041 if (_da.flags() & gtk.MAPPED):
1042 # print "refreshing ..."
1043 _gc = _da.get_style().fg_gc[gtk.STATE_NORMAL]
1044 _gc.set_function(gtk.gdk.COPY)
1045 _da.queue_draw()
1047 #------------------------------------------------------------------
1048 def redraw(self):
1049 """This method draws all the objects visible in the window.
1051 redraw()
1053 _da = self.__da
1054 if (_da.flags() & gtk.MAPPED):
1055 if _debug:
1056 print "Redrawing image"
1057 _xmin = self.__xmin
1058 _ymin = self.__ymin
1059 _xmax = self.__xmax
1060 _ymax = self.__ymax
1061 _gc = _da.get_style().fg_gc[gtk.STATE_NORMAL]
1062 self.__pixmap.draw_rectangle(_gc, True, 0, 0,
1063 self.__disp_width, self.__disp_height)
1064 _active_layer = self.__image.getActiveLayer()
1065 _layers = [self.__image.getTopLayer()]
1066 while (len(_layers)):
1067 _layer = _layers.pop()
1068 if _layer is not _active_layer:
1069 self.drawLayer(_layer)
1070 _layers.extend(_layer.getSublayers())
1071 self.drawLayer(_active_layer)
1073 # redraw selected entities
1075 _color = Color('#ff7733')
1076 for _obj in self.__image.getSelectedObjects(False):
1077 _obj.draw(self, _color)
1078 self.refresh()
1080 #------------------------------------------------------------------
1081 def drawLayer(self, l):
1082 if not isinstance(l, Layer):
1083 raise TypeError, "Invalid layer type: " + `type(l)`
1084 if l.getParent() is not self.__image:
1085 raise ValueError, "Layer not found in Image"
1086 if l.isVisible():
1087 _col = self.__image.getOption('INACTIVE_LAYER_COLOR')
1088 if l is self.__image.getActiveLayer():
1089 _col = None
1090 _cobjs = []
1091 _objs = []
1092 _pts = []
1093 for _obj in l.objsInRegion(self.__xmin, self.__ymin, self.__xmax, self.__ymax):
1094 if _obj.isVisible():
1095 if isinstance(_obj, Point):
1096 _pts.append(_obj)
1097 elif isinstance(_obj, ConstructionObject):
1098 _cobjs.append(_obj)
1099 else:
1100 _objs.append(_obj)
1101 for _obj in _cobjs:
1102 _obj.draw(self, _col)
1103 for _obj in _pts:
1104 _obj.draw(self, _col)
1105 for _obj in _objs:
1106 _obj.draw(self, _col)
1108 #------------------------------------------------------------------
1109 def reset(self):
1110 """Set the image to an initial drawing state.
1112 reset()
1114 _tool = self.__image.getTool()
1115 if _tool is None:
1117 # If _tool is None, deselect any selected objects in view.
1118 # This way, if you are currently using a tool, then the
1119 # first time you hit escape, you just clear the tool.
1120 # The second time clears all selections.
1121 debug_print("Entered reset")
1122 if _tool is None:
1123 debug_print(".....This is second time to be in reset")
1124 self.__image.clearSelectedObjects()
1125 self.__image.setTool()
1126 self.redraw()
1127 self.setPrompt(_('Enter command'))
1130 # Entity drawing operations
1133 def __drawObject(self, obj, col=None):
1134 # print "__drawObject()"
1135 _col = col
1136 if self.__xmin is None:
1137 return
1138 _xmin, _ymin, _xmax, _ymax = self.getView()
1139 if obj.inRegion(_xmin, _ymin, _xmax, _ymax):
1140 _image = self.__image
1141 if _col is None:
1142 if obj.getParent() is not _image.getActiveLayer():
1143 _col = _image.getOption('INACTIVE_LAYER_COLOR')
1144 obj.draw(self, _col)
1145 self.__refresh = True
1147 def __eraseObject(self, obj):
1148 # print "__eraseObject()"
1149 _xmin, _ymin, _xmax, _ymax = self.getView()
1150 if self.__xmin is None:
1151 return
1152 if obj.inRegion(_xmin, _ymin, _xmax, _ymax):
1153 obj.erase(self)
1154 self.__refresh = True
1156 def __imageAddedChild(self, obj, *args):
1157 # print "__imageAddedChild()"
1158 _alen = len(args)
1159 if _alen < 1:
1160 raise ValueError, "Invalid argument count: %d" % _alen
1161 _layer = args[0]
1162 if not isinstance(_layer, Layer):
1163 raise TypeError, "Unexpected child type: " + `type(_layer)`
1164 _layer.connect('added_child', self.__layerAddedChild)
1165 _layer.connect('removed_child', self.__layerRemovedChild)
1167 def __layerAddedChild(self, obj, *args):
1168 # print "__layerAddedChild()"
1169 _alen = len(args)
1170 if _alen < 1:
1171 raise ValueError, "Invalid argument count: %d" % _alen
1172 _child = args[0] # need some verification test here ...
1173 _child.connect('refresh', self.__refreshObject)
1174 _child.connect('change_pending', self.__objChangePending)
1175 _child.connect('change_complete', self.__objChangeComplete)
1176 if _child.isVisible() and obj.isVisible():
1177 self.__drawObject(_child)
1179 def __imageRemovedChild(self, obj, *args):
1180 # print "__imageRemovedChild()"
1181 _alen = len(args)
1182 if _alen < 1:
1183 raise ValueError, "Invalid argument count: %d" % _alen
1184 _layer = args[0]
1185 if not isinstance(_layer, Layer):
1186 raise TypeError, "Unexpected child type: " + `type(_layer)`
1187 _layer.disconnect(self)
1189 def __layerRemovedChild(self, obj, *args):
1190 # print "__layerRemovedChild()"
1191 _alen = len(args)
1192 if _alen < 1:
1193 raise ValueError, "Invalid argument count: %d" % _alen
1194 _child = args[0] # need some verification test here ...
1195 if _child.isVisible() and obj.isVisible():
1196 self.__eraseObject(_child)
1197 _child.disconnect(self)
1199 def __groupActionStarted(self, obj, *args):
1200 # print "__groupActionStarted()"
1201 self.__refresh = False
1203 def __groupActionEnded(self, obj, *args):
1204 # print "__groupActionEnded()"
1205 if self.__refresh:
1206 self.refresh()
1207 else:
1208 self.__refresh = True
1210 def __imageUnitsChanged(self, obj, *args):
1211 # print "__imageUnitsChanged()"
1212 self.redraw()
1214 def __imageToolChanged(self, obj, *args):
1215 _tool = self.__image.getTool()
1216 if _tool is not None:
1217 _init = GTKImage.__inittool.get(type(_tool))
1218 if _init is not None:
1219 _init(self)
1221 def __objChangePending(self, obj, *args):
1222 # print "__objChangePending()" + `obj`
1223 _alen = len(args)
1224 if _alen < 1:
1225 raise ValueError, "Invalid argument count: %d" % _alen
1226 _change = args[0]
1227 if (obj.isVisible() and
1228 obj.getParent().isVisible() and
1229 _change != 'added_user' and
1230 _change != 'removed_user'):
1231 self.__eraseObject(obj)
1233 def __objChangeComplete(self, obj, *args):
1234 # print "__objChangeComplete()" + `obj`
1235 if obj.isVisible() and obj.getParent().isVisible():
1236 self.__drawObject(obj)
1238 def __refreshObject(self, obj, *args):
1239 # print "__refreshObject()"
1240 _col = None
1241 if not obj.isVisible() or not obj.getParent().isVisible():
1242 _col = self.__image.getOption('BACKGROUND_COLOR')
1243 self.__drawObject(obj, _col)
1245 #------------------------------------------------------------------
1246 def debug_print(string):
1247 if _debug is True:
1248 print "SDB Debug: " + string