changeset 307:22253b2c9b14

- added LightEdit editor plugin (needs light branch to work; deactivated if lighting renderer is not available) - added animation viewer to ObjectEdit - several bugfixes for ObjectEdit plugin FEATURES: - ObjectEdit - viewing and rotating animated instances (rotations are hardcoded for now, FIFE needs to expose available angles to python in order to make animation rotation work for every client) - LightEdit - test global light values
author chewie@33b003aa-7bff-0310-803a-e67f0ece8222
date Tue, 11 Aug 2009 15:32:54 +0000
parents 6177cdf72489
children af0b233e246f
files clients/editor/gui/lightedit.xml clients/editor/gui/objectedit.xml clients/editor/plugins/LightEdit.py clients/editor/plugins/ObjectEdit.py
diffstat 4 files changed, 758 insertions(+), 106 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/gui/lightedit.xml	Tue Aug 11 15:32:54 2009 +0000
@@ -0,0 +1,32 @@
+<Panel title="Light editor" position="10,700">
+	<Label text="RGB values (float)" />
+	<HBox>
+		<Label text="R" />
+		<TextBox text="1.0" name="value_R" min_size="20,20"/>
+		<Button text="+" name="increase_R" />
+		<Button text="-" name="decrease_R" />
+	</HBox>
+	<HBox>
+		<Label text="G" />
+		<TextBox text="1.0" name="value_G" min_size="20,20"/>
+		<Button text="+" name="increase_G" />
+		<Button text="-" name="decrease_G" />
+	</HBox>
+	<HBox>
+		<Label text="B" />
+		<TextBox text="1.0" name="value_B" min_size="20,20"/>
+		<Button text="+" name="increase_B" />
+		<Button text="-" name="decrease_B" />
+	</HBox>
+	<HBox>
+		<Label text="A" />
+		<TextBox text="1.0" name="value_A" min_size="20,20"/>
+		<Button text="+" name="increase_A" />
+		<Button text="-" name="decrease_A" />
+	</HBox>
+	<VBox>
+		<ToggleButton text="Enable" name="enable_global_light" />
+		<Button text="Random" name="random_global_light" />
+		<Button text="Reset" name="reset_global_light" />
+	</VBox>	
+</Panel>
--- a/clients/editor/gui/objectedit.xml	Sat Aug 08 14:24:35 2009 +0000
+++ b/clients/editor/gui/objectedit.xml	Tue Aug 11 15:32:54 2009 +0000
@@ -1,9 +1,9 @@
-<Panel title="Object editor" position="10,700"> <!-- size="200,250" min_size="200,250" max_size="200,250" > -->
-
+<Panel title="Object editor" position="10,700" min_size="200,250"> <!-- size="200,250" min_size="200,250" max_size="200,250" > -->
 	<Label text="           Object" background_color="0,0,0" />
-	<Label text="Namespace:" min_size="85,20"/>
-	<Label text="None" name="object_namespace" min_size="30,20"/>
-
+	<HBox>
+		<Label text="Namespace:" min_size="85,20"/>
+		<Label text="None" name="object_namespace" min_size="30,20"/>
+	</HBox>
 	<HBox>	 
 		<Label text="Object ID:" min_size="85,20"/>
 		<Label text="None" name="object_id" min_size="30,20"/>
@@ -16,28 +16,56 @@
 		<TextBox text="0" name="object_static" min_size="20,20"/>
 	</HBox>
 
-	<Button name="change_data" text="Save object"/>
-
 	<Label text="          Selected Instance" background_color="0,0,0" />
 
 	<HBox>	 
 		<Label text="Select Rotation:" min_size="85,20" />
-		<DropDown min_size="80,0" name="select_rotations"/>
 	</HBox>
 	
+	<DropDown min_size="80,0" name="select_rotations"/>
+
+	<VBox>
+		<Label text="Offset:" min_size="45,20"/>
+		<HBox>
+			<Label text="X: " min_size="25,20"/>
+			<TextBox text="0" name="x_offset" size="30,20" min_size="30,20" max_size="30,20" />
+			<Button name="x_offset_up" text="+" max_size="20,20"/>
+			<Button name="x_offset_dn" text="-" max_size="20,20"/>
+		</HBox>
+
+		<HBox>
+			<Label text="Y: " min_size="25,20"/>
+			<TextBox text="0" name="y_offset" size="30,20" min_size="30,20" max_size="30,20"/>
+			<Button name="y_offset_up" text="+" max_size="20,20"/>
+			<Button name="y_offset_dn" text="-" max_size="20,20"/>
+		</HBox>
+	</VBox>
+
+	<Label text="Selected Instance" min_size="85,20" />
+	
 	<HBox >	 
 		<Label text="Instance ID:" min_size="85,20"/>
 		<TextBox text="None" name="instance_id" min_size="30,20"/>
 	</HBox>
 	<HBox >	 
 		<Label text="Instance rot:" min_size="85,20"/>
-		<TextBox text="0" name="instance_rotation" min_size="30,20" hexpand="1"/>
+		<TextBox text="0" name="instance_rotation" min_size="30,20"/>
 	</HBox>
 
-	<VBox name="animation_panel_wrapper" max_size="150,50">
+	<HBox>
+		<Button name="use_data" text="Use"/>
+	</HBox>
+	<Spacer />
+	<Button name="change_data" text="Save rotation"/>
+
+
+	<Label text="          Animation viewer" background_color="0,0,0" />
+	<VBox name="animation_panel_wrapper">
 		<Spacer />
 		<VBox name="animation_panel">
-			<Label text="Animation panel" min_size="85,20" />
+			<Label text="Actions:" min_size="85,20" />
+			<DropDown min_size="80,0" name="select_actions"/>
+
 			<HBox>
 				<Button name="anim_start_pos" text="S" max_size="20,20"/>
 				<Button name="anim_left" text="l1" max_size="20,20"/>
@@ -45,11 +73,21 @@
 				<Button name="anim_right" text="r1" max_size="20,20"/>
 				<Button name="anim_end_pos" text="E" max_size="20,20"/>
 			</HBox>
+			<HBox>
+				<ToggleButton name="anim_playback" text="P" max_size="20,20"/>
+				<CheckBox name="anim_loop" marked="1" text="Loop:"/>
+			</HBox>
+			
+			<HBox>
+				<Label text="Rotation: (" min_size="60,20" />
+				<Label name="anim_rotation" text="" min_size="20,20"/>
+				<Label text=")" min_size="10,20" />
+			</HBox>			
+
+			<VBox min_size="100,100" size="100,100">
+			<Icon image="gui/icons/add_instance.png" size="200,200" min_size="250,250" name="animTest"/>
+			</VBox>
 		</VBox>
 	</VBox>
 
-	<HBox>
-		<Button name="use_data" text="Use"/>
-	</HBox>	
-
 </Panel>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/plugins/LightEdit.py	Tue Aug 11 15:32:54 2009 +0000
@@ -0,0 +1,292 @@
+#!/usr/bin/env python
+# coding: utf-8
+# ###################################################
+# Copyright (C) 2008 The Zero-Projekt team
+# http://zero-projekt.net
+# info@zero-projekt.net
+# This file is part of Zero "Was vom Morgen blieb"
+#
+# The Zero-Projekt codebase is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the
+# Free Software Foundation, Inc.,
+# 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+# ###################################################
+
+""" a tool for FIFEdit to test global light """
+
+import fife
+import pychan
+import pychan.widgets as widgets
+from pychan.tools import callbackWithArguments as cbwa
+
+from fife_timer import Timer
+
+import scripts
+import scripts.plugin as plugin
+from scripts.events import *
+from scripts.gui.action import Action
+
+import random
+
+DEFAULT_GLOBAL_LIGHT = {
+	"R"	:	1.0,
+	"G"	:	1.0,
+	"B"	:	1.0,
+	"A" :	1.0,
+}
+DEFAULT_LIGHT_ID = "LightEdit"
+
+class LightEdit(plugin.Plugin):
+	""" The B{LightEdit} module is a plugin for FIFedit and allows to change the
+	global light value
+	
+		FEATURES:
+			- enable FIFE lighting renderer
+			- generate random light values
+			- test lightsetups by manipulating the color channels
+			- reset to default
+	"""
+	def __init__(self):
+		self.active = False
+		
+		self._renderer = None
+		self._camera = None
+		self._enabled = False
+		self._light = False
+		
+		self.map_loaded = False
+		
+		self._color = {}
+		self._color.update(DEFAULT_GLOBAL_LIGHT)
+		
+		random.seed()
+		
+		if "LightRenderer" in dir(fife):
+			self._renderer_available = True
+		else:
+			self._renderer_available = False
+
+	def _reset(self):
+		""" 	resets all dynamic vars	"""
+		pass		
+
+	def enable(self):
+		""" plugin method """
+		if not self._renderer_available:
+			self._enabled = False
+			return
+		if self._enabled is True:
+			return
+			
+		self._editor = scripts.editor.getEditor()
+		self.engine = self._editor.getEngine()
+		
+		self._showAction = Action(unicode(self.getName(),"utf-8"), checkable=True)
+		scripts.gui.action.activated.connect(self.toggle_gui, sender=self._showAction)
+		
+		self._editor._toolsMenu.addAction(self._showAction)
+		
+		events.postMapShown.connect(self.update_renderer)
+		events.onMapChanged.connect(self.update_renderer)
+		
+		self._reset()		
+		self.create_gui()
+
+	def disable(self):
+		""" plugin method """
+		if self._enabled is False:
+			return
+			
+		self._reset()
+		self.container.hide()
+		self.removeAllChildren()
+		
+		self._editor._toolsMenu.removeAction(self._showAction)
+		
+		events.postMapShown.disconnect(self.update_renderer)
+		events.onMapChanged.disconnect(self.update_renderer)
+
+	def isEnabled(self):
+		""" plugin method """
+		return self._enabled;
+
+	def getName(self):
+		""" plugin method """
+		return "Light editor"
+
+	def create_gui(self):
+		""" create gui container and setup callbacks """
+		self.container = pychan.loadXML('gui/lightedit.xml')
+		self.container.mapEvents({
+			"enable_global_light"	:	self.toggle_light,
+			"random_global_light"	:	self.random_color,
+			"reset_global_light"	:	self.reset_global_light,
+			
+			"increase_R"			:	cbwa(self.increase_color, r=True),
+			"decrease_R"			:	cbwa(self.decrease_color, r=True),
+			"value_R/mouseWheelMovedUp"			:	cbwa(self.increase_color, step=0.2, r=True),
+			"value_R/mouseWheelMovedDown"		:	cbwa(self.decrease_color, step=0.2, r=True),
+			
+			"increase_G"			:	cbwa(self.increase_color, g=True),
+			"decrease_G"			:	cbwa(self.decrease_color, g=True),
+			"value_G/mouseWheelMovedUp"			:	cbwa(self.increase_color, step=0.2, g=True),
+			"value_G/mouseWheelMovedDown"		:	cbwa(self.decrease_color, step=0.2, g=True),
+			
+			"increase_B"			:	cbwa(self.increase_color, b=True),
+			"decrease_B"			:	cbwa(self.decrease_color, b=True),
+			"value_B/mouseWheelMovedUp"			:	cbwa(self.increase_color, step=0.2, b=True),
+			"value_B/mouseWheelMovedDown"		:	cbwa(self.decrease_color, step=0.2, b=True),
+			
+			"increase_A"			:	cbwa(self.increase_color, a=True),
+			"decrease_A"			:	cbwa(self.decrease_color, a=True),			
+			"value_A/mouseWheelMovedUp"			:	cbwa(self.increase_color, step=0.2, a=True),
+			"value_A/mouseWheelMovedDown"		:	cbwa(self.decrease_color, step=0.2, a=True),			
+		})
+		self._widgets = {
+			"enable_global_light"	:	self.container.findChild(name="enable_global_light"),
+			"random_global_light"	:	self.container.findChild(name="random_global_light"),
+			"reset_global_light"	:	self.container.findChild(name="reset_global_light"),
+			
+			"value_R"				:	self.container.findChild(name="value_R"),
+			"value_G"				:	self.container.findChild(name="value_G"),
+			"value_B"				:	self.container.findChild(name="value_B"),
+			"value_A"				:	self.container.findChild(name="value_A"),			
+		}
+		
+	def toggle_gui(self):
+		""" show / hide the gui """
+		if self.active:
+			self.active = False
+			if self.container.isVisible() or self.container.isDocked():
+				self.container.setDocked(False)
+				self.container.hide()
+			self._showAction.setChecked(False)
+		else:
+			self.active = True
+			self._showAction.setChecked(True)
+			self.container.show()
+			
+	def toggle_light(self):
+		""" toggle light on / off """
+		if not self._renderer:
+			self._widgets['enable_global_light']._setToggled(False)
+			return
+		
+		if self._light:
+			self._light = False
+			self.reset_global_light()
+			self._renderer.setEnabled(False)
+		else:
+			self._light = True
+			self._renderer.setEnabled(True)
+			
+	def update_renderer(self):
+		""" sets current camera and renderer
+			bound to FIFedit core (updated on map change)
+		"""
+		self._camera = self._editor.getActiveMapView().getCamera()
+		self._renderer = fife.LightRenderer.getInstance(self._camera)
+		
+	def update_gui(self):
+		""" update gui widgets according to plugin data """
+		self._widgets["value_R"].text = unicode(str(self._color["R"]))
+		self._widgets["value_G"].text = unicode(str(self._color["G"]))
+		self._widgets["value_B"].text = unicode(str(self._color["B"]))
+		self._widgets["value_A"].text = unicode(str(self._color["A"]))
+						
+	def reset_global_light(self):
+		""" reset global light to default values (1.0) """
+		if not self._renderer: return
+
+		self._color.update(DEFAULT_GLOBAL_LIGHT)
+		self.update_gui()
+		self.set_global_light()
+			
+	def increase_color(self, step=0.1, r=None, g=None, b=None, a=None):
+		"""	increase a given color value by step value
+		
+		@type	step	float
+		@param	step	the step for changing the color channel
+		@type	r		bool
+		@param	r		flag to alter red color value
+		@type	g		bool
+		@param	g		flag to alter green color value
+		@type	b		bool
+		@param	b		flag to alter blue color value
+		@type	a		bool
+		@type	a		flag to alter alpha channel value (no effect atm)		
+		"""
+		if r:
+			self._color["R"] += step
+		if g:
+			self._color["G"] += step
+		if b:
+			self._color["B"] += step
+		if a:
+			self._color["A"] += step
+
+		self.update_gui()					
+		self.set_global_light()
+			
+	def decrease_color(self, step=0.1, r=None, g=None, b=None, a=None):
+		"""	decrease a given color value by step value
+		
+		@type	step	float
+		@param	step	the step for changing the color channel
+		@type	r		bool
+		@param	r		flag to alter red color value
+		@type	g		bool
+		@param	g		flag to alter green color value
+		@type	b		bool
+		@param	b		flag to alter blue color value
+		@type	a		bool
+		@type	a		flag to alter alpha channel value (no effect atm)		
+		"""
+		if r:
+			self._color["R"] -= step
+		if g:
+			self._color["G"] -= step
+		if b:
+			self._color["B"] -= step
+		if a:
+			self._color["A"] -= step
+			
+		self.update_gui()					
+		self.set_global_light()
+			
+	def random_color(self):
+		""" generate random values for color channels """
+		if not self._renderer: return	
+		
+		self._color["R"] = random.uniform(0,2)
+		self._color["G"] = random.uniform(0,2)
+		self._color["B"] = random.uniform(0,2)
+		self._color["A"] = random.uniform(0,2)
+
+		self.update_gui()					
+		self.set_global_light()
+		
+	def set_global_light(self):
+		""" update the global light with the current set colors """
+		if not self._renderer: return
+
+		self._renderer.removeAll(DEFAULT_LIGHT_ID)
+		self._renderer.setglobalLight(
+			DEFAULT_LIGHT_ID,
+			1,
+			self._color["R"],
+			self._color["G"],
+			self._color["B"],
+			self._color["A"]
+		)
+		
--- a/clients/editor/plugins/ObjectEdit.py	Sat Aug 08 14:24:35 2009 +0000
+++ b/clients/editor/plugins/ObjectEdit.py	Tue Aug 11 15:32:54 2009 +0000
@@ -1,3 +1,4 @@
+#!/usr/bin/env python
 # coding: utf-8
 # ###################################################
 # Copyright (C) 2008 The Zero-Projekt team
@@ -28,39 +29,56 @@
 import pychan.widgets as widgets
 from pychan.tools import callbackWithArguments as cbwa
 
+from fife_timer import Timer
+
 import scripts
 import scripts.plugin as plugin
 from scripts.events import *
 from scripts.gui.action import Action
 
+
+import os
+try:
+	import xml.etree.cElementTree as ET
+except:
+	import xml.etree.ElementTree as ET
+
 import math
 
+WHITE = {
+	"r"	:	205,
+	"g"	:	205,
+	"b"	:	205
+}
+OUTLINE_SIZE = 1
+
 class ObjectEdit(plugin.Plugin):
 	""" The B{ObjectEdit} module is a plugin for FIFedit and allows to edit
-	attributes of an selected instance - like instance id or rotation
+	attributes of an selected instance - like offset, instance id or rotation
 	(namespaces and object id editing is excluded)
 	
 	current features:
 		- click instance and get all known data
-		- edit rotation, instance id
+		- edit offsets, rotation, instance id
+		- save offsets to object file
 		- outline highlighting of the selected object
-			
-	missing features:
-		- blocking flag (flag doesn't work yet from FIFE side)
-		- static flag (flag doesn't work yet from FIFE side)		
-		- object saving
-		- a lot of bug fixing concerning the rotation
-		- the module should be able to use the editors global undo history
+		- animation viewer
+
+	FIXME:
+		- add static and blocking flag to save routine
+		- fix animation rotation (FIFE has no method yet to export all 
+		  angles of an animation to python)
 	"""
 	def __init__(self):
 		self.active = False
 		self._camera = None
 		self._layer = None
+		self._anim_timer = None
 		
 		self._enabled = False
 		
 		self.imagepool = None
-		self.animationpool = None
+		self._animationpool = None
 		
 		self.guidata = {}
 		self.objectdata = {}
@@ -70,9 +88,20 @@
 			resets all dynamic vars, but leaves out static ones (e.g. camera, layer)
 
 		"""
+		if self._anim_timer:
+			self._anim_timer.stop()
+			# reset the ToggleButton
+			if self._gui_anim_playback._isToggled():
+				self._gui_anim_playback._setToggled(0)
+		self._anim_timer = None
+		
+		self._object = None
 		self._instances = None
 		self._image = None
+		self._image_default_x_offset = None
+		self._image_default_y_offset = None
 		self._animation = False
+		self._anim_data = {}
 		self._rotation = None
 		self._avail_rotations = []
 		self._namespace = None	
@@ -83,10 +112,10 @@
 		self._fixed_rotation = None
 		
 		if self._camera is not None:
-			self.renderer.removeAllOutlines()		
-		
+			self.renderer.removeAllOutlines()			
 
 	def enable(self):
+		""" plugin method """
 		if self._enabled is True:
 			return
 			
@@ -94,9 +123,9 @@
 		self.engine = self._editor.getEngine()
 		
 		self.imagepool = self.engine.getImagePool()
-		self.animationpool = self.engine.getAnimationPool()
+		self._animationpool = self.engine.getAnimationPool()
 		
-		self._showAction = Action(u"Object editor", checkable=True)
+		self._showAction = Action(unicode(self.getName(),"utf-8"), checkable=True)
 		scripts.gui.action.activated.connect(self.toggle_gui, sender=self._showAction)
 		
 		self._editor._toolsMenu.addAction(self._showAction)
@@ -107,6 +136,7 @@
 		self.create_gui()
 
 	def disable(self):
+		""" plugin method """
 		if self._enabled is False:
 			return
 			
@@ -119,77 +149,173 @@
 		self._editor._toolsMenu.removeAction(self._showAction)
 
 	def isEnabled(self):
+		""" plugin method """
 		return self._enabled;
 
 	def getName(self):
-		return "Object editor"
+		""" plugin method """
+		return "Object editor v2"
 
 	def create_gui(self):
 		"""
 			- creates the gui skeleton by loading the xml file
 			- finds some important childs and saves their widget in the object
+			
+		FIXME:
+			- move all dynamic widgets to dict
 		"""
 		self.container = pychan.loadXML('gui/objectedit.xml')
 		self.container.mapEvents({
-			'use_data'		: self.use_user_data,
+			'x_offset_up' 		: cbwa(self.change_offset_x, 1),
+			'x_offset_dn' 		: cbwa(self.change_offset_x, -1),
+			
+			'y_offset_up' 		: cbwa(self.change_offset_y, 1),
+			'y_offset_dn' 		: cbwa(self.change_offset_y, -1),
 			
+			'use_data'			: self.use_user_data,
+			'change_data'		: self.save_user_data,
+			
+			'anim_left'			: self.previous_anim_frame,
+			'anim_right'		: self.next_anim_frame,
+			'anim_start_pos' 	: self.anim_start_frame,
+			'anim_end_pos'		: self.anim_end_frame,
 		})
 
 		self._gui_anim_panel_wrapper = self.container.findChild(name="animation_panel_wrapper")
 		self._gui_anim_panel = self._gui_anim_panel_wrapper.findChild(name="animation_panel")
 		
-		self._gui_anim_panel_wrapper.removeChild(self._gui_anim_panel)
+		self._gui_rotation_dropdown = self.container.findChild(name="select_rotations")
+		self._gui_rotation_dropdown.capture(self.gui_rotate_instance,"mouseWheelMovedUp")
+		self._gui_rotation_dropdown.capture(self.gui_rotate_instance,"mouseWheelMovedDown")
+		self._gui_rotation_dropdown.capture(self.gui_rotate_instance,"action")	
+		
+		self._gui_anim_actions_dropdown = self._gui_anim_panel_wrapper.findChild(name="select_actions")	
+		self._gui_anim_actions_dropdown.capture(self.eval_gui_anim_action,"mouseWheelMovedUp")
+		self._gui_anim_actions_dropdown.capture(self.eval_gui_anim_action,"mouseWheelMovedDown")
+		self._gui_anim_actions_dropdown.capture(self.eval_gui_anim_action,"action")	
+		
+		self._gui_anim_playback = self._gui_anim_panel_wrapper.findChild(name="anim_playback")
+		self._gui_anim_playback.capture(self.anim_playback, "mousePressed")
+		self._gui_anim_loop = self._gui_anim_panel_wrapper.findChild(name="anim_loop")
 
-		self._gui_rotation_dropdown = self.container.findChild(name="select_rotations")
-
+		self._gui_current_frame = self._gui_anim_panel_wrapper.findChild(name="anim_current_frame")
+		self._gui_current_frame.capture(self.previous_anim_frame,"mouseWheelMovedUp")
+		self._gui_current_frame.capture(self.next_anim_frame,"mouseWheelMovedDown")	
+		
+		self._gui_xoffset_textfield = self.container.findChild(name="x_offset")
+		self._gui_yoffset_textfield = self.container.findChild(name="y_offset")
+		
 		self._gui_instance_id_textfield = self.container.findChild(name="instance_id")
 
-	def _get_gui_size(self):
+	def anim_playback(self, widget):
+		""" start / stop playback of an animation due to status of a gui ToggleButton
+			Sets also two ivars of timer object (active & loop)
 		"""
-			gets the current size of the gui window and calculates new position
-			(atm top right corner)
-		"""
-		size = self.container.size
-		self.position = ((pychan.internal.screen_width() - 50 - size[0]), 50)
+		if widget._isToggled():
+			self._anim_timer.stop()
+			self._anim_timer.active = False
+		else:
+			frame_delay = self._anim_data['obj'].getFrameDuration(self._anim_data['current'])
+			self._anim_timer = Timer(delay=frame_delay,callback=self.next_anim_frame)
+			self._anim_timer.active = True
+			self._anim_timer.loop = self._gui_anim_loop._isMarked()
+			self._anim_timer.start()
+	
+	def previous_anim_frame(self):
+		""" show previous anim frame """
+		if self._anim_data['current'] > 0:
+			self._anim_data['current'] -= 1
+			self.update_gui()
+		
+	def next_anim_frame(self):
+		""" show next anim frame and reset animation frame to 0 if playback looping is active"""
+		if self._anim_timer.loop and (self._anim_data['current'] == self._anim_data['frames']):
+			self._anim_data['current'] = 0
 		
+		if self._anim_data['current'] < self._anim_data['frames']:
+			self._anim_data['current'] += 1
+			self.update_gui()
+		
+	def anim_start_frame(self):
+		""" set start frame of animation """
+		self._anim_data['current'] = 0
+		self.update_gui()
+
+	def anim_end_frame(self):
+		""" set end frame of animation """		
+		self._anim_data['current'] = self._anim_data['frames']
+		self.update_gui()
+	
+	def set_default_offset(self, axis):
+		""" set default image offset for given axis """
+		if axis == 'x':
+			self.set_offset(x=self._image_default_x_offset)
+		elif axis == 'y':
+			self.set_offset(y=self._image_default_y_offset)
+
 	def update_gui(self):
 		"""
 			updates the gui widgets with current instance data
 			
-			FIXME: 
-				- drop animation support or turn it into something useful
 		"""
-		#if self._animation is False:
-			#try:
-				#self._gui_anim_panel_wrapper.removeChild(self._gui_anim_panel)
-			#except:
-				#pass
-		#elif self._animation is True:
-			#try:
-				#self._gui_anim_panel_wrapper.resizeToContent()				
-				#self._gui_anim_panel_wrapper.addChild(self._gui_anim_panel)
-				#self._gui_anim_panel_wrapper.resizeToContent()
-			#except:
-				#pass
+		# show the image we retrieved from an animated object
+		if self._animation:
+			if not self._gui_anim_panel_wrapper.findChild(name="animation_panel"):
+				self._gui_anim_panel_wrapper.addChild(self._gui_anim_panel)			
 
+			# get current selected image and update the icon widget				
+			dur = 0
+			for i in range(self._anim_data['frames']):
+				dur += self._anim_data['obj'].getFrameDuration(i)
+				
+				# set new duration for the playback timer
+				if self._anim_timer:
+					frame_delay = self._anim_data['obj'].getFrameDuration(self._anim_data['current'])
+				
+				if i == self._anim_data['current']:
+					# set new duration for the playback timer
+					if self._anim_timer and self._anim_timer.active:
+						self._anim_timer.setPeriod(self._anim_data['obj'].getFrameDuration(self._anim_data['current']))		
+					break
+											
+			image = self._anim_data['obj'].getFrameByTimestamp(dur)	
+			self.container.findChild(name="animTest").image = image.getResourceFile()
+			self.container.findChild(name="animTest").size= (250,250)
+			self.container.findChild(name="animTest").min_size= (250,250)
+		
+			self.container.distributeInitialData({
+				'anim_current_frame'	:	unicode(str(self._anim_data['current'])),
+				'anim_rotation'			:	unicode(str(self._anim_data['obj'].getDirection())),
+			})	
+
+		else:
+			if self._gui_anim_panel_wrapper.findChild(name="animation_panel"):
+				self._gui_anim_panel_wrapper.removeChild(self._gui_anim_panel)			
+			
+		if self._image is not None:
+			x_offset = unicode( self._image.getXShift() )
+			y_offset = unicode( self._image.getYShift() )
+		else:
+			x_offset = unicode( 0 )
+			y_offset = unicode( 0 )
+			
 		self.container.distributeInitialData({
 			'select_rotations' 	: self._avail_rotations,
 			'instance_id'		: unicode( self._instances[0].getId() ),
 			'object_id'			: unicode( self._object_id ),
+			'x_offset'			: x_offset,
+			'y_offset'			: y_offset,
 			'instance_rotation' : unicode( self._instances[0].getRotation() ),
 			'object_namespace'	: unicode( self._namespace ),
 			'object_blocking'	: unicode( self._blocking ),
 			'object_static'		: unicode( self._static ),
 		})
-		try:
-			print self._avail_rotations
-			print self._fixed_rotation
-			index = self._avail_rotations.index( str(self._fixed_rotation) )
+		
+		if not self._animation:
+			index = self._avail_rotations.index( self._fixed_rotation )
 			self._gui_rotation_dropdown._setSelected(index)
-		except:
-#			pass
-			print "Angle (", self._fixed_rotation, ") not supported by this instance"
-		self.container.adaptLayout()
+
+		self.container.adaptLayout()			
 		
 	def toggle_gui(self):
 		"""
@@ -206,11 +332,43 @@
 			self._showAction.setChecked(True)
 	
 	def highlight_selected_instance(self):
-		"""
-			just highlights selected instance
+		""" highlights selected instance """
+		self.renderer.removeAllOutlines() 
+		self.renderer.addOutlined(self._instances[0], WHITE["r"], WHITE["g"], WHITE["b"], OUTLINE_SIZE)		
+			
+	def change_offset_x(self, value=1):
 		"""
-		self.renderer.removeAllOutlines() 
-		self.renderer.addOutlined(self._instances[0], 205, 205, 205, 1)
+			- callback for changing x offset
+			- changes x offset of current instance (image)
+			- updates gui
+			
+			@type	value:	int
+			@param	value:	the modifier for the x offset
+		"""		
+		if self._animation:
+			print "Offset changes of animations are not supported yet"
+			return
+		
+		if self._image is not None:
+			self._image.setXShift(self._image.getXShift() + value)
+			self.update_gui()
+
+	def change_offset_y(self, value=1):
+		"""
+			- callback for changing y offset
+			- changes y offset of current instance (image)
+			- updates gui
+			
+			@type	value:	int
+			@param	value:	the modifier for the y offset
+		"""
+		if self._animation:
+			print "Offset changes of animations are not supported yet"
+			return
+		
+		if self._image is not None:
+			self._image.setYShift(self._image.getYShift() + value)
+			self.update_gui()
 
 	def use_user_data(self):
 		"""
@@ -218,8 +376,22 @@
 			- writes current data record
 			- writes previous data record
 			- updates gui
-		"""		
+		
+			FIXME:
+			- parse user data in case user think strings are considered to be integer offset values...
+		"""
+		if self._animation:
+			print "Editing animated instances is not supported yet"
+			return		
+		
+		xoffset = self._gui_xoffset_textfield._getText()
+		yoffset = self._gui_yoffset_textfield._getText()
+		
 		instance_id = str(self._gui_instance_id_textfield._getText())
+		
+		if instance_id == "":
+			instance_id = "None"
+
 		if instance_id is not None and instance_id is not "None":
 			existing_instances = self._editor.getActiveMapView().getController()._layer.getInstances(instance_id)
 			if len(existing_instances) <= 0:
@@ -228,32 +400,127 @@
 			else:
 				print "Instance ID is already in use."
 		
-		# workaround - dropdown list only has 2 entries, but sends 3 -> pychan bug?
-		if len(self._avail_rotations) < self._gui_rotation_dropdown._getSelected():
-			index = len(self._avail_rotations)
-		else:
-			index = self._gui_rotation_dropdown._getSelected()
+		# update rotation
+		angle = self.eval_gui_rotation()
+		self.set_rotation(angle)
+		
+		# update offsets
+		self.set_offset(int(xoffset), int(yoffset))
+
+		self.update_gui()
+		
+	def save_user_data(self):
+		""" saves the current object to its xml file 
+		
+			NOTE:
+				- animations can't be saved for now
+				
+			FIXME:
+				- add missing object attributes to saving routine		
+		"""
+		if self._object is None:
+			return
+		if self._animation:
+			return
+		
+		file = self._object.getResourceFile()	
+		self.tree = ET.parse(file)
 		
-		# strange, but this helps to rotate the image correctly to the value the user selected
+		img_lst = self.tree.findall("image")
+		
+		# apply changes to the XML structure due to current user settings in the gui
+		for img_tag in img_lst:
+			if img_tag.attrib["direction"] == self._avail_rotations[self._gui_rotation_dropdown._getSelected()]:
+				img_tag.attrib["x_offset"] = self._gui_xoffset_textfield._getText()
+				img_tag.attrib["y_offset"] = self._gui_yoffset_textfield._getText()
+				break
+
+		xmlcontent = ET.tostring(self.tree.getroot())
+		
+		# save xml data beneath the <?fife type="object"?> definition into the object file
+		tmp = open(file, 'w')
+		tmp.write('<?fife type="object"?>\n')
+		tmp.write(xmlcontent + "\n")
+		tmp.close()
+		
+	def gui_rotate_instance(self):
+		""" rotate an instance due to selected angle """
+		angle = self.eval_gui_rotation()
+		self.set_rotation(angle)
+		
+	def eval_gui_rotation(self):
+		""" prepare rotation from gui and apply it to the current selected instance """
+		index = self._gui_rotation_dropdown._getSelected()
 		angle = int( self._avail_rotations[index] )
-		angle = int(angle - abs( self._camera.getTilt() ) )
+	
 		if angle == 360:
 			angle = 0
+			
+		return angle
 		
+	def eval_gui_anim_action(self):	
+		""" check the selected action of an animation and update the gui accordingly """
+		if not self._anim_data['actions']: return
+		
+		index = self._gui_anim_actions_dropdown._getSelected()
+		action = self._anim_data['actions'][index]
+		
+		self.update_anim_data(action)
+		self.update_gui()
+		
+	def set_rotation(self, angle):
+		""" set the rotation of the current instance """	
+#		print "...setting instance rotation from %s to %s" % (self._rotation, angle)
 		self._instances[0].setRotation(angle)
 		self.get_instance_data(None, None, angle)
+		self.update_gui()	
+#		print "...new internal FIFE rotation ", int(self._instances[0].getRotation())
+		
+	def set_offset(self, x=None, y=None):
+		""" set x/y offset of current selected instance """
+		if x is not None:
+			self._image.setXShift(x)
+		if y is not None:
+			self._image.setYShift(y)
+			
+	def update_anim_data(self, action=None):
+		""" update animation data for the current selected instance from FIFE's data structure
+		
+		@type	animation	FIFE animation
+		@return	animation	current selected animation
+		"""
+		if action:
+			animation_id = action.get2dGfxVisual().getAnimationIndexByAngle(self._fixed_rotation)
+			animation = self._animationpool.getAnimation(animation_id)		
 
-		self.update_gui()
+		action_ids = []
+		actions = []
+		
+		try:
+			action_ids = self._object.getActionIds()
+			for id in action_ids:
+				actions.append(self._object.getAction(id))
+		except:
+			pass		
+		
+		self._anim_data = {}
+		self._anim_data['obj'] = animation
+		self._anim_data['id'] = animation_id
+		self._anim_data['frames'] = animation.getNumFrames()
+		self._anim_data['current'] = 0
+		self._anim_data['actions'] = actions
+		self._anim_data['action_ids'] = action_ids
+		self._anim_data['default_action'] = self._object.getDefaultAction()	
+		self._anim_data['action'] = action		
+
+		return animation
 		
 	def get_instance_data(self, timestamp=None, frame=None, angle=-1, instance=None):
 		"""
 			- grabs all available data from both object and instance
-			- checks if we already hold a record (namespace + object id)
-			
+		
 			FIXME:
 				1.) we need to fix the instance rotation / rotation issue
-				2.) use correct instance rotations to store data for _each_ available rotation
-				3.) move record code out of this method
 		"""
 		visual = None
 		self._avail_rotations = []
@@ -262,9 +529,10 @@
 			instance = self._instances[0]
 			
 		object = instance.getObject()
+		self._object = object
 		self._namespace = object.getNamespace()
 		self._object_id = object.getId()
-
+		
 		self._instance_id = instance.getId()
 	
 		if self._instance_id == '':
@@ -289,50 +557,72 @@
 			print 'Fetching visual of object - failed. :/'
 			raise			
 
-#		print "Camera Tilt: ", self._camera.getTilt()
-#		print "Camera Rotation: ", self._camera.getRotation()
+#		print "Camera tilt: ", self._camera.getTilt()
+#		print "Camera rotation: ", self._camera.getRotation()
+#		print "Instance rotation: ", instance.getRotation()
 
 		self._fixed_rotation = int(instance.getRotation() + abs( self._camera.getTilt() ) )		
-		self._fixed_rotation = visual.getClosestMatchingAngle(self._fixed_rotation)	
+		self._fixed_rotation = instance.getRotation()
+#		self._fixed_rotation = visual.getClosestMatchingAngle(self._fixed_rotation)	
 
 		index = visual.getStaticImageIndexByAngle(self._fixed_rotation)
 
-		if index == -1:
+		if index is -1:
 			# object is an animation
 			self._animation = True
+			self._image = None
+			
 			# no static image available, try default action
 			action = object.getDefaultAction()
+
 			if action:
-				animation_id = action.get2dGfxVisual().getAnimationIndexByAngle(self._fixed_rotation)
-				animation = self.animationpool.getAnimation(animation_id)
-#				if timestamp is None and frame is not None:
-#					self._image = animation.getFrame(frame)	
-#				elif timestamp is not None and frame is None:
-#					self._image = animation.getFrameByTimestamp(timestamp)
-#				else:
-				self._image = animation.getFrameByTimestamp(0)
-				index = self._image.getPoolId()
-		elif index != -1:
+				animation = self.update_anim_data(action)
+
+				# update gui
+				if animation:
+					self._gui_anim_actions_dropdown._setItems(self._anim_data['action_ids'])
+					self._gui_anim_actions_dropdown._setSelected(0)					
+				
+				if timestamp is None and frame is not None:
+					self._image = animation.getFrame(frame)	
+				elif timestamp is not None and frame is None:
+					self._image = animation.getFrameByTimestamp(timestamp)
+				else:
+					self._image = animation.getFrameByTimestamp(0)
+		elif index is not -1:
 			# object is a static image
 			self._animation = False
 			self._image = self.imagepool.getImage(index)
 
 		if not self._animation:
-			rotation_tuple = visual.getStaticImageAngles()
-			for angle in rotation_tuple:
-				self._avail_rotations.append( str(angle) )
-				
+			rotations = visual.getStaticImageAngles()
+			for angle in rotations:
+				self._avail_rotations.append(angle)
+	
+			self._image_default_x_offset = self._image.getXShift()
+			self._image_default_y_offset = self._image.getYShift()
+		else:
+			# these doesn't work correctly
+#			rotations = [0,60,120,180,240,300]
 
-# FIXME: see l. 40
-		self._editor.getActiveMapView().getController()._objectedit_rotations = self._avail_rotations
-# end FIXME
-		
+			# testbench to get valid angles
+#			angle = 0
+#			rotations = []
+#			while angle != 360:
+#				angle += 10
+#				rotations.append(angle)
+
+			# estimated angles (for hex!) to make things work - use testbench to test 
+			# various angles and note down the working ones (watch instance 
+			# rotation and the animation rotations shown in the gui; valid
+			# angles are given once the rotations are in sync
+			self._avail_rotations = [9,69,139,169,249,319]
+
 	def input(self, instances):
 		"""
-			if called _and_ the objectedit is active,
+			if called _and_ the user wishes to edit offsets,
 			gets instance data and show gui
 			
-			(see run.py, pump() )
 		"""
 		if instances != self._instances:
 			if self.active is True:
@@ -351,8 +641,8 @@
 					self.update_gui()
 					self.container.adaptLayout()
 					self.container.show()
-					self._get_gui_size()
-					self.container._setPosition(self.position)
 				else:
 					self._reset()
 					self.container.hide()
+					
+		self.container.adaptLayout()