# Sketch - A Python-based interactive drawing program
# Copyright (C) 1996, 1997, 1998, 1999, 2000, 2003 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
#
# Classes:
#
# SketchDocument
# EditDocument(SketchDocument)
#
# The document class represents a complete Sketch drawing. Each drawing
# consists of one or more Layers, which in turn consist of zero of more
# graphics objects. Graphics objects can be primitives like rectangles
# or curves or composite objects like groups which consist of graphics
# objects themselves. Objects may be arbitrarily nested.
#
# The distinction between SketchDocument and EditDocument has only
# historical reasons...
#
from types import ListType,IntType,StringType,TupleType
from string import join
from Sketch.warn import pdebug,warn,warn_tb,USER,INTERNAL
from Sketch import SketchInternalError
from Sketch import config,_
from Sketch.connector import Issue,RemovePublisher,Connect,Disconnect,\
QueueingPublisher, Connector
from Sketch.undodict import UndoDict
from Sketch import Rect,Point,UnionRects,InfinityRect,Trafo
from Sketch import UndoRedo,Undo,CreateListUndo,NullUndo,UndoAfter
import color, selinfo, pagelayout
from base import Protocols
from layer import Layer,GuideLayer,GridLayer
from group import Group
from bezier import CombineBeziers
from properties import EmptyProperties
from pattern import SolidPattern
import guide
from selection import SizeSelection,EditSelection,TrafoSelection
from Sketch.const import STYLE,SELECTION,EDITED,MODE,UNDO,REDRAW,LAYOUT
from Sketch.const import LAYER,LAYER_ORDER,LAYER_ACTIVE,GUIDE_LINES,GRID
from Sketch.const import SelectSet,SelectAdd,SelectSubtract,SelectSubobjects,\
SelectDrag, SelectGuide, Button1Mask
from Sketch.const import SCRIPT_OBJECT,SCRIPT_OBJECTLIST,SCRIPT_GET
#
from text import CanCreatePathText,CreatePathText
# SketchDocument is derived from Protocols for the benefit of the loader
# classes
class SketchDocument(Protocols):
can_be_empty = 1
script_access = {}
def __init__(self, create_layer = 0):
self.snap_grid = GridLayer()
self.snap_grid.SetDocument(self)
self.guide_layer = GuideLayer(_("Guide Lines"))
self.guide_layer.SetDocument(self)
if create_layer:
# a new empty document
self.active_layer = Layer(_("Layer 1"))
self.active_layer.SetDocument(self)
self.layers = [self.active_layer, self.guide_layer,
self.snap_grid]
else:
# we're being created by the load module
self.active_layer = None
self.layers = []
def __del__(self):
if __debug__:
pdebug('__del__', '__del__', self.meta.filename)
def __getitem__(self, idx):
if type(idx) == IntType:
return self.layers[idx]
elif type(idx) == TupleType:
if len(idx) > 1:
return self.layers[idx[0]][idx[1:]]
elif len(idx) == 1:
return self.layers[idx[0]]
raise ValueError, 'invalid index %s' % `idx`
def AppendLayer(self, layer_name = None, *args, **kw_args):
try:
old_layers = self.layers[:]
if layer_name is None:
layer_name = _("Layer %d") % (len(self.layers) + 1)
else:
layer_name = str(layer_name)
layer = apply(Layer, (layer_name,) + args, kw_args)
layer.SetDocument(self)
self.layers.append(layer)
if not self.active_layer:
self.active_layer = layer
return layer
except:
self.layers[:] = old_layers
raise
script_access['AppendLayer'] = SCRIPT_OBJECT
def BoundingRect(self, visible = 1, printable = 0):
rects = []
for layer in self.layers:
if ((visible and layer.Visible())
or (printable and layer.Printable())):
rect = layer.bounding_rect
if rect and rect != InfinityRect:
rects.append(rect)
if rects:
return reduce(UnionRects, rects)
return None
script_access['BoundingRect'] = SCRIPT_GET
def augment_sel_info(self, info, layeridx):
if type(layeridx) != IntType:
layeridx = self.layers.index(layeridx)
return selinfo.prepend_idx(layeridx, info)
def insert(self, object, at = None, layer = None):
undo_info = None
try:
if layer is None:
layer = self.active_layer
elif type(layer) == IntType:
layer = self.layers[layer]
if layer is None or layer.Locked():
raise SketchInternalError('Layer %s is locked' % layer)
if type(object) == ListType:
for obj in object:
obj.SetDocument(self)
else:
object.SetDocument(self)
sel_info, undo_info = layer.Insert(object, at)
sel_info = self.augment_sel_info(sel_info, layer)
return (sel_info, undo_info)
except:
if undo_info is not None:
Undo(undo_info)
raise
def selection_from_point(self, p, hitrect, device, path = None):
# iterate top down (i.e. backwards) through the list of layers
if path:
path_layer = path[0]
path = path[1:]
else:
path_layer = -1
for idx in range(len(self.layers) - 1, -1, -1):
if idx == path_layer:
info = self.layers[idx].SelectSubobject(p, hitrect, device,
path)
else:
info = self.layers[idx].SelectSubobject(p, hitrect, device)
if info:
return self.augment_sel_info(info, idx)
else:
return None
def selection_from_rect(self, rect):
info = []
for layer in self.layers:
info = info + self.augment_sel_info(layer.SelectRect(rect), layer)
return info
def Draw(self, device, rect = None):
for layer in self.layers:
layer.Draw(device, rect)
def Grid(self):
return self.snap_grid
def SnapToGrid(self, p):
return self.snap_grid.Snap(p)
def SnapToGuide(self, p, maxdist):
return self.guide_layer.Snap(p) #, maxdist)
def DocumentInfo(self):
info = []
info.append('%d layers' % len(self.layers))
for idx in range(len(self.layers)):
layer = self.layers[idx]
info.append('%d: %s,\t%d objects' % (idx + 1, layer.name,
len(layer.objects)))
return join(info, '\n')
def SaveToFile(self, file):
file.BeginDocument()
self.page_layout.SaveToFile(file)
self.write_styles(file)
for layer in self.layers:
layer.SaveToFile(file)
file.EndDocument()
def load_AppendObject(self, layer):
self.layers.append(layer)
def load_Done(self):
pass
def load_Completed(self):
if not self.layers:
self.layers = [Layer(_("Layer 1"))]
if self.active_layer is None:
for layer in self.layers:
if layer.CanSelect():
self.active_layer = layer
break
add_guide_layer = add_grid_layer = 1
for layer in self.layers:
layer.SetDocument(self)
if isinstance(layer, GuideLayer):
self.guide_layer = layer
add_guide_layer = 0
if isinstance(layer, GridLayer):
self.snap_grid = layer
add_grid_layer = 0
if add_guide_layer:
self.layers.append(self.guide_layer)
if add_grid_layer:
self.layers.append(self.snap_grid)
#
# Class MetaInfo
#
# Each document has an instance of this class as the variable
# meta. The application object uses this variable to store various
# data about the document, such as the name of the file it was
# read from, the file type, etc. See skapp.py
#
class MetaInfo:
pass
class AbortTransactionError(SketchInternalError):
pass
SelectionMode = 0
EditMode = 1
class EditDocument(SketchDocument, QueueingPublisher):
drag_mask = Button1Mask # canvas sometimes has the doc as current
# object
script_access = SketchDocument.script_access.copy()
def __init__(self, create_layer = 0):
SketchDocument.__init__(self, create_layer)
QueueingPublisher.__init__(self)
self.selection = SizeSelection()
self.__init_undo()
self.was_dragged = 0
self.meta = MetaInfo()
self.hit_cache = None
self.connector = Connector()
self.init_transaction()
self.init_clear()
self.init_styles()
self.init_after_handler()
self.init_layout()
def Destroy(self):
self.undo = None
self.destroy_styles()
RemovePublisher(self)
for layer in self.layers:
layer.Destroy()
self.layers = []
self.active_layer = None
self.guide_layer = None
self.snap_grid = None
# make self.connector empty connector to remove circular refs
# and to allow object to call document.connector.RemovePublisher
# in their __del__ methods
self.connector = Connector()
self.selection = None
self.transaction_undo = []
self.transaction_sel = []
def queue_layer(self, *args):
if self.transaction:
apply(self.queue_message, (LAYER,) + args)
return (self.queue_layer, args)
else:
apply(self.issue, (LAYER,) + args)
def queue_selection(self):
self.queue_message(SELECTION)
def queue_edited(self):
# An EDITED message should probably indicate the type of edit,
# i.e. whether properties changed, the geometry of objects
# changed, etc.; hence the additional string argument which may
# hold this information in the future
self.queue_message(EDITED, '')
return (self.queue_edited,)
def Subscribe(self, channel, func, *args):
Connect(self, channel, func, args)
def Unsubscribe(self, channel, func, *args):
Disconnect(self, channel, func, args)
def init_after_handler(self):
self.after_handlers = []
def AddAfterHandler(self, handler, args = (), depth = 0):
handler = (depth, handler, args)
try:
self.after_handlers.remove(handler)
except ValueError:
pass
self.after_handlers.append(handler)
def call_after_handlers(self):
if not self.after_handlers:
return 0
while self.after_handlers:
handlers = self.after_handlers
handlers.sort()
handlers.reverse()
depth = handlers[0][0]
count = 0
for d, handler, args in handlers:
if d == depth:
count = count + 1
else:
break
self.after_handlers = handlers[count:]
handlers = handlers[:count]
for d, handler, args in handlers:
try:
apply(handler, args)
except:
warn_tb(INTERNAL, "In after handler `%s'%s", handler, args)
return 1
def init_clear(self):
self.clear_rects = []
self.clear_all = 0
reset_clear = init_clear
def AddClearRect(self, rect):
self.clear_rects.append(rect)
return (self.AddClearRect, rect)
def view_redraw_all(self):
self.clear_all = 1
return (self.view_redraw_all,)
def issue_redraw(self):
try:
if self.clear_all:
Issue(self, REDRAW, 1)
else:
Issue(self, REDRAW, 0, self.clear_rects)
finally:
self.clear_rects = []
self.clear_all = 0
def init_transaction(self):
self.reset_transaction()
def reset_transaction(self):
self.transaction = 0
self.transaction_name = ''
self.transaction_sel = []
self.transaction_undo = []
self.transaction_sel_ignore = 0
self.transaction_clear = None
self.transaction_aborted = 0
self.transaction_cleanup = []
def cleanup_transaction(self):
for handler, args in self.transaction_cleanup:
try:
apply(handler, args)
except:
warn_tb(INTERNAL, "in cleanup handler %s%s", handler, `args`)
self.transaction_cleanup = []
def add_cleanup_handler(self, handler, *args):
handler = (handler, args)
try:
self.transaction_cleanup.remove(handler)
except ValueError:
pass
self.transaction_cleanup.append(handler)
def begin_transaction(self, name = '', no_selection = 0,
clear_selection_rect = 1):
if self.transaction_aborted:
raise AbortTransactionError
if self.transaction == 0:
if not no_selection:
selinfo = self.selection.GetInfo()[:]
if selinfo != self.transaction_sel:
self.transaction_sel = selinfo
self.transaction_sel_mode = self.selection.__class__
self.transaction_sel_ignore = no_selection
self.transaction_name = name
self.transaction_undo = []
if clear_selection_rect:
if self.selection:
self.transaction_clear = self.selection.bounding_rect
else:
self.transaction_clear = None
elif not self.transaction_name:
self.transaction_name = name
self.transaction = self.transaction + 1
def end_transaction(self, issue = (), queue_edited = 0):
self.transaction = self.transaction - 1
if self.transaction_aborted:
# end an aborted transaction
if self.transaction == 0:
# undo the changes already done...
undo = self.transaction_undo
undo.reverse()
map(Undo, undo)
self.cleanup_transaction()
self.reset_transaction()
self.reset_clear()
else:
# a normal transaction
if type(issue) == StringType:
self.queue_message(issue)
else:
for channel in issue:
self.queue_message(channel)
if self.transaction == 0:
# the outermost end_transaction
# increase transaction flag temporarily because some
# after handlers might call public methods that are
# themselves transactions...
self.transaction = 1
if self.call_after_handlers():
self.selection.ResetRectangle()
self.transaction = 0
undo = CreateListUndo(self.transaction_undo)
if undo is not NullUndo:
undo = [undo]
if self.transaction_clear is not None:
undo.append(self.AddClearRect(self.transaction_clear))
if self.selection:
self.selection.ResetRectangle()
rect = self.selection.bounding_rect
undo.append(self.AddClearRect(rect))
if queue_edited:
undo.append(self.queue_edited())
undo = CreateListUndo(undo)
if self.transaction_sel_ignore:
self.__real_add_undo(self.transaction_name, undo)
else:
self.__real_add_undo(self.transaction_name, undo,
self.transaction_sel,
self.transaction_sel_mode)
self.flush_message_queue()
self.issue_redraw()
self.cleanup_transaction()
self.reset_transaction()
self.reset_clear()
elif self.transaction < 0:
raise SketchInternalError('transaction < 0')
def abort_transaction(self):
self.transaction_aborted = 1
warn_tb(INTERNAL, "in transaction `%s'" % self.transaction_name)
raise AbortTransactionError
# public versions of the transaction methods
BeginTransaction = begin_transaction
AbortTransaction = abort_transaction
def EndTransaction(self):
self.end_transaction(queue_edited = 1)
def Insert(self, object, undo_text = _("Create Object")):
if isinstance(object, guide.GuideLine):
self.add_guide_line(object)
else:
self.begin_transaction(undo_text, clear_selection_rect = 0)
try:
try:
object.SetDocument(self)
selected, undo = self.insert(object)
self.add_undo(undo)
self.add_undo(self.AddClearRect(object.bounding_rect))
self.__set_selection(selected, SelectSet)
except:
self.abort_transaction()
finally:
self.end_transaction(queue_edited = 1)
def SelectPoint(self, p, device, type = SelectSet):
# find object at point, and modify the current selection
# according to type
self.begin_transaction(clear_selection_rect = 0)
try:
try:
if type == SelectSubobjects:
path = self.selection.GetPath()
else:
path = ()
rect = device.HitRectAroundPoint(p)
if self.hit_cache:
cp, cdevice, hit = self.hit_cache
self.hit_cache = None
if p is cp and device is cdevice:
selected = hit
else:
selected = self.selection_from_point(p, rect, device,
path)
else:
selected = self.selection_from_point(p, rect, device, path)
if type == SelectGuide:
if selected and selected[-1].is_GuideLine:
return selected[-1]
return None
elif selected:
path, object = selected
if self.layers[path[0]] is self.guide_layer:
if object.is_GuideLine:
# guide lines cannot be selected in the
# ordinary way, but other objects on the
# guide layer can.
selected = None
self.__set_selection(selected, type)
if self.IsEditMode():
object = self.CurrentObject()
if object is not None and object.is_Text:
self.SelectPointPart(p, device, SelectSet)
except:
self.abort_transaction()
finally:
self.end_transaction()
return selected
def SelectRect(self, rect, mode = SelectSet):
# Find all objects contained in rect and modify the current
# selection according to mode
self.begin_transaction(clear_selection_rect = 0)
try:
try:
self.hit_cache = None
selected = self.selection_from_rect(rect)
self.__set_selection(selected, mode)
except:
self.abort_transaction()
finally:
self.end_transaction()
return selected
def SelectRectPart(self, rect, mode = SelectSet):
# Select the part of the CSO that lies in rect. Currently this
# works only in edit mode. For a PolyBezier this means that all
# nodes within rect are selected.
if not self.IsEditMode():
raise SketchInternalError('SelectRectPart requires edit mode')
self.begin_transaction(clear_selection_rect = 0)
try:
try:
self.hit_cache = None
self.selection.SelectRect(rect, mode)
self.queue_selection()
except:
self.abort_transaction()
finally:
self.end_transaction()
def SelectPointPart(self, p, device, mode = SelectSet):
# Select the part of the current object under the point p.
# Like SelectRectPart this only works in edit mode.
self.begin_transaction(clear_selection_rect = 0)
try:
try:
self.hit_cache = None
rect = device.HitRectAroundPoint(p)
self.selection.SelectPoint(p, rect, device, mode)
if mode != SelectDrag:
self.queue_selection()
except:
self.abort_transaction()
finally:
self.end_transaction()
def SelectHandle(self, handle, mode = SelectSet):
# Select the handle indicated by handle. This only works in edit
# mode.
self.begin_transaction(clear_selection_rect = 0)
try:
try:
self.hit_cache = None
self.selection.SelectHandle(handle, mode)
if mode != SelectDrag:
self.queue_selection()
except:
self.abort_transaction()
finally:
self.end_transaction()
def SelectAll(self):
# Select all objects that can currently be selected.
# XXX should the objects in the guide layer also be selected by
# this method? (currently they are)
self.begin_transaction(clear_selection_rect = 0)
try:
try:
sel_info = []
for layer_idx in range(len(self.layers)):
sel = self.layers[layer_idx].SelectAll()
if sel:
sel = self.augment_sel_info(sel, layer_idx)
sel_info = sel_info + sel
self.__set_selection(sel_info, SelectSet)
except:
self.abort_transaction()
finally:
self.end_transaction()
script_access['SelectAll'] = SCRIPT_GET
def SelectNone(self):
# Deselect all objects.
self.begin_transaction(clear_selection_rect = 0)
try:
try:
self.__set_selection(None, SelectSet)
except:
self.abort_transaction()
finally:
self.end_transaction()
script_access['SelectNone'] = SCRIPT_GET
def SelectObject(self, objects, mode = SelectSet):
# Select the objects defined by OBJECTS. OBJECTS may be a single
# GraphicsObject or a list of such objects. Modify the current
# selection according to MODE.
self.begin_transaction(clear_selection_rect = 0)
try:
try:
if type(objects) != ListType:
objects = [objects]
selinfo = []
for object in objects:
selinfo.append(object.SelectionInfo())
if selinfo:
self.__set_selection(selinfo, mode)
else:
self.__set_selection(None, SelectSet)
except:
self.abort_transaction()
finally:
self.end_transaction()
#script_access['SelectObject'] = SCRIPT_GET
def select_first_in_layer(self, idx = 0):
for layer in self.layers[idx:]:
if layer.CanSelect() and not layer.is_SpecialLayer:
object = layer.SelectFirstChild()
if object is not None:
return object
def SelectNextObject(self):
# If exactly one object is selected select its next higher
# sibling. If there is no next sibling and its parent is a
# layer, select the first object in the next higher layer that
# allows selections.
#
# If more than one object is currently selected, deselect all
# but the the highest of them.
self.begin_transaction(clear_selection_rect = 0)
try:
try:
info = self.selection.GetInfo()
if len(info) > 1:
self.__set_selection(info[-1], SelectSet)
elif info:
path, object = info[0]
parent = object.parent
object = parent.SelectNextChild(object, path[-1])
if object is None and parent.is_Layer:
idx = self.layers.index(parent)
object = self.select_first_in_layer(idx + 1)
if object is not None:
self.SelectObject(object)
else:
object = self.select_first_in_layer()
if object is not None:
self.SelectObject(object)
except:
self.abort_transaction()
finally:
self.end_transaction()
script_access['SelectNextObject'] = SCRIPT_GET
def select_last_in_layer(self, idx):
if idx < 0:
return
layers = self.layers[:idx + 1]
layers.reverse()
for layer in layers:
if layer.CanSelect() and not layer.is_SpecialLayer:
object = layer.SelectLastChild()
if object is not None:
return object
def SelectPreviousObject(self):
# If exactly one object is selected select its next lower
# sibling. If there is no lower sibling and its parent is a
# layer, select the last object in the next lower layer that
# allows selections.
#
# If more than one object is currently selected, deselect all
# but the the lowest of them.
self.begin_transaction(clear_selection_rect = 0)
try:
try:
info = self.selection.GetInfo()
if len(info) > 1:
self.__set_selection(info[0], SelectSet)
elif info:
path, object = info[0]
parent = object.parent
object = parent.SelectPreviousChild(object, path[-1])
if object is None and parent.is_Layer:
idx = self.layers.index(parent)
object = self.select_last_in_layer(idx - 1)
if object is not None:
self.SelectObject(object)
else:
object = self.select_last_in_layer(len(self.layers))
if object is not None:
self.SelectObject(object)
except:
self.abort_transaction()
finally:
self.end_transaction()
script_access['SelectPreviousObject'] = SCRIPT_GET
def SelectFirstChild(self):
# If exactly one object is selected and this object is a
# compound object, select its first (lowest) child. The first
# child is the object returned by the compound object's method
# SelectFirstChild. If that method returns none, do nothing.
self.begin_transaction(clear_selection_rect = 0)
try:
try:
objects = self.selection.GetObjects()
if len(objects) == 1:
object = objects[0]
if object.is_Compound:
object = object.SelectFirstChild()
if object is not None:
self.SelectObject(object)
except:
self.abort_transaction()
finally:
self.end_transaction()
script_access['SelectFirstChild'] = SCRIPT_GET
def SelectParent(self):
# Select the parent of the currently selected object(s).
self.begin_transaction(clear_selection_rect = 0)
try:
try:
if len(self.selection) > 1:
path = selinfo.common_prefix(self.selection.GetInfo())
if len(path) > 1:
object = self[path]
self.SelectObject(object)
elif len(self.selection) == 1:
object = self.selection.GetObjects()[0].parent
if not object.is_Layer:
self.SelectObject(object)
except:
self.abort_transaction()
finally:
self.end_transaction()
script_access['SelectParent'] = SCRIPT_GET
def DeselectObject(self, object):
# Deselect the object OBJECT.
# XXX: for large selections this can be very slow.
selected = self.selection.GetObjects()
try:
index = selected.index(object)
except ValueError:
return
info = self.selection.GetInfo()
del info[index]
self.__set_selection(info, SelectSet)
def __set_selection(self, selected, type):
# Modify the current selection. SELECTED is a list of selection
# info describing the new selection, TYPE indicates how the
# current selection is modified:
#
# type Meaning
# SelectSet Replace the old selection by the new one
# SelectSubtract Subtract the new selection from the old one
# SelectAdd Add the new selection to the old one.
# SelectSubobjects like SelectSet here
changed = 0
if type == SelectAdd:
if selected:
changed = self.selection.Add(selected)
elif type == SelectSubtract:
if selected:
changed = self.selection.Subtract(selected)
elif type == SelectGuide:
if selected:
pass
else:
# type is SelectSet or SelectSubobjects
# set the selection. make a size selection if necessary
if self.selection.__class__ == TrafoSelection:
self.selection = SizeSelection()
changed = 1
changed = self.selection.SetSelection(selected) or changed
if changed:
self.queue_selection()
def SetMode(self, mode):
self.begin_transaction(clear_selection_rect = 0)
try:
try:
if mode == SelectionMode:
self.selection = SizeSelection(self.selection)
else:
self.selection = EditSelection(self.selection)
except:
self.abort_transaction()
finally:
self.end_transaction(issue = (SELECTION, MODE))
def Mode(self):
if self.selection.__class__ == EditSelection:
return EditMode
return SelectionMode
script_access['Mode'] = SCRIPT_GET
def IsSelectionMode(self):
return self.Mode() == SelectionMode
script_access['IsSelectionMode'] = SCRIPT_GET
def IsEditMode(self):
return self.Mode() == EditMode
script_access['IsEditMode'] = SCRIPT_GET
def SelectionHit(self, p, device, test_all = 1):
# Return true, if the point P hits the currently selected
# objects.
#
# If test_all is true (the default), find the object that would
# be selected by SelectPoint and return true if it or one of its
# ancestors is contained in the current selection and false
# otherwise.
#
# If test_all is false, just test the currently selected objects.
rect = device.HitRectAroundPoint(p)
if len(self.selection) < 10 or not test_all:
selection_hit = self.selection.Hit(p, rect, device)
if not test_all or not selection_hit:
return selection_hit
if test_all:
path = self.selection.GetPath()
if len(path) > 2:
path = path[:-1]
else:
path = ()
hit = self.selection_from_point(p, rect, device, path)
self.hit_cache = (p, device, hit)
while hit:
if hit in self.selection.GetInfo():
return 1
hit = selinfo.get_parent(hit)
#self.hit_cache = None
return 0
def GetSelectionHandles(self):
if self.selection:
return self.selection.GetHandles()
else:
return []
#
# Get information about the selected objects
#
def HasSelection(self):
# Return true, if one or more objects are selected
return len(self.selection)
script_access['HasSelection'] = SCRIPT_GET
def CountSelected(self):
# Return the number of currently selected objects
return len(self.selection)
script_access['CountSelected'] = SCRIPT_GET
def SelectionInfoText(self):
# Return a string describing the selected object(s)
return self.selection.InfoText()
script_access['SelectionInfoText'] = SCRIPT_GET
def CurrentInfoText(self):
return self.selection.CurrentInfoText()
def SelectionBoundingRect(self):
# Return the bounding rect of the current selection
return self.selection.bounding_rect
script_access['SelectionBoundingRect'] = SCRIPT_GET
def CurrentObject(self):
# If exactly one object is selected return that, None instead.
if len(self.selection) == 1:
return self.selection.GetObjects()[0]
return None
script_access['CurrentObject'] = SCRIPT_OBJECT
def SelectedObjects(self):
# Return the selected objects as a list. They are listed in the
# order in which they are drawn.
return self.selection.GetObjects()
script_access['SelectedObjects'] = SCRIPT_OBJECTLIST
def CurrentProperties(self):
# Return the properties of the current object if exactly one
# object is selected. Return EmptyProperties otherwise.
if self.selection:
if len(self.selection) > 1:
return EmptyProperties
return self.selection.GetInfo()[0][-1].Properties()
return EmptyProperties
script_access['CurrentProperties'] = SCRIPT_OBJECT
def CurrentFillColor(self):
# Return the fill color of the current object if exactly one
# object is selected and that object has a solid fill. Return
# None otherwise.
if len(self.selection) == 1:
properties = self.selection.GetInfo()[0][-1].Properties()
try:
return properties.fill_pattern.Color()
except AttributeError:
pass
return None
script_access['CurrentFillColor'] = SCRIPT_GET
def PickObject(self, device, point, selectable = 0):
# Return the object that is hit by a click at POINT. The object
# is not selected and should not be modified by the caller.
#
# If selectable is false, this function descends into compound
# objects that are normally selected as a whole when one of
# their children is hit. If selectable is true, the search is
# done as for a normal selection.
#
# This method is intended to be used to
# let the user click on the drawing and extract properties from
# the indicated object. The fill and line dialogs use this
# indirectly (through the canvas object's PickObject) for their
# 'Update From...' button.
#
# XXX should this be implemented by calling WalkHierarchy
# instead of requiring a special PickObject method in each
# compound? Unlike the normal hit-test, this method is not that
# time critical and WalkHierarchy is sufficiently fast for most
# purposes (see extract_snap_points in the canvas).
# WalkHierarchy would have to be able to traverse the hierarchy
# top down and not just bottom up.
object = None
rect = device.HitRectAroundPoint(point)
if not selectable:
layers = self.layers[:]
layers.reverse()
for layer in layers:
object = layer.PickObject(point, rect, device)
if object is not None:
break
else:
selected = self.selection_from_point(point, rect, device)
if selected:
object = selected[-1]
return object
def PickActiveObject(self, device, p):
# return the object under point if it's selected or a guide
# line. None otherwise.
rect = device.HitRectAroundPoint(p)
path = self.selection.GetPath()
if len(path) > 2:
path = path[:-1]
else:
path = ()
hit = self.selection_from_point(p, rect, device, path)
#self.hit_cache = (p, device, hit)
if hit:
if not hit[-1].is_GuideLine:
while hit:
if hit in self.selection.GetInfo():
hit = hit[-1]
break
hit = selinfo.get_parent(hit)
else:
hit = hit[-1]
return hit
#
#
#
def WalkHierarchy(self, func, printable = 1, visible = 1, all = 0):
# XXX make the selection of layers more versatile
for layer in self.layers:
if (all
or printable and layer.Printable()
or visible and layer.Visible()):
layer.WalkHierarchy(func)
#
#
#
def ButtonDown(self, p, button, state):
self.was_dragged = 0
self.old_change_rect = self.selection.ChangeRect()
result = self.selection.ButtonDown(p, button, state)
return result
def MouseMove(self, p, state):
self.was_dragged = 1
self.selection.MouseMove(p, state)
def ButtonUp(self, p, button, state):
self.begin_transaction(clear_selection_rect = 0)
try:
try:
if self.was_dragged:
undo_text, undo_edit \
= self.selection.ButtonUp(p, button, state)
if undo_edit is not None and undo_edit != NullUndo:
self.add_undo(undo_text, undo_edit)
uc1 = self.AddClearRect(self.old_change_rect)
uc2 = self.AddClearRect(self.selection.ChangeRect())
self.add_undo(uc1, uc2)
self.add_undo(self.queue_edited())
else:
# the user probably just moved the rotation
# center point. The canvas has to update the
# handles
self.queue_selection()
else:
self.selection.ButtonUp(p, button, state, forget_trafo = 1)
self.ToggleSelectionBehaviour()
except:
self.abort_transaction()
finally:
self.end_transaction()
def ToggleSelectionBehaviour(self):
self.begin_transaction(clear_selection_rect = 0)
try:
try:
if self.selection.__class__ == SizeSelection:
self.selection = TrafoSelection(self.selection)
elif self.selection.__class__ == TrafoSelection:
self.selection = SizeSelection(self.selection)
self.queue_selection()
except:
self.abort_transaction()
finally:
self.end_transaction()
def DrawDragged(self, device, partially = 0):
self.selection.DrawDragged(device, partially)
def Hide(self, device, partially = 0):
self.selection.Hide(device, partially)
def Show(self, device, partially = 0):
self.selection.Show(device, partially)
def ChangeRect(self):
return self.selection.ChangeRect()
#
# The undo mechanism
#
def __init_undo(self):
self.undo = UndoRedo()
def CanUndo(self):
return self.undo.CanUndo()
script_access['CanUndo'] = SCRIPT_GET
def CanRedo(self):
return self.undo.CanRedo()
script_access['CanRedo'] = SCRIPT_GET
def Undo(self):
if self.undo.CanUndo():
self.begin_transaction(clear_selection_rect = 0)
try:
try:
self.undo.Undo()
except:
self.abort_transaction()
finally:
self.end_transaction(issue = UNDO)
script_access['Undo'] = SCRIPT_GET
def add_undo(self, *infos):
# Add undoinfo for the current transaction. should not be called
# when not in a transaction.
if infos:
if type(infos[0]) == StringType:
if not self.transaction_name:
self.transaction_name = infos[0]
infos = infos[1:]
if not infos:
return
for info in infos:
if type(info) == ListType:
info = CreateListUndo(info)
else:
if type(info[0]) == StringType:
if __debug__:
pdebug(None, 'add_undo: info contains text')
info = info[1:]
self.transaction_undo.append(info)
# public version of add_undo. to be called between calls to
# BeginTransaction and EndTransaction/AbortTransaction
AddUndo = add_undo
def __undo_set_sel(self, selclass, selinfo, redo_class, redo_info):
old_class = self.selection.__class__
if old_class != selclass:
self.selection = selclass(selinfo)
self.queue_message(MODE)
else:
# keep the same selection object to avoid creating a new
# editor object in EditMode
self.selection.SetSelection(selinfo)
self.queue_selection()
return (self.__undo_set_sel, redo_class, redo_info, selclass, selinfo)
def __real_add_undo(self, text, undo, selinfo = None, selclass = None):
if undo is not NullUndo:
if selinfo is not None:
new_class = self.selection.__class__
new_info = self.selection.GetInfo()[:]
if new_info == selinfo:
# make both lists identical
new_info = selinfo
undo_sel = (self.__undo_set_sel, selclass, selinfo,
new_class, new_info)
info = (text, UndoAfter, undo_sel, undo)
else:
info = (text, undo)
self.undo.AddUndo(info)
self.queue_message(UNDO)
def Redo(self):
if self.undo.CanRedo():
self.begin_transaction(clear_selection_rect = 0)
try:
try:
self.undo.Redo()
except:
self.abort_transaction()
finally:
self.end_transaction(issue = UNDO)
script_access['Redo'] = SCRIPT_GET
def ResetUndo(self):
self.begin_transaction(clear_selection_rect = 0)
try:
try:
self.undo.Reset()
except:
self.abort_transaction()
finally:
self.end_transaction(issue = UNDO)
script_access['ResetUndo'] = SCRIPT_GET
def UndoMenuText(self):
return self.undo.UndoText()
script_access['UndoMenuText'] = SCRIPT_GET
def RedoMenuText(self):
return self.undo.RedoText()
script_access['RedoMenuText'] = SCRIPT_GET
def SetUndoLimit(self, limit):
self.begin_transaction(clear_selection_rect = 0)
try:
try:
self.undo.SetUndoLimit(limit)
except:
self.abort_transaction()
finally:
self.end_transaction(issue = UNDO)
script_access['SetUndoLimit'] = SCRIPT_GET
def WasEdited(self):
# return true if document has changed since last save
return self.undo.UndoCount()
script_access['WasEdited'] = SCRIPT_GET
def ClearEdited(self):
self.undo.ResetUndoCount()
self.issue(UNDO)
#
#
def apply_to_selected(self, undo_text, func):
if self.selection:
self.begin_transaction(undo_text)
try:
try:
self.add_undo(self.selection.ForAllUndo(func))
self.queue_selection()
except:
self.abort_transaction()
finally:
self.end_transaction()
def AddStyle(self, style):
if type(style) == StringType:
style = self.GetDynamicStyle(style)
self.apply_to_selected(_("Add Style"),
lambda o, style = style: o.AddStyle(style))
def SetLineColor(self, color):
# Set the line color of the currently selected objects.
# XXX this method shpuld be removed in favour of the more
# generic SetProperties.
self.SetProperties(line_pattern = SolidPattern(color),
if_type_present = 1)
def SetProperties(self, **kw):
self.apply_to_selected(_("Set Properties"),
lambda o, kw=kw: apply(o.SetProperties, (), kw))
def SetStyle(self, style):
if type(style) == StringType:
style = self.get_dynamic_style(style)
self.AddStyle(style)
#
# Deleting and rearranging objects...
#
def remove_objects(self, infolist):
split = selinfo.list_to_tree(infolist)
undo = []
try:
for layer, infolist in split:
undo.append(self.layers[layer].RemoveObjects(infolist))
return CreateListUndo(undo)
except:
Undo(CreateListUndo(undo))
raise
def remove_selected(self):
return self.remove_objects(self.selection.GetInfo())
def RemoveSelected(self):
# Remove all selected objects. After successful completion, the
# selection will be empty.
if self.selection:
self.begin_transaction(_("Delete"))
try:
try:
self.add_undo(self.remove_selected())
self.__set_selection(None, SelectSet)
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
def __call_layer_method_sel(self, undotext, methodname, *args):
if not self.selection:
return
self.begin_transaction(undotext)
try:
try:
split = selinfo.list_to_tree(self.selection.GetInfo())
edited = 0
selection = []
for layer, infolist in split:
method = getattr(self.layers[layer], methodname)
sel, undo = apply(method, (infolist,) + args)
if undo is not NullUndo:
self.add_undo(undo)
edited = 1
selection = selection + self.augment_sel_info(sel, layer)
self.__set_selection(selection, SelectSet)
if edited:
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
def MoveSelectedToTop(self):
self.__call_layer_method_sel(_("Move To Top"), 'MoveObjectsToTop')
def MoveSelectedToBottom(self):
self.__call_layer_method_sel(_("Move To Bottom"),'MoveObjectsToBottom')
def MoveSelectionDown(self):
self.__call_layer_method_sel(_("Lower"), 'MoveObjectsDown')
def MoveSelectionUp(self):
self.__call_layer_method_sel(_("Raise"), 'MoveObjectsUp')
def MoveSelectionToLayer(self, layer):
if self.selection:
self.begin_transaction(_("Move Selection to `%s'")
% self.layers[layer].Name())
try:
try:
# remove the objects from the document...
self.add_undo(self.remove_selected())
# ... and insert them a the end of the layer
objects = self.selection.GetObjects()
select, undo_insert = self.insert(objects, layer = layer)
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
#
# Cut/Copy
#
def copy_objects(self, objects):
copies = []
for obj in objects:
copies.append(obj.Duplicate())
if len(copies) > 1:
copies = Group(copies)
else:
copies = copies[0]
# This is ugly: Special case for internal path text objects.
# If the internal path text object is the only selected
# object, turn the copy into a normal simple text object.
# Thsi avoids some of the problems when you "Copy" an
# internal path text.
import text
if copies.is_PathTextText:
properties = copies.Properties().Duplicate()
copies = text.SimpleText(text = copies.Text(),
properties = properties)
copies.UntieFromDocument()
copies.SetDocument(None)
return copies
def CopyForClipboard(self):
if self.selection:
return self.copy_objects(self.selection.GetObjects())
def CutForClipboard(self):
result = None
if self.selection:
self.begin_transaction(_("Cut"))
try:
try:
objects = self.selection.GetObjects()
result = self.copy_objects(objects)
self.add_undo(self.remove_selected())
self.__set_selection(None, SelectSet)
self.add_undo(self.queue_edited())
except:
result = None
self.abort_transaction()
finally:
self.end_transaction()
return result
#
# Duplicate
#
def DuplicateSelected(self, offset = None):
if offset is None:
offset = Point(config.preferences.duplicate_offset)
self.__call_layer_method_sel(_("Duplicate"), 'DuplicateObjects',
offset)
#
# Group
#
def group_selected(self, title, creator):
self.begin_transaction(title)
try:
try:
self.add_undo(self.remove_selected())
objects = self.selection.GetObjects()
group = creator(objects)
parent = selinfo.common_prefix(self.selection.GetInfo())
if parent:
layer = parent[0]
at = parent[1:]
else:
layer = None
at = None
select, undo_insert = self.insert(group, at = at, layer =layer)
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
except:
self.abort_transaction()
finally:
self.end_transaction()
def CanGroup(self):
return len(self.selection) > 1
def GroupSelected(self):
if self.CanGroup():
self.group_selected(_("Create Group"), Group)
def CanUngroup(self):
infos = self.selection.GetInfo()
return len(infos) == 1 and infos[0][-1].is_Group
def UngroupSelected(self):
if self.CanUngroup():
self.begin_transaction(_("Ungroup"))
try:
try:
self.add_undo(self.remove_selected())
info, group = self.selection.GetInfo()[0]
objects = group.Ungroup()
select, undo_insert = self.insert(objects, at = info[1:],
layer = info[0])
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
except:
self.abort_transaction()
finally:
self.end_transaction()
def CanCreateMaskGroup(self):
infos = self.selection.GetInfo()
return len(infos) > 1 and infos[-1][-1].is_clip
def CreateMaskGroup(self):
if self.CanCreateMaskGroup():
self.begin_transaction(_("Create Mask Group"))
try:
try:
import maskgroup
self.add_undo(self.remove_selected())
objects = self.selection.GetObjects()
if config.preferences.topmost_is_mask:
mask = objects[-1]
del objects[-1]
objects.insert(0, mask)
group = maskgroup.MaskGroup(objects)
parent = selinfo.common_prefix(self.selection.GetInfo())
if parent:
layer = parent[0]
at = parent[1:]
else:
layer = None
at = None
select, undo_insert = self.insert(group, at = at,
layer = layer)
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
except:
self.abort_transaction()
finally:
self.end_transaction()
#
# Transform, Translate, ...
#
def TransformSelected(self, trafo, undo_text = _("Transform")):
self.apply_to_selected(undo_text, lambda o, t = trafo: o.Transform(t))
def TranslateSelected(self, offset, undo_text = _("Translate")):
self.apply_to_selected(undo_text, lambda o, v = offset: o.Translate(v))
def RemoveTransformation(self):
self.apply_to_selected(_("Remove Transformation"),
lambda o: o.RemoveTransformation())
#
# Align, Flip, ...
#
# XXX These functions could be implemented outside of the document.
# (Maybe by command plugins or scripts?)
#
def AlignSelection(self, x, y, reference = 'selection'):
if self.selection and (x or y):
self.begin_transaction(_("Align Objects"))
try:
try:
add_undo = self.add_undo
objects = self.selection.GetObjects()
if reference == 'page':
br = self.PageRect()
elif reference == 'lowermost':
br = objects[0].coord_rect
else:
br = self.selection.coord_rect
for obj in objects:
r = obj.coord_rect
xoff = yoff = 0
if x == 1:
xoff = br.left - r.left
elif x == 3:
xoff = br.right - r.right
elif x == 2:
xoff = (br.left + br.right - r.left - r.right) / 2
if y == 1:
yoff = br.top - r.top
elif y == 3:
yoff = br.bottom - r.bottom
elif y == 2:
yoff = (br.top + br.bottom - r.top - r.bottom) / 2
add_undo(obj.Translate(Point(xoff, yoff)))
add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
def AbutHorizontal(self):
if len(self.selection) > 1:
self.begin_transaction(_("Abut Horizontal"))
try:
try:
pos = []
for obj in self.selection.GetObjects():
rect = obj.coord_rect
pos.append((rect.left, rect.top,
rect.right - rect.left, obj))
pos.sort()
undo = []
start, top, width, ob = pos[0]
next = start + width
for left, top, width, obj in pos[1:]:
off = Point(next - left, 0)
self.add_undo(obj.Translate(off))
next = next + width
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
def AbutVertical(self):
if len(self.selection) > 1:
self.begin_transaction(_("Abut Vertical"))
try:
try:
pos = []
for obj in self.selection.GetObjects():
rect = obj.coord_rect
pos.append((rect.top, -rect.left,
rect.top - rect.bottom, obj))
pos.sort()
pos.reverse()
undo = []
start, left, height, ob = pos[0]
next = start - height
for top, left, height, obj in pos[1:]:
off = Point(0, next - top)
self.add_undo(obj.Translate(off))
next = next - height
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
def FlipSelected(self, horizontal = 0, vertical = 0):
if self.selection and (horizontal or vertical):
self.begin_transaction()
try:
try:
rect = self.selection.coord_rect
if horizontal:
xoff = rect.left + rect.right
factx = -1
text = _("Flip Horizontal")
else:
xoff = 0
factx = 1
if vertical:
yoff = rect.top + rect.bottom
facty = -1
text = _("Flip Vertical")
else:
yoff = 0
facty = 1
if horizontal and vertical:
text = _("Flip Both")
trafo = Trafo(factx, 0, 0, facty, xoff, yoff)
self.TransformSelected(trafo, text)
except:
self.abort_transaction()
finally:
self.end_transaction()
#
#
#
def CallObjectMethod(self, aclass, description, methodname, *args):
self.begin_transaction(description)
try:
try:
undo = self.selection.CallObjectMethod(aclass, methodname,
args)
if undo != NullUndo:
self.add_undo(undo)
self.add_undo(self.queue_edited())
# force recomputation of selections rects:
self.selection.ResetRectangle()
else:
# in case the handles have to be updated
self.queue_selection()
except:
self.abort_transaction()
finally:
self.end_transaction()
def GetObjectMethod(self, aclass, method):
return self.selection.GetObjectMethod(aclass, method)
def CurrentObjectCompatible(self, aclass):
obj = self.CurrentObject()
if obj is not None:
if aclass.is_Editor:
return isinstance(obj, aclass.EditedClass)
else:
return isinstance(obj, aclass)
return 0
# XXX the following methods for blend groups, path text, clones and
# bezier objects should perhaps be implemented in their respective
# modules (and then somehow grafted onto the document class?)
def CanBlend(self):
info = self.selection.GetInfo()
if len(info) == 2:
path1, obj1 = info[0]
path2, obj2 = info[1]
if len(path1) == len(path2) + 1:
return obj1.parent.is_Blend and 2
if len(path1) + 1 == len(path2):
return obj2.parent.is_Blend and 2
return len(path1) == len(path2)
return 0
def Blend(self, steps):
info = self.selection.GetInfo()
path1, obj1 = info[0]
path2, obj2 = info[1]
if len(path1) == len(path2) + 1:
if obj1.parent.is_Blend:
del info[0]
else:
return
elif len(path1) + 1 == len(path2):
if obj2.parent.is_Blend:
del info[1]
else:
return
elif len(path1) != len(path2):
return
if steps >= 2:
import blendgroup, blend
self.begin_transaction(_("Blend"))
try:
try:
self.add_undo(self.remove_objects(info))
try:
blendgrp, undo = blendgroup.CreateBlendGroup(obj1,obj2,
steps)
self.add_undo(undo)
except blend.MismatchError:
warn(USER, _("I can't blend the selected objects"))
# XXX: is there some other solution?:
raise
if len(info) == 2:
select, undo_insert = self.insert(blendgrp,
at = path1[1:],
layer = path1[0])
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
else:
self.SelectObject(blendgrp)
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
def CanCancelBlend(self):
info = self.selection.GetInfo()
return len(info) == 1 and info[0][-1].is_Blend
def CancelBlend(self):
if self.CanCancelBlend():
self.begin_transaction(_("Cancel Blend"))
try:
try:
info = self.selection.GetInfo()[0]
self.add_undo(self.remove_selected())
group = info[-1]
objs = group.CancelEffect()
info = info[0]
layer = info[0]
at = info[1:]
select, undo_insert = self.insert(objs, at = at,
layer = layer)
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
#
#
def CanCreatePathText(self):
return CanCreatePathText(self.selection.GetObjects())
def CreatePathText(self):
if self.CanCreatePathText():
self.begin_transaction(_("Create Path Text"))
try:
try:
self.add_undo(self.remove_selected())
object = CreatePathText(self.selection.GetObjects())
select, undo_insert = self.insert(object)
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
#
# Clone (under construction...)
#
def CanCreateClone(self):
if len(self.selection) == 1:
obj = self.selection.GetObjects()[0]
return not obj.is_Compound
return 0
def CreateClone(self):
if self.CanCreateClone():
self.begin_transaction(_("Create Clone"))
try:
try:
from clone import CreateClone
object = self.selection.GetObjects()[0]
clone, undo_clone = CreateClone(object)
self.add_undo(undo_clone)
select, undo_insert = self.insert(clone)
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
#
# Bezier Curves
#
def CanCombineBeziers(self):
if len(self.selection) > 1:
can = 1
for obj in self.selection.GetObjects():
can = can and obj.is_Bezier
return can
return 0
def CombineBeziers(self):
if self.CanCombineBeziers():
self.begin_transaction(_("Combine Beziers"))
try:
try:
self.add_undo(self.remove_selected())
objects = self.selection.GetObjects()
combined = CombineBeziers(objects)
select, undo_insert = self.insert(combined)
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
def CanSplitBeziers(self):
return len(self.selection) == 1 \
and self.selection.GetObjects()[0].is_Bezier
def SplitBeziers(self):
if self.CanSplitBeziers():
self.begin_transaction(_("Split Beziers"))
try:
try:
self.add_undo(self.remove_selected())
info, bezier = self.selection.GetInfo()[0]
objects = bezier.PathsAsObjects()
select, undo_insert = self.insert(objects, at = info[1:],
layer =info[0])
self.add_undo(undo_insert)
self.__set_selection(select, SelectSet)
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
def CanConvertToCurve(self):
return len(self.selection) == 1 \
and self.selection.GetObjects()[0].is_curve
def ConvertToCurve(self):
if self.CanConvertToCurve():
self.begin_transaction(_("Convert To Curve"))
try:
try:
selection = []
edited = 0
for info, object in self.selection.GetInfo():
if object.is_Bezier:
selection.append((info, object))
else:
bezier = object.AsBezier()
parent = object.parent
self.add_undo(parent.ReplaceChild(object, bezier))
selection.append((info, bezier))
edited = 1
self.__set_selection(selection, SelectSet)
if edited:
self.add_undo(self.queue_edited())
except:
self.abort_transaction()
finally:
self.end_transaction()
#
#
#
def Layers(self):
return self.layers[:]
def NumLayers(self):
return len(self.layers)
def ActiveLayer(self):
return self.active_layer
def ActiveLayerIdx(self):
if self.active_layer is None:
return None
return self.layers.index(self.active_layer)
def SetActiveLayer(self, idx):
if type(idx) == IntType:
layer = self.layers[idx]
else:
layer = idx
if not layer.Locked():
self.active_layer = layer
self.queue_layer(LAYER_ACTIVE)
def LayerIndex(self, layer):
return self.layers.index(layer)
def update_active_layer(self):
if self.active_layer is not None and self.active_layer.CanSelect():
return
self.find_active_layer()
def find_active_layer(self, idx = None):
if idx is not None:
layer = self.layers[idx]
if layer.CanSelect():
self.SetActiveLayer(idx)
return
for layer in self.layers:
if layer.CanSelect():
self.SetActiveLayer(layer)
return
self.active_layer = None
self.queue_layer(LAYER_ACTIVE)
def deselect_layer(self, layer_idx):
# Deselect all objects in layer given by layer_idx
# Called when a layer is deleted or becomes locked
sel = selinfo.list_to_tree(self.selection.GetInfo())
for idx, info in sel:
if idx == layer_idx:
self.__set_selection(selinfo.tree_to_list([(idx, info)]),
SelectSubtract)
def SelectLayer(self, layer_idx, mode = SelectSet):
# Select all children of the layer given by layer_idx
self.begin_transaction(_("Select Layer"), clear_selection_rect = 0)
try:
try:
layer = self.layers[layer_idx]
info = self.augment_sel_info(layer.SelectAll(), layer_idx)
self.__set_selection(info, mode)
except:
self.abort_transaction()
finally:
self.end_transaction()
def SetLayerState(self, layer_idx, visible, printable, locked, outlined):
self.begin_transaction(_("Change Layer State"),
clear_selection_rect = 0)
try:
try:
layer = self.layers[layer_idx]
self.add_undo(layer.SetState(visible, printable, locked,
outlined))
if not layer.CanSelect():
# XXX: this depends on whether we're drawing visible or
# printable layers
self.deselect_layer(layer_idx)
self.update_active_layer()
except:
self.abort_transaction()
finally:
self.end_transaction()
def SetLayerColor(self, layer_idx, color):
self.begin_transaction(_("Set Layer Outline Color"),
clear_selection_rect = 0)
try:
try:
layer = self.layers[layer_idx]
self.add_undo(layer.SetOutlineColor(color))
except:
self.abort_transaction()
finally:
self.end_transaction()
def SetLayerName(self, idx, name):
self.begin_transaction(_("Rename Layer"), clear_selection_rect = 0)
try:
try:
layer = self.layers[idx]
self.add_undo(layer.SetName(name))
self.add_undo(self.queue_layer())
except:
self.abort_transaction()
finally:
self.end_transaction()
def AppendLayer(self, *args, **kw_args):
self.begin_transaction(_("Append Layer"),clear_selection_rect = 0)
try:
try:
layer = apply(SketchDocument.AppendLayer, (self,) + args,
kw_args)
self.add_undo((self._remove_layer, len(self.layers) - 1))
self.queue_layer(LAYER_ORDER, layer)
except:
self.abort_transaction()
finally:
self.end_transaction()
return layer
def NewLayer(self):
self.begin_transaction(_("New Layer"), clear_selection_rect = 0)
try:
try:
self.AppendLayer()
self.active_layer = self.layers[-1]
except:
self.abort_transaction()
finally:
self.end_transaction()
def _move_layer_up(self, idx):
# XXX: exception handling
if idx < len(self.layers) - 1:
# move the layer...
layer = self.layers[idx]
del self.layers[idx]
self.layers.insert(idx + 1, layer)
other = self.layers[idx]
# ... and adjust the selection
sel = self.selection.GetInfoTree()
newsel = []
for i, info in sel:
if i == idx:
i = idx + 1
elif i == idx + 1:
i = idx
newsel.append((i, info))
self.__set_selection(selinfo.tree_to_list(newsel), SelectSet)
self.queue_layer(LAYER_ORDER, layer, other)
return (self._move_layer_down, idx + 1)
return None
def _move_layer_down(self, idx):
# XXX: exception handling
if idx > 0:
# move the layer...
layer = self.layers[idx]
del self.layers[idx]
self.layers.insert(idx - 1, layer)
other = self.layers[idx]
# ...and adjust the selection
sel = self.selection.GetInfoTree()
newsel = []
for i, info in sel:
if i == idx:
i = idx - 1
elif i == idx - 1:
i = idx
newsel.append((i, info))
self.__set_selection(selinfo.tree_to_list(newsel), SelectSet)
self.queue_layer(LAYER_ORDER, layer, other)
return (self._move_layer_up, idx - 1)
return NullUndo
def MoveLayerUp(self, idx):
if idx < len(self.layers) - 1:
self.begin_transaction(_("Move Layer Up"), clear_selection_rect=0)
try:
try:
self.add_undo(self._move_layer_up(idx))
except:
self.abort_transaction()
finally:
self.end_transaction()
def MoveLayerDown(self, idx):
if idx > 0:
self.begin_transaction(_("Move Layer Down"),clear_selection_rect=0)
try:
try:
self.add_undo(self._move_layer_down(idx))
except:
self.abort_transaction()
finally:
self.end_transaction()
def _remove_layer(self, idx):
layer = self.layers[idx]
del self.layers[idx]
if layer is self.active_layer:
if idx < len(self.layers):
self.find_active_layer(idx)
else:
self.find_active_layer()
sel = self.selection.GetInfoTree()
newsel = []
for i, info in sel:
if i == idx:
continue
elif i > idx:
i = i - 1
newsel.append((i, info))
self.__set_selection(selinfo.tree_to_list(newsel), SelectSet)
self.queue_layer(LAYER_ORDER, layer)
return (self._insert_layer, idx, layer)
def _insert_layer(self, idx, layer):
self.layers.insert(idx, layer)
layer.SetDocument(self)
self.queue_layer(LAYER_ORDER, layer)
return (self._remove_layer, idx)
def CanDeleteLayer(self, idx):
return (len(self.layers) > 3 and not self.layers[idx].is_SpecialLayer)
def DeleteLayer(self, idx):
if self.CanDeleteLayer(idx):
self.begin_transaction(_("Delete Layer"), clear_selection_rect = 0)
try:
try:
self.add_undo(self._remove_layer(idx))
except:
self.abort_transaction()
finally:
self.end_transaction()
#
# Style management
#
def queue_style(self):
self.queue_message(STYLE)
return (self.queue_style,)
def init_styles(self):
self.styles = UndoDict()
self.auto_assign_styles = 1
self.asked_about = {}
def destroy_styles(self):
for style in self.styles.values():
style.Destroy()
self.styles = None
def get_dynamic_style(self, name):
return self.styles[name]
def GetDynamicStyle(self, name):
try:
return self.styles[name]
except KeyError:
return None
def Styles(self):
return self.styles.values()
def write_styles(self, file):
for style in self.styles.values():
style.SaveToFile(file)
def load_AddStyle(self, style):
self.styles.SetItem(style.Name(), style)
def add_dynamic_style(self, name, style):
if style:
style = style.AsDynamicStyle()
self.add_undo(self.styles.SetItem(name, style))
self.add_undo(self.queue_style())
return style
def update_style_dependencies(self, style):
def update(obj, style = style):
obj.ObjectChanged(style)
self.WalkHierarchy(update)
return (self.update_style_dependencies, style)
def UpdateDynamicStyleSel(self):
if len(self.selection) == 1:
self.begin_transaction(_("Update Style"), clear_selection_rect = 0)
try:
try:
properties = self.CurrentProperties()
# XXX hack
for style in properties.stack:
if style.is_dynamic:
break
else:
return
undo = []
# we used to use dir(style) to get at the list of
# instance variables of style. In Python 2.2 dir
# returns class attributes as well. So we use
# __dict__.keys() now.
for attr in style.__dict__.keys():
if attr not in ('name', 'is_dynamic'):
undo.append(style.SetProperty(attr,
getattr(properties,
attr)))
undo.append(properties.AddStyle(style))
undo = (UndoAfter, CreateListUndo(undo),
self.update_style_dependencies(style))
self.add_undo(undo)
except:
self.abort_transaction()
finally:
self.end_transaction()
def CanCreateStyle(self):
if len(self.selection) == 1:
obj = self.selection.GetObjects()[0]
return obj.has_fill or obj.has_line
return 0
def CreateStyleFromSelection(self, name, which_properties):
if self.CanCreateStyle():
properties = self.CurrentProperties()
style = properties.CreateStyle(which_properties)
self.begin_transaction(_("Create Style %s") % name,
clear_selection_rect = 0)
try:
try:
style = self.add_dynamic_style(name, style)
self.AddStyle(style)
except:
self.abort_transaction()
finally:
self.end_transaction()
def RemoveDynamicStyle(self, name):
style = self.GetDynamicStyle(name)
if not style:
# style does not exist. XXX: raise an exception ?
return
self.begin_transaction(_("Remove Style %s") % name,
clear_selection_rect = 0)
try:
try:
def remove(obj, style = style, add_undo = self.add_undo):
add_undo(obj.ObjectRemoved(style))
self.WalkHierarchy(remove)
self.add_undo(self.styles.DelItem(name))
self.add_undo(self.queue_style())
except:
self.abort_transaction()
finally:
self.end_transaction()
def GetStyleNames(self):
names = self.styles.keys()
names.sort()
return names
#
# Layout
#
def queue_layout(self):
self.queue_message(LAYOUT)
return (self.queue_layout,)
def init_layout(self):
self.page_layout = pagelayout.PageLayout()
def Layout(self):
return self.page_layout
def PageSize(self):
return (self.page_layout.Width(), self.page_layout.Height())
def PageRect(self):
w, h = self.page_layout.Size()
return Rect(0, 0, w, h)
def load_SetLayout(self, layout):
self.page_layout = layout
def __set_page_layout(self, layout):
undo = (self.__set_page_layout, self.page_layout)
self.page_layout = layout
self.queue_layout()
return undo
def SetLayout(self, layout):
self.begin_transaction(clear_selection_rect = 0)
try:
try:
undo = self.__set_page_layout(layout)
self.add_undo(_("Change Page Layout"), undo)
except:
self.abort_transaction()
finally:
self.end_transaction()
#
# Grid Settings
#
def queue_grid(self):
self.queue_message(GRID)
return (self.queue_grid,)
def SetGridGeometry(self, geometry):
self.begin_transaction(_("Set Grid Geometry"))
try:
try:
self.add_undo(self.snap_grid.SetGeometry(geometry))
self.add_undo(self.queue_grid())
except:
self.abort_transaction()
finally:
self.end_transaction()
def GridGeometry(self):
return self.snap_grid.Geometry()
def GridLayerChanged(self):
return self.queue_grid()
#
# Guide Lines
#
def add_guide_line(self, line):
self.begin_transaction(_("Add Guide Line"), clear_selection_rect = 0)
try:
try:
sel, undo = self.guide_layer.Insert(line, 0)
self.add_undo(undo)
self.add_undo(self.AddClearRect(line.get_clear_rect()))
except:
self.abort_transaction()
finally:
self.end_transaction()
def AddGuideLine(self, point, horizontal):
self.add_guide_line(guide.GuideLine(point, horizontal))
def RemoveGuideLine(self, line):
if not line.parent is self.guide_layer or not line.is_GuideLine:
return
self.begin_transaction(_("Delete Guide Line"),
clear_selection_rect = 0)
try:
try:
self.add_undo(self.remove_objects([line.SelectionInfo()]))
self.add_undo(self.AddClearRect(line.get_clear_rect()))
except:
self.abort_transaction()
finally:
self.end_transaction()
def MoveGuideLine(self, line, point):
if not line.parent is self.guide_layer or not line.is_GuideLine:
return
self.begin_transaction(_("Move Guide Line"), clear_selection_rect = 0)
try:
try:
self.add_undo(self.AddClearRect(line.get_clear_rect()))
self.add_undo(line.SetPoint(point))
self.add_undo(self.AddClearRect(line.get_clear_rect()))
self.add_undo(self.GuideLayerChanged(line.parent))
except:
self.abort_transaction()
finally:
self.end_transaction()
def GuideLayerChanged(self, layer):
self.queue_message(GUIDE_LINES, layer)
return (self.GuideLayerChanged, layer)
def GuideLines(self):
return self.guide_layer.GuideLines()
#
#
def as_group(self):
for name in self.GetStyleNames():
self.RemoveDynamicStyle(name)
layers = self.layers
self.layers = []
groups = []
for layer in layers:
if not layer.is_SpecialLayer:
layer.UntieFromDocument()
objects = layer.GetObjects()
layer.objects = []
if objects:
groups.append(Group(objects))
else:
layer.Destroy()
if groups:
return Group(groups)
else:
return None
|