# Sketch - A Python-based interactive drawing program
# Copyright (C) 1997, 1998, 1999, 2000, 2001, 2002, 2003, 2004 by Bernhard Herzog
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Library General Public
# License as published by the Free Software Foundation; either
# version 2 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# Library General Public License for more details.
#
# You should have received a copy of the GNU Library General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
import os, sys, string
from types import TupleType,ListType
from Sketch.Lib import util
from Sketch.warn import warn,warn_tb,INTERNAL,USER
from Sketch import _,config,load,plugins,SketchVersion
from Sketch import Publisher,Point,EmptyFillStyle,EmptyLineStyle,\
EmptyPattern, Document, GuideLine, PostScriptDevice, SketchError
import Sketch
from Sketch.Graphics import image,eps
import Sketch.Scripting
from Sketch.const import DOCUMENT,CLIPBOARD,CLOSED,COLOR1,COLOR2
from Sketch.const import STATE,VIEW,MODE,SELECTION,POSITION,UNDO,EDITED,\
CURRENTINFO
from Tkinter import TclVersion,TkVersion,Frame,Scrollbar
from Tkinter import X,BOTTOM,BOTH,TOP,HORIZONTAL,LEFT,Y
import Tkinter
from tkext import AppendMenu,UpdatedLabel,UpdatedButton,CommandButton,\
CommandCheckbutton, MakeCommand, MultiButton
import tkext
from command import CommandClass,Keymap,Commands
from canvas import SketchCanvas
import ruler
from poslabel import PositionLabel
import palette, tooltips
import skpixmaps
pixmaps = skpixmaps.PixmapTk
import skapp
command_list = []
def AddCmd(name, menu_name, method_name = None, **kw):
kw['menu_name'] = menu_name
if not method_name:
method_name = name
cmd = apply(CommandClass, (name, method_name), kw)
command_list.append(cmd)
def AddDocCmd(name, menu_name, method_name = None, **kw):
kw['menu_name'] = menu_name
if not method_name:
method_name = name
method_name = ('document', method_name)
for key in CommandClass.callable_attributes:
if kw.has_key(key):
value = kw[key]
if type(value) == type(""):
kw[key] = ('document', value)
if not kw.has_key('subscribe_to'):
kw['subscribe_to'] = SELECTION
if not kw.has_key('sensitive_cb'):
kw['sensitive_cb'] = ('document', 'HasSelection')
cmd = apply(CommandClass, (name, method_name), kw)
command_list.append(cmd)
class SketchMainWindow(Publisher):
tk_basename = 'sketch'
tk_class_name = 'Sketch'
def __init__(self, application, filename, run_script = None):
self.application = application
self.root = application.root # XXX
self.filename = filename
self.run_script = run_script
self.canvas = None
self.document = None
self.commands = None
self.NewDocument()
self.create_commands()
self.build_window()
self.build_menu()
self.build_toolbar()
self.build_status_bar()
self.__init_dlgs()
def issue_document(self):
self.issue(DOCUMENT, self.document)
def create_commands(self):
cmds = Commands()
keymap = Keymap()
for cmd_class in command_list:
cmd = cmd_class.InstantiateFor(self)
setattr(cmds, cmd.name, cmd)
keymap.AddCommand(cmd)
self.commands = cmds
self.commands.Update()
self.keymap = keymap
def MapKeystroke(self, stroke):
return self.keymap.MapKeystroke(stroke)
def save_doc_if_edited(self, title = _("Save Document")):
if self.document is not None and self.document.WasEdited():
message = _("%s has been changed.\nDo you want to save it?") \
% self.document.meta.filename
result = self.application.MessageBox(title = title,
message = message,
buttons = tkext.YesNoCancel)
if result == tkext.Yes:
self.SaveToFileInteractive()
return result
return tkext.No
def Document(self):
return self.document
def SetDocument(self, document):
channels = (SELECTION, UNDO, MODE)
old_doc = self.document
if old_doc is not None:
for channel in channels:
old_doc.Unsubscribe(channel, self.issue, channel)
self.document = document
for channel in channels:
self.document.Subscribe(channel, self.issue, channel)
if self.canvas is not None:
self.canvas.SetDocument(document)
self.issue_document()
# issue_document has to be called before old_doc is destroyed,
# because destroying it causes all connections to be deleted and
# some dialogs (derived from SketchDlg) try to unsubscribe in
# response to our DOCUMENT message. The connector currently
# raises an exception in this case. Perhaps it should silently
# ignore Unsubscribe() calls with methods that are actually not
# subscribers (any more)
if old_doc is not None:
old_doc.Destroy()
self.set_window_title()
if self.commands:
self.commands.Update()
AddCmd('NewDocument', _("New"), bitmap = pixmaps.NewDocument)
def NewDocument(self):
if self.save_doc_if_edited(_("New Document")) == tkext.Cancel:
return
self.SetDocument(Document(create_layer = 1))
AddCmd('LoadFromFile', _("Open..."), bitmap = pixmaps.Open,
key_stroke = 'C-o')
AddCmd('LoadMRU0', '', 'LoadFromFile', args = 0, key_stroke = 'M-1',
name_cb = lambda: os.path.split(config.preferences.mru_files[0])[1])
AddCmd('LoadMRU1', '', 'LoadFromFile', args = 1, key_stroke = 'M-2',
name_cb = lambda: os.path.split(config.preferences.mru_files[1])[1])
AddCmd('LoadMRU2', '', 'LoadFromFile', args = 2, key_stroke = 'M-3',
name_cb = lambda: os.path.split(config.preferences.mru_files[2])[1])
AddCmd('LoadMRU3', '', 'LoadFromFile', args = 3, key_stroke = 'M-4',
name_cb = lambda: os.path.split(config.preferences.mru_files[3])[1])
def LoadFromFile(self, filename = None, directory = None):
app = self.application
if self.save_doc_if_edited(_("Open Document")) == tkext.Cancel:
return
if type(filename) == type(0):
filename = config.preferences.mru_files[filename]
if not filename:
if not directory:
directory = self.document.meta.directory
if not directory:
directory = os.getcwd()
name = ''
filename = app.GetOpenFilename(filetypes = skapp.openfiletypes(),
initialdir = directory,
initialfile = name)
if not filename:
return
try:
if not os.path.isabs(filename):
filename = os.path.join(os.getcwd(), filename)
doc = load.load_drawing(filename)
self.SetDocument(doc)
self.add_mru_file(filename)
except SketchError, value:
app.MessageBox(title = _("Open"),
message = _("An error occurred:\n") + str(value))
#warn_tb(USER, "An error occurred:\n", str(value))
self.remove_mru_file(filename)
else:
messages = doc.meta.load_messages
if messages:
app.MessageBox(title = _("Open"),
message=_("Warnings from the import filter:\n")
+ messages)
doc.meta.load_messages = ''
AddCmd('SaveToFile', _("Save"), 'SaveToFileInteractive',
bitmap = pixmaps.Save, key_stroke = ('C-s', 'F2'))
AddCmd('SaveToFileAs', _("Save As..."), 'SaveToFileInteractive', args = 1,
key_stroke = ('C-w', 'F3'))
def SaveToFileInteractive(self, use_dialog = 0):
filename = self.document.meta.fullpathname
native_format = self.document.meta.native_format
compressed_file = self.document.meta.compressed_file
compressed = self.document.meta.compressed
app = self.application
if use_dialog or not filename or not native_format:
dir = self.document.meta.directory
if not dir:
dir = os.getcwd()
name = self.document.meta.filename
basename, ext = os.path.splitext(name)
if not native_format:
name = basename + '.sk'
filename = app.GetSaveFilename(filetypes = skapp.savefiletypes(),
initialdir = dir,
initialfile = name)
if not filename:
return
extension = os.path.splitext(filename)[1]
fileformat = plugins.guess_export_plugin(extension)
if not fileformat:
fileformat = plugins.NativeFormat
compressed_file = '' # guess compression from filename
compressed = ''
else:
fileformat = plugins.NativeFormat
self.SaveToFile(filename, fileformat, compressed, compressed_file)
def SaveToFile(self, filename, fileformat = None, compressed = '',
compressed_file = ''):
app = self.application
try:
if not self.document.meta.backup_created:
try:
if compressed_file:
util.make_backup(compressed_file)
else:
util.make_backup(filename)
except util.BackupError, value:
backupfile = value.filename
strerror = value.strerror
msg = (_("Cannot create backup file %(filename)s:\n"
"%(message)s\n"
"Choose `continue' to try saving anyway,\n"
"or `cancel' to cancel.")
% {'filename':`backupfile`, 'message':strerror})
cancel = _("Cancel")
result = app.MessageBox(title = _("Save To File"),
message = msg, icon = 'warning',
buttons = (_("Continue"), cancel))
if result == cancel:
return
self.document.meta.backup_created = 1
if fileformat is None:
fileformat = plugins.NativeFormat
try:
saver = plugins.find_export_plugin(fileformat)
if compressed:
# XXX there should be a plugin interface for this kind
# of post-processing
if compressed == "gzip":
cmd = 'gzip -c -9 > ' + util.sh_quote(compressed_file)
elif compressed == "bzip2":
cmd = 'bzip2 > ' + util.sh_quote(compressed_file)
file = os.popen(cmd, 'w')
saver(self.document, filename, file = file)
else:
saver(self.document, filename)
finally:
saver.UnloadPlugin()
except IOError, value:
if type(value) == type(()):
value = value[1]
app.MessageBox(title = _("Save To File"),
message = _("Cannot save %(filename)s:\n"
"%(message)s") \
% {'filename':`os.path.split(filename)[1]`,
'message':value},
icon = 'warning')
self.remove_mru_file(filename)
return
if fileformat == plugins.NativeFormat:
dir, name = os.path.split(filename)
# XXX should meta.directory be set for non-native formats as well
self.document.meta.directory = dir
self.document.meta.filename = name
self.document.meta.fullpathname = filename
self.document.meta.file_type = plugins.NativeFormat
self.document.meta.native_format = 1
if not compressed_file:
self.document.meta.compressed_file = ''
self.document.meta.compressed = ''
if compressed_file:
self.add_mru_file(compressed_file)
else:
self.add_mru_file(filename)
self.set_window_title()
AddCmd('SavePS', _("Save as PostScript..."), key_stroke = 'M-p')
def SavePS(self, filename = None):
app = self.application
bbox = self.document.BoundingRect(visible = 0, printable = 1)
if bbox is None:
app.MessageBox(title = _("Save As PostScript"),
message = _("The document doesn't have "
"any printable layers."),
icon = "warning")
return
if not filename:
dir = self.document.meta.ps_directory
if not dir:
dir = self.document.meta.directory
if not dir:
dir = os.getcwd()
name = self.document.meta.filename
name, ext = os.path.splitext(name)
name = name + '.ps'
filename = app.GetSaveFilename(title = _("Save As PostScript"),
filetypes = skapp.psfiletypes,
initialdir = dir,
initialfile = name)
if not filename:
return
try:
ps_dev = PostScriptDevice(filename, as_eps = 1,
bounding_box = tuple(bbox),
For = util.get_real_username(),
CreationDate = util.current_date(),
Title = os.path.basename(filename),
document = self.document)
self.document.Draw(ps_dev)
ps_dev.Close()
self.document.meta.ps_directory = os.path.split(filename)[0]
except IOError, value:
app.MessageBox(title = _("Save As PostScript"),
message = _("Cannot save %(filename)s:\n"
"%(message)s") \
% {'filename':`os.path.split(filename)[1]`,
'message':value[1]},
icon = 'warning')
def add_mru_file(self, filename):
if filename:
config.add_mru_file(filename)
self.update_mru_files()
def remove_mru_file(self, filename):
if filename:
config.remove_mru_file(filename)
self.update_mru_files()
def update_mru_files(self):
self.commands.LoadMRU0.Update()
self.commands.LoadMRU1.Update()
self.commands.LoadMRU2.Update()
self.commands.LoadMRU3.Update()
self.file_menu.RebuildMenu()
AddCmd('InsertFile', _("Insert Document..."))
def InsertFile(self, filename = None):
app = self.application
if not filename:
dir = self.document.meta.directory
if not dir:
dir = os.getcwd()
name = ''
filename = app.GetOpenFilename(filetypes = skapp.openfiletypes(),
initialdir = dir,
initialfile = name)
if not filename:
return
try:
if not os.path.isabs(filename):
filename = os.path.join(os.getcwd(), filename)
doc = load.load_drawing(filename)
group = doc.as_group()
except SketchError, value:
app.MessageBox(title = _("Insert Document"),
message = _("An error occurred:\n") + str(value))
#warn_tb(USER, "An error occurred:\n", str(value))
self.remove_mru_file(filename)
else:
messages = doc.meta.load_messages
if messages:
app.MessageBox(title = _("Insert Document"),
message=_("Warnings from the import filter:\n")
+ messages)
doc.meta.load_messages = ''
#
if group is not None:
self.canvas.PlaceObject(group)
else:
app.MessageBox(title = _("Insert Document"),
message=_("The document is empty"))
AddCmd('LoadPalette', _("Load Palette..."))
def LoadPalette(self, filename = None):
if not filename:
dir = config.std_res_dir
if not dir:
dir = os.getcwd()
name = ''
filename = self.application.GetOpenFilename(
filetypes = palette.file_types,
initialdir = dir,
initialfile = name)
if not filename:
return
pal = palette.LoadPalette(filename)
if not pal:
self.application.MessageBox(title = _("Load Palette"),
message = _("Cannot load palette %(filename)s")
% {'filename': filename})
else:
self.palette.SetPalette(pal)
# XXX Should we just store the basename if the palette file
# is located in the resource_dir?
config.preferences.palette = filename
def __init_dlgs(self):
self.dialogs = {}
def CreateDialog(self, module, dlgname):
if self.dialogs.has_key(dlgname):
dialog = self.dialogs[dlgname]
dialog.deiconify_and_raise()
else:
exec "from %s import %s" % (module, dlgname)
dlgclass = locals()[dlgname]
dialog = dlgclass(self.root, self, self.document)
dialog.Subscribe(CLOSED, self.__dlg_closed, dlgname)
self.dialogs[dlgname] = dialog
def HideDialogs(self):
for dialog in self.dialogs.values():
dialog.withdraw()
AddCmd('HideDialogs', _("Hide Dialogs"))
def ShowDialogs(self):
for dialog in self.dialogs.values():
dialog.deiconify_and_raise()
AddCmd('ShowDialogs', _("Show Dialogs"))
def __dlg_closed(self, dialog, name):
try:
del self.dialogs[name]
except:
# This might happen if the dialog is buggy...
warn(INTERNAL, 'dialog %s alread removed from dialog list', name)
AddCmd('CreateLayerDialog', _("Layers..."),
'CreateDialog', args = ('layerdlg', 'LayerPanel'),
key_stroke = 'F5')
AddCmd('CreateAlignDialog', _("Align..."),
'CreateDialog', args = ('aligndlg', 'AlignPanel'))
AddCmd('CreateGridDialog', _("Grid..."),
'CreateDialog', args = ('griddlg', 'GridPanel'))
AddCmd('CreateLineStyleDialog', _("Line..."),
'CreateDialog', args = ('linedlg', 'LinePanel'),
key_stroke = 'F7')
AddCmd('CreateFillStyleDialog', _("Fill..."),
'CreateDialog', args = ('filldlg', 'FillPanel'),
key_stroke = 'F6')
AddCmd('CreateFontDialog', _("Font..."),
'CreateDialog', args = ('fontdlg', 'FontPanel'))
AddCmd('CreateStyleDialog', _("Styles..."),
'CreateDialog', args = ('styledlg', 'StylePanel'))
AddCmd('CreateBlendDialog', _("Blend..."),
'CreateDialog', args = ('blenddlg', 'BlendPanel'),
key_stroke = 'C-b')
AddCmd('CreateLayoutDialog', _("Page Layout..."),
'CreateDialog', args = ('layoutdlg', 'LayoutPanel'))
#AddCmd('CreateExportDialog', 'Export...',
# 'CreateDialog', args = ('export', 'ExportPanel'))
AddCmd('CreateCurveDialog', _("Curve Commands..."),
'CreateDialog', args = ('curvedlg', 'CurvePanel'))
AddCmd('CreateGuideDialog', _("Guide Lines..."),
'CreateDialog', args = ('guidedlg', 'GuidePanel'))
AddCmd('CreatePrintDialog', _("Print..."),
'CreateDialog', args = ('printdlg', 'PrintPanel'),
key_stroke = "C-p")
AddCmd('CreateReloadPanel', _("Reload Module..."),
'CreateDialog', args = ('reloaddlg', 'ReloadPanel'))
def CreatePluginDialog(self, info):
if info.HasCustomDialog():
dialog = info.CreateCustomDialog(self.root, self, self.document)
else:
from plugindlg import PluginPanel
dialog = PluginPanel(self.root, self, self.document, info)
dialog.Subscribe(CLOSED, self.__dlg_closed, info.class_name)
self.dialogs[info.class_name] = dialog
AddCmd('SetOptions', _("Options..."))
def SetOptions(self):
import optiondlg
optiondlg.OptionDialog(self.root, self.canvas)
def set_window_title(self):
self.root.client(util.gethostname())
if self.document:
appname = config.name
meta = self.document.meta
if meta.compressed:
docname = os.path.split(meta.compressed_file)[1]
docname = os.path.splitext(docname)[0]
else:
docname = self.document.meta.filename
title = config.preferences.window_title_template % locals()
command = (config.sketch_command, self.document.meta.fullpathname)
else:
title = config.name
command = (config.sketch_command, )
self.root.title(title)
self.root.command(command)
def UpdateCommands(self):
self.canvas.UpdateCommands()
def Run(self):
#self.root.wait_visibility(self.canvas)
if self.filename:
if os.path.isdir(self.filename):
filename = ''
directory = self.filename
else:
filename = self.filename
directory = ''
self.LoadFromFile(filename, directory = directory)
self.filename = ''
if self.run_script:
from Sketch.Scripting.script import Context
dict = {'context': Context()}
try:
execfile(self.run_script, dict)
except:
warn_tb(USER, _("Error running script `%s'"), self.run_script)
self.application.Mainloop()
AddCmd('Exit', _("Exit"), key_stroke = ('M-Q', 'M-F4'))
def Exit(self):
if self.save_doc_if_edited(_("Exit")) != tkext.Cancel:
self.commands = None
self.application.Exit()
def build_window(self):
root = self.application.root
self.mbar = Frame(root, name = 'menubar')
self.mbar.pack(fill=X)
self.tbar = Frame(root, name = 'toolbar')
self.tbar.pack(fill=X)
self.status_bar = Frame(root, name = 'statusbar')
self.status_bar.pack(side = BOTTOM, fill=X)
palette_frame = Frame(root, name = 'palette_frame')
palette_frame.pack(side = BOTTOM, fill = X)
frame = Frame(root, name = 'canvas_frame')
frame.pack(side = TOP, fill = BOTH, expand = 1)
vbar = Scrollbar(frame)
vbar.grid(in_ = frame, column = 2, row = 1, sticky = 'ns')
hbar = Scrollbar(frame, orient = HORIZONTAL)
hbar.grid(in_ = frame, column = 1, row = 2, sticky = 'ew')
hrule = ruler.Ruler(root, orient = ruler.HORIZONTAL)
hrule.grid(in_ = frame, column = 1, row = 0, sticky = 'ew',
columnspan = 2)
vrule = ruler.Ruler(root, orient = ruler.VERTICAL)
vrule.grid(in_ = frame, column = 0, row = 1, sticky = 'ns',
rowspan = 2)
tmp = Frame(frame, name = 'rulercorner')
tmp.grid(column = 0, row = 0, sticky = 'news')
resolution = config.preferences.screen_resolution
self.canvas = SketchCanvas(root, toplevel = root,
background = 'white', name = 'canvas',
resolution = resolution, main_window = self,
document = self.document)
self.canvas.grid(in_ = frame, column = 1, row = 1, sticky = 'news')
self.canvas.focus()
self.canvas.SetScrollbars(hbar, vbar)
self.canvas.SetRulers(hrule, vrule)
hrule.SetCanvas(self.canvas)
vrule.SetCanvas(self.canvas)
frame.columnconfigure(0, weight = 0)
frame.columnconfigure(1, weight = 1)
frame.columnconfigure(2, weight = 0)
frame.rowconfigure(0, weight = 0)
frame.rowconfigure(1, weight = 1)
frame.rowconfigure(2, weight = 0)
hbar['command'] = self.canvas._w + ' xview'
vbar['command'] = self.canvas._w + ' yview'
# the palette
button = MultiButton(palette_frame, bitmap = pixmaps.NoPattern,
command = self.no_pattern, args = 'fill')
button.pack(side = LEFT)
button.Subscribe('COMMAND2', self.no_pattern, 'line')
pal = palette.GetStandardPalette()
self.palette = palette.PaletteWidget(palette_frame, pal)
ScrollXUnits = self.palette.ScrollXUnits
ScrollXPages = self.palette.ScrollXPages
CanScrollLeft = self.palette.CanScrollLeft
CanScrollRight = self.palette.CanScrollRight
for bitmap, command, args, sensitivecb in [
(pixmaps.ArrArrLeft, ScrollXPages, -1, CanScrollLeft),
(pixmaps.ArrLeft, ScrollXUnits, -1, CanScrollLeft),
(pixmaps.ArrRight, ScrollXUnits, +1, CanScrollRight),
(pixmaps.ArrArrRight, ScrollXPages, +1, CanScrollRight)]:
button = UpdatedButton(palette_frame, bitmap = bitmap,
command = command, args = args,
sensitivecb = sensitivecb)
button.pack(side = LEFT)
self.palette.Subscribe(VIEW, button.Update)
if bitmap == pixmaps.ArrLeft:
self.palette.pack(side = LEFT, fill = X, expand = 1)
self.palette.Subscribe(COLOR1, self.canvas.FillSolid)
self.palette.Subscribe(COLOR2, self.canvas.LineColor)
root.protocol('WM_DELETE_WINDOW', tkext.MakeMethodCommand(self.Exit))
def make_file_menu(self):
cmds = self.commands
return map(MakeCommand,
[cmds.NewDocument,
cmds.LoadFromFile,
cmds.SaveToFile,
cmds.SaveToFileAs,
cmds.SavePS,
cmds.CreatePrintDialog,
None,
cmds.InsertFile,
#cmds.CreateExportDialog,
None,
cmds.SetOptions,
cmds.AboutBox,
None,
cmds.LoadMRU0,
cmds.LoadMRU1,
cmds.LoadMRU2,
cmds.LoadMRU3,
None,
cmds.Exit])
def make_edit_menu(self):
cmds = self.canvas.commands
return map(MakeCommand,
[self.commands.Undo,
self.commands.Redo,
self.commands.ResetUndo,
None,
self.commands.CopySelected,
self.commands.CutSelected,
self.commands.PasteClipboard,
self.commands.RemoveSelected,
None,
self.commands.SelectAll,
None,
self.commands.DuplicateSelected,
None,
[(_("Create"), {'auto_rebuild':self.creation_entries}),
[]],
None,
cmds.SelectionMode, cmds.EditMode,
])
def creation_entries(self):
cmds = self.canvas.commands
entries = [cmds.CreateRectangle,
cmds.CreateEllipse,
cmds.CreatePolyBezier,
cmds.CreatePolyLine,
cmds.CreateSimpleText,
self.commands.CreateImage,
None]
items = plugins.object_plugins.items()
items.sort()
place = self.place_plugin_object
dialog = self.CreatePluginDialog
group = self.create_plugin_group
for name, plugin in items:
if plugin.UsesSelection():
entries.append((plugin.menu_text, group, plugin))
elif plugin.HasParameters() or plugin.HasCustomDialog():
entries.append((plugin.menu_text + '...', dialog, plugin))
else:
entries.append((plugin.menu_text, place, plugin))
return map(MakeCommand, entries)
def place_plugin_object(self, info):
self.canvas.PlaceObject(info())
def create_plugin_group(self, info):
self.document.group_selected(info.menu_text, info.CallFactory)
def PlaceObject(self, object):
self.canvas.PlaceObject(object)
def make_effects_menu(self):
return map(MakeCommand,
[self.commands.FlipHorizontal,
self.commands.FlipVertical,
None,
self.commands.RemoveTransformation,
None,
self.commands.CreateBlendDialog,
self.commands.CancelBlend,
None,
self.commands.CreateMaskGroup,
self.commands.CreatePathText,
])
def make_curve_menu(self):
canvas = self.canvas
cmds = self.canvas.commands.PolyBezierEditor
return map(MakeCommand,
[cmds.ContAngle,
cmds.ContSmooth,
cmds.ContSymmetrical,
cmds.SegmentsToLines,
cmds.SegmentsToCurve,
cmds.SelectAllNodes,
None,
cmds.DeleteNodes,
cmds.InsertNodes,
None,
cmds.CloseNodes,
cmds.OpenNodes,
None,
self.commands.CombineBeziers,
self.commands.SplitBeziers,
None,
self.commands.ConvertToCurve])
def make_view_menu(self):
def MakeEntry(scale, call = self.canvas.SetScale):
percent = int(100 * scale)
return (('%3d%%' % percent), call, scale)
cmds = self.canvas.commands
scale = map(MakeEntry, [ 0.125, 0.25, 0.5, 1, 2, 4, 8])
return map(MakeCommand,
[MakeEntry(1),
[_("Zoom")] + scale,
cmds.ZoomIn,
cmds.ZoomOut,
cmds.ZoomMode,
None,
cmds.FitToWindow,
cmds.FitSelectedToWindow,
cmds.FitPageToWindow,
cmds.RestoreViewport,
None,
cmds.ForceRedraw,
None,
cmds.ToggleOutlineMode,
cmds.TogglePageOutlineMode,
None,
cmds.ToggleCrosshairs,
None,
self.commands.LoadPalette
])
def make_arrange_menu(self):
commands = [self.commands.CreateAlignDialog,
None,
self.commands.MoveSelectedToTop,
self.commands.MoveSelectedToBottom,
self.commands.MoveSelectionUp,
self.commands.MoveSelectionDown,
None,
self.commands.AbutHorizontal,
self.commands.AbutVertical,
None,
self.commands.GroupSelected,
self.commands.UngroupSelected,
None,
self.canvas.commands.ToggleSnapToObjects,
self.canvas.commands.ToggleSnapToGrid,
self.commands.CreateGridDialog,
None,
self.canvas.commands.ToggleSnapToGuides,
self.commands.AddHorizGuideLine,
self.commands.AddVertGuideLine,
self.commands.CreateGuideDialog]
if config.preferences.show_advanced_snap_commands:
commands.append(None)
commands.append(self.canvas.commands.ToggleSnapMoveRelative)
commands.append(self.canvas.commands.ToggleSnapBoundingRect)
commands = commands + [None,
self.commands.CreateLayoutDialog
]
return map(MakeCommand, commands)
def make_style_menu(self):
return map(MakeCommand,
[self.commands.FillNone,
self.commands.CreateFillStyleDialog,
self.canvas.commands.FillSolid,
None,
self.commands.LineNone,
self.commands.CreateLineStyleDialog,
None,
self.commands.CreateStyleFromSelection,
self.commands.CreateStyleDialog,
self.commands.UpdateStyle,
None,
self.commands.CreateFontDialog
])
def make_window_menu(self):
cmds = self.commands
return map(MakeCommand,
[cmds.HideDialogs,
cmds.ShowDialogs,
None,
cmds.CreateLayerDialog,
cmds.CreateAlignDialog,
cmds.CreateGridDialog,
None,
cmds.CreateLineStyleDialog,
cmds.CreateFillStyleDialog,
cmds.CreateFontDialog,
cmds.CreateStyleDialog,
None,
cmds.CreateLayoutDialog,
None,
cmds.CreateBlendDialog,
cmds.CreateCurveDialog
])
def make_special_menu(self):
cmdlist = [self.commands.python_prompt,
self.commands.CreateReloadPanel,
self.commands.DocumentInfo,
None,
self.commands.DumpXImage,
self.commands.CreateClone,
#self.commands.export_bitmap,
]
Sketch.Issue(None, Sketch.const.ADD_TO_SPECIAL_MENU, cmdlist)
return map(MakeCommand, cmdlist)
def make_script_menu(self):
tree = Sketch.Scripting.Registry.MenuTree()
cmdlist = self.convert_menu_tree(tree)
return map(MakeCommand, cmdlist)
def convert_menu_tree(self, tree):
result = []
for title, item in tree:
if type(item) == ListType:
result.append([title] + self.convert_menu_tree(item))
else:
result.append((title, item.Execute))
return result
def build_menu(self):
mbar = self.mbar
self.file_menu = AppendMenu(mbar, _("File"), self.make_file_menu())
AppendMenu(mbar, _("Edit"), self.make_edit_menu())
AppendMenu(mbar, _("Effects"), self.make_effects_menu())
AppendMenu(mbar, _("Curve"), self.make_curve_menu())
AppendMenu(mbar, _("Arrange"), self.make_arrange_menu())
AppendMenu(mbar, _("Style"), self.make_style_menu())
AppendMenu(mbar, _("View"), self.make_view_menu())
AppendMenu(mbar, _("Script"), self.make_script_menu())
AppendMenu(mbar, _("Windows"), self.make_window_menu())
if config.preferences.show_special_menu:
AppendMenu(mbar, _("Special"), self.make_special_menu())
self.update_mru_files()
self.file_menu.RebuildMenu()
def build_toolbar(self):
tbar = self.tbar
canvas = self.canvas
cmds = [self.commands.NewDocument,
self.commands.LoadFromFile,
self.commands.SaveToFile,
None,
canvas.commands.EditMode,
canvas.commands.SelectionMode,
None,
self.commands.Undo,
self.commands.Redo,
self.commands.RemoveSelected,
None,
self.commands.DuplicateSelected,
self.commands.FlipHorizontal,
self.commands.FlipVertical,
None,
self.commands.MoveSelectedToTop,
self.commands.MoveSelectionUp,
self.commands.MoveSelectionDown,
self.commands.MoveSelectedToBottom,
None,
self.commands.GroupSelected,
self.commands.UngroupSelected,
None,
canvas.commands.ZoomMode,
canvas.commands.ToggleSnapToGrid,
None,
canvas.commands.CreateRectangle,
canvas.commands.CreateEllipse,
canvas.commands.CreatePolyBezier,
canvas.commands.CreatePolyLine,
canvas.commands.CreateSimpleText,
self.commands.CreateImage,
]
buttons = []
for cmd in cmds:
if cmd is None:
b = Frame(tbar, class_ = 'TBSeparator')
b.pack(side = LEFT, fill = Y)
else:
if cmd.is_check:
b = CommandCheckbutton(tbar, cmd,
highlightthickness = 0,
takefocus = 0)
else:
b = CommandButton(tbar, cmd, highlightthickness = 0,
takefocus = 0)
tooltips.AddDescription(b, cmd.menu_name)
b.pack(side = LEFT, fill = Y)
def state_changed(buttons = buttons):
for button in buttons:
button.Update()
canvas.Subscribe(STATE, state_changed)
def build_status_bar(self):
status_bar = self.status_bar
canvas = self.canvas
stat_mode = UpdatedLabel(status_bar, name = 'mode', text = '',
updatecb = canvas.ModeInfoText)
stat_mode.pack(side = 'left')
stat_mode.Update()
canvas.Subscribe(MODE, stat_mode.Update)
stat_edited = UpdatedLabel(status_bar, name = 'edited', text = '',
updatecb = self.EditedInfoText)
stat_edited.pack(side = 'left')
stat_edited.Update()
self.Subscribe(UNDO, stat_edited.Update)
stat_zoom = UpdatedLabel(status_bar, name = 'zoom', text = '',
updatecb = canvas.ZoomInfoText)
stat_zoom.pack(side = 'left')
stat_zoom.Update()
canvas.Subscribe(VIEW, stat_zoom.Update)
stat_pos = PositionLabel(status_bar, name = 'position', text = '',
updatecb = canvas.GetCurrentPos)
stat_pos.pack(side = 'left')
stat_pos.Update()
canvas.Subscribe(POSITION, stat_pos.Update)
stat_sel = UpdatedLabel(status_bar, name = 'selection', text = '',
updatecb = canvas.CurrentInfoText)
stat_sel.pack(side = 'left', fill = X, expand = 1)
stat_sel.Update()
update = stat_sel.Update
canvas.Subscribe(SELECTION, update)
canvas.Subscribe(CURRENTINFO, update)
canvas.Subscribe(EDITED, update)
def EditedInfoText(self):
if self.document.WasEdited():
return _("modified")
return _("unmodified")
AddCmd('AboutBox', _("About..."))
def AboutBox(self):
abouttext = _("Skencil (%(version)s)\n"
"(c) 1996-2005 by Bernhard Herzog\n\n"
"Versions:\n"
"Python:\t%(py)s\tTcl:\t%(tcl)s\n"
"Tkinter:\t%(tkinter)s\tTk:\t%(tk)s") \
% {'version':SketchVersion,
'py':string.split(sys.version)[0],
'tcl':TclVersion,
'tkinter':string.split(Tkinter.__version__)[1],
'tk':TkVersion}
self.application.MessageBox(title = _("About Skencil"),
message = abouttext,
icon = pixmaps.IconLarge)
#
# Special methods. Mainly interesting for debugging
#
AddCmd('DocumentInfo', "Document Info...", key_stroke = 'F12')
def DocumentInfo(self):
text = self.document.DocumentInfo()
from Sketch import _sketch
meminfo = 'Memory:\n'\
'# Bezier Paths:\t%d\n'\
'# RGBColors:\t%d\n' \
'# Rects:\t\t%d\n'\
'# Trafos:\t\t%d\n'\
'# Points:\t\t%d' % (_sketch.num_allocated(),
_sketch.colors_allocated(),
_sketch.rects_allocated(),
_sketch.trafos_allocted(),
_sketch.points_allocated())
text = text + '\n\n' + meminfo
self.application.MessageBox(title = 'Document Info', message = text,
icon = 'info')
AddCmd('DumpXImage', 'Dump XImage')
def DumpXImage(self):
gc = self.canvas.gc
if gc.ximage:
gc.ximage.dump_data("/tmp/ximage.dat")
# AddCmd('export_bitmap', 'Export Bitmap')
# def export_bitmap(self):
# import export
# export.export_bitmap(self.document)
AddCmd('python_prompt', 'Python Prompt', key_stroke = 'F11')
def python_prompt(self):
if config.preferences.show_special_menu:
import prompt
prompt.PythonPrompt()
#
# Insert Special Objects
#
#
# Create Image
#
def GetOpenImageFilename(self, title = None, initialdir = '',
initialfile = '', no_eps = 0):
if title is None:
title = _("Load Image")
if no_eps:
filetypes = skapp.bitmapfiletypes
else:
filetypes = skapp.imagefiletypes
filename = self.application.GetOpenFilename(title = title,
filetypes = filetypes,
initialdir = initialdir,
initialfile = initialfile)
return filename
AddCmd('CreateImage', _("Load Raster/EPS Image..."),
bitmap = pixmaps.Image, subscribe_to = None)
def CreateImage(self, filename = None):
if not filename:
filename = self.GetOpenImageFilename(title = _("Load Image"),
initialdir = config.preferences.image_dir,
initialfile = '')
if filename:
try:
file = open(filename, 'r')
is_eps = eps.IsEpsFileStart(file.read(256))
file.close()
dir, name = os.path.split(filename)
config.preferences.image_dir = dir
if is_eps:
imageobj = eps.EpsImage(filename = filename)
else:
imageobj = image.Image(imagefile = filename)
self.canvas.PlaceObject(imageobj)
except IOError, value:
if type(value) == TupleType:
value = value[1]
self.application.MessageBox(title = _("Load Image"),
message = _("Cannot load %(filename)s:\n"
"%(message)s") \
% {'filename':`os.path.split(filename)[1]`,
'message':value},
icon = 'warning')
AddCmd('AddHorizGuideLine', _("Add Horizontal Guide Line"), 'AddGuideLine',
args = 1)
AddCmd('AddVertGuideLine', _("Add Vertical Guide Line"), 'AddGuideLine',
args = 0)
def AddGuideLine(self, horizontal = 1):
self.canvas.PlaceObject(GuideLine(Point(0, 0), horizontal))
#
#
#
AddCmd('CreateStyleFromSelection', _("Name Style..."),
sensitive_cb = ('document', 'CanCreateStyle'),
subscribe_to = SELECTION)
def CreateStyleFromSelection(self):
import styledlg
doc = self.document
object = doc.CurrentObject()
style_names = doc.GetStyleNames()
if object:
name = styledlg.GetStyleName(self.root, object, style_names)
if name:
name, which_properties = name
doc.CreateStyleFromSelection(name, which_properties)
def no_pattern(self, category):
import styledlg
if category == 'fill':
title = _("No Fill")
prop = 'fill_pattern'
else:
title = _("No Line")
prop = 'line_pattern'
styledlg.set_properties(self.root, self.document, title, category,
{prop: EmptyPattern})
#
# Document commands
#
AddDocCmd('SelectAll', _("Select All"), sensitive_cb = 'IsSelectionMode',
subscribe_to = MODE)
AddDocCmd('SelectNextObject', _("Select Next"), key_stroke = 'M-Right')
AddDocCmd('SelectPreviousObject', _("Select Previous"),
key_stroke = 'M-Left')
AddDocCmd('SelectFirstChild', _("Select First Child"),
key_stroke = 'M-Down')
AddDocCmd('SelectParent', _("Select Parent"), key_stroke = 'M-Up')
# rearrange object
AddDocCmd('RemoveSelected', _("Delete"), key_stroke = 'Delete',
bitmap = pixmaps.Delete)
AddDocCmd('MoveSelectedToTop', _("Move to Top"),
bitmap = pixmaps.MoveToTop, key_stroke = 'Home')
AddDocCmd('MoveSelectedToBottom', _("Move to Bottom"),
bitmap = pixmaps.MoveToBottom, key_stroke = 'End')
AddDocCmd('MoveSelectionUp', _("Move One Up"), bitmap = pixmaps.MoveOneUp,
key_stroke = 'S-Prior')
AddDocCmd('MoveSelectionDown', _("Move One Down"),
bitmap = pixmaps.MoveOneDown, key_stroke = 'S-Next')
AddDocCmd('DuplicateSelected', _("Duplicate"), bitmap = pixmaps.Duplicate,
key_stroke = 'C-d')
#
AddDocCmd('GroupSelected', _("Group"), sensitive_cb = 'CanGroup',
key_stroke = 'C-g', bitmap = pixmaps.Group)
AddDocCmd('UngroupSelected', _("Ungroup"), sensitive_cb = 'CanUngroup',
key_stroke = 'C-u', bitmap = pixmaps.Ungroup)
#
AddDocCmd('ConvertToCurve', _("Convert To Curve"),
sensitive_cb = 'CanConvertToCurve')
AddDocCmd('CombineBeziers', _("Combine Beziers"),
sensitive_cb = 'CanCombineBeziers')
AddDocCmd('SplitBeziers', _("Split Beziers"),
sensitive_cb = 'CanSplitBeziers')
#
# Align
#
AddDocCmd('AbutHorizontal', _("Abut Horizontal"))
AddDocCmd('AbutVertical', _("Abut Vertical"))
AddDocCmd('FlipHorizontal', _("Flip Horizontal"), 'FlipSelected',
args = (1, 0), bitmap = pixmaps.FlipHorizontal)
AddDocCmd('FlipVertical', _("Flip Vertical"), 'FlipSelected',
args = (0, 1), bitmap = pixmaps.FlipVertical)
# effects
AddDocCmd('CancelBlend', _("Cancel Blend"),
sensitive_cb = 'CanCancelBlend')
AddDocCmd('RemoveTransformation', _("Remove Transformation"))
AddDocCmd('CreateMaskGroup', _("Create Mask Group"),
sensitive_cb = 'CanCreateMaskGroup')
AddDocCmd('CreatePathText', _("Create Path Text"),
sensitive_cb = 'CanCreatePathText')
AddDocCmd('CreateClone', _("Create Clone"),
sensitive_cb = 'CanCreateClone')
#
# Cut/Paste
#
def CutCopySelected(self, method):
objects = getattr(self.document, method)()
if objects is not None:
self.application.SetClipboard(objects)
AddCmd('CopySelected', _("Copy"), 'CutCopySelected',
args= ('CopyForClipboard',), subscribe_to = SELECTION,
sensitive_cb = ('document', 'HasSelection'))
AddCmd('CutSelected', _("Cut"), 'CutCopySelected',
args= ('CutForClipboard',), subscribe_to = SELECTION,
sensitive_cb = ('document', 'HasSelection'))
AddCmd('PasteClipboard', _("Paste"),
subscribe_to = ('application', CLIPBOARD),
sensitive_cb = ('application', 'ClipboardContainsData'))
def PasteClipboard(self):
if self.application.ClipboardContainsData():
obj = self.application.GetClipboard().Object()
obj = obj.Duplicate()
self.canvas.PlaceObject(obj)
#
# Undo/Redo
#
AddDocCmd('Undo', _("Undo"), subscribe_to = UNDO,
sensitive_cb = 'CanUndo', name_cb = 'UndoMenuText',
key_stroke = 'C-z', bitmap = pixmaps.Undo)
AddDocCmd('Redo', _("Redo"), subscribe_to = UNDO,
sensitive_cb = 'CanRedo', name_cb = 'RedoMenuText',
key_stroke = 'C-r', bitmap = pixmaps.Redo)
AddDocCmd('ResetUndo', _("Discard Undo History"), subscribe_to = None,
sensitive_cb = None)
#
# Styles
#
AddDocCmd('FillNone', _("No Fill"), 'AddStyle', args = EmptyFillStyle)
AddDocCmd('LineNone', _("No Line"), 'AddStyle', args = EmptyLineStyle)
AddDocCmd('UpdateStyle', _("Update Style"), 'UpdateDynamicStyleSel')
|