3     A simple object-oriented drawing program. 
   5     This is completely free software; please feel free to adapt or use this in 
   8     Author: Erik Westra (ewestra@wave.co.nz) 
  10     ######################################################################### 
  14     pySketch requires wxPython version 2.3.  If you are running an earlier 
  15     version, you need to patch your copy of wxPython to fix a bug which will 
  16     cause the "Edit Text Object" dialog box to crash. 
  18     To patch an earlier version of wxPython, edit the wxPython/windows.py file, 
  19     find the wxPyValidator.__init__ method and change the line which reads: 
  21         self._setSelf(self, wxPyValidator, 0) 
  25         self._setSelf(self, wxPyValidator, 1) 
  27     This fixes a known bug in wxPython 2.2.5 (and possibly earlier) which has 
  28     now been fixed in wxPython 2.3. 
  30     ######################################################################### 
  34       * Add ARGV checking to see if a document was double-clicked on. 
  38       * Scrolling the window causes the drawing panel to be mucked up until you 
  39         refresh it.  I've got no idea why. 
  41       * I suspect that the reference counting for some wxPoint objects is 
  42         getting mucked up; when the user quits, we get errors about being 
  43         unable to call del on a 'None' object. 
  46 import cPickle
, os
.path
 
  48 from wx
.lib
.buttons 
import GenBitmapButton
 
  51 import traceback
, types
 
  53 #---------------------------------------------------------------------------- 
  55 #---------------------------------------------------------------------------- 
  59 menu_UNDO          
= 10001 # Edit menu items. 
  60 menu_SELECT_ALL    
= 10002 
  61 menu_DUPLICATE     
= 10003 
  62 menu_EDIT_TEXT     
= 10004 
  65 menu_SELECT        
= 10101 # Tools menu items. 
  71 menu_MOVE_FORWARD  
= 10201 # Object menu items. 
  72 menu_MOVE_TO_FRONT 
= 10202 
  73 menu_MOVE_BACKWARD 
= 10203 
  74 menu_MOVE_TO_BACK  
= 10204 
  76 menu_ABOUT         
= 10205 # Help menu items. 
  86 # Our tool option IDs: 
  99 # DrawObject type IDs: 
 106 # Selection handle IDs: 
 111 handle_BOTTOM_LEFT  
= 4 
 112 handle_BOTTOM_RIGHT 
= 5 
 113 handle_START_POINT  
= 6 
 116 # Dragging operations: 
 123 # Visual Feedback types: 
 129 # Mouse-event action parameter types: 
 134 # Size of the drawing page, in pixels. 
 139 #---------------------------------------------------------------------------- 
 141 class DrawingFrame(wx
.Frame
): 
 142     """ A frame showing the contents of a single document. """ 
 144     # ========================================== 
 145     # == Initialisation and Window Management == 
 146     # ========================================== 
 148     def __init__(self
, parent
, id, title
, fileName
=None): 
 149         """ Standard constructor. 
 151             'parent', 'id' and 'title' are all passed to the standard wx.Frame 
 152             constructor.  'fileName' is the name and path of a saved file to 
 153             load into this frame, if any. 
 155         wx
.Frame
.__init
__(self
, parent
, id, title
, 
 156                          style 
= wx
.DEFAULT_FRAME_STYLE | wx
.WANTS_CHARS |
 
 157                                  wx
.NO_FULL_REPAINT_ON_RESIZE
) 
 159         # Setup our menu bar. 
 161         menuBar 
= wx
.MenuBar() 
 163         self
.fileMenu 
= wx
.Menu() 
 164         self
.fileMenu
.Append(wx
.ID_NEW
,    "New\tCTRL-N") 
 165         self
.fileMenu
.Append(wx
.ID_OPEN
,   "Open...\tCTRL-O") 
 166         self
.fileMenu
.Append(wx
.ID_CLOSE
,  "Close\tCTRL-W") 
 167         self
.fileMenu
.AppendSeparator() 
 168         self
.fileMenu
.Append(wx
.ID_SAVE
,   "Save\tCTRL-S") 
 169         self
.fileMenu
.Append(wx
.ID_SAVEAS
, "Save As...") 
 170         self
.fileMenu
.Append(wx
.ID_REVERT
, "Revert...") 
 171         self
.fileMenu
.AppendSeparator() 
 172         self
.fileMenu
.Append(wx
.ID_EXIT
,   "Quit\tCTRL-Q") 
 174         menuBar
.Append(self
.fileMenu
, "File") 
 176         self
.editMenu 
= wx
.Menu() 
 177         self
.editMenu
.Append(menu_UNDO
,          "Undo\tCTRL-Z") 
 178         self
.editMenu
.AppendSeparator() 
 179         self
.editMenu
.Append(menu_SELECT_ALL
,    "Select All\tCTRL-A") 
 180         self
.editMenu
.AppendSeparator() 
 181         self
.editMenu
.Append(menu_DUPLICATE
,     "Duplicate\tCTRL-D") 
 182         self
.editMenu
.Append(menu_EDIT_TEXT
,     "Edit...\tCTRL-E") 
 183         self
.editMenu
.Append(menu_DELETE
,        "Delete\tDEL") 
 185         menuBar
.Append(self
.editMenu
, "Edit") 
 187         self
.toolsMenu 
= wx
.Menu() 
 188         self
.toolsMenu
.Append(menu_SELECT
,  "Selection", kind
=wx
.ITEM_CHECK
) 
 189         self
.toolsMenu
.Append(menu_LINE
,    "Line",      kind
=wx
.ITEM_CHECK
) 
 190         self
.toolsMenu
.Append(menu_RECT
,    "Rectangle", kind
=wx
.ITEM_CHECK
) 
 191         self
.toolsMenu
.Append(menu_ELLIPSE
, "Ellipse",   kind
=wx
.ITEM_CHECK
) 
 192         self
.toolsMenu
.Append(menu_TEXT
,    "Text",      kind
=wx
.ITEM_CHECK
) 
 194         menuBar
.Append(self
.toolsMenu
, "Tools") 
 196         self
.objectMenu 
= wx
.Menu() 
 197         self
.objectMenu
.Append(menu_MOVE_FORWARD
,  "Move Forward") 
 198         self
.objectMenu
.Append(menu_MOVE_TO_FRONT
, "Move to Front\tCTRL-F") 
 199         self
.objectMenu
.Append(menu_MOVE_BACKWARD
, "Move Backward") 
 200         self
.objectMenu
.Append(menu_MOVE_TO_BACK
,  "Move to Back\tCTRL-B") 
 202         menuBar
.Append(self
.objectMenu
, "Object") 
 204         self
.helpMenu 
= wx
.Menu() 
 205         self
.helpMenu
.Append(menu_ABOUT
, "About pySketch...") 
 207         menuBar
.Append(self
.helpMenu
, "Help") 
 209         self
.SetMenuBar(menuBar
) 
 211         # Create our toolbar. 
 214         self
.toolbar 
= self
.CreateToolBar(wx
.TB_HORIZONTAL |
 
 215                                           wx
.NO_BORDER | wx
.TB_FLAT
) 
 217         self
.toolbar
.AddSimpleTool(wx
.ID_NEW
, 
 218                                    wx
.ArtProvider
.GetBitmap(wx
.ART_NEW
, wx
.ART_TOOLBAR
, tsize
), 
 220         self
.toolbar
.AddSimpleTool(wx
.ID_OPEN
, 
 221                                    wx
.ArtProvider
.GetBitmap(wx
.ART_FILE_OPEN
, wx
.ART_TOOLBAR
, tsize
), 
 223         self
.toolbar
.AddSimpleTool(wx
.ID_SAVE
, 
 224                                    wx
.ArtProvider
.GetBitmap(wx
.ART_FILE_SAVE
, wx
.ART_TOOLBAR
, tsize
), 
 226         self
.toolbar
.AddSeparator() 
 227         self
.toolbar
.AddSimpleTool(menu_UNDO
, 
 228                                    wx
.ArtProvider
.GetBitmap(wx
.ART_UNDO
, wx
.ART_TOOLBAR
, tsize
), 
 230         self
.toolbar
.AddSeparator() 
 231         self
.toolbar
.AddSimpleTool(menu_DUPLICATE
, 
 232                                    wx
.Bitmap("images/duplicate.bmp", 
 235         self
.toolbar
.AddSeparator() 
 236         self
.toolbar
.AddSimpleTool(menu_MOVE_FORWARD
, 
 237                                    wx
.Bitmap("images/moveForward.bmp", 
 240         self
.toolbar
.AddSimpleTool(menu_MOVE_BACKWARD
, 
 241                                    wx
.Bitmap("images/moveBack.bmp", 
 245         self
.toolbar
.Realize() 
 247         # Associate each menu/toolbar item with the method that handles that 
 250         (wx
.ID_NEW
,    self
.doNew
), 
 251         (wx
.ID_OPEN
,   self
.doOpen
), 
 252         (wx
.ID_CLOSE
,  self
.doClose
), 
 253         (wx
.ID_SAVE
,   self
.doSave
), 
 254         (wx
.ID_SAVEAS
, self
.doSaveAs
), 
 255         (wx
.ID_REVERT
, self
.doRevert
), 
 256         (wx
.ID_EXIT
,   self
.doExit
), 
 258         (menu_UNDO
,          self
.doUndo
), 
 259         (menu_SELECT_ALL
,    self
.doSelectAll
), 
 260         (menu_DUPLICATE
,     self
.doDuplicate
), 
 261         (menu_EDIT_TEXT
,     self
.doEditText
), 
 262         (menu_DELETE
,        self
.doDelete
), 
 264         (menu_SELECT
,  self
.doChooseSelectTool
), 
 265         (menu_LINE
,    self
.doChooseLineTool
), 
 266         (menu_RECT
,    self
.doChooseRectTool
), 
 267         (menu_ELLIPSE
, self
.doChooseEllipseTool
), 
 268         (menu_TEXT
,    self
.doChooseTextTool
), 
 270         (menu_MOVE_FORWARD
,  self
.doMoveForward
), 
 271         (menu_MOVE_TO_FRONT
, self
.doMoveToFront
), 
 272         (menu_MOVE_BACKWARD
, self
.doMoveBackward
), 
 273         (menu_MOVE_TO_BACK
,  self
.doMoveToBack
), 
 275         (menu_ABOUT
, self
.doShowAbout
)] 
 276         for combo 
in menuHandlers
: 
 278                 self
.Bind(wx
.EVT_MENU
, handler
, id = id) 
 281         # Install our own method to handle closing the window.  This allows us 
 282         # to ask the user if he/she wants to save before closing the window, as 
 283         # well as keeping track of which windows are currently open. 
 285         self
.Bind(wx
.EVT_CLOSE
, self
.doClose
) 
 287         # Install our own method for handling keystrokes.  We use this to let 
 288         # the user move the selected object(s) around using the arrow keys. 
 290         self
.Bind(wx
.EVT_CHAR_HOOK
, self
.onKeyEvent
) 
 292         # Setup our top-most panel.  This holds the entire contents of the 
 293         # window, excluding the menu bar. 
 295         self
.topPanel 
= wx
.Panel(self
, -1, style
=wx
.SIMPLE_BORDER
) 
 297         # Setup our tool palette, with all our drawing tools and option icons. 
 299         self
.toolPalette 
= wx
.BoxSizer(wx
.VERTICAL
) 
 301         self
.selectIcon  
= ToolPaletteIcon(self
.topPanel
, id_SELECT
, 
 302                                            "select", "Selection Tool") 
 303         self
.lineIcon    
= ToolPaletteIcon(self
.topPanel
, id_LINE
, 
 305         self
.rectIcon    
= ToolPaletteIcon(self
.topPanel
, id_RECT
, 
 306                                            "rect", "Rectangle Tool") 
 307         self
.ellipseIcon 
= ToolPaletteIcon(self
.topPanel
, id_ELLIPSE
, 
 308                                            "ellipse", "Ellipse Tool") 
 309         self
.textIcon    
= ToolPaletteIcon(self
.topPanel
, id_TEXT
, 
 312         toolSizer 
= wx
.GridSizer(0, 2, 5, 5) 
 313         toolSizer
.Add(self
.selectIcon
) 
 314         toolSizer
.Add((0, 0)) # Gap to make tool icons line up nicely. 
 315         toolSizer
.Add(self
.lineIcon
) 
 316         toolSizer
.Add(self
.rectIcon
) 
 317         toolSizer
.Add(self
.ellipseIcon
) 
 318         toolSizer
.Add(self
.textIcon
) 
 320         self
.optionIndicator 
= ToolOptionIndicator(self
.topPanel
) 
 321         self
.optionIndicator
.SetToolTip( 
 322                 wx
.ToolTip("Shows Current Pen/Fill/Line Size Settings")) 
 324         optionSizer 
= wx
.BoxSizer(wx
.HORIZONTAL
) 
 326         self
.penOptIcon  
= ToolPaletteIcon(self
.topPanel
, id_PEN_OPT
, 
 327                                            "penOpt", "Set Pen Colour") 
 328         self
.fillOptIcon 
= ToolPaletteIcon(self
.topPanel
, id_FILL_OPT
, 
 329                                            "fillOpt", "Set Fill Colour") 
 330         self
.lineOptIcon 
= ToolPaletteIcon(self
.topPanel
, id_LINE_OPT
, 
 331                                            "lineOpt", "Set Line Size") 
 333         margin 
= wx
.LEFT | wx
.RIGHT
 
 334         optionSizer
.Add(self
.penOptIcon
,  0, margin
, 1) 
 335         optionSizer
.Add(self
.fillOptIcon
, 0, margin
, 1) 
 336         optionSizer
.Add(self
.lineOptIcon
, 0, margin
, 1) 
 338         margin 
= wx
.TOP | wx
.LEFT | wx
.RIGHT | wx
.ALIGN_CENTRE
 
 339         self
.toolPalette
.Add(toolSizer
,            0, margin
, 5) 
 340         self
.toolPalette
.Add((0, 0),               0, margin
, 5) # Spacer. 
 341         self
.toolPalette
.Add(self
.optionIndicator
, 0, margin
, 5) 
 342         self
.toolPalette
.Add(optionSizer
,          0, margin
, 5) 
 344         # Make the tool palette icons respond when the user clicks on them. 
 346         self
.selectIcon
.Bind(wx
.EVT_BUTTON
, self
.onToolIconClick
) 
 347         self
.lineIcon
.Bind(wx
.EVT_BUTTON
, self
.onToolIconClick
) 
 348         self
.rectIcon
.Bind(wx
.EVT_BUTTON
, self
.onToolIconClick
) 
 349         self
.ellipseIcon
.Bind(wx
.EVT_BUTTON
, self
.onToolIconClick
) 
 350         self
.textIcon
.Bind(wx
.EVT_BUTTON
, self
.onToolIconClick
) 
 351         self
.penOptIcon
.Bind(wx
.EVT_BUTTON
, self
.onPenOptionIconClick
) 
 352         self
.fillOptIcon
.Bind(wx
.EVT_BUTTON
, self
.onFillOptionIconClick
) 
 353         self
.lineOptIcon
.Bind(wx
.EVT_BUTTON
, self
.onLineOptionIconClick
) 
 355         # Setup the main drawing area. 
 357         self
.drawPanel 
= wx
.ScrolledWindow(self
.topPanel
, -1, 
 358                                           style
=wx
.SUNKEN_BORDER
) 
 359         self
.drawPanel
.SetBackgroundColour(wx
.WHITE
) 
 361         self
.drawPanel
.EnableScrolling(True, True) 
 362         self
.drawPanel
.SetScrollbars(20, 20, PAGE_WIDTH 
/ 20, PAGE_HEIGHT 
/ 20) 
 364         self
.drawPanel
.Bind(wx
.EVT_LEFT_DOWN
, self
.onMouseEvent
) 
 365         self
.drawPanel
.Bind(wx
.EVT_LEFT_DCLICK
, self
.onDoubleClickEvent
) 
 366         self
.drawPanel
.Bind(wx
.EVT_RIGHT_DOWN
, self
.onRightClick
) 
 367         self
.drawPanel
.Bind(wx
.EVT_MOTION
, self
.onMouseEvent
) 
 368         self
.drawPanel
.Bind(wx
.EVT_LEFT_UP
, self
.onMouseEvent
) 
 369         self
.drawPanel
.Bind(wx
.EVT_PAINT
, self
.onPaintEvent
) 
 371         # Position everything in the window. 
 373         topSizer 
= wx
.BoxSizer(wx
.HORIZONTAL
) 
 374         topSizer
.Add(self
.toolPalette
, 0) 
 375         topSizer
.Add(self
.drawPanel
, 1, wx
.EXPAND
) 
 377         self
.topPanel
.SetAutoLayout(True) 
 378         self
.topPanel
.SetSizer(topSizer
) 
 380         self
.SetSizeHints(250, 200) 
 381         self
.SetSize(wx
.Size(600, 400)) 
 383         # Select an initial tool. 
 386         self
._setCurrentTool
(self
.selectIcon
) 
 388         # Setup our frame to hold the contents of a sketch document. 
 391         self
.fileName  
= fileName
 
 392         self
.contents  
= []     # front-to-back ordered list of DrawingObjects. 
 393         self
.selection 
= []     # List of selected DrawingObjects. 
 394         self
.undoInfo  
= None   # Saved contents for undo. 
 395         self
.dragMode  
= drag_NONE 
# Current mouse-drag mode. 
 397         if self
.fileName 
!= None: 
 402         # Finally, set our initial pen, fill and line options. 
 404         self
.penColour  
= wx
.BLACK
 
 405         self
.fillColour 
= wx
.WHITE
 
 408     # ============================ 
 409     # == Event Handling Methods == 
 410     # ============================ 
 412     def onToolIconClick(self
, event
): 
 413         """ Respond to the user clicking on one of our tool icons. 
 415         iconID 
= event
.GetEventObject().GetId() 
 416         if   iconID 
== id_SELECT
:   self
.doChooseSelectTool() 
 417         elif iconID 
== id_LINE
:     self
.doChooseLineTool() 
 418         elif iconID 
== id_RECT
:     self
.doChooseRectTool() 
 419         elif iconID 
== id_ELLIPSE
:  self
.doChooseEllipseTool() 
 420         elif iconID 
== id_TEXT
:     self
.doChooseTextTool() 
 421         else:                       wx
.Bell(); print "1" 
 424     def onPenOptionIconClick(self
, event
): 
 425         """ Respond to the user clicking on the "Pen Options" icon. 
 427         data 
= wx
.ColourData() 
 428         if len(self
.selection
) == 1: 
 429             data
.SetColour(self
.selection
[0].getPenColour()) 
 431             data
.SetColour(self
.penColour
) 
 433         dialog 
= wx
.ColourDialog(self
, data
) 
 434         if dialog
.ShowModal() == wx
.ID_OK
: 
 435             c 
= dialog
.GetColourData().GetColour() 
 436             self
._setPenColour
(wx
.Colour(c
.Red(), c
.Green(), c
.Blue())) 
 440     def onFillOptionIconClick(self
, event
): 
 441         """ Respond to the user clicking on the "Fill Options" icon. 
 443         data 
= wx
.ColourData() 
 444         if len(self
.selection
) == 1: 
 445             data
.SetColour(self
.selection
[0].getFillColour()) 
 447             data
.SetColour(self
.fillColour
) 
 449         dialog 
= wx
.ColourDialog(self
, data
) 
 450         if dialog
.ShowModal() == wx
.ID_OK
: 
 451             c 
= dialog
.GetColourData().GetColour() 
 452             self
._setFillColour
(wx
.Colour(c
.Red(), c
.Green(), c
.Blue())) 
 455     def onLineOptionIconClick(self
, event
): 
 456         """ Respond to the user clicking on the "Line Options" icon. 
 458         if len(self
.selection
) == 1: 
 459             menu 
= self
._buildLineSizePopup
(self
.selection
[0].getLineSize()) 
 461             menu 
= self
._buildLineSizePopup
(self
.lineSize
) 
 463         pos 
= self
.lineOptIcon
.GetPosition() 
 464         pos
.y 
= pos
.y 
+ self
.lineOptIcon
.GetSize().height
 
 465         self
.PopupMenu(menu
, pos
) 
 469     def onKeyEvent(self
, event
): 
 470         """ Respond to a keypress event. 
 472             We make the arrow keys move the selected object(s) by one pixel in 
 475         if event
.GetKeyCode() == wx
.WXK_UP
: 
 476             self
._moveObject
(0, -1) 
 477         elif event
.GetKeyCode() == wx
.WXK_DOWN
: 
 478             self
._moveObject
(0, 1) 
 479         elif event
.GetKeyCode() == wx
.WXK_LEFT
: 
 480             self
._moveObject
(-1, 0) 
 481         elif event
.GetKeyCode() == wx
.WXK_RIGHT
: 
 482             self
._moveObject
(1, 0) 
 487     def onMouseEvent(self
, event
): 
 488         """ Respond to the user clicking on our main drawing panel. 
 490             How we respond depends on the currently selected tool. 
 492         if not (event
.LeftDown() or event
.Dragging() or event
.LeftUp()): 
 493             return # Ignore mouse movement without click/drag. 
 495         if self
.curTool 
== self
.selectIcon
: 
 496             feedbackType 
= feedback_RECT
 
 497             action       
= self
.selectByRectangle
 
 498             actionParam  
= param_RECT
 
 501         elif self
.curTool 
== self
.lineIcon
: 
 502             feedbackType 
= feedback_LINE
 
 503             action       
= self
.createLine
 
 504             actionParam  
= param_LINE
 
 507         elif self
.curTool 
== self
.rectIcon
: 
 508             feedbackType 
= feedback_RECT
 
 509             action       
= self
.createRect
 
 510             actionParam  
= param_RECT
 
 513         elif self
.curTool 
== self
.ellipseIcon
: 
 514             feedbackType 
= feedback_ELLIPSE
 
 515             action       
= self
.createEllipse
 
 516             actionParam  
= param_RECT
 
 519         elif self
.curTool 
== self
.textIcon
: 
 520             feedbackType 
= feedback_RECT
 
 521             action       
= self
.createText
 
 522             actionParam  
= param_RECT
 
 530             mousePt 
= self
._getEventCoordinates
(event
) 
 532                 obj
, handle 
= self
._getObjectAndSelectionHandleAt
(mousePt
) 
 534             if selecting 
and (obj 
!= None) and (handle 
!= handle_NONE
): 
 536                 # The user clicked on an object's selection handle.  Let the 
 537                 # user resize the clicked-on object. 
 539                 self
.dragMode     
= drag_RESIZE
 
 540                 self
.resizeObject 
= obj
 
 542                 if obj
.getType() == obj_LINE
: 
 543                     self
.resizeFeedback 
= feedback_LINE
 
 544                     pos  
= obj
.getPosition() 
 545                     startPt 
= wx
.Point(pos
.x 
+ obj
.getStartPt().x
, 
 546                                       pos
.y 
+ obj
.getStartPt().y
) 
 547                     endPt   
= wx
.Point(pos
.x 
+ obj
.getEndPt().x
, 
 548                                       pos
.y 
+ obj
.getEndPt().y
) 
 549                     if handle 
== handle_START_POINT
: 
 550                         self
.resizeAnchor  
= endPt
 
 551                         self
.resizeFloater 
= startPt
 
 553                         self
.resizeAnchor  
= startPt
 
 554                         self
.resizeFloater 
= endPt
 
 556                     self
.resizeFeedback 
= feedback_RECT
 
 557                     pos  
= obj
.getPosition() 
 559                     topLeft  
= wx
.Point(pos
.x
, pos
.y
) 
 560                     topRight 
= wx
.Point(pos
.x 
+ size
.width
, pos
.y
) 
 561                     botLeft  
= wx
.Point(pos
.x
, pos
.y 
+ size
.height
) 
 562                     botRight 
= wx
.Point(pos
.x 
+ size
.width
, pos
.y 
+ size
.height
) 
 564                     if handle 
== handle_TOP_LEFT
: 
 565                         self
.resizeAnchor  
= botRight
 
 566                         self
.resizeFloater 
= topLeft
 
 567                     elif handle 
== handle_TOP_RIGHT
: 
 568                         self
.resizeAnchor  
= botLeft
 
 569                         self
.resizeFloater 
= topRight
 
 570                     elif handle 
== handle_BOTTOM_LEFT
: 
 571                         self
.resizeAnchor  
= topRight
 
 572                         self
.resizeFloater 
= botLeft
 
 573                     elif handle 
== handle_BOTTOM_RIGHT
: 
 574                         self
.resizeAnchor  
= topLeft
 
 575                         self
.resizeFloater 
= botRight
 
 578                 self
.resizeOffsetX 
= self
.resizeFloater
.x 
- mousePt
.x
 
 579                 self
.resizeOffsetY 
= self
.resizeFloater
.y 
- mousePt
.y
 
 580                 endPt 
= wx
.Point(self
.curPt
.x 
+ self
.resizeOffsetX
, 
 581                                 self
.curPt
.y 
+ self
.resizeOffsetY
) 
 582                 self
._drawVisualFeedback
(self
.resizeAnchor
, endPt
, 
 583                                          self
.resizeFeedback
, False) 
 585             elif selecting 
and (self
._getObjectAt
(mousePt
) != None): 
 587                 # The user clicked on an object to select it.  If the user 
 588                 # drags, he/she will move the object. 
 590                 self
.select(self
._getObjectAt
(mousePt
)) 
 591                 self
.dragMode 
= drag_MOVE
 
 592                 self
.moveOrigin 
= mousePt
 
 594                 self
._drawObjectOutline
(0, 0) 
 598                 # The user is dragging out a selection rect or new object. 
 600                 self
.dragOrigin 
= mousePt
 
 602                 self
.drawPanel
.SetCursor(wx
.CROSS_CURSOR
) 
 603                 self
.drawPanel
.CaptureMouse() 
 604                 self
._drawVisualFeedback
(mousePt
, mousePt
, feedbackType
, 
 606                 self
.dragMode 
= drag_DRAG
 
 612             if self
.dragMode 
== drag_RESIZE
: 
 614                 # We're resizing an object. 
 616                 mousePt 
= self
._getEventCoordinates
(event
) 
 617                 if (self
.curPt
.x 
!= mousePt
.x
) or (self
.curPt
.y 
!= mousePt
.y
): 
 618                     # Erase previous visual feedback. 
 619                     endPt 
= wx
.Point(self
.curPt
.x 
+ self
.resizeOffsetX
, 
 620                                     self
.curPt
.y 
+ self
.resizeOffsetY
) 
 621                     self
._drawVisualFeedback
(self
.resizeAnchor
, endPt
, 
 622                                              self
.resizeFeedback
, False) 
 624                     # Draw new visual feedback. 
 625                     endPt 
= wx
.Point(self
.curPt
.x 
+ self
.resizeOffsetX
, 
 626                                     self
.curPt
.y 
+ self
.resizeOffsetY
) 
 627                     self
._drawVisualFeedback
(self
.resizeAnchor
, endPt
, 
 628                                              self
.resizeFeedback
, False) 
 630             elif self
.dragMode 
== drag_MOVE
: 
 632                 # We're moving a selected object. 
 634                 mousePt 
= self
._getEventCoordinates
(event
) 
 635                 if (self
.curPt
.x 
!= mousePt
.x
) or (self
.curPt
.y 
!= mousePt
.y
): 
 636                     # Erase previous visual feedback. 
 637                     self
._drawObjectOutline
(self
.curPt
.x 
- self
.moveOrigin
.x
, 
 638                                             self
.curPt
.y 
- self
.moveOrigin
.y
) 
 640                     # Draw new visual feedback. 
 641                     self
._drawObjectOutline
(self
.curPt
.x 
- self
.moveOrigin
.x
, 
 642                                             self
.curPt
.y 
- self
.moveOrigin
.y
) 
 644             elif self
.dragMode 
== drag_DRAG
: 
 646                 # We're dragging out a new object or selection rect. 
 648                 mousePt 
= self
._getEventCoordinates
(event
) 
 649                 if (self
.curPt
.x 
!= mousePt
.x
) or (self
.curPt
.y 
!= mousePt
.y
): 
 650                     # Erase previous visual feedback. 
 651                     self
._drawVisualFeedback
(self
.dragOrigin
, self
.curPt
, 
 652                                              feedbackType
, dashedLine
) 
 654                     # Draw new visual feedback. 
 655                     self
._drawVisualFeedback
(self
.dragOrigin
, self
.curPt
, 
 656                                              feedbackType
, dashedLine
) 
 662             if self
.dragMode 
== drag_RESIZE
: 
 664                 # We're resizing an object. 
 666                 mousePt 
= self
._getEventCoordinates
(event
) 
 667                 # Erase last visual feedback. 
 668                 endPt 
= wx
.Point(self
.curPt
.x 
+ self
.resizeOffsetX
, 
 669                                 self
.curPt
.y 
+ self
.resizeOffsetY
) 
 670                 self
._drawVisualFeedback
(self
.resizeAnchor
, endPt
, 
 671                                          self
.resizeFeedback
, False) 
 673                 resizePt 
= wx
.Point(mousePt
.x 
+ self
.resizeOffsetX
, 
 674                                    mousePt
.y 
+ self
.resizeOffsetY
) 
 676                 if (self
.resizeFloater
.x 
!= resizePt
.x
) or \
 
 677                    (self
.resizeFloater
.y 
!= resizePt
.y
): 
 678                    self
._resizeObject
(self
.resizeObject
, 
 683                     self
.drawPanel
.Refresh() # Clean up after empty resize. 
 685             elif self
.dragMode 
== drag_MOVE
: 
 687                 # We're moving a selected object. 
 689                 mousePt 
= self
._getEventCoordinates
(event
) 
 690                 # Erase last visual feedback. 
 691                 self
._drawObjectOutline
(self
.curPt
.x 
- self
.moveOrigin
.x
, 
 692                                         self
.curPt
.y 
- self
.moveOrigin
.y
) 
 693                 if (self
.moveOrigin
.x 
!= mousePt
.x
) or \
 
 694                    (self
.moveOrigin
.y 
!= mousePt
.y
): 
 695                     self
._moveObject
(mousePt
.x 
- self
.moveOrigin
.x
, 
 696                                      mousePt
.y 
- self
.moveOrigin
.y
) 
 698                     self
.drawPanel
.Refresh() # Clean up after empty drag. 
 700             elif self
.dragMode 
== drag_DRAG
: 
 702                 # We're dragging out a new object or selection rect. 
 704                 mousePt 
= self
._getEventCoordinates
(event
) 
 705                 # Erase last visual feedback. 
 706                 self
._drawVisualFeedback
(self
.dragOrigin
, self
.curPt
, 
 707                                          feedbackType
, dashedLine
) 
 708                 self
.drawPanel
.ReleaseMouse() 
 709                 self
.drawPanel
.SetCursor(wx
.STANDARD_CURSOR
) 
 710                 # Perform the appropriate action for the current tool. 
 711                 if actionParam 
== param_RECT
: 
 712                     x1 
= min(self
.dragOrigin
.x
, self
.curPt
.x
) 
 713                     y1 
= min(self
.dragOrigin
.y
, self
.curPt
.y
) 
 714                     x2 
= max(self
.dragOrigin
.x
, self
.curPt
.x
) 
 715                     y2 
= max(self
.dragOrigin
.y
, self
.curPt
.y
) 
 723                         if ((x2
-x1
) < 8) or ((y2
-y1
) < 8): return # Too small. 
 725                     action(x1
, y1
, x2
-x1
, y2
-y1
) 
 726                 elif actionParam 
== param_LINE
: 
 727                     action(self
.dragOrigin
.x
, self
.dragOrigin
.y
, 
 728                            self
.curPt
.x
, self
.curPt
.y
) 
 730             self
.dragMode 
= drag_NONE 
# We've finished with this mouse event. 
 734     def onDoubleClickEvent(self
, event
): 
 735         """ Respond to a double-click within our drawing panel. 
 737         mousePt 
= self
._getEventCoordinates
(event
) 
 738         obj 
= self
._getObjectAt
(mousePt
) 
 739         if obj 
== None: return 
 741         # Let the user edit the given object. 
 743         if obj
.getType() == obj_TEXT
: 
 744             editor 
= EditTextObjectDialog(self
, "Edit Text Object") 
 745             editor
.objectToDialog(obj
) 
 746             if editor
.ShowModal() == wx
.ID_CANCEL
: 
 752             editor
.dialogToObject(obj
) 
 756             self
.drawPanel
.Refresh() 
 762     def onRightClick(self
, event
): 
 763         """ Respond to the user right-clicking within our drawing panel. 
 765             We select the clicked-on item, if necessary, and display a pop-up 
 766             menu of available options which can be applied to the selected 
 769         mousePt 
= self
._getEventCoordinates
(event
) 
 770         obj 
= self
._getObjectAt
(mousePt
) 
 772         if obj 
== None: return # Nothing selected. 
 774         # Select the clicked-on object. 
 778         # Build our pop-up menu. 
 781         menu
.Append(menu_DUPLICATE
, "Duplicate") 
 782         menu
.Append(menu_EDIT_TEXT
, "Edit...") 
 783         menu
.Append(menu_DELETE
,    "Delete") 
 784         menu
.AppendSeparator() 
 785         menu
.Append(menu_MOVE_FORWARD
,   "Move Forward") 
 786         menu
.Append(menu_MOVE_TO_FRONT
,  "Move to Front") 
 787         menu
.Append(menu_MOVE_BACKWARD
,  "Move Backward") 
 788         menu
.Append(menu_MOVE_TO_BACK
,   "Move to Back") 
 790         menu
.Enable(menu_EDIT_TEXT
,     obj
.getType() == obj_TEXT
) 
 791         menu
.Enable(menu_MOVE_FORWARD
,  obj 
!= self
.contents
[0]) 
 792         menu
.Enable(menu_MOVE_TO_FRONT
, obj 
!= self
.contents
[0]) 
 793         menu
.Enable(menu_MOVE_BACKWARD
, obj 
!= self
.contents
[-1]) 
 794         menu
.Enable(menu_MOVE_TO_BACK
,  obj 
!= self
.contents
[-1]) 
 796         EVT_MENU(self
, menu_DUPLICATE
,     self
.doDuplicate
) 
 797         EVT_MENU(self
, menu_EDIT_TEXT
,     self
.doEditText
) 
 798         EVT_MENU(self
, menu_DELETE
,        self
.doDelete
) 
 799         EVT_MENU(self
, menu_MOVE_FORWARD
,  self
.doMoveForward
) 
 800         EVT_MENU(self
, menu_MOVE_TO_FRONT
, self
.doMoveToFront
) 
 801         EVT_MENU(self
, menu_MOVE_BACKWARD
, self
.doMoveBackward
) 
 802         EVT_MENU(self
, menu_MOVE_TO_BACK
,  self
.doMoveToBack
) 
 804         # Show the pop-up menu. 
 806         clickPt 
= wx
.Point(mousePt
.x 
+ self
.drawPanel
.GetPosition().x
, 
 807                           mousePt
.y 
+ self
.drawPanel
.GetPosition().y
) 
 808         self
.drawPanel
.PopupMenu(menu
, clickPt
) 
 812     def onPaintEvent(self
, event
): 
 813         """ Respond to a request to redraw the contents of our drawing panel. 
 815         dc 
= wx
.PaintDC(self
.drawPanel
) 
 816         self
.drawPanel
.PrepareDC(dc
) 
 819         for i 
in range(len(self
.contents
)-1, -1, -1): 
 820             obj 
= self
.contents
[i
] 
 821             if obj 
in self
.selection
: 
 828     # ========================== 
 829     # == Menu Command Methods == 
 830     # ========================== 
 832     def doNew(self
, event
): 
 833         """ Respond to the "New" menu command. 
 836         newFrame 
= DrawingFrame(None, -1, "Untitled") 
 838         _docList
.append(newFrame
) 
 841     def doOpen(self
, event
): 
 842         """ Respond to the "Open" menu command. 
 847         fileName 
= wx
.FileSelector("Open File", default_extension
="psk", 
 848                                   flags 
= wx
.OPEN | wx
.FILE_MUST_EXIST
) 
 849         if fileName 
== "": return 
 850         fileName 
= os
.path
.join(os
.getcwd(), fileName
) 
 853         title 
= os
.path
.basename(fileName
) 
 855         if (self
.fileName 
== None) and (len(self
.contents
) == 0): 
 856             # Load contents into current (empty) document. 
 857             self
.fileName 
= fileName
 
 858             self
.SetTitle(os
.path
.basename(fileName
)) 
 861             # Open a new frame for this document. 
 862             newFrame 
= DrawingFrame(None, -1, os
.path
.basename(fileName
), 
 865             _docList
.append(newFrame
) 
 868     def doClose(self
, event
): 
 869         """ Respond to the "Close" menu command. 
 874             if not self
.askIfUserWantsToSave("closing"): return 
 876         _docList
.remove(self
) 
 880     def doSave(self
, event
): 
 881         """ Respond to the "Save" menu command. 
 883         if self
.fileName 
!= None: 
 887     def doSaveAs(self
, event
): 
 888         """ Respond to the "Save As" menu command. 
 890         if self
.fileName 
== None: 
 893             default 
= self
.fileName
 
 896         fileName 
= wx
.FileSelector("Save File As", "Saving", 
 897                                   default_filename
=default
, 
 898                                   default_extension
="psk", 
 900                                   flags 
= wx
.SAVE | wx
.OVERWRITE_PROMPT
) 
 901         if fileName 
== "": return # User cancelled. 
 902         fileName 
= os
.path
.join(os
.getcwd(), fileName
) 
 905         title 
= os
.path
.basename(fileName
) 
 908         self
.fileName 
= fileName
 
 912     def doRevert(self
, event
): 
 913         """ Respond to the "Revert" menu command. 
 915         if not self
.dirty
: return 
 917         if wx
.MessageBox("Discard changes made to this document?", "Confirm", 
 918                         style 
= wx
.OK | wx
.CANCEL | wx
.ICON_QUESTION
, 
 919                         parent
=self
) == wx
.CANCEL
: return 
 923     def doExit(self
, event
): 
 924         """ Respond to the "Quit" menu command. 
 926         global _docList
, _app
 
 928             if not doc
.dirty
: continue 
 930             if not doc
.askIfUserWantsToSave("quitting"): return 
 937     def doUndo(self
, event
): 
 938         """ Respond to the "Undo" menu command. 
 940         if self
.undoInfo 
== None: return 
 942         undoData 
= self
.undoInfo
 
 943         self
._saveUndoInfo
() # For undoing the undo... 
 947         for type, data 
in undoData
["contents"]: 
 948             obj 
= DrawingObject(type) 
 950             self
.contents
.append(obj
) 
 953         for i 
in undoData
["selection"]: 
 954             self
.selection
.append(self
.contents
[i
]) 
 957         self
.drawPanel
.Refresh() 
 961     def doSelectAll(self
, event
): 
 962         """ Respond to the "Select All" menu command. 
 967     def doDuplicate(self
, event
): 
 968         """ Respond to the "Duplicate" menu command. 
 973         for obj 
in self
.contents
: 
 974             if obj 
in self
.selection
: 
 975                 newObj 
= DrawingObject(obj
.getType()) 
 976                 newObj
.setData(obj
.getData()) 
 977                 pos 
= obj
.getPosition() 
 978                 newObj
.setPosition(wx
.Point(pos
.x 
+ 10, pos
.y 
+ 10)) 
 981         self
.contents 
= objs 
+ self
.contents
 
 983         self
.selectMany(objs
) 
 986     def doEditText(self
, event
): 
 987         """ Respond to the "Edit Text" menu command. 
 989         if len(self
.selection
) != 1: return 
 991         obj 
= self
.selection
[0] 
 992         if obj
.getType() != obj_TEXT
: return 
 994         editor 
= EditTextObjectDialog(self
, "Edit Text Object") 
 995         editor
.objectToDialog(obj
) 
 996         if editor
.ShowModal() == wx
.ID_CANCEL
: 
1000         self
._saveUndoInfo
() 
1002         editor
.dialogToObject(obj
) 
1006         self
.drawPanel
.Refresh() 
1010     def doDelete(self
, event
): 
1011         """ Respond to the "Delete" menu command. 
1013         self
._saveUndoInfo
() 
1015         for obj 
in self
.selection
: 
1016             self
.contents
.remove(obj
) 
1021     def doChooseSelectTool(self
, event
=None): 
1022         """ Respond to the "Select Tool" menu command. 
1024         self
._setCurrentTool
(self
.selectIcon
) 
1025         self
.drawPanel
.SetCursor(wx
.STANDARD_CURSOR
) 
1029     def doChooseLineTool(self
, event
=None): 
1030         """ Respond to the "Line Tool" menu command. 
1032         self
._setCurrentTool
(self
.lineIcon
) 
1033         self
.drawPanel
.SetCursor(wx
.CROSS_CURSOR
) 
1038     def doChooseRectTool(self
, event
=None): 
1039         """ Respond to the "Rect Tool" menu command. 
1041         self
._setCurrentTool
(self
.rectIcon
) 
1042         self
.drawPanel
.SetCursor(wx
.CROSS_CURSOR
) 
1047     def doChooseEllipseTool(self
, event
=None): 
1048         """ Respond to the "Ellipse Tool" menu command. 
1050         self
._setCurrentTool
(self
.ellipseIcon
) 
1051         self
.drawPanel
.SetCursor(wx
.CROSS_CURSOR
) 
1056     def doChooseTextTool(self
, event
=None): 
1057         """ Respond to the "Text Tool" menu command. 
1059         self
._setCurrentTool
(self
.textIcon
) 
1060         self
.drawPanel
.SetCursor(wx
.CROSS_CURSOR
) 
1065     def doMoveForward(self
, event
): 
1066         """ Respond to the "Move Forward" menu command. 
1068         if len(self
.selection
) != 1: return 
1070         self
._saveUndoInfo
() 
1072         obj 
= self
.selection
[0] 
1073         index 
= self
.contents
.index(obj
) 
1074         if index 
== 0: return 
1076         del self
.contents
[index
] 
1077         self
.contents
.insert(index
-1, obj
) 
1079         self
.drawPanel
.Refresh() 
1083     def doMoveToFront(self
, event
): 
1084         """ Respond to the "Move to Front" menu command. 
1086         if len(self
.selection
) != 1: return 
1088         self
._saveUndoInfo
() 
1090         obj 
= self
.selection
[0] 
1091         self
.contents
.remove(obj
) 
1092         self
.contents
.insert(0, obj
) 
1094         self
.drawPanel
.Refresh() 
1098     def doMoveBackward(self
, event
): 
1099         """ Respond to the "Move Backward" menu command. 
1101         if len(self
.selection
) != 1: return 
1103         self
._saveUndoInfo
() 
1105         obj 
= self
.selection
[0] 
1106         index 
= self
.contents
.index(obj
) 
1107         if index 
== len(self
.contents
) - 1: return 
1109         del self
.contents
[index
] 
1110         self
.contents
.insert(index
+1, obj
) 
1112         self
.drawPanel
.Refresh() 
1116     def doMoveToBack(self
, event
): 
1117         """ Respond to the "Move to Back" menu command. 
1119         if len(self
.selection
) != 1: return 
1121         self
._saveUndoInfo
() 
1123         obj 
= self
.selection
[0] 
1124         self
.contents
.remove(obj
) 
1125         self
.contents
.append(obj
) 
1127         self
.drawPanel
.Refresh() 
1131     def doShowAbout(self
, event
): 
1132         """ Respond to the "About pySketch" menu command. 
1134         dialog 
= wx
.Dialog(self
, -1, "About pySketch") # , 
1135                           #style=wx.DIALOG_MODAL | wx.STAY_ON_TOP) 
1136         dialog
.SetBackgroundColour(wx
.WHITE
) 
1138         panel 
= wx
.Panel(dialog
, -1) 
1139         panel
.SetBackgroundColour(wx
.WHITE
) 
1141         panelSizer 
= wx
.BoxSizer(wx
.VERTICAL
) 
1143         boldFont 
= wx
.Font(panel
.GetFont().GetPointSize(), 
1144                           panel
.GetFont().GetFamily(), 
1147         logo 
= wx
.StaticBitmap(panel
, -1, wx
.Bitmap("images/logo.bmp", 
1148                                                   wx
.BITMAP_TYPE_BMP
)) 
1150         lab1 
= wx
.StaticText(panel
, -1, "pySketch") 
1151         lab1
.SetFont(wx
.Font(36, boldFont
.GetFamily(), wx
.ITALIC
, wx
.BOLD
)) 
1152         lab1
.SetSize(lab1
.GetBestSize()) 
1154         imageSizer 
= wx
.BoxSizer(wx
.HORIZONTAL
) 
1155         imageSizer
.Add(logo
, 0, wx
.ALL | wx
.ALIGN_CENTRE_VERTICAL
, 5) 
1156         imageSizer
.Add(lab1
, 0, wx
.ALL | wx
.ALIGN_CENTRE_VERTICAL
, 5) 
1158         lab2 
= wx
.StaticText(panel
, -1, "A simple object-oriented drawing " + \
 
1160         lab2
.SetFont(boldFont
) 
1161         lab2
.SetSize(lab2
.GetBestSize()) 
1163         lab3 
= wx
.StaticText(panel
, -1, "pySketch is completely free " + \
 
1165         lab3
.SetFont(boldFont
) 
1166         lab3
.SetSize(lab3
.GetBestSize()) 
1168         lab4 
= wx
.StaticText(panel
, -1, "feel free to adapt or use this " + \
 
1169                                        "in any way you like.") 
1170         lab4
.SetFont(boldFont
) 
1171         lab4
.SetSize(lab4
.GetBestSize()) 
1173         lab5 
= wx
.StaticText(panel
, -1, "Author: Erik Westra " + \
 
1174                                        "(ewestra@wave.co.nz)") 
1175         lab5
.SetFont(boldFont
) 
1176         lab5
.SetSize(lab5
.GetBestSize()) 
1178         btnOK 
= wx
.Button(panel
, wx
.ID_OK
, "OK") 
1180         panelSizer
.Add(imageSizer
, 0, wx
.ALIGN_CENTRE
) 
1181         panelSizer
.Add((10, 10)) # Spacer. 
1182         panelSizer
.Add(lab2
, 0, wx
.ALIGN_CENTRE
) 
1183         panelSizer
.Add((10, 10)) # Spacer. 
1184         panelSizer
.Add(lab3
, 0, wx
.ALIGN_CENTRE
) 
1185         panelSizer
.Add(lab4
, 0, wx
.ALIGN_CENTRE
) 
1186         panelSizer
.Add((10, 10)) # Spacer. 
1187         panelSizer
.Add(lab5
, 0, wx
.ALIGN_CENTRE
) 
1188         panelSizer
.Add((10, 10)) # Spacer. 
1189         panelSizer
.Add(btnOK
, 0, wx
.ALL | wx
.ALIGN_CENTRE
, 5) 
1191         panel
.SetAutoLayout(True) 
1192         panel
.SetSizer(panelSizer
) 
1193         panelSizer
.Fit(panel
) 
1195         topSizer 
= wx
.BoxSizer(wx
.HORIZONTAL
) 
1196         topSizer
.Add(panel
, 0, wx
.ALL
, 10) 
1198         dialog
.SetAutoLayout(True) 
1199         dialog
.SetSizer(topSizer
) 
1200         topSizer
.Fit(dialog
) 
1204         btn 
= dialog
.ShowModal() 
1207     # ============================= 
1208     # == Object Creation Methods == 
1209     # ============================= 
1211     def createLine(self
, x1
, y1
, x2
, y2
): 
1212         """ Create a new line object at the given position and size. 
1214         self
._saveUndoInfo
() 
1216         topLeftX  
= min(x1
, x2
) 
1217         topLeftY  
= min(y1
, y2
) 
1218         botRightX 
= max(x1
, x2
) 
1219         botRightY 
= max(y1
, y2
) 
1221         obj 
= DrawingObject(obj_LINE
, position
=wx
.Point(topLeftX
, topLeftY
), 
1222                             size
=wx
.Size(botRightX
-topLeftX
, 
1223                                         botRightY
-topLeftY
), 
1224                             penColour
=self
.penColour
, 
1225                             fillColour
=self
.fillColour
, 
1226                             lineSize
=self
.lineSize
, 
1227                             startPt 
= wx
.Point(x1 
- topLeftX
, y1 
- topLeftY
), 
1228                             endPt   
= wx
.Point(x2 
- topLeftX
, y2 
- topLeftY
)) 
1229         self
.contents
.insert(0, obj
) 
1231         self
.doChooseSelectTool() 
1235     def createRect(self
, x
, y
, width
, height
): 
1236         """ Create a new rectangle object at the given position and size. 
1238         self
._saveUndoInfo
() 
1240         obj 
= DrawingObject(obj_RECT
, position
=wx
.Point(x
, y
), 
1241                             size
=wx
.Size(width
, height
), 
1242                             penColour
=self
.penColour
, 
1243                             fillColour
=self
.fillColour
, 
1244                             lineSize
=self
.lineSize
) 
1245         self
.contents
.insert(0, obj
) 
1247         self
.doChooseSelectTool() 
1251     def createEllipse(self
, x
, y
, width
, height
): 
1252         """ Create a new ellipse object at the given position and size. 
1254         self
._saveUndoInfo
() 
1256         obj 
= DrawingObject(obj_ELLIPSE
, position
=wx
.Point(x
, y
), 
1257                             size
=wx
.Size(width
, height
), 
1258                             penColour
=self
.penColour
, 
1259                             fillColour
=self
.fillColour
, 
1260                             lineSize
=self
.lineSize
) 
1261         self
.contents
.insert(0, obj
) 
1263         self
.doChooseSelectTool() 
1267     def createText(self
, x
, y
, width
, height
): 
1268         """ Create a new text object at the given position and size. 
1270         editor 
= EditTextObjectDialog(self
, "Create Text Object") 
1271         if editor
.ShowModal() == wx
.ID_CANCEL
: 
1275         self
._saveUndoInfo
() 
1277         obj 
= DrawingObject(obj_TEXT
, position
=wx
.Point(x
, y
), 
1278                                       size
=wx
.Size(width
, height
)) 
1279         editor
.dialogToObject(obj
) 
1282         self
.contents
.insert(0, obj
) 
1284         self
.doChooseSelectTool() 
1287     # ======================= 
1288     # == Selection Methods == 
1289     # ======================= 
1291     def selectAll(self
): 
1292         """ Select every DrawingObject in our document. 
1295         for obj 
in self
.contents
: 
1296             self
.selection
.append(obj
) 
1297         self
.drawPanel
.Refresh() 
1301     def deselectAll(self
): 
1302         """ Deselect every DrawingObject in our document. 
1305         self
.drawPanel
.Refresh() 
1309     def select(self
, obj
): 
1310         """ Select the given DrawingObject within our document. 
1312         self
.selection 
= [obj
] 
1313         self
.drawPanel
.Refresh() 
1317     def selectMany(self
, objs
): 
1318         """ Select the given list of DrawingObjects. 
1320         self
.selection 
= objs
 
1321         self
.drawPanel
.Refresh() 
1325     def selectByRectangle(self
, x
, y
, width
, height
): 
1326         """ Select every DrawingObject in the given rectangular region. 
1329         for obj 
in self
.contents
: 
1330             if obj
.objectWithinRect(x
, y
, width
, height
): 
1331                 self
.selection
.append(obj
) 
1332         self
.drawPanel
.Refresh() 
1335     # ====================== 
1336     # == File I/O Methods == 
1337     # ====================== 
1339     def loadContents(self
): 
1340         """ Load the contents of our document into memory. 
1342         f 
= open(self
.fileName
, "rb") 
1343         objData 
= cPickle
.load(f
) 
1346         for type, data 
in objData
: 
1347             obj 
= DrawingObject(type) 
1349             self
.contents
.append(obj
) 
1353         self
.undoInfo  
= None 
1355         self
.drawPanel
.Refresh() 
1359     def saveContents(self
): 
1360         """ Save the contents of our document to disk. 
1363         for obj 
in self
.contents
: 
1364             objData
.append([obj
.getType(), obj
.getData()]) 
1366         f 
= open(self
.fileName
, "wb") 
1367         cPickle
.dump(objData
, f
) 
1373     def askIfUserWantsToSave(self
, action
): 
1374         """ Give the user the opportunity to save the current document. 
1376             'action' is a string describing the action about to be taken.  If 
1377             the user wants to save the document, it is saved immediately.  If 
1378             the user cancels, we return False. 
1380         if not self
.dirty
: return True # Nothing to do. 
1382         response 
= wx
.MessageBox("Save changes before " + action 
+ "?", 
1383                                 "Confirm", wx
.YES_NO | wx
.CANCEL
, self
) 
1385         if response 
== wx
.YES
: 
1386             if self
.fileName 
== None: 
1387                 fileName 
= wx
.FileSelector("Save File As", "Saving", 
1388                                           default_extension
="psk", 
1390                                           flags 
= wx
.SAVE | wx
.OVERWRITE_PROMPT
) 
1391                 if fileName 
== "": return False # User cancelled. 
1392                 self
.fileName 
= fileName
 
1396         elif response 
== wx
.NO
: 
1397             return True # User doesn't want changes saved. 
1398         elif response 
== wx
.CANCEL
: 
1399             return False # User cancelled. 
1401     # ===================== 
1402     # == Private Methods == 
1403     # ===================== 
1405     def _adjustMenus(self
): 
1406         """ Adjust our menus and toolbar to reflect the current state of the 
1409         canSave   
= (self
.fileName 
!= None) and self
.dirty
 
1410         canRevert 
= (self
.fileName 
!= None) and self
.dirty
 
1411         canUndo   
= self
.undoInfo 
!= None 
1412         selection 
= len(self
.selection
) > 0 
1413         onlyOne   
= len(self
.selection
) == 1 
1414         isText    
= onlyOne 
and (self
.selection
[0].getType() == obj_TEXT
) 
1415         front     
= onlyOne 
and (self
.selection
[0] == self
.contents
[0]) 
1416         back      
= onlyOne 
and (self
.selection
[0] == self
.contents
[-1]) 
1418         # Enable/disable our menu items. 
1420         self
.fileMenu
.Enable(wx
.ID_SAVE
,   canSave
) 
1421         self
.fileMenu
.Enable(wx
.ID_REVERT
, canRevert
) 
1423         self
.editMenu
.Enable(menu_UNDO
,      canUndo
) 
1424         self
.editMenu
.Enable(menu_DUPLICATE
, selection
) 
1425         self
.editMenu
.Enable(menu_EDIT_TEXT
, isText
) 
1426         self
.editMenu
.Enable(menu_DELETE
,    selection
) 
1428         self
.toolsMenu
.Check(menu_SELECT
,  self
.curTool 
== self
.selectIcon
) 
1429         self
.toolsMenu
.Check(menu_LINE
,    self
.curTool 
== self
.lineIcon
) 
1430         self
.toolsMenu
.Check(menu_RECT
,    self
.curTool 
== self
.rectIcon
) 
1431         self
.toolsMenu
.Check(menu_ELLIPSE
, self
.curTool 
== self
.ellipseIcon
) 
1432         self
.toolsMenu
.Check(menu_TEXT
,    self
.curTool 
== self
.textIcon
) 
1434         self
.objectMenu
.Enable(menu_MOVE_FORWARD
,  onlyOne 
and not front
) 
1435         self
.objectMenu
.Enable(menu_MOVE_TO_FRONT
, onlyOne 
and not front
) 
1436         self
.objectMenu
.Enable(menu_MOVE_BACKWARD
, onlyOne 
and not back
) 
1437         self
.objectMenu
.Enable(menu_MOVE_TO_BACK
,  onlyOne 
and not back
) 
1439         # Enable/disable our toolbar icons. 
1441         self
.toolbar
.EnableTool(wx
.ID_NEW
,           True) 
1442         self
.toolbar
.EnableTool(wx
.ID_OPEN
,          True) 
1443         self
.toolbar
.EnableTool(wx
.ID_SAVE
,          canSave
) 
1444         self
.toolbar
.EnableTool(menu_UNDO
,          canUndo
) 
1445         self
.toolbar
.EnableTool(menu_DUPLICATE
,     selection
) 
1446         self
.toolbar
.EnableTool(menu_MOVE_FORWARD
,  onlyOne 
and not front
) 
1447         self
.toolbar
.EnableTool(menu_MOVE_BACKWARD
, onlyOne 
and not back
) 
1450     def _setCurrentTool(self
, newToolIcon
): 
1451         """ Set the currently selected tool. 
1453         if self
.curTool 
== newToolIcon
: return # Nothing to do. 
1455         if self
.curTool 
!= None: 
1456             self
.curTool
.deselect() 
1458         newToolIcon
.select() 
1459         self
.curTool 
= newToolIcon
 
1462     def _setPenColour(self
, colour
): 
1463         """ Set the default or selected object's pen colour. 
1465         if len(self
.selection
) > 0: 
1466             self
._saveUndoInfo
() 
1467             for obj 
in self
.selection
: 
1468                 obj
.setPenColour(colour
) 
1469             self
.drawPanel
.Refresh() 
1471             self
.penColour 
= colour
 
1472             self
.optionIndicator
.setPenColour(colour
) 
1475     def _setFillColour(self
, colour
): 
1476         """ Set the default or selected object's fill colour. 
1478         if len(self
.selection
) > 0: 
1479             self
._saveUndoInfo
() 
1480             for obj 
in self
.selection
: 
1481                 obj
.setFillColour(colour
) 
1482             self
.drawPanel
.Refresh() 
1484             self
.fillColour 
= colour
 
1485             self
.optionIndicator
.setFillColour(colour
) 
1488     def _setLineSize(self
, size
): 
1489         """ Set the default or selected object's line size. 
1491         if len(self
.selection
) > 0: 
1492             self
._saveUndoInfo
() 
1493             for obj 
in self
.selection
: 
1494                 obj
.setLineSize(size
) 
1495             self
.drawPanel
.Refresh() 
1497             self
.lineSize 
= size
 
1498             self
.optionIndicator
.setLineSize(size
) 
1501     def _saveUndoInfo(self
): 
1502         """ Remember the current state of the document, to allow for undo. 
1504             We make a copy of the document's contents, so that we can return to 
1505             the previous contents if the user does something and then wants to 
1509         for obj 
in self
.contents
: 
1510             savedContents
.append([obj
.getType(), obj
.getData()]) 
1513         for i 
in range(len(self
.contents
)): 
1514             if self
.contents
[i
] in self
.selection
: 
1515                 savedSelection
.append(i
) 
1517         self
.undoInfo 
= {"contents"  : savedContents
, 
1518                          "selection" : savedSelection
} 
1521     def _resizeObject(self
, obj
, anchorPt
, oldPt
, newPt
): 
1522         """ Resize the given object. 
1524             'anchorPt' is the unchanging corner of the object, while the 
1525             opposite corner has been resized.  'oldPt' are the current 
1526             coordinates for this corner, while 'newPt' are the new coordinates. 
1527             The object should fit within the given dimensions, though if the 
1528             new point is less than the anchor point the object will need to be 
1529             moved as well as resized, to avoid giving it a negative size. 
1531         if obj
.getType() == obj_TEXT
: 
1532             # Not allowed to resize text objects -- they're sized to fit text. 
1533             wx
.Bell(); print "4" 
1536         self
._saveUndoInfo
() 
1538         topLeft  
= wx
.Point(min(anchorPt
.x
, newPt
.x
), 
1539                            min(anchorPt
.y
, newPt
.y
)) 
1540         botRight 
= wx
.Point(max(anchorPt
.x
, newPt
.x
), 
1541                            max(anchorPt
.y
, newPt
.y
)) 
1543         newWidth  
= botRight
.x 
- topLeft
.x
 
1544         newHeight 
= botRight
.y 
- topLeft
.y
 
1546         if obj
.getType() == obj_LINE
: 
1547             # Adjust the line so that its start and end points match the new 
1548             # overall object size. 
1550             startPt 
= obj
.getStartPt() 
1551             endPt   
= obj
.getEndPt() 
1553             slopesDown 
= ((startPt
.x 
< endPt
.x
) and (startPt
.y 
< endPt
.y
)) or \
 
1554                          ((startPt
.x 
> endPt
.x
) and (startPt
.y 
> endPt
.y
)) 
1556             # Handle the user flipping the line. 
1558             hFlip 
= ((anchorPt
.x 
< oldPt
.x
) and (anchorPt
.x 
> newPt
.x
)) or \
 
1559                     ((anchorPt
.x 
> oldPt
.x
) and (anchorPt
.x 
< newPt
.x
)) 
1560             vFlip 
= ((anchorPt
.y 
< oldPt
.y
) and (anchorPt
.y 
> newPt
.y
)) or \
 
1561                     ((anchorPt
.y 
> oldPt
.y
) and (anchorPt
.y 
< newPt
.y
)) 
1563             if (hFlip 
and not vFlip
) or (vFlip 
and not hFlip
): 
1564                 slopesDown 
= not slopesDown 
# Line flipped. 
1567                 obj
.setStartPt(wx
.Point(0, 0)) 
1568                 obj
.setEndPt(wx
.Point(newWidth
, newHeight
)) 
1570                 obj
.setStartPt(wx
.Point(0, newHeight
)) 
1571                 obj
.setEndPt(wx
.Point(newWidth
, 0)) 
1573         # Finally, adjust the bounds of the object to match the new dimensions. 
1575         obj
.setPosition(topLeft
) 
1576         obj
.setSize(wx
.Size(botRight
.x 
- topLeft
.x
, botRight
.y 
- topLeft
.y
)) 
1578         self
.drawPanel
.Refresh() 
1581     def _moveObject(self
, offsetX
, offsetY
): 
1582         """ Move the currently selected object(s) by the given offset. 
1584         self
._saveUndoInfo
() 
1586         for obj 
in self
.selection
: 
1587             pos 
= obj
.getPosition() 
1588             pos
.x 
= pos
.x 
+ offsetX
 
1589             pos
.y 
= pos
.y 
+ offsetY
 
1590             obj
.setPosition(pos
) 
1592         self
.drawPanel
.Refresh() 
1595     def _buildLineSizePopup(self
, lineSize
): 
1596         """ Build the pop-up menu used to set the line size. 
1598             'lineSize' is the current line size value.  The corresponding item 
1599             is checked in the pop-up menu. 
1602         menu
.Append(id_LINESIZE_0
, "no line",      kind
=wx
.ITEM_CHECK
) 
1603         menu
.Append(id_LINESIZE_1
, "1-pixel line", kind
=wx
.ITEM_CHECK
) 
1604         menu
.Append(id_LINESIZE_2
, "2-pixel line", kind
=wx
.ITEM_CHECK
) 
1605         menu
.Append(id_LINESIZE_3
, "3-pixel line", kind
=wx
.ITEM_CHECK
) 
1606         menu
.Append(id_LINESIZE_4
, "4-pixel line", kind
=wx
.ITEM_CHECK
) 
1607         menu
.Append(id_LINESIZE_5
, "5-pixel line", kind
=wx
.ITEM_CHECK
) 
1609         if   lineSize 
== 0: menu
.Check(id_LINESIZE_0
, True) 
1610         elif lineSize 
== 1: menu
.Check(id_LINESIZE_1
, True) 
1611         elif lineSize 
== 2: menu
.Check(id_LINESIZE_2
, True) 
1612         elif lineSize 
== 3: menu
.Check(id_LINESIZE_3
, True) 
1613         elif lineSize 
== 4: menu
.Check(id_LINESIZE_4
, True) 
1614         elif lineSize 
== 5: menu
.Check(id_LINESIZE_5
, True) 
1616         self
.Bind(wx
.EVT_MENU
, self
._lineSizePopupSelected
, id=id_LINESIZE_0
, id2
=id_LINESIZE_5
) 
1621     def _lineSizePopupSelected(self
, event
): 
1622         """ Respond to the user selecting an item from the line size popup menu 
1625         if   id == id_LINESIZE_0
: self
._setLineSize
(0) 
1626         elif id == id_LINESIZE_1
: self
._setLineSize
(1) 
1627         elif id == id_LINESIZE_2
: self
._setLineSize
(2) 
1628         elif id == id_LINESIZE_3
: self
._setLineSize
(3) 
1629         elif id == id_LINESIZE_4
: self
._setLineSize
(4) 
1630         elif id == id_LINESIZE_5
: self
._setLineSize
(5) 
1632             wx
.Bell(); print "5" 
1635         self
.optionIndicator
.setLineSize(self
.lineSize
) 
1638     def _getEventCoordinates(self
, event
): 
1639         """ Return the coordinates associated with the given mouse event. 
1641             The coordinates have to be adjusted to allow for the current scroll 
1644         originX
, originY 
= self
.drawPanel
.GetViewStart() 
1645         unitX
, unitY 
= self
.drawPanel
.GetScrollPixelsPerUnit() 
1646         return wx
.Point(event
.GetX() + (originX 
* unitX
), 
1647                        event
.GetY() + (originY 
* unitY
)) 
1650     def _getObjectAndSelectionHandleAt(self
, pt
): 
1651         """ Return the object and selection handle at the given point. 
1653             We draw selection handles (small rectangles) around the currently 
1654             selected object(s).  If the given point is within one of the 
1655             selection handle rectangles, we return the associated object and a 
1656             code indicating which selection handle the point is in.  If the 
1657             point isn't within any selection handle at all, we return the tuple 
1658             (None, handle_NONE). 
1660         for obj 
in self
.selection
: 
1661             handle 
= obj
.getSelectionHandleContainingPoint(pt
.x
, pt
.y
) 
1662             if handle 
!= handle_NONE
: 
1665         return None, handle_NONE
 
1668     def _getObjectAt(self
, pt
): 
1669         """ Return the first object found which is at the given point. 
1671         for obj 
in self
.contents
: 
1672             if obj
.objectContainsPoint(pt
.x
, pt
.y
): 
1677     def _drawObjectOutline(self
, offsetX
, offsetY
): 
1678         """ Draw an outline of the currently selected object. 
1680             The selected object's outline is drawn at the object's position 
1681             plus the given offset. 
1683             Note that the outline is drawn by *inverting* the window's 
1684             contents, so calling _drawObjectOutline twice in succession will 
1685             restore the window's contents back to what they were previously. 
1687         if len(self
.selection
) != 1: return 
1689         position 
= self
.selection
[0].getPosition() 
1690         size     
= self
.selection
[0].getSize() 
1692         dc 
= wx
.ClientDC(self
.drawPanel
) 
1693         self
.drawPanel
.PrepareDC(dc
) 
1695         dc
.SetPen(wx
.BLACK_DASHED_PEN
) 
1696         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1697         dc
.SetLogicalFunction(wx
.INVERT
) 
1699         dc
.DrawRectangle(position
.x 
+ offsetX
, position
.y 
+ offsetY
, 
1700                          size
.width
, size
.height
) 
1705     def _drawVisualFeedback(self
, startPt
, endPt
, type, dashedLine
): 
1706         """ Draw visual feedback for a drawing operation. 
1708             The visual feedback consists of a line, ellipse, or rectangle based 
1709             around the two given points.  'type' should be one of the following 
1710             predefined feedback type constants: 
1712                 feedback_RECT     ->  draw rectangular feedback. 
1713                 feedback_LINE     ->  draw line feedback. 
1714                 feedback_ELLIPSE  ->  draw elliptical feedback. 
1716             if 'dashedLine' is True, the feedback is drawn as a dashed rather 
1719             Note that the feedback is drawn by *inverting* the window's 
1720             contents, so calling _drawVisualFeedback twice in succession will 
1721             restore the window's contents back to what they were previously. 
1723         dc 
= wx
.ClientDC(self
.drawPanel
) 
1724         self
.drawPanel
.PrepareDC(dc
) 
1727             dc
.SetPen(wx
.BLACK_DASHED_PEN
) 
1729             dc
.SetPen(wx
.BLACK_PEN
) 
1730         dc
.SetBrush(wx
.TRANSPARENT_BRUSH
) 
1731         dc
.SetLogicalFunction(wx
.INVERT
) 
1733         if type == feedback_RECT
: 
1734             dc
.DrawRectangle(startPt
.x
, startPt
.y
, 
1735                              endPt
.x 
- startPt
.x
, 
1736                              endPt
.y 
- startPt
.y
) 
1737         elif type == feedback_LINE
: 
1738             dc
.DrawLine(startPt
.x
, startPt
.y
, endPt
.x
, endPt
.y
) 
1739         elif type == feedback_ELLIPSE
: 
1740             dc
.DrawEllipse(startPt
.x
, startPt
.y
, 
1741                            endPt
.x 
- startPt
.x
, 
1742                            endPt
.y 
- startPt
.y
) 
1746 #---------------------------------------------------------------------------- 
1748 class DrawingObject
: 
1749     """ An object within the drawing panel. 
1751         A pySketch document consists of a front-to-back ordered list of 
1752         DrawingObjects.  Each DrawingObject has the following properties: 
1754             'type'          What type of object this is (text, line, etc). 
1755             'position'      The position of the object within the document. 
1756             'size'          The size of the object within the document. 
1757             'penColour'     The colour to use for drawing the object's outline. 
1758             'fillColour'    Colour to use for drawing object's interior. 
1759             'lineSize'      Line width (in pixels) to use for object's outline. 
1760             'startPt'       The point, relative to the object's position, where 
1761                             an obj_LINE object's line should start. 
1762             'endPt'         The point, relative to the object's position, where 
1763                             an obj_LINE object's line should end. 
1764             'text'          The object's text (obj_TEXT objects only). 
1765             'textFont'      The text object's font name. 
1766             'textSize'      The text object's point size. 
1767             'textBoldface'  If True, this text object will be drawn in 
1769             'textItalic'    If True, this text object will be drawn in italic. 
1770             'textUnderline' If True, this text object will be drawn underlined. 
1773     # ================== 
1774     # == Constructors == 
1775     # ================== 
1777     def __init__(self
, type, position
=wx
.Point(0, 0), size
=wx
.Size(0, 0), 
1778                  penColour
=wx
.BLACK
, fillColour
=wx
.WHITE
, lineSize
=1, 
1779                  text
=None, startPt
=wx
.Point(0, 0), endPt
=wx
.Point(0,0)): 
1780         """ Standard constructor. 
1782             'type' is the type of object being created.  This should be one of 
1783             the following constants: 
1790             The remaining parameters let you set various options for the newly 
1791             created DrawingObject. 
1794         self
.position          
= position
 
1796         self
.penColour         
= penColour
 
1797         self
.fillColour        
= fillColour
 
1798         self
.lineSize          
= lineSize
 
1799         self
.startPt           
= startPt
 
1802         self
.textFont          
= wx
.SystemSettings_GetFont( 
1803                                     wx
.SYS_DEFAULT_GUI_FONT
).GetFaceName() 
1805         self
.textBoldface      
= False 
1806         self
.textItalic        
= False 
1807         self
.textUnderline     
= False 
1809     # ============================= 
1810     # == Object Property Methods == 
1811     # ============================= 
1814         """ Return a copy of the object's internal data. 
1816             This is used to save this DrawingObject to disk. 
1818         return [self
.type, self
.position
.x
, self
.position
.y
, 
1819                 self
.size
.width
, self
.size
.height
, 
1820                 self
.penColour
.Red(), 
1821                 self
.penColour
.Green(), 
1822                 self
.penColour
.Blue(), 
1823                 self
.fillColour
.Red(), 
1824                 self
.fillColour
.Green(), 
1825                 self
.fillColour
.Blue(), 
1827                 self
.startPt
.x
, self
.startPt
.y
, 
1828                 self
.endPt
.x
, self
.endPt
.y
, 
1837     def setData(self
, data
): 
1838         """ Set the object's internal data. 
1840             'data' is a copy of the object's saved data, as returned by 
1841             getData() above.  This is used to restore a previously saved 
1844         #data = copy.deepcopy(data) # Needed? 
1847         self
.position          
= wx
.Point(data
[1], data
[2]) 
1848         self
.size              
= wx
.Size(data
[3], data
[4]) 
1849         self
.penColour         
= wx
.Colour(red
=data
[5], 
1852         self
.fillColour        
= wx
.Colour(red
=data
[8], 
1855         self
.lineSize          
= data
[11] 
1856         self
.startPt           
= wx
.Point(data
[12], data
[13]) 
1857         self
.endPt             
= wx
.Point(data
[14], data
[15]) 
1858         self
.text              
= data
[16] 
1859         self
.textFont          
= data
[17] 
1860         self
.textSize          
= data
[18] 
1861         self
.textBoldface      
= data
[19] 
1862         self
.textItalic        
= data
[20] 
1863         self
.textUnderline     
= data
[21] 
1867         """ Return this DrawingObject's type. 
1872     def setPosition(self
, position
): 
1873         """ Set the origin (top-left corner) for this DrawingObject. 
1875         self
.position 
= position
 
1878     def getPosition(self
): 
1879         """ Return this DrawingObject's position. 
1881         return self
.position
 
1884     def setSize(self
, size
): 
1885         """ Set the size for this DrawingObject. 
1891         """ Return this DrawingObject's size. 
1896     def setPenColour(self
, colour
): 
1897         """ Set the pen colour used for this DrawingObject. 
1899         self
.penColour 
= colour
 
1902     def getPenColour(self
): 
1903         """ Return this DrawingObject's pen colour. 
1905         return self
.penColour
 
1908     def setFillColour(self
, colour
): 
1909         """ Set the fill colour used for this DrawingObject. 
1911         self
.fillColour 
= colour
 
1914     def getFillColour(self
): 
1915         """ Return this DrawingObject's fill colour. 
1917         return self
.fillColour
 
1920     def setLineSize(self
, lineSize
): 
1921         """ Set the linesize used for this DrawingObject. 
1923         self
.lineSize 
= lineSize
 
1926     def getLineSize(self
): 
1927         """ Return this DrawingObject's line size. 
1929         return self
.lineSize
 
1932     def setStartPt(self
, startPt
): 
1933         """ Set the starting point for this line DrawingObject. 
1935         self
.startPt 
= startPt
 
1938     def getStartPt(self
): 
1939         """ Return the starting point for this line DrawingObject. 
1944     def setEndPt(self
, endPt
): 
1945         """ Set the ending point for this line DrawingObject. 
1951         """ Return the ending point for this line DrawingObject. 
1956     def setText(self
, text
): 
1957         """ Set the text for this DrawingObject. 
1963         """ Return this DrawingObject's text. 
1968     def setTextFont(self
, font
): 
1969         """ Set the typeface for this text DrawingObject. 
1971         self
.textFont 
= font
 
1974     def getTextFont(self
): 
1975         """ Return this text DrawingObject's typeface. 
1977         return self
.textFont
 
1980     def setTextSize(self
, size
): 
1981         """ Set the point size for this text DrawingObject. 
1983         self
.textSize 
= size
 
1986     def getTextSize(self
): 
1987         """ Return this text DrawingObject's text size. 
1989         return self
.textSize
 
1992     def setTextBoldface(self
, boldface
): 
1993         """ Set the boldface flag for this text DrawingObject. 
1995         self
.textBoldface 
= boldface
 
1998     def getTextBoldface(self
): 
1999         """ Return this text DrawingObject's boldface flag. 
2001         return self
.textBoldface
 
2004     def setTextItalic(self
, italic
): 
2005         """ Set the italic flag for this text DrawingObject. 
2007         self
.textItalic 
= italic
 
2010     def getTextItalic(self
): 
2011         """ Return this text DrawingObject's italic flag. 
2013         return self
.textItalic
 
2016     def setTextUnderline(self
, underline
): 
2017         """ Set the underling flag for this text DrawingObject. 
2019         self
.textUnderline 
= underline
 
2022     def getTextUnderline(self
): 
2023         """ Return this text DrawingObject's underline flag. 
2025         return self
.textUnderline
 
2027     # ============================ 
2028     # == Object Drawing Methods == 
2029     # ============================ 
2031     def draw(self
, dc
, selected
): 
2032         """ Draw this DrawingObject into our window. 
2034             'dc' is the device context to use for drawing.  If 'selected' is 
2035             True, the object is currently selected and should be drawn as such. 
2037         if self
.type != obj_TEXT
: 
2038             if self
.lineSize 
== 0: 
2039                 dc
.SetPen(wx
.Pen(self
.penColour
, self
.lineSize
, wx
.TRANSPARENT
)) 
2041                 dc
.SetPen(wx
.Pen(self
.penColour
, self
.lineSize
, wx
.SOLID
)) 
2042             dc
.SetBrush(wx
.Brush(self
.fillColour
, wx
.SOLID
)) 
2044             dc
.SetTextForeground(self
.penColour
) 
2045             dc
.SetTextBackground(self
.fillColour
) 
2047         self
._privateDraw
(dc
, self
.position
, selected
) 
2049     # ======================= 
2050     # == Selection Methods == 
2051     # ======================= 
2053     def objectContainsPoint(self
, x
, y
): 
2054         """ Returns True iff this object contains the given point. 
2056             This is used to determine if the user clicked on the object. 
2058         # Firstly, ignore any points outside of the object's bounds. 
2060         if x 
< self
.position
.x
: return False 
2061         if x 
> self
.position
.x 
+ self
.size
.x
: return False 
2062         if y 
< self
.position
.y
: return False 
2063         if y 
> self
.position
.y 
+ self
.size
.y
: return False 
2065         if self
.type in [obj_RECT
, obj_TEXT
]: 
2066             # Rectangles and text are easy -- they're always selected if the 
2067             # point is within their bounds. 
2070         # Now things get tricky.  There's no straightforward way of knowing 
2071         # whether the point is within the object's bounds...to get around this, 
2072         # we draw the object into a memory-based bitmap and see if the given 
2073         # point was drawn.  This could no doubt be done more efficiently by 
2074         # some tricky maths, but this approach works and is simple enough. 
2076         bitmap 
= wx
.EmptyBitmap(self
.size
.x 
+ 10, self
.size
.y 
+ 10) 
2078         dc
.SelectObject(bitmap
) 
2080         dc
.SetBackground(wx
.WHITE_BRUSH
) 
2082         dc
.SetPen(wx
.Pen(wx
.BLACK
, self
.lineSize 
+ 5, wx
.SOLID
)) 
2083         dc
.SetBrush(wx
.BLACK_BRUSH
) 
2084         self
._privateDraw
(dc
, wx
.Point(5, 5), True) 
2086         pixel 
= dc
.GetPixel(x 
- self
.position
.x 
+ 5, y 
- self
.position
.y 
+ 5) 
2087         if (pixel
.Red() == 0) and (pixel
.Green() == 0) and (pixel
.Blue() == 0): 
2093     def getSelectionHandleContainingPoint(self
, x
, y
): 
2094         """ Return the selection handle containing the given point, if any. 
2096             We return one of the predefined selection handle ID codes. 
2098         if self
.type == obj_LINE
: 
2099             # We have selection handles at the start and end points. 
2100             if self
._pointInSelRect
(x
, y
, self
.position
.x 
+ self
.startPt
.x
, 
2101                                           self
.position
.y 
+ self
.startPt
.y
): 
2102                 return handle_START_POINT
 
2103             elif self
._pointInSelRect
(x
, y
, self
.position
.x 
+ self
.endPt
.x
, 
2104                                             self
.position
.y 
+ self
.endPt
.y
): 
2105                 return handle_END_POINT
 
2109             # We have selection handles at all four corners. 
2110             if self
._pointInSelRect
(x
, y
, self
.position
.x
, self
.position
.y
): 
2111                 return handle_TOP_LEFT
 
2112             elif self
._pointInSelRect
(x
, y
, self
.position
.x 
+ self
.size
.width
, 
2114                 return handle_TOP_RIGHT
 
2115             elif self
._pointInSelRect
(x
, y
, self
.position
.x
, 
2116                                             self
.position
.y 
+ self
.size
.height
): 
2117                 return handle_BOTTOM_LEFT
 
2118             elif self
._pointInSelRect
(x
, y
, self
.position
.x 
+ self
.size
.width
, 
2119                                             self
.position
.y 
+ self
.size
.height
): 
2120                 return handle_BOTTOM_RIGHT
 
2125     def objectWithinRect(self
, x
, y
, width
, height
): 
2126         """ Return True iff this object falls completely within the given rect. 
2128         if x          
> self
.position
.x
:                    return False 
2129         if x 
+ width  
< self
.position
.x 
+ self
.size
.width
:  return False 
2130         if y          
> self
.position
.y
:                    return False 
2131         if y 
+ height 
< self
.position
.y 
+ self
.size
.height
: return False 
2134     # ===================== 
2135     # == Utility Methods == 
2136     # ===================== 
2138     def fitToText(self
): 
2139         """ Resize a text DrawingObject so that it fits it's text exactly. 
2141         if self
.type != obj_TEXT
: return 
2143         if self
.textBoldface
: weight 
= wx
.BOLD
 
2144         else:                 weight 
= wx
.NORMAL
 
2145         if self
.textItalic
: style 
= wx
.ITALIC
 
2146         else:               style 
= wx
.NORMAL
 
2147         font 
= wx
.Font(self
.textSize
, wx
.DEFAULT
, style
, weight
, 
2148                       self
.textUnderline
, self
.textFont
) 
2150         dummyWindow 
= wx
.Frame(None, -1, "") 
2151         dummyWindow
.SetFont(font
) 
2152         width
, height 
= dummyWindow
.GetTextExtent(self
.text
) 
2153         dummyWindow
.Destroy() 
2155         self
.size 
= wx
.Size(width
, height
) 
2157     # ===================== 
2158     # == Private Methods == 
2159     # ===================== 
2161     def _privateDraw(self
, dc
, position
, selected
): 
2162         """ Private routine to draw this DrawingObject. 
2164             'dc' is the device context to use for drawing, while 'position' is 
2165             the position in which to draw the object.  If 'selected' is True, 
2166             the object is drawn with selection handles.  This private drawing 
2167             routine assumes that the pen and brush have already been set by the 
2170         if self
.type == obj_LINE
: 
2171             dc
.DrawLine(position
.x 
+ self
.startPt
.x
, 
2172                         position
.y 
+ self
.startPt
.y
, 
2173                         position
.x 
+ self
.endPt
.x
, 
2174                         position
.y 
+ self
.endPt
.y
) 
2175         elif self
.type == obj_RECT
: 
2176             dc
.DrawRectangle(position
.x
, position
.y
, 
2177                              self
.size
.width
, self
.size
.height
) 
2178         elif self
.type == obj_ELLIPSE
: 
2179             dc
.DrawEllipse(position
.x
, position
.y
, 
2180                            self
.size
.width
, self
.size
.height
) 
2181         elif self
.type == obj_TEXT
: 
2182             if self
.textBoldface
: weight 
= wx
.BOLD
 
2183             else:                 weight 
= wx
.NORMAL
 
2184             if self
.textItalic
: style 
= wx
.ITALIC
 
2185             else:               style 
= wx
.NORMAL
 
2186             font 
= wx
.Font(self
.textSize
, wx
.DEFAULT
, style
, weight
, 
2187                           self
.textUnderline
, self
.textFont
) 
2189             dc
.DrawText(self
.text
, position
.x
, position
.y
) 
2192             dc
.SetPen(wx
.TRANSPARENT_PEN
) 
2193             dc
.SetBrush(wx
.BLACK_BRUSH
) 
2195             if self
.type == obj_LINE
: 
2196                 # Draw selection handles at the start and end points. 
2197                 self
._drawSelHandle
(dc
, position
.x 
+ self
.startPt
.x
, 
2198                                         position
.y 
+ self
.startPt
.y
) 
2199                 self
._drawSelHandle
(dc
, position
.x 
+ self
.endPt
.x
, 
2200                                         position
.y 
+ self
.endPt
.y
) 
2202                 # Draw selection handles at all four corners. 
2203                 self
._drawSelHandle
(dc
, position
.x
, position
.y
) 
2204                 self
._drawSelHandle
(dc
, position
.x 
+ self
.size
.width
, 
2206                 self
._drawSelHandle
(dc
, position
.x
, 
2207                                         position
.y 
+ self
.size
.height
) 
2208                 self
._drawSelHandle
(dc
, position
.x 
+ self
.size
.width
, 
2209                                         position
.y 
+ self
.size
.height
) 
2212     def _drawSelHandle(self
, dc
, x
, y
): 
2213         """ Draw a selection handle around this DrawingObject. 
2215             'dc' is the device context to draw the selection handle within, 
2216             while 'x' and 'y' are the coordinates to use for the centre of the 
2219         dc
.DrawRectangle(x 
- 3, y 
- 3, 6, 6) 
2222     def _pointInSelRect(self
, x
, y
, rX
, rY
): 
2223         """ Return True iff (x, y) is within the selection handle at (rX, ry). 
2225         if   x 
< rX 
- 3: return False 
2226         elif x 
> rX 
+ 3: return False 
2227         elif y 
< rY 
- 3: return False 
2228         elif y 
> rY 
+ 3: return False 
2231 #---------------------------------------------------------------------------- 
2233 class ToolPaletteIcon(GenBitmapButton
): 
2234     """ An icon appearing in the tool palette area of our sketching window. 
2236         Note that this is actually implemented as a wx.Bitmap rather 
2237         than as a wx.Icon.  wx.Icon has a very specific meaning, and isn't 
2238         appropriate for this more general use. 
2241     def __init__(self
, parent
, iconID
, iconName
, toolTip
): 
2242         """ Standard constructor. 
2244             'parent'   is the parent window this icon will be part of. 
2245             'iconID'   is the internal ID used for this icon. 
2246             'iconName' is the name used for this icon. 
2247             'toolTip'  is the tool tip text to show for this icon. 
2249             The icon name is used to get the appropriate bitmap for this icon. 
2251         bmp 
= wx
.Bitmap("images/" + iconName 
+ "Icon.bmp", wx
.BITMAP_TYPE_BMP
) 
2252         GenBitmapButton
.__init
__(self
, parent
, iconID
, bmp
, wx
.DefaultPosition
, 
2253                                 wx
.Size(bmp
.GetWidth(), bmp
.GetHeight())) 
2254         self
.SetToolTip(wx
.ToolTip(toolTip
)) 
2256         self
.iconID     
= iconID
 
2257         self
.iconName   
= iconName
 
2258         self
.isSelected 
= False 
2262         """ Select the icon. 
2264             The icon's visual representation is updated appropriately. 
2266         if self
.isSelected
: return # Nothing to do! 
2268         bmp 
= wx
.Bitmap("images/" + self
.iconName 
+ "IconSel.bmp", 
2270         self
.SetBitmapLabel(bmp
) 
2271         self
.isSelected 
= True 
2275         """ Deselect the icon. 
2277             The icon's visual representation is updated appropriately. 
2279         if not self
.isSelected
: return # Nothing to do! 
2281         bmp 
= wx
.Bitmap("images/" + self
.iconName 
+ "Icon.bmp", 
2283         self
.SetBitmapLabel(bmp
) 
2284         self
.isSelected 
= False 
2286 #---------------------------------------------------------------------------- 
2288 class ToolOptionIndicator(wx
.Window
): 
2289     """ A visual indicator which shows the current tool options. 
2291     def __init__(self
, parent
): 
2292         """ Standard constructor. 
2294         wx
.Window
.__init
__(self
, parent
, -1, wx
.DefaultPosition
, wx
.Size(52, 32)) 
2296         self
.penColour  
= wx
.BLACK
 
2297         self
.fillColour 
= wx
.WHITE
 
2300         self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
) 
2303     def setPenColour(self
, penColour
): 
2304         """ Set the indicator's current pen colour. 
2306         self
.penColour 
= penColour
 
2310     def setFillColour(self
, fillColour
): 
2311         """ Set the indicator's current fill colour. 
2313         self
.fillColour 
= fillColour
 
2317     def setLineSize(self
, lineSize
): 
2318         """ Set the indicator's current pen colour. 
2320         self
.lineSize 
= lineSize
 
2324     def OnPaint(self
, event
): 
2325         """ Paint our tool option indicator. 
2327         dc 
= wx
.PaintDC(self
) 
2330         if self
.lineSize 
== 0: 
2331             dc
.SetPen(wx
.Pen(self
.penColour
, self
.lineSize
, wx
.TRANSPARENT
)) 
2333             dc
.SetPen(wx
.Pen(self
.penColour
, self
.lineSize
, wx
.SOLID
)) 
2334         dc
.SetBrush(wx
.Brush(self
.fillColour
, wx
.SOLID
)) 
2336         dc
.DrawRectangle(5, 5, self
.GetSize().width 
- 10, 
2337                                self
.GetSize().height 
- 10) 
2341 #---------------------------------------------------------------------------- 
2343 class EditTextObjectDialog(wx
.Dialog
): 
2344     """ Dialog box used to edit the properties of a text object. 
2346         The user can edit the object's text, font, size, and text style. 
2349     def __init__(self
, parent
, title
): 
2350         """ Standard constructor. 
2352         wx
.Dialog
.__init
__(self
, parent
, -1, title
) 
2354         self
.textCtrl 
= wx
.TextCtrl(self
, 1001, "", style
=wx
.TE_PROCESS_ENTER
, 
2355                                    validator
=TextObjectValidator()) 
2356         extent 
= self
.textCtrl
.GetFullTextExtent("Hy") 
2357         lineHeight 
= extent
[1] + extent
[3] 
2358         self
.textCtrl
.SetSize(wx
.Size(-1, lineHeight 
* 4)) 
2360         self
.Bind(wx
.EVT_TEXT_ENTER
, self
._doEnter
, id=1001) 
2362         fonts 
= wx
.FontEnumerator() 
2363         fonts
.EnumerateFacenames() 
2364         self
.fontList 
= fonts
.GetFacenames() 
2365         self
.fontList
.sort() 
2367         fontLabel 
= wx
.StaticText(self
, -1, "Font:") 
2368         self
._setFontOptions
(fontLabel
, weight
=wx
.BOLD
) 
2370         self
.fontCombo 
= wx
.ComboBox(self
, -1, "", wx
.DefaultPosition
, 
2371                                     wx
.DefaultSize
, self
.fontList
, 
2372                                     style 
= wx
.CB_READONLY
) 
2373         self
.fontCombo
.SetSelection(0) # Default to first available font. 
2375         self
.sizeList 
= ["8", "9", "10", "12", "14", "16", 
2376                          "18", "20", "24", "32", "48", "72"] 
2378         sizeLabel 
= wx
.StaticText(self
, -1, "Size:") 
2379         self
._setFontOptions
(sizeLabel
, weight
=wx
.BOLD
) 
2381         self
.sizeCombo 
= wx
.ComboBox(self
, -1, "", wx
.DefaultPosition
, 
2382                                     wx
.DefaultSize
, self
.sizeList
, 
2383                                     style
=wx
.CB_READONLY
) 
2384         self
.sizeCombo
.SetSelection(3) # Default to 12 point text. 
2386         gap 
= wx
.LEFT | wx
.TOP | wx
.RIGHT
 
2388         comboSizer 
= wx
.BoxSizer(wx
.HORIZONTAL
) 
2389         comboSizer
.Add(fontLabel
,      0, gap | wx
.ALIGN_CENTRE_VERTICAL
, 5) 
2390         comboSizer
.Add(self
.fontCombo
, 0, gap
, 5) 
2391         comboSizer
.Add((5, 5)) # Spacer. 
2392         comboSizer
.Add(sizeLabel
,      0, gap | wx
.ALIGN_CENTRE_VERTICAL
, 5) 
2393         comboSizer
.Add(self
.sizeCombo
, 0, gap
, 5) 
2395         self
.boldCheckbox      
= wx
.CheckBox(self
, -1, "Bold") 
2396         self
.italicCheckbox    
= wx
.CheckBox(self
, -1, "Italic") 
2397         self
.underlineCheckbox 
= wx
.CheckBox(self
, -1, "Underline") 
2399         self
._setFontOptions
(self
.boldCheckbox
,      weight
=wx
.BOLD
) 
2400         self
._setFontOptions
(self
.italicCheckbox
,    style
=wx
.ITALIC
) 
2401         self
._setFontOptions
(self
.underlineCheckbox
, underline
=True) 
2403         styleSizer 
= wx
.BoxSizer(wx
.HORIZONTAL
) 
2404         styleSizer
.Add(self
.boldCheckbox
,      0, gap
, 5) 
2405         styleSizer
.Add(self
.italicCheckbox
,    0, gap
, 5) 
2406         styleSizer
.Add(self
.underlineCheckbox
, 0, gap
, 5) 
2408         self
.okButton     
= wx
.Button(self
, wx
.ID_OK
,     "OK") 
2409         self
.cancelButton 
= wx
.Button(self
, wx
.ID_CANCEL
, "Cancel") 
2411         btnSizer 
= wx
.BoxSizer(wx
.HORIZONTAL
) 
2412         btnSizer
.Add(self
.okButton
,     0, gap
, 5) 
2413         btnSizer
.Add(self
.cancelButton
, 0, gap
, 5) 
2415         sizer 
= wx
.BoxSizer(wx
.VERTICAL
) 
2416         sizer
.Add(self
.textCtrl
, 1, gap | wx
.EXPAND
,       5) 
2417         sizer
.Add((10, 10)) # Spacer. 
2418         sizer
.Add(comboSizer
,    0, gap | wx
.ALIGN_CENTRE
, 5) 
2419         sizer
.Add(styleSizer
,    0, gap | wx
.ALIGN_CENTRE
, 5) 
2420         sizer
.Add((10, 10)) # Spacer. 
2421         sizer
.Add(btnSizer
,      0, gap | wx
.ALIGN_CENTRE
, 5) 
2423         self
.SetAutoLayout(True) 
2424         self
.SetSizer(sizer
) 
2427         self
.textCtrl
.SetFocus() 
2430     def objectToDialog(self
, obj
): 
2431         """ Copy the properties of the given text object into the dialog box. 
2433         self
.textCtrl
.SetValue(obj
.getText()) 
2434         self
.textCtrl
.SetSelection(0, len(obj
.getText())) 
2436         for i 
in range(len(self
.fontList
)): 
2437             if self
.fontList
[i
] == obj
.getTextFont(): 
2438                 self
.fontCombo
.SetSelection(i
) 
2441         for i 
in range(len(self
.sizeList
)): 
2442             if self
.sizeList
[i
] == str(obj
.getTextSize()): 
2443                 self
.sizeCombo
.SetSelection(i
) 
2446         self
.boldCheckbox
.SetValue(obj
.getTextBoldface()) 
2447         self
.italicCheckbox
.SetValue(obj
.getTextItalic()) 
2448         self
.underlineCheckbox
.SetValue(obj
.getTextUnderline()) 
2451     def dialogToObject(self
, obj
): 
2452         """ Copy the properties from the dialog box into the given text object. 
2454         obj
.setText(self
.textCtrl
.GetValue()) 
2455         obj
.setTextFont(self
.fontCombo
.GetValue()) 
2456         obj
.setTextSize(int(self
.sizeCombo
.GetValue())) 
2457         obj
.setTextBoldface(self
.boldCheckbox
.GetValue()) 
2458         obj
.setTextItalic(self
.italicCheckbox
.GetValue()) 
2459         obj
.setTextUnderline(self
.underlineCheckbox
.GetValue()) 
2462     # ====================== 
2463     # == Private Routines == 
2464     # ====================== 
2466     def _setFontOptions(self
, ctrl
, family
=None, pointSize
=-1, 
2467                                     style
=wx
.NORMAL
, weight
=wx
.NORMAL
, 
2469         """ Change the font settings for the given control. 
2471             The meaning of the 'family', 'pointSize', 'style', 'weight' and 
2472             'underline' parameters are the same as for the wx.Font constructor. 
2473             If the family and/or pointSize isn't specified, the current default 
2476         if family 
== None: family 
= ctrl
.GetFont().GetFamily() 
2477         if pointSize 
== -1: pointSize 
= ctrl
.GetFont().GetPointSize() 
2479         ctrl
.SetFont(wx
.Font(pointSize
, family
, style
, weight
, underline
)) 
2480         ctrl
.SetSize(ctrl
.GetBestSize()) # Adjust size to reflect font change. 
2483     def _doEnter(self
, event
): 
2484         """ Respond to the user hitting the ENTER key. 
2486             We simulate clicking on the "OK" button. 
2488         if self
.Validate(): self
.Show(False) 
2490 #---------------------------------------------------------------------------- 
2492 class TextObjectValidator(wx
.PyValidator
): 
2493     """ This validator is used to ensure that the user has entered something 
2494         into the text object editor dialog's text field. 
2497         """ Standard constructor. 
2499         wx
.PyValidator
.__init
__(self
) 
2503         """ Standard cloner. 
2505             Note that every validator must implement the Clone() method. 
2507         return TextObjectValidator() 
2510     def Validate(self
, win
): 
2511         """ Validate the contents of the given text control. 
2513         textCtrl 
= self
.GetWindow() 
2514         text 
= textCtrl
.GetValue() 
2517             wx
.MessageBox("A text object must contain some text!", "Error") 
2523     def TransferToWindow(self
): 
2524         """ Transfer data from validator to window. 
2526             The default implementation returns False, indicating that an error 
2527             occurred.  We simply return True, as we don't do any data transfer. 
2529         return True # Prevent wx.Dialog from complaining. 
2532     def TransferFromWindow(self
): 
2533         """ Transfer data from window to validator. 
2535             The default implementation returns False, indicating that an error 
2536             occurred.  We simply return True, as we don't do any data transfer. 
2538         return True # Prevent wx.Dialog from complaining. 
2540 #---------------------------------------------------------------------------- 
2542 class ExceptionHandler
: 
2543     """ A simple error-handling class to write exceptions to a text file. 
2545         Under MS Windows, the standard DOS console window doesn't scroll and 
2546         closes as soon as the application exits, making it hard to find and 
2547         view Python exceptions.  This utility class allows you to handle Python 
2548         exceptions in a more friendly manner. 
2552         """ Standard constructor. 
2555         if os
.path
.exists("errors.txt"): 
2556             os
.remove("errors.txt") # Delete previous error log, if any. 
2560         """ Write the given error message to a text file. 
2562             Note that if the error message doesn't end in a carriage return, we 
2563             have to buffer up the inputs until a carriage return is received. 
2565         if (s
[-1] != "\n") and (s
[-1] != "\r"): 
2566             self
._buff 
= self
._buff 
+ s
 
2573             if s
[:9] == "Traceback": 
2574                 # Tell the user than an exception occurred. 
2575                 wx
.MessageBox("An internal error has occurred.\nPlease " + \
 
2576                              "refer to the 'errors.txt' file for details.", 
2577                              "Error", wx
.OK | wx
.CENTRE | wx
.ICON_EXCLAMATION
) 
2579             f 
= open("errors.txt", "a") 
2583             pass # Don't recursively crash on errors. 
2585 #---------------------------------------------------------------------------- 
2587 class SketchApp(wx
.App
): 
2588     """ The main pySketch application object. 
2591         """ Initialise the application. 
2596         if len(sys
.argv
) == 1: 
2597             # No file name was specified on the command line -> start with a 
2599             frame 
= DrawingFrame(None, -1, "Untitled") 
2602             _docList
.append(frame
) 
2604             # Load the file(s) specified on the command line. 
2605             for arg 
in sys
.argv
[1:]: 
2606                 fileName 
= os
.path
.join(os
.getcwd(), arg
) 
2607                 if os
.path
.isfile(fileName
): 
2608                     frame 
= DrawingFrame(None, -1, 
2609                                          os
.path
.basename(fileName
), 
2612                     _docList
.append(frame
) 
2616 #---------------------------------------------------------------------------- 
2619     """ Start up the pySketch application. 
2623     # Redirect python exceptions to a log file. 
2625     sys
.stderr 
= ExceptionHandler() 
2627     # Create and start the pySketch application. 
2633 if __name__ 
== "__main__":