# Sketch - A Python-based interactive drawing program
# Copyright (C) 1998, 1999, 2001 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


from types import TupleType
from Sketch import _, SketchError, CreateListUndo, Undo, \
     Trafo, Translation, Scale, Identity, TrafoType

from compound import Compound


class PluginCompound(Compound):

    class_name = ''	 # has to be provided by derived classes
    is_Plugin = 1

    def SetParameters(self, kw = None):
	# XXX could be extended to handle keyword arguments.
	undo = {}
	for key, value in kw.items():
	    undo[key] = getattr(self, key)
	    setattr(self, key, value)
	return self.SetParameters, undo

    def SaveToFile(self, file, *args, **kw):
	if self.class_name:
	    apply(file.BeginPluginCompound, (self.class_name,) + args, kw)
	    for obj in self.objects:
		obj.SaveToFile(file)
	    file.EndPluginCompound()
	else:
	    raise SketchError("Plugin %s doesn't define a class name"
			      % self.__class__)

class TrafoPlugin(PluginCompound):

    def __init__(self, trafo = None, duplicate = None, loading = 0):
	PluginCompound.__init__(self, duplicate = duplicate)
	if duplicate is not None:
	    self.trafo = duplicate.trafo
	else:
	    if trafo is None:
		trafo = Identity
	    elif type(trafo) == TupleType:
		trafo = apply(Trafo, trafo)
            elif isinstance(trafo, TrafoType):
                # trafo is already a trafo object
                pass
	    else:
		# assume a number and interpret it as a scaling transformation
		trafo = Scale(trafo)
	    self.trafo = trafo

    def recompute(self):
	# Implement this in the derived class to update the children.
	pass

    def SetParameters(self, kw):
	undo = PluginCompound.SetParameters(self, kw)
	try:
	    self.recompute()
	except:
	    Undo(undo)
	    raise
	return undo

    def set_transformation(self, trafo):
	undo = (self.set_transformation, self.trafo)
	self.trafo = trafo
	self.recompute()
	return undo

    def Transform(self, trafo):
	undo = [self.begin_change_children()]
	try:
	    undo.append(PluginCompound.Transform(self, trafo))
	    undo.append(self.set_transformation(trafo(self.trafo)))
	    undo.append(self.end_change_children())
	except:
	    undo.reverse()
	    map(Undo, undo)
	    raise
	return CreateListUndo(undo)

    def Translate(self, offset):
	return self.Transform(Translation(offset))

    def RemoveTransformation(self):
	return self.set_transformation(Translation(self.trafo.offset()))

    def LayoutPoint(self):
	return self.trafo.offset()

    def Trafo(self):
        return self.trafo


class UnknownPlugin(PluginCompound):

    is_Group = 1
    changed = 0

    def __init__(self, class_name = '', *args, **kw):
	if kw.has_key('loading'):
	    del kw['loading']
	duplicate = kw.get('duplicate')
	if duplicate is not None:
	    self.class_name = duplicate.class_name
	    self.args = duplicate.args
	    self.kw = duplicate.kw
	else:
	    self.class_name = class_name
	    self.args = args
	    self.kw = kw
	PluginCompound.__init__(self, duplicate = duplicate)
	self.disguise()

    def disguise(self):
	# If self has only one child, try to behave like it:
	if len(self.objects) == 1:
	    object = self.objects[0]
	    if object.is_curve:
		self.is_curve = object.is_curve
		self.AsBezier = object.AsBezier
		self.Paths = object.Paths

    def _changed(self):
	PluginCompound._changed(self)
	self.changed = 1

    def load_Done(self):
	PluginCompound.load_Done(self)
	self.disguise()

    def SaveToFile(self, file):
	if not self.changed:
	    apply(PluginCompound.SaveToFile, (self, file) + self.args, self.kw)
	else:
	    # XXX an alternative approach for a changed UnknownPlugin
	    # might be to store explicitly as an 'UnknownPlugin' so that
	    # always is represented by this class.
	    if len(self.objects) == 1:
		self.objects[0].SaveToFile(file)
	    else:
		# Save as ordinary group. This might not be a good idea,
		# since the UnknownPlugin may (in the future) have some
		# special behaviour, that the group doesn't have.
		file.BeginGroup()
		for obj in self.objects:
		    obj.SaveToFile(file)
		file.EndGroup()


    def Info(self):
	return _("Unknown Plugin Object `%s'") % self.class_name

    Ungroup = PluginCompound.GetObjects


# XXX this implementation of the UnknownPlugin my fail in some
# situations.
#
# The following example is now fixed, but similar situations are
# conceivable:
#
# If the plugin defines is_curve and AsBezier(), for instance, it can be
# used as a control object in a BlendGroup with, say, a PolyBezier
# object as the other control object. The UnknownPlugin does not provide
# that interface, so that loading a document containing such a
# BlendGroup would fail, because the UnknownPlugin instance cannot be
# converted to a PolyBezier object when the interpolation is recomputed.
#
# A solution would be to make the UnknownPlugin behave different if it
# only has one child. Or to have it examine its children and try to
# support the interfaces they have in common. I.e. if all children have
# is_curve == 1, it could also set its instance variable is_curve to 1,
# and implement the AsBezier meghod by returning a PolyBezier object
# that is the combination (`Combine Beziers') of its converted children.
# This would not always be appropriate, however.
#
# Are there other cases where UnknownPlugin has shortcomings?

