# -*- coding: utf-8 -*-

# Copyright (C) 2003-2006 by Igor E. Novikov
# Copyright (C) 1997, 1998, 1999, 2000 by Bernhard Herzog
#
# This library is covered by GNU Library General Public License.
# For more info see COPYRIGHTS file in sK1 root directory.


# Classes for handling selections. These include classes that represent
# lists of selected objects and classes that represent the combined
# bounding rectangle of all selected objects. The user can interact in
# the usual fashion with these selection rects to transform (translate,
# rotate, scale, shear) the selected objects.


import operator, math
from types import ListType, InstanceType, TupleType
import time

from uc.utils import flatten

from sk1.skpixmaps import pixmaps
from sk1 import appconst
from app.events.warn import pdebug, warn, INTERNAL
from app.conf import const
from app.conf.const import SelectSet

from app import _, Point, Polar, Rect, UnionRects, RectType, Identity, \
		Trafo, TrafoType, Rotation, CreateListUndo, NullUndo

import handle
from base import SelectAndDrag, Bounded
import selinfo

from math import floor, ceil

class SelRectBase(SelectAndDrag, Bounded):

	#
	# Handle/selection numbers:
	#   sx              ex
	#   1       2       3       sy
	#
	#   8               4
	#
	#   7       6       5       ey
	#
	#   -1: whole object

	selTop = (1, 2, 3)
	selBottom = (7, 6, 5)
	selLeft = (1, 8, 7)
	selRight = (3, 4, 5)
	selAspect = (1, 3, 5, 7)# constrain aspect ratio for these selections

	handle_idx_to_sel = (7, 6, 5, 8, 4, 1, 2, 3)

	def __init__(self):
		SelectAndDrag.__init__(self)
		self.outline_object = None

	def update_rects(self):
		self.coord_rect = Rect(self.start, self.end)
		self.bounding_rect = self.coord_rect

	def SetOutlineObject(self, obj):
		# XXX: this is a hack...
		if obj is not None:
			if obj.is_Compound:
				objects = obj.GetObjects()
				if len(objects) == 1:
					obj = objects[0]
				else:
					return
			if not obj.is_Compound and not obj.is_Text:
				self.outline_object = obj


class SelectionRectangle(SelRectBase):

	def __init__(self, rect, anchor=None):
		SelRectBase.__init__(self)
		if type(rect) == RectType:
			self.start = Point(rect.left, rect.bottom)
			self.end = Point(rect.right, rect.top)
			self.Normalize()
			self.anchor = anchor
		else:
			# assume type Point and interactive creation
			self.start = rect
			self.end = rect
			self.anchor = None
			self.selection = 5

	def DrawDragged(self, device, partially):
		sel = self.selection

		if sel == -1:
			sx, sy = self.start + self.off
			ex, ey = self.end + self.off
		else:
			if sel in self.selTop:
				sy = self.drag_cur.y
			else:
				sy = self.start.y

			if sel in self.selBottom:
				ey = self.drag_cur.y
			else:
				ey = self.end.y

			if sel in self.selLeft:
				sx = self.drag_cur.x
			else:
				sx = self.start.x

			if sel in self.selRight:
				ex = self.drag_cur.x
			else:
				ex = self.end.x

		if sx > ex:
			tmp = sx; sx = ex; ex = tmp
		if sy < ey:
			tmp = sy; sy = ey; ey = tmp

		device.DrawRubberRect(Point(sx, sy), Point(ex, ey))

	def ButtonDown(self, p, button, state):
		SelectAndDrag.DragStart(self, p)
		sel = self.selection
		if sel == -1:
			if self.anchor:#XXX shouldn't this be 'if self.anchor is not None'
				start = self.anchor
			else:
				start = self.start
			self.drag_start = self.drag_cur = start
			return (p - start, self.coord_rect.translated(-start))
		ds_x , ds_y = (self.start + self.end) / 2
		if sel in self.selLeft:
			ds_x = self.start.x
		if sel in self.selTop:
			ds_y = self.start.y
		if sel in self.selRight:
			ds_x = self.end.x
		if sel in self.selBottom:
			ds_y = self.end.y
		self.drag_cur = self.drag_start = ds = Point(ds_x, ds_y)
		self.init_constraint()
		return p - ds

	def init_constraint(self):
		pass

	def apply_constraint(self, p, state):
		return p

	def MouseMove(self, p, state):
		p = self.apply_constraint(p, state)
		SelectAndDrag.MouseMove(self, p, state)

	def compute_endpoints(self):
		cur = self.drag_cur
		start = self.start
		end = self.end
		sel = self.selection
		if sel in self.selTop:
			start = Point(start.x, cur.y)
		if sel in self.selBottom:
			end = Point(end.x, cur.y)
		if sel in self.selLeft:
			start = Point(cur.x, start.y)
		if sel in self.selRight:
			end = Point(cur.x, end.y)
		if sel == -1:
			start = start + self.off
			end = end + self.off
		return start, end

	def ButtonUp(self, p, button, state):
		p = self.apply_constraint(p, state)
		SelectAndDrag.DragStop(self, p)
		cur = self.drag_cur
		oldstart = self.start
		oldend = self.end
		start, end = self.compute_endpoints()
		self.start = start
		self.end = end
		result = self.ComputeTrafo(oldstart, oldend, start, end)
		self.Normalize()
		return result

	def ComputeTrafo(self, oldStart, oldEnd, start, end):
		pass

	def Normalize(self):
		sx, sy = self.start
		ex, ey = self.end
		if sx > ex:
			sx, ex = ex, sx
		if sy > ey:
			sy, ey = ey, sy
		self.start = Point(sx, sy)
		self.end = Point(ex, ey)

	def Hit(self, p, rect, device):
		pass

	def Select(self):
		self.selection = -1

	def SelectPoint(self, p, rect, device, mode=SelectSet):
		if p:
			self.selection = 0
		else:
			self.selection = -1
		return self.selection

	def SelectHandle(self, handle, mode=SelectSet):
		self.selection = self.handle_idx_to_sel[handle.index]

	def GetHandles(self):
		sx = self.start.x
		sy = self.start.y
		ex = self.end.x
		ey = self.end.y
		x2 = (sx + ex) / 2
		y2 = (sy + ey) / 2
		return map(handle.MakeOffsetHandle,
					[Point(sx, ey), Point(x2, ey), Point(ex, ey),
					Point(sx, y2), Point(ex, y2),
					Point(sx, sy), Point(x2, sy), Point(ex, sy)],
					[(-1, 1), (0, 1), (1, 1),
					(-1, 0), (1, 0),
					(-1, -1), (0, -1), (1, -1)])



class Selection(Bounded):

	is_EditSelection = 0

	_lazy_attrs = Bounded._lazy_attrs.copy()
	_lazy_attrs['rect'] = 'update_rectangle'

	def __init__(self, copy_from=None):
		if copy_from is not None:
			if type(copy_from) == ListType:
				self.objects = copy_from[:]
			else:
				# assume copy_from is another instance of a selection class
				self.objects = copy_from.objects
				self.coord_rect = copy_from.coord_rect
				self.bounding_rect = copy_from.bounding_rect
				self.anchor = copy_from.anchor

		else:
			self.objects = []
			self.anchor = None

	def normalize(self):
		# make sure that self.objects contains no object more than once
		# and that no two objects have a direct or indirect parent/child
		# relationship.
		objs = self.objects
		changed = 0
		if len(objs) > 1:
			objs.sort()
			last_info, obj = objs[-1]
			for idx in range(len(objs) - 2, -1, -1):
				info, obj = objs[idx]
				if info == last_info:
					del objs[idx]
					changed = 1
					continue
				if len(info) < len(last_info):
					while info == last_info[:len(info)]:
						del objs[idx + 1]
						changed = 1
						if idx + 1 < len(objs):
							last_info = objs[idx + 1][0]
						else:
							break
				last_info = info
		return changed

	def update_selinfo(self):
		objs = self.objects
		for i in range(len(objs)):
			objs[i] = objs[i][-1].SelectionInfo()

	def SetSelection(self, info):
		old_objs = self.objects
		if info:
			if type(info) == ListType:
				self.objects = info
				self.objects.sort()
			else:
				self.objects = [info]
		else:
			self.objects = []
		self.del_lazy_attrs()
		return old_objs != self.objects

	def SetSelectionTree(self, info):
		return self.SetSelection(selinfo.tree_to_list(info))

	def Add(self, info):
		if not info:
			return 0
		if self.TestSubtract(info) == 1:
				return self.Subtract(info)
		old_len = len(self.objects)
		if type(info) == ListType:
			self.objects = self.objects + info
		elif info:
			self.objects.append(info)
		changed = self.normalize()
		self.del_lazy_attrs()
		return changed or old_len != len(self.objects)

	def TestSubtract(self, info):
		result = 0
		old_len = len(self.objects)
		if type(info) != ListType:
			info = [info]
		objects = self.objects
		for item in info:
			if item in objects:
				result = 1
		return result

	def Subtract(self, info):
		if not info:
			return 0
		old_len = len(self.objects)
		if type(info) != ListType:
			info = [info]
		objects = self.objects
		for item in info:
			if item in objects:
				objects.remove(item)
		self.del_lazy_attrs()
		return old_len != len(self.objects)

	def GetObjects(self):
		return map(operator.getitem, self.objects, [-1] * len(self.objects))

	def GetInfo(self):
		return self.objects

	def GetInfoTree(self):
		return selinfo.list_to_tree(self.objects)

	def Depth(self):
		if self.objects:
			lengths = map(len, map(operator.getitem, self.objects,
									[0] * len(self.objects)))
			lmin = min(lengths)
			lmax = max(lengths)
			if lmin == lmax:
				return lmin
			return (lmin, lmax)
		return 0

	def IsSingleDepth(self):
		if self.objects:
			return type(self.Depth()) != TupleType
		return 1

	def GetPath(self):
		if len(self.objects) == 1:
			return self.objects[0][0]
		return ()

	def for_all(self, func):
		return map(func, self.GetObjects())

	def ForAllUndo(self, func):
		undoinfo = self.for_all(func)
		self.del_lazy_attrs()
		if len(undoinfo) == 1:
			undoinfo = undoinfo[0]
		if type(undoinfo) == ListType:
			return CreateListUndo(undoinfo)
		return undoinfo

	def ForAllUndo2(self, method, *args):
		t = time.clock()
		methods = map(getattr, self.GetObjects(), [method] * len(self.objects))
		#print time.clock() - t,
		#undoinfo = self.for_all(func)
		undoinfo = map(apply, methods, [args] * len(methods))
		#print time.clock() - t
		self.del_lazy_attrs()
		if len(undoinfo) == 1:
			undoinfo = undoinfo[0]
		if type(undoinfo) == ListType:
			return CreateListUndo(undoinfo)
		return undoinfo

	def __len__(self):
		return len(self.objects)

	__nonzero__ = __len__

	def update_rects(self):
		objects = self.GetObjects()
		boxes = map(lambda o: o.coord_rect, objects)
		if boxes:
			self.coord_rect = reduce(UnionRects, boxes)
		else:
			self.coord_rect = None
		boxes = map(lambda o: o.bounding_rect, objects)
		if boxes:
			self.bounding_rect = reduce(UnionRects, boxes)
		else:
			self.bounding_rect = None
		if len(objects) == 1:
			self.anchor = objects[0].LayoutPoint()
		else:
			self.anchor = None

	def ChangeRect(self):
		return self.bounding_rect

	def ResetRectangle(self):
		self.del_lazy_attrs()

	def Hit(self, p, rect, device):
		test = rect.overlaps
		for obj in self.GetObjects():
			if test(obj.bounding_rect):
				if obj.Hit(p, rect, device):
					return 1
		return 0

	def DragCancel(self):
		self.rect.DragCancel()

	def GetHandles(self):
		rect_handles = self.rect.GetHandles()
		multiple = len(self.objects) > 1
		handles = flatten(self.for_all(lambda o, m=multiple:
										o.GetObjectHandle(m)))
		rect_handles.append(handle.MakeObjectHandleList(handles))
		return rect_handles

	def CallObjectMethod(self, aclass, methodname, args):
		if len(self.objects) == 1:
			obj = self.objects[0][-1]
			if not isinstance(obj, aclass):
				return NullUndo
			try:
				method = getattr(obj, methodname)
			except AttributeError:
				return NullUndo

			undo = apply(method, args)
			if undo is None:
				undo = NullUndo
			return undo
		return NullUndo

	def GetObjectMethod(self, aclass, method):
		if len(self.objects) == 1:
			obj = self.objects[0][-1]
			if isinstance(obj, aclass):
				try:
					return getattr(obj, method)
				except AttributeError:
					pass
		return None

	def InfoText(self):
		# Return a string describing the selected object(s)
		result = _("No Selection")
		if self.objects:
			sel_info = self.objects
			br = self.coord_rect
			hor_sel = round((br.right - br.left) / .283465) / 10
			ver_sel = round((br.top - br.bottom) / .283465) / 10
			document = sel_info[0][1].document
			if len(sel_info) == 1:
				path, obj = sel_info[0]
				dict = {'layer': document[path[0]].Name()}
				info = obj.Info()
				if type(info) == TupleType:
					dict.update(info[1])
					# the %% is correct here. The result has to be a
					# %-template itself.
					text = _("%s on `%%(layer)s'") % info[0] + "\n " + _("Selection size:") + " " + str(hor_sel) + " x " + str(ver_sel) + " mm"###
				else:
					dict['object'] = info
					text = _("%(object)s on `%(layer)s'") + "\n " + _("Selection size:") + " " + str(hor_sel) + " x " + str(ver_sel) + " mm"###
				result = text, dict
			else:
				layer = sel_info[0][0][0]
				if layer == sel_info[-1][0][0]:
					# a single layer
					layer_name = document.layers[layer].Name()
					result = _("%(number)d objects on `%(layer)s'") \
								% {'number':len(sel_info), 'layer':layer_name}
					result = result + "\n " + _("Selection size:") + " " + str(hor_sel) + " x " + str(ver_sel) + " mm"
				else:
					result = _("%d objects on several layers") % len(sel_info) + "\n " + _("Selection size:") + " " + str(hor_sel) + " x " + str(ver_sel) + " mm"###
		return result

	def CurrentInfoText(self):
		return ''

	def _dummy(self, *args):
		pass

	Hide = _dummy
	DragStart = None
	DragMove = None
	DragStop = None
	Show = _dummy
	Hide = _dummy
	SelectPoint = _dummy
	SelectHandle = _dummy

	drag_mask = SelectAndDrag.drag_mask



class SizeRectangle(SelectionRectangle):

	def init_constraint(self):
		sel = self.selection
		if sel == 1:
			self.reference = tuple(self.end)
		elif sel == 3:
			self.reference = (self.start.x, self.end.y)
		elif sel == 5:
			self.reference = tuple(self.start)
		elif sel == 7:
			self.reference = (self.end.x, self.start.y)
		else:
			return
		width = abs(self.start.x - self.end.x)
		height = abs(self.start.y - self.end.y)
		if width >= 1e-10:
			self.aspect = height / width
		else:
			self.aspect = None

	def apply_constraint(self, p, state):
		if state:
			if self.selection in self.selAspect:
				ref_x, ref_y = self.reference
				aspect = self.aspect
				if aspect is None:
					# width is 0
					p = Point(self.drag_start.x, p.y)
				else:
					w = p.x - ref_x
					h = p.y - ref_y
					if w == 0:
						w = 0.00001
					a = h / w
					if a > 0:
						sign = 1
					else:
						sign = -1
					if abs(a) > aspect:
						h = sign * w * aspect
					else:
						w = sign * h / aspect
					p = Point(ref_x + w, ref_y + h)
#       if state & const.AlternateMask:
#               pi4 = math.pi / 4
#               off = p - self.drag_start
#                 d = Polar(pi4 * round(math.atan2(off.y, off.x) / pi4))
#                 p = self.drag_start + (off * d) * d
#               print 'ALT'
		if state & const.ConstraintMask:# and self.selection == -1:
				pi4 = math.pi / 4
				off = p - self.drag_start
				d = Polar(pi4 * round(math.atan2(off.y, off.x) / pi4))
				p = self.drag_start + (off * d) * d
		return p

	def ButtonDown(self, p, button, state):
		self.trafo = Identity
		return SelectionRectangle.ButtonDown(self, p, button, state)

	def MouseMove(self, p, state):
		p = self.apply_constraint(p, state)
		SelectAndDrag.MouseMove(self, p, state)
		start, end = self.compute_endpoints()
		text, self.trafo = self.ComputeTrafo(self.start, self.end, start, end)

	def ComputeTrafo(self, oldStart, oldEnd, start, end):
		oldDelta = oldEnd - oldStart
		delta = end - start
		if self.selection == -1:
			# a translation.
			return _("Move Objects"), start - oldStart
		else:
			try:
				m11 = delta.x / oldDelta.x
			except ZeroDivisionError:
				m11 = 0
				if __debug__:
					pdebug(None, 'ComputeTrafo: ZeroDivisionError')
			try:
				m22 = delta.y / oldDelta.y
			except ZeroDivisionError:
				m22 = 0
				if __debug__:
					pdebug(None, 'ComputeTrafo: ZeroDivisionError')
			offx = start.x - m11 * oldStart.x
			offy = start.y - m22 * oldStart.y
			return _("Resize Objects"), Trafo(m11, 0, 0, m22, offx, offy)

	def DrawDragged(self, device, partial):
		SelectionRectangle.DrawDragged(self, device, partial)
		if self.outline_object is not None:
			trafo = self.trafo
			device.PushTrafo()
			if type(trafo) == TrafoType:
				device.Concat(trafo)
			else:
				device.Translate(trafo.x, trafo.y)
			self.outline_object.DrawShape(device)
			device.PopTrafo()

	def CurrentInfoText(self):
		t = self.trafo
		data = {}
		if type(t) == TrafoType:
			x = t.m11
			y = t.m22
			#if round(x, 3) == round(y, 3):
			#    text = _("Uniform Scale %(factor)[factor]")
			#    data['factor'] = x
			#else:
			br = self.coord_rect
			hor_sel = ceil(floor(10 ** 3 * x * (br.right - br.left) / 2.83465) / 10) / 100
			ver_sel = ceil(floor(10 ** 3 * y * (br.top - br.bottom) / 2.83465) / 10) / 100
			text = _("Scale %(factorx)[factor], %(factory)[factor]")
			text = text + "\n " + _("Changing size to:") + " " + str(hor_sel) + " x " + str(ver_sel) + " mm"
			data['factorx'] = x
			data['factory'] = y
		else:
			text = _("Move %(x)[length], %(y)[length]")
			data['x'] = t.x
			data['y'] = t.y
		return text, data



class SizeSelection(Selection):

	def __init__(self, arg=None):
		Selection.__init__(self, arg)

	def update_rectangle(self):
		if self:
			self.rect = SizeRectangle(self.coord_rect, self.anchor)
		else:
			self.rect = SizeRectangle(Rect(0, 0, 0, 0))

	def ButtonDown(self, p, button, state):
		if len(self.objects) == 1:
			self.rect.SetOutlineObject(self.objects[0][-1])
		return self.rect.ButtonDown(p, button, state)

	def MouseMove(self, p, state):
		self.rect.MouseMove(p, state)

	def ButtonUp(self, p, button, state, forget_trafo=0):
		self.rect.SetOutlineObject(None)
		undo_text, trafo = self.rect.ButtonUp(p, button, state)
		if forget_trafo:
			return None, None
		t = time.clock()
		if type(trafo) == TrafoType:
			#undo = self.ForAllUndo(lambda o, t = trafo: o.Transform(t))
			undo = self.ForAllUndo2('Transform', trafo)
		else:
			# trafo is point representing a translation
			undo = self.ForAllUndo2('Translate', trafo)
		#print 'transform/translate', time.clock() - t
		self.del_lazy_attrs()
		return undo_text, undo

	def Show(self, device, partially=0):
		self.rect.Show(device, partially)

	def Hide(self, device, partially=0):
		self.rect.Hide(device, partially)

	def DrawDragged(self, device, partial):
		self.rect.DrawDragged(device, partial)

	def SelectPoint(self, p, rect, device, mode=SelectSet):
		if not self.rect.SelectPoint(p, rect, device, mode):
			if self.Hit(p, rect, device):
				self.rect.Select()

	def SelectHandle(self, handle, mode=SelectSet):
		self.rect.SelectHandle(handle, mode)

	def Hit(self, p, rect, device):
		if self.objects:
			return (rect.overlaps(self.bounding_rect)
					and Selection.Hit(self, p, rect, device))
		return 0

	def CurrentInfoText(self):
		return self.rect.CurrentInfoText()

class TrafoRectangle(SelRectBase):

	selTurn = [1, 3, 5, 7]
	selShear = [2, 4, 6, 8]
	selCenter = 100

	def __init__(self, rect, center=None):
		SelRectBase.__init__(self)
		self.start = Point(rect.left, rect.bottom)
		self.end = Point(rect.right, rect.top)
		if center is None:
			self.center = rect.center()
		else:
			self.center = center


	def compute_trafo(self, state=0):
		sel = self.selection
		if sel in self.selTurn:
			# rotation
			vec = self.drag_cur - self.center
			angle = math.atan2(vec.y, vec.x)
			angle = angle - self.start_angle + 2 * math.pi
			if state & const.ConstraintMask:
				pi12 = math.pi / 12
				angle = pi12 * int(angle / pi12 + 0.5)
			self.trafo = Rotation(angle, self.center)
			self.trafo_desc = (1, angle)
		elif sel in self.selShear:
			if sel in (2, 6):
				# horiz. shear
				height = self.drag_start.y - self.reference
				if height:
					ratio = self.off.x / height
					self.trafo = Trafo(1, 0, ratio, 1,
										- ratio * self.reference, 0)
					self.trafo_desc = (2, ratio)
			else:
				# vert. shear
				width = self.drag_start.x - self.reference
				if width:
					ratio = self.off.y / width
					self.trafo = Trafo(1, ratio, 0, 1, 0,
										- ratio * self.reference)
					self.trafo_desc = (3, ratio)

	def DrawDragged(self, device, partially):
		sel = self.selection
		if sel == self.selCenter:
			device.DrawPixmapHandle(self.drag_cur, pixmaps.Center)
		else:
			trafo = self.trafo
			if trafo:
				device.PushTrafo()
				device.Concat(trafo)
				device.DrawRubberRect(self.start, self.end)
				if self.outline_object is not None:
					self.outline_object.DrawShape(device)
				device.PopTrafo()

	def ButtonDown(self, p, button, state):
		self.drag_state = state
		self.trafo = Identity
		self.trafo_desc = (0, 0)
		SelectAndDrag.DragStart(self, p)
		sel = self.selection
		if sel == self.selCenter:
			self.drag_cur = self.drag_start = self.center
			return p - self.center
		ds_x = ds_y = 0
		if sel in self.selLeft:
			ds_x = self.start.x
		if sel in self.selTop:
			ds_y = self.start.y
		if sel in self.selRight:
			ds_x = self.end.x
		if sel in self.selBottom:
			ds_y = self.end.y
		self.drag_cur = self.drag_start = ds = Point(ds_x, ds_y)
		if sel in self.selTurn:
			vec = ds - self.center
			self.start_angle = math.atan2(vec.y, vec.x)
		else:
			if sel == 2:
				self.reference = self.end.y
			elif sel == 4:
				self.reference = self.start.x
			elif sel == 6:
				self.reference = self.start.y
			elif sel == 8:
				self.reference = self.end.x
		return p - ds

	def constrain_center(self, p, state):
		if state & const.ConstraintMask:
			start = self.start
			end = self.end
			if p.x < 0.75 * start.x + 0.25 * end.x:
				x = start.x
			elif p.x > 0.25 * start.x + 0.75 * end.x:
				x = end.x
			else:
				x = (start.x + end.x) / 2
			if p.y < 0.75 * start.y + 0.25 * end.y:
				y = start.y
			elif p.y > 0.25 * start.y + 0.75 * end.y:
				y = end.y
			else:
				y = (start.y + end.y) / 2
			return Point(x, y)
		return p

	def MouseMove(self, p, state):
		self.drag_state = state
		if self.selection == self.selCenter:
			p = self.constrain_center(p, state)
		SelectAndDrag.MouseMove(self, p, state)
		self.compute_trafo(state)

	def ButtonUp(self, p, button, state):
		if self.selection == self.selCenter:
			p = self.constrain_center(p, state)
		SelectAndDrag.DragStop(self, p)
		sel = self.selection
		if sel == self.selCenter:
			self.center = self.drag_cur
			return '', None
		else:
			self.compute_trafo(state)
			trafo = self.trafo
			if self.selection in self.selShear:
				text = _("Shear Objects")
			else:
				text = _("Rotate Objects")
			return text, trafo

	def CurrentInfoText(self):
		if self.selection == self.selCenter:
			text = _("Rotation Center at %(position)[position]")
			data = {'position': self.drag_cur}
		else:
			type, value = self.trafo_desc
			if type == 1:
				text = _("Rotate by %(angle)[angle]")
				data = {'angle': value}
			elif type == 2:
				text = _("Horizontal Shear by %(ratio)[factor]")
				data = {'ratio': value}
			elif type == 3:
				text = _("Vertical Shear by %(ratio)[factor]")
				data = {'ratio': value}
			else:
				text = _("Identity Transform")
				data = {}
		return text, data

	def Hit(self, p, rect, device):
		pass

	def Select(self):
		pass

	def SelectPoint(self, p, rect, device, mode=SelectSet):
		self.selection = 0
		return self.selection

	def SelectHandle(self, handle, mode=SelectSet):
		handle = handle.index
		if handle == len(self.handle_idx_to_sel):
			self.selection = self.selCenter
		else:
			self.selection = self.handle_idx_to_sel[handle]

	def GetHandles(self):
		sx = self.start.x
		sy = self.start.y
		ex = self.end.x
		ey = self.end.y
		x2 = (sx + ex) / 2
		y2 = (sy + ey) / 2
		return map(handle.MakePixmapHandle,
					[Point(sx, ey), Point(x2, ey), Point(ex, ey),
					Point(sx, y2), Point(ex, y2),
					Point(sx, sy), Point(x2, sy), Point(ex, sy)],
					[(-1, 1), (0, 1), (1, 1),
					(-1, 0), (1, 0),
					(-1, -1), (0, -1), (1, -1)],
					[pixmaps.TurnTL, pixmaps.ShearLR, pixmaps.TurnTR,
					pixmaps.ShearUD, pixmaps.ShearUD,
					pixmaps.TurnBL, pixmaps.ShearLR, pixmaps.TurnBR],
					[appconst.CurCreate] * 8) \
				+ [handle.MakePixmapHandle(self.center, (0, 0), pixmaps.Center)]


class TrafoSelection(Selection):

	def __init__(self, copy_from=None):
		Selection.__init__(self, copy_from)
		self.center = None

	def update_rectangle(self, same_center=1):
		if self:
			self.rect = TrafoRectangle(self.coord_rect, self.center)
		else:
			self.rect = TrafoRectangle(Rect(0, 0, 0, 0))

	def ButtonDown(self, p, button, state):
		if len(self.objects) == 1:
			self.rect.SetOutlineObject(self.objects[0][-1])
		return self.rect.ButtonDown(p, button, state)

	def MouseMove(self, p, state):
		self.rect.MouseMove(p, state)

	def ButtonUp(self, p, button, state, forget_trafo=0):
		self.rect.SetOutlineObject(None)
		undo_text, trafo = self.rect.ButtonUp(p, button, state)
		self.center = self.rect.center
		if forget_trafo:
			return None, None
		if trafo is not None:
			t = time.clock()
			#undo = self.ForAllUndo(lambda o, t = trafo: o.Transform(t))
			undo = self.ForAllUndo2('Transform', trafo)
			self.del_lazy_attrs()
			#print 'transform/translate', time.clock() - t
			return undo_text, undo
		return '', None

	def Show(self, device, partially=0):
		self.rect.Show(device, partially)

	def Hide(self, device, partially=0):
		self.rect.Hide(device, partially)

	def DrawDragged(self, device, partial):
		self.rect.DrawDragged(device, partial)

	def SelectPoint(self, p, rect, device, mode=SelectSet):
		if not self.rect.SelectPoint(p, rect, device, mode):
			if self.Hit(p, rect, device):
				self.rect.Select()

	def SelectHandle(self, handle, mode=SelectSet):
		self.rect.SelectHandle(handle, mode)

	def Hit(self, p, rect, device):
		if self.objects:
			return (rect.overlaps(self.rect.bounding_rect)
					and Selection.Hit(self, p, rect, device))
		return 0

	def CurrentInfoText(self):
		return self.rect.CurrentInfoText()

class EditorWrapper:

	def __init__(self, editor):
		self.editor = editor

	def __del__(self):
		self.editor.Destroy()

	def __getattr__(self, attr):
		return getattr(self.editor, attr)

	def compatible(self, aclass):
		obj = self.editor
		#Just a hack. Don't unerstand why normal editor is not recognized.
		#This hack may produces a problem in future.
		return 1
#		return isinstance(obj, aclass) or issubclass(obj.EditedClass, aclass)



class EditSelection(Selection):

	is_EditSelection = 1

	drag_this = None
	editor = None

	def __init__(self, copy_from=None):
		Selection.__init__(self, copy_from)
		self.check_edit_mode()
		if type(copy_from) == InstanceType \
			and copy_from.__class__ == self.__class__:
			self.editor = copy_from.editor
		else:
			self.get_editor()

	def check_edit_mode(self):
		# allow only one object for editing at a time
		if self.objects:
			if len(self.objects) > 1 or not self.objects[0][-1].has_edit_mode:
				self.objects = []

	def get_editor(self):
		if self.objects:
			self.editor = EditorWrapper(self.objects[0][-1].Editor())
		else:
			self.editor = None

	def GetHandles(self):
		if self.editor is not None:
			return self.editor.GetHandles()
		return []

	def ButtonDown(self, p, button, state):
		if self.drag_this is not None:
			return self.drag_this.ButtonDown(p, button, state)
		else:
			return None

	def MouseMove(self, p, state):
		if self.drag_this is not None:
			self.drag_this.MouseMove(p, state)

	def ButtonUp(self, p, button, state, forget_trafo=0):
		if self.drag_this is not None:
			self.del_lazy_attrs()
			return _("Edit Object"), self.drag_this.ButtonUp(p, button, state)
		return ('', None)
		# XXX make the undo text more general by a method of graphics objects

	def Show(self, device, partially=0):
		if self.editor is not None:
			self.editor.Show(device, partially)

	def Hide(self, device, partially=0):
		if self.editor is not None:
			self.editor.Hide(device, partially)

	def DrawDragged(self, device, partial):
		if self.editor is not None:
			self.editor.DrawDragged(device, partial)

	def SetSelection(self, info):
		old_sel = self.objects
		Selection.SetSelection(self, info)
		self.check_edit_mode()
		if old_sel != self.objects:
			self.get_editor()
			return 1
		return 0

	def SelectPoint(self, p, rect, device, mode=const.SelectSet):
		self.drag_this = None
		if self.editor is not None:
			if self.editor.SelectPoint(p, rect, device, mode):
				self.drag_this = self.editor
		return self.drag_this != None

	def SelectHandle(self, handle, mode=SelectSet):
		if self.editor is not None:
			self.editor.SelectHandle(handle, mode)
			self.drag_this = self.editor
		else:
			self.drag_this = None

	def SelectRect(self, rect, mode=SelectSet):
		self.drag_this = None
		if self.editor is not None:
			if self.editor.SelectRect(rect, mode):
				self.drag_this = self.editor
		return self.drag_this != None

	def CallObjectMethod(self, aclass, methodname, args):
		if len(self.objects) == 1:
			if self.editor is not None:
				obj = self.editor
				if not obj.compatible(aclass):
					warn(INTERNAL, 'EditSelection.GetObjectMethod: '
							'editor %s is not compatible with class %s',
							self.editor, aclass)
					return NullUndo
			else:
				obj = self.objects[0][-1]
				if not isinstance(obj, aclass):
					warn(INTERNAL, 'EditSelection.GetObjectMethod: '
							'object is not instance of %s', aclass)
					return NullUndo
			try:
				method = getattr(obj, methodname)
			except AttributeError:
				warn(INTERNAL, 'EditSelection.GetObjectMethod: '
						'no method %s for class %s', methodname, aclass)
				return NullUndo

			undo = apply(method, args)
			if undo is None:
				undo = NullUndo
			return undo
		return NullUndo

	def GetObjectMethod(self, aclass, method):
		if len(self.objects) == 1:
			if self.editor is not None:
				obj = self.editor
				if not obj.compatible(aclass):
					warn(INTERNAL, 'EditSelection.GetObjectMethod: '
							'editor is not compatible with class %s', aclass)
					return None
			else:
				obj = self.objects[0][-1]
				if not isinstance(obj, aclass):
					warn(INTERNAL, 'EditSelection.GetObjectMethod: '
							'object is not instance of %s', aclass)
					return None
			try:
				return getattr(obj, method)
			except AttributeError:
				warn(INTERNAL, 'EditSelection.GetObjectMethod: '
						'no method %s for class %s', method, aclass)
				pass
		return None


	def ChangeRect(self):
		if self.editor is not None:
			return self.editor.ChangeRect()
		return self.bounding_rect

	def InfoText(self):
		# Return a string describing the selected object(s)
		# XXX we shouldn't access document.layers directly
		if self.editor is not None:
			return self.editor.Info()
		else:
			return _("No Selection")

	def CurrentInfoText(self):
		if self.editor is not None:
			return self.editor.CurrentInfoText()
		else:
			return ""

