# 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA


from types import TupleType
from app 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?

