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)
+ Original Author: Erik Westra (ewestra@wave.co.nz)
+
+ Other contributors: Bill Baxter (wbaxter@gmail.com)
#########################################################################
Known Bugs:
* Scrolling the window causes the drawing panel to be mucked up until you
- refresh it. I've got no idea why.
+ 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.
+ unable to call del on a 'None' object.
+
+ * Saving files via pickling is not a robust cross-platform solution.
"""
-import string, cPickle, os.path
-from wxPython.wx import *
+import sys
+import cPickle, os.path
+import copy
+import wx
+from wx.lib.buttons import GenBitmapButton,GenBitmapToggleButton
+
import traceback, types
# 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_DUPLICATE = wx.NewId() # Edit menu items.
+menu_EDIT_PROPS = wx.NewId()
+
+menu_SELECT = wx.NewId() # Tools menu items.
+menu_LINE = wx.NewId()
+menu_POLYGON = wx.NewId()
+menu_RECT = wx.NewId()
+menu_ELLIPSE = wx.NewId()
+menu_TEXT = wx.NewId()
-menu_SELECT = 10101 # Tools menu items.
-menu_LINE = 10102
-menu_RECT = 10103
-menu_ELLIPSE = 10104
-menu_TEXT = 10105
+menu_DC = wx.NewId() # View menu items.
+menu_GCDC = wx.NewId()
-menu_MOVE_FORWARD = 10201 # Object menu items.
-menu_MOVE_TO_FRONT = 10202
-menu_MOVE_BACKWARD = 10203
-menu_MOVE_TO_BACK = 10204
+menu_MOVE_FORWARD = wx.NewId() # Object menu items.
+menu_MOVE_TO_FRONT = wx.NewId()
+menu_MOVE_BACKWARD = wx.NewId()
+menu_MOVE_TO_BACK = wx.NewId()
-menu_ABOUT = 10205 # Help menu items.
+menu_ABOUT = wx.NewId() # Help menu items.
# Our tool IDs:
-id_SELECT = 11001
-id_LINE = 11002
-id_RECT = 11003
-id_ELLIPSE = 11004
-id_TEXT = 11005
+id_SELECT = wx.NewId()
+id_LINE = wx.NewId()
+id_POLYGON = wx.NewId()
+id_SCRIBBLE = wx.NewId()
+id_RECT = wx.NewId()
+id_ELLIPSE = wx.NewId()
+id_TEXT = wx.NewId()
# Our tool option IDs:
-id_FILL_OPT = 12001
-id_PEN_OPT = 12002
-id_LINE_OPT = 12003
+id_FILL_OPT = wx.NewId()
+id_PEN_OPT = wx.NewId()
+id_LINE_OPT = wx.NewId()
-id_LINESIZE_0 = 13001
-id_LINESIZE_1 = 13002
-id_LINESIZE_2 = 13003
-id_LINESIZE_3 = 13004
-id_LINESIZE_4 = 13005
-id_LINESIZE_5 = 13006
+id_LINESIZE_0 = wx.NewId()
+id_LINESIZE_1 = wx.NewId()
+id_LINESIZE_2 = wx.NewId()
+id_LINESIZE_3 = wx.NewId()
+id_LINESIZE_4 = wx.NewId()
+id_LINESIZE_5 = wx.NewId()
-# DrawObject type IDs:
+# Size of the drawing page, in pixels.
-obj_LINE = 1
-obj_RECT = 2
-obj_ELLIPSE = 3
-obj_TEXT = 4
+PAGE_WIDTH = 1000
+PAGE_HEIGHT = 1000
-# 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
+class DrawingFrame(wx.Frame):
+ """ A frame showing the contents of a single document. """
-# Dragging operations:
+ # ==========================================
+ # == Initialisation and Window Management ==
+ # ==========================================
-drag_NONE = 1
-drag_RESIZE = 2
-drag_MOVE = 3
-drag_DRAG = 4
+ def __init__(self, parent, id, title, fileName=None):
+ """ Standard constructor.
+
+ 'parent', 'id' and 'title' are all passed to the standard wx.Frame
+ constructor. 'fileName' is the name and path of a saved file to
+ load into this frame, if any.
+ """
+ wx.Frame.__init__(self, parent, id, title,
+ style = wx.DEFAULT_FRAME_STYLE | wx.WANTS_CHARS |
+ wx.NO_FULL_REPAINT_ON_RESIZE)
+
+ # Setup our menu bar.
+ menuBar = wx.MenuBar()
+
+ self.fileMenu = wx.Menu()
+ self.fileMenu.Append(wx.ID_NEW, "New\tCtrl-N", "Create a new document")
+ self.fileMenu.Append(wx.ID_OPEN, "Open...\tCtrl-O", "Open an existing document")
+ self.fileMenu.Append(wx.ID_CLOSE, "Close\tCtrl-W")
+ self.fileMenu.AppendSeparator()
+ self.fileMenu.Append(wx.ID_SAVE, "Save\tCtrl-S")
+ self.fileMenu.Append(wx.ID_SAVEAS, "Save As...")
+ self.fileMenu.Append(wx.ID_REVERT, "Revert...")
+ self.fileMenu.AppendSeparator()
+ self.fileMenu.Append(wx.ID_EXIT, "Quit\tCtrl-Q")
+
+ menuBar.Append(self.fileMenu, "File")
+
+ self.editMenu = wx.Menu()
+ self.editMenu.Append(wx.ID_UNDO, "Undo\tCtrl-Z")
+ self.editMenu.Append(wx.ID_REDO, "Redo\tCtrl-Y")
+ self.editMenu.AppendSeparator()
+ self.editMenu.Append(wx.ID_SELECTALL, "Select All\tCtrl-A")
+ self.editMenu.AppendSeparator()
+ self.editMenu.Append(menu_DUPLICATE, "Duplicate\tCtrl-D")
+ self.editMenu.Append(menu_EDIT_PROPS,"Edit...\tCtrl-E", "Edit object properties")
+ self.editMenu.Append(wx.ID_CLEAR, "Delete\tDel")
+
+ menuBar.Append(self.editMenu, "Edit")
+
+ self.viewMenu = wx.Menu()
+ self.viewMenu.Append(menu_DC, "Normal quality",
+ "Normal rendering using wx.DC",
+ kind=wx.ITEM_RADIO)
+ self.viewMenu.Append(menu_GCDC,"High quality",
+ "Anti-aliased rendering using wx.GCDC",
+ kind=wx.ITEM_RADIO)
+
+ menuBar.Append(self.viewMenu, "View")
+
+ self.toolsMenu = wx.Menu()
+ self.toolsMenu.Append(id_SELECT, "Selection", kind=wx.ITEM_RADIO)
+ self.toolsMenu.Append(id_LINE, "Line", kind=wx.ITEM_RADIO)
+ self.toolsMenu.Append(id_POLYGON, "Polygon", kind=wx.ITEM_RADIO)
+ self.toolsMenu.Append(id_SCRIBBLE,"Scribble", kind=wx.ITEM_RADIO)
+ self.toolsMenu.Append(id_RECT, "Rectangle", kind=wx.ITEM_RADIO)
+ self.toolsMenu.Append(id_ELLIPSE, "Ellipse", kind=wx.ITEM_RADIO)
+ self.toolsMenu.Append(id_TEXT, "Text", kind=wx.ITEM_RADIO)
+
+ menuBar.Append(self.toolsMenu, "Tools")
+
+ self.objectMenu = wx.Menu()
+ 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 = wx.Menu()
+ self.helpMenu.Append(menu_ABOUT, "About pySketch...")
+
+ menuBar.Append(self.helpMenu, "Help")
+
+ self.SetMenuBar(menuBar)
+
+ # Create our statusbar
+
+ self.CreateStatusBar()
+
+ # Create our toolbar.
+
+ tsize = (15,15)
+ self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL | wx.NO_BORDER | wx.TB_FLAT)
+
+ artBmp = wx.ArtProvider.GetBitmap
+ self.toolbar.AddSimpleTool(
+ wx.ID_NEW, artBmp(wx.ART_NEW, wx.ART_TOOLBAR, tsize), "New")
+ self.toolbar.AddSimpleTool(
+ wx.ID_OPEN, artBmp(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, tsize), "Open")
+ self.toolbar.AddSimpleTool(
+ wx.ID_SAVE, artBmp(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, tsize), "Save")
+ self.toolbar.AddSimpleTool(
+ wx.ID_SAVEAS, artBmp(wx.ART_FILE_SAVE_AS, wx.ART_TOOLBAR, tsize),
+ "Save As...")
+ #-------
+ self.toolbar.AddSeparator()
+ self.toolbar.AddSimpleTool(
+ wx.ID_UNDO, artBmp(wx.ART_UNDO, wx.ART_TOOLBAR, tsize), "Undo")
+ self.toolbar.AddSimpleTool(
+ wx.ID_REDO, artBmp(wx.ART_REDO, wx.ART_TOOLBAR, tsize), "Redo")
+ self.toolbar.AddSeparator()
+ self.toolbar.AddSimpleTool(
+ menu_DUPLICATE, wx.Bitmap("images/duplicate.bmp", wx.BITMAP_TYPE_BMP),
+ "Duplicate")
+ #-------
+ self.toolbar.AddSeparator()
+ self.toolbar.AddSimpleTool(
+ menu_MOVE_FORWARD, wx.Bitmap("images/moveForward.bmp", wx.BITMAP_TYPE_BMP),
+ "Move Forward")
+ self.toolbar.AddSimpleTool(
+ menu_MOVE_BACKWARD, wx.Bitmap("images/moveBack.bmp", wx.BITMAP_TYPE_BMP),
+ "Move Backward")
+
+ self.toolbar.Realize()
+
+ # Associate menu/toolbar items with their handlers.
+ menuHandlers = [
+ (wx.ID_NEW, self.doNew),
+ (wx.ID_OPEN, self.doOpen),
+ (wx.ID_CLOSE, self.doClose),
+ (wx.ID_SAVE, self.doSave),
+ (wx.ID_SAVEAS, self.doSaveAs),
+ (wx.ID_REVERT, self.doRevert),
+ (wx.ID_EXIT, self.doExit),
+
+ (wx.ID_UNDO, self.doUndo),
+ (wx.ID_REDO, self.doRedo),
+ (wx.ID_SELECTALL, self.doSelectAll),
+ (menu_DUPLICATE, self.doDuplicate),
+ (menu_EDIT_PROPS, self.doEditObject),
+ (wx.ID_CLEAR, self.doDelete),
+
+ (id_SELECT, self.onChooseTool, self.updChooseTool),
+ (id_LINE, self.onChooseTool, self.updChooseTool),
+ (id_POLYGON, self.onChooseTool, self.updChooseTool),
+ (id_SCRIBBLE,self.onChooseTool, self.updChooseTool),
+ (id_RECT, self.onChooseTool, self.updChooseTool),
+ (id_ELLIPSE, self.onChooseTool, self.updChooseTool),
+ (id_TEXT, self.onChooseTool, self.updChooseTool),
+
+ (menu_DC, self.doChooseQuality),
+ (menu_GCDC, self.doChooseQuality),
+
+ (menu_MOVE_FORWARD, self.doMoveForward),
+ (menu_MOVE_TO_FRONT, self.doMoveToFront),
+ (menu_MOVE_BACKWARD, self.doMoveBackward),
+ (menu_MOVE_TO_BACK, self.doMoveToBack),
+
+ (menu_ABOUT, self.doShowAbout)]
+ for combo in menuHandlers:
+ id, handler = combo[:2]
+ self.Bind(wx.EVT_MENU, handler, id = id)
+ if len(combo)>2:
+ self.Bind(wx.EVT_UPDATE_UI, combo[2], id = id)
+
+ # 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.
+
+ self.Bind(wx.EVT_CLOSE, 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.
+
+ self.Bind(wx.EVT_CHAR_HOOK, self.onKeyEvent)
+
+ # Setup our top-most panel. This holds the entire contents of the
+ # window, excluding the menu bar.
+
+ self.topPanel = wx.Panel(self, -1, style=wx.SIMPLE_BORDER)
+
+ # Setup our tool palette, with all our drawing tools and option icons.
+
+ self.toolPalette = wx.BoxSizer(wx.VERTICAL)
+
+ self.selectIcon = ToolPaletteToggle(self.topPanel, id_SELECT,
+ "select", "Selection Tool", mode=wx.ITEM_RADIO)
+ self.lineIcon = ToolPaletteToggle(self.topPanel, id_LINE,
+ "line", "Line Tool", mode=wx.ITEM_RADIO)
+ self.polygonIcon = ToolPaletteToggle(self.topPanel, id_POLYGON,
+ "polygon", "Polygon Tool", mode=wx.ITEM_RADIO)
+ self.scribbleIcon = ToolPaletteToggle(self.topPanel, id_SCRIBBLE,
+ "scribble", "Scribble Tool", mode=wx.ITEM_RADIO)
+ self.rectIcon = ToolPaletteToggle(self.topPanel, id_RECT,
+ "rect", "Rectangle Tool", mode=wx.ITEM_RADIO)
+ self.ellipseIcon = ToolPaletteToggle(self.topPanel, id_ELLIPSE,
+ "ellipse", "Ellipse Tool", mode=wx.ITEM_RADIO)
+ self.textIcon = ToolPaletteToggle(self.topPanel, id_TEXT,
+ "text", "Text Tool", mode=wx.ITEM_RADIO)
+
+ # Create the tools
+ self.tools = {
+ 'select' : (self.selectIcon, SelectDrawingTool()),
+ 'line' : (self.lineIcon, LineDrawingTool()),
+ 'polygon' : (self.polygonIcon, PolygonDrawingTool()),
+ 'scribble': (self.scribbleIcon, ScribbleDrawingTool()),
+ 'rect' : (self.rectIcon, RectDrawingTool()),
+ 'ellipse' : (self.ellipseIcon, EllipseDrawingTool()),
+ 'text' : (self.textIcon, TextDrawingTool())
+ }
+
+
+ toolSizer = wx.GridSizer(0, 2, 5, 5)
+ toolSizer.Add(self.selectIcon)
+ toolSizer.Add(self.lineIcon)
+ toolSizer.Add(self.rectIcon)
+ toolSizer.Add(self.ellipseIcon)
+ toolSizer.Add(self.polygonIcon)
+ toolSizer.Add(self.scribbleIcon)
+ toolSizer.Add(self.textIcon)
+
+ self.optionIndicator = ToolOptionIndicator(self.topPanel)
+ self.optionIndicator.SetToolTip(
+ wx.ToolTip("Shows Current Pen/Fill/Line Size Settings"))
+
+ optionSizer = wx.BoxSizer(wx.HORIZONTAL)
+
+ self.penOptIcon = ToolPaletteButton(self.topPanel, id_PEN_OPT,
+ "penOpt", "Set Pen Colour",)
+ self.fillOptIcon = ToolPaletteButton(self.topPanel, id_FILL_OPT,
+ "fillOpt", "Set Fill Colour")
+ self.lineOptIcon = ToolPaletteButton(self.topPanel, id_LINE_OPT,
+ "lineOpt", "Set Line Size")
-# Visual Feedback types:
+ margin = wx.LEFT | wx.RIGHT
+ optionSizer.Add(self.penOptIcon, 0, margin, 1)
+ optionSizer.Add(self.fillOptIcon, 0, margin, 1)
+ optionSizer.Add(self.lineOptIcon, 0, margin, 1)
-feedback_LINE = 1
-feedback_RECT = 2
-feedback_ELLIPSE = 3
+ margin = wx.TOP | wx.LEFT | wx.RIGHT | wx.ALIGN_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)
-# Mouse-event action parameter types:
+ # Make the tool palette icons respond when the user clicks on them.
-param_RECT = 1
-param_LINE = 2
+ for tool in self.tools.itervalues():
+ tool[0].Bind(wx.EVT_BUTTON, self.onChooseTool)
-# Size of the drawing page, in pixels.
+ self.selectIcon.Bind(wx.EVT_BUTTON, self.onChooseTool)
+ self.lineIcon.Bind(wx.EVT_BUTTON, self.onChooseTool)
-PAGE_WIDTH = 1000
-PAGE_HEIGHT = 1000
-#----------------------------------------------------------------------------
+ self.penOptIcon.Bind(wx.EVT_BUTTON, self.onPenOptionIconClick)
+ self.fillOptIcon.Bind(wx.EVT_BUTTON, self.onFillOptionIconClick)
+ self.lineOptIcon.Bind(wx.EVT_BUTTON, self.onLineOptionIconClick)
-class DrawingFrame(wxFrame):
- """ A frame showing the contents of a single document. """
+ # Setup the main drawing area.
- # ==========================================
- # == Initialisation and Window Management ==
- # ==========================================
+ self.drawPanel = wx.ScrolledWindow(self.topPanel, -1,
+ style=wx.SUNKEN_BORDER|wx.NO_FULL_REPAINT_ON_RESIZE)
+ self.drawPanel.SetBackgroundColour(wx.WHITE)
- 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", kind=wxITEM_CHECK)
- self.toolsMenu.Append(menu_LINE, "Line", kind=wxITEM_CHECK)
- self.toolsMenu.Append(menu_RECT, "Rectangle", kind=wxITEM_CHECK)
- self.toolsMenu.Append(menu_ELLIPSE, "Ellipse", kind=wxITEM_CHECK)
- self.toolsMenu.Append(menu_TEXT, "Text", kind=wxITEM_CHECK)
-
- 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)
+ self.drawPanel.EnableScrolling(True, True)
+ self.drawPanel.SetScrollbars(20, 20, PAGE_WIDTH / 20, PAGE_HEIGHT / 20)
+
+ self.drawPanel.Bind(wx.EVT_MOUSE_EVENTS, self.onMouseEvent)
+
+ self.drawPanel.Bind(wx.EVT_IDLE, self.onIdle)
+ self.drawPanel.Bind(wx.EVT_SIZE, self.onSize)
+ self.drawPanel.Bind(wx.EVT_PAINT, self.onPaint)
+ self.drawPanel.Bind(wx.EVT_ERASE_BACKGROUND, self.onEraseBackground)
+ self.drawPanel.Bind(wx.EVT_SCROLLWIN, self.onPanelScroll)
+
+ self.Bind(wx.EVT_TIMER, self.onIdle)
- # 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)
+ # Position everything in the window.
- # Setup the main drawing area.
+ topSizer = wx.BoxSizer(wx.HORIZONTAL)
+ topSizer.Add(self.toolPalette, 0)
+ topSizer.Add(self.drawPanel, 1, wx.EXPAND)
- self.drawPanel = wxScrolledWindow(self.topPanel, -1,
- style=wxSUNKEN_BORDER)
- self.drawPanel.SetBackgroundColour(wxWHITE)
+ self.topPanel.SetAutoLayout(True)
+ self.topPanel.SetSizer(topSizer)
- self.drawPanel.EnableScrolling(true, true)
- self.drawPanel.SetScrollbars(20, 20, PAGE_WIDTH / 20, PAGE_HEIGHT / 20)
+ self.SetSizeHints(250, 200)
+ self.SetSize(wx.Size(600, 400))
- 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)
+ # Select an initial tool.
- # Position everything in the window.
+ self.curToolName = None
+ self.curToolIcon = None
+ self.curTool = None
+ self.setCurrentTool("select")
- topSizer = wxBoxSizer(wxHORIZONTAL)
- topSizer.Add(self.toolPalette, 0)
- topSizer.Add(self.drawPanel, 1, wxEXPAND)
+ # Set initial dc mode to fast
+ self.wrapDC = lambda dc: dc
- self.topPanel.SetAutoLayout(true)
- self.topPanel.SetSizer(topSizer)
+ # Setup our frame to hold the contents of a sketch document.
- self.SetSizeHints(minW=250, minH=200)
- self.SetSize(wxSize(600, 400))
+ self.dirty = False
+ self.fileName = fileName
+ self.contents = [] # front-to-back ordered list of DrawingObjects.
+ self.selection = [] # List of selected DrawingObjects.
+ self.undoStack = [] # Stack of saved contents for undo.
+ self.redoStack = [] # Stack of saved contents for redo.
- # Select an initial tool.
+ if self.fileName != None:
+ self.loadContents()
- self.curTool = None
- self._setCurrentTool(self.selectIcon)
+ self._initBuffer()
- # Setup our frame to hold the contents of a sketch document.
+ self._adjustMenus()
- 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.
+ # Finally, set our initial pen, fill and line options.
+
+ self._setPenColour(wx.BLACK)
+ self._setFillColour(wx.Colour(215,253,254))
+ self._setLineSize(2)
+
+ self.backgroundFillBrush = None # create on demand
+
+ # Start the background redraw timer
+ # This is optional, but it gives the double-buffered contents a
+ # chance to redraw even when idle events are disabled (like during
+ # resize and scrolling)
+ self.redrawTimer = wx.Timer(self)
+ self.redrawTimer.Start(700)
- 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()))
+ """ Respond to the user clicking on the "Pen Options" icon.
+ """
+ data = wx.ColourData()
+ if len(self.selection) == 1:
+ data.SetColour(self.selection[0].getPenColour())
+ else:
+ data.SetColour(self.penColour)
+
+ dialog = wx.ColourDialog(self, data)
+ dialog.SetTitle('Choose line colour')
+ if dialog.ShowModal() == wx.ID_OK:
+ c = dialog.GetColourData().GetColour()
+ self._setPenColour(wx.Colour(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()))
+ """ Respond to the user clicking on the "Fill Options" icon.
+ """
+ data = wx.ColourData()
+ if len(self.selection) == 1:
+ data.SetColour(self.selection[0].getFillColour())
+ else:
+ data.SetColour(self.fillColour)
+
+ dialog = wx.ColourDialog(self, data)
+ dialog.SetTitle('Choose fill colour')
+ if dialog.ShowModal() == wx.ID_OK:
+ c = dialog.GetColourData().GetColour()
+ self._setFillColour(wx.Colour(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
+ """ 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()
+ """ Respond to a keypress event.
+
+ We make the arrow keys move the selected object(s) by one pixel in
+ the given direction.
+ """
+ step = 1
+ if event.ShiftDown():
+ step = 20
+
+ if event.GetKeyCode() == wx.WXK_UP:
+ self._moveObject(0, -step)
+ elif event.GetKeyCode() == wx.WXK_DOWN:
+ self._moveObject(0, step)
+ elif event.GetKeyCode() == wx.WXK_LEFT:
+ self._moveObject(-step, 0)
+ elif event.GetKeyCode() == wx.WXK_RIGHT:
+ self._moveObject(step, 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()
+ """ Respond to mouse events in the main drawing panel
+
+ How we respond depends on the currently selected tool.
+ """
+ if self.curTool is None: return
+
+ # Translate event into canvas coordinates and pass to current tool
+ origx,origy = event.X, event.Y
+ pt = self._getEventCoordinates(event)
+ event.m_x = pt.x
+ event.m_y = pt.y
+ handled = self.curTool.onMouseEvent(self,event)
+ event.m_x = origx
+ event.m_y = origy
+
+ if handled: return
+
+ # otherwise handle it ourselves
+ if event.RightDown():
+ self.doPopupContextMenu(event)
+
+
+ def doPopupContextMenu(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 = wx.Menu()
+ menu.Append(menu_DUPLICATE, "Duplicate")
+ menu.Append(menu_EDIT_PROPS,"Edit...")
+ menu.Append(wx.ID_CLEAR, "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_PROPS, obj.hasPropertyEditor())
+ 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])
+
+ self.Bind(wx.EVT_MENU, self.doDuplicate, id=menu_DUPLICATE)
+ self.Bind(wx.EVT_MENU, self.doEditObject, id=menu_EDIT_PROPS)
+ self.Bind(wx.EVT_MENU, self.doDelete, id=wx.ID_CLEAR)
+ self.Bind(wx.EVT_MENU, self.doMoveForward, id=menu_MOVE_FORWARD)
+ self.Bind(wx.EVT_MENU, self.doMoveToFront, id=menu_MOVE_TO_FRONT)
+ self.Bind(wx.EVT_MENU, self.doMoveBackward,id=menu_MOVE_BACKWARD)
+ self.Bind(wx.EVT_MENU, self.doMoveToBack, id=menu_MOVE_TO_BACK)
+
+ # Show the pop-up menu.
+
+ clickPt = wx.Point(mousePt.x + self.drawPanel.GetPosition().x,
+ mousePt.y + self.drawPanel.GetPosition().y)
+ self.drawPanel.PopupMenu(menu, mousePt)
+ menu.Destroy()
+
+
+ def onSize(self, event):
+ """
+ Called when the window is resized. We set a flag so the idle
+ handler will resize the buffer.
+ """
+ self.requestRedraw()
+
+
+ def onIdle(self, event):
+ """
+ If the size was changed then resize the bitmap used for double
+ buffering to match the window size. We do it in Idle time so
+ there is only one refresh after resizing is done, not lots while
+ it is happening.
+ """
+ if self._reInitBuffer and self.IsShown():
+ self._initBuffer()
+ self.drawPanel.Refresh(False)
+
+ def requestRedraw(self):
+ """Requests a redraw of the drawing panel contents.
+
+ The actual redrawing doesn't happen until the next idle time.
+ """
+ self._reInitBuffer = True
+
+ def onPaint(self, event):
+ """
+ Called when the window is exposed.
+ """
+ # Create a buffered paint DC. It will create the real
+ # wx.PaintDC and then blit the bitmap to it when dc is
+ # deleted.
+ dc = wx.BufferedPaintDC(self.drawPanel, self.buffer)
+
+
+ # On Windows, if that's all we do things look a little rough
+ # So in order to make scrolling more polished-looking
+ # we iterate over the exposed regions and fill in unknown
+ # areas with a fall-back pattern.
+
+ if wx.Platform != '__WXMSW__':
+ return
+
+ # First get the update rects and subtract off the part that
+ # self.buffer has correct already
+ region = self.drawPanel.GetUpdateRegion()
+ panelRect = self.drawPanel.GetClientRect()
+ offset = list(self.drawPanel.CalcUnscrolledPosition(0,0))
+ offset[0] -= self.saved_offset[0]
+ offset[1] -= self.saved_offset[1]
+ region.Subtract(-offset[0],- offset[1],panelRect.Width, panelRect.Height)
+
+ # Now iterate over the remaining region rects and fill in with a pattern
+ rgn_iter = wx.RegionIterator(region)
+ if rgn_iter.HaveRects():
+ self.setBackgroundMissingFillStyle(dc)
+ offset = self.drawPanel.CalcUnscrolledPosition(0,0)
+ while rgn_iter:
+ r = rgn_iter.GetRect()
+ if r.Size != self.drawPanel.ClientSize:
+ dc.DrawRectangleRect(r)
+ rgn_iter.Next()
+
+
+ def setBackgroundMissingFillStyle(self, dc):
+ if self.backgroundFillBrush is None:
+ # Win95 can only handle a 8x8 stipple bitmaps max
+ #stippleBitmap = wx.BitmapFromBits("\xf0"*4 + "\x0f"*4,8,8)
+ # ...but who uses Win95?
+ stippleBitmap = wx.BitmapFromBits("\x06",2,2)
+ stippleBitmap.SetMask(wx.Mask(stippleBitmap))
+ bgbrush = wx.Brush(wx.WHITE, wx.STIPPLE_MASK_OPAQUE)
+ bgbrush.SetStipple(stippleBitmap)
+ self.backgroundFillBrush = bgbrush
+
+ dc.SetPen(wx.TRANSPARENT_PEN)
+ dc.SetBrush(self.backgroundFillBrush)
+ dc.SetTextForeground(wx.LIGHT_GREY)
+ dc.SetTextBackground(wx.WHITE)
+
+
+ def onEraseBackground(self, event):
+ """
+ Overridden to do nothing to prevent flicker
+ """
+ pass
+
+
+ def onPanelScroll(self, event):
+ """
+ Called when the user changes scrolls the drawPanel
+ """
+ # make a note to ourselves to redraw when we get a chance
+ self.requestRedraw()
+ event.Skip()
+ pass
+
+ def drawContents(self, dc):
+ """
+ Does the actual drawing of all drawing contents with the specified dc
+ """
+ # PrepareDC sets the device origin according to current scrolling
+ self.drawPanel.PrepareDC(dc)
+
+ gdc = self.wrapDC(dc)
+
+ # First pass draws objects
+ ordered_selection = []
+ for obj in self.contents[::-1]:
+ if obj in self.selection:
+ obj.draw(gdc, True)
+ ordered_selection.append(obj)
+ else:
+ obj.draw(gdc, False)
+
+ # First pass draws objects
+ if self.curTool is not None:
+ self.curTool.draw(gdc)
+
+ # Second pass draws selection handles so they're always on top
+ for obj in ordered_selection:
+ obj.drawHandles(gdc)
- 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)
+ """ 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)
+ """ Respond to the "Open" menu command.
+ """
+ global _docList
+
+ curDir = os.getcwd()
+ fileName = wx.FileSelector("Open File", default_extension="psk",
+ flags = wx.OPEN | wx.FILE_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
+ """ Respond to the "Close" menu command.
+ """
+ global _docList
- if self.dirty:
- if not self.askIfUserWantsToSave("closing"): return
+ if self.dirty:
+ if not self.askIfUserWantsToSave("closing"): return
- _docList.remove(self)
- self.Destroy()
+ _docList.remove(self)
+ self.Destroy()
def doSave(self, event):
- """ Respond to the "Save" menu command.
- """
- if self.fileName != None:
- self.saveContents()
+ """ 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()
+ """ Respond to the "Save As" menu command.
+ """
+ if self.fileName == None:
+ default = ""
+ else:
+ default = self.fileName
+
+ curDir = os.getcwd()
+ fileName = wx.FileSelector("Save File As", "Saving",
+ default_filename=default,
+ default_extension="psk",
+ wildcard="*.psk",
+ flags = wx.SAVE | wx.OVERWRITE_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
+ """ 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()
+ if wx.MessageBox("Discard changes made to this document?", "Confirm",
+ style = wx.OK | wx.CANCEL | wx.ICON_QUESTION,
+ parent=self) == wx.CANCEL: 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()
+ """ 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()
+ _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...
+ """ Respond to the "Undo" menu command.
+ """
+ if not self.undoStack: return
- self.contents = []
+ state = self._buildStoredState()
+ self.redoStack.append(state)
+ state = self.undoStack.pop()
+ self._restoreStoredState(state)
- 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 doRedo(self, event):
+ """ Respond to the "Redo" menu.
+ """
+ if not self.redoStack: return
+ state = self._buildStoredState()
+ self.undoStack.append(state)
+ state = self.redoStack.pop()
+ self._restoreStoredState(state)
def doSelectAll(self, event):
- """ Respond to the "Select All" menu command.
- """
- self.selectAll()
+ """ 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)
+ """ Respond to the "Duplicate" menu command.
+ """
+ self.saveUndoInfo()
- self.contents = objs + self.contents
+ objs = []
+ for obj in self.contents:
+ if obj in self.selection:
+ newObj = copy.deepcopy(obj)
+ pos = obj.getPosition()
+ newObj.setPosition(wx.Point(pos.x + 10, pos.y + 10))
+ objs.append(newObj)
- self.selectMany(objs)
+ 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
+ def doEditObject(self, event):
+ """ Respond to the "Edit..." menu command.
+ """
+ if len(self.selection) != 1: return
- editor = EditTextObjectDialog(self, "Edit Text Object")
- editor.objectToDialog(obj)
- if editor.ShowModal() == wxID_CANCEL:
- editor.Destroy()
- return
+ obj = self.selection[0]
+ if not obj.hasPropertyEditor():
+ assert False, "doEditObject called on non-editable"
- self._saveUndoInfo()
-
- editor.dialogToObject(obj)
- editor.Destroy()
-
- self.dirty = true
- self.drawPanel.Refresh()
- self._adjustMenus()
+ ret = obj.doPropertyEdit(self)
+ if ret:
+ self.dirty = True
+ self.requestRedraw()
+ 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()
-
+ """ Respond to the "Delete" menu command.
+ """
+ self.saveUndoInfo()
+
+ for obj in self.selection:
+ self.contents.remove(obj)
+ del obj
+ self.deselectAll()
+
+
+ def onChooseTool(self, event):
+ """ Respond to tool selection menu and tool palette selections
+ """
+ obj = event.GetEventObject()
+ id2name = { id_SELECT: "select",
+ id_LINE: "line",
+ id_POLYGON: "polygon",
+ id_SCRIBBLE: "scribble",
+ id_RECT: "rect",
+ id_ELLIPSE: "ellipse",
+ id_TEXT: "text" }
+ toolID = event.GetId()
+ name = id2name.get( toolID )
+
+ if name:
+ self.setCurrentTool(name)
+
+ def updChooseTool(self, event):
+ """UI update event that keeps tool menu in sync with the PaletteIcons"""
+ obj = event.GetEventObject()
+ id2name = { id_SELECT: "select",
+ id_LINE: "line",
+ id_POLYGON: "polygon",
+ id_SCRIBBLE: "scribble",
+ id_RECT: "rect",
+ id_ELLIPSE: "ellipse",
+ id_TEXT: "text" }
+ toolID = event.GetId()
+ event.Check( toolID == self.curToolIcon.GetId() )
+
+
+ def doChooseQuality(self, event):
+ """Respond to the render quality menu commands
+ """
+ if event.GetId() == menu_DC:
+ self.wrapDC = lambda dc: dc
+ else:
+ self.wrapDC = lambda dc: wx.GCDC(dc)
+ self._adjustMenus()
+ self.requestRedraw()
def doMoveForward(self, event):
- """ Respond to the "Move Forward" menu command.
- """
- if len(self.selection) != 1: return
+ """ Respond to the "Move Forward" menu command.
+ """
+ if len(self.selection) != 1: return
- self._saveUndoInfo()
+ self.saveUndoInfo()
- obj = self.selection[0]
- index = self.contents.index(obj)
- if index == 0: return
+ obj = self.selection[0]
+ index = self.contents.index(obj)
+ if index == 0: return
- del self.contents[index]
- self.contents.insert(index-1, obj)
+ del self.contents[index]
+ self.contents.insert(index-1, obj)
- self.drawPanel.Refresh()
- self._adjustMenus()
+ self.requestRedraw()
+ self._adjustMenus()
def doMoveToFront(self, event):
- """ Respond to the "Move to Front" menu command.
- """
- if len(self.selection) != 1: return
+ """ Respond to the "Move to Front" menu command.
+ """
+ if len(self.selection) != 1: return
- self._saveUndoInfo()
+ self.saveUndoInfo()
- obj = self.selection[0]
- self.contents.remove(obj)
- self.contents.insert(0, obj)
+ obj = self.selection[0]
+ self.contents.remove(obj)
+ self.contents.insert(0, obj)
- self.drawPanel.Refresh()
- self._adjustMenus()
+ self.requestRedraw()
+ self._adjustMenus()
def doMoveBackward(self, event):
- """ Respond to the "Move Backward" menu command.
- """
- if len(self.selection) != 1: return
+ """ Respond to the "Move Backward" menu command.
+ """
+ if len(self.selection) != 1: return
- self._saveUndoInfo()
+ self.saveUndoInfo()
- obj = self.selection[0]
- index = self.contents.index(obj)
- if index == len(self.contents) - 1: return
+ 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)
+ del self.contents[index]
+ self.contents.insert(index+1, obj)
- self.drawPanel.Refresh()
- self._adjustMenus()
+ self.requestRedraw()
+ self._adjustMenus()
def doMoveToBack(self, event):
- """ Respond to the "Move to Back" menu command.
- """
- if len(self.selection) != 1: return
+ """ Respond to the "Move to Back" menu command.
+ """
+ if len(self.selection) != 1: return
- self._saveUndoInfo()
+ self.saveUndoInfo()
- obj = self.selection[0]
- self.contents.remove(obj)
- self.contents.append(obj)
+ obj = self.selection[0]
+ self.contents.remove(obj)
+ self.contents.append(obj)
- self.drawPanel.Refresh()
- self._adjustMenus()
+ self.requestRedraw()
+ 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()
+ """ Respond to the "About pySketch" menu command.
+ """
+ dialog = wx.Dialog(self, -1, "About pySketch") # ,
+ #style=wx.DIALOG_MODAL | wx.STAY_ON_TOP)
+ dialog.SetBackgroundColour(wx.WHITE)
+
+ panel = wx.Panel(dialog, -1)
+ panel.SetBackgroundColour(wx.WHITE)
+
+ panelSizer = wx.BoxSizer(wx.VERTICAL)
+
+ boldFont = wx.Font(panel.GetFont().GetPointSize(),
+ panel.GetFont().GetFamily(),
+ wx.NORMAL, wx.BOLD)
+
+ logo = wx.StaticBitmap(panel, -1, wx.Bitmap("images/logo.bmp",
+ wx.BITMAP_TYPE_BMP))
+
+ lab1 = wx.StaticText(panel, -1, "pySketch")
+ lab1.SetFont(wx.Font(36, boldFont.GetFamily(), wx.ITALIC, wx.BOLD))
+ lab1.SetSize(lab1.GetBestSize())
+
+ imageSizer = wx.BoxSizer(wx.HORIZONTAL)
+ imageSizer.Add(logo, 0, wx.ALL | wx.ALIGN_CENTRE_VERTICAL, 5)
+ imageSizer.Add(lab1, 0, wx.ALL | wx.ALIGN_CENTRE_VERTICAL, 5)
+
+ lab2 = wx.StaticText(panel, -1, "A simple object-oriented drawing " + \
+ "program.")
+ lab2.SetFont(boldFont)
+ lab2.SetSize(lab2.GetBestSize())
+
+ lab3 = wx.StaticText(panel, -1, "pySketch is completely free " + \
+ "software; please")
+ lab3.SetFont(boldFont)
+ lab3.SetSize(lab3.GetBestSize())
+
+ lab4 = wx.StaticText(panel, -1, "feel free to adapt or use this " + \
+ "in any way you like.")
+ lab4.SetFont(boldFont)
+ lab4.SetSize(lab4.GetBestSize())
+
+ lab5 = wx.StaticText(panel, -1,
+ "Author: Erik Westra " + \
+ "(ewestra@wave.co.nz)\n" + \
+ "Contributors: Bill Baxter " +\
+ "(wbaxter@gmail.com) ")
+
+ lab5.SetFont(boldFont)
+ lab5.SetSize(lab5.GetBestSize())
+
+ btnOK = wx.Button(panel, wx.ID_OK, "OK")
+
+ panelSizer.Add(imageSizer, 0, wx.ALIGN_CENTRE)
+ panelSizer.Add((10, 10)) # Spacer.
+ panelSizer.Add(lab2, 0, wx.ALIGN_CENTRE)
+ panelSizer.Add((10, 10)) # Spacer.
+ panelSizer.Add(lab3, 0, wx.ALIGN_CENTRE)
+ panelSizer.Add(lab4, 0, wx.ALIGN_CENTRE)
+ panelSizer.Add((10, 10)) # Spacer.
+ panelSizer.Add(lab5, 0, wx.ALIGN_CENTRE)
+ panelSizer.Add((10, 10)) # Spacer.
+ panelSizer.Add(btnOK, 0, wx.ALL | wx.ALIGN_CENTRE, 5)
+
+ panel.SetAutoLayout(True)
+ panel.SetSizer(panelSizer)
+ panelSizer.Fit(panel)
+
+ topSizer = wx.BoxSizer(wx.HORIZONTAL)
+ topSizer.Add(panel, 0, wx.ALL, 10)
+
+ dialog.SetAutoLayout(True)
+ dialog.SetSizer(topSizer)
+ topSizer.Fit(dialog)
+
+ dialog.Centre()
+
+ btn = dialog.ShowModal()
+ dialog.Destroy()
- btn = dialog.ShowModal()
- dialog.Destroy()
+ def getTextEditor(self):
+ if not hasattr(self,'textEditor') or not self.textEditor:
+ self.textEditor = EditTextObjectDialog(self, "Edit Text Object")
+ return self.textEditor
# =============================
# == 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)
+ def addObject(self, obj, select=True):
+ """Add a new drawing object to the canvas.
+
+ If select is True then also select the object
+ """
+ self.saveUndoInfo()
+ self.contents.insert(0, obj)
+ self.dirty = True
+ if select:
+ self.select(obj)
+ #self.setCurrentTool('select')
+
+ 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.
+
+ This should be called only for a new modification to the document
+ since it erases the redo history.
+ """
+ state = self._buildStoredState()
+
+ self.undoStack.append(state)
+ self.redoStack = []
+ self.dirty = True
+ self._adjustMenus()
# =======================
# == Selection Methods ==
# =======================
+ def setCurrentTool(self, toolName):
+ """ Set the currently selected tool.
+ """
+
+ toolIcon, tool = self.tools[toolName]
+ if self.curToolIcon is not None:
+ self.curToolIcon.SetValue(False)
+
+ toolIcon.SetValue(True)
+ self.curToolName = toolName
+ self.curToolIcon = toolIcon
+ self.curTool = tool
+ self.drawPanel.SetCursor(tool.getDefaultCursor())
+
+
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()
+ """ Select every DrawingObject in our document.
+ """
+ self.selection = []
+ for obj in self.contents:
+ self.selection.append(obj)
+ self.requestRedraw()
+ self._adjustMenus()
def deselectAll(self):
- """ Deselect every DrawingObject in our document.
- """
- self.selection = []
- self.drawPanel.Refresh()
- self._adjustMenus()
+ """ Deselect every DrawingObject in our document.
+ """
+ self.selection = []
+ self.requestRedraw()
+ self._adjustMenus()
- def select(self, obj):
- """ Select the given DrawingObject within our document.
- """
- self.selection = [obj]
- self.drawPanel.Refresh()
- self._adjustMenus()
+ def select(self, obj, add=False):
+ """ Select the given DrawingObject within our document.
+ If 'add' is True obj is added onto the current selection
+ """
+ if not add:
+ self.selection = []
+ if obj not in self.selection:
+ self.selection += [obj]
+ self.requestRedraw()
+ self._adjustMenus()
def selectMany(self, objs):
- """ Select the given list of DrawingObjects.
- """
- self.selection = objs
- self.drawPanel.Refresh()
- self._adjustMenus()
+ """ Select the given list of DrawingObjects.
+ """
+ self.selection = objs
+ self.requestRedraw()
+ 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()
+ """ 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.requestRedraw()
+ self._adjustMenus()
+
+ 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, None).
+ """
+ for obj in self.selection:
+ handle = obj.getSelectionHandleContainingPoint(pt.x, pt.y)
+ if handle is not None:
+ return obj, handle
+
+ return None, 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
+
# ======================
# == 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()
+ """ Load the contents of our document into memory.
+ """
- for type, data in objData:
- obj = DrawingObject(type)
- obj.setData(data)
- self.contents.append(obj)
+ try:
+ f = open(self.fileName, "rb")
+ objData = cPickle.load(f)
+ f.close()
- self.dirty = false
- self.selection = []
- self.undoInfo = None
+ for klass, data in objData:
+ obj = klass()
+ obj.setData(data)
+ self.contents.append(obj)
- self.drawPanel.Refresh()
- self._adjustMenus()
+ self.dirty = False
+ self.selection = []
+ self.undoStack = []
+ self.redoStack = []
+ self.requestRedraw()
+ self._adjustMenus()
+ except:
+ response = wx.MessageBox("Unable to load " + self.fileName + ".",
+ "Error", wx.OK|wx.ICON_ERROR, self)
- 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 saveContents(self):
+ """ Save the contents of our document to disk.
+ """
+ # SWIG-wrapped native wx contents cannot be pickled, so
+ # we have to convert our data to something pickle-friendly.
+
+ try:
+ objData = []
+ for obj in self.contents:
+ objData.append([obj.__class__, obj.getData()])
+
+ f = open(self.fileName, "wb")
+ cPickle.dump(objData, f)
+ f.close()
+
+ self.dirty = False
+ self._adjustMenus()
+ except:
+ response = wx.MessageBox("Unable to load " + self.fileName + ".",
+ "Error", wx.OK|wx.ICON_ERROR, self)
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.
+ """ 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 = wx.MessageBox("Save changes before " + action + "?",
+ "Confirm", wx.YES_NO | wx.CANCEL, self)
+
+ if response == wx.YES:
+ if self.fileName == None:
+ fileName = wx.FileSelector("Save File As", "Saving",
+ default_extension="psk",
+ wildcard="*.psk",
+ flags = wx.SAVE | wx.OVERWRITE_PROMPT)
+ if fileName == "": return False # User cancelled.
+ self.fileName = fileName
+
+ self.saveContents()
+ return True
+ elif response == wx.NO:
+ return True # User doesn't want changes saved.
+ elif response == wx.CANCEL:
+ return False # User cancelled.
# =====================
# == Private Methods ==
# =====================
+ def _initBuffer(self):
+ """Initialize the bitmap used for buffering the display."""
+ size = self.drawPanel.GetSize()
+ self.buffer = wx.EmptyBitmap(max(1,size.width),max(1,size.height))
+ dc = wx.BufferedDC(None, self.buffer)
+ dc.SetBackground(wx.Brush(self.drawPanel.GetBackgroundColour()))
+ dc.Clear()
+ self.drawContents(dc)
+ del dc # commits all drawing to the buffer
+
+ self.saved_offset = self.drawPanel.CalcUnscrolledPosition(0,0)
+
+ self._reInitBuffer = False
+
+
+
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
+ """ Adjust our menus and toolbar to reflect the current state of the
+ world.
+
+ Doing this manually rather than using an EVT_UPDATE_UI is a bit
+ more efficient (since it's only done when it's really needed),
+ but it means we have to remember to call _adjustMenus any time
+ menus may need adjusting.
+ """
+ canSave = (self.fileName != None) and self.dirty
+ canRevert = (self.fileName != None) and self.dirty
+ canUndo = self.undoStack!=[]
+ canRedo = self.redoStack!=[]
+ selection = len(self.selection) > 0
+ onlyOne = len(self.selection) == 1
+ hasEditor = onlyOne and self.selection[0].hasPropertyEditor()
+ 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(wx.ID_SAVE, canSave)
+ self.fileMenu.Enable(wx.ID_REVERT, canRevert)
+
+ self.editMenu.Enable(wx.ID_UNDO, canUndo)
+ self.editMenu.Enable(wx.ID_REDO, canRedo)
+ self.editMenu.Enable(menu_DUPLICATE, selection)
+ self.editMenu.Enable(menu_EDIT_PROPS,hasEditor)
+ self.editMenu.Enable(wx.ID_CLEAR, selection)
+
+ 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(wx.ID_NEW, True)
+ self.toolbar.EnableTool(wx.ID_OPEN, True)
+ self.toolbar.EnableTool(wx.ID_SAVE, canSave)
+ self.toolbar.EnableTool(wx.ID_UNDO, canUndo)
+ self.toolbar.EnableTool(wx.ID_REDO, canRedo)
+ 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 _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)
+ """ 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.requestRedraw()
+
+ 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)
+ """ 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.requestRedraw()
+ 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 _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.requestRedraw()
+
+ self.lineSize = size
+ self.optionIndicator.setLineSize(size)
+
+
+ def _buildStoredState(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.
+
+ Returns an object representing the current document state.
+ """
+ savedContents = []
+ for obj in self.contents:
+ savedContents.append([obj.__class__, obj.getData()])
+
+ savedSelection = []
+ for i in range(len(self.contents)):
+ if self.contents[i] in self.selection:
+ savedSelection.append(i)
+
+ info = {"contents" : savedContents,
+ "selection" : savedSelection}
+
+ return info
+
+ def _restoreStoredState(self, savedState):
+ """Restore the state of the document to a previous point for undo/redo.
+
+ Takes a stored state object and recreates the document from it.
+ Used by undo/redo implementation.
+ """
+ self.contents = []
+
+ for draw_class, data in savedState["contents"]:
+ obj = draw_class()
+ obj.setData(data)
+ self.contents.append(obj)
+
+ self.selection = []
+ for i in savedState["selection"]:
+ self.selection.append(self.contents[i])
+
+ self.dirty = True
+ self._adjustMenus()
+ self.requestRedraw()
def _resizeObject(self, obj, anchorPt, oldPt, newPt):
- """ Resize the given object.
+ """ 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
+ '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 isinstance(obj, TextDrawingObject):
+ # Not allowed to resize text objects -- they're sized to fit text.
+ wx.Bell()
+ return
- self._saveUndoInfo()
+ 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))
+ topLeft = wx.Point(min(anchorPt.x, newPt.x),
+ min(anchorPt.y, newPt.y))
+ botRight = wx.Point(max(anchorPt.x, newPt.x),
+ max(anchorPt.y, newPt.y))
- newWidth = botRight.x - topLeft.x
- newHeight = botRight.y - topLeft.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.
+ if isinstance(obj, LineDrawingObject):
+ # Adjust the line so that its start and end points match the new
+ # overall object size.
- startPt = obj.getStartPt()
- endPt = obj.getEndPt()
+ 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))
+ 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.
+ # 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))
+ 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 (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))
+ if slopesDown:
+ obj.setStartPt(wx.Point(0, 0))
+ obj.setEndPt(wx.Point(newWidth, newHeight))
+ else:
+ obj.setStartPt(wx.Point(0, newHeight))
+ obj.setEndPt(wx.Point(newWidth, 0))
- # Finally, adjust the bounds of the object to match the new dimensions.
+ # 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))
+ obj.setPosition(topLeft)
+ obj.setSize(wx.Size(botRight.x - topLeft.x, botRight.y - topLeft.y))
- self.drawPanel.Refresh()
+ self.requestRedraw()
def _moveObject(self, offsetX, offsetY):
- """ Move the currently selected object(s) by the given offset.
- """
- self._saveUndoInfo()
+ """ 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)
+ for obj in self.selection:
+ pos = obj.getPosition()
+ pos.x = pos.x + offsetX
+ pos.y = pos.y + offsetY
+ obj.setPosition(pos)
- self.drawPanel.Refresh()
+ self.requestRedraw()
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", kind=wxITEM_CHECK)
- menu.Append(id_LINESIZE_1, "1-pixel line", kind=wxITEM_CHECK)
- menu.Append(id_LINESIZE_2, "2-pixel line", kind=wxITEM_CHECK)
- menu.Append(id_LINESIZE_3, "3-pixel line", kind=wxITEM_CHECK)
- menu.Append(id_LINESIZE_4, "4-pixel line", kind=wxITEM_CHECK)
- menu.Append(id_LINESIZE_5, "5-pixel line", kind=wxITEM_CHECK)
-
- 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
+ """ 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 = wx.Menu()
+ menu.Append(id_LINESIZE_0, "no line", kind=wx.ITEM_CHECK)
+ menu.Append(id_LINESIZE_1, "1-pixel line", kind=wx.ITEM_CHECK)
+ menu.Append(id_LINESIZE_2, "2-pixel line", kind=wx.ITEM_CHECK)
+ menu.Append(id_LINESIZE_3, "3-pixel line", kind=wx.ITEM_CHECK)
+ menu.Append(id_LINESIZE_4, "4-pixel line", kind=wx.ITEM_CHECK)
+ menu.Append(id_LINESIZE_5, "5-pixel line", kind=wx.ITEM_CHECK)
+
+ 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)
+
+ self.Bind(wx.EVT_MENU, self._lineSizePopupSelected, id=id_LINESIZE_0, id2=id_LINESIZE_5)
+
+ 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)
+ """ 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:
+ wx.Bell()
+ return
+
+ self.optionIndicator.setLineSize(self.lineSize)
def _getEventCoordinates(self, event):
- """ Return the coordinates associated with the given mouse event.
+ """ Return the coordinates associated with the given mouse event.
- The coordinates have to be adjusted to allow for the current scroll
- position.
- """
+ 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))
+ return wx.Point(event.GetX() + (originX * unitX),
+ event.GetY() + (originY * unitY))
- def _getObjectAndSelectionHandleAt(self, pt):
- """ Return the object and selection handle at the given point.
+ def _drawObjectOutline(self, offsetX, offsetY):
+ """ Draw an outline of the currently selected object.
- 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
+ The selected object's outline is drawn at the object's position
+ plus the given offset.
- return None, handle_NONE
+ 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()
- 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
+ dc = wx.ClientDC(self.drawPanel)
+ self.drawPanel.PrepareDC(dc)
+ dc.BeginDrawing()
+ dc.SetPen(wx.BLACK_DASHED_PEN)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.SetLogicalFunction(wx.INVERT)
+ dc.DrawRectangle(position.x + offsetX, position.y + offsetY,
+ size.width, size.height)
- 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()
+ dc.EndDrawing()
+
+
+#============================================================================
+class DrawingTool(object):
+ """Base class for drawing tools"""
+
+ def __init__(self):
+ pass
+
+ def getDefaultCursor(self):
+ """Return the cursor to use by default which this drawing tool is selected"""
+ return wx.STANDARD_CURSOR
+
+ def draw(self,dc):
+ pass
+
+
+ def onMouseEvent(self,parent, event):
+ """Mouse events passed in from the parent.
+
+ Returns True if the event is handled by the tool,
+ False if the canvas can try to use it.
+ """
+ event.Skip()
+ return False
#----------------------------------------------------------------------------
+class SelectDrawingTool(DrawingTool):
+ """Represents the tool for selecting things"""
+
+ def __init__(self):
+ self.curHandle = None
+ self.curObject = None
+ self.objModified = False
+ self.startPt = None
+ self.curPt = None
+
+ def getDefaultCursor(self):
+ """Return the cursor to use by default which this drawing tool is selected"""
+ return wx.STANDARD_CURSOR
+
+ def draw(self, dc):
+ if self._doingRectSelection():
+ dc.SetPen(wx.BLACK_DASHED_PEN)
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ x = [self.startPt.x, self.curPt.x]; x.sort()
+ y = [self.startPt.y, self.curPt.y]; y.sort()
+ dc.DrawRectangle(x[0],y[0], x[1]-x[0],y[1]-y[0])
+
+
+ def onMouseEvent(self,parent, event):
+ handlers = { wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
+ wx.EVT_MOTION.evtType[0]: self.onMouseMotion,
+ wx.EVT_LEFT_UP.evtType[0]: self.onMouseLeftUp,
+ wx.EVT_LEFT_DCLICK.evtType[0]: self.onMouseLeftDClick }
+ handler = handlers.get(event.GetEventType())
+ if handler is not None:
+ return handler(parent,event)
+ else:
+ event.Skip()
+ return False
+
+ def onMouseLeftDown(self,parent,event):
+ mousePt = wx.Point(event.X,event.Y)
+ obj, handle = parent.getObjectAndSelectionHandleAt(mousePt)
+ self.startPt = mousePt
+ self.curPt = mousePt
+ if obj is not None and handle is not None:
+ self.curObject = obj
+ self.curHandle = handle
+ else:
+ self.curObject = None
+ self.curHandle = None
+
+ obj = parent.getObjectAt(mousePt)
+ if self.curObject is None and obj is not None:
+ self.curObject = obj
+ self.dragDelta = obj.position-mousePt
+ self.curHandle = None
+ parent.select(obj, event.ShiftDown())
+
+ return True
+
+ def onMouseMotion(self,parent,event):
+ if not event.LeftIsDown(): return
+
+ self.curPt = wx.Point(event.X,event.Y)
+
+ obj,handle = self.curObject,self.curHandle
+ if self._doingDragHandle():
+ self._prepareToModify(parent)
+ obj.moveHandle(handle,event.X,event.Y)
+ parent.requestRedraw()
+
+ elif self._doingDragObject():
+ self._prepareToModify(parent)
+ obj.position = self.curPt + self.dragDelta
+ parent.requestRedraw()
+
+ elif self._doingRectSelection():
+ parent.requestRedraw()
+
+ return True
+
+ def onMouseLeftUp(self,parent,event):
+
+ obj,handle = self.curObject,self.curHandle
+ if self._doingDragHandle():
+ obj.moveHandle(handle,event.X,event.Y)
+ obj.finalizeHandle(handle,event.X,event.Y)
+
+ elif self._doingDragObject():
+ curPt = wx.Point(event.X,event.Y)
+ obj.position = curPt + self.dragDelta
+
+ elif self._doingRectSelection():
+ x = [event.X, self.startPt.x]
+ y = [event.Y, self.startPt.y]
+ x.sort()
+ y.sort()
+ parent.selectByRectangle(x[0],y[0],x[1]-x[0],y[1]-y[0])
+
+
+ self.curObject = None
+ self.curHandle = None
+ self.curPt = None
+ self.startPt = None
+ self.objModified = False
+ parent.requestRedraw()
+
+ return True
+
+ def onMouseLeftDClick(self,parent,event):
+ event.Skip()
+ mousePt = wx.Point(event.X,event.Y)
+ obj = parent.getObjectAt(mousePt)
+ if obj and obj.hasPropertyEditor():
+ if obj.doPropertyEdit(parent):
+ parent.requestRedraw()
+ return True
+
+ return False
+
+
+ def _prepareToModify(self,parent):
+ if not self.objModified:
+ parent.saveUndoInfo()
+ self.objModified = True
+
+ def _doingRectSelection(self):
+ return self.curObject is None \
+ and self.startPt is not None \
+ and self.curPt is not None
+
+ def _doingDragObject(self):
+ return self.curObject is not None and self.curHandle is None
+
+ def _doingDragHandle(self):
+ return self.curObject is not None and self.curHandle is not None
+
+
+
+#----------------------------------------------------------------------------
+class LineDrawingTool(DrawingTool):
+ """Represents the tool for drawing lines"""
+
+ def __init__(self):
+ self.newObject = None
+ self.startPt = None
+
+
+ def getDefaultCursor(self):
+ """Return the cursor to use by default which this drawing tool is selected"""
+ return wx.StockCursor(wx.CURSOR_PENCIL)
+
+ def draw(self, dc):
+ if self.newObject is None: return
+ self.newObject.draw(dc,True)
+
+ def onMouseEvent(self,parent, event):
+ handlers = { wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
+ wx.EVT_MOTION.evtType[0]: self.onMouseMotion,
+ wx.EVT_LEFT_UP.evtType[0]: self.onMouseLeftUp }
+ handler = handlers.get(event.GetEventType())
+ if handler is not None:
+ return handler(parent,event)
+ else:
+ event.Skip()
+ return False
+
+ def onMouseLeftDown(self,parent, event):
+ self.startPt = wx.Point(event.GetX(), event.GetY())
+ self.newObject = None
+ event.Skip()
+ return True
+
+ def onMouseMotion(self,parent, event):
+ if not event.Dragging(): return
+
+ if self.newObject is None:
+ obj = LineDrawingObject(startPt=wx.Point(0,0),
+ penColour=parent.penColour,
+ fillColour=parent.fillColour,
+ lineSize=parent.lineSize,
+ position=wx.Point(event.X,event.Y))
+ self.newObject = obj
+
+ self._updateObjFromEvent(self.newObject, event)
-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.
- """
+ parent.requestRedraw()
+ event.Skip()
+ return True
+
+ def onMouseLeftUp(self,parent, event):
+
+ if self.newObject is None:
+ return
+
+ self._updateObjFromEvent(self.newObject,event)
+
+ parent.addObject(self.newObject)
+
+ self.newObject = None
+ self.startPt = None
+
+ event.Skip()
+ return True
+
+
+ def _updateObjFromEvent(self,obj,event):
+ obj.setEndPt(wx.Point(event.X,event.Y))
+
+
+#----------------------------------------------------------------------------
+class RectDrawingTool(DrawingTool):
+ """Represents the tool for drawing rectangles"""
+
+ def __init__(self):
+ self.newObject = None
+
+ def getDefaultCursor(self):
+ """Return the cursor to use by default which this drawing tool is selected"""
+ return wx.CROSS_CURSOR
+
+ def draw(self, dc):
+ if self.newObject is None: return
+ self.newObject.draw(dc,True)
+
+
+ def onMouseEvent(self,parent, event):
+ handlers = { wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
+ wx.EVT_MOTION.evtType[0]: self.onMouseMotion,
+ wx.EVT_LEFT_UP.evtType[0]: self.onMouseLeftUp }
+ handler = handlers.get(event.GetEventType())
+ if handler is not None:
+ return handler(parent,event)
+ else:
+ event.Skip()
+ return False
+
+ def onMouseLeftDown(self,parent, event):
+ self.startPt = wx.Point(event.GetX(), event.GetY())
+ self.newObject = None
+ event.Skip()
+ return True
+
+ def onMouseMotion(self,parent, event):
+ if not event.Dragging(): return
+
+ if self.newObject is None:
+ obj = RectDrawingObject(penColour=parent.penColour,
+ fillColour=parent.fillColour,
+ lineSize=parent.lineSize)
+ self.newObject = obj
+
+ self._updateObjFromEvent(self.newObject, event)
+
+ parent.requestRedraw()
+ event.Skip()
+ return True
+
+ def onMouseLeftUp(self,parent, event):
+
+ if self.newObject is None:
+ return
+
+ self._updateObjFromEvent(self.newObject,event)
+
+ parent.addObject(self.newObject)
+
+ self.newObject = None
+
+ event.Skip()
+ return True
+
+
+ def _updateObjFromEvent(self,obj,event):
+ x = [event.X, self.startPt.x]
+ y = [event.Y, self.startPt.y]
+ x.sort()
+ y.sort()
+ width = x[1]-x[0]
+ height = y[1]-y[0]
+
+ obj.setPosition(wx.Point(x[0],y[0]))
+ obj.setSize(wx.Size(width,height))
+
+
+
+
+#----------------------------------------------------------------------------
+class EllipseDrawingTool(DrawingTool):
+ """Represents the tool for drawing ellipses"""
+
+ def getDefaultCursor(self):
+ """Return the cursor to use by default which this drawing tool is selected"""
+ return wx.CROSS_CURSOR
+
+
+ def __init__(self):
+ self.newObject = None
+
+ def getDefaultCursor(self):
+ """Return the cursor to use by default which this drawing tool is selected"""
+ return wx.CROSS_CURSOR
+
+ def draw(self, dc):
+ if self.newObject is None: return
+ self.newObject.draw(dc,True)
+
+
+ def onMouseEvent(self,parent, event):
+ handlers = { wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
+ wx.EVT_MOTION.evtType[0]: self.onMouseMotion,
+ wx.EVT_LEFT_UP.evtType[0]: self.onMouseLeftUp }
+ handler = handlers.get(event.GetEventType())
+ if handler is not None:
+ return handler(parent,event)
+ else:
+ event.Skip()
+ return False
+
+ def onMouseLeftDown(self,parent, event):
+ self.startPt = wx.Point(event.GetX(), event.GetY())
+ self.newObject = None
+ event.Skip()
+ return True
+
+ def onMouseMotion(self,parent, event):
+ if not event.Dragging(): return
+
+ if self.newObject is None:
+ obj = EllipseDrawingObject(penColour=parent.penColour,
+ fillColour=parent.fillColour,
+ lineSize=parent.lineSize)
+ self.newObject = obj
+
+ self._updateObjFromEvent(self.newObject, event)
+
+ parent.requestRedraw()
+ event.Skip()
+ return True
+
+ def onMouseLeftUp(self,parent, event):
+
+ if self.newObject is None:
+ return
+
+ self._updateObjFromEvent(self.newObject,event)
+
+ parent.addObject(self.newObject)
+
+ self.newObject = None
+
+ event.Skip()
+ return True
+
+
+ def _updateObjFromEvent(self,obj,event):
+ x = [event.X, self.startPt.x]
+ y = [event.Y, self.startPt.y]
+ x.sort()
+ y.sort()
+ width = x[1]-x[0]
+ height = y[1]-y[0]
+
+ obj.setPosition(wx.Point(x[0],y[0]))
+ obj.setSize(wx.Size(width,height))
+
+
+#----------------------------------------------------------------------------
+class PolygonDrawingTool(DrawingTool):
+ """Represents the tool for drawing polygons"""
+
+ def __init__(self):
+ self.newObject = None
+
+ def getDefaultCursor(self):
+ """Return the cursor to use by default which this drawing tool is selected"""
+ return wx.CROSS_CURSOR
+
+
+ def draw(self, dc):
+ if self.newObject is None: return
+ self.newObject.draw(dc,True)
+
+
+ def onMouseEvent(self,parent, event):
+ handlers = { wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
+ wx.EVT_MOTION.evtType[0]: self.onMouseMotion,
+ wx.EVT_LEFT_UP.evtType[0]: self.onMouseLeftUp,
+ wx.EVT_LEFT_DCLICK.evtType[0]:self.onMouseLeftDClick }
+ handler = handlers.get(event.GetEventType())
+ if handler is not None:
+ return handler(parent,event)
+ else:
+ event.Skip()
+ return False
+
+ def onMouseLeftDown(self,parent, event):
+ event.Skip()
+ self.startPt = (event.GetX(), event.GetY())
+ if self.newObject is None:
+ obj = PolygonDrawingObject(points=[(0,0)],penColour=parent.penColour,
+ fillColour=parent.fillColour,
+ lineSize=parent.lineSize,
+ position=wx.Point(event.X, event.Y))
+ obj.addPoint(event.X,event.Y)
+ self.newObject = obj
+ else:
+ CLOSE_THRESH=3
+ pt0 = self.newObject.getPoint(0)
+ if abs(pt0[0]-event.X)<CLOSE_THRESH and abs(pt0[1]-event.Y)<CLOSE_THRESH:
+ self.newObject.popPoint()
+ parent.addObject(self.newObject)
+ self.newObject = None
+ else:
+ self.newObject.addPoint(event.X,event.Y)
+
+ return True
+
+ def onMouseMotion(self,parent, event):
+
+ event.Skip()
+ if self.newObject:
+ self.newObject.movePoint(-1, event.X, event.Y)
+ parent.requestRedraw()
+ return True
+
+ return False
+
+ def onMouseLeftDClick(self,parent,event):
+ event.Skip()
+ if self.newObject:
+ CLOSE_THRESH=3
+ pt0 = self.newObject.getPoint(0)
+ if abs(pt0[0]-event.X)<CLOSE_THRESH and abs(pt0[1]-event.Y)<CLOSE_THRESH:
+ self.newObject.popPoint()
+ self.newObject.popPoint()
+ parent.addObject(self.newObject)
+ self.newObject = None
+
+ return True
+
+ def onMouseLeftUp(self,parent, event):
+ event.Skip()
+ return True
+
+
+
+
+#----------------------------------------------------------------------------
+class ScribbleDrawingTool(DrawingTool):
+ """Represents the tool for drawing scribble drawing objects"""
+
+ def __init__(self):
+ self.newObject = None
+
+ def getDefaultCursor(self):
+ """Return the cursor to use by default which this drawing tool is selected"""
+ return wx.StockCursor(wx.CURSOR_PENCIL)
+
+ def draw(self, dc):
+ if self.newObject is None: return
+ self.newObject.draw(dc,True)
+
+
+ def onMouseEvent(self,parent, event):
+ handlers = { wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
+ wx.EVT_MOTION.evtType[0]: self.onMouseMotion,
+ wx.EVT_LEFT_UP.evtType[0]: self.onMouseLeftUp
+ }
+ handler = handlers.get(event.GetEventType())
+ if handler is not None:
+ return handler(parent,event)
+ else:
+ event.Skip()
+ return False
+
+ def onMouseLeftDown(self,parent, event):
+ event.Skip()
+ obj = ScribbleDrawingObject(points=[(0,0)],penColour=parent.penColour,
+ fillColour=parent.fillColour,
+ lineSize=parent.lineSize,
+ position=wx.Point(event.X, event.Y))
+ self.newObject = obj
+ return True
+
+ def onMouseMotion(self,parent, event):
+ event.Skip()
+ if self.newObject:
+ self.newObject.addPoint(event.X,event.Y)
+ parent.requestRedraw()
+ return True
+
+ return False
+
+ def onMouseLeftUp(self,parent, event):
+ event.Skip()
+ if self.newObject:
+ parent.addObject(self.newObject)
+ self.newObject = None
+ return True
+
+
+
+
+
+#----------------------------------------------------------------------------
+class TextDrawingTool(DrawingTool):
+ """Represents the tool for drawing text"""
+
+ def getDefaultCursor(self):
+ """Return the cursor to use by default which this drawing tool is selected"""
+ return wx.StockCursor(wx.CURSOR_IBEAM)
+
+ def onMouseEvent(self,parent, event):
+ handlers = { #wx.EVT_LEFT_DOWN.evtType[0]: self.onMouseLeftDown,
+ #wx.EVT_MOTION.evtType[0]: self.onMouseMotion,
+ wx.EVT_LEFT_UP.evtType[0]: self.onMouseLeftUp
+ }
+ handler = handlers.get(event.GetEventType())
+ if handler is not None:
+ return handler(parent,event)
+ else:
+ event.Skip()
+ return False
+
+ def onMouseLeftUp(self,parent, event):
+
+ editor = parent.getTextEditor()
+ editor.SetTitle("Create Text Object")
+ if editor.ShowModal() == wx.ID_CANCEL:
+ editor.Hide()
+ return True
+
+ obj = TextDrawingObject(position=wx.Point(event.X, event.Y))
+ editor.dialogToObject(obj)
+ editor.Hide()
+
+ parent.addObject(obj)
+
+ event.Skip()
+ return True
+
+
+
+#============================================================================
+class DrawingObject(object):
+ """ Base class for objects within the drawing panel.
+
+ A pySketch document consists of a front-to-back ordered list of
+ DrawingObjects. Each DrawingObject has the following properties:
+
+ '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.
+ """
# ==================
# == 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
+ def __init__(self, position=wx.Point(0, 0), size=wx.Size(0, 0),
+ penColour=wx.BLACK, fillColour=wx.WHITE, lineSize=1,
+ ):
+ """ Standard constructor.
+
+ The remaining parameters let you set various options for the newly
+ created DrawingObject.
+ """
+ # One must take great care with constructed default arguments
+ # like wx.Point(0,0) above. *EVERY* caller that uses the
+ # default will get the same instance. Thus, below we make a
+ # deep copy of those arguments with object defaults.
+
+ self.position = wx.Point(position.x,position.y)
+ self.size = wx.Size(size.x,size.y)
+ self.penColour = penColour
+ self.fillColour = fillColour
+ self.lineSize = lineSize
# =============================
# == 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]
+ """ Return a copy of the object's internal data.
+
+ This is used to save this DrawingObject to disk.
+ """
+ return [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]
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
+ """ 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.
+
+ Returns an iterator to any remaining data not consumed by
+ this base class method.
+ """
+ #data = copy.deepcopy(data) # Needed?
+
+ d = iter(data)
+ try:
+ self.position = wx.Point(d.next(), d.next())
+ self.size = wx.Size(d.next(), d.next())
+ self.penColour = wx.Colour(red=d.next(),
+ green=d.next(),
+ blue=d.next())
+ self.fillColour = wx.Colour(red=d.next(),
+ green=d.next(),
+ blue=d.next())
+ self.lineSize = d.next()
+ except StopIteration:
+ raise ValueError('Not enough data in setData call')
+
+ return d
+
+
+ def hasPropertyEditor(self):
+ return False
+
+ def doPropertyEdit(self, parent):
+ assert False, "Must be overridden if hasPropertyEditor returns True"
def setPosition(self, position):
- """ Set the origin (top-left corner) for this DrawingObject.
- """
- self.position = position
+ """ Set the origin (top-left corner) for this DrawingObject.
+ """
+ self.position = position
def getPosition(self):
- """ Return this DrawingObject's position.
- """
- return self.position
+ """ Return this DrawingObject's position.
+ """
+ return self.position
def setSize(self, size):
- """ Set the size for this DrawingObject.
- """
- self.size = size
+ """ Set the size for this DrawingObject.
+ """
+ self.size = size
def getSize(self):
- """ Return this DrawingObject's size.
- """
- return self.size
+ """ Return this DrawingObject's size.
+ """
+ return self.size
def setPenColour(self, colour):
- """ Set the pen colour used for this DrawingObject.
- """
- self.penColour = colour
+ """ Set the pen colour used for this DrawingObject.
+ """
+ self.penColour = colour
def getPenColour(self):
- """ Return this DrawingObject's pen colour.
- """
- return self.penColour
+ """ Return this DrawingObject's pen colour.
+ """
+ return self.penColour
def setFillColour(self, colour):
- """ Set the fill colour used for this DrawingObject.
- """
- self.fillColour = colour
+ """ Set the fill colour used for this DrawingObject.
+ """
+ self.fillColour = colour
def getFillColour(self):
- """ Return this DrawingObject's fill colour.
- """
- return self.fillColour
+ """ Return this DrawingObject's fill colour.
+ """
+ return self.fillColour
def setLineSize(self, lineSize):
- """ Set the linesize used for this DrawingObject.
- """
- self.lineSize = lineSize
+ """ Set the linesize used for this DrawingObject.
+ """
+ self.lineSize = lineSize
def getLineSize(self):
- """ Return this DrawingObject's line size.
- """
- return self.lineSize
+ """ Return this DrawingObject's line size.
+ """
+ return self.lineSize
+
+
+ # ============================
+ # == 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.
+ Drawing objects can use this to change the way selected objects
+ are drawn, however the actual drawing of selection handles
+ should be done in the 'drawHandles' method
+ """
+ if self.lineSize == 0:
+ dc.SetPen(wx.Pen(self.penColour, self.lineSize, wx.TRANSPARENT))
+ else:
+ dc.SetPen(wx.Pen(self.penColour, self.lineSize, wx.SOLID))
+ dc.SetBrush(wx.Brush(self.fillColour, wx.SOLID))
+
+ self._privateDraw(dc, self.position, selected)
+
+
+ def drawHandles(self, dc):
+ """Draw selection handles for this DrawingObject"""
+
+ # Default is to draw selection handles at all four corners.
+ dc.SetPen(wx.BLACK_PEN)
+ dc.SetBrush(wx.BLACK_BRUSH)
+
+ x,y = self.position
+ self._drawSelHandle(dc, x, y)
+ self._drawSelHandle(dc, x + self.size.width, y)
+ self._drawSelHandle(dc, x, y + self.size.height)
+ self._drawSelHandle(dc, x + self.size.width, y + self.size.height)
+
+
+ # =======================
+ # == 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
+
+ # Now things get tricky. There's no straightforward way of
+ # knowing whether the point is within an arbitrary 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.
+
+ # Subclasses can implement smarter faster versions of this.
+
+ bitmap = wx.EmptyBitmap(self.size.x + 10, self.size.y + 10)
+ dc = wx.MemoryDC()
+ dc.SelectObject(bitmap)
+ dc.BeginDrawing()
+ dc.SetBackground(wx.WHITE_BRUSH)
+ dc.Clear()
+ dc.SetPen(wx.Pen(wx.BLACK, self.lineSize + 5, wx.SOLID))
+ dc.SetBrush(wx.BLACK_BRUSH)
+ self._privateDraw(dc, wx.Point(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
+
+ handle_TOP = 0
+ handle_BOTTOM = 1
+ handle_LEFT = 0
+ handle_RIGHT = 1
+
+ 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.
+ """
+ # Default implementation assumes selection handles at all four bbox corners.
+ # Return a list so we can modify the contents later in moveHandle()
+ if self._pointInSelRect(x, y, self.position.x, self.position.y):
+ return [self.handle_TOP, self.handle_LEFT]
+ elif self._pointInSelRect(x, y, self.position.x + self.size.width,
+ self.position.y):
+ return [self.handle_TOP, self.handle_RIGHT]
+ elif self._pointInSelRect(x, y, self.position.x,
+ self.position.y + self.size.height):
+ return [self.handle_BOTTOM, self.handle_LEFT]
+ elif self._pointInSelRect(x, y, self.position.x + self.size.width,
+ self.position.y + self.size.height):
+ return [self.handle_BOTTOM, self.handle_RIGHT]
+ else:
+ return None
+
+ def moveHandle(self, handle, x, y):
+ """ Move the specified selection handle to given canvas location.
+ """
+ assert handle is not None
+
+ # Default implementation assumes selection handles at all four bbox corners.
+ pt = wx.Point(x,y)
+ x,y = self.position
+ w,h = self.size
+ if handle[0] == self.handle_TOP:
+ if handle[1] == self.handle_LEFT:
+ dpos = pt - self.position
+ self.position = pt
+ self.size.width -= dpos.x
+ self.size.height -= dpos.y
+ else:
+ dx = pt.x - ( x + w )
+ dy = pt.y - ( y )
+ self.position.y = pt.y
+ self.size.width += dx
+ self.size.height -= dy
+ else: # BOTTOM
+ if handle[1] == self.handle_LEFT:
+ dx = pt.x - ( x )
+ dy = pt.y - ( y + h )
+ self.position.x = pt.x
+ self.size.width -= dx
+ self.size.height += dy
+ else:
+ dpos = pt - self.position
+ dpos.x -= w
+ dpos.y -= h
+ self.size.width += dpos.x
+ self.size.height += dpos.y
+
+
+ # Finally, normalize so no negative widths or heights.
+ # And update the handle variable accordingly.
+ if self.size.height<0:
+ self.position.y += self.size.height
+ self.size.height = -self.size.height
+ handle[0] = 1-handle[0]
+
+ if self.size.width<0:
+ self.position.x += self.size.width
+ self.size.width = -self.size.width
+ handle[1] = 1-handle[1]
+
+
+
+ def finalizeHandle(self, handle, x, y):
+ pass
+
+
+ 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
+
+ # =====================
+ # == 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.
+ """
+ pass
+
+ 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 LineDrawingObject(DrawingObject):
+ """ DrawingObject subclass that represents one line segment.
+
+ Adds the following members to the base DrawingObject:
+ 'startPt' The point, relative to the object's position, where
+ the line starts.
+ 'endPt' The point, relative to the object's position, where
+ the line ends.
+ """
+
+ def __init__(self, startPt=wx.Point(0,0), endPt=wx.Point(0,0), *varg, **kwarg):
+ DrawingObject.__init__(self, *varg, **kwarg)
+
+ self.startPt = wx.Point(startPt.x,startPt.y)
+ self.endPt = wx.Point(endPt.x,endPt.y)
+
+ # ============================
+ # == Object Drawing Methods ==
+ # ============================
+
+ def drawHandles(self, dc):
+ """Draw selection handles for this DrawingObject"""
+
+ dc.SetPen(wx.BLACK_PEN)
+ dc.SetBrush(wx.BLACK_BRUSH)
+
+ x,y = self.position
+ # Draw selection handles at the start and end points.
+ self._drawSelHandle(dc, x + self.startPt.x, y + self.startPt.y)
+ self._drawSelHandle(dc, x + self.endPt.x, y + self.endPt.y)
+
+
+
+ # =======================
+ # == Selection Methods ==
+ # =======================
+
+
+ handle_START_POINT = 1
+ handle_END_POINT = 2
+
+ 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.
+ """
+ # 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 self.handle_START_POINT
+ elif self._pointInSelRect(x, y, self.position.x + self.endPt.x,
+ self.position.y + self.endPt.y):
+ return self.handle_END_POINT
+ else:
+ return None
+
+ def moveHandle(self, handle, x, y):
+ """Move the handle to specified handle to the specified canvas coordinates
+ """
+ ptTrans = wx.Point(x-self.position.x, y-self.position.y)
+ if handle == self.handle_START_POINT:
+ self.startPt = ptTrans
+ elif handle == self.handle_END_POINT:
+ self.endPt = ptTrans
+ else:
+ raise ValueError("Bad handle type for a line")
+
+ self._updateBoundingBox()
+
+ # =============================
+ # == Object Property Methods ==
+ # =============================
+
+ def getData(self):
+ """ Return a copy of the object's internal data.
+
+ This is used to save this DrawingObject to disk.
+ """
+ # get the basics
+ data = DrawingObject.getData(self)
+ # add our specifics
+ data += [self.startPt.x, self.startPt.y,
+ self.endPt.x, self.endPt.y]
+ return data
+
+ 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?
+
+ d = DrawingObject.setData(self, data)
+
+ try:
+ self.startPt = wx.Point(d.next(), d.next())
+ self.endPt = wx.Point(d.next(), d.next())
+ except StopIteration:
+ raise ValueError('Not enough data in setData call')
+
+ return d
def setStartPt(self, startPt):
- """ Set the starting point for this line DrawingObject.
- """
- self.startPt = startPt
+ """ Set the starting point for this line DrawingObject.
+ """
+ self.startPt = startPt - self.position
+ self._updateBoundingBox()
def getStartPt(self):
- """ Return the starting point for this line DrawingObject.
- """
- return self.startPt
+ """ Return the starting point for this line DrawingObject.
+ """
+ return self.startPt + self.position
def setEndPt(self, endPt):
- """ Set the ending point for this line DrawingObject.
- """
- self.endPt = endPt
+ """ Set the ending point for this line DrawingObject.
+ """
+ self.endPt = endPt - self.position
+ self._updateBoundingBox()
def getEndPt(self):
- """ Return the ending point for this line DrawingObject.
- """
- return self.endPt
+ """ Return the ending point for this line DrawingObject.
+ """
+ return self.endPt + self.position
- def setText(self, text):
- """ Set the text for this DrawingObject.
- """
- self.text = text
+ # =====================
+ # == Private Methods ==
+ # =====================
- def getText(self):
- """ Return this DrawingObject's text.
- """
- return self.text
+ 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.
+ """
+ dc.DrawLine(position.x + self.startPt.x,
+ position.y + self.startPt.y,
+ position.x + self.endPt.x,
+ position.y + self.endPt.y)
+
+ def _updateBoundingBox(self):
+ x = [self.startPt.x, self.endPt.x]; x.sort()
+ y = [self.startPt.y, self.endPt.y]; y.sort()
+
+ dp = wx.Point(-x[0],-y[0])
+ self.position.x += x[0]
+ self.position.y += y[0]
+ self.size.width = x[1]-x[0]
+ self.size.height = y[1]-y[0]
+
+ self.startPt += dp
+ self.endPt += dp
+
+#----------------------------------------------------------------------------
+class PolygonDrawingObject(DrawingObject):
+ """ DrawingObject subclass that represents a poly-line or polygon
+ """
+ def __init__(self, points=[], *varg, **kwarg):
+ DrawingObject.__init__(self, *varg, **kwarg)
+ self.points = list(points)
+ # =======================
+ # == Selection Methods ==
+ # =======================
- def setTextFont(self, font):
- """ Set the typeface for this text DrawingObject.
- """
- self.textFont = font
+ 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.
+ """
+ # We have selection handles at the start and end points.
+ for i,p in enumerate(self.points):
+ if self._pointInSelRect(x, y,
+ self.position.x + p[0],
+ self.position.y + p[1]):
+ return i+1
- def getTextFont(self):
- """ Return this text DrawingObject's typeface.
- """
- return self.textFont
+ return None
+
+ def addPoint(self, x,y):
+ self.points.append((x-self.position.x,y-self.position.y))
+ self._updateBoundingBox()
- def setTextSize(self, size):
- """ Set the point size for this text DrawingObject.
- """
- self.textSize = size
+ def getPoint(self, idx):
+ x,y = self.points[idx]
+ return (x+self.position.x,y+self.position.y)
+ def movePoint(self, idx, x,y):
+ self.points[idx] = (x-self.position.x,y-self.position.y)
+ self._updateBoundingBox()
+
+ def popPoint(self, idx=-1):
+ self.points.pop(idx)
+ self._updateBoundingBox()
+
+ # =====================
+ # == Drawing Methods ==
+ # =====================
- def getTextSize(self):
- """ Return this text DrawingObject's text size.
- """
- return self.textSize
+ def drawHandles(self, dc):
+ """Draw selection handles for this DrawingObject"""
+ dc.SetPen(wx.BLACK_PEN)
+ dc.SetBrush(wx.BLACK_BRUSH)
- def setTextBoldface(self, boldface):
- """ Set the boldface flag for this text DrawingObject.
- """
- self.textBoldface = boldface
+ x,y = self.position
+ # Draw selection handles at the start and end points.
+ for p in self.points:
+ self._drawSelHandle(dc, x + p[0], y + p[1])
+ def moveHandle(self, handle, x, y):
+ """Move the specified handle"""
+ self.movePoint(handle-1,x,y)
- def getTextBoldface(self):
- """ Return this text DrawingObject's boldface flag.
- """
- return self.textBoldface
+ # =============================
+ # == Object Property Methods ==
+ # =============================
- def setTextItalic(self, italic):
- """ Set the italic flag for this text DrawingObject.
- """
- self.textItalic = italic
+ def getData(self):
+ """ Return a copy of the object's internal data.
+ This is used to save this DrawingObject to disk.
+ """
+ # get the basics
+ data = DrawingObject.getData(self)
+ # add our specifics
+ data += [list(self.points)]
- def getTextItalic(self):
- """ Return this text DrawingObject's italic flag.
- """
- return self.textItalic
+ return data
- def setTextUnderline(self, underline):
- """ Set the underling flag for this text DrawingObject.
- """
- self.textUnderline = underline
+ 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?
+ d = DrawingObject.setData(self, data)
- def getTextUnderline(self):
- """ Return this text DrawingObject's underline flag.
- """
- return self.textUnderline
+ try:
+ self.points = d.next()
+ except StopIteration:
+ raise ValueError('Not enough data in setData call')
- # ============================
- # == Object Drawing Methods ==
- # ============================
+ return d
+
- 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)
+ # =====================
+ # == 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.
+ """
+ dc.DrawPolygon(self.points, position.x, position.y)
+
+ def _updateBoundingBox(self):
+ x = min([p[0] for p in self.points])
+ y = min([p[1] for p in self.points])
+ x2 = max([p[0] for p in self.points])
+ y2 = max([p[1] for p in self.points])
+ dx = -x
+ dy = -y
+ self.position.x += x
+ self.position.y += y
+ self.size.width = x2-x
+ self.size.height = y2-y
+ # update coords also because they're relative to self.position
+ for i,p in enumerate(self.points):
+ self.points[i] = (p[0]+dx,p[1]+dy)
+
+
+#----------------------------------------------------------------------------
+class ScribbleDrawingObject(DrawingObject):
+ """ DrawingObject subclass that represents a poly-line or polygon
+ """
+ def __init__(self, points=[], *varg, **kwarg):
+ DrawingObject.__init__(self, *varg, **kwarg)
+ self.points = list(points)
# =======================
# == 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 addPoint(self, x,y):
+ self.points.append((x-self.position.x,y-self.position.y))
+ self._updateBoundingBox()
+ def getPoint(self, idx):
+ x,y = self.points[idx]
+ return (x+self.position.x,y+self.position.y)
- 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 movePoint(self, idx, x,y):
+ self.points[idx] = (x-self.position.x,y-self.position.y)
+ self._updateBoundingBox()
+ def popPoint(self, idx=-1):
+ self.points.pop(idx)
+ self._updateBoundingBox()
- 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
+
+ # =============================
+ # == Object Property Methods ==
+ # =============================
+
+ def getData(self):
+ """ Return a copy of the object's internal data.
+
+ This is used to save this DrawingObject to disk.
+ """
+ # get the basics
+ data = DrawingObject.getData(self)
+ # add our specifics
+ data += [list(self.points)]
+
+ return data
+
+
+ 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?
+ d = DrawingObject.setData(self, data)
+
+ try:
+ self.points = d.next()
+ except StopIteration:
+ raise ValueError('Not enough data in setData call')
+
+ return d
+
# =====================
- # == Utility Methods ==
+ # == 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.
+ """
+ dc.SetBrush(wx.TRANSPARENT_BRUSH)
+ dc.DrawLines(self.points, position.x, position.y)
+
+ def _updateBoundingBox(self):
+ x = min([p[0] for p in self.points])
+ y = min([p[1] for p in self.points])
+ x2 = max([p[0] for p in self.points])
+ y2 = max([p[1] for p in self.points])
+ dx = -x
+ dy = -y
+ self.position = wx.Point(self.position.x + x,self.position.y + y)
+ self.size = wx.Size(x2-x, y2-y)
+ #self.position.x += x
+ #self.position.y += y
+ #self.size.width = x2-x
+ #self.size.height = y2-y
+ # update coords also because they're relative to self.position
+ for i,p in enumerate(self.points):
+ self.points[i] = (p[0]+dx,p[1]+dy)
- def fitToText(self):
- """ Resize a text DrawingObject so that it fits it's text exactly.
- """
- if self.type != obj_TEXT: return
+#----------------------------------------------------------------------------
+class RectDrawingObject(DrawingObject):
+ """ DrawingObject subclass that represents an axis-aligned rectangle.
+ """
+ def __init__(self, *varg, **kwarg):
+ DrawingObject.__init__(self, *varg, **kwarg)
+
+ def objectContainsPoint(self, x, y):
+ """ Returns True iff this object contains the given point.
- 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)
+ This is used to determine if the user clicked on the object.
+ """
+ # Firstly, ignore any points outside of the object's bounds.
- dummyWindow = wxFrame(None, -1, "")
- dummyWindow.SetFont(font)
- width, height = dummyWindow.GetTextExtent(self.text)
- dummyWindow.Destroy()
+ 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
- self.size = wxSize(width, height)
+ # Rectangles are easy -- they're always selected if the
+ # point is within their bounds.
+ return True
# =====================
# == 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)
+ """ 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.
+ """
+ dc.DrawRectangle(position.x, position.y,
+ self.size.width, 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)
+#----------------------------------------------------------------------------
+class EllipseDrawingObject(DrawingObject):
+ """ DrawingObject subclass that represents an axis-aligned ellipse.
+ """
+ def __init__(self, *varg, **kwarg):
+ DrawingObject.__init__(self, *varg, **kwarg)
+
+ # =====================
+ # == 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.
+ """
+ dc.DrawEllipse(position.x, position.y,
+ self.size.width, self.size.height)
+
+
+
+
+#----------------------------------------------------------------------------
+class TextDrawingObject(DrawingObject):
+ """ DrawingObject subclass that holds text.
+
+ Adds the following members to the base DrawingObject:
+ 'text' The object's text (obj_TEXT objects only).
+ 'textFont' The text object's font name.
+ """
+
+ def __init__(self, text=None, *varg, **kwarg):
+ DrawingObject.__init__(self, *varg, **kwarg)
+
+ self.text = text
+ self.textFont = wx.SystemSettings_GetFont(wx.SYS_DEFAULT_GUI_FONT)
+
+
+ # =============================
+ # == Object Property Methods ==
+ # =============================
+
+ def getData(self):
+ """ Return a copy of the object's internal data.
+
+ This is used to save this DrawingObject to disk.
+ """
+ # get the basics
+ data = DrawingObject.getData(self)
+ # add our specifics
+ data += [self.text, self.textFont.GetNativeFontInfoDesc()]
+
+ return data
+
+
+ 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.
+ """
+ d = DrawingObject.setData(self, data)
+
+ try:
+ self.text = d.next()
+ desc = d.next()
+ self.textFont = wx.FontFromNativeInfoString(desc)
+ except StopIteration:
+ raise ValueError('Not enough data in setData call')
+
+ return d
+
+
+ def hasPropertyEditor(self):
+ return True
+
+ def doPropertyEdit(self, parent):
+ editor = parent.getTextEditor()
+ editor.SetTitle("Edit Text Object")
+ editor.objectToDialog(self)
+ if editor.ShowModal() == wx.ID_CANCEL:
+ editor.Hide()
+ return False
+
+ parent.saveUndoInfo()
+
+ editor.dialogToObject(self)
+ editor.Hide()
+
+ return True
+
+
+ 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 setFont(self, font):
+ """ Set the font for this text DrawingObject.
+ """
+ self.textFont = font
+
+
+ def getFont(self):
+ """ Return this text DrawingObject's font.
+ """
+ return self.textFont
+
+
+
+ # ============================
+ # == 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.
+ """
+ dc.SetTextForeground(self.penColour)
+ dc.SetTextBackground(self.fillColour)
+
+ self._privateDraw(dc, self.position, selected)
+
+ 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
+
+ # Text is easy -- it's always selected if the
+ # point is within its bounds.
+ return True
+
+
+ def fitToText(self):
+ """ Resize a text DrawingObject so that it fits it's text exactly.
+ """
+
+ dummyWindow = wx.Frame(None, -1, "")
+ dummyWindow.SetFont(self.textFont)
+ width, height = dummyWindow.GetTextExtent(self.text)
+ dummyWindow.Destroy()
+
+ self.size = wx.Size(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.
+ """
+ dc.SetFont(self.textFont)
+ dc.DrawText(self.text, position.x, position.y)
- 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 ToolPaletteToggleX(wx.ToggleButton):
+ """ An icon appearing in the tool palette area of our sketching window.
+
+ Note that this is actually implemented as a wx.Bitmap rather
+ than as a wx.Icon. wx.Icon has a very specific meaning, and isn't
+ appropriate for this more general use.
+ """
+
+ def __init__(self, parent, iconID, iconName, toolTip, mode = wx.ITEM_NORMAL):
+ """ 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.
+ 'mode' is one of wx.ITEM_NORMAL, wx.ITEM_CHECK, wx.ITEM_RADIO
+
+ The icon name is used to get the appropriate bitmap for this icon.
+ """
+ bmp = wx.Bitmap("images/" + iconName + "Icon.bmp", wx.BITMAP_TYPE_BMP)
+ bmpsel = wx.Bitmap("images/" + iconName + "IconSel.bmp", wx.BITMAP_TYPE_BMP)
+
+ wx.ToggleButton.__init__(self, parent, iconID,
+ size=(bmp.GetWidth()+1, bmp.GetHeight()+1)
+ )
+ self.SetLabel( iconName )
+ self.SetToolTip(wx.ToolTip(toolTip))
+ #self.SetBitmapLabel(bmp)
+ #self.SetBitmapSelected(bmpsel)
+
+ self.iconID = iconID
+ self.iconName = iconName
-class ToolPaletteIcon(wxStaticBitmap):
+class ToolPaletteToggle(GenBitmapToggleButton):
""" 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.
+ Note that this is actually implemented as a wx.Bitmap rather
+ than as a wx.Icon. wx.Icon has a very specific meaning, and isn't
+ appropriate for this more general use.
"""
- def __init__(self, parent, iconID, iconName, toolTip):
- """ Standard constructor.
+ def __init__(self, parent, iconID, iconName, toolTip, mode = wx.ITEM_NORMAL):
+ """ 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.
+ 'mode' is one of wx.ITEM_NORMAL, wx.ITEM_CHECK, wx.ITEM_RADIO
- '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 = wx.Bitmap("images/" + iconName + "Icon.bmp", wx.BITMAP_TYPE_BMP)
+ bmpsel = wx.Bitmap("images/" + iconName + "IconSel.bmp", wx.BITMAP_TYPE_BMP)
- 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))
+ GenBitmapToggleButton.__init__(self, parent, iconID, bitmap=bmp,
+ size=(bmp.GetWidth()+1, bmp.GetHeight()+1),
+ style=wx.BORDER_NONE)
- self.iconID = iconID
- self.iconName = iconName
- self.isSelected = false
+ self.SetToolTip(wx.ToolTip(toolTip))
+ self.SetBitmapLabel(bmp)
+ self.SetBitmapSelected(bmpsel)
+ self.iconID = iconID
+ self.iconName = iconName
- def select(self):
- """ Select the icon.
- The icon's visual representation is updated appropriately.
- """
- if self.isSelected: return # Nothing to do!
+class ToolPaletteButton(GenBitmapButton):
+ """ An icon appearing in the tool palette area of our sketching window.
+
+ Note that this is actually implemented as a wx.Bitmap rather
+ than as a wx.Icon. wx.Icon has a very specific meaning, and isn't
+ appropriate for this more general use.
+ """
+
+ def __init__(self, parent, iconID, iconName, toolTip):
+ """ Standard constructor.
- bmp = wxBitmap("images/" + self.iconName + "IconSel.bmp",
- wxBITMAP_TYPE_BMP)
- self.SetBitmap(bmp)
- self.isSelected = true
+ '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 = wx.Bitmap("images/" + iconName + "Icon.bmp", wx.BITMAP_TYPE_BMP)
+ GenBitmapButton.__init__(self, parent, iconID, bitmap=bmp,
+ size=(bmp.GetWidth()+1, bmp.GetHeight()+1),
+ style=wx.BORDER_NONE)
+ self.SetToolTip(wx.ToolTip(toolTip))
+ self.SetBitmapLabel(bmp)
- def deselect(self):
- """ Deselect the icon.
+ self.iconID = iconID
+ self.iconName = iconName
- 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):
+class ToolOptionIndicator(wx.Window):
""" A visual indicator which shows the current tool options.
"""
def __init__(self, parent):
- """ Standard constructor.
- """
- wxWindow.__init__(self, parent, -1, wxDefaultPosition, wxSize(52, 32))
+ """ Standard constructor.
+ """
+ wx.Window.__init__(self, parent, -1, wx.DefaultPosition, wx.Size(52, 32))
- self.penColour = wxBLACK
- self.fillColour = wxWHITE
- self.lineSize = 1
+ self.penColour = wx.BLACK
+ self.fillColour = wx.WHITE
+ self.lineSize = 1
- EVT_PAINT(self, self.OnPaint)
+ # Win95 can only handle a 8x8 stipple bitmaps max
+ #self.stippleBitmap = wx.BitmapFromBits("\xf0"*4 + "\x0f"*4,8,8)
+ # ...but who uses Win95?
+ self.stippleBitmap = wx.BitmapFromBits("\xff\x00"*8+"\x00\xff"*8,16,16)
+ self.stippleBitmap.SetMask(wx.Mask(self.stippleBitmap))
+
+ self.Bind(wx.EVT_PAINT, self.onPaint)
def setPenColour(self, penColour):
- """ Set the indicator's current pen colour.
- """
- self.penColour = penColour
- self.Refresh()
+ """ 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()
+ """ 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()
+ """ Set the indicator's current pen colour.
+ """
+ self.lineSize = lineSize
+ self.Refresh()
+
+
+ def onPaint(self, event):
+ """ Paint our tool option indicator.
+ """
+ dc = wx.PaintDC(self)
+ dc.BeginDrawing()
+
+ dc.SetPen(wx.BLACK_PEN)
+ bgbrush = wx.Brush(wx.WHITE, wx.STIPPLE_MASK_OPAQUE)
+ bgbrush.SetStipple(self.stippleBitmap)
+ dc.SetTextForeground(wx.LIGHT_GREY)
+ dc.SetTextBackground(wx.WHITE)
+ dc.SetBrush(bgbrush)
+ dc.DrawRectangle(0, 0, self.GetSize().width,self.GetSize().height)
+
+ if self.lineSize == 0:
+ dc.SetPen(wx.Pen(self.penColour, self.lineSize, wx.TRANSPARENT))
+ else:
+ dc.SetPen(wx.Pen(self.penColour, self.lineSize, wx.SOLID))
+ dc.SetBrush(wx.Brush(self.fillColour, wx.SOLID))
+
+ size = self.GetSize()
+ ctrx = size.x/2
+ ctry = size.y/2
+ radius = min(size)//2 - 5
+ dc.DrawCircle(ctrx, ctry, radius)
+
+ dc.EndDrawing()
#----------------------------------------------------------------------------
-class EditTextObjectDialog(wxDialog):
+class EditTextObjectDialog(wx.Dialog):
""" Dialog box used to edit the properties of a text object.
- The user can edit the object's text, font, size, and text style.
+ 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))
+ """ Standard constructor.
+ """
+ wx.Dialog.__init__(self, parent, -1, title,
+ style=wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER)
- EVT_TEXT_ENTER(self, 1001, self._doEnter)
+ self.textCtrl = wx.TextCtrl(
+ self, 1001, "Enter text here", style=wx.TE_PROCESS_ENTER|wx.TE_RICH,
+ validator=TextObjectValidator()
+ )
+ extent = self.textCtrl.GetFullTextExtent("Hy")
+ lineHeight = extent[1] + extent[3]
+ self.textCtrl.SetSize(wx.Size(-1, lineHeight * 4))
+ self.curFont = self.textCtrl.GetFont()
+ self.curClr = wx.BLACK
- fonts = wxFontEnumerator()
- fonts.EnumerateFacenames()
- self.fontList = fonts.GetFacenames()
- self.fontList.sort()
+ self.Bind(wx.EVT_TEXT_ENTER, self._doEnter, id=1001)
- fontLabel = wxStaticText(self, -1, "Font:")
- self._setFontOptions(fontLabel, weight=wxBOLD)
+ fontBtn = wx.Button(self, -1, "Select Font...")
+ self.Bind(wx.EVT_BUTTON, self.OnSelectFont, fontBtn)
- self.fontCombo = wxComboBox(self, -1, "", wxDefaultPosition,
- wxDefaultSize, self.fontList,
- style = wxCB_READONLY)
- self.fontCombo.SetSelection(0) # Default to first available font.
+ gap = wx.LEFT | wx.TOP | wx.RIGHT
- self.sizeList = ["8", "9", "10", "12", "14", "16",
- "18", "20", "24", "32", "48", "72"]
+ self.okButton = wx.Button(self, wx.ID_OK, "&OK")
+ self.okButton.SetDefault()
+ self.cancelButton = wx.Button(self, wx.ID_CANCEL, "&Cancel")
- sizeLabel = wxStaticText(self, -1, "Size:")
- self._setFontOptions(sizeLabel, weight=wxBOLD)
+ btnSizer = wx.StdDialogButtonSizer()
- self.sizeCombo = wxComboBox(self, -1, "", wxDefaultPosition,
- wxDefaultSize, self.sizeList,
- style=wxCB_READONLY)
- self.sizeCombo.SetSelection(3) # Default to 12 point text.
+ btnSizer.Add(self.okButton, 0, gap, 5)
+ btnSizer.Add(self.cancelButton, 0, gap, 5)
- gap = wxLEFT | wxTOP | wxRIGHT
+ sizer = wx.BoxSizer(wx.VERTICAL)
+ sizer.Add(self.textCtrl, 1, gap | wx.EXPAND, 5)
+ sizer.Add(fontBtn, 0, gap | wx.ALIGN_RIGHT, 5)
+ sizer.Add((10, 10)) # Spacer.
+ btnSizer.Realize()
+ sizer.Add(btnSizer, 0, gap | wx.ALIGN_CENTRE, 5)
- 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.SetAutoLayout(True)
+ self.SetSizer(sizer)
+ sizer.Fit(self)
- self.boldCheckbox = wxCheckBox(self, -1, "Bold")
- self.italicCheckbox = wxCheckBox(self, -1, "Italic")
- self.underlineCheckbox = wxCheckBox(self, -1, "Underline")
+ self.textCtrl.SetFocus()
- 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)
+ def OnSelectFont(self, evt):
+ """Shows the font dialog and sets the font of the sample text"""
+ data = wx.FontData()
+ data.EnableEffects(True)
+ data.SetColour(self.curClr) # set colour
+ data.SetInitialFont(self.curFont)
- self.okButton = wxButton(self, wxID_OK, "OK")
- self.cancelButton = wxButton(self, wxID_CANCEL, "Cancel")
+ dlg = wx.FontDialog(self, data)
+
+ if dlg.ShowModal() == wx.ID_OK:
+ data = dlg.GetFontData()
+ font = data.GetChosenFont()
+ colour = data.GetColour()
- btnSizer = wxBoxSizer(wxHORIZONTAL)
- btnSizer.Add(self.okButton, 0, gap, 5)
- btnSizer.Add(self.cancelButton, 0, gap, 5)
+ self.curFont = font
+ self.curClr = colour
- 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.textCtrl.SetFont(font)
+ # Update dialog for the new height of the text
+ self.GetSizer().Fit(self)
- self.SetAutoLayout(true)
- self.SetSizer(sizer)
- sizer.Fit(self)
-
- self.textCtrl.SetFocus()
+ dlg.Destroy()
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
+ """ 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.sizeList)):
- if self.sizeList[i] == str(obj.getTextSize()):
- self.sizeCombo.SetSelection(i)
- break
+ self.curFont = obj.getFont()
+ self.textCtrl.SetFont(self.curFont)
- 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()
+ """ Copy the properties from the dialog box into the given text object.
+ """
+ obj.setText(self.textCtrl.GetValue())
+ obj.setFont(self.curFont)
+ 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.
+ """ Respond to the user hitting the ENTER key.
- We simulate clicking on the "OK" button.
- """
- if self.Validate(): self.Show(false)
+ We simulate clicking on the "OK" button.
+ """
+ if self.Validate(): self.Show(False)
#----------------------------------------------------------------------------
-class TextObjectValidator(wxPyValidator):
+class TextObjectValidator(wx.PyValidator):
""" This validator is used to ensure that the user has entered something
- into the text object editor dialog's text field.
+ into the text object editor dialog's text field.
"""
def __init__(self):
- """ Standard constructor.
- """
- wxPyValidator.__init__(self)
+ """ Standard constructor.
+ """
+ wx.PyValidator.__init__(self)
def Clone(self):
- """ Standard cloner.
+ """ Standard cloner.
- Note that every validator must implement the Clone() method.
- """
- return TextObjectValidator()
+ 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()
+ """ Validate the contents of the given text control.
+ """
+ textCtrl = self.GetWindow()
+ text = textCtrl.GetValue()
- if len(text) == 0:
- wxMessageBox("A text object must contain some text!", "Error")
- return false
- else:
- return true
+ if len(text) == 0:
+ wx.MessageBox("A text object must contain some text!", "Error")
+ return False
+ else:
+ return True
def TransferToWindow(self):
- """ Transfer data from validator to window.
+ """ 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.
+ 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 wx.Dialog from complaining.
def TransferFromWindow(self):
- """ Transfer data from window to validator.
+ """ 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.
+ 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 wx.Dialog 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.
+ 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.
+ """ 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.
+ """ 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 = ""
+
+ f = open("errors.txt", "a")
+ f.write(s)
+ f.close()
+
+ if s[:9] == "Traceback":
+ # Tell the user than an exception occurred.
+ wx.MessageBox("An internal error has occurred.\nPlease " + \
+ "refer to the 'errors.txt' file for details.",
+ "Error", wx.OK | wx.CENTRE | wx.ICON_EXCLAMATION)
+
+
+ except:
+ pass # Don't recursively crash on errors.
#----------------------------------------------------------------------------
-class SketchApp(wxApp):
+class SketchApp(wx.App):
""" 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
+ """ Initialise the application.
+ """
+ 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
#----------------------------------------------------------------------------
if __name__ == "__main__":
main()
+