# -*- 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 app.utils.os_utils import flatten

from app.UI.skpixmaps import pixmaps
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
#             hor_sel=ceil(floor(10**3*(br.right - br.left)/2.83465)/10)/100
#             ver_sel=ceil(floor(10**3*(br.top - br.bottom)/2.83465)/10)/100
			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],
					[const.CurTurn] * 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 ""
		
