git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@11570 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
		
			
				
	
	
		
			2639 lines
		
	
	
		
			74 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			2639 lines
		
	
	
		
			74 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
""" pySketch
 | 
						|
 | 
						|
    A simple object-oriented drawing program.
 | 
						|
 | 
						|
    This is completely free software; please feel free to adapt or use this in
 | 
						|
    any way you like.
 | 
						|
 | 
						|
    Author: Erik Westra (ewestra@wave.co.nz)
 | 
						|
 | 
						|
    #########################################################################
 | 
						|
 | 
						|
    NOTE
 | 
						|
 | 
						|
    pySketch requires wxPython version 2.3.  If you are running an earlier
 | 
						|
    version, you need to patch your copy of wxPython to fix a bug which will
 | 
						|
    cause the "Edit Text Object" dialog box to crash.
 | 
						|
 | 
						|
    To patch an earlier version of wxPython, edit the wxPython/windows.py file,
 | 
						|
    find the wxPyValidator.__init__ method and change the line which reads:
 | 
						|
 | 
						|
        self._setSelf(self, wxPyValidator, 0)
 | 
						|
 | 
						|
    to:
 | 
						|
 | 
						|
        self._setSelf(self, wxPyValidator, 1)
 | 
						|
 | 
						|
    This fixes a known bug in wxPython 2.2.5 (and possibly earlier) which has
 | 
						|
    now been fixed in wxPython 2.3.
 | 
						|
 | 
						|
    #########################################################################
 | 
						|
 | 
						|
    TODO:
 | 
						|
 | 
						|
      * Add ARGV checking to see if a document was double-clicked on.
 | 
						|
 | 
						|
    Known Bugs:
 | 
						|
 | 
						|
      * Scrolling the window causes the drawing panel to be mucked up until you
 | 
						|
	refresh it.  I've got no idea why.
 | 
						|
 | 
						|
      * I suspect that the reference counting for some wxPoint objects is
 | 
						|
        getting mucked up; when the user quits, we get errors about being
 | 
						|
	unable to call del on a 'None' object.
 | 
						|
"""
 | 
						|
import string, cPickle, os.path
 | 
						|
from wxPython.wx import *
 | 
						|
 | 
						|
import traceback, types
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
#                            System Constants
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
# Our menu item IDs:
 | 
						|
 | 
						|
menu_UNDO          = 10001 # Edit menu items.
 | 
						|
menu_SELECT_ALL    = 10002
 | 
						|
menu_DUPLICATE     = 10003
 | 
						|
menu_EDIT_TEXT     = 10004
 | 
						|
menu_DELETE        = 10005
 | 
						|
 | 
						|
menu_SELECT        = 10101 # Tools menu items.
 | 
						|
menu_LINE          = 10102
 | 
						|
menu_RECT          = 10103
 | 
						|
menu_ELLIPSE       = 10104
 | 
						|
menu_TEXT          = 10105
 | 
						|
 | 
						|
menu_MOVE_FORWARD  = 10201 # Object menu items.
 | 
						|
menu_MOVE_TO_FRONT = 10202
 | 
						|
menu_MOVE_BACKWARD = 10203
 | 
						|
menu_MOVE_TO_BACK  = 10204
 | 
						|
 | 
						|
menu_ABOUT         = 10205 # Help menu items.
 | 
						|
 | 
						|
# Our tool IDs:
 | 
						|
 | 
						|
id_SELECT  = 11001
 | 
						|
id_LINE    = 11002
 | 
						|
id_RECT    = 11003
 | 
						|
id_ELLIPSE = 11004
 | 
						|
id_TEXT    = 11005
 | 
						|
 | 
						|
# Our tool option IDs:
 | 
						|
 | 
						|
id_FILL_OPT   = 12001
 | 
						|
id_PEN_OPT    = 12002
 | 
						|
id_LINE_OPT   = 12003
 | 
						|
 | 
						|
id_LINESIZE_0 = 13001
 | 
						|
id_LINESIZE_1 = 13002
 | 
						|
id_LINESIZE_2 = 13003
 | 
						|
id_LINESIZE_3 = 13004
 | 
						|
id_LINESIZE_4 = 13005
 | 
						|
id_LINESIZE_5 = 13006
 | 
						|
 | 
						|
# DrawObject type IDs:
 | 
						|
 | 
						|
obj_LINE    = 1
 | 
						|
obj_RECT    = 2
 | 
						|
obj_ELLIPSE = 3
 | 
						|
obj_TEXT    = 4
 | 
						|
 | 
						|
# Selection handle IDs:
 | 
						|
 | 
						|
handle_NONE         = 1
 | 
						|
handle_TOP_LEFT     = 2
 | 
						|
handle_TOP_RIGHT    = 3
 | 
						|
handle_BOTTOM_LEFT  = 4
 | 
						|
handle_BOTTOM_RIGHT = 5
 | 
						|
handle_START_POINT  = 6
 | 
						|
handle_END_POINT    = 7
 | 
						|
 | 
						|
# Dragging operations:
 | 
						|
 | 
						|
drag_NONE   = 1
 | 
						|
drag_RESIZE = 2
 | 
						|
drag_MOVE   = 3
 | 
						|
drag_DRAG   = 4
 | 
						|
 | 
						|
# Visual Feedback types:
 | 
						|
 | 
						|
feedback_LINE    = 1
 | 
						|
feedback_RECT    = 2
 | 
						|
feedback_ELLIPSE = 3
 | 
						|
 | 
						|
# Mouse-event action parameter types:
 | 
						|
 | 
						|
param_RECT = 1
 | 
						|
param_LINE = 2
 | 
						|
 | 
						|
# Size of the drawing page, in pixels.
 | 
						|
 | 
						|
PAGE_WIDTH  = 1000
 | 
						|
PAGE_HEIGHT = 1000
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
class DrawingFrame(wxFrame):
 | 
						|
    """ A frame showing the contents of a single document. """
 | 
						|
 | 
						|
    # ==========================================
 | 
						|
    # == Initialisation and Window Management ==
 | 
						|
    # ==========================================
 | 
						|
 | 
						|
    def __init__(self, parent, id, title, fileName=None):
 | 
						|
	""" Standard constructor.
 | 
						|
 | 
						|
	    'parent', 'id' and 'title' are all passed to the standard wxFrame
 | 
						|
	    constructor.  'fileName' is the name and path of a saved file to
 | 
						|
	    load into this frame, if any.
 | 
						|
	"""
 | 
						|
        wxFrame.__init__(self, parent, id, title,
 | 
						|
			 style = wxDEFAULT_FRAME_STYLE | wxWANTS_CHARS |
 | 
						|
				 wxNO_FULL_REPAINT_ON_RESIZE)
 | 
						|
 | 
						|
	# Setup our menu bar.
 | 
						|
 | 
						|
	menuBar = wxMenuBar()
 | 
						|
 | 
						|
	self.fileMenu = wxMenu()
 | 
						|
	self.fileMenu.Append(wxID_NEW,    "New\tCTRL-N")
 | 
						|
	self.fileMenu.Append(wxID_OPEN,   "Open...\tCTRL-O")
 | 
						|
	self.fileMenu.Append(wxID_CLOSE,  "Close\tCTRL-W")
 | 
						|
	self.fileMenu.AppendSeparator()
 | 
						|
	self.fileMenu.Append(wxID_SAVE,   "Save\tCTRL-S")
 | 
						|
	self.fileMenu.Append(wxID_SAVEAS, "Save As...")
 | 
						|
	self.fileMenu.Append(wxID_REVERT, "Revert...")
 | 
						|
	self.fileMenu.AppendSeparator()
 | 
						|
	self.fileMenu.Append(wxID_EXIT,   "Quit\tCTRL-Q")
 | 
						|
 | 
						|
	menuBar.Append(self.fileMenu, "File")
 | 
						|
 | 
						|
	self.editMenu = wxMenu()
 | 
						|
	self.editMenu.Append(menu_UNDO,          "Undo\tCTRL-Z")
 | 
						|
	self.editMenu.AppendSeparator()
 | 
						|
	self.editMenu.Append(menu_SELECT_ALL,    "Select All\tCTRL-A")
 | 
						|
	self.editMenu.AppendSeparator()
 | 
						|
	self.editMenu.Append(menu_DUPLICATE,     "Duplicate\tCTRL-D")
 | 
						|
	self.editMenu.Append(menu_EDIT_TEXT,     "Edit...\tCTRL-E")
 | 
						|
	self.editMenu.Append(menu_DELETE,        "Delete\tDEL")
 | 
						|
 | 
						|
	menuBar.Append(self.editMenu, "Edit")
 | 
						|
 | 
						|
	self.toolsMenu = wxMenu()
 | 
						|
	self.toolsMenu.Append(menu_SELECT,  "Selection", checkable=true)
 | 
						|
	self.toolsMenu.Append(menu_LINE,    "Line",      checkable=true)
 | 
						|
	self.toolsMenu.Append(menu_RECT,    "Rectangle", checkable=true)
 | 
						|
	self.toolsMenu.Append(menu_ELLIPSE, "Ellipse",   checkable=true)
 | 
						|
	self.toolsMenu.Append(menu_TEXT,    "Text",      checkable=true)
 | 
						|
 | 
						|
	menuBar.Append(self.toolsMenu, "Tools")
 | 
						|
 | 
						|
	self.objectMenu = wxMenu()
 | 
						|
	self.objectMenu.Append(menu_MOVE_FORWARD,  "Move Forward")
 | 
						|
	self.objectMenu.Append(menu_MOVE_TO_FRONT, "Move to Front\tCTRL-F")
 | 
						|
	self.objectMenu.Append(menu_MOVE_BACKWARD, "Move Backward")
 | 
						|
	self.objectMenu.Append(menu_MOVE_TO_BACK,  "Move to Back\tCTRL-B")
 | 
						|
 | 
						|
	menuBar.Append(self.objectMenu, "Object")
 | 
						|
 | 
						|
	self.helpMenu = wxMenu()
 | 
						|
	self.helpMenu.Append(menu_ABOUT, "About pySketch...")
 | 
						|
 | 
						|
	menuBar.Append(self.helpMenu, "Help")
 | 
						|
 | 
						|
	self.SetMenuBar(menuBar)
 | 
						|
 | 
						|
	# Create our toolbar.
 | 
						|
 | 
						|
	self.toolbar = self.CreateToolBar(wxTB_HORIZONTAL |
 | 
						|
					  wxNO_BORDER | wxTB_FLAT)
 | 
						|
 | 
						|
	self.toolbar.AddSimpleTool(wxID_NEW,
 | 
						|
				   wxBitmap("images/new.bmp",
 | 
						|
					    wxBITMAP_TYPE_BMP),
 | 
						|
				   "New")
 | 
						|
	self.toolbar.AddSimpleTool(wxID_OPEN,
 | 
						|
				   wxBitmap("images/open.bmp",
 | 
						|
					    wxBITMAP_TYPE_BMP),
 | 
						|
				   "Open")
 | 
						|
	self.toolbar.AddSimpleTool(wxID_SAVE,
 | 
						|
				   wxBitmap("images/save.bmp",
 | 
						|
					    wxBITMAP_TYPE_BMP),
 | 
						|
				   "Save")
 | 
						|
	self.toolbar.AddSeparator()
 | 
						|
	self.toolbar.AddSimpleTool(menu_UNDO,
 | 
						|
				   wxBitmap("images/undo.bmp",
 | 
						|
					    wxBITMAP_TYPE_BMP),
 | 
						|
				   "Undo")
 | 
						|
	self.toolbar.AddSeparator()
 | 
						|
	self.toolbar.AddSimpleTool(menu_DUPLICATE,
 | 
						|
				   wxBitmap("images/duplicate.bmp",
 | 
						|
					    wxBITMAP_TYPE_BMP),
 | 
						|
				   "Duplicate")
 | 
						|
	self.toolbar.AddSeparator()
 | 
						|
	self.toolbar.AddSimpleTool(menu_MOVE_FORWARD,
 | 
						|
				   wxBitmap("images/moveForward.bmp",
 | 
						|
					    wxBITMAP_TYPE_BMP),
 | 
						|
				   "Move Forward")
 | 
						|
	self.toolbar.AddSimpleTool(menu_MOVE_BACKWARD,
 | 
						|
				   wxBitmap("images/moveBack.bmp",
 | 
						|
					    wxBITMAP_TYPE_BMP),
 | 
						|
				   "Move Backward")
 | 
						|
 | 
						|
	self.toolbar.Realize()
 | 
						|
 | 
						|
	# Associate each menu/toolbar item with the method that handles that
 | 
						|
	# item.
 | 
						|
 | 
						|
	EVT_MENU(self, wxID_NEW,    self.doNew)
 | 
						|
	EVT_MENU(self, wxID_OPEN,   self.doOpen)
 | 
						|
	EVT_MENU(self, wxID_CLOSE,  self.doClose)
 | 
						|
	EVT_MENU(self, wxID_SAVE,   self.doSave)
 | 
						|
	EVT_MENU(self, wxID_SAVEAS, self.doSaveAs)
 | 
						|
	EVT_MENU(self, wxID_REVERT, self.doRevert)
 | 
						|
	EVT_MENU(self, wxID_EXIT,   self.doExit)
 | 
						|
 | 
						|
	EVT_MENU(self, menu_UNDO,          self.doUndo)
 | 
						|
	EVT_MENU(self, menu_SELECT_ALL,    self.doSelectAll)
 | 
						|
	EVT_MENU(self, menu_DUPLICATE,     self.doDuplicate)
 | 
						|
	EVT_MENU(self, menu_EDIT_TEXT,     self.doEditText)
 | 
						|
	EVT_MENU(self, menu_DELETE,        self.doDelete)
 | 
						|
 | 
						|
	EVT_MENU(self, menu_SELECT,  self.doChooseSelectTool)
 | 
						|
	EVT_MENU(self, menu_LINE,    self.doChooseLineTool)
 | 
						|
	EVT_MENU(self, menu_RECT,    self.doChooseRectTool)
 | 
						|
	EVT_MENU(self, menu_ELLIPSE, self.doChooseEllipseTool)
 | 
						|
	EVT_MENU(self, menu_TEXT,    self.doChooseTextTool)
 | 
						|
 | 
						|
	EVT_MENU(self, menu_MOVE_FORWARD,  self.doMoveForward)
 | 
						|
	EVT_MENU(self, menu_MOVE_TO_FRONT, self.doMoveToFront)
 | 
						|
	EVT_MENU(self, menu_MOVE_BACKWARD, self.doMoveBackward)
 | 
						|
	EVT_MENU(self, menu_MOVE_TO_BACK,  self.doMoveToBack)
 | 
						|
 | 
						|
	EVT_MENU(self, menu_ABOUT, self.doShowAbout)
 | 
						|
 | 
						|
	# Install our own method to handle closing the window.  This allows us
 | 
						|
	# to ask the user if he/she wants to save before closing the window, as
 | 
						|
	# well as keeping track of which windows are currently open.
 | 
						|
 | 
						|
	EVT_CLOSE(self, self.doClose)
 | 
						|
 | 
						|
	# Install our own method for handling keystrokes.  We use this to let
 | 
						|
	# the user move the selected object(s) around using the arrow keys.
 | 
						|
 | 
						|
	EVT_CHAR_HOOK(self, self.onKeyEvent)
 | 
						|
 | 
						|
	# Setup our top-most panel.  This holds the entire contents of the
 | 
						|
	# window, excluding the menu bar.
 | 
						|
 | 
						|
	self.topPanel = wxPanel(self, -1, style=wxSIMPLE_BORDER)
 | 
						|
 | 
						|
	# Setup our tool palette, with all our drawing tools and option icons.
 | 
						|
 | 
						|
	self.toolPalette = wxBoxSizer(wxVERTICAL)
 | 
						|
 | 
						|
	self.selectIcon  = ToolPaletteIcon(self.topPanel, id_SELECT,
 | 
						|
					   "select", "Selection Tool")
 | 
						|
	self.lineIcon    = ToolPaletteIcon(self.topPanel, id_LINE,
 | 
						|
					   "line", "Line Tool")
 | 
						|
	self.rectIcon    = ToolPaletteIcon(self.topPanel, id_RECT,
 | 
						|
					   "rect", "Rectangle Tool")
 | 
						|
	self.ellipseIcon = ToolPaletteIcon(self.topPanel, id_ELLIPSE,
 | 
						|
					   "ellipse", "Ellipse Tool")
 | 
						|
	self.textIcon    = ToolPaletteIcon(self.topPanel, id_TEXT,
 | 
						|
					   "text", "Text Tool")
 | 
						|
 | 
						|
	toolSizer = wxGridSizer(0, 2, 5, 5)
 | 
						|
	toolSizer.Add(self.selectIcon)
 | 
						|
	toolSizer.Add(0, 0) # Gap to make tool icons line up nicely.
 | 
						|
	toolSizer.Add(self.lineIcon)
 | 
						|
	toolSizer.Add(self.rectIcon)
 | 
						|
	toolSizer.Add(self.ellipseIcon)
 | 
						|
	toolSizer.Add(self.textIcon)
 | 
						|
 | 
						|
	self.optionIndicator = ToolOptionIndicator(self.topPanel)
 | 
						|
	self.optionIndicator.SetToolTip(
 | 
						|
		wxToolTip("Shows Current Pen/Fill/Line Size Settings"))
 | 
						|
 | 
						|
	optionSizer = wxBoxSizer(wxHORIZONTAL)
 | 
						|
 | 
						|
	self.penOptIcon  = ToolPaletteIcon(self.topPanel, id_PEN_OPT,
 | 
						|
					   "penOpt", "Set Pen Colour")
 | 
						|
	self.fillOptIcon = ToolPaletteIcon(self.topPanel, id_FILL_OPT,
 | 
						|
					   "fillOpt", "Set Fill Colour")
 | 
						|
	self.lineOptIcon = ToolPaletteIcon(self.topPanel, id_LINE_OPT,
 | 
						|
					   "lineOpt", "Set Line Size")
 | 
						|
 | 
						|
	margin = wxLEFT | wxRIGHT
 | 
						|
	optionSizer.Add(self.penOptIcon,  0, margin, 1)
 | 
						|
	optionSizer.Add(self.fillOptIcon, 0, margin, 1)
 | 
						|
	optionSizer.Add(self.lineOptIcon, 0, margin, 1)
 | 
						|
 | 
						|
	margin = wxTOP | wxLEFT | wxRIGHT | wxALIGN_CENTRE
 | 
						|
	self.toolPalette.Add(toolSizer,            0, margin, 5)
 | 
						|
	self.toolPalette.Add(0, 0,                 0, margin, 5) # Spacer.
 | 
						|
	self.toolPalette.Add(self.optionIndicator, 0, margin, 5)
 | 
						|
	self.toolPalette.Add(optionSizer,          0, margin, 5)
 | 
						|
 | 
						|
	# Make the tool palette icons respond when the user clicks on them.
 | 
						|
 | 
						|
	EVT_LEFT_DOWN(self.selectIcon,  self.onToolIconClick)
 | 
						|
	EVT_LEFT_DOWN(self.lineIcon,    self.onToolIconClick)
 | 
						|
	EVT_LEFT_DOWN(self.rectIcon,    self.onToolIconClick)
 | 
						|
	EVT_LEFT_DOWN(self.ellipseIcon, self.onToolIconClick)
 | 
						|
	EVT_LEFT_DOWN(self.textIcon,    self.onToolIconClick)
 | 
						|
	EVT_LEFT_DOWN(self.penOptIcon,  self.onPenOptionIconClick)
 | 
						|
	EVT_LEFT_DOWN(self.fillOptIcon, self.onFillOptionIconClick)
 | 
						|
	EVT_LEFT_DOWN(self.lineOptIcon, self.onLineOptionIconClick)
 | 
						|
 | 
						|
	# Setup the main drawing area.
 | 
						|
 | 
						|
	self.drawPanel = wxScrolledWindow(self.topPanel, -1,
 | 
						|
					  style=wxSUNKEN_BORDER)
 | 
						|
	self.drawPanel.SetBackgroundColour(wxWHITE)
 | 
						|
 | 
						|
	self.drawPanel.EnableScrolling(true, true)
 | 
						|
	self.drawPanel.SetScrollbars(20, 20, PAGE_WIDTH / 20, PAGE_HEIGHT / 20)
 | 
						|
 | 
						|
	EVT_LEFT_DOWN(self.drawPanel, self.onMouseEvent)
 | 
						|
	EVT_LEFT_DCLICK(self.drawPanel, self.onDoubleClickEvent)
 | 
						|
	EVT_RIGHT_DOWN(self.drawPanel, self.onRightClick)
 | 
						|
	EVT_MOTION(self.drawPanel, self.onMouseEvent)
 | 
						|
	EVT_LEFT_UP(self.drawPanel, self.onMouseEvent)
 | 
						|
	EVT_PAINT(self.drawPanel, self.onPaintEvent)
 | 
						|
 | 
						|
	# Position everything in the window.
 | 
						|
 | 
						|
	topSizer = wxBoxSizer(wxHORIZONTAL)
 | 
						|
	topSizer.Add(self.toolPalette, 0)
 | 
						|
	topSizer.Add(self.drawPanel, 1, wxEXPAND)
 | 
						|
 | 
						|
	self.topPanel.SetAutoLayout(true)
 | 
						|
	self.topPanel.SetSizer(topSizer)
 | 
						|
 | 
						|
	self.SetSizeHints(minW=250, minH=200)
 | 
						|
	self.SetSize(wxSize(600, 400))
 | 
						|
 | 
						|
	# Select an initial tool.
 | 
						|
 | 
						|
	self.curTool = None
 | 
						|
	self._setCurrentTool(self.selectIcon)
 | 
						|
 | 
						|
	# Setup our frame to hold the contents of a sketch document.
 | 
						|
 | 
						|
	self.dirty     = false
 | 
						|
	self.fileName  = fileName
 | 
						|
	self.contents  = []     # front-to-back ordered list of DrawingObjects.
 | 
						|
	self.selection = []     # List of selected DrawingObjects.
 | 
						|
	self.undoInfo  = None   # Saved contents for undo.
 | 
						|
	self.dragMode  = drag_NONE # Current mouse-drag mode.
 | 
						|
 | 
						|
	if self.fileName != None:
 | 
						|
	    self.loadContents()
 | 
						|
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
	# Finally, set our initial pen, fill and line options.
 | 
						|
 | 
						|
	self.penColour  = wxBLACK
 | 
						|
	self.fillColour = wxWHITE
 | 
						|
	self.lineSize   = 1
 | 
						|
 | 
						|
    # ============================
 | 
						|
    # == Event Handling Methods ==
 | 
						|
    # ============================
 | 
						|
 | 
						|
    def onToolIconClick(self, event):
 | 
						|
	""" Respond to the user clicking on one of our tool icons.
 | 
						|
	"""
 | 
						|
	iconID = wxPyTypeCast(event.GetEventObject(), "wxWindow").GetId()
 | 
						|
	if   iconID == id_SELECT:   self.doChooseSelectTool()
 | 
						|
	elif iconID == id_LINE:     self.doChooseLineTool()
 | 
						|
	elif iconID == id_RECT:     self.doChooseRectTool()
 | 
						|
	elif iconID == id_ELLIPSE:  self.doChooseEllipseTool()
 | 
						|
	elif iconID == id_TEXT:     self.doChooseTextTool()
 | 
						|
	else:                       wxBell()
 | 
						|
 | 
						|
 | 
						|
    def onPenOptionIconClick(self, event):
 | 
						|
	""" Respond to the user clicking on the "Pen Options" icon.
 | 
						|
	"""
 | 
						|
	data = wxColourData()
 | 
						|
	if len(self.selection) == 1:
 | 
						|
	    data.SetColour(self.selection[0].getPenColour())
 | 
						|
	else:
 | 
						|
	    data.SetColour(self.penColour)
 | 
						|
 | 
						|
	dialog = wxColourDialog(self, data)
 | 
						|
	if dialog.ShowModal() == wxID_OK:
 | 
						|
	    c = dialog.GetColourData().GetColour()
 | 
						|
	    self._setPenColour(wxColour(c.Red(), c.Green(), c.Blue()))
 | 
						|
        dialog.Destroy()
 | 
						|
 | 
						|
 | 
						|
    def onFillOptionIconClick(self, event):
 | 
						|
	""" Respond to the user clicking on the "Fill Options" icon.
 | 
						|
	"""
 | 
						|
	data = wxColourData()
 | 
						|
	if len(self.selection) == 1:
 | 
						|
	    data.SetColour(self.selection[0].getFillColour())
 | 
						|
	else:
 | 
						|
	    data.SetColour(self.fillColour)
 | 
						|
 | 
						|
	dialog = wxColourDialog(self, data)
 | 
						|
	if dialog.ShowModal() == wxID_OK:
 | 
						|
	    c = dialog.GetColourData().GetColour()
 | 
						|
	    self._setFillColour(wxColour(c.Red(), c.Green(), c.Blue()))
 | 
						|
        dialog.Destroy()
 | 
						|
 | 
						|
    def onLineOptionIconClick(self, event):
 | 
						|
	""" Respond to the user clicking on the "Line Options" icon.
 | 
						|
	"""
 | 
						|
	if len(self.selection) == 1:
 | 
						|
	    menu = self._buildLineSizePopup(self.selection[0].getLineSize())
 | 
						|
	else:
 | 
						|
	    menu = self._buildLineSizePopup(self.lineSize)
 | 
						|
 | 
						|
	pos = self.lineOptIcon.GetPosition()
 | 
						|
	pos.y = pos.y + self.lineOptIcon.GetSize().height
 | 
						|
        self.PopupMenu(menu, pos)
 | 
						|
        menu.Destroy()
 | 
						|
 | 
						|
 | 
						|
    def onKeyEvent(self, event):
 | 
						|
	""" Respond to a keypress event.
 | 
						|
 | 
						|
	    We make the arrow keys move the selected object(s) by one pixel in
 | 
						|
	    the given direction.
 | 
						|
	"""
 | 
						|
	if event.GetKeyCode() == WXK_UP:
 | 
						|
	    self._moveObject(0, -1)
 | 
						|
	elif event.GetKeyCode() == WXK_DOWN:
 | 
						|
	    self._moveObject(0, 1)
 | 
						|
	elif event.GetKeyCode() == WXK_LEFT:
 | 
						|
	    self._moveObject(-1, 0)
 | 
						|
	elif event.GetKeyCode() == WXK_RIGHT:
 | 
						|
	    self._moveObject(1, 0)
 | 
						|
	else:
 | 
						|
	    event.Skip()
 | 
						|
 | 
						|
 | 
						|
    def onMouseEvent(self, event):
 | 
						|
	""" Respond to the user clicking on our main drawing panel.
 | 
						|
 | 
						|
	    How we respond depends on the currently selected tool.
 | 
						|
	"""
 | 
						|
	if not (event.LeftDown() or event.Dragging() or event.LeftUp()):
 | 
						|
	    return # Ignore mouse movement without click/drag.
 | 
						|
 | 
						|
	if self.curTool == self.selectIcon:
 | 
						|
	    feedbackType = feedback_RECT
 | 
						|
	    action       = self.selectByRectangle
 | 
						|
	    actionParam  = param_RECT
 | 
						|
	    selecting    = true
 | 
						|
	    dashedLine   = true
 | 
						|
	elif self.curTool == self.lineIcon:
 | 
						|
	    feedbackType = feedback_LINE
 | 
						|
	    action       = self.createLine
 | 
						|
	    actionParam  = param_LINE
 | 
						|
	    selecting    = false
 | 
						|
	    dashedLine   = false
 | 
						|
	elif self.curTool == self.rectIcon:
 | 
						|
	    feedbackType = feedback_RECT
 | 
						|
	    action       = self.createRect
 | 
						|
	    actionParam  = param_RECT
 | 
						|
	    selecting    = false
 | 
						|
	    dashedLine   = false
 | 
						|
	elif self.curTool == self.ellipseIcon:
 | 
						|
	    feedbackType = feedback_ELLIPSE
 | 
						|
	    action       = self.createEllipse
 | 
						|
	    actionParam  = param_RECT
 | 
						|
	    selecting    = false
 | 
						|
	    dashedLine   = false
 | 
						|
	elif self.curTool == self.textIcon:
 | 
						|
	    feedbackType = feedback_RECT
 | 
						|
	    action       = self.createText
 | 
						|
	    actionParam  = param_RECT
 | 
						|
	    selecting    = false
 | 
						|
	    dashedLine   = true
 | 
						|
	else:
 | 
						|
	    wxBell()
 | 
						|
	    return
 | 
						|
 | 
						|
	if event.LeftDown():
 | 
						|
	    mousePt = self._getEventCoordinates(event)
 | 
						|
	    if selecting:
 | 
						|
		obj, handle = self._getObjectAndSelectionHandleAt(mousePt)
 | 
						|
 | 
						|
	    if selecting and (obj != None) and (handle != handle_NONE):
 | 
						|
 | 
						|
		# The user clicked on an object's selection handle.  Let the
 | 
						|
		# user resize the clicked-on object.
 | 
						|
 | 
						|
		self.dragMode     = drag_RESIZE
 | 
						|
		self.resizeObject = obj
 | 
						|
 | 
						|
		if obj.getType() == obj_LINE:
 | 
						|
		    self.resizeFeedback = feedback_LINE
 | 
						|
		    pos  = obj.getPosition()
 | 
						|
		    startPt = wxPoint(pos.x + obj.getStartPt().x,
 | 
						|
				      pos.y + obj.getStartPt().y)
 | 
						|
		    endPt   = wxPoint(pos.x + obj.getEndPt().x,
 | 
						|
				      pos.y + obj.getEndPt().y)
 | 
						|
		    if handle == handle_START_POINT:
 | 
						|
			self.resizeAnchor  = endPt
 | 
						|
			self.resizeFloater = startPt
 | 
						|
		    else:
 | 
						|
			self.resizeAnchor  = startPt
 | 
						|
			self.resizeFloater = endPt
 | 
						|
		else:
 | 
						|
		    self.resizeFeedback = feedback_RECT
 | 
						|
		    pos  = obj.getPosition()
 | 
						|
		    size = obj.getSize()
 | 
						|
		    topLeft  = wxPoint(pos.x, pos.y)
 | 
						|
		    topRight = wxPoint(pos.x + size.width, pos.y)
 | 
						|
		    botLeft  = wxPoint(pos.x, pos.y + size.height)
 | 
						|
		    botRight = wxPoint(pos.x + size.width, pos.y + size.height)
 | 
						|
 | 
						|
		    if handle == handle_TOP_LEFT:
 | 
						|
			self.resizeAnchor  = botRight
 | 
						|
			self.resizeFloater = topLeft
 | 
						|
		    elif handle == handle_TOP_RIGHT:
 | 
						|
			self.resizeAnchor  = botLeft
 | 
						|
			self.resizeFloater = topRight
 | 
						|
		    elif handle == handle_BOTTOM_LEFT:
 | 
						|
			self.resizeAnchor  = topRight
 | 
						|
			self.resizeFloater = botLeft
 | 
						|
		    elif handle == handle_BOTTOM_RIGHT:
 | 
						|
			self.resizeAnchor  = topLeft
 | 
						|
			self.resizeFloater = botRight
 | 
						|
 | 
						|
		self.curPt = mousePt
 | 
						|
		self.resizeOffsetX = self.resizeFloater.x - mousePt.x
 | 
						|
		self.resizeOffsetY = self.resizeFloater.y - mousePt.y
 | 
						|
		endPt = wxPoint(self.curPt.x + self.resizeOffsetX,
 | 
						|
				self.curPt.y + self.resizeOffsetY)
 | 
						|
		self._drawVisualFeedback(self.resizeAnchor, endPt,
 | 
						|
					 self.resizeFeedback, false)
 | 
						|
 | 
						|
	    elif selecting and (self._getObjectAt(mousePt) != None):
 | 
						|
 | 
						|
		# The user clicked on an object to select it.  If the user
 | 
						|
		# drags, he/she will move the object.
 | 
						|
 | 
						|
		self.select(self._getObjectAt(mousePt))
 | 
						|
		self.dragMode = drag_MOVE
 | 
						|
		self.moveOrigin = mousePt
 | 
						|
		self.curPt      = mousePt
 | 
						|
		self._drawObjectOutline(0, 0)
 | 
						|
 | 
						|
	    else:
 | 
						|
 | 
						|
		# The user is dragging out a selection rect or new object.
 | 
						|
 | 
						|
		self.dragOrigin = mousePt
 | 
						|
		self.curPt      = mousePt
 | 
						|
		self.drawPanel.SetCursor(wxCROSS_CURSOR)
 | 
						|
	    	self.drawPanel.CaptureMouse()
 | 
						|
		self._drawVisualFeedback(mousePt, mousePt, feedbackType,
 | 
						|
					 dashedLine)
 | 
						|
		self.dragMode = drag_DRAG
 | 
						|
 | 
						|
	    event.Skip()
 | 
						|
	    return
 | 
						|
 | 
						|
	if event.Dragging():
 | 
						|
	    if self.dragMode == drag_RESIZE:
 | 
						|
 | 
						|
		# We're resizing an object.
 | 
						|
 | 
						|
		mousePt = self._getEventCoordinates(event)
 | 
						|
		if (self.curPt.x != mousePt.x) or (self.curPt.y != mousePt.y):
 | 
						|
		    # Erase previous visual feedback.
 | 
						|
		    endPt = wxPoint(self.curPt.x + self.resizeOffsetX,
 | 
						|
				    self.curPt.y + self.resizeOffsetY)
 | 
						|
		    self._drawVisualFeedback(self.resizeAnchor, endPt,
 | 
						|
					     self.resizeFeedback, false)
 | 
						|
		    self.curPt = mousePt
 | 
						|
		    # Draw new visual feedback.
 | 
						|
		    endPt = wxPoint(self.curPt.x + self.resizeOffsetX,
 | 
						|
				    self.curPt.y + self.resizeOffsetY)
 | 
						|
		    self._drawVisualFeedback(self.resizeAnchor, endPt,
 | 
						|
					     self.resizeFeedback, false)
 | 
						|
 | 
						|
	    elif self.dragMode == drag_MOVE:
 | 
						|
 | 
						|
		# We're moving a selected object.
 | 
						|
 | 
						|
		mousePt = self._getEventCoordinates(event)
 | 
						|
		if (self.curPt.x != mousePt.x) or (self.curPt.y != mousePt.y):
 | 
						|
		    # Erase previous visual feedback.
 | 
						|
		    self._drawObjectOutline(self.curPt.x - self.moveOrigin.x,
 | 
						|
					    self.curPt.y - self.moveOrigin.y)
 | 
						|
		    self.curPt = mousePt
 | 
						|
		    # Draw new visual feedback.
 | 
						|
		    self._drawObjectOutline(self.curPt.x - self.moveOrigin.x,
 | 
						|
					    self.curPt.y - self.moveOrigin.y)
 | 
						|
 | 
						|
	    elif self.dragMode == drag_DRAG:
 | 
						|
 | 
						|
		# We're dragging out a new object or selection rect.
 | 
						|
 | 
						|
		mousePt = self._getEventCoordinates(event)
 | 
						|
		if (self.curPt.x != mousePt.x) or (self.curPt.y != mousePt.y):
 | 
						|
		    # Erase previous visual feedback.
 | 
						|
		    self._drawVisualFeedback(self.dragOrigin, self.curPt,
 | 
						|
					     feedbackType, dashedLine)
 | 
						|
		    self.curPt = mousePt
 | 
						|
		    # Draw new visual feedback.
 | 
						|
		    self._drawVisualFeedback(self.dragOrigin, self.curPt,
 | 
						|
					     feedbackType, dashedLine)
 | 
						|
 | 
						|
	    event.Skip()
 | 
						|
	    return
 | 
						|
 | 
						|
	if event.LeftUp():
 | 
						|
	    if self.dragMode == drag_RESIZE:
 | 
						|
 | 
						|
		# We're resizing an object.
 | 
						|
 | 
						|
		mousePt = self._getEventCoordinates(event)
 | 
						|
		# Erase last visual feedback.
 | 
						|
		endPt = wxPoint(self.curPt.x + self.resizeOffsetX,
 | 
						|
				self.curPt.y + self.resizeOffsetY)
 | 
						|
		self._drawVisualFeedback(self.resizeAnchor, endPt,
 | 
						|
					 self.resizeFeedback, false)
 | 
						|
 | 
						|
		resizePt = wxPoint(mousePt.x + self.resizeOffsetX,
 | 
						|
				   mousePt.y + self.resizeOffsetY)
 | 
						|
 | 
						|
		if (self.resizeFloater.x != resizePt.x) or \
 | 
						|
		   (self.resizeFloater.y != resizePt.y):
 | 
						|
		   self._resizeObject(self.resizeObject,
 | 
						|
				      self.resizeAnchor,
 | 
						|
				      self.resizeFloater,
 | 
						|
				      resizePt)
 | 
						|
		else:
 | 
						|
		    self.drawPanel.Refresh() # Clean up after empty resize.
 | 
						|
 | 
						|
	    elif self.dragMode == drag_MOVE:
 | 
						|
 | 
						|
		# We're moving a selected object.
 | 
						|
 | 
						|
		mousePt = self._getEventCoordinates(event)
 | 
						|
		# Erase last visual feedback.
 | 
						|
		self._drawObjectOutline(self.curPt.x - self.moveOrigin.x,
 | 
						|
					self.curPt.y - self.moveOrigin.y)
 | 
						|
		if (self.moveOrigin.x != mousePt.x) or \
 | 
						|
		   (self.moveOrigin.y != mousePt.y):
 | 
						|
		    self._moveObject(mousePt.x - self.moveOrigin.x,
 | 
						|
				     mousePt.y - self.moveOrigin.y)
 | 
						|
		else:
 | 
						|
		    self.drawPanel.Refresh() # Clean up after empty drag.
 | 
						|
 | 
						|
	    elif self.dragMode == drag_DRAG:
 | 
						|
 | 
						|
		# We're dragging out a new object or selection rect.
 | 
						|
 | 
						|
		mousePt = self._getEventCoordinates(event)
 | 
						|
		# Erase last visual feedback.
 | 
						|
		self._drawVisualFeedback(self.dragOrigin, self.curPt,
 | 
						|
					 feedbackType, dashedLine)
 | 
						|
		self.drawPanel.ReleaseMouse()
 | 
						|
		self.drawPanel.SetCursor(wxSTANDARD_CURSOR)
 | 
						|
		# Perform the appropriate action for the current tool.
 | 
						|
		if actionParam == param_RECT:
 | 
						|
		    x1 = min(self.dragOrigin.x, self.curPt.x)
 | 
						|
		    y1 = min(self.dragOrigin.y, self.curPt.y)
 | 
						|
		    x2 = max(self.dragOrigin.x, self.curPt.x)
 | 
						|
		    y2 = max(self.dragOrigin.y, self.curPt.y)
 | 
						|
 | 
						|
		    startX = x1
 | 
						|
		    startY = y1
 | 
						|
		    width  = x2 - x1
 | 
						|
		    height = y2 - y1
 | 
						|
 | 
						|
		    if not selecting:
 | 
						|
			if ((x2-x1) < 8) or ((y2-y1) < 8): return # Too small.
 | 
						|
 | 
						|
		    action(x1, y1, x2-x1, y2-y1)
 | 
						|
		elif actionParam == param_LINE:
 | 
						|
		    action(self.dragOrigin.x, self.dragOrigin.y,
 | 
						|
			   self.curPt.x, self.curPt.y)
 | 
						|
 | 
						|
	    self.dragMode = drag_NONE # We've finished with this mouse event.
 | 
						|
	    event.Skip()
 | 
						|
 | 
						|
 | 
						|
    def onDoubleClickEvent(self, event):
 | 
						|
	""" Respond to a double-click within our drawing panel.
 | 
						|
	"""
 | 
						|
	mousePt = self._getEventCoordinates(event)
 | 
						|
	obj = self._getObjectAt(mousePt)
 | 
						|
	if obj == None: return
 | 
						|
 | 
						|
	# Let the user edit the given object.
 | 
						|
 | 
						|
	if obj.getType() == obj_TEXT:
 | 
						|
	    editor = EditTextObjectDialog(self, "Edit Text Object")
 | 
						|
	    editor.objectToDialog(obj)
 | 
						|
	    if editor.ShowModal() == wxID_CANCEL:
 | 
						|
		editor.Destroy()
 | 
						|
		return
 | 
						|
 | 
						|
	    self._saveUndoInfo()
 | 
						|
 | 
						|
	    editor.dialogToObject(obj)
 | 
						|
	    editor.Destroy()
 | 
						|
 | 
						|
	    self.dirty = true
 | 
						|
	    self.drawPanel.Refresh()
 | 
						|
	    self._adjustMenus()
 | 
						|
	else:
 | 
						|
	    wxBell()
 | 
						|
 | 
						|
 | 
						|
    def onRightClick(self, event):
 | 
						|
	""" Respond to the user right-clicking within our drawing panel.
 | 
						|
 | 
						|
	    We select the clicked-on item, if necessary, and display a pop-up
 | 
						|
	    menu of available options which can be applied to the selected
 | 
						|
	    item(s).
 | 
						|
	"""
 | 
						|
	mousePt = self._getEventCoordinates(event)
 | 
						|
	obj = self._getObjectAt(mousePt)
 | 
						|
 | 
						|
	if obj == None: return # Nothing selected.
 | 
						|
 | 
						|
	# Select the clicked-on object.
 | 
						|
 | 
						|
	self.select(obj)
 | 
						|
 | 
						|
	# Build our pop-up menu.
 | 
						|
 | 
						|
	menu = wxMenu()
 | 
						|
	menu.Append(menu_DUPLICATE, "Duplicate")
 | 
						|
	menu.Append(menu_EDIT_TEXT, "Edit...")
 | 
						|
	menu.Append(menu_DELETE,    "Delete")
 | 
						|
	menu.AppendSeparator()
 | 
						|
	menu.Append(menu_MOVE_FORWARD,   "Move Forward")
 | 
						|
	menu.Append(menu_MOVE_TO_FRONT,  "Move to Front")
 | 
						|
	menu.Append(menu_MOVE_BACKWARD,  "Move Backward")
 | 
						|
	menu.Append(menu_MOVE_TO_BACK,   "Move to Back")
 | 
						|
 | 
						|
	menu.Enable(menu_EDIT_TEXT,     obj.getType() == obj_TEXT)
 | 
						|
	menu.Enable(menu_MOVE_FORWARD,  obj != self.contents[0])
 | 
						|
	menu.Enable(menu_MOVE_TO_FRONT, obj != self.contents[0])
 | 
						|
	menu.Enable(menu_MOVE_BACKWARD, obj != self.contents[-1])
 | 
						|
	menu.Enable(menu_MOVE_TO_BACK,  obj != self.contents[-1])
 | 
						|
 | 
						|
	EVT_MENU(self, menu_DUPLICATE,     self.doDuplicate)
 | 
						|
	EVT_MENU(self, menu_EDIT_TEXT,     self.doEditText)
 | 
						|
	EVT_MENU(self, menu_DELETE,        self.doDelete)
 | 
						|
	EVT_MENU(self, menu_MOVE_FORWARD,  self.doMoveForward)
 | 
						|
	EVT_MENU(self, menu_MOVE_TO_FRONT, self.doMoveToFront)
 | 
						|
	EVT_MENU(self, menu_MOVE_BACKWARD, self.doMoveBackward)
 | 
						|
	EVT_MENU(self, menu_MOVE_TO_BACK,  self.doMoveToBack)
 | 
						|
 | 
						|
	# Show the pop-up menu.
 | 
						|
 | 
						|
	clickPt = wxPoint(mousePt.x + self.drawPanel.GetPosition().x,
 | 
						|
			  mousePt.y + self.drawPanel.GetPosition().y)
 | 
						|
	self.drawPanel.PopupMenu(menu, clickPt)
 | 
						|
	menu.Destroy()
 | 
						|
 | 
						|
 | 
						|
    def onPaintEvent(self, event):
 | 
						|
	""" Respond to a request to redraw the contents of our drawing panel.
 | 
						|
	"""
 | 
						|
	dc = wxPaintDC(self.drawPanel)
 | 
						|
	self.drawPanel.PrepareDC(dc)
 | 
						|
	dc.BeginDrawing()
 | 
						|
 | 
						|
	for i in range(len(self.contents)-1, -1, -1):
 | 
						|
	    obj = self.contents[i]
 | 
						|
	    if obj in self.selection:
 | 
						|
		obj.draw(dc, true)
 | 
						|
	    else:
 | 
						|
		obj.draw(dc, false)
 | 
						|
 | 
						|
	dc.EndDrawing()
 | 
						|
 | 
						|
    # ==========================
 | 
						|
    # == Menu Command Methods ==
 | 
						|
    # ==========================
 | 
						|
 | 
						|
    def doNew(self, event):
 | 
						|
	""" Respond to the "New" menu command.
 | 
						|
	"""
 | 
						|
	global _docList
 | 
						|
	newFrame = DrawingFrame(None, -1, "Untitled")
 | 
						|
	newFrame.Show(TRUE)
 | 
						|
	_docList.append(newFrame)
 | 
						|
 | 
						|
 | 
						|
    def doOpen(self, event):
 | 
						|
	""" Respond to the "Open" menu command.
 | 
						|
	"""
 | 
						|
	global _docList
 | 
						|
 | 
						|
	curDir = os.getcwd()
 | 
						|
	fileName = wxFileSelector("Open File", default_extension="psk",
 | 
						|
				  flags = wxOPEN | wxFILE_MUST_EXIST)
 | 
						|
	if fileName == "": return
 | 
						|
	fileName = os.path.join(os.getcwd(), fileName)
 | 
						|
	os.chdir(curDir)
 | 
						|
 | 
						|
	title = os.path.basename(fileName)
 | 
						|
 | 
						|
	if (self.fileName == None) and (len(self.contents) == 0):
 | 
						|
	    # Load contents into current (empty) document.
 | 
						|
	    self.fileName = fileName
 | 
						|
	    self.SetTitle(os.path.basename(fileName))
 | 
						|
	    self.loadContents()
 | 
						|
	else:
 | 
						|
	    # Open a new frame for this document.
 | 
						|
	    newFrame = DrawingFrame(None, -1, os.path.basename(fileName),
 | 
						|
				    fileName=fileName)
 | 
						|
	    newFrame.Show(true)
 | 
						|
	    _docList.append(newFrame)
 | 
						|
 | 
						|
 | 
						|
    def doClose(self, event):
 | 
						|
	""" Respond to the "Close" menu command.
 | 
						|
	"""
 | 
						|
	global _docList
 | 
						|
 | 
						|
	if self.dirty:
 | 
						|
	    if not self.askIfUserWantsToSave("closing"): return
 | 
						|
 | 
						|
	_docList.remove(self)
 | 
						|
	self.Destroy()
 | 
						|
 | 
						|
 | 
						|
    def doSave(self, event):
 | 
						|
	""" Respond to the "Save" menu command.
 | 
						|
	"""
 | 
						|
	if self.fileName != None:
 | 
						|
	    self.saveContents()
 | 
						|
 | 
						|
 | 
						|
    def doSaveAs(self, event):
 | 
						|
	""" Respond to the "Save As" menu command.
 | 
						|
	"""
 | 
						|
	if self.fileName == None:
 | 
						|
	    default = ""
 | 
						|
	else:
 | 
						|
	    default = self.fileName
 | 
						|
 | 
						|
	curDir = os.getcwd()
 | 
						|
	fileName = wxFileSelector("Save File As", "Saving",
 | 
						|
				  default_filename=default,
 | 
						|
				  default_extension="psk",
 | 
						|
				  wildcard="*.psk",
 | 
						|
				  flags = wxSAVE | wxOVERWRITE_PROMPT)
 | 
						|
	if fileName == "": return # User cancelled.
 | 
						|
	fileName = os.path.join(os.getcwd(), fileName)
 | 
						|
	os.chdir(curDir)
 | 
						|
 | 
						|
	title = os.path.basename(fileName)
 | 
						|
	self.SetTitle(title)
 | 
						|
 | 
						|
	self.fileName = fileName
 | 
						|
	self.saveContents()
 | 
						|
 | 
						|
 | 
						|
    def doRevert(self, event):
 | 
						|
	""" Respond to the "Revert" menu command.
 | 
						|
	"""
 | 
						|
	if not self.dirty: return
 | 
						|
 | 
						|
	if wxMessageBox("Discard changes made to this document?", "Confirm",
 | 
						|
			style = wxOK | wxCANCEL | wxICON_QUESTION,
 | 
						|
			parent=self) == wxCANCEL: return
 | 
						|
	self.loadContents()
 | 
						|
 | 
						|
 | 
						|
    def doExit(self, event):
 | 
						|
	""" Respond to the "Quit" menu command.
 | 
						|
	"""
 | 
						|
	global _docList, _app
 | 
						|
	for doc in _docList:
 | 
						|
	    if not doc.dirty: continue
 | 
						|
	    doc.Raise()
 | 
						|
	    if not doc.askIfUserWantsToSave("quitting"): return
 | 
						|
	    _docList.remove(doc)
 | 
						|
	    doc.Destroy()
 | 
						|
 | 
						|
	_app.ExitMainLoop()
 | 
						|
 | 
						|
 | 
						|
    def doUndo(self, event):
 | 
						|
	""" Respond to the "Undo" menu command.
 | 
						|
	"""
 | 
						|
	if self.undoInfo == None: return
 | 
						|
 | 
						|
	undoData = self.undoInfo
 | 
						|
	self._saveUndoInfo() # For undoing the undo...
 | 
						|
 | 
						|
	self.contents = []
 | 
						|
 | 
						|
	for type, data in undoData["contents"]:
 | 
						|
	    obj = DrawingObject(type)
 | 
						|
	    obj.setData(data)
 | 
						|
	    self.contents.append(obj)
 | 
						|
 | 
						|
	self.selection = []
 | 
						|
	for i in undoData["selection"]:
 | 
						|
	    self.selection.append(self.contents[i])
 | 
						|
 | 
						|
	self.dirty = true
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doSelectAll(self, event):
 | 
						|
	""" Respond to the "Select All" menu command.
 | 
						|
	"""
 | 
						|
	self.selectAll()
 | 
						|
 | 
						|
 | 
						|
    def doDuplicate(self, event):
 | 
						|
	""" Respond to the "Duplicate" menu command.
 | 
						|
	"""
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	objs = []
 | 
						|
	for obj in self.contents:
 | 
						|
	    if obj in self.selection:
 | 
						|
		newObj = DrawingObject(obj.getType())
 | 
						|
		newObj.setData(obj.getData())
 | 
						|
		pos = obj.getPosition()
 | 
						|
		newObj.setPosition(wxPoint(pos.x + 10, pos.y + 10))
 | 
						|
		objs.append(newObj)
 | 
						|
 | 
						|
	self.contents = objs + self.contents
 | 
						|
 | 
						|
	self.selectMany(objs)
 | 
						|
 | 
						|
 | 
						|
    def doEditText(self, event):
 | 
						|
	""" Respond to the "Edit Text" menu command.
 | 
						|
	"""
 | 
						|
	if len(self.selection) != 1: return
 | 
						|
 | 
						|
	obj = self.selection[0]
 | 
						|
	if obj.getType() != obj_TEXT: return
 | 
						|
 | 
						|
	editor = EditTextObjectDialog(self, "Edit Text Object")
 | 
						|
	editor.objectToDialog(obj)
 | 
						|
	if editor.ShowModal() == wxID_CANCEL:
 | 
						|
	    editor.Destroy()
 | 
						|
	    return
 | 
						|
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	editor.dialogToObject(obj)
 | 
						|
	editor.Destroy()
 | 
						|
 | 
						|
	self.dirty = true
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doDelete(self, event):
 | 
						|
	""" Respond to the "Delete" menu command.
 | 
						|
	"""
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	for obj in self.selection:
 | 
						|
	    self.contents.remove(obj)
 | 
						|
	    del obj
 | 
						|
	self.deselectAll()
 | 
						|
 | 
						|
 | 
						|
    def doChooseSelectTool(self, event=None):
 | 
						|
	""" Respond to the "Select Tool" menu command.
 | 
						|
	"""
 | 
						|
	self._setCurrentTool(self.selectIcon)
 | 
						|
	self.drawPanel.SetCursor(wxSTANDARD_CURSOR)
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doChooseLineTool(self, event=None):
 | 
						|
	""" Respond to the "Line Tool" menu command.
 | 
						|
	"""
 | 
						|
	self._setCurrentTool(self.lineIcon)
 | 
						|
	self.drawPanel.SetCursor(wxCROSS_CURSOR)
 | 
						|
	self.deselectAll()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doChooseRectTool(self, event=None):
 | 
						|
	""" Respond to the "Rect Tool" menu command.
 | 
						|
	"""
 | 
						|
	self._setCurrentTool(self.rectIcon)
 | 
						|
	self.drawPanel.SetCursor(wxCROSS_CURSOR)
 | 
						|
	self.deselectAll()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doChooseEllipseTool(self, event=None):
 | 
						|
	""" Respond to the "Ellipse Tool" menu command.
 | 
						|
	"""
 | 
						|
	self._setCurrentTool(self.ellipseIcon)
 | 
						|
	self.drawPanel.SetCursor(wxCROSS_CURSOR)
 | 
						|
	self.deselectAll()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doChooseTextTool(self, event=None):
 | 
						|
	""" Respond to the "Text Tool" menu command.
 | 
						|
	"""
 | 
						|
	self._setCurrentTool(self.textIcon)
 | 
						|
	self.drawPanel.SetCursor(wxCROSS_CURSOR)
 | 
						|
	self.deselectAll()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doMoveForward(self, event):
 | 
						|
	""" Respond to the "Move Forward" menu command.
 | 
						|
	"""
 | 
						|
	if len(self.selection) != 1: return
 | 
						|
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	obj = self.selection[0]
 | 
						|
	index = self.contents.index(obj)
 | 
						|
	if index == 0: return
 | 
						|
 | 
						|
	del self.contents[index]
 | 
						|
	self.contents.insert(index-1, obj)
 | 
						|
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doMoveToFront(self, event):
 | 
						|
	""" Respond to the "Move to Front" menu command.
 | 
						|
	"""
 | 
						|
	if len(self.selection) != 1: return
 | 
						|
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	obj = self.selection[0]
 | 
						|
	self.contents.remove(obj)
 | 
						|
	self.contents.insert(0, obj)
 | 
						|
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doMoveBackward(self, event):
 | 
						|
	""" Respond to the "Move Backward" menu command.
 | 
						|
	"""
 | 
						|
	if len(self.selection) != 1: return
 | 
						|
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	obj = self.selection[0]
 | 
						|
	index = self.contents.index(obj)
 | 
						|
	if index == len(self.contents) - 1: return
 | 
						|
 | 
						|
	del self.contents[index]
 | 
						|
	self.contents.insert(index+1, obj)
 | 
						|
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doMoveToBack(self, event):
 | 
						|
	""" Respond to the "Move to Back" menu command.
 | 
						|
	"""
 | 
						|
	if len(self.selection) != 1: return
 | 
						|
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	obj = self.selection[0]
 | 
						|
	self.contents.remove(obj)
 | 
						|
	self.contents.append(obj)
 | 
						|
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def doShowAbout(self, event):
 | 
						|
	""" Respond to the "About pySketch" menu command.
 | 
						|
	"""
 | 
						|
	dialog = wxDialog(self, -1, "About pySketch") # ,
 | 
						|
			  #style=wxDIALOG_MODAL | wxSTAY_ON_TOP)
 | 
						|
	dialog.SetBackgroundColour(wxWHITE)
 | 
						|
 | 
						|
	panel = wxPanel(dialog, -1)
 | 
						|
	panel.SetBackgroundColour(wxWHITE)
 | 
						|
 | 
						|
	panelSizer = wxBoxSizer(wxVERTICAL)
 | 
						|
 | 
						|
	boldFont = wxFont(panel.GetFont().GetPointSize(),
 | 
						|
			  panel.GetFont().GetFamily(),
 | 
						|
			  wxNORMAL, wxBOLD)
 | 
						|
 | 
						|
	logo = wxStaticBitmap(panel, -1, wxBitmap("images/logo.bmp",
 | 
						|
						  wxBITMAP_TYPE_BMP))
 | 
						|
 | 
						|
	lab1 = wxStaticText(panel, -1, "pySketch")
 | 
						|
	lab1.SetFont(wxFont(36, boldFont.GetFamily(), wxITALIC, wxBOLD))
 | 
						|
	lab1.SetSize(lab1.GetBestSize())
 | 
						|
 | 
						|
	imageSizer = wxBoxSizer(wxHORIZONTAL)
 | 
						|
	imageSizer.Add(logo, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5)
 | 
						|
	imageSizer.Add(lab1, 0, wxALL | wxALIGN_CENTRE_VERTICAL, 5)
 | 
						|
 | 
						|
	lab2 = wxStaticText(panel, -1, "A simple object-oriented drawing " + \
 | 
						|
				       "program.")
 | 
						|
	lab2.SetFont(boldFont)
 | 
						|
	lab2.SetSize(lab2.GetBestSize())
 | 
						|
 | 
						|
	lab3 = wxStaticText(panel, -1, "pySketch is completely free " + \
 | 
						|
				       "software; please")
 | 
						|
	lab3.SetFont(boldFont)
 | 
						|
	lab3.SetSize(lab3.GetBestSize())
 | 
						|
 | 
						|
	lab4 = wxStaticText(panel, -1, "feel free to adapt or use this " + \
 | 
						|
				       "in any way you like.")
 | 
						|
	lab4.SetFont(boldFont)
 | 
						|
	lab4.SetSize(lab4.GetBestSize())
 | 
						|
 | 
						|
	lab5 = wxStaticText(panel, -1, "Author: Erik Westra " + \
 | 
						|
				       "(ewestra@wave.co.nz)")
 | 
						|
	lab5.SetFont(boldFont)
 | 
						|
	lab5.SetSize(lab5.GetBestSize())
 | 
						|
 | 
						|
	btnOK = wxButton(panel, wxID_OK, "OK")
 | 
						|
 | 
						|
	panelSizer.Add(imageSizer, 0, wxALIGN_CENTRE)
 | 
						|
	panelSizer.Add(10, 10) # Spacer.
 | 
						|
	panelSizer.Add(lab2, 0, wxALIGN_CENTRE)
 | 
						|
	panelSizer.Add(10, 10) # Spacer.
 | 
						|
	panelSizer.Add(lab3, 0, wxALIGN_CENTRE)
 | 
						|
	panelSizer.Add(lab4, 0, wxALIGN_CENTRE)
 | 
						|
	panelSizer.Add(10, 10) # Spacer.
 | 
						|
	panelSizer.Add(lab5, 0, wxALIGN_CENTRE)
 | 
						|
	panelSizer.Add(10, 10) # Spacer.
 | 
						|
	panelSizer.Add(btnOK, 0, wxALL | wxALIGN_CENTRE, 5)
 | 
						|
 | 
						|
	panel.SetAutoLayout(true)
 | 
						|
	panel.SetSizer(panelSizer)
 | 
						|
	panelSizer.Fit(panel)
 | 
						|
 | 
						|
	topSizer = wxBoxSizer(wxHORIZONTAL)
 | 
						|
	topSizer.Add(panel, 0, wxALL, 10)
 | 
						|
 | 
						|
	dialog.SetAutoLayout(true)
 | 
						|
	dialog.SetSizer(topSizer)
 | 
						|
	topSizer.Fit(dialog)
 | 
						|
 | 
						|
	dialog.Centre()
 | 
						|
 | 
						|
	btn = dialog.ShowModal()
 | 
						|
	dialog.Destroy()
 | 
						|
 | 
						|
    # =============================
 | 
						|
    # == Object Creation Methods ==
 | 
						|
    # =============================
 | 
						|
 | 
						|
    def createLine(self, x1, y1, x2, y2):
 | 
						|
	""" Create a new line object at the given position and size.
 | 
						|
	"""
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	topLeftX  = min(x1, x2)
 | 
						|
	topLeftY  = min(y1, y2)
 | 
						|
	botRightX = max(x1, x2)
 | 
						|
	botRightY = max(y1, y2)
 | 
						|
 | 
						|
	obj = DrawingObject(obj_LINE, position=wxPoint(topLeftX, topLeftY),
 | 
						|
			    size=wxSize(botRightX-topLeftX,
 | 
						|
					botRightY-topLeftY),
 | 
						|
			    penColour=self.penColour,
 | 
						|
			    fillColour=self.fillColour,
 | 
						|
			    lineSize=self.lineSize,
 | 
						|
			    startPt = wxPoint(x1 - topLeftX, y1 - topLeftY),
 | 
						|
			    endPt   = wxPoint(x2 - topLeftX, y2 - topLeftY))
 | 
						|
	self.contents.insert(0, obj)
 | 
						|
	self.dirty = true
 | 
						|
	self.doChooseSelectTool()
 | 
						|
	self.select(obj)
 | 
						|
 | 
						|
 | 
						|
    def createRect(self, x, y, width, height):
 | 
						|
	""" Create a new rectangle object at the given position and size.
 | 
						|
	"""
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	obj = DrawingObject(obj_RECT, position=wxPoint(x, y),
 | 
						|
			    size=wxSize(width, height),
 | 
						|
			    penColour=self.penColour,
 | 
						|
			    fillColour=self.fillColour,
 | 
						|
			    lineSize=self.lineSize)
 | 
						|
	self.contents.insert(0, obj)
 | 
						|
	self.dirty = true
 | 
						|
	self.doChooseSelectTool()
 | 
						|
	self.select(obj)
 | 
						|
 | 
						|
 | 
						|
    def createEllipse(self, x, y, width, height):
 | 
						|
	""" Create a new ellipse object at the given position and size.
 | 
						|
	"""
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	obj = DrawingObject(obj_ELLIPSE, position=wxPoint(x, y),
 | 
						|
			    size=wxSize(width, height),
 | 
						|
			    penColour=self.penColour,
 | 
						|
			    fillColour=self.fillColour,
 | 
						|
			    lineSize=self.lineSize)
 | 
						|
	self.contents.insert(0, obj)
 | 
						|
	self.dirty = true
 | 
						|
	self.doChooseSelectTool()
 | 
						|
	self.select(obj)
 | 
						|
 | 
						|
 | 
						|
    def createText(self, x, y, width, height):
 | 
						|
	""" Create a new text object at the given position and size.
 | 
						|
	"""
 | 
						|
	editor = EditTextObjectDialog(self, "Create Text Object")
 | 
						|
	if editor.ShowModal() == wxID_CANCEL:
 | 
						|
	    editor.Destroy()
 | 
						|
	    return
 | 
						|
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	obj = DrawingObject(obj_TEXT, position=wxPoint(x, y),
 | 
						|
				      size=wxSize(width, height))
 | 
						|
	editor.dialogToObject(obj)
 | 
						|
	editor.Destroy()
 | 
						|
 | 
						|
	self.contents.insert(0, obj)
 | 
						|
	self.dirty = true
 | 
						|
	self.doChooseSelectTool()
 | 
						|
	self.select(obj)
 | 
						|
 | 
						|
    # =======================
 | 
						|
    # == Selection Methods ==
 | 
						|
    # =======================
 | 
						|
 | 
						|
    def selectAll(self):
 | 
						|
	""" Select every DrawingObject in our document.
 | 
						|
	"""
 | 
						|
	self.selection = []
 | 
						|
	for obj in self.contents:
 | 
						|
	    self.selection.append(obj)
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def deselectAll(self):
 | 
						|
	""" Deselect every DrawingObject in our document.
 | 
						|
	"""
 | 
						|
	self.selection = []
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def select(self, obj):
 | 
						|
	""" Select the given DrawingObject within our document.
 | 
						|
	"""
 | 
						|
	self.selection = [obj]
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def selectMany(self, objs):
 | 
						|
	""" Select the given list of DrawingObjects.
 | 
						|
	"""
 | 
						|
	self.selection = objs
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def selectByRectangle(self, x, y, width, height):
 | 
						|
	""" Select every DrawingObject in the given rectangular region.
 | 
						|
	"""
 | 
						|
	self.selection = []
 | 
						|
	for obj in self.contents:
 | 
						|
	    if obj.objectWithinRect(x, y, width, height):
 | 
						|
		self.selection.append(obj)
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
    # ======================
 | 
						|
    # == File I/O Methods ==
 | 
						|
    # ======================
 | 
						|
 | 
						|
    def loadContents(self):
 | 
						|
	""" Load the contents of our document into memory.
 | 
						|
	"""
 | 
						|
	f = open(self.fileName, "rb")
 | 
						|
	objData = cPickle.load(f)
 | 
						|
	f.close()
 | 
						|
 | 
						|
	for type, data in objData:
 | 
						|
	    obj = DrawingObject(type)
 | 
						|
	    obj.setData(data)
 | 
						|
	    self.contents.append(obj)
 | 
						|
 | 
						|
	self.dirty = false
 | 
						|
	self.selection = []
 | 
						|
	self.undoInfo  = None
 | 
						|
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
	self._adjustMenus()
 | 
						|
 | 
						|
 | 
						|
    def saveContents(self):
 | 
						|
	""" Save the contents of our document to disk.
 | 
						|
	"""
 | 
						|
	objData = []
 | 
						|
	for obj in self.contents:
 | 
						|
	    objData.append([obj.getType(), obj.getData()])
 | 
						|
 | 
						|
	f = open(self.fileName, "wb")
 | 
						|
	cPickle.dump(objData, f)
 | 
						|
	f.close()
 | 
						|
 | 
						|
	self.dirty = false
 | 
						|
 | 
						|
 | 
						|
    def askIfUserWantsToSave(self, action):
 | 
						|
	""" Give the user the opportunity to save the current document.
 | 
						|
 | 
						|
	    'action' is a string describing the action about to be taken.  If
 | 
						|
	    the user wants to save the document, it is saved immediately.  If
 | 
						|
	    the user cancels, we return false.
 | 
						|
	"""
 | 
						|
	if not self.dirty: return true # Nothing to do.
 | 
						|
 | 
						|
	response = wxMessageBox("Save changes before " + action + "?",
 | 
						|
				"Confirm", wxYES_NO | wxCANCEL, self)
 | 
						|
 | 
						|
	if response == wxYES:
 | 
						|
	    if self.fileName == None:
 | 
						|
		fileName = wxFileSelector("Save File As", "Saving",
 | 
						|
					  default_extension="psk",
 | 
						|
					  wildcard="*.psk",
 | 
						|
					  flags = wxSAVE | wxOVERWRITE_PROMPT)
 | 
						|
		if fileName == "": return false # User cancelled.
 | 
						|
		self.fileName = fileName
 | 
						|
 | 
						|
	    self.saveContents()
 | 
						|
	    return true
 | 
						|
	elif response == wxNO:
 | 
						|
	    return true # User doesn't want changes saved.
 | 
						|
	elif response == wxCANCEL:
 | 
						|
	    return false # User cancelled.
 | 
						|
 | 
						|
    # =====================
 | 
						|
    # == Private Methods ==
 | 
						|
    # =====================
 | 
						|
 | 
						|
    def _adjustMenus(self):
 | 
						|
	""" Adjust our menus and toolbar to reflect the current state of the
 | 
						|
	    world.
 | 
						|
	"""
 | 
						|
	canSave   = (self.fileName != None) and self.dirty
 | 
						|
	canRevert = (self.fileName != None) and self.dirty
 | 
						|
	canUndo   = self.undoInfo != None
 | 
						|
	selection = len(self.selection) > 0
 | 
						|
	onlyOne   = len(self.selection) == 1
 | 
						|
	isText    = onlyOne and (self.selection[0].getType() == obj_TEXT)
 | 
						|
	front     = onlyOne and (self.selection[0] == self.contents[0])
 | 
						|
	back      = onlyOne and (self.selection[0] == self.contents[-1])
 | 
						|
 | 
						|
	# Enable/disable our menu items.
 | 
						|
 | 
						|
	self.fileMenu.Enable(wxID_SAVE,   canSave)
 | 
						|
	self.fileMenu.Enable(wxID_REVERT, canRevert)
 | 
						|
 | 
						|
	self.editMenu.Enable(menu_UNDO,      canUndo)
 | 
						|
	self.editMenu.Enable(menu_DUPLICATE, selection)
 | 
						|
	self.editMenu.Enable(menu_EDIT_TEXT, isText)
 | 
						|
	self.editMenu.Enable(menu_DELETE,    selection)
 | 
						|
 | 
						|
	self.toolsMenu.Check(menu_SELECT,  self.curTool == self.selectIcon)
 | 
						|
	self.toolsMenu.Check(menu_LINE,    self.curTool == self.lineIcon)
 | 
						|
	self.toolsMenu.Check(menu_RECT,    self.curTool == self.rectIcon)
 | 
						|
	self.toolsMenu.Check(menu_ELLIPSE, self.curTool == self.ellipseIcon)
 | 
						|
	self.toolsMenu.Check(menu_TEXT,    self.curTool == self.textIcon)
 | 
						|
 | 
						|
	self.objectMenu.Enable(menu_MOVE_FORWARD,  onlyOne and not front)
 | 
						|
	self.objectMenu.Enable(menu_MOVE_TO_FRONT, onlyOne and not front)
 | 
						|
	self.objectMenu.Enable(menu_MOVE_BACKWARD, onlyOne and not back)
 | 
						|
	self.objectMenu.Enable(menu_MOVE_TO_BACK,  onlyOne and not back)
 | 
						|
 | 
						|
	# Enable/disable our toolbar icons.
 | 
						|
 | 
						|
	self.toolbar.EnableTool(wxID_NEW,           true)
 | 
						|
	self.toolbar.EnableTool(wxID_OPEN,          true)
 | 
						|
	self.toolbar.EnableTool(wxID_SAVE,          canSave)
 | 
						|
	self.toolbar.EnableTool(menu_UNDO,          canUndo)
 | 
						|
	self.toolbar.EnableTool(menu_DUPLICATE,     selection)
 | 
						|
	self.toolbar.EnableTool(menu_MOVE_FORWARD,  onlyOne and not front)
 | 
						|
	self.toolbar.EnableTool(menu_MOVE_BACKWARD, onlyOne and not back)
 | 
						|
 | 
						|
 | 
						|
    def _setCurrentTool(self, newToolIcon):
 | 
						|
	""" Set the currently selected tool.
 | 
						|
	"""
 | 
						|
	if self.curTool == newToolIcon: return # Nothing to do.
 | 
						|
 | 
						|
	if self.curTool != None:
 | 
						|
	    self.curTool.deselect()
 | 
						|
 | 
						|
	newToolIcon.select()
 | 
						|
	self.curTool = newToolIcon
 | 
						|
 | 
						|
 | 
						|
    def _setPenColour(self, colour):
 | 
						|
	""" Set the default or selected object's pen colour.
 | 
						|
	"""
 | 
						|
	if len(self.selection) > 0:
 | 
						|
	    self._saveUndoInfo()
 | 
						|
	    for obj in self.selection:
 | 
						|
		obj.setPenColour(colour)
 | 
						|
	    self.drawPanel.Refresh()
 | 
						|
	else:
 | 
						|
	    self.penColour = colour
 | 
						|
	    self.optionIndicator.setPenColour(colour)
 | 
						|
 | 
						|
 | 
						|
    def _setFillColour(self, colour):
 | 
						|
	""" Set the default or selected object's fill colour.
 | 
						|
	"""
 | 
						|
	if len(self.selection) > 0:
 | 
						|
	    self._saveUndoInfo()
 | 
						|
	    for obj in self.selection:
 | 
						|
		obj.setFillColour(colour)
 | 
						|
	    self.drawPanel.Refresh()
 | 
						|
	else:
 | 
						|
	    self.fillColour = colour
 | 
						|
	    self.optionIndicator.setFillColour(colour)
 | 
						|
 | 
						|
 | 
						|
    def _setLineSize(self, size):
 | 
						|
	""" Set the default or selected object's line size.
 | 
						|
	"""
 | 
						|
	if len(self.selection) > 0:
 | 
						|
	    self._saveUndoInfo()
 | 
						|
	    for obj in self.selection:
 | 
						|
		obj.setLineSize(size)
 | 
						|
	    self.drawPanel.Refresh()
 | 
						|
	else:
 | 
						|
	    self.lineSize = size
 | 
						|
	    self.optionIndicator.setLineSize(size)
 | 
						|
 | 
						|
 | 
						|
    def _saveUndoInfo(self):
 | 
						|
	""" Remember the current state of the document, to allow for undo.
 | 
						|
 | 
						|
	    We make a copy of the document's contents, so that we can return to
 | 
						|
	    the previous contents if the user does something and then wants to
 | 
						|
	    undo the operation.
 | 
						|
	"""
 | 
						|
	savedContents = []
 | 
						|
	for obj in self.contents:
 | 
						|
	    savedContents.append([obj.getType(), obj.getData()])
 | 
						|
 | 
						|
	savedSelection = []
 | 
						|
	for i in range(len(self.contents)):
 | 
						|
	    if self.contents[i] in self.selection:
 | 
						|
		savedSelection.append(i)
 | 
						|
 | 
						|
	self.undoInfo = {"contents"  : savedContents,
 | 
						|
			 "selection" : savedSelection}
 | 
						|
 | 
						|
 | 
						|
    def _resizeObject(self, obj, anchorPt, oldPt, newPt):
 | 
						|
	""" Resize the given object.
 | 
						|
 | 
						|
	    'anchorPt' is the unchanging corner of the object, while the
 | 
						|
	    opposite corner has been resized.  'oldPt' are the current
 | 
						|
	    coordinates for this corner, while 'newPt' are the new coordinates.
 | 
						|
	    The object should fit within the given dimensions, though if the
 | 
						|
	    new point is less than the anchor point the object will need to be
 | 
						|
	    moved as well as resized, to avoid giving it a negative size.
 | 
						|
	"""
 | 
						|
	if obj.getType() == obj_TEXT:
 | 
						|
	    # Not allowed to resize text objects -- they're sized to fit text.
 | 
						|
	    wxBell()
 | 
						|
	    return
 | 
						|
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	topLeft  = wxPoint(min(anchorPt.x, newPt.x),
 | 
						|
			   min(anchorPt.y, newPt.y))
 | 
						|
	botRight = wxPoint(max(anchorPt.x, newPt.x),
 | 
						|
			   max(anchorPt.y, newPt.y))
 | 
						|
 | 
						|
	newWidth  = botRight.x - topLeft.x
 | 
						|
	newHeight = botRight.y - topLeft.y
 | 
						|
 | 
						|
	if obj.getType() == obj_LINE:
 | 
						|
	    # Adjust the line so that its start and end points match the new
 | 
						|
	    # overall object size.
 | 
						|
 | 
						|
	    startPt = obj.getStartPt()
 | 
						|
	    endPt   = obj.getEndPt()
 | 
						|
 | 
						|
	    slopesDown = ((startPt.x < endPt.x) and (startPt.y < endPt.y)) or \
 | 
						|
			 ((startPt.x > endPt.x) and (startPt.y > endPt.y))
 | 
						|
 | 
						|
	    # Handle the user flipping the line.
 | 
						|
 | 
						|
	    hFlip = ((anchorPt.x < oldPt.x) and (anchorPt.x > newPt.x)) or \
 | 
						|
		    ((anchorPt.x > oldPt.x) and (anchorPt.x < newPt.x))
 | 
						|
	    vFlip = ((anchorPt.y < oldPt.y) and (anchorPt.y > newPt.y)) or \
 | 
						|
		    ((anchorPt.y > oldPt.y) and (anchorPt.y < newPt.y))
 | 
						|
 | 
						|
	    if (hFlip and not vFlip) or (vFlip and not hFlip):
 | 
						|
		slopesDown = not slopesDown # Line flipped.
 | 
						|
 | 
						|
	    if slopesDown:
 | 
						|
		obj.setStartPt(wxPoint(0, 0))
 | 
						|
		obj.setEndPt(wxPoint(newWidth, newHeight))
 | 
						|
	    else:
 | 
						|
		obj.setStartPt(wxPoint(0, newHeight))
 | 
						|
		obj.setEndPt(wxPoint(newWidth, 0))
 | 
						|
 | 
						|
	# Finally, adjust the bounds of the object to match the new dimensions.
 | 
						|
 | 
						|
	obj.setPosition(topLeft)
 | 
						|
	obj.setSize(wxSize(botRight.x - topLeft.x, botRight.y - topLeft.y))
 | 
						|
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
 | 
						|
 | 
						|
    def _moveObject(self, offsetX, offsetY):
 | 
						|
	""" Move the currently selected object(s) by the given offset.
 | 
						|
	"""
 | 
						|
	self._saveUndoInfo()
 | 
						|
 | 
						|
	for obj in self.selection:
 | 
						|
	    pos = obj.getPosition()
 | 
						|
	    pos.x = pos.x + offsetX
 | 
						|
	    pos.y = pos.y + offsetY
 | 
						|
	    obj.setPosition(pos)
 | 
						|
 | 
						|
	self.drawPanel.Refresh()
 | 
						|
 | 
						|
 | 
						|
    def _buildLineSizePopup(self, lineSize):
 | 
						|
	""" Build the pop-up menu used to set the line size.
 | 
						|
 | 
						|
	    'lineSize' is the current line size value.  The corresponding item
 | 
						|
	    is checked in the pop-up menu.
 | 
						|
	"""
 | 
						|
	menu = wxMenu()
 | 
						|
	menu.Append(id_LINESIZE_0, "no line",      checkable=true)
 | 
						|
	menu.Append(id_LINESIZE_1, "1-pixel line", checkable=true)
 | 
						|
	menu.Append(id_LINESIZE_2, "2-pixel line", checkable=true)
 | 
						|
	menu.Append(id_LINESIZE_3, "3-pixel line", checkable=true)
 | 
						|
	menu.Append(id_LINESIZE_4, "4-pixel line", checkable=true)
 | 
						|
	menu.Append(id_LINESIZE_5, "5-pixel line", checkable=true)
 | 
						|
 | 
						|
	if   lineSize == 0: menu.Check(id_LINESIZE_0, true)
 | 
						|
	elif lineSize == 1: menu.Check(id_LINESIZE_1, true)
 | 
						|
	elif lineSize == 2: menu.Check(id_LINESIZE_2, true)
 | 
						|
	elif lineSize == 3: menu.Check(id_LINESIZE_3, true)
 | 
						|
	elif lineSize == 4: menu.Check(id_LINESIZE_4, true)
 | 
						|
	elif lineSize == 5: menu.Check(id_LINESIZE_5, true)
 | 
						|
 | 
						|
	EVT_MENU(self, id_LINESIZE_0, self._lineSizePopupSelected)
 | 
						|
	EVT_MENU(self, id_LINESIZE_1, self._lineSizePopupSelected)
 | 
						|
	EVT_MENU(self, id_LINESIZE_2, self._lineSizePopupSelected)
 | 
						|
	EVT_MENU(self, id_LINESIZE_3, self._lineSizePopupSelected)
 | 
						|
	EVT_MENU(self, id_LINESIZE_4, self._lineSizePopupSelected)
 | 
						|
	EVT_MENU(self, id_LINESIZE_5, self._lineSizePopupSelected)
 | 
						|
 | 
						|
	return menu
 | 
						|
 | 
						|
 | 
						|
    def _lineSizePopupSelected(self, event):
 | 
						|
	""" Respond to the user selecting an item from the line size popup menu
 | 
						|
	"""
 | 
						|
	id = event.GetId()
 | 
						|
	if   id == id_LINESIZE_0: self._setLineSize(0)
 | 
						|
	elif id == id_LINESIZE_1: self._setLineSize(1)
 | 
						|
	elif id == id_LINESIZE_2: self._setLineSize(2)
 | 
						|
	elif id == id_LINESIZE_3: self._setLineSize(3)
 | 
						|
	elif id == id_LINESIZE_4: self._setLineSize(4)
 | 
						|
	elif id == id_LINESIZE_5: self._setLineSize(5)
 | 
						|
	else:
 | 
						|
	    wxBell()
 | 
						|
	    return
 | 
						|
 | 
						|
	self.optionIndicator.setLineSize(self.lineSize)
 | 
						|
 | 
						|
 | 
						|
    def _getEventCoordinates(self, event):
 | 
						|
	""" Return the coordinates associated with the given mouse event.
 | 
						|
 | 
						|
	    The coordinates have to be adjusted to allow for the current scroll
 | 
						|
	    position.
 | 
						|
	"""
 | 
						|
        originX, originY = self.drawPanel.GetViewStart()
 | 
						|
        unitX, unitY = self.drawPanel.GetScrollPixelsPerUnit()
 | 
						|
        return wxPoint(event.GetX() + (originX * unitX),
 | 
						|
		       event.GetY() + (originY * unitY))
 | 
						|
 | 
						|
 | 
						|
    def _getObjectAndSelectionHandleAt(self, pt):
 | 
						|
	""" Return the object and selection handle at the given point.
 | 
						|
 | 
						|
	    We draw selection handles (small rectangles) around the currently
 | 
						|
	    selected object(s).  If the given point is within one of the
 | 
						|
	    selection handle rectangles, we return the associated object and a
 | 
						|
	    code indicating which selection handle the point is in.  If the
 | 
						|
	    point isn't within any selection handle at all, we return the tuple
 | 
						|
	    (None, handle_NONE).
 | 
						|
	"""
 | 
						|
	for obj in self.selection:
 | 
						|
	    handle = obj.getSelectionHandleContainingPoint(pt.x, pt.y)
 | 
						|
	    if handle != handle_NONE:
 | 
						|
		return obj, handle
 | 
						|
 | 
						|
	return None, handle_NONE
 | 
						|
 | 
						|
 | 
						|
    def _getObjectAt(self, pt):
 | 
						|
	""" Return the first object found which is at the given point.
 | 
						|
	"""
 | 
						|
	for obj in self.contents:
 | 
						|
	    if obj.objectContainsPoint(pt.x, pt.y):
 | 
						|
		return obj
 | 
						|
	return None
 | 
						|
 | 
						|
 | 
						|
    def _drawObjectOutline(self, offsetX, offsetY):
 | 
						|
	""" Draw an outline of the currently selected object.
 | 
						|
 | 
						|
	    The selected object's outline is drawn at the object's position
 | 
						|
	    plus the given offset.
 | 
						|
 | 
						|
	    Note that the outline is drawn by *inverting* the window's
 | 
						|
	    contents, so calling _drawObjectOutline twice in succession will
 | 
						|
	    restore the window's contents back to what they were previously.
 | 
						|
	"""
 | 
						|
	if len(self.selection) != 1: return
 | 
						|
 | 
						|
	position = self.selection[0].getPosition()
 | 
						|
	size     = self.selection[0].getSize()
 | 
						|
 | 
						|
	dc = wxClientDC(self.drawPanel)
 | 
						|
	self.drawPanel.PrepareDC(dc)
 | 
						|
	dc.BeginDrawing()
 | 
						|
	dc.SetPen(wxBLACK_DASHED_PEN)
 | 
						|
	dc.SetBrush(wxTRANSPARENT_BRUSH)
 | 
						|
	dc.SetLogicalFunction(wxINVERT)
 | 
						|
 | 
						|
	dc.DrawRectangle(position.x + offsetX, position.y + offsetY,
 | 
						|
			 size.width, size.height)
 | 
						|
 | 
						|
	dc.EndDrawing()
 | 
						|
 | 
						|
 | 
						|
    def _drawVisualFeedback(self, startPt, endPt, type, dashedLine):
 | 
						|
	""" Draw visual feedback for a drawing operation.
 | 
						|
 | 
						|
	    The visual feedback consists of a line, ellipse, or rectangle based
 | 
						|
	    around the two given points.  'type' should be one of the following
 | 
						|
	    predefined feedback type constants:
 | 
						|
 | 
						|
		feedback_RECT     ->  draw rectangular feedback.
 | 
						|
		feedback_LINE     ->  draw line feedback.
 | 
						|
		feedback_ELLIPSE  ->  draw elliptical feedback.
 | 
						|
 | 
						|
	    if 'dashedLine' is true, the feedback is drawn as a dashed rather
 | 
						|
	    than a solid line.
 | 
						|
 | 
						|
	    Note that the feedback is drawn by *inverting* the window's
 | 
						|
	    contents, so calling _drawVisualFeedback twice in succession will
 | 
						|
	    restore the window's contents back to what they were previously.
 | 
						|
	"""
 | 
						|
	dc = wxClientDC(self.drawPanel)
 | 
						|
	self.drawPanel.PrepareDC(dc)
 | 
						|
	dc.BeginDrawing()
 | 
						|
	if dashedLine:
 | 
						|
	    dc.SetPen(wxBLACK_DASHED_PEN)
 | 
						|
	else:
 | 
						|
	    dc.SetPen(wxBLACK_PEN)
 | 
						|
	dc.SetBrush(wxTRANSPARENT_BRUSH)
 | 
						|
	dc.SetLogicalFunction(wxINVERT)
 | 
						|
 | 
						|
	if type == feedback_RECT:
 | 
						|
	    dc.DrawRectangle(startPt.x, startPt.y,
 | 
						|
			     endPt.x - startPt.x,
 | 
						|
			     endPt.y - startPt.y)
 | 
						|
	elif type == feedback_LINE:
 | 
						|
	    dc.DrawLine(startPt.x, startPt.y, endPt.x, endPt.y)
 | 
						|
	elif type == feedback_ELLIPSE:
 | 
						|
	    dc.DrawEllipse(startPt.x, startPt.y,
 | 
						|
			   endPt.x - startPt.x,
 | 
						|
			   endPt.y - startPt.y)
 | 
						|
 | 
						|
	dc.EndDrawing()
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
class DrawingObject:
 | 
						|
    """ An object within the drawing panel.
 | 
						|
 | 
						|
	A pySketch document consists of a front-to-back ordered list of
 | 
						|
	DrawingObjects.  Each DrawingObject has the following properties:
 | 
						|
 | 
						|
	    'type'          What type of object this is (text, line, etc).
 | 
						|
	    'position'      The position of the object within the document.
 | 
						|
	    'size'          The size of the object within the document.
 | 
						|
	    'penColour'     The colour to use for drawing the object's outline.
 | 
						|
	    'fillColour'    Colour to use for drawing object's interior.
 | 
						|
	    'lineSize'      Line width (in pixels) to use for object's outline.
 | 
						|
	    'startPt'       The point, relative to the object's position, where
 | 
						|
			    an obj_LINE object's line should start.
 | 
						|
	    'endPt'         The point, relative to the object's position, where
 | 
						|
			    an obj_LINE object's line should end.
 | 
						|
	    'text'          The object's text (obj_TEXT objects only).
 | 
						|
	    'textFont'      The text object's font name.
 | 
						|
	    'textSize'      The text object's point size.
 | 
						|
	    'textBoldface'  If true, this text object will be drawn in
 | 
						|
			    boldface.
 | 
						|
	    'textItalic'    If true, this text object will be drawn in italic.
 | 
						|
	    'textUnderline' If true, this text object will be drawn underlined.
 | 
						|
	    """
 | 
						|
 | 
						|
    # ==================
 | 
						|
    # == Constructors ==
 | 
						|
    # ==================
 | 
						|
 | 
						|
    def __init__(self, type, position=wxPoint(0, 0), size=wxSize(0, 0),
 | 
						|
		 penColour=wxBLACK, fillColour=wxWHITE, lineSize=1,
 | 
						|
		 text=None, startPt=wxPoint(0, 0), endPt=wxPoint(0,0)):
 | 
						|
	""" Standard constructor.
 | 
						|
 | 
						|
	    'type' is the type of object being created.  This should be one of
 | 
						|
	    the following constants:
 | 
						|
 | 
						|
		obj_LINE
 | 
						|
		obj_RECT
 | 
						|
		obj_ELLIPSE
 | 
						|
		obj_TEXT
 | 
						|
 | 
						|
	    The remaining parameters let you set various options for the newly
 | 
						|
	    created DrawingObject.
 | 
						|
	"""
 | 
						|
	self.type              = type
 | 
						|
	self.position          = position
 | 
						|
	self.size              = size
 | 
						|
	self.penColour         = penColour
 | 
						|
	self.fillColour        = fillColour
 | 
						|
	self.lineSize          = lineSize
 | 
						|
	self.startPt           = startPt
 | 
						|
	self.endPt             = endPt
 | 
						|
	self.text              = text
 | 
						|
	self.textFont          = wxSystemSettings_GetSystemFont(
 | 
						|
				    wxSYS_DEFAULT_GUI_FONT).GetFaceName()
 | 
						|
	self.textSize          = 12
 | 
						|
	self.textBoldface      = false
 | 
						|
	self.textItalic        = false
 | 
						|
	self.textUnderline     = false
 | 
						|
 | 
						|
    # =============================
 | 
						|
    # == Object Property Methods ==
 | 
						|
    # =============================
 | 
						|
 | 
						|
    def getData(self):
 | 
						|
	""" Return a copy of the object's internal data.
 | 
						|
 | 
						|
	    This is used to save this DrawingObject to disk.
 | 
						|
	"""
 | 
						|
	return [self.type, self.position.x, self.position.y,
 | 
						|
		self.size.width, self.size.height,
 | 
						|
		self.penColour.Red(),
 | 
						|
		self.penColour.Green(),
 | 
						|
		self.penColour.Blue(),
 | 
						|
		self.fillColour.Red(),
 | 
						|
		self.fillColour.Green(),
 | 
						|
		self.fillColour.Blue(),
 | 
						|
		self.lineSize,
 | 
						|
		self.startPt.x, self.startPt.y,
 | 
						|
		self.endPt.x, self.endPt.y,
 | 
						|
		self.text,
 | 
						|
		self.textFont,
 | 
						|
		self.textSize,
 | 
						|
		self.textBoldface,
 | 
						|
		self.textItalic,
 | 
						|
		self.textUnderline]
 | 
						|
 | 
						|
 | 
						|
    def setData(self, data):
 | 
						|
	""" Set the object's internal data.
 | 
						|
 | 
						|
	    'data' is a copy of the object's saved data, as returned by
 | 
						|
	    getData() above.  This is used to restore a previously saved
 | 
						|
	    DrawingObject.
 | 
						|
	"""
 | 
						|
	#data = copy.deepcopy(data) # Needed?
 | 
						|
 | 
						|
	self.type              = data[0]
 | 
						|
	self.position          = wxPoint(data[1], data[2])
 | 
						|
	self.size              = wxSize(data[3], data[4])
 | 
						|
	self.penColour         = wxColour(red=data[5],
 | 
						|
					  green=data[6],
 | 
						|
					  blue=data[7])
 | 
						|
	self.fillColour        = wxColour(red=data[8],
 | 
						|
					  green=data[9],
 | 
						|
					  blue=data[10])
 | 
						|
	self.lineSize          = data[11]
 | 
						|
	self.startPt           = wxPoint(data[12], data[13])
 | 
						|
	self.endPt             = wxPoint(data[14], data[15])
 | 
						|
	self.text              = data[16]
 | 
						|
	self.textFont          = data[17]
 | 
						|
	self.textSize          = data[18]
 | 
						|
	self.textBoldface      = data[19]
 | 
						|
	self.textItalic        = data[20]
 | 
						|
	self.textUnderline     = data[21]
 | 
						|
 | 
						|
 | 
						|
    def getType(self):
 | 
						|
	""" Return this DrawingObject's type.
 | 
						|
	"""
 | 
						|
	return self.type
 | 
						|
 | 
						|
 | 
						|
    def setPosition(self, position):
 | 
						|
	""" Set the origin (top-left corner) for this DrawingObject.
 | 
						|
	"""
 | 
						|
	self.position = position
 | 
						|
 | 
						|
 | 
						|
    def getPosition(self):
 | 
						|
	""" Return this DrawingObject's position.
 | 
						|
	"""
 | 
						|
	return self.position
 | 
						|
 | 
						|
 | 
						|
    def setSize(self, size):
 | 
						|
	""" Set the size for this DrawingObject.
 | 
						|
	"""
 | 
						|
	self.size = size
 | 
						|
 | 
						|
 | 
						|
    def getSize(self):
 | 
						|
	""" Return this DrawingObject's size.
 | 
						|
	"""
 | 
						|
	return self.size
 | 
						|
 | 
						|
 | 
						|
    def setPenColour(self, colour):
 | 
						|
	""" Set the pen colour used for this DrawingObject.
 | 
						|
	"""
 | 
						|
	self.penColour = colour
 | 
						|
 | 
						|
 | 
						|
    def getPenColour(self):
 | 
						|
	""" Return this DrawingObject's pen colour.
 | 
						|
	"""
 | 
						|
	return self.penColour
 | 
						|
 | 
						|
 | 
						|
    def setFillColour(self, colour):
 | 
						|
	""" Set the fill colour used for this DrawingObject.
 | 
						|
	"""
 | 
						|
	self.fillColour = colour
 | 
						|
 | 
						|
 | 
						|
    def getFillColour(self):
 | 
						|
	""" Return this DrawingObject's fill colour.
 | 
						|
	"""
 | 
						|
	return self.fillColour
 | 
						|
 | 
						|
 | 
						|
    def setLineSize(self, lineSize):
 | 
						|
	""" Set the linesize used for this DrawingObject.
 | 
						|
	"""
 | 
						|
	self.lineSize = lineSize
 | 
						|
 | 
						|
 | 
						|
    def getLineSize(self):
 | 
						|
	""" Return this DrawingObject's line size.
 | 
						|
	"""
 | 
						|
	return self.lineSize
 | 
						|
 | 
						|
 | 
						|
    def setStartPt(self, startPt):
 | 
						|
	""" Set the starting point for this line DrawingObject.
 | 
						|
	"""
 | 
						|
	self.startPt = startPt
 | 
						|
 | 
						|
 | 
						|
    def getStartPt(self):
 | 
						|
	""" Return the starting point for this line DrawingObject.
 | 
						|
	"""
 | 
						|
	return self.startPt
 | 
						|
 | 
						|
 | 
						|
    def setEndPt(self, endPt):
 | 
						|
	""" Set the ending point for this line DrawingObject.
 | 
						|
	"""
 | 
						|
	self.endPt = endPt
 | 
						|
 | 
						|
 | 
						|
    def getEndPt(self):
 | 
						|
	""" Return the ending point for this line DrawingObject.
 | 
						|
	"""
 | 
						|
	return self.endPt
 | 
						|
 | 
						|
 | 
						|
    def setText(self, text):
 | 
						|
	""" Set the text for this DrawingObject.
 | 
						|
	"""
 | 
						|
	self.text = text
 | 
						|
 | 
						|
 | 
						|
    def getText(self):
 | 
						|
	""" Return this DrawingObject's text.
 | 
						|
	"""
 | 
						|
	return self.text
 | 
						|
 | 
						|
 | 
						|
    def setTextFont(self, font):
 | 
						|
	""" Set the typeface for this text DrawingObject.
 | 
						|
	"""
 | 
						|
	self.textFont = font
 | 
						|
 | 
						|
 | 
						|
    def getTextFont(self):
 | 
						|
	""" Return this text DrawingObject's typeface.
 | 
						|
	"""
 | 
						|
	return self.textFont
 | 
						|
 | 
						|
 | 
						|
    def setTextSize(self, size):
 | 
						|
	""" Set the point size for this text DrawingObject.
 | 
						|
	"""
 | 
						|
	self.textSize = size
 | 
						|
 | 
						|
 | 
						|
    def getTextSize(self):
 | 
						|
	""" Return this text DrawingObject's text size.
 | 
						|
	"""
 | 
						|
	return self.textSize
 | 
						|
 | 
						|
 | 
						|
    def setTextBoldface(self, boldface):
 | 
						|
	""" Set the boldface flag for this text DrawingObject.
 | 
						|
	"""
 | 
						|
	self.textBoldface = boldface
 | 
						|
 | 
						|
 | 
						|
    def getTextBoldface(self):
 | 
						|
	""" Return this text DrawingObject's boldface flag.
 | 
						|
	"""
 | 
						|
	return self.textBoldface
 | 
						|
 | 
						|
 | 
						|
    def setTextItalic(self, italic):
 | 
						|
	""" Set the italic flag for this text DrawingObject.
 | 
						|
	"""
 | 
						|
	self.textItalic = italic
 | 
						|
 | 
						|
 | 
						|
    def getTextItalic(self):
 | 
						|
	""" Return this text DrawingObject's italic flag.
 | 
						|
	"""
 | 
						|
	return self.textItalic
 | 
						|
 | 
						|
 | 
						|
    def setTextUnderline(self, underline):
 | 
						|
	""" Set the underling flag for this text DrawingObject.
 | 
						|
	"""
 | 
						|
	self.textUnderline = underline
 | 
						|
 | 
						|
 | 
						|
    def getTextUnderline(self):
 | 
						|
	""" Return this text DrawingObject's underline flag.
 | 
						|
	"""
 | 
						|
	return self.textUnderline
 | 
						|
 | 
						|
    # ============================
 | 
						|
    # == Object Drawing Methods ==
 | 
						|
    # ============================
 | 
						|
 | 
						|
    def draw(self, dc, selected):
 | 
						|
	""" Draw this DrawingObject into our window.
 | 
						|
 | 
						|
	    'dc' is the device context to use for drawing.  If 'selected' is
 | 
						|
	    true, the object is currently selected and should be drawn as such.
 | 
						|
	"""
 | 
						|
	if self.type != obj_TEXT:
 | 
						|
	    if self.lineSize == 0:
 | 
						|
		dc.SetPen(wxPen(self.penColour, self.lineSize, wxTRANSPARENT))
 | 
						|
	    else:
 | 
						|
		dc.SetPen(wxPen(self.penColour, self.lineSize, wxSOLID))
 | 
						|
	    dc.SetBrush(wxBrush(self.fillColour, wxSOLID))
 | 
						|
	else:
 | 
						|
	    dc.SetTextForeground(self.penColour)
 | 
						|
	    dc.SetTextBackground(self.fillColour)
 | 
						|
 | 
						|
	self._privateDraw(dc, self.position, selected)
 | 
						|
 | 
						|
    # =======================
 | 
						|
    # == Selection Methods ==
 | 
						|
    # =======================
 | 
						|
 | 
						|
    def objectContainsPoint(self, x, y):
 | 
						|
	""" Returns true iff this object contains the given point.
 | 
						|
 | 
						|
	    This is used to determine if the user clicked on the object.
 | 
						|
	"""
 | 
						|
	# Firstly, ignore any points outside of the object's bounds.
 | 
						|
 | 
						|
	if x < self.position.x: return false
 | 
						|
	if x > self.position.x + self.size.x: return false
 | 
						|
	if y < self.position.y: return false
 | 
						|
	if y > self.position.y + self.size.y: return false
 | 
						|
 | 
						|
	if self.type in [obj_RECT, obj_TEXT]:
 | 
						|
	    # Rectangles and text are easy -- they're always selected if the
 | 
						|
	    # point is within their bounds.
 | 
						|
	    return true
 | 
						|
 | 
						|
	# Now things get tricky.  There's no straightforward way of knowing
 | 
						|
	# whether the point is within the object's bounds...to get around this,
 | 
						|
	# we draw the object into a memory-based bitmap and see if the given
 | 
						|
	# point was drawn.  This could no doubt be done more efficiently by
 | 
						|
	# some tricky maths, but this approach works and is simple enough.
 | 
						|
 | 
						|
	bitmap = wxEmptyBitmap(self.size.x + 10, self.size.y + 10)
 | 
						|
	dc = wxMemoryDC()
 | 
						|
	dc.SelectObject(bitmap)
 | 
						|
	dc.BeginDrawing()
 | 
						|
	dc.SetBackground(wxWHITE_BRUSH)
 | 
						|
	dc.Clear()
 | 
						|
	dc.SetPen(wxPen(wxBLACK, self.lineSize + 5, wxSOLID))
 | 
						|
	dc.SetBrush(wxBLACK_BRUSH)
 | 
						|
	self._privateDraw(dc, wxPoint(5, 5), true)
 | 
						|
	dc.EndDrawing()
 | 
						|
	pixel = dc.GetPixel(x - self.position.x + 5, y - self.position.y + 5)
 | 
						|
	if (pixel.Red() == 0) and (pixel.Green() == 0) and (pixel.Blue() == 0):
 | 
						|
	    return true
 | 
						|
	else:
 | 
						|
	    return false
 | 
						|
 | 
						|
 | 
						|
    def getSelectionHandleContainingPoint(self, x, y):
 | 
						|
	""" Return the selection handle containing the given point, if any.
 | 
						|
 | 
						|
	    We return one of the predefined selection handle ID codes.
 | 
						|
	"""
 | 
						|
	if self.type == obj_LINE:
 | 
						|
	    # We have selection handles at the start and end points.
 | 
						|
	    if self._pointInSelRect(x, y, self.position.x + self.startPt.x,
 | 
						|
					  self.position.y + self.startPt.y):
 | 
						|
		return handle_START_POINT
 | 
						|
	    elif self._pointInSelRect(x, y, self.position.x + self.endPt.x,
 | 
						|
					    self.position.y + self.endPt.y):
 | 
						|
		return handle_END_POINT
 | 
						|
	    else:
 | 
						|
		return handle_NONE
 | 
						|
	else:
 | 
						|
	    # We have selection handles at all four corners.
 | 
						|
	    if self._pointInSelRect(x, y, self.position.x, self.position.y):
 | 
						|
		return handle_TOP_LEFT
 | 
						|
	    elif self._pointInSelRect(x, y, self.position.x + self.size.width,
 | 
						|
					    self.position.y):
 | 
						|
		return handle_TOP_RIGHT
 | 
						|
	    elif self._pointInSelRect(x, y, self.position.x,
 | 
						|
					    self.position.y + self.size.height):
 | 
						|
		return handle_BOTTOM_LEFT
 | 
						|
	    elif self._pointInSelRect(x, y, self.position.x + self.size.width,
 | 
						|
					    self.position.y + self.size.height):
 | 
						|
		return handle_BOTTOM_RIGHT
 | 
						|
	    else:
 | 
						|
		return handle_NONE
 | 
						|
 | 
						|
 | 
						|
    def objectWithinRect(self, x, y, width, height):
 | 
						|
	""" Return true iff this object falls completely within the given rect.
 | 
						|
	"""
 | 
						|
	if x          > self.position.x:                    return false
 | 
						|
	if x + width  < self.position.x + self.size.width:  return false
 | 
						|
	if y          > self.position.y:                    return false
 | 
						|
	if y + height < self.position.y + self.size.height: return false
 | 
						|
	return true
 | 
						|
 | 
						|
    # =====================
 | 
						|
    # == Utility Methods ==
 | 
						|
    # =====================
 | 
						|
 | 
						|
    def fitToText(self):
 | 
						|
	""" Resize a text DrawingObject so that it fits it's text exactly.
 | 
						|
	"""
 | 
						|
	if self.type != obj_TEXT: return
 | 
						|
 | 
						|
	if self.textBoldface: weight = wxBOLD
 | 
						|
	else:                 weight = wxNORMAL
 | 
						|
	if self.textItalic: style = wxITALIC
 | 
						|
	else:               style = wxNORMAL
 | 
						|
	font = wxFont(self.textSize, wxDEFAULT, style, weight,
 | 
						|
		      self.textUnderline, self.textFont)
 | 
						|
 | 
						|
	dummyWindow = wxFrame(None, -1, "")
 | 
						|
	dummyWindow.SetFont(font)
 | 
						|
	width, height = dummyWindow.GetTextExtent(self.text)
 | 
						|
	dummyWindow.Destroy()
 | 
						|
 | 
						|
	self.size = wxSize(width, height)
 | 
						|
 | 
						|
    # =====================
 | 
						|
    # == Private Methods ==
 | 
						|
    # =====================
 | 
						|
 | 
						|
    def _privateDraw(self, dc, position, selected):
 | 
						|
	""" Private routine to draw this DrawingObject.
 | 
						|
 | 
						|
	    'dc' is the device context to use for drawing, while 'position' is
 | 
						|
	    the position in which to draw the object.  If 'selected' is true,
 | 
						|
	    the object is drawn with selection handles.  This private drawing
 | 
						|
	    routine assumes that the pen and brush have already been set by the
 | 
						|
	    caller.
 | 
						|
	"""
 | 
						|
	if self.type == obj_LINE:
 | 
						|
	    dc.DrawLine(position.x + self.startPt.x,
 | 
						|
			position.y + self.startPt.y,
 | 
						|
			position.x + self.endPt.x,
 | 
						|
			position.y + self.endPt.y)
 | 
						|
	elif self.type == obj_RECT:
 | 
						|
	    dc.DrawRectangle(position.x, position.y,
 | 
						|
			     self.size.width, self.size.height)
 | 
						|
	elif self.type == obj_ELLIPSE:
 | 
						|
	    dc.DrawEllipse(position.x, position.y,
 | 
						|
			   self.size.width, self.size.height)
 | 
						|
	elif self.type == obj_TEXT:
 | 
						|
	    if self.textBoldface: weight = wxBOLD
 | 
						|
	    else:                 weight = wxNORMAL
 | 
						|
	    if self.textItalic: style = wxITALIC
 | 
						|
	    else:               style = wxNORMAL
 | 
						|
	    font = wxFont(self.textSize, wxDEFAULT, style, weight,
 | 
						|
			  self.textUnderline, self.textFont)
 | 
						|
	    dc.SetFont(font)
 | 
						|
	    dc.DrawText(self.text, position.x, position.y)
 | 
						|
 | 
						|
	if selected:
 | 
						|
	    dc.SetPen(wxTRANSPARENT_PEN)
 | 
						|
	    dc.SetBrush(wxBLACK_BRUSH)
 | 
						|
 | 
						|
	    if self.type == obj_LINE:
 | 
						|
		# Draw selection handles at the start and end points.
 | 
						|
		self._drawSelHandle(dc, position.x + self.startPt.x,
 | 
						|
					position.y + self.startPt.y)
 | 
						|
		self._drawSelHandle(dc, position.x + self.endPt.x,
 | 
						|
					position.y + self.endPt.y)
 | 
						|
	    else:
 | 
						|
		# Draw selection handles at all four corners.
 | 
						|
		self._drawSelHandle(dc, position.x, position.y)
 | 
						|
		self._drawSelHandle(dc, position.x + self.size.width,
 | 
						|
					position.y)
 | 
						|
		self._drawSelHandle(dc, position.x,
 | 
						|
					position.y + self.size.height)
 | 
						|
		self._drawSelHandle(dc, position.x + self.size.width,
 | 
						|
					position.y + self.size.height)
 | 
						|
 | 
						|
 | 
						|
    def _drawSelHandle(self, dc, x, y):
 | 
						|
	""" Draw a selection handle around this DrawingObject.
 | 
						|
 | 
						|
	    'dc' is the device context to draw the selection handle within,
 | 
						|
	    while 'x' and 'y' are the coordinates to use for the centre of the
 | 
						|
	    selection handle.
 | 
						|
	"""
 | 
						|
	dc.DrawRectangle(x - 3, y - 3, 6, 6)
 | 
						|
 | 
						|
 | 
						|
    def _pointInSelRect(self, x, y, rX, rY):
 | 
						|
	""" Return true iff (x, y) is within the selection handle at (rX, ry).
 | 
						|
	"""
 | 
						|
	if   x < rX - 3: return false
 | 
						|
	elif x > rX + 3: return false
 | 
						|
	elif y < rY - 3: return false
 | 
						|
	elif y > rY + 3: return false
 | 
						|
	else:            return true
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
class ToolPaletteIcon(wxStaticBitmap):
 | 
						|
    """ An icon appearing in the tool palette area of our sketching window.
 | 
						|
 | 
						|
	Note that this is actually implemented as a wxStaticBitmap rather
 | 
						|
	than as a wxIcon.  wxIcon has a very specific meaning, and isn't
 | 
						|
	appropriate for this more general use.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, parent, iconID, iconName, toolTip):
 | 
						|
	""" Standard constructor.
 | 
						|
 | 
						|
	    'parent'   is the parent window this icon will be part of.
 | 
						|
	    'iconID'   is the internal ID used for this icon.
 | 
						|
	    'iconName' is the name used for this icon.
 | 
						|
	    'toolTip'  is the tool tip text to show for this icon.
 | 
						|
 | 
						|
	    The icon name is used to get the appropriate bitmap for this icon.
 | 
						|
	"""
 | 
						|
	bmp = wxBitmap("images/" + iconName + "Icon.bmp", wxBITMAP_TYPE_BMP)
 | 
						|
	wxStaticBitmap.__init__(self, parent, iconID, bmp, wxDefaultPosition,
 | 
						|
				wxSize(bmp.GetWidth(), bmp.GetHeight()))
 | 
						|
	self.SetToolTip(wxToolTip(toolTip))
 | 
						|
 | 
						|
	self.iconID     = iconID
 | 
						|
	self.iconName   = iconName
 | 
						|
	self.isSelected = false
 | 
						|
 | 
						|
 | 
						|
    def select(self):
 | 
						|
	""" Select the icon.
 | 
						|
 | 
						|
	    The icon's visual representation is updated appropriately.
 | 
						|
	"""
 | 
						|
	if self.isSelected: return # Nothing to do!
 | 
						|
 | 
						|
	bmp = wxBitmap("images/" + self.iconName + "IconSel.bmp",
 | 
						|
		       wxBITMAP_TYPE_BMP)
 | 
						|
	self.SetBitmap(bmp)
 | 
						|
	self.isSelected = true
 | 
						|
 | 
						|
 | 
						|
    def deselect(self):
 | 
						|
	""" Deselect the icon.
 | 
						|
 | 
						|
	    The icon's visual representation is updated appropriately.
 | 
						|
	"""
 | 
						|
	if not self.isSelected: return # Nothing to do!
 | 
						|
 | 
						|
	bmp = wxBitmap("images/" + self.iconName + "Icon.bmp",
 | 
						|
		       wxBITMAP_TYPE_BMP)
 | 
						|
	self.SetBitmap(bmp)
 | 
						|
	self.isSelected = false
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
class ToolOptionIndicator(wxWindow):
 | 
						|
    """ A visual indicator which shows the current tool options.
 | 
						|
    """
 | 
						|
    def __init__(self, parent):
 | 
						|
	""" Standard constructor.
 | 
						|
	"""
 | 
						|
	wxWindow.__init__(self, parent, -1, wxDefaultPosition, wxSize(52, 32))
 | 
						|
 | 
						|
	self.penColour  = wxBLACK
 | 
						|
	self.fillColour = wxWHITE
 | 
						|
	self.lineSize   = 1
 | 
						|
 | 
						|
	EVT_PAINT(self, self.OnPaint)
 | 
						|
 | 
						|
 | 
						|
    def setPenColour(self, penColour):
 | 
						|
	""" Set the indicator's current pen colour.
 | 
						|
	"""
 | 
						|
	self.penColour = penColour
 | 
						|
	self.Refresh()
 | 
						|
 | 
						|
 | 
						|
    def setFillColour(self, fillColour):
 | 
						|
	""" Set the indicator's current fill colour.
 | 
						|
	"""
 | 
						|
	self.fillColour = fillColour
 | 
						|
	self.Refresh()
 | 
						|
 | 
						|
 | 
						|
    def setLineSize(self, lineSize):
 | 
						|
	""" Set the indicator's current pen colour.
 | 
						|
	"""
 | 
						|
	self.lineSize = lineSize
 | 
						|
	self.Refresh()
 | 
						|
 | 
						|
 | 
						|
    def OnPaint(self, event):
 | 
						|
	""" Paint our tool option indicator.
 | 
						|
	"""
 | 
						|
	dc = wxPaintDC(self)
 | 
						|
	dc.BeginDrawing()
 | 
						|
 | 
						|
	if self.lineSize == 0:
 | 
						|
	    dc.SetPen(wxPen(self.penColour, self.lineSize, wxTRANSPARENT))
 | 
						|
	else:
 | 
						|
	    dc.SetPen(wxPen(self.penColour, self.lineSize, wxSOLID))
 | 
						|
	dc.SetBrush(wxBrush(self.fillColour, wxSOLID))
 | 
						|
 | 
						|
	dc.DrawRectangle(5, 5, self.GetSize().width - 10,
 | 
						|
			       self.GetSize().height - 10)
 | 
						|
 | 
						|
	dc.EndDrawing()
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
class EditTextObjectDialog(wxDialog):
 | 
						|
    """ Dialog box used to edit the properties of a text object.
 | 
						|
 | 
						|
	The user can edit the object's text, font, size, and text style.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, parent, title):
 | 
						|
	""" Standard constructor.
 | 
						|
	"""
 | 
						|
	wxDialog.__init__(self, parent, -1, title)
 | 
						|
 | 
						|
	self.textCtrl = wxTextCtrl(self, 1001, "", style=wxTE_PROCESS_ENTER,
 | 
						|
				   validator=TextObjectValidator())
 | 
						|
	extent = self.textCtrl.GetFullTextExtent("Hy")
 | 
						|
	lineHeight = extent[1] + extent[3]
 | 
						|
	self.textCtrl.SetSize(wxSize(-1, lineHeight * 4))
 | 
						|
 | 
						|
	EVT_TEXT_ENTER(self, 1001, self._doEnter)
 | 
						|
 | 
						|
	fonts = wxFontEnumerator()
 | 
						|
	fonts.EnumerateFacenames()
 | 
						|
	self.fontList = fonts.GetFacenames()
 | 
						|
	self.fontList.sort()
 | 
						|
 | 
						|
	fontLabel = wxStaticText(self, -1, "Font:")
 | 
						|
	self._setFontOptions(fontLabel, weight=wxBOLD)
 | 
						|
 | 
						|
	self.fontCombo = wxComboBox(self, -1, "", wxDefaultPosition,
 | 
						|
				    wxDefaultSize, self.fontList,
 | 
						|
				    style = wxCB_READONLY)
 | 
						|
	self.fontCombo.SetSelection(0) # Default to first available font.
 | 
						|
 | 
						|
	self.sizeList = ["8", "9", "10", "12", "14", "16",
 | 
						|
			 "18", "20", "24", "32", "48", "72"]
 | 
						|
 | 
						|
	sizeLabel = wxStaticText(self, -1, "Size:")
 | 
						|
	self._setFontOptions(sizeLabel, weight=wxBOLD)
 | 
						|
 | 
						|
	self.sizeCombo = wxComboBox(self, -1, "", wxDefaultPosition,
 | 
						|
				    wxDefaultSize, self.sizeList,
 | 
						|
				    style=wxCB_READONLY)
 | 
						|
	self.sizeCombo.SetSelection(3) # Default to 12 point text.
 | 
						|
 | 
						|
	gap = wxLEFT | wxTOP | wxRIGHT
 | 
						|
 | 
						|
	comboSizer = wxBoxSizer(wxHORIZONTAL)
 | 
						|
	comboSizer.Add(fontLabel,      0, gap | wxALIGN_CENTRE_VERTICAL, 5)
 | 
						|
	comboSizer.Add(self.fontCombo, 0, gap, 5)
 | 
						|
	comboSizer.Add(5, 5) # Spacer.
 | 
						|
	comboSizer.Add(sizeLabel,      0, gap | wxALIGN_CENTRE_VERTICAL, 5)
 | 
						|
	comboSizer.Add(self.sizeCombo, 0, gap, 5)
 | 
						|
 | 
						|
	self.boldCheckbox      = wxCheckBox(self, -1, "Bold")
 | 
						|
	self.italicCheckbox    = wxCheckBox(self, -1, "Italic")
 | 
						|
	self.underlineCheckbox = wxCheckBox(self, -1, "Underline")
 | 
						|
 | 
						|
	self._setFontOptions(self.boldCheckbox,      weight=wxBOLD)
 | 
						|
	self._setFontOptions(self.italicCheckbox,    style=wxITALIC)
 | 
						|
	self._setFontOptions(self.underlineCheckbox, underline=true)
 | 
						|
 | 
						|
	styleSizer = wxBoxSizer(wxHORIZONTAL)
 | 
						|
	styleSizer.Add(self.boldCheckbox,      0, gap, 5)
 | 
						|
	styleSizer.Add(self.italicCheckbox,    0, gap, 5)
 | 
						|
	styleSizer.Add(self.underlineCheckbox, 0, gap, 5)
 | 
						|
 | 
						|
	self.okButton     = wxButton(self, wxID_OK,     "OK")
 | 
						|
	self.cancelButton = wxButton(self, wxID_CANCEL, "Cancel")
 | 
						|
 | 
						|
	btnSizer = wxBoxSizer(wxHORIZONTAL)
 | 
						|
	btnSizer.Add(self.okButton,     0, gap, 5)
 | 
						|
	btnSizer.Add(self.cancelButton, 0, gap, 5)
 | 
						|
 | 
						|
	sizer = wxBoxSizer(wxVERTICAL)
 | 
						|
	sizer.Add(self.textCtrl, 1, gap | wxEXPAND,       5)
 | 
						|
	sizer.Add(10, 10) # Spacer.
 | 
						|
	sizer.Add(comboSizer,    0, gap | wxALIGN_CENTRE, 5)
 | 
						|
	sizer.Add(styleSizer,    0, gap | wxALIGN_CENTRE, 5)
 | 
						|
	sizer.Add(10, 10) # Spacer.
 | 
						|
	sizer.Add(btnSizer,      0, gap | wxALIGN_CENTRE, 5)
 | 
						|
 | 
						|
	self.SetAutoLayout(true)
 | 
						|
	self.SetSizer(sizer)
 | 
						|
	sizer.Fit(self)
 | 
						|
 | 
						|
	self.textCtrl.SetFocus()
 | 
						|
 | 
						|
 | 
						|
    def objectToDialog(self, obj):
 | 
						|
	""" Copy the properties of the given text object into the dialog box.
 | 
						|
	"""
 | 
						|
	self.textCtrl.SetValue(obj.getText())
 | 
						|
	self.textCtrl.SetSelection(0, len(obj.getText()))
 | 
						|
 | 
						|
	for i in range(len(self.fontList)):
 | 
						|
	    if self.fontList[i] == obj.getTextFont():
 | 
						|
		self.fontCombo.SetSelection(i)
 | 
						|
		break
 | 
						|
 | 
						|
	for i in range(len(self.sizeList)):
 | 
						|
	    if self.sizeList[i] == str(obj.getTextSize()):
 | 
						|
		self.sizeCombo.SetSelection(i)
 | 
						|
		break
 | 
						|
 | 
						|
	self.boldCheckbox.SetValue(obj.getTextBoldface())
 | 
						|
	self.italicCheckbox.SetValue(obj.getTextItalic())
 | 
						|
	self.underlineCheckbox.SetValue(obj.getTextUnderline())
 | 
						|
 | 
						|
 | 
						|
    def dialogToObject(self, obj):
 | 
						|
	""" Copy the properties from the dialog box into the given text object.
 | 
						|
	"""
 | 
						|
	obj.setText(self.textCtrl.GetValue())
 | 
						|
	obj.setTextFont(self.fontCombo.GetValue())
 | 
						|
	obj.setTextSize(string.atoi(self.sizeCombo.GetValue()))
 | 
						|
	obj.setTextBoldface(self.boldCheckbox.GetValue())
 | 
						|
	obj.setTextItalic(self.italicCheckbox.GetValue())
 | 
						|
	obj.setTextUnderline(self.underlineCheckbox.GetValue())
 | 
						|
	obj.fitToText()
 | 
						|
 | 
						|
    # ======================
 | 
						|
    # == Private Routines ==
 | 
						|
    # ======================
 | 
						|
 | 
						|
    def _setFontOptions(self, ctrl, family=None, pointSize=-1,
 | 
						|
				    style=wxNORMAL, weight=wxNORMAL,
 | 
						|
				    underline=false):
 | 
						|
	""" Change the font settings for the given control.
 | 
						|
 | 
						|
	    The meaning of the 'family', 'pointSize', 'style', 'weight' and
 | 
						|
	    'underline' parameters are the same as for the wxFont constructor.
 | 
						|
	    If the family and/or pointSize isn't specified, the current default
 | 
						|
	    value is used.
 | 
						|
	"""
 | 
						|
	if family == None: family = ctrl.GetFont().GetFamily()
 | 
						|
	if pointSize == -1: pointSize = ctrl.GetFont().GetPointSize()
 | 
						|
 | 
						|
	ctrl.SetFont(wxFont(pointSize, family, style, weight, underline))
 | 
						|
	ctrl.SetSize(ctrl.GetBestSize()) # Adjust size to reflect font change.
 | 
						|
 | 
						|
 | 
						|
    def _doEnter(self, event):
 | 
						|
	""" Respond to the user hitting the ENTER key.
 | 
						|
 | 
						|
	    We simulate clicking on the "OK" button.
 | 
						|
	"""
 | 
						|
	if self.Validate(): self.Show(false)
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
class TextObjectValidator(wxPyValidator):
 | 
						|
    """ This validator is used to ensure that the user has entered something
 | 
						|
	into the text object editor dialog's text field.
 | 
						|
    """
 | 
						|
    def __init__(self):
 | 
						|
	""" Standard constructor.
 | 
						|
	"""
 | 
						|
	wxPyValidator.__init__(self)
 | 
						|
 | 
						|
 | 
						|
    def Clone(self):
 | 
						|
	""" Standard cloner.
 | 
						|
 | 
						|
	    Note that every validator must implement the Clone() method.
 | 
						|
	"""
 | 
						|
	return TextObjectValidator()
 | 
						|
 | 
						|
 | 
						|
    def Validate(self, win):
 | 
						|
	""" Validate the contents of the given text control.
 | 
						|
	"""
 | 
						|
	textCtrl = wxPyTypeCast(self.GetWindow(), "wxTextCtrl")
 | 
						|
	text = textCtrl.GetValue()
 | 
						|
 | 
						|
	if len(text) == 0:
 | 
						|
	    wxMessageBox("A text object must contain some text!", "Error")
 | 
						|
	    return false
 | 
						|
	else:
 | 
						|
	    return true
 | 
						|
 | 
						|
 | 
						|
    def TransferToWindow(self):
 | 
						|
	""" Transfer data from validator to window.
 | 
						|
 | 
						|
	    The default implementation returns false, indicating that an error
 | 
						|
	    occurred.  We simply return true, as we don't do any data transfer.
 | 
						|
	"""
 | 
						|
	return true # Prevent wxDialog from complaining.
 | 
						|
 | 
						|
 | 
						|
    def TransferFromWindow(self):
 | 
						|
	""" Transfer data from window to validator.
 | 
						|
 | 
						|
	    The default implementation returns false, indicating that an error
 | 
						|
	    occurred.  We simply return true, as we don't do any data transfer.
 | 
						|
	"""
 | 
						|
	return true # Prevent wxDialog from complaining.
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
class ExceptionHandler:
 | 
						|
    """ A simple error-handling class to write exceptions to a text file.
 | 
						|
 | 
						|
	Under MS Windows, the standard DOS console window doesn't scroll and
 | 
						|
	closes as soon as the application exits, making it hard to find and
 | 
						|
	view Python exceptions.  This utility class allows you to handle Python
 | 
						|
	exceptions in a more friendly manner.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self):
 | 
						|
	""" Standard constructor.
 | 
						|
	"""
 | 
						|
	self._buff = ""
 | 
						|
	if os.path.exists("errors.txt"):
 | 
						|
	    os.remove("errors.txt") # Delete previous error log, if any.
 | 
						|
 | 
						|
 | 
						|
    def write(self, s):
 | 
						|
	""" Write the given error message to a text file.
 | 
						|
 | 
						|
	    Note that if the error message doesn't end in a carriage return, we
 | 
						|
	    have to buffer up the inputs until a carriage return is received.
 | 
						|
	"""
 | 
						|
	if (s[-1] != "\n") and (s[-1] != "\r"):
 | 
						|
	    self._buff = self._buff + s
 | 
						|
	    return
 | 
						|
 | 
						|
	try:
 | 
						|
	    s = self._buff + s
 | 
						|
	    self._buff = ""
 | 
						|
 | 
						|
	    if s[:9] == "Traceback":
 | 
						|
		# Tell the user than an exception occurred.
 | 
						|
		wxMessageBox("An internal error has occurred.\nPlease " + \
 | 
						|
			     "refer to the 'errors.txt' file for details.",
 | 
						|
			     "Error", wxOK | wxCENTRE | wxICON_EXCLAMATION)
 | 
						|
 | 
						|
	    f = open("errors.txt", "a")
 | 
						|
	    f.write(s)
 | 
						|
	    f.close()
 | 
						|
	except:
 | 
						|
	    pass # Don't recursively crash on errors.
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
class SketchApp(wxApp):
 | 
						|
    """ The main pySketch application object.
 | 
						|
    """
 | 
						|
    def OnInit(self):
 | 
						|
	""" Initialise the application.
 | 
						|
	"""
 | 
						|
        wxInitAllImageHandlers()
 | 
						|
 | 
						|
	global _docList
 | 
						|
	_docList = []
 | 
						|
 | 
						|
	if len(sys.argv) == 1:
 | 
						|
	    # No file name was specified on the command line -> start with a
 | 
						|
	    # blank document.
 | 
						|
	    frame = DrawingFrame(None, -1, "Untitled")
 | 
						|
	    frame.Centre()
 | 
						|
	    frame.Show(TRUE)
 | 
						|
	    _docList.append(frame)
 | 
						|
	else:
 | 
						|
	    # Load the file(s) specified on the command line.
 | 
						|
	    for arg in sys.argv[1:]:
 | 
						|
		fileName = os.path.join(os.getcwd(), arg)
 | 
						|
		if os.path.isfile(fileName):
 | 
						|
		    frame = DrawingFrame(None, -1,
 | 
						|
					 os.path.basename(fileName),
 | 
						|
					 fileName=fileName)
 | 
						|
		    frame.Show(TRUE)
 | 
						|
		    _docList.append(frame)
 | 
						|
 | 
						|
        return TRUE
 | 
						|
 | 
						|
#----------------------------------------------------------------------------
 | 
						|
 | 
						|
def main():
 | 
						|
    """ Start up the pySketch application.
 | 
						|
    """
 | 
						|
    global _app
 | 
						|
 | 
						|
    # Redirect python exceptions to a log file.
 | 
						|
 | 
						|
    sys.stderr = ExceptionHandler()
 | 
						|
 | 
						|
    # Create and start the pySketch application.
 | 
						|
 | 
						|
    _app = SketchApp(0)
 | 
						|
    _app.MainLoop()
 | 
						|
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    main()
 | 
						|
 |