📄 pysketch.py
字号:
""" pySketch
A simple object-oriented drawing program.
This is completely free software; please feel free to adapt or use this in
any way you like.
Author: Erik Westra (ewestra@wave.co.nz)
#########################################################################
NOTE
pySketch requires wxPython version 2.3. If you are running an earlier
version, you need to patch your copy of wxPython to fix a bug which will
cause the "Edit Text Object" dialog box to crash.
To patch an earlier version of wxPython, edit the wxPython/windows.py file,
find the wxPyValidator.__init__ method and change the line which reads:
self._setSelf(self, wxPyValidator, 0)
to:
self._setSelf(self, wxPyValidator, 1)
This fixes a known bug in wxPython 2.2.5 (and possibly earlier) which has
now been fixed in wxPython 2.3.
#########################################################################
TODO:
* Add ARGV checking to see if a document was double-clicked on.
Known Bugs:
* Scrolling the window causes the drawing panel to be mucked up until you
refresh it. I've got no idea why.
* I suspect that the reference counting for some wxPoint objects is
getting mucked up; when the user quits, we get errors about being
unable to call del on a 'None' object.
"""
import sys
import cPickle, os.path
import wx
from wx.lib.buttons import GenBitmapButton
import traceback, types
#----------------------------------------------------------------------------
# System Constants
#----------------------------------------------------------------------------
# Our menu item IDs:
menu_UNDO = 10001 # Edit menu items.
menu_SELECT_ALL = 10002
menu_DUPLICATE = 10003
menu_EDIT_TEXT = 10004
menu_DELETE = 10005
menu_SELECT = 10101 # Tools menu items.
menu_LINE = 10102
menu_RECT = 10103
menu_ELLIPSE = 10104
menu_TEXT = 10105
menu_MOVE_FORWARD = 10201 # Object menu items.
menu_MOVE_TO_FRONT = 10202
menu_MOVE_BACKWARD = 10203
menu_MOVE_TO_BACK = 10204
menu_ABOUT = 10205 # Help menu items.
# Our tool IDs:
id_SELECT = 11001
id_LINE = 11002
id_RECT = 11003
id_ELLIPSE = 11004
id_TEXT = 11005
# Our tool option IDs:
id_FILL_OPT = 12001
id_PEN_OPT = 12002
id_LINE_OPT = 12003
id_LINESIZE_0 = 13001
id_LINESIZE_1 = 13002
id_LINESIZE_2 = 13003
id_LINESIZE_3 = 13004
id_LINESIZE_4 = 13005
id_LINESIZE_5 = 13006
# DrawObject type IDs:
obj_LINE = 1
obj_RECT = 2
obj_ELLIPSE = 3
obj_TEXT = 4
# Selection handle IDs:
handle_NONE = 1
handle_TOP_LEFT = 2
handle_TOP_RIGHT = 3
handle_BOTTOM_LEFT = 4
handle_BOTTOM_RIGHT = 5
handle_START_POINT = 6
handle_END_POINT = 7
# Dragging operations:
drag_NONE = 1
drag_RESIZE = 2
drag_MOVE = 3
drag_DRAG = 4
# Visual Feedback types:
feedback_LINE = 1
feedback_RECT = 2
feedback_ELLIPSE = 3
# Mouse-event action parameter types:
param_RECT = 1
param_LINE = 2
# Size of the drawing page, in pixels.
PAGE_WIDTH = 1000
PAGE_HEIGHT = 1000
#----------------------------------------------------------------------------
class DrawingFrame(wx.Frame):
""" A frame showing the contents of a single document. """
# ==========================================
# == Initialisation and Window Management ==
# ==========================================
def __init__(self, parent, id, title, fileName=None):
""" Standard constructor.
'parent', 'id' and 'title' are all passed to the standard 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")
self.fileMenu.Append(wx.ID_OPEN, "Open...\tCTRL-O")
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(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 = wx.Menu()
self.toolsMenu.Append(menu_SELECT, "Selection", kind=wx.ITEM_CHECK)
self.toolsMenu.Append(menu_LINE, "Line", kind=wx.ITEM_CHECK)
self.toolsMenu.Append(menu_RECT, "Rectangle", kind=wx.ITEM_CHECK)
self.toolsMenu.Append(menu_ELLIPSE, "Ellipse", kind=wx.ITEM_CHECK)
self.toolsMenu.Append(menu_TEXT, "Text", kind=wx.ITEM_CHECK)
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 toolbar.
tsize = (16,16)
self.toolbar = self.CreateToolBar(wx.TB_HORIZONTAL |
wx.NO_BORDER | wx.TB_FLAT)
self.toolbar.AddSimpleTool(wx.ID_NEW,
wx.ArtProvider.GetBitmap(wx.ART_NEW, wx.ART_TOOLBAR, tsize),
"New")
self.toolbar.AddSimpleTool(wx.ID_OPEN,
wx.ArtProvider.GetBitmap(wx.ART_FILE_OPEN, wx.ART_TOOLBAR, tsize),
"Open")
self.toolbar.AddSimpleTool(wx.ID_SAVE,
wx.ArtProvider.GetBitmap(wx.ART_FILE_SAVE, wx.ART_TOOLBAR, tsize),
"Save")
self.toolbar.AddSeparator()
self.toolbar.AddSimpleTool(menu_UNDO,
wx.ArtProvider.GetBitmap(wx.ART_UNDO, wx.ART_TOOLBAR, tsize),
"Undo")
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 each menu/toolbar item with the method that handles that
# item.
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),
(menu_UNDO, self.doUndo),
(menu_SELECT_ALL, self.doSelectAll),
(menu_DUPLICATE, self.doDuplicate),
(menu_EDIT_TEXT, self.doEditText),
(menu_DELETE, self.doDelete),
(menu_SELECT, self.doChooseSelectTool),
(menu_LINE, self.doChooseLineTool),
(menu_RECT, self.doChooseRectTool),
(menu_ELLIPSE, self.doChooseEllipseTool),
(menu_TEXT, self.doChooseTextTool),
(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
self.Bind(wx.EVT_MENU, handler, 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 = 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 = wx.GridSizer(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(
wx.ToolTip("Shows Current Pen/Fill/Line Size Settings"))
optionSizer = wx.BoxSizer(wx.HORIZONTAL)
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 = 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)
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)
# Make the tool palette icons respond when the user clicks on them.
self.selectIcon.Bind(wx.EVT_BUTTON, self.onToolIconClick)
self.lineIcon.Bind(wx.EVT_BUTTON, self.onToolIconClick)
self.rectIcon.Bind(wx.EVT_BUTTON, self.onToolIconClick)
self.ellipseIcon.Bind(wx.EVT_BUTTON, self.onToolIconClick)
self.textIcon.Bind(wx.EVT_BUTTON, self.onToolIconClick)
self.penOptIcon.Bind(wx.EVT_BUTTON, self.onPenOptionIconClick)
self.fillOptIcon.Bind(wx.EVT_BUTTON, self.onFillOptionIconClick)
self.lineOptIcon.Bind(wx.EVT_BUTTON, self.onLineOptionIconClick)
# Setup the main drawing area.
self.drawPanel = wx.ScrolledWindow(self.topPanel, -1,
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -