changeset 255:51cc05d862f2

Merged editor_rewrite branch to trunk. This contains changes that may break compatibility against existing clients. For a list of changes that may affect your client, see: http://wiki.fifengine.de/Changes_to_pychan_and_FIFE_in_editor_rewrite_branch
author cheesesucker@33b003aa-7bff-0310-803a-e67f0ece8222
date Mon, 08 Jun 2009 16:00:02 +0000
parents 10b5f7f36dd4
children e893afb4963b
files build/linux2-config-dist.py clients/editor/__init__.py clients/editor/fifedit.py clients/editor/gui/cameraedit.xml clients/editor/gui/eleveditor.xml clients/editor/gui/error.xml clients/editor/gui/filebrowser.xml clients/editor/gui/help.xml clients/editor/gui/icons/add_instance.png clients/editor/gui/icons/add_layer.png clients/editor/gui/icons/cycle_styles.png clients/editor/gui/icons/delete_layer.png clients/editor/gui/icons/erase_instance.png clients/editor/gui/icons/help.png clients/editor/gui/icons/is_visible.png clients/editor/gui/icons/load_map.png clients/editor/gui/icons/map_wizard.png clients/editor/gui/icons/move_instance.png clients/editor/gui/icons/new_map.png clients/editor/gui/icons/next_branch.png clients/editor/gui/icons/objectpicker.png clients/editor/gui/icons/previous_branch.png clients/editor/gui/icons/quit.png clients/editor/gui/icons/redo.png clients/editor/gui/icons/rotate_clockwise.png clients/editor/gui/icons/rotate_countercw.png clients/editor/gui/icons/save_allmaps.png clients/editor/gui/icons/save_map.png clients/editor/gui/icons/save_mapas.png clients/editor/gui/icons/select_instance.png clients/editor/gui/icons/take_screenshot.png clients/editor/gui/icons/undo.png clients/editor/gui/icons/zoom_default.png clients/editor/gui/icons/zoom_in.png clients/editor/gui/icons/zoom_out.png clients/editor/gui/layertool.xml clients/editor/gui/mapeditor.xml clients/editor/gui/objectedit.xml clients/editor/gui/objectselector.xml clients/editor/gui/selection.xml clients/editor/icons/add_instance.png clients/editor/icons/add_layer.png clients/editor/icons/delete_layer.png clients/editor/icons/eraser.png clients/editor/icons/hand.png clients/editor/icons/help.png clients/editor/icons/is_visible.png clients/editor/icons/load_map.png clients/editor/icons/map_wizard.png clients/editor/icons/new_map.png clients/editor/icons/quit.png clients/editor/icons/save_map.png clients/editor/icons/select_layer.png clients/editor/icons/take_screenshot.png clients/editor/icons/zoom_default.png clients/editor/icons/zoom_in.png clients/editor/icons/zoom_out.png clients/editor/input.py clients/editor/lang/infotext.txt clients/editor/listener.py clients/editor/misc/infotext.txt clients/editor/plugins/HistoryManager.py clients/editor/plugins/LayerTool.py clients/editor/plugins/ObjectEdit.py clients/editor/plugins/ObjectSelector.py clients/editor/plugins/importer.py clients/editor/plugins/layertool.py clients/editor/plugins/mapeditor.py clients/editor/plugins/maploader.py clients/editor/plugins/mapwizard.py clients/editor/plugins/objectedit.py clients/editor/plugins/objectselector.py clients/editor/plugins/plugin.py clients/editor/run.py clients/editor/scripts/__init__.py clients/editor/scripts/editor.py clients/editor/scripts/events/__init__.py clients/editor/scripts/events/events.py clients/editor/scripts/events/saferef.py clients/editor/scripts/events/signal.py clients/editor/scripts/gui/__init__.py clients/editor/scripts/gui/action.py clients/editor/scripts/gui/dockarea.py clients/editor/scripts/gui/error.py clients/editor/scripts/gui/faketabwidget.py clients/editor/scripts/gui/filemanager.py clients/editor/scripts/gui/input.py clients/editor/scripts/gui/mainwindow.py clients/editor/scripts/gui/mapeditor.py clients/editor/scripts/gui/menubar.py clients/editor/scripts/gui/panel.py clients/editor/scripts/gui/resizablebase.py clients/editor/scripts/gui/selection.py clients/editor/scripts/gui/statusbar.py clients/editor/scripts/gui/toolbar.py clients/editor/scripts/mapcontroller.py clients/editor/scripts/mapview.py clients/editor/scripts/plugin.py clients/editor/scripts/settings.py clients/editor/scripts/tests/__init__.py clients/editor/scripts/tests/undotest.py clients/editor/scripts/undomanager.py clients/editor/selection.py clients/editor/settings-dist.xml clients/editor/settings.py clients/pychan_demo/dynamic.py clients/pychan_demo/gui/demoapp.xml clients/pychan_demo/gui/slider.xml clients/pychan_demo/gui/styling.xml clients/pychan_demo/pychan_test.py clients/pychan_demo/sliders.py clients/pychan_demo/styling.py clients/rio_de_hola/maps/shrine.xml clients/rio_de_hola/maps/tourist_beach.xml clients/rio_de_hola/scripts/world.py engine/core/gui/guimanager.cpp engine/core/gui/guimanager.h engine/core/gui/widgets/clicklabel.cpp engine/core/gui/widgets/togglebutton.cpp engine/core/gui/widgets/togglebutton.h engine/core/gui/widgets/widgets.i engine/core/model/structures/layer.cpp engine/core/model/structures/layer.h engine/core/model/structures/layer.i engine/core/util/time/timemanager.cpp engine/core/video/cursor.cpp engine/core/video/cursor.h engine/core/video/video.i engine/core/view/renderers/cellselectionrenderer.cpp engine/core/view/renderers/cellselectionrenderer.h engine/core/view/renderers/cellselectionrenderer.i engine/extensions/fife_timer.py engine/extensions/filebrowser.py engine/extensions/pychan/__init__.py engine/extensions/pychan/dialogs.py engine/extensions/pychan/events.py engine/extensions/pychan/internal.py engine/extensions/pychan/layout.py engine/extensions/pychan/widgets/__init__.py engine/extensions/pychan/widgets/basictextwidget.py engine/extensions/pychan/widgets/buttons.py engine/extensions/pychan/widgets/containers.py engine/extensions/pychan/widgets/label.py engine/extensions/pychan/widgets/layout.py engine/extensions/pychan/widgets/listbox.py engine/extensions/pychan/widgets/scrollarea.py engine/extensions/pychan/widgets/slider.py engine/extensions/pychan/widgets/textbox.py engine/extensions/pychan/widgets/textfield.py engine/extensions/pychan/widgets/widget.py engine/extensions/timer.py
diffstat 151 files changed, 7329 insertions(+), 3025 deletions(-) [+]
line wrap: on
line diff
--- a/build/linux2-config-dist.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/build/linux2-config-dist.py	Mon Jun 08 16:00:02 2009 +0000
@@ -24,6 +24,7 @@
 	context.checkSimpleLib(['boost_filesystem', 'boost_filesystem-gcc', 'boost_filesystem-gcc41', 'boost_filesystem-mt'])
 	context.checkSimpleLib(['boost_regex', 'boost_regex-gcc', 'boost_regex-gcc41', 'boost_regex-mt'])
 	context.checkSimpleLib(['png'], 'png.h');
+	context.checkSimpleLib(['xcursor']);
 
 	if context.env['opengl']:
 		# linking explicitly against libstdc++ to work around Segfault_in_cxa_allocate_exception issue: http://wiki.fifengine.de/Segfault_in_cxa_allocate_exception
--- a/clients/editor/__init__.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/__init__.py	Mon Jun 08 16:00:02 2009 +0000
@@ -1,6 +1,3 @@
 # coding: utf-8
 
-from fifedit import Fifedit
-from selection import Selection
-
-__all__ = [ 'fifedit', 'selection', 'plugins' ]
+__all__ = [ 'plugins', 'scripts' ]
--- a/clients/editor/fifedit.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,30 +0,0 @@
-# coding: utf-8
-
-import fife
-import pychan
-import pychan.widgets as widgets
-
-class Fifedit():
-	"""
-	Fifedit is the editor tool. It is designed to be embedded in clients, most notably the editor.
-	Fifedit is a plugin system for editing tools. See L{registerPlugin}.
-	"""
-	def __init__(self, engine):
-		pychan.init(engine,debug=False)
-		self.gui = pychan.loadXML('gui/rootpanel.xml')
-		eventMap = {
-			'quitButton'  : self.quit
-		}
-		self.gui.mapEvents(eventMap)
-		self.gui.show()
-
-		self.active = True
-
-	# To create a plugin, just define menu_items with string keys and function values.
-	# The key will be displayed on the Editor menu, and the value will be called when the key is clicked.
-	def registerPlugin(self, plugin):
-		plugin.install(self.gui)
-
-	def quit(self):
-		self.gui.hide()
-		self.active = False
--- a/clients/editor/gui/cameraedit.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/gui/cameraedit.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,40 +1,40 @@
-<Window title="Camera Editor">
-	<VBox>
+<Panel title="Camera Editor">
+	<VBox hexpand="1">
 		<HBox>
-			<Label name="idLabel" text="Camera ID:"/>
-			<TextField name="idBox" min_size="50,10"/>
+			<Label name="idLabel" text="Camera ID:" min_size="125,10"/>
+			<TextField name="idBox" min_size="100,10"/>
 		</HBox>
 		<HBox>
-			<Label name="mapLabel" text="Camera Map ID:"/>
-      <TextField name="mapBox" min_size="50,10"/>
+			<Label name="mapLabel" text="Camera Map ID:" min_size="125,10"/>
+      <TextField name="mapBox" min_size="100,10"/>
 		</HBox>
 		<HBox>
-			<Label name="layerLabel" text="Camera Layer ID:"/>
-      <TextField name="layerBox" min_size="50,10"/>
+			<Label name="layerLabel" text="Camera Layer ID:" min_size="125,10"/>
+      <TextField name="layerBox" min_size="100,10"/>
 		</HBox>
 		<HBox>
-			<Label name="viewLabel" text="Viewport ('x,y,w,h'):"/>
-		  <TextField name="viewBox" text="0,0,640,480" min_size="50,10"/>
+			<Label name="viewLabel" text="Viewport ('x,y,w,h'):" min_size="125,10"/>
+		  <TextField name="viewBox" text="0,0,640,480" min_size="100,10"/>
 		</HBox>
 		<HBox>
-			<Label name="refhLabel" text="Reference cell height:"/>
-			<TextField name="refhBox" min_size="50,10"/>
+			<Label name="refhLabel" text="Reference cell height:" min_size="125,10"/>
+			<TextField name="refhBox" min_size="100,10"/>
 		</HBox>
 		<HBox>
-			<Label name="refwLabel" text="Reference cell width:"/>
-			<TextField name="refwBox" min_size="50,10"/>
+			<Label name="refwLabel" text="Reference cell width:" min_size="125,10"/>
+			<TextField name="refwBox" min_size="100,10"/>
 		</HBox>
 		<HBox>
-			<Label name="rotLabel" text="Rotation:"/>
-			<TextField name="rotBox" text="0" min_size="50,10"/>
+			<Label name="rotLabel" text="Rotation:" min_size="125,10"/>
+			<TextField name="rotBox" text="0" min_size="100,10"/>
 		</HBox>
 		<HBox>
-			<Label name="tiltLabel" text="Tilt:"/>
-			<TextField name="tiltBox" text="0" min_size="50,10"/>
+			<Label name="tiltLabel" text="Tilt:" min_size="125,10"/>
+			<TextField name="tiltBox" text="0" min_size="100,10"/>
 		</HBox>
 	</VBox>
 	<HBox>
 		<Button name="okButton" text="OK"/>
 		<Button name="cancelButton" text="Cancel"/>
 	</HBox>
-</Window>
+</Panel>
--- a/clients/editor/gui/eleveditor.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/gui/eleveditor.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,4 +1,4 @@
-<Window title="Elevation Editor">
+<Panel title="Elevation Editor">
 	<HBox>
 		<VBox name="Metadata Properties">
 		</VBox>
@@ -8,4 +8,4 @@
 			<Button name="closeButton" text="Close"/>
 		</VBox>
 	</HBox>
-</Window>
+</Panel>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/gui/error.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,6 @@
+<Panel name="errorWindow" title="Error" min_size="300, 100">
+	<ScrollArea>
+		<Label name="message" text="Error:" wrap_text="1" vexpand="1"/>
+	</ScrollArea>
+	<Button name="okButton" text="OK"/>
+</Panel>
--- a/clients/editor/gui/filebrowser.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/gui/filebrowser.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,21 +1,21 @@
-<Window title="File Browser">
-	<HBox>
-		<VBox>
+<Panel title="File Browser">
+	<HBox hexpand="1" vexpand="1">
+		<VBox hexpand="1">
 			<Label name="dirLabel" text="Directories:"/>
-			<ScrollArea size="200,300">
+			<ScrollArea min_size="200,300">
 				<ListBox name="dirList"/>
 			</ScrollArea>
 		</VBox>
-		<VBox name="fileColumn">
+		<VBox name="fileColumn" hexpand="1">
 			<Label name="fileLabel" text="Files:"/>
-			<ScrollArea size="200,300">
+			<ScrollArea min_size="200,300">
 				<ListBox name="fileList"/>
 			</ScrollArea>
 		</VBox>
-		<VBox>
+		<VBox hexpand="1">
 			<Button name="selectButton" text="Select"/>
 			<Spacer />
 			<Button name="closeButton" text="Close"/>
 		</VBox>
 	</HBox>
-</Window>
+</Panel>
--- a/clients/editor/gui/help.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/gui/help.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,4 +1,6 @@
-<Window title="Help">
-	<TextBox name="helpText"/>
+<Panel title="Help">
+	<ScrollArea min_size="300,400">
+		<TextBox name="helpText"/>
+	</ScrollArea>
 	<Button name="closeButton" text="Close"/>
-</Window>
+</Panel>
Binary file clients/editor/gui/icons/add_instance.png has changed
Binary file clients/editor/gui/icons/add_layer.png has changed
Binary file clients/editor/gui/icons/cycle_styles.png has changed
Binary file clients/editor/gui/icons/delete_layer.png has changed
Binary file clients/editor/gui/icons/erase_instance.png has changed
Binary file clients/editor/gui/icons/help.png has changed
Binary file clients/editor/gui/icons/is_visible.png has changed
Binary file clients/editor/gui/icons/load_map.png has changed
Binary file clients/editor/gui/icons/map_wizard.png has changed
Binary file clients/editor/gui/icons/move_instance.png has changed
Binary file clients/editor/gui/icons/new_map.png has changed
Binary file clients/editor/gui/icons/next_branch.png has changed
Binary file clients/editor/gui/icons/objectpicker.png has changed
Binary file clients/editor/gui/icons/previous_branch.png has changed
Binary file clients/editor/gui/icons/quit.png has changed
Binary file clients/editor/gui/icons/redo.png has changed
Binary file clients/editor/gui/icons/rotate_clockwise.png has changed
Binary file clients/editor/gui/icons/rotate_countercw.png has changed
Binary file clients/editor/gui/icons/save_allmaps.png has changed
Binary file clients/editor/gui/icons/save_map.png has changed
Binary file clients/editor/gui/icons/save_mapas.png has changed
Binary file clients/editor/gui/icons/select_instance.png has changed
Binary file clients/editor/gui/icons/take_screenshot.png has changed
Binary file clients/editor/gui/icons/undo.png has changed
Binary file clients/editor/gui/icons/zoom_default.png has changed
Binary file clients/editor/gui/icons/zoom_in.png has changed
Binary file clients/editor/gui/icons/zoom_out.png has changed
--- a/clients/editor/gui/layertool.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/gui/layertool.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,5 +1,5 @@
-<Window title="Layertool" position="10,200">
+<Panel title="Layertool" position="10,200" min_size="100, 25">
 	<VBox name="layers_wrapper">
 	
 	</VBox>
-</Window>
+</Panel>
--- a/clients/editor/gui/mapeditor.xml	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-<Window title="Map Editor">
-	<HBox>
-		<VBox name="Properties">
-		</VBox>
-		<VBox>
-			<Button name="layerButton" text="Edit Layer"/>
-			<Button name="objButton" text="View Objects"/>
-			<Spacer /> 		
-			<Button name="closeButton" text="Close"/>
-		</VBox>
-	</HBox>
-</Window>
--- a/clients/editor/gui/objectedit.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/gui/objectedit.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,5 +1,6 @@
-<Window title="Object editor" position="10,700"> <!-- size="200,250" min_size="200,250" max_size="200,250" > -->
+<Panel title="Object editor" position="10,700"> <!-- 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"/>
 
@@ -15,45 +16,24 @@
 		<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"/>
-			<Slider size="100,20" name="x_offset_slider" orientation="0" scale_start="0.0" scale_end="4.0" />
-		</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"/>
-			<Slider size="100,20" name="y_offset_slider" orientation="0" scale_start="0.0" scale_end="4.0" />
-		</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"/>
+		<TextBox text="0" name="instance_rotation" min_size="30,20" hexpand="1"/>
 	</HBox>
 
-
-
 	<VBox name="animation_panel_wrapper" max_size="150,50">
 		<Spacer />
 		<VBox name="animation_panel">
@@ -70,8 +50,6 @@
 
 	<HBox>
 		<Button name="use_data" text="Use"/>
-	</HBox>
-	<Spacer />
-	<Button name="change_data" text="Save object"/>
+	</HBox>	
 
-</Window>
+</Panel>
--- a/clients/editor/gui/objectselector.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/gui/objectselector.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,16 +1,16 @@
-<Window title="Object selector">
-	<VBox>
+<Panel title="Object selector" min_size="200,300">
+	<VBox hexpand="1" vexpand="1">
 		<!-- Search field -->
 		<HBox>
-			<TextField name="searchField" min_size="200,20" />
-			<Button name="searchButton" text="Search" /> 
+			<TextField name="searchField" />
+			<Button name="searchButton" text="Search" hexpand="0" /> 
 		</HBox>
 
     <!-- Namespaces -->
 		<DropDown name="namespaceDropdown" />
 
     <!-- Main area (object list) -->
-		<ScrollArea name="mainScrollArea" size="230,350" />
+		<ScrollArea name="mainScrollArea" min_size="150,50"/>
 
     <!-- Action buttons -->
 		<HBox>
@@ -20,8 +20,8 @@
 		</HBox>
 
     <!-- Preview area -->
-		<ScrollArea name="previewScrollArea" size="230,1">
+		<ScrollArea name="previewScrollArea" max_size="200,1" vexpand="1">
 			<Icon name="previewIcon" />
 		</ScrollArea>
 	</VBox>
-</Window>
+</Panel>
--- a/clients/editor/gui/selection.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/gui/selection.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,6 +1,6 @@
-<Window title="Select an option">
+<Panel title="Select an option">
 	<VBox>
-		<ScrollArea size="150,300">
+		<ScrollArea min_size="150,300">
 			<ListBox name="optionDrop"/>	
 		</ScrollArea>
 		<HBox>
@@ -8,4 +8,4 @@
 			<Button name="cancelButton" text="Cancel"/>
 		</HBox>
 	</VBox>
-</Window>
+</Panel>
Binary file clients/editor/icons/add_instance.png has changed
Binary file clients/editor/icons/add_layer.png has changed
Binary file clients/editor/icons/delete_layer.png has changed
Binary file clients/editor/icons/eraser.png has changed
Binary file clients/editor/icons/hand.png has changed
Binary file clients/editor/icons/help.png has changed
Binary file clients/editor/icons/is_visible.png has changed
Binary file clients/editor/icons/load_map.png has changed
Binary file clients/editor/icons/map_wizard.png has changed
Binary file clients/editor/icons/new_map.png has changed
Binary file clients/editor/icons/quit.png has changed
Binary file clients/editor/icons/save_map.png has changed
Binary file clients/editor/icons/select_layer.png has changed
Binary file clients/editor/icons/take_screenshot.png has changed
Binary file clients/editor/icons/zoom_default.png has changed
Binary file clients/editor/icons/zoom_in.png has changed
Binary file clients/editor/icons/zoom_out.png has changed
--- a/clients/editor/input.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-import pychan
-import pychan.widgets as widgets
-
-class Input():
-	"""
-	Input supplies a text box for entering data. The result is passed to onEntry.
-	onEntry - the function to call when a input is complete. Accepts one argument: a string of text.
-	"""
-	def __init__(self, prompt, onEntry):
-		self._callback = onEntry
-
-		self._widget = pychan.loadXML('gui/input.xml')
-
-		self._widget.mapEvents({
-			'okButton'     : self._complete,
-			'cancelButton' : self._widget.hide
-		})
-
-		self._widget.distributeInitialData({
-			'prompt' : prompt
-		})
-		self._widget.show()
-
-	def _complete(self):
-		self._callback(self._widget.collectData('inputBox'))
-		self._widget.hide()
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/lang/infotext.txt	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,80 @@
+This is the standalone FIFE editor, HEAD release
+
+User interface:
+---------------
+When you open the editor, you will see a menubar
+at the top of the screen. You can reach all the
+editor functions from this menu.
+
+Right below it is a toolbar which provides fast
+access to the most frequently used actions.
+
+In the middle is a big black area. This is the map
+view. When you create a new map, or open an existing
+map, it will be displayed here.
+
+At the bottom of the screen is the statusbar which
+displays information. If you hover the mouse over a
+button, a help text will be displayed here.
+
+Docking/Undocking:
+------------------
+Panels and toolbars can be either floating, or docked.
+
+To dock a toolbar or panel, move it one of the
+sides and a red line should appear. This indicates
+that it will be docked at that location. Panels can
+be docked before, after or inside existing panels.
+
+To undock a toolbar, simply right click it. To
+undock a panel, right click its button,
+
+Keybindings:
+--------------
+Map editing:
+- S: Enter select mode
+- I: Enter insert mode
+- R: Enter removal mode
+- M: Enter move mode
+- INS: Fills selection with current object
+- DEL: Removes selected instances
+
+Camera navigation:
+- Arrowkeys: Scroll map
+- Middle mouse button + Drag mouse: Scroll map
+- Ctrl+Mouse wheel: Zoom
+
+Selection:
+- Click starts a new selection
+- CTRL+Click adds to selection
+- Shift+Click subtracts from selection
+- Right click deselects
+
+Moving instances:
+- Shift+Drag: Exact instance move
+
+Undo:
+- Ctrl+Z: Undo
+- Ctrl+Shift+Z: Redo
+- Ctrl+Alt+Z: Next undobranch
+- Ctrl+Alt+Shift+Z: Previous undobranch
+
+File management:
+- Ctrl+N: New level
+- Ctrl+O: Open level
+- Ctrl+S: Save current level
+- Ctrl+Shift+S: Save all levels
+
+Misc:
+- F10 = Toggle console on / off
+- ESC = Quit editor
+- INS = Makes a new instance on the map with
+        current object selection at current
+        mouse selection (toggle).
+- DEL = Removes instances at current mouse
+        selection (toggle).
+- T   = shows / hides grid
+- B   = shows / hides blocking info
+
+
+http://www.fifengine.de
--- a/clients/editor/listener.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,108 +0,0 @@
-# Defines the event listener that is used by the editor. See editor.py.
-
-import fife
-
-class EditorListener(fife.IKeyListener, fife.ICommandListener, fife.IMouseListener, 
-	              fife.ConsoleExecuter):
-	def __init__(self, app):
-		self.app = app
-		engine = app.engine
-		eventmanager = engine.getEventManager()
-		#eventmanager.setNonConsumableKeys([
-			#fife.Key.ESCAPE,
-			#fife.Key.TAB,])
-		
-		fife.IKeyListener.__init__(self)
-		eventmanager.addKeyListener(self)
-		fife.ICommandListener.__init__(self)
-		eventmanager.addCommandListener(self)
-		fife.IMouseListener.__init__(self)
-		eventmanager.addMouseListener(self)
-		fife.ConsoleExecuter.__init__(self)
-		engine.getGuiManager().getConsole().setConsoleExecuter(self)
-	
-		self.engine = engine		
-		self.showTileOutline = True
-		self.showEditor = False
-		self.showCoordinates = False
-		self.showSecondCamera = False
-		self.reloadRequested = False
-
-	def mousePressed(self, evt):
-		if(evt.getButton() == fife.MouseEvent.RIGHT ):
-			print 'right click'
-	def mouseReleased(self, evt):
-		pass	
-	def mouseEntered(self, evt):
-		pass
-	def mouseExited(self, evt):
-		pass
-	def mouseClicked(self, evt):
-		pass
-	def mouseWheelMovedUp(self, evt):
-		pass	
-	def mouseWheelMovedDown(self, evt):
-		pass
-	def mouseMoved(self, evt):
-		pass
-	def mouseDragged(self, evt):
-		pass
-
-	def keyPressed(self, evt):
-		keyval = evt.getKey().getValue()
-		keystr = evt.getKey().getAsString().lower()
-		if keyval == fife.Key.ESCAPE:
-			self.app.quit()
-		elif keyval == fife.Key.F10:
-			self.engine.getGuiManager().getConsole().toggleShowHide()
-		elif keystr == 'p':
-			self.engine.getRenderBackend().captureScreen('screenshot.png')
-		elif keystr == 't':
-			self.showTileOutline = not self.showTileOutline
-		elif keystr == 'c':
-			self.showCoordinates = not self.showCoordinates
-		elif keystr == 's':
-			self.showSecondCamera = not self.showSecondCamera
-		elif keystr == 'r':
-			self.reloadRequested = True
-		elif keystr == 'e':
-			self.showEditor = True
-	
-	def keyReleased(self, evt):
-		pass
-
-	def onCommand(self, command):
-		if command.getCommandType() == fife.CMD_QUIT_GAME:
-			self.app.quit()
-
-	def onToolsClick(self):
-		print "No tools set up yet"
-	
-	def onConsoleCommand(self, command):
-		result = "no result"
-		if command.lower() in ('quit', 'exit'):
-			self.app.quit()
-
-		if command.lower() in ( 'help', 'help()' ):
-			self.engine.getGuiManager().getConsole().println( open( 'misc/infotext.txt', 'r' ).read() )
-			return "-- End of help --"
-		
-		try:
-			result = str(eval(command))
-		except:
-			pass
-		return result
-	
-	def onWidgetAction(self, evt):
-		evtid = evt.getId()
-		if evtid == 'WidgetEvtQuit':
-			cmd = fife.Command()
-			cmd.setSource(evt.getSource())
-			cmd.setCommandType(fife.CMD_QUIT_GAME)
-			self.engine.getEventManager().dispatchCommand( cmd );
-		if evtid == 'WidgetEvtAbout':
-			if self.showInfo:
-				self.showInfo = False
-			else:
-				self.showInfo = True
-
--- a/clients/editor/misc/infotext.txt	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,29 +0,0 @@
-This is the standalone FIFE editor, release 2008.1
-
-Keybindings:
---------------
-- P = Make screenshot
-- LEFT = Move camera left
-- RIGHT = Move camera right
-- UP = Move camera up
-- DOWN = Move camera down
-- F10 = Toggle console on / off
-- ESC = Quit techdemo
-
-- INS = Makes a new instance on the map with
-        current object selection at current
-        mouse selection (toggle).
-- DEL = Removes instances at current mouse
-        selection (toggle).
-- R   = Rotates selected instace (if object
-        contains rotated images)
-- M   = Moves selected instace (drag it around)
-- T   = shows / hides grid
-- B   = shows / hides blocking info
-- U   = Undo an instance placement or removal
-- CTRL = Enters pan/zoom mode. Pan with LMB drag,
-         zoom with mouse wheel while CTRL is
-         pressed
-
-
-http://www.fifengine.de
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/plugins/HistoryManager.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,259 @@
+# coding: utf-8
+
+import pychan
+from pychan import widgets, tools, attrs, internal
+import scripts
+import scripts.plugin as plugin
+from scripts.events import *
+from scripts.gui.action import Action, ActionGroup
+import fife
+from fife import Color
+from scripts import undomanager
+import scripts.gui
+from scripts.gui.panel import Panel
+import pdb
+
+class HistoryManager(plugin.Plugin):
+	def __init__(self):
+		self.editor = None
+		self.engine = None
+		
+		self._enabled = False
+		self.undomanager = None
+
+	def enable(self):
+		if self._enabled is True:
+			return
+			
+		self.editor = scripts.editor.getEditor()
+		self.engine = self.editor.getEngine()
+			
+		self._undoGroup = ActionGroup(name=u"UndoGroup")
+		self._showAction = Action(u"History manager", checkable=True)
+		self._undoAction = Action(u"Undo", "gui/icons/undo.png")
+		self._redoAction = Action(u"Redo", "gui/icons/redo.png")
+		self._nextAction = Action(u"Next branch", "gui/icons/next_branch.png")
+		self._prevAction = Action(u"Previous branch", "gui/icons/previous_branch.png")
+		scripts.gui.action.activated.connect(self.toggle, sender=self._showAction)
+		scripts.gui.action.activated.connect(self._undo, sender=self._undoAction)
+		scripts.gui.action.activated.connect(self._redo, sender=self._redoAction)
+		scripts.gui.action.activated.connect(self._next, sender=self._nextAction)
+		scripts.gui.action.activated.connect(self._prev, sender=self._prevAction)
+		
+		self._undoGroup.addAction(self._undoAction)
+		self._undoGroup.addAction(self._redoAction)
+		self._undoGroup.addAction(self._nextAction)
+		self._undoGroup.addAction(self._prevAction)
+		
+		self.editor._toolsMenu.addAction(self._showAction)
+		self.editor._editMenu.insertAction(self._undoGroup, 0)
+		self.editor._editMenu.insertSeparator(position=1)
+		
+		events.postMapShown.connect(self.update)
+		undomanager.changed.connect(self.update)
+		
+		self.buildGui()
+
+	def disable(self):
+		if self._enabled is False:
+			return
+			
+		self.gui.hide()
+		self.removeAllChildren()
+		
+		events.postMapShown.disconnect(self.update)
+		undomanager.changed.disconnect(self.update)
+		
+		scripts.gui.action.activated.connect(self.toggle, sender=self._showAction)
+		scripts.gui.action.activated.disconnect(self._undo, sender=self._undoAction)
+		scripts.gui.action.activated.disconnect(self._redo, sender=self._redoAction)
+		scripts.gui.action.activated.disconnect(self._next, sender=self._nextAction)
+		scripts.gui.action.activated.disconnect(self._prev, sender=self._prevAction)
+		
+		self.editor._toolsMenu.removeAction(self._showAction)
+		self.editor._toolsMenu.removeAction(self._undoGroup)
+
+
+	def isEnabled(self):
+		return self._enabled;
+
+	def getName(self):
+		return "History manager"
+		
+
+	def buildGui(self):
+		self.gui = Panel(title=u"History")
+		self.scrollarea = widgets.ScrollArea(min_size=(200,300))
+		self.list = widgets.ListBox()
+		self.list.capture(self._itemSelected)
+		
+		self.gui.addChild(self.scrollarea)
+		self.scrollarea.addChild(self.list)
+		
+		self.gui.position_technique = "right:center"
+		
+	def _itemSelected(self):
+		mapview = self.editor.getActiveMapView()
+		if mapview is None:
+			return
+			
+		undomanager = mapview.getController().getUndoManager()
+		
+		stackitem = self.list.selected_item.item
+		if stackitem == undomanager.current_item:
+			#print "Selected current item"
+			return
+		
+		searchlist = []
+		searchlist2 = []
+		parent = stackitem
+		branch = parent.next
+		while parent is not None:
+			if parent is undomanager.first_item or len(parent._branches) > 1:
+				searchlist.append( (parent, branch) )
+			branch = parent
+			parent = parent.parent
+		
+		current_item = undomanager.current_item
+		
+		parent = current_item
+		branch = parent.next
+		while parent is not None:
+			if parent is undomanager.first_item or len(parent._branches) > 1:
+				searchlist2.append( (parent, branch) )
+			branch = parent
+			parent = parent.parent
+			
+		searchlist.reverse()
+		searchlist2.reverse()
+		
+		# Remove duplicate entries, except the last duplicate, so we don't undo
+		# more items than necessary
+		sl = len(searchlist);
+		if len(searchlist2) < sl:
+			sl = len(searchlist2)
+		for s in range(sl):
+			if searchlist[s][0] != searchlist[s][0]:
+				searchlist = searchlist[s-1:]
+		
+		s_item = searchlist[0][0]
+
+		# Undo until we reach the first shared parent
+		i = 0
+		item = current_item
+		while item is not None:
+			if item == s_item:
+				undomanager.undo(i)
+				current_item = item
+				break
+			i += 1
+			item = item.previous
+		else:
+			print "Nada (undo)"
+			return
+				
+		# Switch branches
+		for s_item in searchlist:
+			 if s_item[0].setBranch(s_item[1]) is False:
+				print "Warning: HistoryManager: Switching branch failed for: ", s_item
+			 
+		# Redo to stackitem
+		item = current_item
+		i = 0
+		while item is not None:
+			if item == stackitem:
+				undomanager.redo(i)
+				break
+			i += 1
+			item = item.next
+		else:
+			print "Nada (redo)"
+		
+		# Select the current item, important to see if the undo/redo didn't work as expected
+		self.update()
+		
+		
+	def recursiveUpdate(self, item, indention, parent=None, branchstr="-"):
+		items = []
+		
+		branchnr = 0
+		
+		class _ListItem:
+			def __init__(self, str, item, parent):
+				self.str = str
+				self.item = item
+				self.parent = parent
+				
+			def __str__(self):
+				return self.str
+		
+		while item is not None:
+			listitem = _ListItem(u" "*indention + branchstr + " " + item.object.name, item, parent)
+			items.append(listitem)
+			branchnr = -1
+			
+			for branch in item._branches:
+				branchnr += 1
+				if branchnr == 0:
+					continue
+					
+				items.extend(self.recursiveUpdate(branch, indention+2, listitem, str(branchnr)))
+			
+			if len(item._branches) > 0:
+				item = item._branches[0]
+			else:
+				break
+			#item = item.next
+
+		return items
+		
+	def update(self):
+		mapview = self.editor.getActiveMapView()
+		if mapview is None:
+			self.list.items = []
+			return
+			
+		self.undomanager = undomanager = mapview.getController().getUndoManager()
+		items = []
+		items = self.recursiveUpdate(undomanager.first_item, 0)
+
+		self.list.items = items
+		i = 0
+		for it in items:
+			if it.item == undomanager.current_item:
+				self.list.selected = i
+				break
+			i += 1
+		self.scrollarea.adaptLayout()
+
+	def show(self):
+		self.update()
+		self.gui.show()
+		self._showAction.setChecked(True)
+
+	def hide(self):
+		self.gui.setDocked(False)
+		self.gui.hide()
+		self._showAction.setChecked(False)
+		
+	def _undo(self):
+		if self.undomanager:
+			self.undomanager.undo()
+	
+	def _redo(self):
+		if self.undomanager:
+			self.undomanager.redo()
+		
+	def _next(self):
+		if self.undomanager:
+			self.undomanager.nextBranch()
+		
+	def _prev(self):
+		if self.undomanager:
+			self.undomanager.previousBranch()
+		
+	def toggle(self):
+		if self.gui.isVisible() or self.gui.isDocked():
+			self.hide()
+		else:
+			self.show()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/plugins/LayerTool.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,265 @@
+#!/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
+# ###################################################
+
+""" An advanced layer tool for FIFedit """
+
+import fife
+import scripts.plugin as plugin
+import scripts.editor
+from scripts.events import *
+from scripts.gui.action import Action
+import pychan
+import pychan.widgets as widgets
+from pychan.tools import callbackWithArguments as cbwa
+
+# default should be pychan default, highlight can be choosen (format: r,g,b)
+_DEFAULT_BACKGROUND_COLOR = pychan.internal.DEFAULT_STYLE['default']['base_color']
+_HIGHLIGHT_BACKGROUND_COLOR = pychan.internal.DEFAULT_STYLE['default']['selection_color']
+
+# the dynamicly created widgets have the name scheme prefix + layerid
+_LABEL_NAME_PREFIX = "select_"
+
+class LayerTool(plugin.Plugin):
+	""" The B{LayerTool} is an advanced method to view
+	and change layer informations.
+	
+	While the original FIFedit tool only allows to select
+	layers, this one will provide the following functionality:
+	
+		- toggle layer visibility
+		- select layer
+		- list layers
+		
+	"""
+	def __init__(self):	
+		self._editor = None
+		self._enabled = False
+		self._mapview = None
+		
+		self._showAction = None
+		
+		self.subwrappers = []
+			
+	#--- Plugin function ---#
+	def enable(self):
+		if self._enabled is True:
+			return
+			
+		# Fifedit plugin data
+		self._editor = scripts.editor.getEditor()
+		self._showAction = Action(u"LayerTool", checkable=True)
+		scripts.gui.action.activated.connect(self.toggle, sender=self._showAction)
+		self._editor._toolsMenu.addAction(self._showAction)
+
+		self.__create_gui()
+		
+		self.toggle()
+		
+		events.postMapShown.connect(self.update)
+
+	def disable(self):
+		if self._enabled is False:
+			return
+		self.container.setDocked(False)
+		self.container.hide()
+		self.removeAllChildren()
+		
+		events.postMapShown.disconnect(self.update)
+		
+		self._editor._toolsMenu.removeAction(self._showAction)
+
+	def isEnabled(self):
+		return self._enabled;
+
+	def getName(self):
+		return u"Layertool"
+	
+	#--- End plugin functions ---#
+	
+	def __create_gui(self):
+		""" create the basic gui container """
+		self.container =  pychan.loadXML('gui/layertool.xml')
+		self.wrapper = self.container.findChild(name="layers_wrapper")
+		self.update(None)
+
+	def _adjust_position(self):
+		"""	adjusts the position of the container - we don't want to
+		let the window appear at the center of the screen.
+		(new default position: left, beneath the tools window)
+		"""
+		self.container.position = (50, 200)
+		
+	def clear(self):
+		""" remove all subwrappers """
+		if self.subwrappers is []: return
+		
+		for subwrapper in self.subwrappers:
+			self.wrapper.removeChild(subwrapper)
+			
+		self.subwrappers = []
+		
+	def update(self, mapview):
+		""" Dump new layer informations into the wrapper 
+		
+		We group one ToggleButton and one Label into a HBox, the main wrapper
+		itself is a VBox and we also capture both the Button and the Label to listen
+		for mouse actions
+		"""
+		layers = []
+		self._mapview = mapview
+		if self._mapview is not None:
+			layers = self._mapview.getMap().getLayers()
+		
+		self.clear()
+		
+		if len(layers) <= 0:
+			layerid = "No layers"
+			subwrapper = pychan.widgets.HBox()
+
+			layer_name_widget = pychan.widgets.Label()
+			layer_name_widget.text = unicode(layerid)
+			layer_name_widget.name = _LABEL_NAME_PREFIX + layerid
+			subwrapper.addChild(layer_name_widget)
+			
+			self.wrapper.addChild(subwrapper)
+			self.subwrappers.append(subwrapper)
+		
+		active_layer = self.getActiveLayer()
+		if active_layer:
+			active_layer = active_layer.getId()
+		for layer in reversed(layers):
+			layerid = layer.getId()
+			subwrapper = pychan.widgets.HBox()
+
+			visibility_widget = pychan.widgets.ToggleButton(hexpand=0, up_image="gui/icons/is_visible.png", down_image="gui/icons/is_visible.png", hover_image="gui/icons/is_visible.png")
+			visibility_widget.name = "toggle_" + layerid
+			if layer.areInstancesVisible():
+				visibility_widget.toggled = True
+			visibility_widget.capture(self.toggle_layer_visibility,"mousePressed")
+			
+			layer_name_widget = pychan.widgets.Label()
+			layer_name_widget.text = unicode(layerid)
+			layer_name_widget.name = _LABEL_NAME_PREFIX + layerid
+			layer_name_widget.capture(self.select_active_layer,"mousePressed")
+			
+			if active_layer == layerid:
+				layer_name_widget.background_color	= _HIGHLIGHT_BACKGROUND_COLOR
+				layer_name_widget.foreground_color	= _HIGHLIGHT_BACKGROUND_COLOR
+				layer_name_widget.base_color		= _HIGHLIGHT_BACKGROUND_COLOR
+			
+			subwrapper.addChild(visibility_widget)
+			subwrapper.addChild(layer_name_widget)
+			
+			self.wrapper.addChild(subwrapper)
+			self.subwrappers.append(subwrapper)
+		
+		self.container.adaptLayout()		
+			
+	def toggle_layer_visibility(self, event, widget):
+		""" Callback for ToggleButtons 
+		
+		Toggle the chosen layer visible / invisible
+		
+		NOTE:
+			- if a layer is set to invisible, it also shouldn't be the active layer anymore
+		
+		@type	event:	object
+		@param	event:	pychan mouse event
+		@type	widget:	object
+		@param	widget:	the pychan widget where the event occurs, transports the layer id in it's name
+		"""
+
+		layerid = widget.name[len(_LABEL_NAME_PREFIX):]
+		
+		layer = self._mapview.getMap().getLayer(layerid)
+		active_layer = self.getActiveLayer()
+		if active_layer:
+			active_layer = active_layer.getId()
+		
+		if layer.areInstancesVisible():
+			layer.setInstancesVisible(False)
+		else:
+			layer.setInstancesVisible(True)
+			
+		if active_layer == layerid:
+			self.select_no_layer()
+			
+			
+	def select_no_layer(self):
+		""" the exception approach - as soon as the user hides a layer, the mapedit module should stop to use this
+		one, too.
+		
+		A bunch of exceptions is the result (each click on the map will result in a exception as no layer is set etc...)	
+		"""
+		previous_active_layer = self.getActiveLayer()
+		if previous_active_layer is not None:
+			previous_layer_id = previous_active_layer.getId()
+			previous_active_widget = self.container.findChild(name=_LABEL_NAME_PREFIX + previous_layer_id)
+			previous_active_widget.background_color = _DEFAULT_BACKGROUND_COLOR
+			previous_active_widget.foreground_color = _DEFAULT_BACKGROUND_COLOR
+			previous_active_widget.base_color = _DEFAULT_BACKGROUND_COLOR
+			previous_active_widget.text = unicode(previous_layer_id)
+			
+		self._mapview.getController().selectLayer(None)
+		
+	def getActiveLayer(self):
+		""" Returns the active layer """
+		if self._mapview:
+			return self._mapview.getController()._layer
+		
+	def select_active_layer(self, event, widget):
+		""" callback for Labels 
+		
+		We hand the layerid over to the mapeditor module to select a 
+		new active layer
+		
+		Additionally, we mark the active layer widget (changing base color) and reseting the previous one
+
+		@type	event:	object
+		@param	event:	pychan mouse event
+		@type	widget:	object
+		@param	widget:	the pychan widget where the event occurs, transports the layer id in it's name
+		"""
+
+		self.select_no_layer()
+		
+		layerid = widget.name[7:]	
+		
+		widget.background_color = _HIGHLIGHT_BACKGROUND_COLOR
+		widget.foreground_color = _HIGHLIGHT_BACKGROUND_COLOR
+		widget.base_color = _HIGHLIGHT_BACKGROUND_COLOR
+		self.container.adaptLayout()
+		
+		self._mapview.getController().selectLayer(layerid)
+
+	def toggle(self):
+		if self.container.isVisible() or self.container.isDocked():
+			self.container.setDocked(False)
+			self.container.hide()
+
+			self._showAction.setChecked(False)
+		else:
+			self.container.show()
+			self._showAction.setChecked(True)
+			self._adjust_position()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/plugins/ObjectEdit.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,359 @@
+#!/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 edit object and instance attributes """
+
+import fife
+import pychan
+import pychan.widgets as widgets
+from pychan.tools import callbackWithArguments as cbwa
+
+import scripts
+import scripts.plugin as plugin
+from scripts.events import *
+from scripts.gui.action import Action
+
+import math
+
+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
+	(namespaces and object id editing is excluded)
+	
+	current features:
+		- click instance and get all known data
+		- edit rotation, instance id
+		- 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
+	"""
+	def __init__(self):
+		self.active = False
+		self._camera = None
+		self._layer = None
+		
+		self._enabled = False
+		
+		self.imagepool = None
+		self.animationpool = None
+		
+		self.guidata = {}
+		self.objectdata = {}
+
+	def _reset(self):
+		"""
+			resets all dynamic vars, but leaves out static ones (e.g. camera, layer)
+
+		"""
+		self._instances = None
+		self._image = None
+		self._animation = False
+		self._rotation = None
+		self._avail_rotations = []
+		self._namespace = None	
+		self._blocking = 0
+		self._static = 0
+		self._object_id = None	
+		self._instance_id = None
+		self._fixed_rotation = None
+		
+		if self._camera is not None:
+			self.renderer.removeAllOutlines()		
+		
+
+	def enable(self):
+		if self._enabled is True:
+			return
+			
+		self._editor = scripts.editor.getEditor()
+		self.engine = self._editor.getEngine()
+		
+		self.imagepool = self.engine.getImagePool()
+		self.animationpool = self.engine.getAnimationPool()
+		
+		self._showAction = Action(u"Object editor", checkable=True)
+		scripts.gui.action.activated.connect(self.toggle_gui, sender=self._showAction)
+		
+		self._editor._toolsMenu.addAction(self._showAction)
+		
+		events.onInstancesSelected.connect(self.input)
+		
+		self._reset()		
+		self.create_gui()
+
+	def disable(self):
+		if self._enabled is False:
+			return
+			
+		self._reset()
+		self.container.hide()
+		self.removeAllChildren()
+		
+		events.onInstancesSelected.disconnect(self.input)
+		
+		self._editor._toolsMenu.removeAction(self._showAction)
+
+	def isEnabled(self):
+		return self._enabled;
+
+	def getName(self):
+		return "Object editor"
+
+	def create_gui(self):
+		"""
+			- creates the gui skeleton by loading the xml file
+			- finds some important childs and saves their widget in the object
+		"""
+		self.container = pychan.loadXML('gui/objectedit.xml')
+		self.container.mapEvents({
+			'use_data'		: self.use_user_data,
+			
+		})
+
+		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_instance_id_textfield = self.container.findChild(name="instance_id")
+
+	def _get_gui_size(self):
+		"""
+			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)
+		
+	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
+
+		self.container.distributeInitialData({
+			'select_rotations' 	: self._avail_rotations,
+			'instance_id'		: unicode( self._instances[0].getId() ),
+			'object_id'			: unicode( self._object_id ),
+			'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) )
+			self._gui_rotation_dropdown._setSelected(index)
+		except:
+#			pass
+			print "Angle (", self._fixed_rotation, ") not supported by this instance"
+		self.container.adaptLayout()
+		
+	def toggle_gui(self):
+		"""
+			show / hide the gui
+		"""
+		if self.active is True:
+			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)
+	
+	def highlight_selected_instance(self):
+		"""
+			just highlights selected instance
+		"""
+		self.renderer.removeAllOutlines() 
+		self.renderer.addOutlined(self._instances[0], 205, 205, 205, 1)
+
+	def use_user_data(self):
+		"""
+			- takes the users values and applies them directly to the current ._instance
+			- writes current data record
+			- writes previous data record
+			- updates gui
+		"""		
+		instance_id = str(self._gui_instance_id_textfield._getText())
+		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:
+				self._instances[0].setId(instance_id)
+				print "Set new instance id: ", instance_id		
+			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()
+		
+		# strange, but this helps to rotate the image correctly to the value the user selected
+		angle = int( self._avail_rotations[index] )
+		angle = int(angle - abs( self._camera.getTilt() ) )
+		if angle == 360:
+			angle = 0
+		
+		self._instances[0].setRotation(angle)
+		self.get_instance_data(None, None, angle)
+
+		self.update_gui()
+		
+	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 = []
+			
+		if instance is None:
+			instance = self._instances[0]
+			
+		object = instance.getObject()
+		self._namespace = object.getNamespace()
+		self._object_id = object.getId()
+
+		self._instance_id = instance.getId()
+	
+		if self._instance_id == '':
+			self._instance_id = 'None'
+
+		if angle == -1:
+			angle = int(instance.getRotation())
+		else:
+			angle = int(angle)	
+			
+		self._rotation = angle
+		
+		if object.isBlocking():
+			self._blocking = 1
+			
+		if object.isStatic():
+			self._static = 1
+		
+		try:
+			visual = object.get2dGfxVisual()
+		except:
+			print 'Fetching visual of object - failed. :/'
+			raise			
+
+#		print "Camera Tilt: ", self._camera.getTilt()
+#		print "Camera Rotation: ", self._camera.getRotation()
+
+		self._fixed_rotation = int(instance.getRotation() + abs( self._camera.getTilt() ) )		
+		self._fixed_rotation = visual.getClosestMatchingAngle(self._fixed_rotation)	
+
+		index = visual.getStaticImageIndexByAngle(self._fixed_rotation)
+
+		if index == -1:
+			# object is an animation
+			self._animation = True
+			# 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:
+			# 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) )
+				
+
+# FIXME: see l. 40
+		self._editor.getActiveMapView().getController()._objectedit_rotations = self._avail_rotations
+# end FIXME
+		
+	def input(self, instances):
+		"""
+			if called _and_ the objectedit is active,
+			gets instance data and show gui
+			
+			(see run.py, pump() )
+		"""
+		if instances != self._instances:
+			if self.active is True:
+				self._reset()
+				self._instances = instances
+				
+				if self._camera is None:
+					self._camera = self._editor.getActiveMapView().getCamera()
+					self.renderer = fife.InstanceRenderer.getInstance(self._camera)				
+					
+				self._layer = self._editor.getActiveMapView().getController()._layer
+			
+				if self._instances != ():
+					self.highlight_selected_instance()
+					self.get_instance_data()
+					self.update_gui()
+					self.container.adaptLayout()
+					self.container.show()
+					self._get_gui_size()
+					self.container._setPosition(self.position)
+				else:
+					self._reset()
+					self.container.hide()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/plugins/ObjectSelector.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,400 @@
+# coding: utf-8
+
+import pychan
+from pychan import widgets, tools, attrs, internal
+from pychan.tools import callbackWithArguments
+import scripts
+import scripts.plugin as plugin
+from scripts.events import *
+from scripts.gui.action import Action
+import fife
+from fife import Color
+
+# TODO:
+# - Better event handling
+
+_DEFAULT_BASE_COLOR = internal.DEFAULT_STYLE['default']['base_color']
+_DEFAULT_SELECTION_COLOR = internal.DEFAULT_STYLE['default']['selection_color']
+_DEFAULT_COLOR_STEP = Color(10, 10, 10)
+
+class ObjectIcon(widgets.VBox):
+	""" The ObjectIcon is used to represent the object in the object selector.
+	"""	
+	ATTRIBUTES = widgets.VBox.ATTRIBUTES + [ attrs.Attr("text"), attrs.Attr("image"), attrs.BoolAttr("selected") ]
+	
+	def __init__(self,callback,**kwargs):
+		super(ObjectIcon,self).__init__(**kwargs)
+
+		self.callback = callback	
+
+		self.capture(self._mouseEntered, "mouseEntered")
+		self.capture(self._mouseExited, "mouseExited")
+		self.capture(self._mouseClicked, "mouseClicked")
+
+		vbox = widgets.VBox(padding=3)
+
+		# Icon
+		self.icon = widgets.Icon(**kwargs)
+		self.addChild(self.icon)
+
+		# Label
+		hbox = widgets.HBox(padding=1)
+		self.addChild(hbox)
+		self.label = widgets.Label(**kwargs)
+		hbox.addChild(self.label)
+
+	def _setText(self, text):
+		self.label.text = text
+		
+	def _getText(self):
+		return self.label.text
+	text = property(_getText, _setText)
+
+	def _setImage(self, image):
+		self.icon.image = image
+
+	def _getImage(self):
+		return self.icon.image
+	image = property(_getImage, _setImage)
+
+	def _setSelected(self, enabled):
+		if isinstance(self.parent, ObjectIconList):
+			if enabled == True:
+				self.parent.selected_item = self
+			else:
+				if self.selected:
+					self.parent.selected_item = None
+		
+		if self.selected:
+			self.base_color = _DEFAULT_SELECTION_COLOR
+		else:
+			self.base_color = _DEFAULT_BASE_COLOR
+
+	def _isSelected(self):
+		if isinstance(self.parent, ObjectIconList):
+			return self == self.parent.selected_item
+		return False
+	selected = property(_isSelected, _setSelected)
+
+	#--- Event handling ---#
+	def _mouseEntered(self, event):
+		self.base_color += _DEFAULT_COLOR_STEP
+
+	def _mouseExited(self, event):
+		self.base_color -= _DEFAULT_COLOR_STEP
+
+	def _mouseClicked(self, event):
+		self.selected = True
+		self.callback()
+
+class ObjectIconList(widgets.VBox):
+	ATTRIBUTES = widgets.VBox.ATTRIBUTES
+	
+	def __init__(self,**kwargs):
+		super(ObjectIconList, self).__init__(max_size=(5000,500000), **kwargs)
+		self.base_color = self.background_color
+
+		self.capture(self._keyPressed, "keyPressed")
+		#self.capture(self._keyPressed, "keyReleased")
+		self._selectedItem = None
+		self.is_focusable = True
+
+	def _keyPressed(self, event):
+		print "KeyEvent", event
+
+	def clear(self):
+		for c in reversed(self.children):
+			self.removeChild(c)
+
+	def _setSelectedItem(self, item):
+		if isinstance(item, ObjectIcon) or item is None:
+			if self._selectedItem is not None:
+				tmp = self._selectedItem
+				self._selectedItem = item
+				tmp.selected = False
+			else:
+				self._selectedItem = item	
+
+	def _getSelectedItem(self):
+		return self._selectedItem
+	selected_item = property(_getSelectedItem, _setSelectedItem)
+	
+class ObjectSelector(plugin.Plugin):
+	"""The ObjectSelector class offers a gui Widget that let's you select the object you
+	wish to use to in the editor.
+	@param engine: fife instance
+	@param map: fife.Map instance containing your map
+	@param selectNotify: callback function used to tell the editor you selected an object.
+	"""
+	def __init__(self):
+		self.editor = None
+		self.engine = None
+		self.mode = 'list' # Other mode is 'preview'
+		
+		self._enabled = False
+
+	def enable(self):
+		if self._enabled is True:
+			return
+			
+		self.editor = scripts.editor.getEditor()
+		self.engine = self.editor.getEngine()
+			
+		self._showAction = Action(u"Object selector", checkable=True)
+		scripts.gui.action.activated.connect(self.toggle, sender=self._showAction)
+		
+		self.editor._toolsMenu.addAction(self._showAction)
+		
+		events.postMapShown.connect(self.update_namespace)
+		events.onObjectSelected.connect(self.setPreview)
+		
+		self.buildGui()
+
+	def disable(self):
+		if self._enabled is False:
+			return
+			
+		self.gui.hide()
+		self.removeAllChildren()
+		
+		events.postMapShown.disconnect(self.update_namespace)
+		events.onObjectSelected.disconnect(self.setPreview)
+		
+		self.editor._toolsMenu.removeAction(self._showAction)
+
+	def isEnabled(self):
+		return self._enabled;
+
+	def getName(self):
+		return "Object selector"
+		
+
+	def buildGui(self):
+		self.gui = pychan.loadXML('gui/objectselector.xml')
+
+		# Add search field
+		self._searchfield = self.gui.findChild(name="searchField")
+		self._searchfield.capture(self._search)
+		self._searchfield.capture(self._search, "keyPressed")
+		self.gui.findChild(name="searchButton").capture(self._search)
+		
+		# Add the drop down with list of namespaces
+		self.namespaces = self.gui.findChild(name="namespaceDropdown")
+		self.namespaces.items = self.engine.getModel().getNamespaces()
+		self.namespaces.selected = 0
+
+		# TODO: Replace with SelectionEvent, once pychan supports it
+		self.namespaces.capture(self.update_namespace, "action")
+		self.namespaces.capture(self.update_namespace, "mouseWheelMovedUp")
+		self.namespaces.capture(self.update_namespace, "mouseWheelMovedDown")
+		self.namespaces.capture(self.update_namespace, "keyReleased")
+
+		# Object list
+		self.mainScrollArea = self.gui.findChild(name="mainScrollArea")
+		self.objects = None
+		if self.mode == 'list':
+			self.setTextList()
+		else: # Assuming self.mode is 'preview'
+			self.setImageList()
+
+		# Action buttons
+		self.gui.findChild(name="toggleModeButton").capture(self.toggleMode)
+		self.gui.findChild(name="closeButton").capture(self.hide)
+
+		# Preview area
+		self.gui.findChild(name="previewScrollArea").background_color = self.gui.base_color
+		self.preview = self.gui.findChild(name="previewIcon")
+		
+
+	def toggleMode(self):
+		if self.mode == 'list':
+			self.setImageList()
+			self.mode = 'preview'
+		elif self.mode == 'preview':
+			self.setTextList()
+			self.mode = 'list'
+		self.update()
+
+
+	def setImageList(self):
+		"""Sets the mainScrollArea to contain a Vbox that can be used to fill in
+		preview Images"""
+		if self.objects is not None:
+			self.mainScrollArea.removeChild(self.objects)
+		self.objects = ObjectIconList(name='list', size=(200,1000))
+		self.objects.base_color = self.mainScrollArea.background_color
+		self.mainScrollArea.addChild(self.objects)
+
+	def setTextList(self):
+		"""Sets the mainScrollArea to contain a List that can be used to fill in
+		Object names/paths"""
+		if self.objects is not None:
+			self.mainScrollArea.removeChild(self.objects)
+		self.objects = widgets.ListBox(name='list')
+		self.objects.capture(self.listEntrySelected)
+		self.mainScrollArea.addChild(self.objects)
+
+	def _search(self):
+		self.search(self._searchfield.text)
+
+	def search(self, str):
+		results = []	
+			
+		# Format search terms
+		terms = [term.lower() for term in str.split()]
+		
+		# Search
+		if len(terms) > 0:
+			namespaces = self.engine.getModel().getNamespaces()
+			for namesp in namespaces:
+				objects = self.engine.getModel().getObjects(namesp)
+				for obj in objects:
+					doAppend = True
+					for term in terms:
+						if obj.getId().lower().find(term) < 0:
+							doAppend = False
+							break
+					if doAppend:
+						results.append(obj)
+		else:
+			results = None
+		
+		if self.mode == 'list':
+			self.fillTextList(results)
+		elif self.mode == 'preview':
+			self.fillPreviewList(results)
+
+	def fillTextList(self, objects=None):
+		if objects is None:
+			if self.namespaces.selected_item is None:
+				return
+			objects = self.engine.getModel().getObjects(self.namespaces.selected_item)
+		
+		class _ListItem:
+			def __init__( self, name, namespace ):
+				self.name = name
+				self.namespace = namespace
+			def __str__( self ):
+				return self.name
+			
+		if self.namespaces.selected_item:
+			self.objects.items = [_ListItem(obj.getId(), obj.getNamespace()) for obj in objects]
+			if not self.objects.selected_item:
+				self.objects.selected = 0
+				self.listEntrySelected()
+
+	def listEntrySelected(self):
+		"""This function is used as callback for the TextList."""
+		if self.objects.selected_item:
+			object_id = self.objects.selected_item.name
+			namespace = self.objects.selected_item.namespace
+			obj = self.engine.getModel().getObject(object_id, namespace)
+			self.objectSelected(obj)
+
+	def fillPreviewList(self, objects=None):
+		self.objects.clear()
+		
+		if objects is None:
+			if self.namespaces.selected_item is None:
+				return
+			objects = self.engine.getModel().getObjects(self.namespaces.selected_item)
+		
+		for obj in objects:
+			image = self._getImage(obj)
+			if image is None:
+				print 'No image available for selected object'
+				image = ""
+
+			callback = tools.callbackWithArguments(self.objectSelected, obj)	
+			icon = ObjectIcon(callback=callback, image=image, text=unicode(obj.getId()))
+			self.objects.addChild(icon)
+			
+		if len(objects)>0:
+			objects[0].selected = True
+			self.objectSelected(objects[0])
+
+
+	def objectSelected(self, obj):
+		"""This is used as callback function to notify the editor that a new object has
+		been selected.
+		@param obj: fife.Object instance"""
+
+		self.setPreview(obj)
+		
+		self.gui.adaptLayout()
+		events.onObjectSelected.send(sender=self, object=obj)
+
+		self.objects.adaptLayout()
+		self.gui.adaptLayout()
+		
+	# Set preview image
+	def setPreview(self, object):
+		self.preview.image = self._getImage(object)
+		height = self.preview.image.getHeight();
+		if height > 200: height = 200
+		self.preview.parent.max_height = height
+
+	def update_namespace(self):
+		
+		self.namespaces.items = self.engine.getModel().getNamespaces()
+		if not self.namespaces.selected_item:
+			self.namespaces.selected = 0
+		if self.mode == 'list':
+			self.setTextList()
+		elif self.mode == 'preview':
+			self.setImageList()
+		self.update()
+
+	def update(self):
+		if self.mode == 'list':
+			self.fillTextList()
+		elif self.mode == 'preview':
+			self.fillPreviewList()
+
+		self.gui.adaptLayout()
+
+	def _getImage(self, obj):
+		""" Returns an image for the given object.
+		@param: fife.Object for which an image is to be returned
+		@return: fife.GuiImage"""
+		visual = None
+		try:
+			visual = obj.get2dGfxVisual()
+		except:
+			print 'Visual Selection created for type without a visual?'
+			raise
+
+		# Try to find a usable image
+		index = visual.getStaticImageIndexByAngle(0)
+		image = None
+		# if no static image available, try default action
+		if index == -1:
+			action = obj.getDefaultAction()
+			if action:
+				animation_id = action.get2dGfxVisual().getAnimationIndexByAngle(0)
+				animation = self.engine.getAnimationPool().getAnimation(animation_id)
+				image = animation.getFrameByTimestamp(0)
+				index = image.getPoolId()
+
+		# Construct the new GuiImage that is to be returned
+		if index != -1:
+			image = fife.GuiImage(index, self.engine.getImagePool())
+
+		return image
+
+
+	def show(self):
+		self.update_namespace()
+		self.gui.show()
+		self._showAction.setChecked(True)
+
+	def hide(self):
+		self.gui.setDocked(False)
+		self.gui.hide()
+		self._showAction.setChecked(False)
+		
+	def toggle(self):
+		if self.gui.isVisible() or self.gui.isDocked():
+			self.hide()
+		else:
+			self.show()
--- a/clients/editor/plugins/importer.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,32 +0,0 @@
-#!/usr/bin/env python
-# coding: utf-8
-# Plugin for the editor. See fifedit.py. Importer presents a directory browser for finding and importing data files.
-
-import plugin
-import filebrowser
-from loaders import loadImportFile
-from loaders import loadImportDirRec
-
-class Importer(plugin.Plugin):
-	def __init__(self, engine):
-		super(Importer,self).__init__()
-		self.engine = engine
-		
-		self.filebrowser = filebrowser.FileBrowser(engine,self._select,selectdir=True)
-
-		self.menu_items = {
-			'Import Objects' : self.filebrowser.showBrowser,
-		}
-
-		self.newImport = None
-		self.importList = []
-
-	def addDirs(self, dirs):
-		self.importList.extend(dirs)
-
-	def _select(self,path,filename=None):
-		if filename:
-			self.newImport = loadImportFile('/'.join([path, filename]), self.engine)
-		else:
-			self.importList.append(path)
-			loadImportDirRec(path, self.engine)
--- a/clients/editor/plugins/layertool.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,232 +0,0 @@
-#!/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
-# ###################################################
-
-""" an advanced layer tool for FIFedit """
-
-import fife
-import plugin
-import pychan
-import pychan.widgets as widgets
-from pychan.tools import callbackWithArguments as cbwa
-
-import settings as Settings
-
-# default should be pychan default, highlight can be choosen (format: r,g,b)
-_DEFAULT_BACKGROUND_COLOR = (0,0,100)
-_HIGHLIGHT_BACKGROUND_COLOR = (173,216,230)
-
-# the dynamicly created widgets have the name scheme prefix + layerid
-_LABEL_NAME_PREFIX = "select_"
-
-class LayerTool(plugin.Plugin):
-	""" The B{LayerTool} is an advanced method to view
-	and change layer informations.
-	
-	While the original FIFedit tool only allows to select
-	layers, this one will provide the following functionality:
-	
-		- toggle layer visibility
-		- select layer
-		- list layers
-		
-	The plugin has to register itself in the mapeditor module
-	to get actual content when a new map is loaded.
-	"""
-	def __init__(self, engine, mapedit):	
-		# Fifedit plugin data
-		self.menu_items = { 'LayerTool' : self.toggle }
-		self._mapedit = mapedit
-		self.data = False
-		self.previous_active_layer = None
-		
-		# "register" at mapeditor module 
-		self._mapedit.layertool = self
-		
-		self.subwrappers = []
-		self.__create_gui()
-		
-	def __create_gui(self):
-		""" create the basic gui container """
-		self.container =  pychan.loadXML('gui/layertool.xml')
-		self.wrapper = self.container.findChild(name="layers_wrapper")
-
-	def _adjust_position(self):
-		"""	adjusts the position of the container - we don't want to
-		let the window appear at the center of the screen.
-		(new default position: left, beneath the tools window)
-		"""
-		self.container.position = (10, 200)
-		
-	def clear(self):
-		""" remove all subwrappers """
-		if self.subwrappers is []: return
-		
-		for subwrapper in self.subwrappers:
-			self.wrapper.removeChild(subwrapper)
-			
-		self.subwrappers = []
-		
-	def update(self):
-		""" dump new layer informations into the wrapper 
-		
-		We group one ToggleButton and one Lable into a HBox, the main wrapper
-		itself is a VBox and we also capture both the Button and the Label to listen
-		for mouse actions
-		"""
-		layers = self._mapedit._map.getLayers()
-		
-		self.clear()
-		
-		for layer in layers:
-			layerid = layer.getId()
-			subwrapper = pychan.widgets.HBox()
-
-			visibility_widget = pychan.widgets.ToggleButton(up_image="icons/is_visible.png",down_image="icons/quit.png")
-			visibility_widget.name = "toggle_" + layerid
-			visibility_widget.capture(self.toggle_layer_visibility,"mousePressed")
-			
-			layer_name_widget = pychan.widgets.Label()
-			layer_name_widget.text = layerid
-			layer_name_widget.name = _LABEL_NAME_PREFIX + layerid
-			layer_name_widget.capture(self.select_active_layer,"mousePressed")
-			
-			subwrapper.addChild(visibility_widget)
-			subwrapper.addChild(layer_name_widget)
-			
-			self.wrapper.addChild(subwrapper)
-			self.subwrappers.append(subwrapper)
-		
-		self.container.adaptLayout()	
-		self.data = True			
-
-	def toggle(self):
-		""" toggle visibility of the main gui container """
-		if self.container.isVisible():
-			self.container.hide()
-		else:
-			self.container.show()
-			self._adjust_position()
-			
-	def toggle_layer_visibility(self, event, widget):
-		""" callback for ToggleButtons 
-		
-		Toggle the chosen layer visible / invisible
-		
-		NOTE:
-			- if a layer is set to invisible, it also shouldn't be the active layer anymore
-		
-		@type	event:	object
-		@param	event:	pychan mouse event
-		@type	widget:	object
-		@param	widget:	the pychan widget where the event occurs, transports the layer id in it's name
-		"""
-		if not self.data: return
-		
-		layerid = widget.name[7:]
-		
-		layer = self._mapedit._map.getLayer(layerid)
-		
-		if layer.areInstancesVisible():
-			layer.setInstancesVisible(False)
-			self.select_no_layer()
-#			self.select_different_active_layer(layerid)
-		else:
-			layer.setInstancesVisible(True)
-			
-			
-	def select_no_layer(self):
-		""" the exception approach - as soon as the user hides a layer, the mapedit module should stop to use this
-		one, too.
-		
-		A bunch of exceptions is the result (each click on the map will result in a exception as no layer is set etc...)	
-		"""
-		self._mapedit._editLayer(None)
-		
-	def select_different_active_layer(self, layerid):
-		""" a helper method to pick either the previous or next layer in the layerlist
-		by using the given layerid as pivot element
-		
-		NOTE:
-			- dropped for now, we set self.mapedit._layer to None if a layer gets invisible
-		
-		FIXME:
-			- either drop this feature or find a solution for boderline cases:
-				- user hides all layers (which one should be active?)
-				- worst case would be that this algo has to check all layers recursive until it knows that all are invisible
-				  to return with no result (no selection of an active layer, I'm not sure if FIFEdit supports that at all)
-		
-		@type	layerid:	string
-		@param	layerid:	the layerid of the pivot element		
-		"""
-		layers = [layer.getId() for layer in self._mapedit._map.getLayers()]
-		pivot_index = layers.index(layerid)
-
-		if len(layers) == 1:
-			return
-
-		if pivot_index == len(layers) - 1:
-			different_layer = layers[pivot_index - 1]
-		else:
-			different_layer = layers[pivot_index + 1]
-
-		widget = self.container.findChild(name=_LABEL_NAME_PREFIX + different_layer)
-		self.select_active_layer(None, widget)
-		
-	def select_active_layer(self, event, widget):
-		""" callback for Labels 
-		
-		We hand the layerid over to the mapeditor module to select a 
-		new active layer
-		
-		Additionally, we mark the active layer widget (changing base color) and reseting the previous one
-		
-		FIXME:
-			- styled widgets don't accept layout changes (might be a bug in the pychan layout engine)
-			- therefore we can only mark the active layer via text (I added a * to the label text)
-
-		@type	event:	object
-		@param	event:	pychan mouse event
-		@type	widget:	object
-		@param	widget:	the pychan widget where the event occurs, transports the layer id in it's name
-		"""
-		if not self.data: return
-		
-		if self.previous_active_layer is not None:
-			previous_layer_id = str(self.previous_active_layer)
-			previous_active_widget = self.container.findChild(name="select_" + previous_layer_id)
-			previous_active_widget.background_color = _DEFAULT_BACKGROUND_COLOR
-			previous_active_widget.foreground_color = _DEFAULT_BACKGROUND_COLOR
-			previous_active_widget.base_color = _DEFAULT_BACKGROUND_COLOR
-			previous_active_widget.text = previous_layer_id
-		
-		layerid = widget.name[7:]	
-		
-		widget.background_color = _HIGHLIGHT_BACKGROUND_COLOR
-		widget.foreground_color = _HIGHLIGHT_BACKGROUND_COLOR
-		widget.base_color = _HIGHLIGHT_BACKGROUND_COLOR
-		widget.text = widget.text + " *"
-		self.previous_active_layer = layerid
-		self.container.adaptLayout()
-		
-		self._mapedit._editLayer(layerid)
--- a/clients/editor/plugins/mapeditor.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,585 +0,0 @@
-# MapEditor is a plugin for Fifedit. It allows for selection and visual editing of maps.
-
-import math
-
-import fife
-import plugin
-import pychan
-import pychan.widgets as widgets
-from pychan.tools import callbackWithArguments as cbwa
-from selection import Selection, ClickSelection
-from plugins.objectselector import ObjectSelector
-
-SCROLL_TOLERANCE = 10
-SCROLL_SPEED = 1.0
-
-states = ('NOTHING_LOADED', 'VIEWING', 'INSERTING', 'REMOVING', 'MOVING')
-for s in states:
-	globals()[s] = s
-NOT_INITIALIZED = -9999999
-
-class EditorLogicError(Exception):
-	pass
-
-class MapSelection(object):
-	def __init__(self, onLayerSelect, onObjectSelect):
-		self._mapedit = None
-		self._onLayerSelect = onLayerSelect
-		self._onObjectSelect = onObjectSelect
-
-	def show(self, map):
-		if not self._mapedit:
-			self._mapedit = pychan.loadXML('gui/mapeditor.xml')
-			self._mapedit.mapEvents({
-				'layerButton'  : self._onLayerSelect,
-				'objButton'   : self._onObjectSelect,
-				'closeButton' : self.hide
-			})
-		fields = self._mapedit.findChild(name='Properties')
-		# Clear previously added children
-		fields.removeChildren(*fields.children)
-		hbox = widgets.HBox()
-		fields.addChild(hbox)
-		label = widgets.Label(text='ID',min_size=(80,0))
-		hbox.addChild(label)
-		field = widgets.TextField(text=map.getId(),min_size=(100,0))
-		hbox.addChild(field)
-		self._mapedit.adaptLayout()
-		self._mapedit.show()
-		self._mapedit.x = 10
-		self._mapedit.y = 580
-
-	def hide(self):
-		self._mapedit.hide()
-
-class Toolbar(object):
-	def __init__(self, onSelect, onMove, onInsert, onDelete, onBtnEnter, onBtnExit):
-		self._onSelect, self._onMove, self._onInsert, self._onDelete = onSelect, onMove, onInsert, onDelete
-		self.onBtnEnter, self.onBtnExit = onBtnEnter, onBtnExit
-		self._toolbar = None
-
-	def show(self):
-		if not self._toolbar:
-			self._toolbar = pychan.loadXML('gui/tools.xml')
-			evtmap = {
-				'btnSelect' : self._onSelect,
-				'btnMove' : self._onMove,
-				'btnInsert' : self._onInsert,
-				'btnDelete' : self._onDelete
-			}
-			self._toolbar.mapEvents(evtmap)
-			for k in evtmap.keys():
-				btn = self._toolbar.findChild(name=k)
-				btn.setEnterCallback(self.onBtnEnter)
-				btn.setExitCallback(self.onBtnExit)
-
-		#self._toolbar.adaptLayout()
-		self._toolbar.show()
-		self._toolbar.x = 10
-		self._toolbar.y = 50
-
-	def hide(self):
-		self._toolbar.hide()
-
-	def _enableBtn(self, enabled, btn):
-		btn.toggled = enabled;
-
-	def enableSelect(self, enabled):
-		self._enableBtn(enabled, self._toolbar.findChild(name='btnSelect'))
-
-	def enableMove(self, enabled):
-		self._enableBtn(enabled, self._toolbar.findChild(name='btnMove'))
-
-	def enableInsert(self, enabled):
-		self._enableBtn(enabled, self._toolbar.findChild(name='btnInsert'))
-
-	def enableDelete(self, enabled):
-		self._enableBtn(enabled, self._toolbar.findChild(name='btnDelete'))
-
-	def disableAll(self):
-		self.enableDelete(False)
-		self.enableSelect(False)
-		self.enableInsert(False)
-		self.enableMove(False)
-
-class StatusBar(object):
-	def __init__(self, screenw, screenh):
-		self._statusbar = pychan.loadXML('gui/statuspanel.xml')
-		self._statusbar.show()
-		height = 25
-		self._statusbar.position = (0, screenh - height)
-		self._statusbar.size = (screenw, height)
-		self.statustxt = ''
-		self.lbl = self._statusbar.findChild(name='lblStatus')
-
-	def setStatus(self, msg):
-		self.statustxt = msg
-		self.lbl.text = '  ' + msg
-		self.lbl.resizeToContent()
-
-	def showTooltip(self, elem):
-		self.lbl.text = elem.helptext
-		self.lbl.resizeToContent()
-
-	def hideTooltip(self, elem):
-		self.lbl.text = self.statustxt
-		self.lbl.resizeToContent()
-
-
-class MapEditor(plugin.Plugin,fife.IMouseListener, fife.IKeyListener):
-	def __init__(self, engine):
-		self._engine = engine
-		eventmanager = self._engine.getEventManager()
-		#eventmanager.setNonConsumableKeys([
-			#fife.Key.LEFT,
-			#fife.Key.RIGHT,
-			#fife.Key.UP,
-			#fife.Key.DOWN])
-		fife.IMouseListener.__init__(self)
-		eventmanager.addMouseListener(self)
-		fife.IKeyListener.__init__(self)
-		eventmanager.addKeyListener(self)
-
-		# Fifedit plugin data
-		self.menu_items = { 'Select Map' : self._selectMap }
-
-		self._camera = None     # currently selected camera
-		self._map = None        # currently selected map
-		self._layer = None      # currently selected layer
-		self._object = None     # currently selected object
-		self._selection = None  # currently selected coordinates
-		self._instances = None  # currently selected instances
-
-		self._ctrldown = False
-		self._shiftdown = False
-		self._altdown = False
-		self._dragx = NOT_INITIALIZED
-		self._dragy = NOT_INITIALIZED
-		self._scrollx = 0
-		self._scrolly = 0
-
-		self._mapselector = MapSelection(self._selectLayer, self._selectObject)
-		self._objectselector = None
-		rb = self._engine.getRenderBackend()
-		self._statusbar = StatusBar(rb.getWidth(), rb.getHeight())
-		self._toolbar = Toolbar(cbwa(self._setMode, VIEWING), cbwa(self._setMode, MOVING),
-								cbwa(self._setMode, INSERTING), cbwa(self._setMode, REMOVING),
-								self._statusbar.showTooltip, self._statusbar.hideTooltip)
-		self._toolbar.show()
-		self._setMode(NOTHING_LOADED)
-
-		self._undoStack = []
-		self._undo = False # tracks whether current action is an undo
-
-	def _assert(self, statement, msg):
-		if not statement:
-			print msg
-			raise EditorLogicError(msg)
-
-	def _setMode(self, mode):
-		if (mode != NOTHING_LOADED) and (not self._camera):
-			self._statusbar.setStatus('Please load map first')
-			self._toolbar.disableAll()
-			return
-		if (mode == INSERTING) and (not self._object):
-			self._statusbar.setStatus('Please select object first')
-			mode = self._mode
-
-		# Update toolbox buttons
-		if (mode == INSERTING):
-			self._toolbar.enableInsert(True)
-		elif mode == VIEWING:
-			self._toolbar.enableSelect(True)
-		elif mode == REMOVING:
-			self._toolbar.enableDelete(True)
-		elif mode == MOVING:
-			self._toolbar.enableMove(True)
-		else:
-			self._toolbar.disableAll()
-
-		self._mode = mode
-		print "Entered mode " + mode
-		self._statusbar.setStatus(mode.replace('_', ' ').capitalize())
-
-	# gui for selecting a map
-	def _selectMap(self):
-		Selection([map.getId() for map in self._engine.getModel().getMaps()], self.editMap)
-
-	def _selectDefaultCamera(self, map):
-		self._camera = None
-
-		self._engine.getView().resetRenderers()
-		for cam in self._engine.getView().getCameras():
-			cam.setEnabled(False)
-
-		for cam in self._engine.getView().getCameras():
-			if cam.getLocationRef().getMap().getId() == map.getId():
-				rb = self._engine.getRenderBackend()
-				cam.setViewPort(fife.Rect(0, 0, rb.getScreenWidth(), rb.getScreenHeight()))
-				cam.setEnabled(True)
-				self._camera = cam
-				break
-		if not self._camera:
-			raise AttributeError('No cameras found associated with this map: ' + map.getId())
-
-	def editMap(self, mapid):
-		self._camera = None
-		self._map = None
-		self._layer = None
-		self._object = None
-		self._selection = None
-		self._instances = None
-		self._setMode(NOTHING_LOADED)
-
-		self._map = self._engine.getModel().getMap(mapid)
-		if not self._map.getLayers():
-			raise AttributeError('Editor error: map ' + self._map.getId() + ' has no layers. Cannot edit.')
-
-		self._layer = self._map.getLayers()[0]
-		self._selectDefaultCamera(self._map)
-		self._setMode(VIEWING)
-
-		self._mapselector.show(self._map)
-
-		# zero-projekt plugin
-		if self.layertool is not None:
-			self.layertool.update()
-
-	def _selectLayer(self):
-		Selection([layer.getId() for layer in self._map.getLayers()], self._editLayer)
-
-	def _editLayer(self, layerid):
-		self._layer = None
-		layers = [l for l in self._map.getLayers() if l.getId() == layerid]
-		self._assert(len(layers) == 1, 'Layer amount != 1')
-		self._layer = layers[0]
-
-	def _selectObject(self):
-		if not self._objectselector:
-			self._objectselector = ObjectSelector(self._engine, self._map, self._editObject)
-		self._objectselector.show()
-
-	def _editObject(self, object):
-		self._object = object
-
-	def _selectCell(self, screenx, screeny, preciseCoords=False):
-		self._assert(self._camera, 'No camera bind yet, cannot select any cell')
-
-		self._selection = self._camera.toMapCoordinates(fife.ScreenPoint(screenx, screeny), False)
-		self._selection.z = 0
-		loc = fife.Location(self._layer)
-		if preciseCoords:
-			self._selection = self._layer.getCellGrid().toExactLayerCoordinates(self._selection)
-			loc.setExactLayerCoordinates(self._selection)
-		else:
-			self._selection = self._layer.getCellGrid().toLayerCoordinates(self._selection)
-			loc.setLayerCoordinates(self._selection)
-		fife.CellSelectionRenderer.getInstance(self._camera).selectLocation(loc)
-		return loc
-
-	def _getInstancesFromPosition(self, position, top_only):
-		self._assert(self._layer, 'No layer assigned in _getInstancesFromPosition')
-		self._assert(position, 'No position assigned in _getInstancesFromPosition')
-		self._assert(self._camera, 'No camera assigned in _getInstancesFromPosition')
-
-		loc = fife.Location(self._layer)
-		if type(position) == fife.ExactModelCoordinate:
-			loc.setExactLayerCoordinates(position)
-		else:
-			loc.setLayerCoordinates(position)
-		instances = self._camera.getMatchingInstances(loc)
-		if top_only and (len(instances) > 0):
-			instances = [instances[0]]
-		return instances
-
-	def undo(self):
-		if self._undoStack != []:
-			# execute inverse of last action
-			self._undo = True
-			self._undoStack.pop()()
-			self._undo = False
-
-	def _placeInstance(self,position,object):
-		mname = '_placeInstance'
-		self._assert(object, 'No object assigned in %s' % mname)
-		self._assert(position, 'No position assigned in %s' % mname)
-		self._assert(self._layer, 'No layer assigned in %s' % mname)
-
-		print 'Placing instance of ' + object.getId() + ' at ' + str(position)
-		print object
-
-		# don't place repeat instances
-		for i in self._getInstancesFromPosition(position, False):
-			if i.getObject().getId() == object.getId():
-				print 'Warning: attempt to place duplicate instance of object %s. Ignoring request.' % object.getId()
-				return
-
-		inst = self._layer.createInstance(object, position)
-		fife.InstanceVisual.create(inst)
-		if not self._undo:
-			self._undoStack.append(lambda: self._removeInstances(position))
-
-	def _removeInstances(self,position):
-		mname = '_removeInstances'
-		self._assert(position, 'No position assigned in %s' % mname)
-		self._assert(self._layer, 'No layer assigned in %s' % mname)
-
-		for i in self._getInstancesFromPosition(position, top_only=True):
-			print 'Deleting instance ' + str(i) + ' at ' + str(position)
-			if not self._undo:
-				print '>>> ' + i.getObject().getId()
-				print '>>> ' + str(i.getObject())
-				object = i.getObject()
-				self._undoStack.append(lambda: self._placeInstance(position,object))
-			self._layer.deleteInstance(i)
-
-	def _moveInstances(self):
-		mname = '_moveInstances'
-		self._assert(self._selection, 'No selection assigned in %s' % mname)
-		self._assert(self._layer, 'No layer assigned in %s' % mname)
-		self._assert(self._mode == MOVING, 'Mode is not MOVING in %s (is instead %s)' % (mname, str(self._mode)))
-
-		loc = fife.Location(self._layer)
-		if self._shiftdown:
-			loc.setExactLayerCoordinates(self._selection)
-		else:
-			loc.setLayerCoordinates(self._selection)
-		for i in self._instances:
-			f = fife.Location(self._layer)
-			f.setExactLayerCoordinates(i.getFacingLocation().getExactLayerCoordinates() + fife.ExactModelCoordinate(float(self._selection.x), float(self._selection.y)) - i.getLocation().getExactLayerCoordinates())
-			i.setLocation(loc)
-			i.setFacingLocation(f)
-
-	def _rotateInstances(self):
-		mname = '_rotateInstances'
-		self._assert(self._selection, 'No selection assigned in %s' % mname)
-		self._assert(self._layer, 'No layer assigned in %s' % mname)
-
-		for i in self._getInstancesFromPosition(self._selection, top_only=True):
-# by c 09/11/08
-# FIXME:
-			# "hardcoded" rotation is bad for offset editing
-			# instead we should use the angle list given from the object
-			# animations are an issue, as a workaround settings.py provides
-			# project specific animation angles
-			try:
-				if self._objectedit_rotations is not None:
-#				print "available angles: ", self._objectedit_rotations
-					rotation_prev = i.getRotation()
-#				print "previous rotation: ", rotation_prev
-					length = len(self._objectedit_rotations)
-#				print "length: ", length
-					index = self._objectedit_rotations.index( str(rotation_prev) )
-#				print "index, old: ", index
-					if index < length - 1:
-						index += 1
-					elif index == length:
-						index = 0
-					else:
-						index = 0
-#				print "index, new: ", index
-
-					i.setRotation( int(self._objectedit_rotations[index]) )
-#				print "new rotation: ", self._objectedit_rotations[index]
-			except:
-				# Fallback
-				i.setRotation((i.getRotation() + 90) % 360)
-
-# end FIXME
-# end edit c
-
-##    Surprisingly, the following "snap-to-rotation" code is actually incorrect. Object
-##    rotation is independent of the camera, whereas the choice of an actual rotation image
-##    depends very much on how the camera is situated. For example, suppose an object has
-##    rotations defined for 45,135,225,315. And suppose the camera position results in an
-##    effective 60 degree rotation. If the object is given a rotation of 0, then the (correct)
-##    final rotation value of 45 (which is closest to 60 = 0 + 60) will be chosen. If we try
-##    to snap to the closest value to 0 (45), then an incorrect final rotation value will be
-##    chosen: 135, which is closest to 105 = 45 + 60. --jwt
-#			ovis = i.getObject().get2dGfxVisual()
-#			curUsedAngle = ovis.getClosestMatchingAngle(i.getRotation())
-#			angles = ovis.getStaticImageAngles()
-#			if angles:
-#				ind = list(angles).index(curUsedAngle)
-#				if ind == (len(angles) - 1):
-#					ind = 0
-#				else:
-#					ind += 1
-#				i.setRotation(angles[ind])
-#			else:
-#				print "rotation not supported for this instance"
-
-	def changeRotation(self):
-		currot = self._camera.getRotation()
-		self._camera.setRotation((currot + 90) % 360)
-
-	def _moveCamera(self, screen_x, screen_y):
-		coords = self._camera.getLocationRef().getMapCoordinates()
-		z = self._camera.getZoom()
-		r = self._camera.getRotation()
-		if screen_x:
-			coords.x -= screen_x / z * math.cos(r / 180.0 * math.pi) / 100;
-			coords.y -= screen_x / z * math.sin(r / 180.0 * math.pi) / 100;
-		if screen_y:
-			coords.x -= screen_y / z * math.sin(-r / 180.0 * math.pi) / 100;
-			coords.y -= screen_y / z * math.cos(-r / 180.0 * math.pi) / 100;
-		coords = self._camera.getLocationRef().setMapCoordinates(coords)
-		self._camera.refresh()
-
-	def mousePressed(self, evt):
-		if evt.isConsumedByWidgets():
-			return
-
-		if self._ctrldown:
-			if evt.getButton() == fife.MouseEvent.LEFT:
-				self._dragx = evt.getX()
-				self._dragy = evt.getY()
-		else:
-			if self._camera:
-				self._selectCell(evt.getX(), evt.getY(), self._shiftdown)
-			if self._mode == VIEWING:
-				self._instances = self._getInstancesFromPosition(self._selection, top_only=True)
-			elif self._mode == INSERTING:
-				self._placeInstance(self._selection,self._object)
-			elif self._mode == REMOVING:
-				self._removeInstances(self._selection)
-			elif self._mode == MOVING:
-				self._instances = self._getInstancesFromPosition(self._selection, top_only=True)
-			else:
-				self._setMode(self._mode) # refresh status
-
-	def mouseDragged(self, evt):
-		if evt.isConsumedByWidgets():
-			return
-
-		if self._ctrldown:
-			if (self._dragx != NOT_INITIALIZED) and (self._dragy != NOT_INITIALIZED):
-				self._moveCamera(evt.getX() - self._dragx, evt.getY() - self._dragy)
-			self._dragx = evt.getX()
-			self._dragy = evt.getY()
-		else:
-			if self._mode == INSERTING:
-				self._selectCell(evt.getX(), evt.getY())
-				self._placeInstance(self._selection,self._object)
-			elif self._mode == REMOVING:
-				self._selectCell(evt.getX(), evt.getY())
-				self._removeInstances(self._selection)
-			elif self._mode == MOVING and self._instances:
-				self._selectCell(evt.getX(), evt.getY(), self._shiftdown)
-				self._moveInstances()
-
-	def mouseReleased(self, evt):
-		if evt.isConsumedByWidgets():
-			return
-
-		self._dragx = NOT_INITIALIZED
-		self._dragy = NOT_INITIALIZED
-
-	def mouseMoved(self, evt):
-		if self._camera:
-			screen_x = self._engine.getRenderBackend().getWidth()
-			screen_y = self._engine.getRenderBackend().getHeight()
-			ratio = float(screen_x) / screen_y
-
-			mouse_x = evt.getX()
-			mouse_y = evt.getY()
-
-			self._scrollx = 0
-			self._scrolly = 0
-
-			if mouse_y <= SCROLL_TOLERANCE:
-				# up
-				self._scrolly = SCROLL_SPEED * ratio
-			if mouse_x >= screen_x - SCROLL_TOLERANCE:
-				# right
-				self._scrollx = -SCROLL_SPEED
-			if mouse_y >= screen_y - SCROLL_TOLERANCE:
-				# bottom
-				self._scrolly = -SCROLL_SPEED * ratio
-			if mouse_x <= SCROLL_TOLERANCE:
-				# left
-				self._scrollx = SCROLL_SPEED
-
-	def mouseEntered(self, evt):
-		pass
-	def mouseExited(self, evt):
-		pass
-	def mouseClicked(self, evt):
-		pass
-
-	def mouseWheelMovedUp(self, evt):
-		if self._ctrldown and self._camera:
-			self._camera.setZoom(self._camera.getZoom() * 1.05)
-
-	def mouseWheelMovedDown(self, evt):
-		if self._ctrldown and self._camera:
-			self._camera.setZoom(self._camera.getZoom() / 1.05)
-
-
-	def keyPressed(self, evt):
-		keyval = evt.getKey().getValue()
-		keystr = evt.getKey().getAsString().lower()
-
-		if keyval == fife.Key.LEFT:
-			self._moveCamera(50, 0)
-		elif keyval == fife.Key.RIGHT:
-			self._moveCamera(-50, 0)
-		elif keyval == fife.Key.UP:
-			self._moveCamera(0, 50)
-		elif keyval == fife.Key.DOWN:
-			self._moveCamera(0, -50)
-		elif keyval in (fife.Key.LEFT_CONTROL, fife.Key.RIGHT_CONTROL):
-			self._ctrldown = True
-		elif keyval in (fife.Key.LEFT_SHIFT, fife.Key.RIGHT_SHIFT):
-			self._shiftdown = True
-		elif keyval in (fife.Key.LEFT_ALT, fife.Key.RIGHT_ALT):
-			self._altdown = True
-
-		elif keyval == fife.Key.INSERT:
-			if self._mode != INSERTING:
-				self._setMode(INSERTING)
-			else:
-				self._setMode(VIEWING)
-
-		elif keyval == fife.Key.DELETE:
-			if self._mode != REMOVING:
-				self._setMode(REMOVING)
-			else:
-				self._setMode(VIEWING)
-
-		elif keystr == 'm':
-			if self._mode != MOVING:
-				self._setMode(MOVING)
-			else:
-				self._setMode(VIEWING)
-
-		elif keystr == 't':
-			gridrenderer = self._camera.getRenderer('GridRenderer')
-			gridrenderer.setEnabled(not gridrenderer.isEnabled())
-
-		elif keystr == 'b':
-			blockrenderer = self._camera.getRenderer('BlockingInfoRenderer')
-			blockrenderer.setEnabled(not blockrenderer.isEnabled())
-
-		elif keystr == 'r':
-			if self._selection:
-				self._rotateInstances()
-
-		elif keystr == 'o':
-			self.changeRotation()
-
-		elif keystr == 'u':
-			self.undo()
-
-	def keyReleased(self, evt):
-		keyval = evt.getKey().getValue()
-		if keyval in (fife.Key.LEFT_CONTROL, fife.Key.RIGHT_CONTROL):
-			self._ctrldown = False
-		elif keyval in (fife.Key.LEFT_SHIFT, fife.Key.RIGHT_SHIFT):
-			self._shiftdown = False
-		elif keyval in (fife.Key.LEFT_ALT, fife.Key.RIGHT_ALT):
-			self._altdown = False
-
-	def pump(self):
-		if self._scrollx != 0 or self._scrolly != 0:
-			self._moveCamera(self._scrollx * self._engine.getTimeManager().getTimeDelta(), self._scrolly * self._engine.getTimeManager().getTimeDelta())
--- a/clients/editor/plugins/maploader.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,67 +0,0 @@
-#!/usr/bin/env python
-# coding: utf-8
-# Plugin for the editor. See fifedit.py. MapLoader presents a directory browser for finding and loading xml maps.
-
-import plugin
-import filebrowser
-import loaders, savers
-from loaders import loadMapFile
-from savers import saveMapFile
-
-class MapLoader(plugin.Plugin):
-	def __init__(self, engine):
-		super(MapLoader,self).__init__()
-		self.engine = engine
-		
-		self.filebrowser = filebrowser.FileBrowser(engine,self.loadFile, extensions = loaders.fileExtensions)
-
-		self.menu_items = {
-			'Load' : self.filebrowser.showBrowser,
-		}
-		self.newMap = None
-
-	def loadFile(self, path, filename):
-		content = path.split('/')
-		print content
-		self.newMap = loadMapFile('/'.join([path, filename]), self.engine)
-
-class MapSaver(plugin.Plugin):
-	def __init__(self, engine):
-		super(MapSaver,self).__init__()
-		self.engine = engine
-		
-		self.filebrowser = filebrowser.FileBrowser(engine,self._selectFile,savefile=True, extensions = savers.fileExtensions)
-
-		self.menu_items = {
-			'Save' : self.save,
-			'Save As' : self.filebrowser.showBrowser,
-		}	
-
-		self.saveRequested = False
-		self._location = None
-		self.path = '.'
-
-	def save(self):
-		self.saveRequested = True
-
-	def saveMap(self, map, importList):
-		curname = None
-		try:
-			curname = map.getResourceLocation().getFilename()
-		except RuntimeError:
-			pass # no name set for map yet
-		if self._location:
-			fname = '/'.join([self.path, self._location])
-			saveMapFile(fname, self.engine, map, importList)
-			print "map saved as " + fname
-			self._location = None
-		elif curname:
-			saveMapFile(curname, self.engine, map, importList)
-			print "map saved with old name " + curname
-		else:
-			print 'MapSaver: error, no file location specified.'
-
-	def _selectFile(self,path,filename):
-		self._location = filename
-		self.path = path
-		self.saveRequested = True
--- a/clients/editor/plugins/mapwizard.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,116 +0,0 @@
-# MapWizard is a plugin for Fifedit. It is a step-by-step map creation utility.
-
-import math
-
-import fife
-import plugin
-import pychan
-import pychan.widgets as widgets
-from input import Input
-from selection import Selection,ClickSelection
-
-class CameraEditor():
-	"""
-	CameraEditor provides a gui dialog for camera creation. The callback is called when camera creation is complete. A
-	partial specification of the camera parameters may optionally be given.
-	"""
-	def __init__(self, engine, callback=None, map=None, layer=None):
-		self.engine = engine
-		self.callback = callback
-		self._widget = pychan.loadXML('gui/cameraedit.xml')
-
-		if map:
-			self._widget.distributeData({
-				'mapBox'	: map.getId(),
-			})
-
-		if layer:
-			self._widget.distributeData({
-				'layerBox'	: layer.getId(),
-			})
-
-		self._widget.mapEvents({
-			'okButton'     : self._finished,
-			'cancelButton' : self._widget.hide
-		})
-
-		self._widget.show()
-
-	def _finished(self):
-		id = self._widget.collectData('idBox')
-		if id == '':
-			print 'Please enter a camera id.'
-			return
-
-		try:
-			map = self.engine.getModel().getMap(str(self._widget.collectData('mapBox')))
-		except fife.Exception:
-			print 'Cannot find the specified map id.'
-			return
-
-		try:
-			layer = map.getLayer(str(self._widget.collectData('layerBox')))
-		except fife.Exception:
-			print 'Cannot find the specified layer id.'	
-			return
-
-		try:
-			vals = self._widget.collectData('viewBox').split(',')
-			if len(vals) != 4:
-				raise ValueError	
-
-			viewport = fife.Rect(*[int(c) for c in vals])
-		except ValueError:
-			print 'Please enter 4 comma (,) delimited values for viewport x,y,width,height.'
-			return
-
-		try:
-			refh = int(self._widget.collectData('refhBox'))
-			refw = int(self._widget.collectData('refwBox'))
-		except ValueError:
-			print 'Please enter positive integer values for reference width and height.'
-			return
-
-		try:
-			rot = int(self._widget.collectData('rotBox'))
-			tilt = int(self._widget.collectData('tiltBox'))
-		except ValueError:
-			print 'Please enter positive integer values for rotation and tilt.'
-			return
-
-		cam = self.engine.getView().addCamera(id, layer, viewport, fife.ExactModelCoordinate(0,0,0))
-		cam.setCellImageDimensions(refw, refh)
-		cam.setRotation(rot)
-		cam.setTilt(tilt)
-	
-		self._widget.hide()
-
-		if self.callback:
-			self.callback()
-
-class MapWizard(plugin.Plugin):
-	def __init__(self, engine):
-		self.engine = engine
-
-		# Fifedit plugin data
-		self.menu_items = { 'Map Wizard' : self._buildMap }
-
-		self.newMap = False
-		self.map = None
-
-	def _buildMap(self):
-		def newMap(id):
-			def newLayer(id):
-				def newCamera():
-					self.newMap = True
-
-				grid = fife.SquareGrid()
-				layer = self.map.createLayer(id, grid)
-				grid.thisown = 0
-
-				CameraEditor(self.engine, newCamera, self.map, layer)
-
-			self.map = self.engine.getModel().createMap(id)
-			Input('Enter a layer identifier for a default layer:', newLayer)
-		
-		Input('Enter a map identifier:', newMap)
--- a/clients/editor/plugins/objectedit.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,453 +0,0 @@
-#!/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 edit object and instance attributes """
-
-import fife
-import plugin
-import pychan
-import pychan.widgets as widgets
-from pychan.tools import callbackWithArguments as cbwa
-
-import settings as Settings
-import math
-
-class ObjectEdit(plugin.Plugin):
-	""" The B{ObjectEdit} module is a plugin for FIFedit and allows to edit
-	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 offsets, rotation, instance id
-		- 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
-		- use sliders to allow offset changes
-		- the module should be able to use the editors global undo history
-
-	FIXME:
-		- this module owns a pointer to the mapedit module - this shouldn't be
-		  necessary; a better plugin system of fifedit should only hand over the needed
-		  data (selected instance)
-		- we also need to edit run.py of the editor core to make this plugin work (shouldn't be necessary, too)
-	"""
-	def __init__(self, engine, mapedit):
-		# Fifedit plugin data
-		self.menu_items = { 'ObjectEdit' : self.toggle_offsetedit }
-		
-		self._mapedit = mapedit
-
-		# this is _very bad_ - but I need to change the current rotation code by providing
-		# project specific rotation angles. FIFE later should provide a list of the loaded
-		# object rotations (they are provided by the xml files, so we just need to use them...)
-		self._mapedit._objectedit_rotations = None
-	
-		self.active = False
-		self._camera = None
-		self._layer = None
-		
-		self.offset_slider = {}
-		self.offset_slider['x'] = False
-		self.offset_slider['y'] = False
-		
-		self.imagepool = engine.getImagePool()
-		self.animationpool = engine.getAnimationPool()
-		
-		self.guidata = {}
-		self.objectdata = {}
-
-		self._reset()		
-		self.create_gui()
-
-	def _reset(self):
-		"""
-			resets all dynamic vars, but leaves out static ones (e.g. camera, layer)
-
-		"""
-		self._instances = None
-		self._image = None
-		self._image_default_x_offset = None
-		self._image_default_y_offset = None
-		self._animation = False
-		self._rotation = None
-		self._avail_rotations = []
-		self._namespace = None	
-		self._blocking = 0
-		self._static = 0
-		self._object_id = None	
-		self._instance_id = None
-		self._fixed_rotation = None
-		
-		if self._camera is not None:
-			self.renderer.removeAllOutlines()		
-		
-	def create_gui(self):
-		"""
-			- creates the gui skeleton by loading the xml file
-			- finds some important childs and saves their widget in the object
-		"""
-		self.container = pychan.loadXML('gui/objectedit.xml')
-		self.container.mapEvents({
-			'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),
-			
-			'x_offset_slider' : cbwa(self.get_slider_value, "x"),
-			'y_offset_slider' : cbwa(self.get_slider_value, "y"),
-			
-			'use_data'		: self.use_user_data,
-			
-		})
-
-		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_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")
-		
-		print "Steplength x slider", self.container.findChild(name="x_offset_slider").getStepLength()
-		print "Steplength y slider", self.container.findChild(name="y_offset_slider").getStepLength()
-		self.container.findChild(name="x_offset_slider").setStepLength(0.01)
-		self.container.findChild(name="y_offset_slider").setStepLength(0.01)
-		print "New steplength x slider", self.container.findChild(name="x_offset_slider").getStepLength()
-		print "New steplength y slider", self.container.findChild(name="y_offset_slider").getStepLength()
-		
-	def get_slider_value(self, orientation):
-		""" get current slider value for offset manipulation """
-		
-		slider_name = orientation + "_offset_slider"
-		widget = self.container.findChild(name=slider_name)
-		value = widget.getValue()
-
-		print "%s slider value: %s" % (orientation, str(value))
-		
-		if value < 0: 
-			self.offset_slider[orientation] = False
-			return
-		
-		callback = getattr(self, "change_offset_" + orientation)
-
-		if self.offset_slider[orientation] == widget.getScaleStart():
-			self.set_default_offset(orientation)
-			self.offset_slider[orientation] = False
-			return	
-		elif self.offset_slider[orientation] >= widget.getScaleEnd():
-			pass
-		elif self.offset_slider[orientation] < value:
-			callback(1)
-		elif self.offset_slider[orientation] > value :
-			callback(-1)
-
-		self.offset_slider[orientation] = value
-
-	def set_default_offset(self, axis):
-		""" set default image offset for given axis """
-		if axis == 'x':
-			self._image.setXShift(self._image_default_x_offset)
-		elif axis == 'y':
-			self._image.setYShift(self._image_default_y_offset)
-
-	def _get_gui_size(self):
-		"""
-			gets the current size of the gui window and calculates new position
-			(atm top right corner)
-		"""
-		size = self.container._getSize()
-		self.position = ((Settings.ScreenWidth - 10 - size[0]), 10)
-		
-	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
-
-		self.container.distributeInitialData({
-			'select_rotations' 	: self._avail_rotations,
-			'instance_id'		: str( self._instances[0].getId() ),
-			'object_id'			: str( self._object_id ),
-			'x_offset'			: str( self._image.getXShift() ),
-			'y_offset'			: str( self._image.getYShift() ),
-			'instance_rotation' : str( self._instances[0].getRotation() ),
-			'object_namespace'	: str( self._namespace ),
-			'object_blocking'	: str( self._blocking ),
-			'object_static'		: str( self._static ),
-		})
-		try:
-			print self._avail_rotations
-			print self._fixed_rotation
-			index = self._avail_rotations.index( str(self._fixed_rotation) )
-			self._gui_rotation_dropdown._setSelected(index)
-		except:
-#			pass
-			print "Angle (", self._fixed_rotation, ") not supported by this instance"
-		
-	def toggle_gui(self):
-		"""
-			show / hide the gui
-			
-			FIXME:
-				- ATM not in use, needs some additional code when showing / hiding the gui (see input() )
-		"""
-		if self.container.isVisible():
-			self.container.hide()
-		else:
-			self.container.show()
-			
-	def toggle_offsetedit(self):
-		"""
-			- toggles the object editor activ / inactiv - just in case the user don't want to have
-			  the gui popping up all the time while mapping :-)
-			- hides gui
-		"""
-		if self.active is True:
-			self.active = False
-			if self.container.isVisible():
-				self.container.hide()
-		else:
-			self.active = True	
-
-	def highlight_selected_instance(self):
-		"""
-			just highlights selected instance
-		"""
-		self.renderer.removeAllOutlines() 
-		self.renderer.addOutlined(self._instances[0], 205, 205, 205, 1)
-			
-	def change_offset_x(self, value=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._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._image is not None:
-			self._image.setYShift(self._image.getYShift() + value)
-			self.update_gui()
-
-	def use_user_data(self):
-		"""
-			- takes the users values and applies them directly to the current ._instance
-			- 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...
-		"""
-		xoffset = self._gui_xoffset_textfield._getText()
-		yoffset = self._gui_yoffset_textfield._getText()
-		
-		instance_id = self._gui_instance_id_textfield._getText()
-		if instance_id is not None and instance_id is not "None":
-			existing_instances = self._mapedit._layer.getInstances(instance_id)
-			if existing_instances == ():
-				self._instances[0].setId(instance_id)
-				print "Set new instance id: ", instance_id		
-			else:
-				for i in existing_instances:
-					print i
-		
-		# 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()
-		
-		# strange, but this helps to rotate the image correctly to the value the user selected
-		angle = int( self._avail_rotations[index] )
-		angle = int(angle - abs( self._camera.getTilt() ) )
-		if angle == 360:
-			angle = 0
-		
-		self._instances[0].setRotation(angle)
-		self.get_instance_data(None, None, angle)
-		
-		try:
-			self._image.setXShift( int(xoffset) )
-		except:
-			pass
-#		print "x offset must me of type int!"
-		try:
-			self._image.setYShift( int(yoffset) )
-		except:
-			pass
-#		print "y offset must be of type int!"
-
-		self.update_gui()
-		
-	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 = []
-			
-		if instance is None:
-			instance = self._instances[0]
-			
-		object = instance.getObject()
-		self._namespace = object.getNamespace()
-		self._object_id = object.getId()
-
-		self._instance_id = instance.getId()
-	
-		if self._instance_id == '':
-			self._instance_id = 'None'
-
-		if angle == -1:
-			angle = int(instance.getRotation())
-		else:
-			angle = int(angle)	
-			
-		self._rotation = angle
-		
-		if object.isBlocking():
-			self._blocking = 1
-			
-		if object.isStatic():
-			self._static = 1
-		
-		try:
-			visual = object.get2dGfxVisual()
-		except:
-			print 'Fetching visual of object - failed. :/'
-			raise			
-
-#		print "Camera Tilt: ", self._camera.getTilt()
-#		print "Camera Rotation: ", self._camera.getRotation()
-
-		self._fixed_rotation = int(instance.getRotation() + abs( self._camera.getTilt() ) )		
-		self._fixed_rotation = visual.getClosestMatchingAngle(self._fixed_rotation)	
-
-		index = visual.getStaticImageIndexByAngle(self._fixed_rotation)
-
-		if index == -1:
-			# object is an animation
-			self._animation = True
-			# 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:
-			# 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) )
-				
-		self._image_default_x_offset = self._image.getXShift()
-		self._image_default_y_offset = self._image.getYShift()
-
-# FIXME: see l. 40
-		self._mapedit._objectedit_rotations = self._avail_rotations
-# end FIXME
-		
-	def input(self):
-		"""
-			if called _and_ the user wishes to edit offsets,
-			gets instance data and show gui
-			
-			(see run.py, pump() )
-		"""
-		if self._mapedit._instances != self._instances:
-			if self.active is True:
-				self._reset()
-				self._instances = self._mapedit._instances
-				
-				if self._camera is None:
-					self._camera = self._mapedit._camera
-					self.renderer = fife.InstanceRenderer.getInstance(self._camera)				
-					
-				self._layer = self._mapedit._layer
-			
-				if self._instances != ():
-					self.highlight_selected_instance()
-					self.get_instance_data()
-					self.update_gui()
-					self.container.adaptLayout()
-					self.container.show()
-					self._get_gui_size()
-					self.container._setPosition(self.position)
-				else:
-					self._reset()
-					self.container.hide()
--- a/clients/editor/plugins/objectselector.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,352 +0,0 @@
-# coding: utf-8
-
-import pychan
-from pychan import widgets, tools, attrs, internal
-from pychan.tools import callbackWithArguments
-import fife
-from fife import Color
-
-# TODO:
-# - Better event handling
-# - Label background color can't be set
-
-_DEFAULT_BASE_COLOR = internal.DEFAULT_STYLE['default']['base_color']
-_DEFAULT_SELECTION_COLOR = internal.DEFAULT_STYLE['default']['selection_color']
-_DEFAULT_COLOR_STEP = Color(10, 10, 10)
-
-class ObjectIcon(widgets.VBox):
-	""" The ObjectIcon is used to represent the object in the object selector.
-	"""	
-	ATTRIBUTES = widgets.VBox.ATTRIBUTES + [ attrs.Attr("text"), attrs.Attr("image"), attrs.BoolAttr("selected") ]
-	
-	def __init__(self,callback,**kwargs):
-		super(ObjectIcon,self).__init__(**kwargs)
-
-		self.callback = callback	
-
-		self.capture(self._mouseEntered, "mouseEntered")
-		self.capture(self._mouseExited, "mouseExited")
-		self.capture(self._mouseClicked, "mouseClicked")
-
-		vbox = widgets.VBox(padding=3)
-
-		# Icon
-		self.icon = widgets.Icon(**kwargs)
-		self.addChild(self.icon)
-
-		# Label
-		hbox = widgets.HBox(padding=1)
-		self.addChild(hbox)
-		self.label = widgets.Label(**kwargs)
-		hbox.addChild(self.label)
-
-	def _setText(self, text):
-		self.label.text = text
-		
-	def _getText(self):
-		return self.label.text
-	text = property(_getText, _setText)
-
-	def _setImage(self, image):
-		self.icon.image = image
-
-	def _getImage(self):
-		return self.icon.image
-	image = property(_getImage, _setImage)
-
-	def _setSelected(self, enabled):
-		if isinstance(self.parent, ObjectIconList):
-			if enabled == True:
-				self.parent.selected_item = self
-			else:
-				if self.selected:
-					self.parent.selected_item = None
-		
-		# + Color(0,0,0) to force variable copy
-		if self.selected:
-			self.base_color = _DEFAULT_SELECTION_COLOR + Color(0,0,0)
-		else:
-			self.base_color = _DEFAULT_BASE_COLOR + Color(0,0,0)
-
-	def _isSelected(self):
-		if isinstance(self.parent, ObjectIconList):
-			return self == self.parent.selected_item
-		return False
-	selected = property(_isSelected, _setSelected)
-
-	#--- Event handling ---#
-	def _mouseEntered(self, event):
-		self.base_color += _DEFAULT_COLOR_STEP
-
-	def _mouseExited(self, event):
-		self.base_color -= _DEFAULT_COLOR_STEP
-
-	def _mouseClicked(self, event):
-		self.selected = True
-		self.callback()
-
-class ObjectIconList(widgets.VBox):
-	ATTRIBUTES = widgets.VBox.ATTRIBUTES
-	
-	def __init__(self,**kwargs):
-		super(ObjectIconList, self).__init__(max_size=(5000,500000), **kwargs)
-		self.base_color = self.background_color
-
-		# TODO: Pychan doesn't support keyevents for nonfocusable widgets, yet
-		#self.capture(self._keyPressed, "keyPressed")
-		#self.capture(self._keyPressed, "keyReleased")
-		self._selectedItem = None
-
-	#def _keyPressed(self, event):
-		#print "KeyEvent", event
-
-	def clear(self):
-		count = 0
-		for c in reversed(self.children):
-			self.removeChild(c)
-
-	def _setSelectedItem(self, item):
-		if isinstance(item, ObjectIcon) or item is None:
-			if self._selectedItem is not None:
-				tmp = self._selectedItem
-				self._selectedItem = item
-				tmp.selected = False
-			else:
-				self._selectedItem = item	
-
-	def _getSelectedItem(self):
-		return self._selectedItem
-	selected_item = property(_getSelectedItem, _setSelectedItem)
-	
-class ObjectSelector(object):
-	"""The ObjectSelector class offers a gui Widget that let's you select the object you
-	wish to use to in the editor.
-	@param engine: fife instance
-	@param map: fife.Map instance containing your map
-	@param selectNotify: callback function used to tell the editor you selected an object.
-	"""
-	def __init__(self, engine, map, selectNotify):
-		self.engine = engine
-		self.map = map
-		self.notify = selectNotify
-		self.mode = 'list' # Other mode is 'preview'
-
-		self.buildGui()
-
-
-	def buildGui(self):
-		self.gui = pychan.loadXML('gui/objectselector.xml')
-
-		# Add search field
-		self._searchfield = self.gui.findChild(name="searchField")
-		self._searchfield.capture(self._search)
-		self._searchfield.capture(self._search, "keyPressed")
-		self.gui.findChild(name="searchButton").capture(self._search)
-		
-		# Add the drop down with list of namespaces
-		self.namespaces = self.gui.findChild(name="namespaceDropdown")
-		self.namespaces.items = self.engine.getModel().getNamespaces()
-		self.namespaces.selected = 0
-
-		# TODO: Replace with SelectionEvent, once pychan supports it
-		self.namespaces.capture(self.update_namespace, "action")
-		self.namespaces.capture(self.update_namespace, "mouseWheelMovedUp")
-		self.namespaces.capture(self.update_namespace, "mouseWheelMovedDown")
-		self.namespaces.capture(self.update_namespace, "keyReleased")
-
-		# Object list
-		self.mainScrollArea = self.gui.findChild(name="mainScrollArea")
-		self.objects = None
-		if self.mode == 'list':
-			self.setTextList()
-		else: # Assuming self.mode is 'preview'
-			self.setImageList()
-
-		# Action buttons
-		self.gui.findChild(name="toggleModeButton").capture(self.toggleMode)
-		self.gui.findChild(name="closeButton").capture(self.hide)
-
-		# Preview area
-		self.gui.findChild(name="previewScrollArea").background_color = self.gui.base_color
-		self.preview = self.gui.findChild(name="previewIcon")
-		
-
-	def toggleMode(self):
-		if self.mode == 'list':
-			self.setImageList()
-			self.mode = 'preview'
-		elif self.mode == 'preview':
-			self.setTextList()
-			self.mode = 'list'
-		self.update()
-
-
-	def setImageList(self):
-		"""Sets the mainScrollArea to contain a Vbox that can be used to fill in
-		preview Images"""
-		if self.objects is not None:
-			self.mainScrollArea.removeChild(self.objects)
-		self.objects = ObjectIconList(name='list', size=(200,1000))
-		self.objects.base_color = self.mainScrollArea.background_color
-		self.mainScrollArea.addChild(self.objects)
-
-	def setTextList(self):
-		"""Sets the mainScrollArea to contain a List that can be used to fill in
-		Object names/paths"""
-		if self.objects is not None:
-			self.mainScrollArea.removeChild(self.objects)
-		self.objects = widgets.ListBox(name='list')
-		self.objects.capture(self.listEntrySelected)
-		self.mainScrollArea.addChild(self.objects)
-
-	def _search(self):
-		self.search(self._searchfield.text)
-
-	def search(self, str):
-		results = []	
-			
-		# Format search terms
-		terms = [term.lower() for term in str.split()]
-		
-		# Search
-		if len(terms) > 0:
-			namespaces = self.engine.getModel().getNamespaces()
-			for namesp in namespaces:
-				objects = self.engine.getModel().getObjects(namesp)
-				for obj in objects:
-					doAppend = True
-					for term in terms:
-						if obj.getId().lower().find(term) < 0:
-							doAppend = False
-							break
-					if doAppend:
-						results.append(obj)
-		else:
-			results = None
-		
-		if self.mode == 'list':
-			self.fillTextList(results)
-		elif self.mode == 'preview':
-			self.fillPreviewList(results)
-
-	def fillTextList(self, objects=None):
-		if objects is None:
-			if self.namespaces.selected_item is None:
-				return
-			objects = self.engine.getModel().getObjects(self.namespaces.selected_item)
-		
-		class _ListItem:
-			def __init__( self, name, namespace ):
-				self.name = name
-				self.namespace = namespace
-			def __str__( self ):
-				return self.name
-			
-		if self.namespaces.selected_item:
-			self.objects.items = [_ListItem(obj.getId(), obj.getNamespace()) for obj in objects]
-			if not self.objects.selected_item:
-				self.objects.selected = 0
-				self.listEntrySelected()
-
-	def listEntrySelected(self):
-		"""This function is used as callback for the TextList."""
-		if self.objects.selected_item:
-			object_id = self.objects.selected_item.name
-			namespace = self.objects.selected_item.namespace
-			obj = self.engine.getModel().getObject(object_id, namespace)
-			self.objectSelected(obj)
-
-	def fillPreviewList(self, objects=None):
-		self.objects.clear()
-		
-		if objects is None:
-			if self.namespaces.selected_item is None:
-				return
-			objects = self.engine.getModel().getObjects(self.namespaces.selected_item)
-		
-		for obj in objects:
-			image = self._getImage(obj)
-			if image is None:
-				print 'No image available for selected object'
-				image = ""
-
-			callback = tools.callbackWithArguments(self.objectSelected, obj)	
-			icon = ObjectIcon(callback=callback, image=image, text=obj.getId())
-			self.objects.addChild(icon)
-			
-		if len(objects)>0:
-			objects[0].selected = True
-			self.objectSelected(objects[0])
-
-
-	def objectSelected(self, obj):
-		"""This is used as callback function to notify the editor that a new object has
-		been selected.
-		@param obj: fife.Object instance"""
-
-		# Set preview image
-		self.preview.image = self._getImage(obj)
-		height = self.preview.image.getHeight();
-		if height > 200: height = 200
-		self.preview._getParent()._setHeight(height)
-		
-		self.gui.adaptLayout()
-		self.notify(obj)
-
-		self.objects.adaptLayout()
-		self.gui.adaptLayout()		
-
-	def update_namespace(self):
-		self.namespaces.items = self.engine.getModel().getNamespaces()
-		if not self.namespaces.selected_item:
-			self.namespaces.selected = 0
-		if self.mode == 'list':
-			self.setTextList()
-		elif self.mode == 'preview':
-			self.setImageList()
-		self.update()
-
-	def update(self):
-		if self.mode == 'list':
-			self.fillTextList()
-		elif self.mode == 'preview':
-			self.fillPreviewList()
-
-		self.mainScrollArea.resizeToContent()
-
-	def _getImage(self, obj):
-		""" Returns an image for the given object.
-		@param: fife.Object for which an image is to be returned
-		@return: fife.GuiImage"""
-		visual = None
-		try:
-			visual = obj.get2dGfxVisual()
-		except:
-			print 'Visual Selection created for type without a visual?'
-			raise
-
-		# Try to find a usable image
-		index = visual.getStaticImageIndexByAngle(0)
-		image = None
-		# if no static image available, try default action
-		if index == -1:
-			action = obj.getDefaultAction()
-			if action:
-				animation_id = action.get2dGfxVisual().getAnimationIndexByAngle(0)
-				animation = self.engine.getAnimationPool().getAnimation(animation_id)
-				image = animation.getFrameByTimestamp(0)
-				index = image.getPoolId()
-
-		# Construct the new GuiImage that is to be returned
-		if index != -1:
-			image = fife.GuiImage(index, self.engine.getImagePool())
-
-		return image
-
-
-	def show(self):
-		self.update_namespace()
-		self.gui.show()
-
-	def hide(self):
-		self.gui.hide()
--- a/clients/editor/plugins/plugin.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,21 +0,0 @@
-# coding: utf-8
-
-import pychan.widgets as widgets
-
-class Plugin(object):
-	"""
-	Plugin base for the editor.
-	Currently transitional code.
-	"""
-	def __init__(self):
-		self.menu_items = {}
-
-	def install(self,gui):
-		for key in self.menu_items:
-			button = widgets.Button(name=key,text=key)
-			gui.addChild(button)
-		gui.mapEvents(self.menu_items)
-		gui.adaptLayout()
-
-	def deinstall(self,fifedit):
-		pass
--- a/clients/editor/run.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/editor/run.py	Mon Jun 08 16:00:02 2009 +0000
@@ -13,105 +13,9 @@
 		sys.path.append(_jp(p))
 
 import fife
-import fifelog
 import basicapplication
-import pychan
-import pychan.widgets as widgets
-import sys
-
-from listener import EditorListener
-
-from plugins.plugin import Plugin
-from plugins.maploader import MapLoader
-from plugins.maploader import MapSaver
-from plugins.importer import Importer
-from plugins.mapeditor import MapEditor
-from plugins.mapwizard import MapWizard
-from fifedit import Fifedit
-
-# zero-projekt plugins
-from plugins.objectedit import ObjectEdit
-from plugins.layertool import LayerTool
-
-
-# Help display
-class Help(Plugin):
-	def __init__(self):
-		self._helpWidget = None	
-		self.menu_items = { 'Help' : self.showHelp }
-
-	def showHelp(self):
-		if self._helpWidget:
-			self._helpWidget.show()
-			return
-		self._helpWidget = pychan.loadXML('gui/help.xml')
-		self._helpWidget.mapEvents({ 'closeButton' : self._helpWidget.hide })
-		self._helpWidget.distributeData({ 'helpText' : open("misc/infotext.txt").read() })
-		self._helpWidget.show()
-
-class Editor(basicapplication.ApplicationBase):
-	def __init__(self, params):
-		super(Editor,self).__init__()
-
-		# embed Fifedit tools
-		self.fifedit = Fifedit(self.engine)
-
-		# Create this client's modules
-		self.mapedit = MapEditor(self.engine)
-		self.maploader = MapLoader(self.engine)
-		self.mapsaver = MapSaver(self.engine)
-		self.mapwizard = MapWizard(self.engine)
-		self.importer = Importer(self.engine)
-
-		# zero-projekt plugins
-		self.objectedit = ObjectEdit(self.engine, self.mapedit)
-		self.layertool = LayerTool(self.engine, self.mapedit)
-
-		# Register plugins with Fifedit.
-		self.fifedit.registerPlugin(Help())
-		self.fifedit.registerPlugin(self.maploader)
-		self.fifedit.registerPlugin(self.mapsaver)
-		self.fifedit.registerPlugin(self.mapedit)
-		self.fifedit.registerPlugin(self.mapwizard)
-		self.fifedit.registerPlugin(self.importer)
-
-		# zero-projekt plugins
-		self.fifedit.registerPlugin(self.objectedit)
-		self.fifedit.registerPlugin(self.layertool)
-			
-		self.params = params
-
-	def createListener(self):
-		# override default event listener
-		return EditorListener(self)
-
-	def _pump(self):
-		self.mapedit.pump()
-		if self.maploader.newMap:
-			self.mapedit.editMap(self.maploader.newMap.getId())	
-			self.importer.addDirs(self.maploader.newMap.importDirs)
-			self.maploader.newMap = None
-		if self.mapwizard.newMap:
-			self.mapedit.editMap(self.mapwizard.map.getId())
-			self.mapwizard.newMap = False
-		if self.mapsaver.saveRequested:
-			if self.mapedit._map:
-				self.mapsaver.saveMap(self.mapedit._map,self.importer.importList)
-			else:
-				print "Cannot save, map doesn't exist"
-			self.mapsaver.saveRequested = False
-		if not self.fifedit.active: 
-			self.quitRequested = True
-		if self.params:
-			s = os.path.sep
-			parts = self.params.split(s)
-			self.maploader.loadFile(s.join(parts[0:-1]), parts[-1])
-			self.params = None
-
-		# zero-projekt plugins
-		if self.mapedit._instances is not None:
-			self.objectedit.input()
-
+import scripts
+from scripts.editor import Editor
 
 if __name__ == '__main__':
 	print sys.argv
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/__init__.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,5 @@
+# coding: utf-8
+
+"""
+This folder contains all core script files for FIFEdit.
+"""
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/editor.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,399 @@
+from basicapplication import ApplicationBase
+import pychan
+import fife
+import loaders
+import events
+import plugin
+from mapview import MapView
+from events import EventListener
+from events import *
+from gui import ToolBar, action
+from gui.action import Action, ActionGroup
+from gui.filemanager import FileManager
+from gui.mainwindow import MainWindow
+from gui.mapeditor import MapEditor
+from gui.menubar import Menu, MenuBar
+from gui.error import ErrorDialog
+from settings import Settings
+from pychan.tools import callbackWithArguments as cbwa
+import sys
+
+def getEditor():
+	""" Returns the Global editor instance """
+	if Editor.editor is None:
+		Editor(None)
+	return Editor.editor
+
+class Editor(ApplicationBase, MainWindow):
+	""" Editor sets up all subsystems and provides access to them """
+	editor = None
+
+	def __init__(self, params, *args, **kwargs):
+		Editor.editor = self
+	
+		self._filemanager = None
+	
+		self.params = params
+		self._eventlistener = None
+		self._pluginmanager = None
+		
+		self._inited = False
+		
+		self._mapview = None
+		self._mapviewList = []
+		self._mapgroup = None
+		self._mapbar = None
+		self._maparea = None
+		self._mapeditor = None
+		
+		self._fileMenu = None
+		self._editMenu = None
+		self._viewMenu = None
+		self._toolsMenu = None
+		self._helpMenu = None
+		
+		self._settings = None
+		
+		self._helpDialog = None
+	
+		ApplicationBase.__init__(self, *args, **kwargs)
+		MainWindow.__init__(self, *args, **kwargs)
+		pychan.init(self.engine, debug=False)
+		
+
+	def loadSettings(self):
+		"""
+		Load the settings from a python file and load them into the engine.
+		Called in the ApplicationBase constructor.
+		"""
+		self._settings = Settings()
+		TDS = self._settings
+		
+		glyphDft = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?-+/():;%&amp;`'*#=[]\\\""
+		engineSetting = self.engine.getSettings()
+		engineSetting.setDefaultFontGlyphs(TDS.get("FIFE", "FontGlyphs", glyphDft))
+		engineSetting.setDefaultFontPath(TDS.get("FIFE", "Font", "fonts/FreeSans.ttf"))
+		engineSetting.setDefaultFontSize(12)
+		engineSetting.setBitsPerPixel(TDS.get("FIFE", "BitsPerPixel", 0))
+		engineSetting.setInitialVolume(TDS.get("FIFE", "InitialVolume", 5.0))
+		engineSetting.setSDLRemoveFakeAlpha(TDS.get("FIFE", "SDLRemoveFakeAlpha", 1))
+		engineSetting.setScreenWidth(TDS.get("FIFE", "ScreenWidth", 1024))
+		engineSetting.setScreenHeight(TDS.get("FIFE", "ScreenHeight", 768))
+		engineSetting.setRenderBackend(TDS.get("FIFE", "RenderBackend", "OpenGL"))
+		engineSetting.setFullScreen(TDS.get("FIFE", "FullScreen", 0))
+
+		engineSetting.setWindowTitle(TDS.get("FIFE", "WindowTitle", "No window title set"))
+		engineSetting.setWindowIcon(TDS.get("FIFE", "WindowIcon", ""))
+		engineSetting.setImageChunkingSize(TDS.get("FIFE", "ImageChunkSize", 256))
+
+	def initLogging(self):
+		"""
+		Initialize the LogManager.
+		"""
+		import fifelog
+		
+		LogModules = self._settings.get("FIFE", "LogModules")
+		self.log = fifelog.LogManager(self.engine, self._settings.get("FIFE", "LogToPrompt"), self._settings.get("FIFE", "LogToFile"))
+		if LogModules:
+			self.log.setVisibleModules(*LogModules)
+		
+	def _initTools(self):
+		""" Initializes tools """
+		self._pluginmanager = plugin.PluginManager()
+		
+		self._filemanager = FileManager()
+		self._toolbar.adaptLayout()
+		self._mapeditor = MapEditor()
+		
+	def _initGui(self):
+		""" Sets up the GUI """
+		screen_width = self.engine.getSettings().getScreenWidth()
+		screen_height = self.engine.getSettings().getScreenHeight()
+		MainWindow.initGui(self, screen_width, screen_height)
+
+		self._toolbox = ToolBar(title=u"", orientation=1)
+		self._toolbox.position_technique = "explicit"
+		self._toolbox.position = (150, 150)
+		
+		self._mapbar = ToolBar(panel_size=20)
+		self._mapbar.setDocked(True)
+		
+		self._maparea = pychan.widgets.VBox()
+		self._maparea.opaque = False
+		self._maparea.is_focusable = True
+		
+		# Capture mouse and key events for EventListener
+		cw = self._maparea
+		cw.capture(self.__sendMouseEvent, "mouseEntered")
+		cw.capture(self.__sendMouseEvent, "mouseExited")
+		cw.capture(self.__sendMouseEvent, "mousePressed")
+		cw.capture(self.__sendMouseEvent, "mouseReleased")
+		cw.capture(self.__sendMouseEvent, "mouseClicked")
+		cw.capture(self.__sendMouseEvent, "mouseMoved")
+		cw.capture(self.__sendMouseEvent, "mouseWheelMovedUp")
+		cw.capture(self.__sendMouseEvent, "mouseWheelMovedDown")
+		cw.capture(self.__sendMouseEvent, "mouseDragged")
+		cw.capture(self.__sendKeyEvent, "keyPressed")
+		cw.capture(self.__sendKeyEvent, "keyReleased")
+		
+		self._centralwidget.addChild(self._mapbar)
+		self._centralwidget.addChild(self._maparea)
+		
+		self._initActions()
+		
+		self._toolbox.show()
+
+	def _initActions(self):
+		""" Initializes toolbar and menubar buttons """
+		exitAction = Action(u"Exit", "gui/icons/quit.png")
+		exitAction.helptext = u"Exit program"
+		action.activated.connect(self.quit, sender=exitAction)
+		
+		self._fileMenu = Menu(name=u"File")
+		self._fileMenu.addAction(exitAction)
+		
+		self._editMenu = Menu(name=u"Edit")
+		self._viewMenu = Menu(name=u"View")
+		self._toolsMenu = Menu(name=u"Tools")
+		self._windowMenu = Menu(name=u"Window")
+		self._helpMenu = Menu(name=u"Help")
+		
+		self._actionShowStatusbar = Action(u"Statusbar")
+		self._actionShowStatusbar.helptext = u"Toggle statusbar"
+		action.activated.connect(self.toggleStatusbar, sender=self._actionShowStatusbar)
+		
+		self._actionShowToolbar = Action(u"Toolbar")
+		self._actionShowToolbar.helptext = u"Toggle toolbar"
+		action.activated.connect(self.toggleToolbar, sender=self._actionShowToolbar)
+		
+		self._actionShowToolbox = Action(u"Tool box")
+		self._actionShowToolbox.helptext = u"Toggle tool box"
+		action.activated.connect(self.toggleToolbox, sender=self._actionShowToolbox)
+		
+		self._viewMenu.addAction(self._actionShowStatusbar)
+		self._viewMenu.addAction(self._actionShowToolbar)
+		self._viewMenu.addAction(self._actionShowToolbox)
+		self._viewMenu.addSeparator()
+	
+	
+		testAction1 = Action(u"Cycle buttonstyles", "gui/icons/cycle_styles.png")
+		testAction1.helptext = u"Cycles button styles. There are currently four button styles."
+		action.activated.connect(self._actionActivated, sender=testAction1)
+		self._viewMenu.addAction(testAction1)
+		
+		self._mapgroup = ActionGroup(exclusive=True, name="Mapgroup")
+		self._mapbar.addAction(self._mapgroup)
+		self._mapbar.addAction(ActionGroup(exclusive=True, name="Mapgroup2"))
+		self._windowMenu.addAction(self._mapgroup)
+		
+		helpAction = Action(u"Help", "gui/icons/help.png")
+		action.activated.connect(self._showHelpDialog, sender=helpAction)
+		self._helpMenu.addAction(helpAction)
+		
+		self._menubar.addMenu(self._fileMenu)
+		self._menubar.addMenu(self._editMenu)
+		self._menubar.addMenu(self._viewMenu)
+		self._menubar.addMenu(self._toolsMenu)
+		self._menubar.addMenu(self._windowMenu)
+		self._menubar.addMenu(self._helpMenu)
+	
+	def _actionActivated(self, sender):
+		self._toolbar.button_style += 1
+		
+	def _showHelpDialog(self, sender):
+		""" Shows the help dialog """
+		if self._helpDialog is not None:
+			self._helpDialog.show()
+			return
+		
+		self._helpDialog = pychan.loadXML("gui/help.xml")
+		self._helpDialog.findChild(name="closeButton").capture(self._helpDialog.hide)
+		
+		f = open('lang/infotext.txt', 'r')
+		self._helpDialog.findChild(name="helpText").text = unicode(f.read())
+		f.close()
+		
+		self._helpDialog.show()
+		
+	def toggleStatusbar(self):
+		""" Toggles status bar """
+		statusbar = self.getStatusBar()
+		if statusbar.max_size[1] > 0:
+			statusbar.min_size=(statusbar.min_size[0], 0)
+			statusbar.max_size=(statusbar.max_size[0], 0)
+			self._actionShowStatusbar.setChecked(False)
+		else:
+			statusbar.min_size=(statusbar.min_size[0], statusbar.min_size[0])
+			statusbar.max_size=(statusbar.max_size[0], statusbar.max_size[0])
+			self._actionShowStatusbar.setChecked(True)
+		statusbar.adaptLayout()
+			
+	def toggleToolbar(self):
+		""" Toggles toolbar """
+		toolbar = self.getToolBar()
+		if toolbar.isVisible():
+			toolbar.setDocked(False)
+			toolbar.hide()
+			self._actionShowToolbar.setChecked(False)
+		else: 
+			tx = toolbar.x
+			ty = toolbar.y
+			toolbar.show()
+			toolbar.x = tx
+			toolbar.y = ty
+			self._actionShowToolbar.setChecked(True)
+			
+	def toggleToolbox(self):
+		""" Toggles tool box """
+		toolbox = self.getToolbox()
+		if toolbox.isVisible():
+			toolbox.setDocked(False)
+			toolbox.hide()
+			self._actionShowToolbox.setChecked(False)
+		else:
+			tx = toolbox.x
+			ty = toolbox.y
+			toolbox.show()
+			toolbox.x = tx
+			toolbox.y = ty
+			self._actionShowToolbox.setChecked(True)
+		toolbox.adaptLayout()
+			
+	def getToolbox(self): 
+		return self._toolbox
+	
+	def getPluginManager(self): 
+		return self._pluginmanager
+		
+	def getEngine(self): 
+		return self.engine
+
+	def getMapViews(self):
+		return self._mapviewList
+		
+	def getActiveMapView(self):
+		return self._mapview
+		
+	def getSettings(self):
+		return self._settings;
+		
+	def showMapView(self, mapview):
+		""" Switches to mapview. """
+		if mapview is None or mapview == self._mapview:
+			return
+			
+		events.preMapShown.send(sender=self, mapview=mapview)
+		self._mapview = mapview
+		self._mapview.show()
+		events.postMapShown.send(sender=self, mapview=mapview)
+
+	def createListener(self):
+		""" Creates the event listener. This is called by ApplicationBase """
+		if self._eventlistener is None:
+			self._eventlistener = EventListener(self.engine)
+		
+		return self._eventlistener
+		
+	def getEventListener(self):
+		""" Returns the event listener """
+		return self._eventlistener
+	
+	def newMapView(self, map):
+		""" Creates a new map view """
+		mapview = MapView(map)
+		
+		self._mapviewList.append(mapview)
+		
+		mapAction = Action(unicode(map.getId()))
+		action.activated.connect(cbwa(self.showMapView, mapview), sender=mapAction, weak=False)
+		self._mapgroup.addAction(mapAction)
+		
+		self.showMapView(mapview)
+		
+		events.mapAdded.send(sender=self, map=map)
+		
+		return mapview
+	
+	def openFile(self, path):
+		""" Opens a file """
+		try:
+			map = loaders.loadMapFile(path, self.engine)
+			return self.newMapView(map)
+		except:
+			errormsg = u"Opening map failed:\n"
+			errormsg += u"File: "+unicode(path)+"\n"
+			errormsg += u"Error: "+unicode(sys.exc_info()[1])
+			ErrorDialog(errormsg)
+			return None
+			
+	
+	def saveAll(self):
+		""" Saves all open maps """
+		tmpView = self._mapview
+		for mapView in self._mapviewList:
+			self._mapview = mapView
+			self._filemanager.save()
+		self._mapview = tmpView
+		
+	def quit(self):
+		""" Quits the editor. An onQuit signal is sent before the application closes """
+		events.onQuit.send(sender=self)
+		
+		self._settings.saveSettings()
+		ApplicationBase.quit(self)
+
+	def _pump(self):
+		""" Called once per frame """
+		# ApplicationBase and Engine should be done initializing at this point
+		if self._inited == False:
+			self._initGui()
+			self._initTools()
+			self._inited = True
+		
+		events.onPump.send(sender=self)
+		
+	def __sendMouseEvent(self, event, **kwargs):
+		""" Function used to capture mouse events for EventListener """
+		msEvent = fife.MouseEvent
+		type = event.getType()
+		
+		if type == msEvent.MOVED:
+			mouseMoved.send(sender=self._maparea, event=event)
+			
+		elif type == msEvent.PRESSED:
+			mousePressed.send(sender=self._maparea, event=event)
+			
+		elif type == msEvent.RELEASED:
+			mouseReleased.send(sender=self._maparea, event=event)
+			
+		elif type == msEvent.WHEEL_MOVED_DOWN:
+			mouseWheelMovedDown.send(sender=self._maparea, event=event)
+			
+		elif type == msEvent.WHEEL_MOVED_UP:
+			mouseWheelMovedUp.send(sender=self._maparea, event=event)
+			
+		elif type == msEvent.CLICKED:
+			mouseClicked.send(sender=self._maparea, event=event)
+			
+		elif type == msEvent.ENTERED:
+			mouseEntered.send(sender=self._maparea, event=event)
+			
+		elif type == msEvent.EXITED:
+			mouseExited.send(sender=self._maparea, event=event)
+			
+		elif type == msEvent.DRAGGED:
+			mouseDragged.send(sender=self._maparea, event=event)
+		
+	def __sendKeyEvent(self, event, **kwargs):
+		""" Function used to capture key events for EventListener """
+		type = event.getType()
+		
+		if type == fife.KeyEvent.PRESSED:
+			self._eventlistener.keyPressed(event)
+		
+		elif type == fife.KeyEvent.RELEASED:
+			self._eventlistener.keyReleased(event)
+			
+		
+
+		
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/events/__init__.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,2 @@
+from events import *
+from signal import *
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/events/events.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,224 @@
+#from editor import getEditor # Needed to quit application
+
+import fife, scripts
+from fife import IKeyListener, ICommandListener, IMouseListener, LayerChangeListener, MapChangeListener, ConsoleExecuter
+from signal import Signal
+import pdb
+
+#--- Signals ---#
+onPump = Signal()
+preSave	= Signal(providing_args=["mapview"])
+postSave = Signal(providing_args=["mapview"])
+mapAdded = Signal(providing_args=["mapview"])
+preMapRemove = Signal(providing_args=["mapview"])
+postMapRemove = Signal(providing_args=["mapview"])
+preMapShown = Signal(providing_args=["mapview"])
+postMapShown = Signal(providing_args=["mapview"])
+onInstancesSelected = Signal(providing_args=["instances"])
+onObjectSelected = Signal(providing_args=["object"])
+
+# Signals emitted by EventListener
+onQuit			= Signal(providing_args=[])
+keyPressed		= Signal(providing_args=["event"])
+keyReleased		= Signal(providing_args=["event"])
+mouseEntered	= Signal(providing_args=["event"])
+mouseExited		= Signal(providing_args=["event"])
+mousePressed	= Signal(providing_args=["event"])
+mouseReleased	= Signal(providing_args=["event"])
+mouseClicked	= Signal(providing_args=["event"])
+mouseWheelMovedUp = Signal(providing_args=["event"])
+mouseWheelMovedDown = Signal(providing_args=["event"])
+mouseMoved		= Signal(providing_args=["event"])
+mouseDragged	= Signal(providing_args=["event"])
+onLayerChanged	= Signal(providing_args=["layer", "changedInstances"])
+onInstanceCreate = Signal(providing_args=["layer", "instance"])
+onInstanceDelete = Signal(providing_args=["layer", "instance"])
+onMapChanged	= Signal(providing_args=["map", "changedLayers"])
+onLayerCreate	= Signal(providing_args=["map", "layer"])
+onLayerDelete	= Signal(providing_args=["map", "layer"])
+onToolsClick	= Signal(providing_args=[])
+onCommand		= Signal(providing_args=["command"])
+onConsoleCommand= Signal(providing_args=["command"])
+
+class KeySequence(object):
+	def __init__(self):
+		self.key = None
+		self.modifiers = {"alt":False,"ctrl":False,"shift":False,"meta":False}
+		self.signal = None
+
+class EventListener(IKeyListener, ICommandListener, IMouseListener, LayerChangeListener, MapChangeListener, ConsoleExecuter):
+	# NOTE: As FIFEdit currently covers the entire screen with widgets,
+	#		FIFE doesn't receive any mouse or key events. Therefore we have to add
+	#		mouse and key event tracking for the central widget
+	
+	def __init__(self, engine):
+		self.engine = engine
+		
+		eventmanager = self.engine.getEventManager()
+		self.keysequences = []
+
+		IKeyListener.__init__(self)
+		eventmanager.addKeyListener(self)
+		
+		ICommandListener.__init__(self)
+		eventmanager.addCommandListener(self)
+		
+		IMouseListener.__init__(self)
+		eventmanager.addMouseListener(self)
+		
+		ConsoleExecuter.__init__(self)
+		self.engine.getGuiManager().getConsole().setConsoleExecuter(self)
+		
+		MapChangeListener.__init__(self)
+		LayerChangeListener.__init__(self)
+		
+		self.controlPressed = False
+		self.altPressed		= False
+		self.shiftPressed	= False
+		self.metaPressed	= False
+		
+	def getKeySequenceSignal(self, key, modifiers=[]):
+		# Parse modifiers
+		mods = {"alt":False,"ctrl":False,"shift":False,"meta":False}
+		for m in modifiers:
+			m = m.lower()
+			if m in mods:
+				mods[m] = True
+			else:
+				print "Unknown modifier:",m
+		
+		# Check if signal for keysequence has been created
+		for k in self.keysequences:
+			if k.key == key and k.modifiers == mods:
+				return k.signal
+				
+		# Create keysequence and signal
+		keysequence = KeySequence()
+		keysequence.key = key
+		keysequence.modifiers = mods
+		keysequence.signal = Signal(providing_args=["event"])
+		self.keysequences.append(keysequence)
+		
+		return keysequence.signal
+	
+	#--- Listener methods ---#
+	# IKeyListener
+	def keyPressed(self, evt):
+		keyval = evt.getKey().getValue()
+		keystr = evt.getKey().getAsString().lower()
+		
+		self.controlPressed = evt.isControlPressed()
+		self.altPressed		= evt.isAltPressed()
+		self.shiftPressed	= evt.isShiftPressed()
+		self.metaPressed	= evt.isMetaPressed()
+		
+		if keyval in (fife.Key.LEFT_CONTROL, fife.Key.RIGHT_CONTROL):
+			self.controlPressed = True
+		elif keyval in (fife.Key.LEFT_SHIFT, fife.Key.RIGHT_SHIFT):
+			self.shiftPressed = True
+		elif keyval in (fife.Key.LEFT_ALT, fife.Key.RIGHT_ALT):
+			self.altPressed = True
+		elif keyval in (fife.Key.RIGHT_META, fife.Key.LEFT_META):
+			self.metaPressed = True
+	
+		elif keyval == fife.Key.ESCAPE:
+			scripts.editor.getEditor().quit()
+		elif keyval == fife.Key.F10:
+			self.engine.getGuiManager().getConsole().toggleShowHide()
+		elif keystr == "d":
+			pdb.set_trace()
+			
+		# Check keysequences
+		for k in self.keysequences:
+			if k.modifiers["alt"] != self.altPressed: continue
+			if k.modifiers["ctrl"] != self.controlPressed: continue
+			if k.modifiers["shift"]	!= self.shiftPressed: continue
+			if k.modifiers["meta"] != self.metaPressed: continue
+			if keyval != k.key: continue
+			k.signal.send(sender=self, event=evt)
+			
+		keyPressed.send(sender=self.engine, event=evt)
+		
+		evt.consume()
+
+	def keyReleased(self, evt):
+		keyval = evt.getKey().getValue()
+		
+		self.controlPressed = evt.isControlPressed()
+		self.altPressed		= evt.isAltPressed()
+		self.shiftPressed	= evt.isShiftPressed()
+		self.metaPressed	= evt.isMetaPressed()
+		
+		if keyval in (fife.Key.LEFT_CONTROL, fife.Key.RIGHT_CONTROL):
+			self.controlPressed = False
+		elif keyval in (fife.Key.LEFT_SHIFT, fife.Key.RIGHT_SHIFT):
+			self.shiftPressed = False
+		elif keyval in (fife.Key.LEFT_ALT, fife.Key.RIGHT_ALT):
+			self.altPressed = False
+		elif keyval in (fife.Key.RIGHT_META, fife.Key.LEFT_META):
+			self.metaPressed = False
+			
+		keyReleased.send(sender=self.engine, event=evt)
+
+	# ICommandListener
+	def onCommand(self, command):
+		if command.getCommandType() == fife.CMD_QUIT_GAME:
+			scripts.editor.getEditor().quit()
+		else:
+			onCommand.send(sender=self.engine, command=command)
+		
+
+	# IMouseListener
+	def mouseEntered(self, evt):
+		mouseEntered.send(sender=self.engine, event=evt)
+	
+	def mouseExited(self, evt):
+		mouseExited.send(sender=self.engine, event=evt)
+	
+	def mousePressed(self, evt):
+		mousePressed.send(sender=self.engine, event=evt)
+	
+	def mouseReleased(self, evt):
+		mouseReleased.send(sender=self.engine, event=evt)
+	
+	def mouseClicked(self, evt):
+		mouseClicked.send(sender=self.engine, event=evt)
+	
+	def mouseWheelMovedUp(self, evt):
+		mouseWheelMovedUp.send(sender=self.engine, event=evt)
+	
+	def mouseWheelMovedDown(self, evt):
+		mouseWheelMovedDown.send(sender=self.engine, event=evt)
+	
+	def mouseMoved(self, evt):
+		mouseMoved.send(sender=self.engine, event=evt)
+	
+	def mouseDragged(self, evt):
+		mouseDragged.send(sender=self.engine, event=evt)
+
+	# LayerChangeListener
+	def onLayerChanged(self, layer, changedInstances):
+		onLayerChanged.send(sender=self.engine, layer=layer, changedInstances=changedInstances)
+		
+	def onInstanceCreate(self, layer, instance):
+		onInstanceCreate.send(sender=self.engine, layer=layer, instance=instance)
+		
+	def onInstanceDelete(self, layer, instance):
+		onInstanceDelete.send(sender=self.engine, layer=layer, instance=instance)
+
+	# MapChangeListener
+	def onMapChanged(self, map, changedLayers):
+		onMapChanged.send(sender=self.engine, map=map, changedLayers=changedLayers)
+		
+	def onLayerCreate(self, map, layer):
+		onLayerCreate.send(sender=self.engine, map=map, layer=layer)
+		
+	def onLayerDelete(self, map, layer):
+		onLayerDelete.send(sender=self.engine, map=map, layer=layer)
+
+	# ConsoleExecuter
+	def onToolsClick(self):
+		onToolsClick.send(sender=self.engine)
+		
+	def onConsoleCommand(self, command):
+		onConsoleCommand.send(sender=self.engine, command=command)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/events/saferef.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,250 @@
+"""
+"Safe weakrefs", originally from pyDispatcher. Copied from django v1.1 beta 1
+
+Provides a way to safely weakref any function, including bound methods (which
+aren't handled by the core weakref module).
+"""
+
+import weakref, traceback
+
+def safeRef(target, onDelete = None):
+    """Return a *safe* weak reference to a callable target
+
+    target -- the object to be weakly referenced, if it's a
+        bound method reference, will create a BoundMethodWeakref,
+        otherwise creates a simple weakref.
+    onDelete -- if provided, will have a hard reference stored
+        to the callable to be called after the safe reference
+        goes out of scope with the reference object, (either a
+        weakref or a BoundMethodWeakref) as argument.
+    """
+    if hasattr(target, 'im_self'):
+        if target.im_self is not None:
+            # Turn a bound method into a BoundMethodWeakref instance.
+            # Keep track of these instances for lookup by disconnect().
+            assert hasattr(target, 'im_func'), """safeRef target %r has im_self, but no im_func, don't know how to create reference"""%( target,)
+            reference = get_bound_method_weakref(
+                target=target,
+                onDelete=onDelete
+            )
+            return reference
+    if callable(onDelete):
+        return weakref.ref(target, onDelete)
+    else:
+        return weakref.ref( target )
+
+class BoundMethodWeakref(object):
+    """'Safe' and reusable weak references to instance methods
+
+    BoundMethodWeakref objects provide a mechanism for
+    referencing a bound method without requiring that the
+    method object itself (which is normally a transient
+    object) is kept alive.  Instead, the BoundMethodWeakref
+    object keeps weak references to both the object and the
+    function which together define the instance method.
+
+    Attributes:
+        key -- the identity key for the reference, calculated
+            by the class's calculateKey method applied to the
+            target instance method
+        deletionMethods -- sequence of callable objects taking
+            single argument, a reference to this object which
+            will be called when *either* the target object or
+            target function is garbage collected (i.e. when
+            this object becomes invalid).  These are specified
+            as the onDelete parameters of safeRef calls.
+        weakSelf -- weak reference to the target object
+        weakFunc -- weak reference to the target function
+
+    Class Attributes:
+        _allInstances -- class attribute pointing to all live
+            BoundMethodWeakref objects indexed by the class's
+            calculateKey(target) method applied to the target
+            objects.  This weak value dictionary is used to
+            short-circuit creation so that multiple references
+            to the same (object, function) pair produce the
+            same BoundMethodWeakref instance.
+
+    """
+    
+    _allInstances = weakref.WeakValueDictionary()
+    
+    def __new__( cls, target, onDelete=None, *arguments,**named ):
+        """Create new instance or return current instance
+
+        Basically this method of construction allows us to
+        short-circuit creation of references to already-
+        referenced instance methods.  The key corresponding
+        to the target is calculated, and if there is already
+        an existing reference, that is returned, with its
+        deletionMethods attribute updated.  Otherwise the
+        new instance is created and registered in the table
+        of already-referenced methods.
+        """
+        key = cls.calculateKey(target)
+        current =cls._allInstances.get(key)
+        if current is not None:
+            current.deletionMethods.append( onDelete)
+            return current
+        else:
+            base = super( BoundMethodWeakref, cls).__new__( cls )
+            cls._allInstances[key] = base
+            base.__init__( target, onDelete, *arguments,**named)
+            return base
+    
+    def __init__(self, target, onDelete=None):
+        """Return a weak-reference-like instance for a bound method
+
+        target -- the instance-method target for the weak
+            reference, must have im_self and im_func attributes
+            and be reconstructable via:
+                target.im_func.__get__( target.im_self )
+            which is true of built-in instance methods.
+        onDelete -- optional callback which will be called
+            when this weak reference ceases to be valid
+            (i.e. either the object or the function is garbage
+            collected).  Should take a single argument,
+            which will be passed a pointer to this object.
+        """
+        def remove(weak, self=self):
+            """Set self.isDead to true when method or instance is destroyed"""
+            methods = self.deletionMethods[:]
+            del self.deletionMethods[:]
+            try:
+                del self.__class__._allInstances[ self.key ]
+            except KeyError:
+                pass
+            for function in methods:
+                try:
+                    if callable( function ):
+                        function( self )
+                except Exception, e:
+                    try:
+                        traceback.print_exc()
+                    except AttributeError, err:
+                        print '''Exception during saferef %s cleanup function %s: %s'''%(
+                            self, function, e
+                        )
+        self.deletionMethods = [onDelete]
+        self.key = self.calculateKey( target )
+        self.weakSelf = weakref.ref(target.im_self, remove)
+        self.weakFunc = weakref.ref(target.im_func, remove)
+        self.selfName = str(target.im_self)
+        self.funcName = str(target.im_func.__name__)
+    
+    def calculateKey( cls, target ):
+        """Calculate the reference key for this reference
+
+        Currently this is a two-tuple of the id()'s of the
+        target object and the target function respectively.
+        """
+        return (id(target.im_self),id(target.im_func))
+    calculateKey = classmethod( calculateKey )
+    
+    def __str__(self):
+        """Give a friendly representation of the object"""
+        return """%s( %s.%s )"""%(
+            self.__class__.__name__,
+            self.selfName,
+            self.funcName,
+        )
+    
+    __repr__ = __str__
+    
+    def __nonzero__( self ):
+        """Whether we are still a valid reference"""
+        return self() is not None
+    
+    def __cmp__( self, other ):
+        """Compare with another reference"""
+        if not isinstance (other,self.__class__):
+            return cmp( self.__class__, type(other) )
+        return cmp( self.key, other.key)
+    
+    def __call__(self):
+        """Return a strong reference to the bound method
+
+        If the target cannot be retrieved, then will
+        return None, otherwise returns a bound instance
+        method for our object and function.
+
+        Note:
+            You may call this method any number of times,
+            as it does not invalidate the reference.
+        """
+        target = self.weakSelf()
+        if target is not None:
+            function = self.weakFunc()
+            if function is not None:
+                return function.__get__(target)
+        return None
+
+class BoundNonDescriptorMethodWeakref(BoundMethodWeakref):
+    """A specialized BoundMethodWeakref, for platforms where instance methods
+    are not descriptors.
+
+    It assumes that the function name and the target attribute name are the
+    same, instead of assuming that the function is a descriptor. This approach
+    is equally fast, but not 100% reliable because functions can be stored on an
+    attribute named differenty than the function's name such as in:
+
+    class A: pass
+    def foo(self): return "foo"
+    A.bar = foo
+
+    But this shouldn't be a common use case. So, on platforms where methods
+    aren't descriptors (such as Jython) this implementation has the advantage
+    of working in the most cases.
+    """
+    def __init__(self, target, onDelete=None):
+        """Return a weak-reference-like instance for a bound method
+
+        target -- the instance-method target for the weak
+            reference, must have im_self and im_func attributes
+            and be reconstructable via:
+                target.im_func.__get__( target.im_self )
+            which is true of built-in instance methods.
+        onDelete -- optional callback which will be called
+            when this weak reference ceases to be valid
+            (i.e. either the object or the function is garbage
+            collected).  Should take a single argument,
+            which will be passed a pointer to this object.
+        """
+        assert getattr(target.im_self, target.__name__) == target, \
+               ("method %s isn't available as the attribute %s of %s" %
+                (target, target.__name__, target.im_self))
+        super(BoundNonDescriptorMethodWeakref, self).__init__(target, onDelete)
+
+    def __call__(self):
+        """Return a strong reference to the bound method
+
+        If the target cannot be retrieved, then will
+        return None, otherwise returns a bound instance
+        method for our object and function.
+
+        Note:
+            You may call this method any number of times,
+            as it does not invalidate the reference.
+        """
+        target = self.weakSelf()
+        if target is not None:
+            function = self.weakFunc()
+            if function is not None:
+                # Using curry() would be another option, but it erases the
+                # "signature" of the function. That is, after a function is
+                # curried, the inspect module can't be used to determine how
+                # many arguments the function expects, nor what keyword
+                # arguments it supports, and pydispatcher needs this
+                # information.
+                return getattr(target, function.__name__)
+        return None
+
+def get_bound_method_weakref(target, onDelete):
+    """Instantiates the appropiate BoundMethodWeakRef, depending on the details of
+    the underlying class method implementation"""
+    if hasattr(target, '__get__'):
+        # target method is a descriptor, so the default implementation works:
+        return BoundMethodWeakref(target=target, onDelete=onDelete)
+    else:
+        # no luck, use the alternative implementation:
+        return BoundNonDescriptorMethodWeakref(target=target, onDelete=onDelete)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/events/signal.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,222 @@
+"""Multi-consumer multi-producer dispatching mechanism
+
+Originally based on pydispatch (BSD) http://pypi.python.org/pypi/PyDispatcher/2.0.1
+See license.txt for original license.
+
+Heavily modified for Django's purposes.
+
+Copied from django v1.1 beta 1
+Changes:
+ * Receivers aren't needed to accept any arguments
+ * _live_receivers() now work on a copy of self.receivers, which fixes a bug when
+   connecting and disconnecting during send()
+"""
+
+import weakref
+import saferef
+import pychan
+
+WEAKREF_TYPES = (weakref.ReferenceType, saferef.BoundMethodWeakref)
+
+debug = True
+
+def _make_id(target):
+    if hasattr(target, 'im_func'):
+        return (id(target.im_self), id(target.im_func))
+    return id(target)
+
+class Signal(object):
+    """Base class for all signals
+    
+    Internal attributes:
+        receivers -- { receriverkey (id) : weakref(receiver) }
+    """
+    
+    def __init__(self, providing_args=None):
+        """providing_args -- A list of the arguments this signal can pass along in
+                       a send() call.
+        """
+        self.receivers = []
+        if providing_args is None:
+            providing_args = []
+        self.providing_args = set(providing_args)
+
+    def connect(self, receiver, sender=None, weak=True, dispatch_uid=None):
+        """Connect receiver to sender for signal
+    
+        receiver -- a function or an instance method which is to
+            receive signals.  Receivers must be
+            hashable objects.
+
+            if weak is True, then receiver must be weak-referencable
+            (more precisely saferef.safeRef() must be able to create
+            a reference to the receiver).
+        
+            Receivers must be able to accept keyword arguments.
+
+            If receivers have a dispatch_uid attribute, the receiver will
+              not be added if another receiver already exists with that
+              dispatch_uid.
+
+        sender -- the sender to which the receiver should respond
+            Must either be of type Signal, or None to receive events
+            from any sender.
+
+        weak -- whether to use weak references to the receiver
+            By default, the module will attempt to use weak
+            references to the receiver objects.  If this parameter
+            is false, then strong references will be used.
+        
+        dispatch_uid -- an identifier used to uniquely identify a particular
+            instance of a receiver. This will usually be a string, though it
+            may be anything hashable.
+
+        returns None
+        """
+        
+        # If DEBUG is on, check that we got a good receiver
+        if debug:
+            import inspect
+            assert callable(receiver), "Signal receivers must be callable."
+            
+            # Check for **kwargs
+            # Not all callables are inspectable with getargspec, so we'll
+            # try a couple different ways but in the end fall back on assuming
+            # it is -- we don't want to prevent registration of valid but weird
+            # callables.
+            try:
+                argspec = inspect.getargspec(receiver)
+            except TypeError:
+                try:
+                    argspec = inspect.getargspec(receiver.__call__)
+                except (TypeError, AttributeError):
+                    argspec = None
+        
+        if dispatch_uid:
+            lookup_key = (dispatch_uid, _make_id(sender))
+        else:
+            lookup_key = (_make_id(receiver), _make_id(sender))
+
+        if weak:
+            receiver = saferef.safeRef(receiver, onDelete=self._remove_receiver)
+
+        for r_key, _ in self.receivers:
+            if r_key == lookup_key:
+                break
+        else:
+            self.receivers.append((lookup_key, receiver))
+
+    def disconnect(self, receiver=None, sender=None, weak=True, dispatch_uid=None):
+        """Disconnect receiver from sender for signal
+    
+        receiver -- the registered receiver to disconnect. May be none if
+            dispatch_uid is specified.
+        sender -- the registered sender to disconnect
+        weak -- the weakref state to disconnect
+        dispatch_uid -- the unique identifier of the receiver to disconnect
+    
+        disconnect reverses the process of connect.
+
+        If weak references are used, disconnect need not be called.
+          The receiver will be remove from dispatch automatically.
+
+        returns None
+        """
+
+        if dispatch_uid:
+            lookup_key = (dispatch_uid, _make_id(sender))
+        else:
+            lookup_key = (_make_id(receiver), _make_id(sender))
+
+        for idx, (r_key, _) in enumerate(self.receivers):
+            if r_key == lookup_key:
+                del self.receivers[idx]
+
+    def send(self, sender, **named):
+        """Send signal from sender to all connected receivers.
+
+        sender -- the sender of the signal
+            Either a specific object or None.
+    
+        named -- named arguments which will be passed to receivers.
+
+        Returns a list of tuple pairs [(receiver, response), ... ].
+
+        If any receiver raises an error, the error propagates back
+        through send, terminating the dispatch loop, so it is quite
+        possible to not have all receivers called if a raises an
+        error.
+        """
+
+        responses = []
+        if not self.receivers:
+            return responses
+
+        for receiver in self._live_receivers(_make_id(sender)):
+            response = pychan.tools.applyOnlySuitable(receiver, signal=self, sender=sender, **named)
+            responses.append((receiver, response))
+        return responses
+
+    def send_robust(self, sender, **named):
+        """Send signal from sender to all connected receivers catching errors
+
+        sender -- the sender of the signal
+            Can be any python object (normally one registered with
+            a connect if you actually want something to occur).
+
+        named -- named arguments which will be passed to receivers.
+            These arguments must be a subset of the argument names
+            defined in providing_args.
+
+        Return a list of tuple pairs [(receiver, response), ... ],
+        may raise DispatcherKeyError
+
+        if any receiver raises an error (specifically any subclass of Exception),
+        the error instance is returned as the result for that receiver.
+        """
+
+        responses = []
+        if not self.receivers:
+            return responses
+
+        # Call each receiver with whatever arguments it can accept.
+        # Return a list of tuple pairs [(receiver, response), ... ].
+        for receiver in self._live_receivers(_make_id(sender)):
+            try:
+                response = pychan.tools.applyOnlySuitable(receiver, signal=self, sender=sender, **named)
+            except Exception, err:
+                responses.append((receiver, err))
+            else:
+                responses.append((receiver, response))
+        return responses
+
+    def _live_receivers(self, senderkey):
+        """Filter sequence of receivers to get resolved, live receivers
+
+        This checks for weak references
+        and resolves them, then returning only live
+        receivers.
+        """
+        none_senderkey = _make_id(None)
+
+        for (receiverkey, r_senderkey), receiver in self.receivers[:]:
+            if r_senderkey == none_senderkey or r_senderkey == senderkey:
+                if isinstance(receiver, WEAKREF_TYPES):
+                    # Dereference the weak reference.
+                    receiver = receiver()
+                    if receiver is not None:
+                        yield receiver
+                else:
+                    yield receiver
+
+    def _remove_receiver(self, receiver):
+        """Remove dead receivers from connections."""
+
+        to_remove = []
+        for key, connected_receiver in self.receivers:
+            if connected_receiver == receiver:
+                to_remove.append(key)
+        for key in to_remove:
+            for idx, (r_key, _) in enumerate(self.receivers):
+                if r_key == key:
+                    del self.receivers[idx]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/__init__.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,4 @@
+from menubar import MenuBar
+from statusbar import StatusBar
+from toolbar import ToolBar
+from panel import Panel
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/action.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,167 @@
+from scripts.events.signal import Signal
+import pychan.internal
+
+changed = Signal(providing_args=[])
+toggled = Signal(providing_args=["toggled"])
+activated = Signal(providing_args=[])
+#triggered = Signal(providing_args=["action"])
+
+class Action:
+	def __init__(self, text="", icon="", separator=False, checkable=False, checked=False):
+		self._separator = separator
+		self._text = text
+		self._icon = icon
+		self._shortcut = ""
+		self._helptext = ""
+		self._enabled = True
+		self._checked = checked
+		self._checkable = checkable
+	
+	def __str__(self):
+		return "%s(name='%s')" % (self.__class__.__name__,self.text)
+
+	def __repr__(self):
+		return "<%s(name='%s') at %x>" % (self.__class__.__name__,self.text,id(self))
+
+	
+	def activate(self):
+		if self.isCheckable():
+			self.setChecked(not self.isChecked())
+		activated.send(sender=self)
+		
+	def _changed(self):
+		changed.send(sender=self)
+
+	def setSeparator(self, separator):
+		self._separator = separator
+		self._changed()
+	def isSeparator(self): return self._separator
+
+	def _setText(self, text): 
+		self._text = text
+		self._changed(self)
+	def _getText(self): return self._text
+	text = property(_getText, _setText)
+
+	def _setIcon(self, icon):
+		self._icon = icon
+		self._changed()
+	def _getIcon(self): return self._icon
+	icon = property(_getIcon, _setIcon)
+
+	def _setShortcut(self, keysequence): 
+		self._shortcut = keysequence
+		self._changed()
+	def _getShortcut(self): return self._shortcut
+	shortcut = property(_getShortcut, _setShortcut)
+
+	def _setHelpText(self, helptext): 
+		self._helptext = helptext
+		self._changed()
+	def _getHelpText(self): return self._helptext
+	helptext = property(_getHelpText, _setHelpText)
+
+	def setEnabled(self, enabled): 
+		self._enabled = enabled
+		self._changed()
+		
+	def isEnabled(self): 
+		return self._enabled
+
+	def setChecked(self, checked):
+		self._checked = checked
+		self._changed()
+		toggled.send(sender=self, toggled=checked)
+
+	def isChecked(self): 
+		return self._checked
+
+	def setCheckable(self, checkable): 
+		self._checkable = checkable
+		if self._checkable is False and self._checked is True:
+			self.checked = False
+			
+		self._changed()
+		
+	def isCheckable(self):
+		return self._checkable
+
+class ActionGroup:
+	def __init__(self, exclusive=False, name="actiongroup"):
+		self._exclusive = exclusive
+		self._enabled = True
+		self._actions = []
+		self.name = name
+		
+	def __str__(self):
+		return "%s(name='%s')" % (self.__class__.__name__,self.name)
+
+	def __repr__(self):
+		return "<%s(name='%s') at %x>" % (self.__class__.__name__,self.name,id(self))
+
+
+	def setEnabled(self, enabled): 
+		self._enabled = enabled
+		self._changed()
+		
+	def isEnabled(self): 
+		return self._enabled
+
+	def setExclusive(self, exclusive):
+		self._exclusive = exclusive
+		self._changed()
+		
+	def isExclusive(self):
+		return self._exclusive
+
+	def addAction(self, action):
+		if self.hasAction(action):
+			print "Actiongroup already has this action"
+			return
+		self._actions.append(action)
+		toggled.connect(self._actionToggled, sender=action)
+		self._changed()
+
+	def addSeparator(self):
+		separator = Action(separator=True)
+		self.addAction(separator)
+		self._changed()
+	
+	def getActions(self):
+		return self._actions
+	
+	def removeAction(self, action):
+		self._actions.remove(action)
+		toggled.disconnect(self._actionToggled, sender=action)
+		self._changed()
+	
+	def clear(self):
+		for action in self._actions:
+			toggled.disconnect(self._actionToggled, sender=action)
+		self._actions = []
+		self._changed()
+			
+	def hasAction(self, action):
+		for a in self._actions:
+			if a == action:
+				return True
+		return False
+	
+	def _actionToggled(self, sender):
+		if sender.isChecked() is False or self._exclusive is False:
+			return
+			
+		for a in self._actions:
+			if a != sender and a.isChecked():
+				a.setChecked(False)
+				
+	def getChecked(self):
+		for a in self._actions:
+			if a.isChecked():
+				return a
+			
+		return None
+				
+	def _changed(self):
+		changed.send(sender=self)
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/dockarea.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,159 @@
+import pychan
+from pychan import widgets
+import scripts.editor
+import fife
+from panel import Panel
+from faketabwidget import FakeTabWidget
+from resizablebase import ResizableBase
+
+class DockArea(widgets.VBox, ResizableBase):
+	def __init__(self, side, resizable=True, *args, **kwargs):
+		widgets.VBox.__init__(self, margins=(0,0,0,0), *args, **kwargs)
+		ResizableBase.__init__(self, *args, **kwargs)
+		
+		self.cursor_id = 0
+		self.cursor_type = 0
+		
+		self.vexpand=0
+		self.hexpand=0
+		
+		self.side = side
+		self.resizable_top = (side == "bottom")
+		self.resizable_left = (side == "right")
+		self.resizable_right = (side == "left")
+		self.resizable_bottom = (side == "top")
+		
+		self.gui = None
+		
+		self.buildGui()
+		self.tabwidgets = []
+		self.panels = []
+		
+	def getDockLocation(self, x, y):
+		placeAfter = None
+		placeBefore = None
+		placeIn = None
+		
+		if x >= 0 and y >= 0:
+			# See if widget was dropped on a tabwidget
+			for tabwidget in self.tabwidgets:
+				absX, absY = tabwidget.getAbsolutePos()
+					
+				if absX <= x and absY <= y \
+						and absX+tabwidget.width >= x and absY+tabwidget.height >= y:
+					# Check if the user tried to place it in front, or after this widget
+					if self.side == "left" or self.side == "right":
+						if y < absY+10:
+							placeBefore = tabwidget
+						elif y > absY+tabwidget.height-10:
+							placeAfter = tabwidget
+					else:
+						if x < absX+10:
+							placeBefore = tabwidget
+						elif x > absX+tabwidget.width-10:
+							placeAfter = tabwidget
+					if placeAfter is None and placeBefore is None:
+						placeIn = tabwidget
+					break
+					
+		return (placeIn, placeBefore, placeAfter)
+		
+	def dockChild(self, child, x, y):
+		for panel in self.panels:
+			if panel[0] == child:
+				return
+	
+		child.dockarea = self
+		child.setDocked(True)
+		
+		placeIn, placeBefore, placeAfter = self.getDockLocation(x, y)
+	
+		if placeIn is None:
+			tabwidget = FakeTabWidget(resizable=True)
+			tabwidget.hexpand=1
+			tabwidget.vexpand=1
+		
+			if self.side == "left" or self.side == "right":
+				tabwidget.resizable_bottom = True
+			else:
+				tabwidget.resizable_right = True
+			self.tabwidgets.append(tabwidget)
+			
+			if placeBefore:
+				self.gui.insertChildBefore(tabwidget, placeBefore)
+			elif placeAfter:
+				self.gui.insertChild(tabwidget, self.gui.children.index(placeAfter)+1)
+			else:
+				self.gui.addChild(tabwidget)
+		else:
+			tabwidget = placeIn
+
+		tab = tabwidget.addTab(child, child.title)
+		self.panels.append( (child, tabwidget) )
+		
+		def undock(event):
+			if event.getButton() != 2: # Right click
+				return
+				
+			self.undockChild(child)
+			
+		tab[2].capture(undock, "mouseClicked")
+		
+	def undockChild(self, child, childIsCaller=False):
+		tabwidget = None
+		for panel in self.panels:
+			if panel[0] == child:
+				tabwidget = panel[1]
+				self.panels.remove(panel)
+				break
+		else:
+			return
+			
+		tabwidget.removeTab(child)
+		if childIsCaller is False:
+			child.setDocked(False)
+		
+		if len(tabwidget.tabs) <= 0:
+			self.gui.removeChild(tabwidget)
+			self.tabwidgets.remove(tabwidget)
+			
+			# This stops a guichan exception when a widget with modul focus gets focused.
+			# It is not pretty, but crashes aren't pretty either
+			tabwidget.__del__()
+			del tabwidget
+				
+		self.adaptLayout()
+		
+	def buildGui(self):
+		if self.gui:
+			self.removeChild(self.gui)
+		
+		if self.side == "left" or self.side == "right":
+			self.gui = widgets.VBox()
+		else:
+			self.gui = widgets.HBox()
+			
+		self.gui.vexpand = 1
+		self.gui.hexpand = 1
+		
+		self.addChild(self.gui)
+	
+	def mouseReleased(self, event):
+		if self._resize:
+			if self._rLeft or self._rRight:
+				# Resize children
+				for child in self.gui.findChildren(parent=self.gui):
+					child.min_size = (self.width, child.min_size[1])
+					child.max_size = (self.width, child.max_size[1])
+					
+			if self._rTop or self._rBottom:
+				# Resize children
+				for child in self.gui.findChildren(parent=self.gui):
+					child.min_size = (child.min_size[0], self.height)
+					child.max_size = (child.max_size[0], self.height)
+					
+			self.gui.max_size = (self.width, self.height)
+			
+			ResizableBase.mouseReleased(self, event)
+			self.min_size = (0,0) # Override changes done in ResizableBase
+				
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/error.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,20 @@
+import pychan
+import pychan.widgets as widgets
+
+class ErrorDialog(object):
+	"""
+	Shows a dialog with an error message.
+	"""
+	def __init__(self, message):
+		self._widget = pychan.loadXML('gui/error.xml')
+
+		self._widget.mapEvents({
+			'okButton'     : self._widget.hide
+		})
+
+		self._widget.distributeInitialData({
+			'message' : message
+		})
+		self._widget.show()
+		self._widget.adaptLayout() # Necessary to make scrollarea work properly
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/faketabwidget.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,77 @@
+from pychan import widgets
+from pychan.tools import callbackWithArguments as cbwa
+from resizablebase import ResizableBase
+
+import scripts
+
+class FakeTabWidget(widgets.VBox, ResizableBase):
+	def __init__(self, resizable=None, *args, **kwargs):
+		if resizable == None:
+			resizable = False
+
+		widgets.VBox.__init__(self, *args, **kwargs)
+		ResizableBase.__init__(self, resizable, *args, **kwargs)
+		
+		self.tabs = []
+		
+		self.buttonbox = widgets.HBox()
+		self.widgetarea = widgets.VBox()
+		self.buttonbox.hexpand = 1
+		self.buttonbox.vexpand = 0
+		self.widgetarea.hexpand = 1
+		self.widgetarea.vexpand = 1
+		
+		self.addChild(self.buttonbox)
+		self.addChild(self.widgetarea)
+	
+		self.resizable_top = False
+		self.resizable_left = False
+		self.resizable_right = False
+		self.resizable_bottom = False
+		
+	def __del__(self):
+		# Force deletion of C++ object
+		if self.real_widget:
+			self.real_widget.__del__()
+			self.real_widget = None
+		
+	def addTab(self, widget, title):
+		for tab in self.tabs:
+			if tab[1] == widget:
+				return
+	
+		widget.max_size = (5000, 5000)
+		widget.hexpand = 1
+		widget.vexpand = 1
+	
+		button = widgets.ToggleButton(text=title, group="faketab_"+str(id(self)))
+		self.buttonbox.addChild(button)
+		
+		tab = (title, widget, button)
+		self.tabs.append( tab )
+
+		button.capture(cbwa(self.showTab, tab))
+		self.showTab(tab)
+		
+		return tab
+		
+	def removeTab(self, widget):
+		for i, tab in enumerate(self.tabs):
+			if tab[1] == widget:
+				if widget.parent == self.widgetarea:
+					self.widgetarea.removeChild(widget)
+				self.buttonbox.removeChild(tab[2])
+				del self.tabs[i]
+				break
+		else: return
+			
+		if len(self.tabs) > 0:
+			self.showTab(self.tabs[0])
+		
+	def showTab(self, tab):
+		tab[2].toggled = True
+		self.widgetarea.removeAllChildren()
+		self.widgetarea.addChild(tab[1])
+		self.widgetarea.adaptLayout()
+		
+	
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/filemanager.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,223 @@
+import math, fife, pychan, filebrowser
+import loaders, savers
+import action
+import scripts.editor
+import pychan.widgets as widgets
+
+from action import Action, ActionGroup
+from input import InputDialog
+from selection import SelectionDialog, ClickSelectionDialog
+
+class FileManager(object):
+	def __init__(self):
+		self.editor = scripts.editor.getEditor()
+		self.engine = self.editor.getEngine()
+		self._map = None
+		self._layer = None
+		self._mapdlg = None
+		self._layerdlg = None
+		self._cameradlg = None
+		
+		self._filebrowser = None
+		self._savebrowser = None
+
+		newAction = Action(u"New map", "gui/icons/new_map.png")
+		loadAction = Action(u"Open", "gui/icons/load_map.png")
+		saveAction = Action(u"Save", "gui/icons/save_map.png")
+		saveAsAction = Action(u"Save as", "gui/icons/save_mapas.png")
+		saveAllAction = Action(u"Save all", "gui/icons/save_allmaps.png")
+		
+		newAction.helptext = u"Create new map"
+		loadAction.helptext = u"Open existing map"
+		saveAction.helptext = u"Save map"
+		saveAsAction.helptext = u"Save map as"
+		saveAllAction.helptext = u"Save all opened maps"
+		
+		action.activated.connect(self.showMapWizard, sender=newAction)
+		action.activated.connect(self.showLoadDialog, sender=loadAction)
+		action.activated.connect(self.save, sender=saveAction)
+		action.activated.connect(self.saveAs, sender=saveAsAction)
+		action.activated.connect(self.editor.saveAll, sender=saveAllAction)
+		
+		eventlistener = self.editor.getEventListener()
+		eventlistener.getKeySequenceSignal(fife.Key.N, ["ctrl"]).connect(self.showMapWizard)
+		eventlistener.getKeySequenceSignal(fife.Key.O, ["ctrl"]).connect(self.showLoadDialog)
+		eventlistener.getKeySequenceSignal(fife.Key.S, ["ctrl"]).connect(self.save)
+		eventlistener.getKeySequenceSignal(fife.Key.S, ["ctrl", "shift"]).connect(self.editor.saveAll)
+		
+		fileGroup = ActionGroup()
+		fileGroup.addAction(newAction)
+		fileGroup.addAction(loadAction)
+		fileGroup.addAction(saveAction)
+		fileGroup.addAction(saveAsAction)
+		fileGroup.addAction(saveAllAction)
+		
+		self.editor.getToolBar().insertAction(fileGroup, 0)
+		self.editor.getToolBar().insertSeparator(None, 1)
+		self.editor._fileMenu.insertAction(fileGroup, 0)
+		self.editor._fileMenu.insertSeparator(None, 1)
+
+	def showLoadDialog(self):
+		if self._filebrowser is None:
+			self._filebrowser = filebrowser.FileBrowser(self.engine, self.loadFile, extensions = loaders.fileExtensions)
+		self._filebrowser.showBrowser()
+		
+	def showSaveDialog(self):
+		if self._savebrowser is None:
+			self._savebrowser = filebrowser.FileBrowser(self.engine, self.saveFile, savefile=True, extensions = loaders.fileExtensions)
+		self._savebrowser.showBrowser()
+
+	def saveFile(self, path, filename):
+		mapview = self.editor.getActiveMapView()
+		if mapview is None:
+			print "No map is open"
+			return
+			
+		fname = '/'.join([path, filename])
+		mapview.saveAs(fname)
+		
+	def saveAs(self):
+		mapview = self.editor.getActiveMapView()
+		if mapview is None:
+			print "No map is open"
+			return
+		self.showSaveDialog(self)
+		
+	def loadFile(self, path, filename):
+		self.editor.openFile('/'.join([path, filename]))
+		
+	def showMapWizard(self):
+		if self._cameradlg:
+			self._cameradlg._widget.show()
+		elif self._layerdlg:
+			self._layerdlg._widget.show()
+		elif self._mapdlg:
+			self._mapdlg._widget.show()
+		else:
+			self._newMap()
+
+	def _newMap(self):
+		self._mapdlg = InputDialog(u'Enter a map identifier:', self._newLayer, self._clean)
+		
+	def _newLayer(self, mapId):
+		self._map = self.engine.getModel().createMap(str(mapId))
+		self._layerdlg = InputDialog(u'Enter a layer identifier for a default layer:', self._newCamera, self._clean)
+		
+	def _newCamera(self, layerId):
+		grid = fife.SquareGrid()
+		layer = self._map.createLayer(str(layerId), grid)
+		grid.thisown = 0
+
+		self._cameradlg = CameraEditor(self.engine, self._addMap, self._clean, self._map, self._layer)
+		
+	def _addMap(self):
+		self.editor.newMapView(self._map)
+		self._clean()
+		
+	def _clean(self):
+		self._mapdlg = None
+		self._layerdlg = None
+		self._cameradlg = None
+		self._map = None
+		self._layer = None
+
+	def save(self):
+		curname = None
+		mapview = self.editor.getActiveMapView()
+		if mapview is None:
+			print "No map is open"
+			return
+		
+		try:
+			curname = mapview.getMap().getResourceLocation().getFilename()
+		except RuntimeError:
+			self.showSaveDialog()
+			return
+		
+		mapview.save()
+
+class CameraEditor(object):
+	"""
+	CameraEditor provides a gui dialog for camera creation. The callback is called when camera creation is complete. A
+	partial specification of the camera parameters may optionally be given.
+	"""
+	def __init__(self, engine, callback=None, onCancel=None, map=None, layer=None):
+		self.engine = engine
+		self.callback = callback
+		self.onCancel = onCancel
+		self._widget = pychan.loadXML('gui/cameraedit.xml')
+
+		if map:
+			self._widget.distributeData({
+				'mapBox'	: unicode(map.getId()),
+			})
+
+		if layer:
+			self._widget.distributeData({
+				'layerBox'	: unicode(layer.getId()),
+			})
+
+		self._widget.mapEvents({
+			'okButton'     : self._finished,
+			'cancelButton' : self._cancelled
+		})
+
+		self._widget.show()
+		
+	def _cancelled(self):
+		if self.onCancel:
+			self.onCancel()
+		self._widget.hide()
+		
+
+	def _finished(self):
+		id = self._widget.collectData('idBox')
+		if id == '':
+			print 'Please enter a camera id.'
+			return
+
+		try:
+			map = self.engine.getModel().getMap(str(self._widget.collectData('mapBox')))
+		except fife.Exception:
+			print 'Cannot find the specified map id.'
+			return
+
+		try:
+			layer = map.getLayer(str(self._widget.collectData('layerBox')))
+		except fife.Exception:
+			print 'Cannot find the specified layer id.'	
+			return
+
+		try:
+			vals = self._widget.collectData('viewBox').split(',')
+			if len(vals) != 4:
+				raise ValueError	
+
+			viewport = fife.Rect(*[int(c) for c in vals])
+		except ValueError:
+			print 'Please enter 4 comma (,) delimited values for viewport x,y,width,height.'
+			return
+
+		try:
+			refh = int(self._widget.collectData('refhBox'))
+			refw = int(self._widget.collectData('refwBox'))
+		except ValueError:
+			print 'Please enter positive integer values for reference width and height.'
+			return
+
+		try:
+			rot = int(self._widget.collectData('rotBox'))
+			tilt = int(self._widget.collectData('tiltBox'))
+		except ValueError:
+			print 'Please enter positive integer values for rotation and tilt.'
+			return
+
+		cam = self.engine.getView().addCamera(str(id), layer, viewport, fife.ExactModelCoordinate(0,0,0))
+		cam.setCellImageDimensions(refw, refh)
+		cam.setRotation(rot)
+		cam.setTilt(tilt)
+	
+		self._widget.hide()
+
+		if self.callback:
+			self.callback()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/input.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,32 @@
+import pychan
+import pychan.widgets as widgets
+
+class InputDialog(object):
+	"""
+	Input supplies a text box for entering data. The result is passed to onEntry.
+	onEntry - the function to call when a input is complete. Accepts one argument: a string of text.
+	"""
+	def __init__(self, prompt, onEntry, onCancel):
+		self._callback = onEntry
+		self._cancelCallback = onCancel
+
+		self._widget = pychan.loadXML('gui/input.xml')
+
+		self._widget.mapEvents({
+			'okButton'     : self._complete,
+			'cancelButton' : self._cancel
+		})
+
+		self._widget.distributeInitialData({
+			'prompt' : prompt
+		})
+		self._widget.show()
+
+	def _complete(self):
+		self._callback(self._widget.collectData('inputBox'))
+		self._widget.hide()
+		
+	def _cancel(self):
+		self._cancelCallback()
+		self._widget.hide()
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/mainwindow.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,284 @@
+import pychan
+from menubar import MenuBar, Menu
+from panel import Panel
+from dockarea import DockArea
+import toolbar
+from toolbar import ToolBar
+from statusbar import StatusBar
+import fife
+
+DOCKAREA = {
+	'left'	: 'left',
+	'right' : 'right',
+	'top'	: 'top',
+	'bottom': 'bottom'
+}
+
+class MainWindow(object):
+
+	def __init__(self, *args, **kwargs):
+		self._toolbar = None
+		self._menubar = None
+		self._statusbar = None
+	
+		self._rootwidget = None
+		self._centralwidget = None
+		self._dockareas = {
+				DOCKAREA['left']:None, 
+				DOCKAREA['right']:None, 
+				DOCKAREA['top']:None, 
+				DOCKAREA['bottom']:None
+			}
+			
+		self._toolbarareas = {
+				DOCKAREA['left']:None, 
+				DOCKAREA['right']:None, 
+				DOCKAREA['top']:None, 
+				DOCKAREA['bottom']:None
+			}
+			
+		self.dockareamarker = None
+			
+	def initGui(self, screen_width, screen_height):
+		bar_height = 30
+		
+		self._statusbar = StatusBar(text=u"", panel_size=bar_height)
+		self._toolbar = ToolBar(title=u"Toolbar", button_style=0)
+		self._menubar = MenuBar(min_size=(screen_width, bar_height), position=(0, 0))
+		
+		# Set up root widget
+		self._rootwidget = pychan.widgets.VBox(padding=0, vexpand=1, hexpand=1)
+		self._rootwidget.min_size = \
+		self._rootwidget.max_size = (screen_width, screen_height)
+		self._rootwidget.opaque = False
+		
+		self._dockareas[DOCKAREA['left']] = DockArea("left")
+		self._dockareas[DOCKAREA['right']] = DockArea("right")
+		self._dockareas[DOCKAREA['top']] = DockArea("top")
+		self._dockareas[DOCKAREA['bottom']] = DockArea("bottom")
+
+		self._toolbarareas[DOCKAREA['left']] = pychan.widgets.VBox(margins=(0,0,0,0))
+		self._toolbarareas[DOCKAREA['right']] = pychan.widgets.VBox(margins=(0,0,0,0))
+		self._toolbarareas[DOCKAREA['top']] = pychan.widgets.HBox(margins=(0,0,0,0))
+		self._toolbarareas[DOCKAREA['bottom']] = pychan.widgets.HBox(margins=(0,0,0,0))
+		
+		# This is where the map will be displayed
+		self._centralwidget = pychan.widgets.VBox(vexpand=1, hexpand=1)
+		self._centralwidget.opaque = False
+		
+		middle = pychan.widgets.HBox(padding=0, vexpand=1, hexpand=1)
+		middle.opaque = False
+		
+		# Pychan bug? Adding a spacer instead of a container creates
+		# a gap after the right dockarea
+		middle.addChild(self._toolbarareas['left'])
+		middle.addChild(self._dockareas['left'])
+		middle.addChild(self._centralwidget)
+		#middle.addSpacer(pychan.widgets.Spacer())
+		middle.addChild(self._dockareas['right'])
+		middle.addChild(self._toolbarareas['right'])
+		
+		self._rootwidget.addChild(self._menubar)
+		#self._rootwidget.addChild(self._toolbar)
+		self._rootwidget.addChild(self._toolbarareas['top'])
+		self._rootwidget.addChild(self._dockareas['top'])
+		self._rootwidget.addChild(middle)
+		self._rootwidget.addChild(self._dockareas['bottom'])
+		self._rootwidget.addChild(self._toolbarareas['bottom'])
+		self._rootwidget.addChild(self._statusbar)
+
+		self._toolbar.setDocked(True)
+		self.dockWidgetTo(self._toolbar, "top")
+		
+		self._rootwidget.show()
+		
+	def getCentralWidget(self):
+		return self._centralwidget
+	
+	def getStatusBar(self): 
+		return self._statusbar
+		
+	def getMenuBar(self):
+		return self._menubar
+	
+	def getToolBar(self): 
+		return self._toolbar
+	
+	def dockWidgetTo(self, widget, dockarea, x=-1, y=-1):
+		if isinstance(widget, pychan.widgets.Widget) is False:
+			print "Argument is not a valid widget"
+			return
+			
+		if widget.parent:
+			widgetParent = widget.parent
+			widgetParent.removeChild(widget)
+			widgetParent.adaptLayout()
+			
+		# We must hide the widget before adding it to the dockarea,
+		# or we will get a duplicate copy of the widget in the top left corner
+		# of screen.
+		widget.hide() 
+		dockareas = self._dockareas
+		if isinstance(widget, ToolBar):
+			dockareas = self._toolbarareas
+			if dockarea == DOCKAREA['left'] or dockarea == DOCKAREA['right']:
+				widget.setOrientation(ToolBar.ORIENTATION["Vertical"])
+			elif dockarea == DOCKAREA['top'] or dockarea == DOCKAREA['bottom']:
+				widget.setOrientation(ToolBar.ORIENTATION["Horizontal"])
+	
+		if isinstance(widget, ToolBar):
+			if dockarea == DOCKAREA['left']:
+				widget.setDocked(True)
+				dockareas[DOCKAREA['left']].addChild(widget)
+				dockareas[DOCKAREA['left']].adaptLayout()
+				
+			elif dockarea == DOCKAREA['right']:
+				widget.setDocked(True)
+				dockareas[DOCKAREA['right']].addChild(widget)
+				dockareas[DOCKAREA['right']].adaptLayout()
+				
+			elif dockarea == DOCKAREA['top']:
+				widget.setDocked(True)
+				dockareas[DOCKAREA['top']].addChild(widget)
+				dockareas[DOCKAREA['top']].adaptLayout()
+				
+			elif dockarea == DOCKAREA['bottom']:
+				widget.setDocked(True)
+				dockareas[DOCKAREA['bottom']].addChild(widget)
+				dockareas[DOCKAREA['bottom']].adaptLayout()
+				
+			else:
+				print "Invalid dockarea"
+		else:
+			if dockarea == DOCKAREA['left']:
+				dockareas[DOCKAREA['left']].dockChild(widget, x, y)
+				
+			elif dockarea == DOCKAREA['right']:
+				dockareas[DOCKAREA['right']].dockChild(widget, x, y)
+				
+			elif dockarea == DOCKAREA['top']:
+				dockareas[DOCKAREA['top']].dockChild(widget, x, y)
+				
+			elif dockarea == DOCKAREA['bottom']:
+				dockareas[DOCKAREA['bottom']].dockChild(widget, x, y)
+				
+			else:
+				print "Invalid dockarea"
+				
+	def getToolbarAreaAt(self, x, y, mark=False):
+		if self.dockareamarker is None:
+			self.dockareamarker = pychan.widgets.Container()
+			self.dockareamarker.base_color = fife.Color(200, 0, 0, 100)
+		if mark is False:
+			self.dockareamarker.hide()
+	
+		# Mouse wasn't over any dockwidgets. See if it is near any edge of the screen instead
+		if x <= self._toolbarareas["left"].getAbsolutePos()[0]+10:
+			if mark:
+				self.dockareamarker.position = self._toolbarareas["left"].getAbsolutePos()
+				self.dockareamarker.size = (10, self._toolbarareas["left"].height)
+				self.dockareamarker.show()
+			return DOCKAREA["left"]
+			
+		elif x >= self._toolbarareas["right"].getAbsolutePos()[0]-10:
+			if mark:
+				self.dockareamarker.position = self._toolbarareas["right"].getAbsolutePos()
+				self.dockareamarker.size = (10, self._toolbarareas["right"].height)
+				self.dockareamarker.x -= 10
+				self.dockareamarker.show()
+			return DOCKAREA["right"]
+			
+		elif y <= self._toolbarareas["top"].getAbsolutePos()[1]+10:
+			if mark:
+				self.dockareamarker.position = self._toolbarareas["top"].getAbsolutePos()
+				self.dockareamarker.size = (self._toolbarareas["top"].width, 10)
+				self.dockareamarker.show()
+			return DOCKAREA["top"]
+			
+		elif y >= self._toolbarareas["bottom"].getAbsolutePos()[1]-10:
+			if mark:
+				self.dockareamarker.position = self._toolbarareas["bottom"].getAbsolutePos()
+				self.dockareamarker.y -= 10
+				self.dockareamarker.size = (self._toolbarareas["bottom"].width, 10)
+				self.dockareamarker.show()
+			return DOCKAREA["bottom"]
+
+		if mark is True:
+			self.dockareamarker.hide()
+		return None
+			
+	def getDockAreaAt(self, x, y, mark=False):
+		if self.dockareamarker is None:
+			self.dockareamarker = pychan.widgets.Container()
+			self.dockareamarker.base_color = fife.Color(200, 0, 0, 100)
+		if mark is False:
+			self.dockareamarker.hide()
+	
+		for key in DOCKAREA:
+			side = DOCKAREA[key]
+			
+			dockarea = self._dockareas[side]
+			#absX, absY = dockarea.getAbsolutePos()
+			#if absX <= x and absY <= y \
+			#		and absX+dockarea.width >= x and absX+dockarea.height >= y:
+			#	return side
+			placeIn, placeBefore, placeAfter = dockarea.getDockLocation(x, y)
+			if placeIn or placeBefore or placeAfter:
+				if mark is True:
+					if placeIn:
+						self.dockareamarker.position = placeIn.getAbsolutePos()
+						self.dockareamarker.size = placeIn.size
+					elif placeBefore:
+						self.dockareamarker.position = placeBefore.getAbsolutePos()
+						if side == "left" or side == "right":
+							self.dockareamarker.size = (placeBefore.width, 10)
+						else:
+							self.dockareamarker.size = (10, placeBefore.height)
+					elif placeAfter:
+						self.dockareamarker.position = placeAfter.getAbsolutePos()
+						
+						if side == "left" or side == "right":
+							self.dockareamarker.size = (placeAfter.width, 10)
+							self.dockareamarker.y += placeAfter.height-10
+						else:
+							self.dockareamarker.size = (10, placeAfter.height)
+							self.dockareamarker.x += placeAfter.width-10
+						
+					self.dockareamarker.show()
+				return side
+			
+
+		# Mouse wasn't over any dockwidgets. See if it is near any edge of the screen instead
+		if x <= self._dockareas["left"].getAbsolutePos()[0]+10:
+			if mark:
+				self.dockareamarker.position = self._dockareas["left"].getAbsolutePos()
+				self.dockareamarker.size = (10, self._dockareas["left"].height)
+				self.dockareamarker.show()
+			return DOCKAREA["left"]
+			
+		elif x >= self._dockareas["right"].getAbsolutePos()[0]-10:
+			if mark:
+				self.dockareamarker.position = self._dockareas["right"].getAbsolutePos()
+				self.dockareamarker.size = (10, self._dockareas["right"].height)
+				self.dockareamarker.x -= 10
+				self.dockareamarker.show()
+			return DOCKAREA["right"]
+			
+		elif y <= self._dockareas["top"].getAbsolutePos()[1]+10:
+			if mark:
+				self.dockareamarker.position = self._dockareas["top"].getAbsolutePos()
+				self.dockareamarker.size = (self._dockareas["top"].width, 10)
+				self.dockareamarker.show()
+			return DOCKAREA["top"]
+			
+		elif y >= self._dockareas["bottom"].getAbsolutePos()[1]-10:
+			if mark:
+				self.dockareamarker.position = self._dockareas["bottom"].getAbsolutePos()
+				self.dockareamarker.y -= 10
+				self.dockareamarker.size = (self._dockareas["bottom"].width, 10)
+				self.dockareamarker.show()
+			return DOCKAREA["bottom"]
+
+		if mark is True:
+			self.dockareamarker.hide()
+		return None
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/mapeditor.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,470 @@
+# MapEditor is a plugin for Fifedit. It allows for selection and visual editing of maps.
+
+import math
+
+import fife
+import pychan
+import pychan.widgets as widgets
+import scripts
+import scripts.events as events
+import action
+from toolbar import ToolBar
+from menubar import Menu, MenuBar
+from action import Action, ActionGroup
+from scripts.mapcontroller import MapController
+
+states = (u'SELECTING', u'INSERTING', u'REMOVING', u'MOVING', u'OBJECTPICKER')
+for s in states:
+	globals()[s] = s
+NOT_INITIALIZED = -9999999
+
+class MapEditor:
+	def __init__(self):	
+		self._ignoreToggles = False # A hack to avoid infinite recursion when toggling a button
+		self._controller = None
+		self._mode = SELECTING
+		
+		self._editor = scripts.editor.getEditor()
+		self._eventlistener = self._editor.getEventListener()
+		self._statusbar = self._editor.getStatusBar()
+		self._toolbar = self._editor.getToolBar()
+		self._object = None
+		self._startDragPos = None
+		self._lastDragPos = None
+		self._lastDragPosExact = None
+		
+		self._toolbox = self._editor.getToolbox()
+		
+		self._initToolbuttons()
+		
+		self._toolbox.show()
+		self._instances = []
+		
+		events.postMapShown.connect(self._mapChanged)
+		events.onObjectSelected.connect(self.setObject)
+		self._undogroup = False
+		
+		self._scrollX = 0
+		self._scrollY = 0
+		
+	def _init(self):
+		self._dragx = NOT_INITIALIZED
+		self._dragy = NOT_INITIALIZED
+		
+		self._setMode(SELECTING)
+		
+		self._initToolbarbuttons()
+		
+		events.keyPressed.connect(self.keyPressed)
+		events.keyReleased.connect(self.keyReleased)
+		
+		events.mousePressed.connect(self.mousePressed)
+		events.mouseDragged.connect(self.mouseDragged)
+		events.mouseReleased.connect(self.mouseReleased)
+		events.mouseMoved.connect(self.mouseMoved)
+		events.mouseWheelMovedUp.connect(self.mouseWheelMovedUp)
+		events.mouseWheelMovedDown.connect(self.mouseWheelMovedDown)
+		events.mouseExited.connect(self.mouseExited)
+		events.onPump.connect(self.pump)
+		
+	def _clear(self):
+		self._clearToolbarButtons()
+		
+		events.keyPressed.disconnect(self.keyPressed)
+		events.keyReleased.disconnect(self.keyReleased)
+		
+		events.mousePressed.disconnect(self.mousePressed)
+		events.mouseDragged.disconnect(self.mouseDragged)
+		events.mouseReleased.disconnect(self.mouseReleased)
+		events.mouseMoved.disconnect(self.mouseMoved)
+		events.mouseWheelMovedUp.disconnect(self.mouseWheelMovedUp)
+		events.mouseWheelMovedDown.disconnect(self.mouseWheelMovedDown)
+		events.mouseExited.disconnect(self.mouseExited)
+		events.onPump.disconnect(self.pump)
+		
+	def _mapChanged(self, sender, mapview):
+		self.setController(mapview.getController())
+		
+	def setObject(self, object):
+		self._object = object
+		
+	def setController(self, controller):
+		if self._controller is not None:
+			self._clear()
+
+		if controller is not None:
+			self._init()
+			
+		self._controller = controller
+		
+	def _initToolbuttons(self):
+		self._selectAction = Action(text=u"Select", icon="gui/icons/select_instance.png", checkable=True)
+		self._drawAction = Action(text=u"Draw", icon="gui/icons/add_instance.png", checkable=True)
+		self._removeAction = Action(text=u"Remove", icon="gui/icons/erase_instance.png", checkable=True)
+		self._moveAction = Action(text=u"Move", icon="gui/icons/move_instance.png", checkable=True)
+		self._objectpickerAction = Action(text=u"Pick object", icon="gui/icons/objectpicker.png", checkable=True)
+		
+		self._selectAction.helptext = u"Select cells on layer"
+		self._moveAction.helptext = u"Moves instances"
+		self._drawAction.helptext = u"Adds new instances based on currently selected object"
+		self._removeAction.helptext = u"Deletes instances"
+		self._objectpickerAction.helptext = u"Click an instance to set the current object to the one used by instance"
+		
+		action.toggled.connect(self._buttonToggled, sender=self._selectAction)
+		action.toggled.connect(self._buttonToggled, sender=self._moveAction)
+		action.toggled.connect(self._buttonToggled, sender=self._drawAction)
+		action.toggled.connect(self._buttonToggled, sender=self._removeAction)
+		action.toggled.connect(self._buttonToggled, sender=self._objectpickerAction)
+		
+		self._toolgroup = ActionGroup(exclusive=True, name=u"Tool group")
+		self._toolgroup.addAction(self._selectAction)
+		self._toolgroup.addAction(self._moveAction)
+		self._toolgroup.addAction(self._drawAction)
+		self._toolgroup.addAction(self._removeAction)
+		self._toolgroup.addAction(self._objectpickerAction)
+		
+		
+		self._toolbox.addAction(self._toolgroup)
+		self._toolbox.adaptLayout()
+		
+		self._editor._editMenu.addAction(self._toolgroup)
+		
+	def _initToolbarbuttons(self):
+		rotateLeftAction = Action(text=u"Rotate counterclockwise", icon="gui/icons/rotate_countercw.png")
+		rotateRightAction = Action(text=u"Rotate clockwise", icon="gui/icons/rotate_clockwise.png")
+		zoomInAction = Action(text=u"Zoom in", icon="gui/icons/zoom_in.png")
+		zoomOutAction = Action(text=u"Zoom out", icon="gui/icons/zoom_out.png")
+		zoomResetAction = Action(text=u"Reset zoom", icon="gui/icons/zoom_default.png")
+		
+		action.activated.connect(self.rotateCounterClockwise, sender=rotateLeftAction)
+		action.activated.connect(self.rotateClockwise, sender=rotateRightAction)
+		action.activated.connect(self.zoomIn, sender=zoomInAction)
+		action.activated.connect(self.zoomOut, sender=zoomOutAction)
+		action.activated.connect(self.resetZoom, sender=zoomResetAction)
+	
+		self._viewGroup = ActionGroup(name=u"View group")
+		self._viewGroup.addAction(rotateLeftAction)
+		self._viewGroup.addAction(rotateRightAction)
+		self._viewGroup.addAction(zoomInAction)
+		self._viewGroup.addAction(zoomOutAction)
+		self._viewGroup.addAction(zoomResetAction)
+		
+		self._toolbar.addAction(self._viewGroup)
+		self._toolbar.adaptLayout()
+		
+	def _clearToolbarButtons(self):
+		self._toolbar.removeAction(self._viewGroup)
+		self._toolbar.adaptLayout()
+		self._viewGroup = None
+		
+		
+	def _setMode(self, mode):
+		if (mode == INSERTING) and (not self._object):
+			self._statusbar.setText(u'Please select object first')
+			mode = self._mode
+
+		self._ignoreToggles = True
+		# Update toolbox buttons
+		if (mode == INSERTING):
+			self._drawAction.setChecked(True)
+		elif mode == REMOVING:
+			self._removeAction.setChecked(True)
+		elif mode == MOVING:
+			self._moveAction.setChecked(True)
+		elif mode == OBJECTPICKER:
+			self._objectpickerAction.setChecked(True)
+		else:
+			self._selectAction.setChecked(True)
+		self._ignoreToggles = False
+
+		self._mode = mode
+		print "Entered mode " + mode
+		self._statusbar.setText(mode.replace('_', ' ').capitalize())
+		
+	def _buttonToggled(self, sender, toggled):
+		if self._controller is None: return
+		if self._ignoreToggles is True: return
+	
+		mode = SELECTING
+		
+		if toggled:
+			if sender == self._selectAction:
+				mode = SELECTING
+			elif sender == self._moveAction:
+				mode = MOVING
+			elif sender == self._drawAction:
+				mode = INSERTING
+			elif sender == self._removeAction:
+				mode = REMOVING
+			elif sender == self._objectpickerAction:
+				mode = OBJECTPICKER
+
+		self._setMode(mode)
+		
+	def mouseExited(self, sender, event):
+		pass
+
+
+	def mousePressed(self, sender, event):
+		if event.isConsumedByWidgets():
+			return
+
+		realCoords = self._getRealCoords(sender, event)
+
+		if event.getButton() == fife.MouseEvent.MIDDLE:
+			self._dragx = realCoords[0]
+			self._dragy = realCoords[1]
+			
+		else:
+			if event.getButton() == fife.MouseEvent.RIGHT:
+				self._controller.deselectSelection()
+				
+			if self._mode == SELECTING:
+				if event.getButton() == fife.MouseEvent.LEFT:
+					if self._eventlistener.shiftPressed:
+						self._controller.deselectCell(realCoords[0], realCoords[1])
+					else:
+						if self._eventlistener.controlPressed is False:
+							self._controller.deselectSelection()
+						self._controller.selectCell(realCoords[0], realCoords[1])
+					
+				elif event.getButton() == fife.MouseEvent.RIGHT:
+					self._controller.deselectSelection()
+					
+			elif self._mode == INSERTING:
+				if event.getButton() == fife.MouseEvent.LEFT:
+					self._controller.deselectSelection()
+					self._controller.selectCell(realCoords[0], realCoords[1])
+					self._controller.getUndoManager().startGroup("Inserted instances")
+					self._undogroup = True
+					
+					position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)
+					position = self._controller._layer.getCellGrid().toLayerCoordinates(position)
+					
+					self._controller.selectCell(realCoords[0], realCoords[1])
+					self._controller.placeInstance(position, self._object)
+				
+			elif self._mode == REMOVING:
+				if event.getButton() == fife.MouseEvent.LEFT:
+					self._controller.deselectSelection()
+					self._controller.selectCell(realCoords[0], realCoords[1])
+					self._controller.getUndoManager().startGroup("Removed instances")
+					self._undogroup = True
+					
+					self._controller.removeInstances(self._controller.getInstancesFromSelection())
+				
+			elif self._mode == MOVING:
+				if event.getButton() == fife.MouseEvent.LEFT:
+				
+					position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)
+
+					self._lastDragPos = self._controller._layer.getCellGrid().toLayerCoordinates(position)
+					self._lastDragPosExact = self._controller._layer.getCellGrid().toExactLayerCoordinates(position)
+	
+					for loc in self._controller._selection:
+						if loc.getLayerCoordinates() == self._lastDragPos:
+							break
+					else:
+						self._controller.deselectSelection()
+						self._controller.selectCell(realCoords[0], realCoords[1])
+						
+					self._instances = self._controller.getInstancesFromSelection()
+					
+					self._controller.getUndoManager().startGroup("Moved instances")
+					self._undogroup = True
+					
+			elif self._mode == OBJECTPICKER:
+				position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)
+				exact = self._controller._layer.getCellGrid().toExactLayerCoordinates(position)
+				instances = self._controller.getInstancesFromPosition(exact)
+				if len(instances) >= 1:
+					object = instances[0].getObject()
+					if object.getId() != self._object.getId() or object.getNamespace() != self._object.getNamespace():
+						events.onObjectSelected.send(sender=self, object=object)
+
+	def mouseDragged(self, sender, event):
+		if event.isConsumedByWidgets():
+			return
+			
+		realCoords = self._getRealCoords(sender, event)
+			
+		if event.getButton() == fife.MouseEvent.MIDDLE:
+			self._scrollX = (self._dragx-realCoords[0])/10.0
+			self._scrollY = (self._dragy-realCoords[1])/10.0
+		else:
+			if self._mode != SELECTING:
+				self._controller.deselectSelection()
+				
+			if self._mode == SELECTING:
+				if event.getButton() == fife.MouseEvent.LEFT:
+					if self._eventlistener.shiftPressed:
+						self._controller.deselectCell(realCoords[0], realCoords[1])
+					else:
+						self._controller.selectCell(realCoords[0], realCoords[1])
+					
+			elif self._mode == INSERTING:
+				position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)
+				position = self._controller._layer.getCellGrid().toLayerCoordinates(position)
+				
+				self._controller.selectCell(realCoords[0], realCoords[1])
+				self._controller.placeInstance(position, self._object)
+				
+			elif self._mode == REMOVING:
+				self._controller.selectCell(realCoords[0], realCoords[1])
+				self._controller.removeInstances(self._controller.getInstancesFromSelection())
+				
+			elif self._mode == MOVING:
+				position = self._controller._camera.toMapCoordinates(fife.ScreenPoint(realCoords[0], realCoords[1]), False)
+				
+				positionExact = self._controller._layer.getCellGrid().toExactLayerCoordinates(position)
+				position = self._controller._layer.getCellGrid().toLayerCoordinates(position)
+				
+				if self._eventlistener.shiftPressed:
+					self._controller.moveInstances(self._instances, positionExact-self._lastDragPosExact, True)
+				else:
+					self._controller.moveInstances(self._instances, position-self._lastDragPos, False)
+				self._lastDragPos = position
+				self._lastDragPosExact = positionExact
+				
+				# Update selection
+				self._controller.deselectSelection()
+				
+				for i in self._instances:
+					pos = i.getLocation().getMapCoordinates()
+					pos = self._controller._camera.toScreenCoordinates(pos)
+					self._controller.selectCell(pos.x, pos.y)
+			elif self._mode == OBJECTPICKER:
+				pass
+
+	def mouseReleased(self, sender, event):
+		if event.isConsumedByWidgets():
+			return
+			
+		if self._mode == SELECTING or self._mode == MOVING:
+			instances = self._controller.getInstancesFromSelection()
+			if len(instances) > 0:
+				events.onInstancesSelected.send(sender=self, instances=instances)
+			
+		if event.getButton() == fife.MouseEvent.MIDDLE:
+			self._scrollX = 0
+			self._scrollY = 0
+			
+		realCoords = self._getRealCoords(sender, event)
+
+		if self._undogroup:
+			self._controller.getUndoManager().endGroup()
+			self._undogroup = False
+
+		self._dragx = NOT_INITIALIZED
+		self._dragy = NOT_INITIALIZED
+
+	def mouseMoved(self, sender, event):
+		pass
+				
+	def mouseWheelMovedUp(self, event):
+		if self._eventlistener.controlPressed and self._controller._camera:
+			self._controller._camera.setZoom(self._controller._camera.getZoom() * 1.10)
+
+	def mouseWheelMovedDown(self, event):
+		if self._eventlistener.controlPressed and self._controller._camera:
+			self._controller._camera.setZoom(self._controller._camera.getZoom() / 1.10)
+
+
+	def keyPressed(self, event):
+		keyval = event.getKey().getValue()
+		keystr = event.getKey().getAsString().lower()
+		
+		if keyval == fife.Key.LEFT:
+			self._controller.moveCamera(50, 0)
+		elif keyval == fife.Key.RIGHT:
+			self._controller.moveCamera(-50, 0)
+		elif keyval == fife.Key.UP:
+			self._controller.moveCamera(0, 50)
+		elif keyval == fife.Key.DOWN:
+			self._controller.moveCamera(0, -50)
+		
+		elif keyval == fife.Key.INSERT:
+			self._controller.fillSelection(self._object)
+
+		elif keyval == fife.Key.DELETE:
+			self._controller.clearSelection()
+			
+		elif keystr == "s":
+			self._setMode(SELECTING)
+			
+		elif keystr == "i":
+			if self._mode != INSERTING:
+				self._setMode(INSERTING)
+			else:
+				self._setMode(SELECTING)
+			
+		elif keystr == "r":
+			if self._mode != REMOVING:
+				self._setMode(REMOVING)
+			else:
+				self._setMode(SELECTING)
+
+		elif keystr == 'm':
+			if self._mode != MOVING:
+				self._setMode(MOVING)
+			else:
+				self._setMode(SELECTING)
+
+		elif keystr == 't':
+			gridrenderer = self._controller._camera.getRenderer('GridRenderer')
+			gridrenderer.setEnabled(not gridrenderer.isEnabled())
+
+		elif keystr == 'b':
+			blockrenderer = self._controller._camera.getRenderer('BlockingInfoRenderer')
+			blockrenderer.setEnabled(not blockrenderer.isEnabled())
+
+		elif keystr == 'z':
+			if self._eventlistener.controlPressed:
+				if self._eventlistener.altPressed:
+					if self._eventlistener.shiftPressed:
+						self._controller.getUndoManager().previousBranch()
+					else:
+						self._controller.getUndoManager().nextBranch()
+				else:
+					if self._eventlistener.shiftPressed:
+						self._controller.redo()
+					else:
+						self._controller.undo()
+			
+
+	def keyReleased(self, event):		
+		pass
+		
+	def zoomIn(self, zoom=1.10):
+		self._controller.setZoom(self._controller.getZoom()*zoom)
+		
+	def zoomOut(self, zoom=1.10):
+		self._controller.setZoom(self._controller.getZoom()/zoom)
+		
+	def resetZoom(self):
+		self._controller.setZoom(1)
+		
+	def rotateCounterClockwise(self):
+		self._controller.rotateCounterClockwise()
+		
+	def rotateClockwise(self):
+		self._controller.rotateClockwise()
+			
+	def _getRealCoords(self, sender, event):
+		cw = sender
+		offsetX = event.getX()
+		offsetY = event.getY()
+		
+		parent = cw
+		while parent is not None:
+			if isinstance(parent, widgets.Widget):
+				offsetX += parent.x
+				offsetY += parent.y
+				parent = parent.parent
+			else:
+				break
+			
+		return (offsetX, offsetY)
+		
+	def pump(self):
+		self._controller.moveCamera(self._scrollX, self._scrollY)
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/menubar.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,318 @@
+from pychan import widgets
+from pychan.tools import callbackWithArguments as cbwa
+
+import scripts.events
+import scripts.gui.action
+from action import Action, ActionGroup
+from fife import Color
+import fife_timer
+
+MENU_ICON_SIZE = 24
+
+class MenuBar(widgets.HBox):
+	def __init__(self, *args, **kwargs):
+		super(MenuBar, self).__init__(*args, **kwargs)
+		
+		self.menulist = []
+		self._buttonlist = []
+		self.gui = None
+		self._buildGui()
+		
+		self._timer = fife_timer.Timer(500, self._autoHideMenu)
+		self._timer.start()
+			
+	def _buildGui(self):
+		if self.gui is not None:
+			self.removeChild(self.gui)
+			self._buttonlist = []
+			
+		self.gui = widgets.HBox()
+		for i, menu in enumerate(self.menulist):
+			button = widgets.Button(name=menu.name, text=menu.name)
+			button.hexpand = 0
+			button.capture(cbwa(self._showMenu, i))
+			self._buttonlist.append(button)
+			self.gui.addChild(button)
+			
+		self.gui.addSpacer(widgets.Spacer())
+		
+		self.addChild(self.gui)
+		
+	def _showMenu(self, i):
+		if self.menulist[i].isVisible():
+			self.menulist[i].hide()
+			return
+	
+		# Hide all menus
+		for m in self.menulist:
+			m.hide()
+	
+		menu = self.menulist[i]
+		button = self._buttonlist[i]
+		
+		menu.x = 0
+		menu.y = button.height
+
+		# Get absolute position of button
+		parent = button
+		while parent is not None:
+			menu.x += parent.x
+			menu.y += parent.y
+			parent = parent.parent
+
+		menu.show()
+		
+	def _autoHideMenu(self):
+		for i, m in enumerate(self.menulist):
+			if not m.isVisible(): continue
+			if self._buttonlist[i].real_widget.isFocused(): continue
+			if self._isMenuFocused(m) is False:
+				m.hide()
+		
+	def _isMenuFocused(self, widget):
+		if widget.real_widget.isFocused(): return True
+		if hasattr(widget, "children"):
+			for c in widget.children:
+				if self._isMenuFocused(c):
+					return True
+		return False
+		
+	def addMenu(self, menu):
+		if menu is not None and self.menulist.count(menu) <= 0:
+			self.menulist.append(menu)
+		self._buildGui()
+		
+	def insertMenu(self, menu, beforeMenu):
+		try:
+			i = self.menulist.index(beforeMenu)
+			self.menulist.insert(i, menu)
+			self._buildGui()
+			
+		except ValueError:
+			print "MenuBar::insertMenu:", "MenuBar does not contain specified menu."
+			return
+			
+	def insertMenuAt(self, menu, index):
+		self.menulist.insert(index, menu)
+		self._buildGui()
+
+	def removeMenu(self, menu):
+		self.menulist.remove(menu)
+		self._buildGui()
+		
+	def clear(self):
+		self.menulist = []
+		self._buildGui()
+
+class Menu(widgets.VBox):
+	def __init__(self, name=u"", icon=u"", min_width=100, min_height=15, margins=(2,2), *args, **kwargs):
+		super(Menu, self).__init__(*args, **kwargs)
+		self.min_width=min_width
+		self.min_height=min_height
+		self.margins=margins
+		
+		self.name = name
+		self.icon = icon
+		
+		self._actions = []
+		self._actionbuttons = []
+		
+		self._update()
+
+	def addSeparator(self, separator=None): 
+		self.insertSeparator(separator, len(self._actions))
+
+	def addAction(self, action):
+		self.insertAction(action, len(self._actions))
+		
+	def removeAction(self, action):
+		self._actions.remove(action)
+		
+		actions = [action]
+		if isinstance(action, ActionGroup):
+			actions = action.getActions()
+			scripts.gui.action.changed.disconnect(self._updateActionGroup, sender=action)
+
+		for a in actions:
+			for b in self._actionbuttons[:]:
+				if a == b.action:
+					self.removeChild(b)
+					self._actionbuttons.remove(b)
+			
+		self.adaptLayout()
+		
+	def hasAction(self, action):
+		for a in self._actions:
+			if a == action: return True
+		return False
+		
+	def insertAction(self, action, position=0, before=None):
+		if self.hasAction(action):
+			print "Action already added to toolbar"
+			return
+
+		if before is not None:
+			position = self._actions.index(before)
+
+		self._actions.insert(position, action)
+		self._insertButton(action, position)
+		
+	def _updateActionGroup(self, sender):
+		position = self._actions.index(sender)
+		self.removeAction(sender)
+		self.insertAction(sender, position)
+		self.adaptLayout()
+		
+	def _insertButton(self, action, position):
+		actions = [action]
+		if isinstance(action, ActionGroup):
+			actions = action.getActions()
+			scripts.gui.action.changed.connect(self._updateActionGroup, sender=action)
+
+		if position >= 0:
+			actions = reversed(actions)
+		
+		# Action groups are counted as one action, add the hidde number of actions to position
+		for i in range(position):
+			if isinstance(self._actions[i], ActionGroup):
+				position += len(self._actions[i].getActions()) - 1
+
+		for a in actions:
+			button = MenuButton(a, name=a.text)
+			self.insertChild(button, position)
+			self._actionbuttons.insert(position, button)
+		
+	def insertSeparator(self, separator=None, position=0, before=None): 
+		if separator==None:
+			separator = Action(separator=True)
+		self.insertAction(separator, position, before)
+		
+	def clear(self):
+		self.removeAllChildren()
+		self._actions = []
+		
+		for i in reversed(range(len(self._actionbuttons))):
+			self._actionbuttons[i].removeEvents()
+		self._actionbuttons = []
+
+	def _update(self):
+		actions = self._actions
+		
+		self.clear()
+		
+		for action in actions:
+			self.addAction(action)
+
+		self.adaptLayout()
+		
+	def show(self):
+		self._update()
+		super(Menu, self).show()
+		
+class MenuButton(widgets.HBox):
+	def __init__(self, action, **kwargs):
+		self._action = action
+		self._widget = None
+		
+		super(MenuButton, self).__init__(**kwargs)
+		
+		self.update()
+
+		self.initEvents()
+	
+	def initEvents(self):
+		# Register eventlisteners
+		self.capture(self._showTooltip, "mouseEntered")
+		self.capture(self._hideTooltip, "mouseExited")
+		
+		scripts.gui.action.changed.connect(self._actionChanged, sender=self._action)
+	
+	def removeEvents(self):
+		# Remove eventlisteners
+		self.capture(None, "mouseEntered")
+		self.capture(None, "mouseExited")
+		
+		scripts.gui.action.changed.disconnect(self.update, sender=self._action)
+	
+	def setAction(self, action):
+		self.removeEvents()
+		
+		self._action = action
+		self.update()
+		self.adaptLayout()
+		
+		self.initEvents()
+	
+	def getAction(self):
+		return self._action
+	action = property(getAction, setAction)
+	
+	def _showTooltip(self):
+		if self._action is not None and self._action.helptext != "":
+			scripts.editor.getEditor().getStatusBar().showTooltip(self._action.helptext)
+			
+	def _hideTooltip(self):
+		scripts.editor.getEditor().getStatusBar().hideTooltip()
+		
+	def _actionChanged(self):
+		self.update()
+		self.adaptLayout()
+		
+	def update(self):
+		""" Sets up the button widget """
+		if self._widget != None:
+			self.removeChild(self._widget)
+			self._widget = None
+			
+		if self._action is None:
+			return
+			
+		widget = None
+		icon = None
+		text = None
+
+		if self._action.isSeparator():
+			widget = widgets.HBox()
+			widget.base_color += Color(8, 8, 8)
+			widget.min_size = (2, 2)
+		else:
+			hasIcon = len(self._action.icon) > 0
+			
+			if self._action.isCheckable():
+				text = widgets.ToggleButton(text=self._action.text)
+				text.toggled = self._action.isChecked()
+				text.hexpand = 1
+			else:
+				text = widgets.Button(text=self._action.text)
+			text.min_size = (1, MENU_ICON_SIZE)
+			text.max_size = (1000, MENU_ICON_SIZE)
+			text.capture(self._action.activate)
+
+			if hasIcon:
+				if self._action.isCheckable():
+					icon = widgets.ToggleButton(hexpand=0, up_image=self._action.icon,down_image=self._action.icon,hover_image=self._action.icon,offset=(1,1))
+					icon.toggled = self._action.isChecked()
+				else:
+					icon = widgets.ImageButton(hexpand=0, up_image=self._action.icon,down_image=self._action.icon,hover_image=self._action.icon,offset=(1,1))
+
+			else:
+				if self._action.isCheckable():
+					icon = widgets.ToggleButton(hexpand=0, offset=(1,1))
+					icon.toggled = self._action.isChecked()
+				else:
+					icon = widgets.Button(text=u"", hexpand=0, offset=(1,1))
+				
+			icon.min_size = icon.max_size = (MENU_ICON_SIZE, MENU_ICON_SIZE)
+			icon.capture(self._action.activate)
+			
+			widget = widgets.HBox()
+			widget.addChild(icon)
+			widget.addChild(text)
+			
+		widget.position_technique = "left:center"
+		widget.hexpand = 1
+		widget.vexpand = 0
+		
+		self._widget = widget
+		self.addChild(self._widget)
+		
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/panel.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,136 @@
+import pychan
+from pychan import widgets
+import scripts.editor
+import fife
+from resizablebase import ResizableBase
+
+class Panel(widgets.Window, ResizableBase):
+	""" Panel is a window which can be resized and docked. 
+	"""
+	def __init__(self, dockable=True, *args, **kwargs):
+		widgets.Window.__init__(self, *args, **kwargs)
+		ResizableBase.__init__(self, *args, **kwargs)
+	
+		self.dockable = dockable
+		self._movable = self.real_widget.isMovable()
+		self._resizable = self.resizable
+
+		self._floating = True
+		self._titlebarheight = 16
+		
+		self.dockarea = None
+		
+		self._editor = scripts.editor.getEditor()
+		
+	def setDocked(self, docked):
+		""" 
+		Dock or undock the panel
+		
+		setDocked(True) will disable resizing and moving
+		of this panel, but will not dock it in a dockarea.
+		
+		setDocked(False) will enable resizing and moving.
+		If this panel is docked in a dockarea or widget,
+		it will undock itself. The new position will be 
+		offset by panelSize.
+		"""
+		if self.dockable is False:
+			return
+		
+		if docked is True and self._floating == True:
+			self._floating = False
+			self.real_widget.setTitleBarHeight(0)
+			self.real_widget.setMovable(False)
+			self._movable = False
+			self.resizable = False
+				
+		elif docked is False and self._floating is False:			
+			self._floating = True
+			self._movable = True
+			self.real_widget.setMovable(True)
+			self.resizable = self._resizable
+			
+			# Since x and y coordinates are reset if the widget gets hidden,
+			# we need to store them
+			absX, absY = self.getAbsolutePos()
+			
+			# Remove from parent widget
+			if self.dockarea is not None:
+				# Use dockareas undock method
+				self.dockarea.undockChild(self, True)
+				self.dockarea = None
+
+			elif self.parent is not None:
+				# Force removal
+				widgetParent = self.parent
+				widgetParent.removeChild(self)
+				widgetParent.adaptLayout()
+				self.hide()
+				
+			self.real_widget.setTitleBarHeight(self._titlebarheight)
+			self.show()
+			
+			# Slighly offset toolbar when undocking
+			mw = pychan.internal.screen_width() / 2
+			mh = pychan.internal.screen_height() / 2
+			if absX < mw:
+				self.x = absX + self._titlebarheight
+			else:
+				self.x = absX - self._titlebarheight
+			if absY < mh:
+				self.y = absY + self._titlebarheight
+			else:
+				self.y = absY - self._titlebarheight
+
+	def isDocked(self):
+		""" Returns true if the panel is docked """
+		return self._floating == False
+		
+	def mousePressed(self, event):
+		if self.resizable is False:
+			return
+		
+		ResizableBase.mousePressed(self, event)
+		if self._rLeft or self._rRight or self._rTop or self._rBottom:
+			self._movable = self.real_widget.isMovable()
+			self.real_widget.setMovable(False) # Don't let guichan move window while we resize
+			
+	def mouseDragged(self, event):
+		if self._resize is False and self.isDocked() is False:
+			mouseX = self.x+event.getX()
+			mouseY = self.y+event.getY()
+			self._editor.getDockAreaAt(mouseX, mouseY, True)
+		else:
+			ResizableBase.mouseDragged(self, event)
+	
+	def mouseReleased(self, event):
+		# Resize/move done
+		self.real_widget.setMovable(self._movable)
+		
+		if self._resize:
+			ResizableBase.mouseReleased(self, event)
+		elif self._movable:
+			mouseX = self.x+event.getX()
+			mouseY = self.y+event.getY()
+		
+			dockArea = self._editor.getDockAreaAt(mouseX, mouseY)
+			if dockArea is not None:
+				self._editor.dockWidgetTo(self, dockArea, mouseX, mouseY)
+		
+	def hide(self):
+		""" Hides the panel. If the widget is docked, it is first undocked """
+		if self.isDocked():
+			self.setDocked(False)
+		widgets.Window.hide(self)
+		
+	def show(self):
+		""" Show the panel. """
+		if self.isDocked():
+			return
+		widgets.Window.show(self)
+				
+	
+# Register widget to pychan
+if 'Panel' not in widgets.WIDGETS:
+	widgets.WIDGETS['Panel'] = Panel
+		
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/resizablebase.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,146 @@
+import fife
+import scripts
+import pychan
+
+class ResizableBase(object):
+	def __init__(self, resizable=True, *args, **kwargs):
+		self._engine = scripts.editor.getEditor().getEngine()
+	
+		self.resizable = resizable
+		self.resizable_top = True
+		self.resizable_left = True
+		self.resizable_right = True
+		self.resizable_bottom = True
+		
+		self._resize = False
+		
+		self.capture(self.mouseEntered, "mouseEntered", "ResizableBase")
+		self.capture(self.mouseExited, "mouseExited", "ResizableBase")
+		self.capture(self.mouseMoved, "mouseMoved", "ResizableBase")
+		self.capture(self.mouseDragged, "mouseDragged", "ResizableBase")
+		self.capture(self.mousePressed, "mousePressed", "ResizableBase")
+		self.capture(self.mouseReleased, "mouseReleased", "ResizableBase")
+		
+		self.cursor_id = 0
+		self.cursor_type = 1
+		
+	
+	def mouseEntered(self, event):
+		# Save cursor id
+		if self.resizable and self._resize is False:
+			cursor = self._engine.getCursor()
+			self.cursor_id = cursor.getId()
+			self.cursor_type = cursor.getType()
+		
+	def mouseMoved(self, event):
+		if self.resizable is False: 
+			return
+			
+		# Hack to allow consuming mousemove events
+		key = (event.getX(), event.getY())
+		if _mousemoveevent.has_key( key ) is False:
+			_mousemoveevent.clear()
+			_mousemoveevent[key] = event
+		elif _mousemoveevent[key].isConsumed():
+			return
+			
+			
+		titleheight = 0
+		if hasattr(self.real_widget, "getTitleBarHeight"):
+			titleheight = self.real_widget.getTitleBarHeight()
+
+		cursor = self._engine.getCursor()
+		cursor.set(fife.CURSOR_NATIVE, fife.NC_RESIZENW)
+		
+		left	= event.getX() < 5 and self.resizable_left
+		right	= event.getX() > self.width-5 and self.resizable_right
+		top		= event.getY() < 5 and self.resizable_top
+		bottom	= event.getY() - titleheight > self.height-5 and self.resizable_bottom
+
+		if left and top:
+			cursor.set(fife.CURSOR_NATIVE, fife.NC_RESIZENW)
+		elif right and top:
+			cursor.set(fife.CURSOR_NATIVE, fife.NC_RESIZENE)
+		elif left and bottom:
+			cursor.set(fife.CURSOR_NATIVE, fife.NC_RESIZESW)
+		elif right and bottom:
+			cursor.set(fife.CURSOR_NATIVE, fife.NC_RESIZESE)
+		elif left:
+			cursor.set(fife.CURSOR_NATIVE, fife.NC_RESIZEW)
+		elif right:
+			cursor.set(fife.CURSOR_NATIVE, fife.NC_RESIZEE)
+		elif top:
+			cursor.set(fife.CURSOR_NATIVE, fife.NC_RESIZEN)
+		elif bottom:
+			cursor.set(fife.CURSOR_NATIVE, fife.NC_RESIZES)
+		else:
+			cursor.set(self.cursor_type, self.cursor_id)
+			return
+			
+		event.consume()
+		_mousemoveevent[key].consume()
+		
+	def mouseExited(self, event):
+		# Reset cursor to whatever it was before it entered this window
+		if self.resizable and self._resize is False:
+			cursor = self._engine.getCursor()
+			cursor.set(self.cursor_type, self.cursor_id)
+		
+	def mouseDragged(self, event):
+		if self.resizable and self._resize:
+			diffX = event.getX()
+			diffY = event.getY()
+			
+			# Resize horizontally
+			if self._rLeft:
+				self.x += diffX
+				self.width -= diffX
+			elif self._rRight:
+				self.width = diffX
+
+			# Resize vertically
+			if self._rTop:
+				self.y += diffY
+				self.height -= diffY
+			elif self._rBottom:
+				titleheight = 0
+				if hasattr(self.real_widget, "getTitleBarHeight"):
+					titleheight = self.real_widget.getTitleBarHeight()
+				self.height = diffY-titleheight
+
+	def mousePressed(self, event):
+		if self.resizable is False:
+			return
+			
+		titleheight = 0
+		if hasattr(self.real_widget, "getTitleBarHeight"):
+			titleheight = self.real_widget.getTitleBarHeight()
+			
+		self._rLeft		= event.getX() < 5 and self.resizable_left
+		self._rRight	= event.getX() > self.width-5 and self.resizable_right
+		self._rTop		= event.getY() < 5 and self.resizable_top
+		self._rBottom	= event.getY() - titleheight > self.height-5 and self.resizable_bottom
+		
+		if self._rLeft or self._rRight or self._rTop or self._rBottom:
+			self._resize = True
+			self.min_size = (30, 30)
+			event.consume()
+		
+	def mouseReleased(self, event):
+		if self._resize:
+			self.min_size = (self.width, self.height)
+			self.adaptLayout()
+			event.consume()
+			
+			titleheight = 0
+			if hasattr(self.real_widget, "getTitleBarHeight"):
+				titleheight = self.real_widget.getTitleBarHeight()
+		
+			self._resize = False
+			if event.getX() <= 0 or event.getX() >= self.width \
+					or event.getY() <= 0 or event.getY() >= self.height+titleheight:
+				self.mouseExited(event)
+				
+# Ugly hack to allow consumption of mousemove events
+_mousemoveevent = {}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/selection.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,59 @@
+import pychan
+import pychan.widgets as widgets
+
+class SelectionDialog(object):
+	"""
+	Selection displays a list of options for the user to select from. The result is passed to onSelection.
+	list - the list to select from
+	onSelection - the function to call when a selection is made. Accepts one argument: an element of the list.
+	"""
+	def __init__(self, list, onSelection):
+		self.list = list
+		self._callback = onSelection
+
+		self._widget = pychan.loadXML('gui/selection.xml')
+
+		self._widget.mapEvents({
+			'okButton'     : self._selected,
+			'cancelButton' : self._widget.hide
+		})
+
+		self._widget.distributeInitialData({
+			'optionDrop' : list
+		})
+		self._widget.show()
+
+	def _selected(self):
+		selection = self._widget.collectData('optionDrop')
+		if selection < 0: return
+		self._callback(self.list[selection])
+		self._widget.hide()
+
+class ClickSelectionDialog(object):
+	"""
+	ClickSelection displays a list of options for the user to select from. The result is passed to onSelection.
+	Differs from Selection: the selection is made when a list element is clicked, rather than when the box is closed.	
+	list - the list to select from
+	onSelection - the function to call when a selection is made. Accepts one argument: an element of the list.
+	"""
+	def __init__(self, list, onSelection):
+		self.list = list
+		self._callback = onSelection
+
+		self._widget = pychan.loadXML('gui/selection.xml')
+
+		self._widget.mapEvents({
+			'okButton'     : self._widget.hide,
+			'cancelButton' : self._widget.hide,
+			'optionDrop'   : self._selected
+		})
+
+		self._widget.distributeInitialData({
+			'optionDrop' : list
+		})
+		self._widget.show()
+
+	def _selected(self):
+		selection = self._widget.collectData('optionDrop')
+		if selection < 0: return
+		self._callback(self.list[selection])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/statusbar.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,44 @@
+from pychan import widgets
+
+class StatusBar(widgets.HBox):
+	"""
+	A basic widget which displays information of the current status of the program.
+	
+	Use the text property to set the text to be displayed. Use showTooltip() to display
+	a temporary message.
+	"""
+	def __init__(self, text=u"", panel_size=25, *args, **kwargs):
+		super(StatusBar, self).__init__(*args, **kwargs)
+		self.min_size = (panel_size, panel_size)
+		
+		self._tooltip = None
+		self._label = widgets.Label(text=text)
+		self.addChild(self._label)
+	   
+	def setText(self, text):
+		""" Sets the text to be displayed whenever a tooltip isn't displayed """
+		self._label.text = text
+		
+	def getText(self):
+		return self._label.text
+	text = property(getText, setText)
+
+	def showTooltip(self, text):
+		""" Shows a text which is visible until hideTooltip is called """
+		if text == u"":
+			self.hideTooltip()
+		if self._tooltip is not None:
+			self._tooltip.text = text
+		else:
+			self.removeChild(self._label)
+			self._tooltip = widgets.Label(text=text)
+			self.addChild(self._tooltip)
+		self.adaptLayout()
+		
+	def hideTooltip(self):
+		""" Removes the text set by showTooltip. Whatever text previously set by setText is then displayed. """
+		if self._tooltip is not None:
+			self.removeChild(self._tooltip)
+			self.addChild(self._label)
+			self._tooltip = None
+			self.adaptLayout()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/gui/toolbar.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,317 @@
+import pychan
+from pychan import widgets
+
+import scripts.events
+import action
+import scripts.editor
+from action import Action, ActionGroup
+from fife import Color
+from panel import Panel
+from resizablebase import ResizableBase
+
+class ToolBar(Panel):
+	ORIENTATION = {
+			"Horizontal"	: 0,
+			"Vertical"		: 1
+		}
+
+	BUTTON_STYLE = {
+				"IconOnly"			: 0,
+				"TextOnly"			: 1,
+				"TextUnderIcon"		: 2,
+				"TextBesideIcon"	: 3
+			}
+
+	def __init__(self, button_style=0, panel_size=27, orientation=0, *args, **kwargs):
+		super(ToolBar, self).__init__(resizable=False, *args, **kwargs)
+		
+		self._actions = []
+		self._actionbuttons = []
+		self._button_style = 0
+		self._panel_size = panel_size
+		self.gui = None
+		
+		self._orientation = orientation
+		self._button_style = button_style
+
+		self._updateToolbar()
+		
+		self.capture(self.mouseReleased, "mouseReleased", "toolbar")
+		self.capture(self.mouseClicked, "mouseClicked", "toolbar")
+
+	def addSeparator(self, separator=None): 
+		self.insertSeparator(separator, len(self._actions))
+
+	def addAction(self, action):
+		self.insertAction(action, len(self._actions))
+		
+	def removeAction(self, action):
+		self._actions.remove(action)
+		
+		actions = [action]
+		if isinstance(action, ActionGroup):
+			actions = action.getActions()
+			scripts.gui.action.changed.disconnect(self._updateActionGroup, sender=action)
+
+		for a in actions:
+			for b in self._actionbuttons[:]:
+				if a == b.action:
+					self.gui.removeChild(b)
+					self._actionbuttons.remove(b)
+			
+		self.adaptLayout()
+		
+	def hasAction(self, action):
+		for a in self._actions:
+			if a == action: return True
+		return False
+		
+	def insertAction(self, action, position=0, before=None):
+		if self.hasAction(action):
+			print "Action already added to toolbar"
+			return
+
+		if before is not None:
+			position = self._actions.index(before)
+
+		self._actions.insert(position, action)
+		self._insertButton(action, position)
+		
+	def _updateActionGroup(self, sender):
+		position = self._actions.index(sender)
+		self.removeAction(sender)
+		self.insertAction(sender, position)
+		self.adaptLayout()
+		
+	def _insertButton(self, action, position):
+		actions = [action]
+		if isinstance(action, ActionGroup):
+			actions = action.getActions()
+			scripts.gui.action.changed.connect(self._updateActionGroup, sender=action)
+
+		if position >= 0:
+			actions = reversed(actions)
+		
+		# Action groups are counted as one action, add the hidde number of actions to position
+		for i in range(position):
+			if isinstance(self._actions[i], ActionGroup):
+				position += len(self._actions[i].getActions()) - 1
+
+		for a in actions:
+			button = ToolbarButton(a, button_style=self._button_style, name=a.text)
+			self.gui.insertChild(button, position)
+			self._actionbuttons.insert(position, button)
+		
+	def insertSeparator(self, separator=None, position=0, before=None): 
+		if separator==None:
+			separator = Action(separator=True)
+		self.insertAction(separator, position, before)
+		
+	def clear(self):
+		self.removeAllChildren()
+		self._actions = []
+		
+		for i in reversed(range(len(self._actionbuttons))):
+			self._actionbuttons[i].removeEvents()
+		self._actionbuttons = []
+		
+	def setButtonStyle(self, button_style):
+		self._button_style = ToolBar.BUTTON_STYLE['IconOnly']
+		for key, val in ToolBar.BUTTON_STYLE.iteritems():
+			if val == button_style:
+				self._button_style = button_style
+				break
+
+		self._updateToolbar()
+		
+	def getButtonStyle(self):
+		return self._button_style
+	button_style = property(getButtonStyle, setButtonStyle)
+		
+	def _updateToolbar(self):
+		actions = self._actions
+		
+		self.clear()
+		
+		if self._orientation == ToolBar.ORIENTATION['Vertical']:
+			self.gui = widgets.VBox(min_size=(self._panel_size, self._panel_size))
+		else:
+			self.gui = widgets.HBox(min_size=(self._panel_size, self._panel_size))
+		self.addChild(self.gui)
+		
+		for action in actions:
+			self.addAction(action)
+
+		self.adaptLayout()
+		
+	def setOrientation(self, orientation):
+		if orientation == ToolBar.ORIENTATION['Vertical']:
+			self._orientation = ToolBar.ORIENTATION['Vertical']
+			self._max_size = (self._panel_size, 5000)
+		else:
+			self._orientation = ToolBar.ORIENTATION['Horizontal']
+			self._max_size = (5000, self._panel_size)
+		self._orientation = orientation
+		
+		self._updateToolbar()
+		
+	def getOrientation(self):
+		return self._orientation
+	orientation = property(getOrientation, setOrientation)
+		
+	def setPanelSize(self, panel_size):
+		self._panel_size = panel_size
+		self.min_size = self.gui.min_size = (self._panel_size, self._panel_size)
+		self.setOrientation(self._orientation)
+		
+	def getPanelSize(self):
+		return self._panel_size
+	panel_size = property(getPanelSize, setPanelSize)
+			
+	def mouseClicked(self, event):
+		if event.getButton() == 2: # Right click
+			if self.isDocked():
+				self.setDocked(False)
+				event.consume()
+			
+	def mouseDragged(self, event):
+		if self._resize is False and self.isDocked() is False:
+			mouseX = self.x+event.getX()
+			mouseY = self.y+event.getY()
+			self._editor.getToolbarAreaAt(mouseX, mouseY, True)
+		else:
+			ResizableBase.mouseDragged(self, event)
+	
+	def mouseReleased(self, event):
+		# Resize/move done
+		self.real_widget.setMovable(self._movable)
+		
+		if self._resize:
+			ResizableBase.mouseReleased(self, event)
+		elif self._movable:
+			mouseX = self.x+event.getX()
+			mouseY = self.y+event.getY()
+		
+			dockArea = self._editor.getToolbarAreaAt(mouseX, mouseY)
+			if dockArea is not None:
+				self._editor.dockWidgetTo(self, dockArea, mouseX, mouseY)
+			
+class ToolbarButton(widgets.VBox):
+	def __init__(self, action, button_style=0, **kwargs):
+		self._action = action
+		self._widget = None
+		
+		super(ToolbarButton, self).__init__(**kwargs)
+		
+		self.setButtonStyle(button_style)
+		self.update()
+
+		self.initEvents()
+	
+	def initEvents(self):
+		# Register eventlisteners
+		self.capture(self._showTooltip, "mouseEntered")
+		self.capture(self._hideTooltip, "mouseExited")
+		
+		scripts.gui.action.changed.connect(self._actionChanged, sender=self._action)
+	
+	def removeEvents(self):
+		# Remove eventlisteners
+		self.capture(None, "mouseEntered")
+		self.capture(None, "mouseExited")
+		
+		scripts.gui.action.changed.disconnect(self.update, sender=self._action)
+	
+	def setAction(self, action):
+		self.removeEvents()
+		
+		self._action = action
+		self.update()
+		self.adaptLayout()
+		
+		self.initEvents()
+	
+	def getAction(self):
+		return self._action
+	action = property(getAction, setAction)
+	
+	def setButtonStyle(self, button_style):
+		self._button_style = ToolBar.BUTTON_STYLE['IconOnly']
+		for key, val in ToolBar.BUTTON_STYLE.iteritems():
+			if val == button_style:
+				self._button_style = button_style
+				break
+		
+	def getButtonStyle(self):
+		return self._button_style
+	button_style = property(getButtonStyle, setButtonStyle)
+	
+	def _showTooltip(self):
+		if self._action is not None and self._action.helptext != "":
+			scripts.editor.getEditor().getStatusBar().showTooltip(self._action.helptext)
+			
+	def _hideTooltip(self):
+		scripts.editor.getEditor().getStatusBar().hideTooltip()
+		
+	def _actionChanged(self):
+		self.update()
+		self.adaptLayout()
+		
+	def update(self):
+		""" Sets up the button widget """
+		if self._widget != None:
+			self.removeChild(self._widget)
+			self._widget = None
+			
+		if self._action is None:
+			return
+			
+		widget = None
+		icon = None
+		text = None
+
+		if self._action.isSeparator():
+			widget = widgets.VBox()
+			widget.base_color += Color(8, 8, 8)
+			widget.min_size = (2, 2)
+		else:
+			if self._button_style != ToolBar.BUTTON_STYLE['TextOnly'] and len(self._action.icon) > 0:
+				if self._action.isCheckable():
+					icon = widgets.ToggleButton(hexpand=0, up_image=self._action.icon,down_image=self._action.icon,hover_image=self._action.icon,offset=(1,1))
+					icon.toggled = self._action.isChecked()
+				else:
+					icon = widgets.ImageButton(hexpand=0, up_image=self._action.icon,down_image=self._action.icon,hover_image=self._action.icon,offset=(1,1))
+				icon.capture(self._action.activate)
+				
+			if self._button_style != ToolBar.BUTTON_STYLE['IconOnly'] or len(self._action.icon) <= 0:
+				if self._action.isCheckable():
+					text = widgets.ToggleButton(hexpand=0, text=self._action.text,offset=(1,1))
+					text.toggled = self._action.isChecked()
+				else:
+					text = widgets.Button(text=self._action.text)
+				text.capture(self._action.activate)
+			
+			if self._button_style == ToolBar.BUTTON_STYLE['TextOnly'] or len(self._action.icon) <= 0:
+				widget = text
+				
+			elif self._button_style == ToolBar.BUTTON_STYLE['TextUnderIcon']:
+				widget = widgets.VBox()
+				icon.position_technique = "center:top"
+				text.position_technique = "center:bottom"
+				widget.addChild(icon)
+				widget.addChild(text)
+				
+			elif self._button_style == ToolBar.BUTTON_STYLE['TextBesideIcon']:
+				widget = widgets.HBox()
+				widget.addChild(icon)
+				widget.addChild(text)
+					
+			else:
+				widget = icon
+			
+		widget.position_technique = "left:center"
+		widget.hexpand = 0
+		
+		self._widget = widget
+		self.addChild(self._widget)
+		
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/mapcontroller.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,289 @@
+import editor
+import pdb
+
+import math
+
+import fife
+import editor
+import events
+import undomanager
+
+class MapController(object):
+	""" MapController provides an interface for editing maps """
+	def __init__(self, map):
+		
+		self._editor = editor.getEditor()
+		self._engine = self._editor.getEngine()
+
+		self._camera = None     # currently selected camera
+		self._layer = None      # currently selected layer
+		self._selection = []	# currently selected cells
+		self._map = None
+		self._undo = False
+		self._undomanager = undomanager.UndoManager()
+		undomanager.preUndo.connect(self._startUndo, sender=self._undomanager)
+		undomanager.preRedo.connect(self._startUndo, sender=self._undomanager)
+		undomanager.postUndo.connect(self._endUndo, sender=self._undomanager)
+		undomanager.postRedo.connect(self._endUndo, sender=self._undomanager)
+		self.debug = False
+		
+		self.overwriteInstances = True # Remove instances on cell before placing new instance
+		
+		if map is not None:
+			self.setMap(map.getId())
+		
+	def setMap(self, mapid):
+		""" Set the map to be edited """
+		self._camera = None
+		self._map = None
+		self._layer = None
+		self._selection = []
+
+		self._map = self._engine.getModel().getMap(mapid)
+		if not self._map.getLayers():
+			raise AttributeError('Editor error: map ' + self._map.getId() + ' has no layers. Cannot edit.')
+
+		for cam in self._engine.getView().getCameras():
+			if cam.getLocationRef().getMap().getId() == self._map.getId():
+				self._camera = cam
+				break
+
+		self._layer = self._map.getLayers()[0]
+
+	def selectLayer(self, layerid):
+		""" Select layer to be edited """
+		self.deselectSelection()
+		self._layer = None
+		layers = [l for l in self._map.getLayers() if l.getId() == layerid]
+		if len(layers) == 1:
+			self._layer = layers[0]
+
+	def deselectSelection(self):
+		""" Deselects all selected cells """
+		if not self._camera: 
+			if self.debug: print 'No camera bind yet, cannot select any cell'
+			return
+		self._selection = []
+		fife.CellSelectionRenderer.getInstance(self._camera).reset()
+		
+	def clearSelection(self):
+		""" Removes all instances on selected cells """
+		instances = self.getInstancesFromSelection()
+		self._undomanager.startGroup("Cleared selection")
+		self.removeInstances(instances)
+		self._undomanager.endGroup()
+		
+	def fillSelection(self, object):
+		""" Adds an instance of object on each selected cell """
+		self._undomanager.startGroup("Filled selection")
+		for loc in self._selection:
+			self.placeInstance(loc.getLayerCoordinates(), object)
+		self._undomanager.endGroup()
+
+	def selectCell(self, screenx, screeny):
+		""" Selects a cell at a position on screen """
+		if not self._camera: 
+			if self.debug: print 'No camera bind yet, cannot select any cell'
+			return
+		if not self._layer:
+			if self.debug: print 'No layer assigned in selectCell'
+			return
+
+		mapCoords = self._camera.toMapCoordinates(fife.ScreenPoint(screenx, screeny), False)
+		position = self._layer.getCellGrid().toLayerCoordinates(mapCoords)
+		
+		loc = fife.Location(self._layer)
+		loc.setLayerCoordinates(position)
+		
+		for i in self._selection:
+			if loc == i: return
+			
+		self._selection.append( loc )
+		fife.CellSelectionRenderer.getInstance(self._camera).selectLocation(loc)
+		
+	def deselectCell(self, screenx, screeny):
+		""" Deselects a cell at a position on screen """
+		if not self._camera: 
+			if self.debug: print 'No camera bind yet, cannot select any cell'
+			return
+		if not self._layer:
+			if self.debug: print 'No layer assigned in selectCell'
+			return
+
+		mapCoords = self._camera.toMapCoordinates(fife.ScreenPoint(screenx, screeny), False)
+		position = self._layer.getCellGrid().toLayerCoordinates(mapCoords)
+		
+		loc = fife.Location(self._layer)
+		loc.setLayerCoordinates(position)
+		
+		for i in self._selection:
+			if loc == i:
+				self._selection.remove( loc )
+				fife.CellSelectionRenderer.getInstance(self._camera).deselectLocation(loc)
+				return
+		
+		
+	def getInstancesFromSelection(self):
+		""" Returns all instances in the selected cells """
+		instances = []
+		
+		for loc in self._selection:
+			instances.extend(self.getInstancesFromPosition(loc.getLayerCoordinates()))
+
+		return instances
+
+	def getInstancesFromPosition(self, position):
+		""" Returns all instances on a specified position """
+		if not self._layer:
+			if self.debug: print 'No layer assigned in getInstancesFromPosition'
+			return
+		if not position:
+			if self.debug: print 'No position assigned in getInstancesFromPosition'
+			return
+
+		loc = fife.Location(self._layer)
+		if type(position) == fife.ExactModelCoordinate:
+			loc.setExactLayerCoordinates(position)
+		else:
+			loc.setLayerCoordinates(position)
+			
+		instances = self._layer.getInstancesAt(loc)
+
+		return instances
+
+	def getUndoManager(self):
+		""" Returns undo manager """
+		return self._undomanager
+
+	def undo(self):
+		""" Undo one level """
+		self._undomanager.undo()
+
+	def redo(self):
+		""" Redo one level """
+		self._undomanager.redo()
+		
+	def _startUndo(self):
+		""" Called before a undo operation is performed. Makes sure undo stack does not get corrupted """
+		self._undo = True
+		
+	def _endUndo(self):
+		""" Called when a undo operation is done """
+		self._undo = False
+
+	def placeInstance(self, position, object):
+		""" Places an instance of object at position. Any existing instances on position are removed. """
+		mname = 'placeInstance'
+		if not object:
+			if self.debug: print 'No object assigned in %s' % mname
+			return
+		if not position:
+			if self.debug: print 'No position assigned in %s' % mname
+			return
+		if not self._layer:
+			if self.debug: print 'No layer assigned in %s' % mname
+			return
+
+		if self.debug: print 'Placing instance of ' + object.getId() + ' at ' + str(position)
+
+		# Remove instances from target position
+		if not self._undo:
+			self._undomanager.startGroup("Placed instance")
+			self.removeInstances(self.getInstancesFromPosition(position))
+
+		inst = self._layer.createInstance(object, position)
+		fife.InstanceVisual.create(inst)
+		
+		if not self._undo:
+			redocall = lambda: self.placeInstance(position, object)
+			undocall = lambda: self.removeInstances([inst])
+			undoobject = undomanager.UndoObject(undocall, redocall, "Placed instance")
+			self._undomanager.addAction(undoobject)
+			self._undomanager.endGroup()
+			
+	def removeInstances(self, instances):
+		""" Removes all provided instances """
+		mname = 'removeInstances'
+		if not instances:
+			if self.debug: print 'No instances assigned in %s' % mname
+			return
+			
+		for i in instances:
+			if self.debug: print 'Deleting instance ' + i.getObject().getId() + ' at ' + str(i.getLocation().getExactLayerCoordinates())
+			
+			if not self._undo:
+				object = i.getObject()
+				
+				position = i.getLocation().getExactLayerCoordinates()
+				undocall = lambda: self.placeInstance(position, object)
+				redocall = lambda: self.removeInstances([i])
+				undoobject = undomanager.UndoObject(undocall, redocall, "Removed instance")
+				self._undomanager.addAction(undoobject)
+				
+			self._layer.deleteInstance(i)
+
+	def moveInstances(self, instances, moveBy, exact=False):
+		""" Moves provided instances by moveBy. If exact is false, the instances are
+		snapped to closest cell. """
+		mname = 'moveInstances'
+		if not self._layer:
+			if self.debug: print 'No layer assigned in %s' % mname
+			return
+			
+		if exact and type(moveBy) != fife.ExactModelCoordinate:
+			moveBy = fife.ExactModelCoordinate(float(moveBy.x), float(moveBy.y), float(moveBy.z))
+		elif exact is False and type(moveBy) != fife.ModelCoordinate:
+			moveBy = fife.ModelCoordinate(int(round(moveBy.x)), int(round(moveBy.y)), int(round(moveBy.z)))
+			
+		for i in instances:
+			loc = i.getLocation()
+			f = i.getFacingLocation()
+			if exact:
+				newCoords = loc.getExactLayerCoordinates() + moveBy
+				loc.setExactLayerCoordinates(newCoords)
+				f.setExactLayerCoordinates(f.getExactLayerCoordinates() + moveBy)
+			else:
+				# Move instance and snap to closest cell
+				newCoords = loc.getLayerCoordinates() + moveBy
+				loc.setLayerCoordinates(newCoords)
+				f.setLayerCoordinates(f.getLayerCoordinates() + moveBy)
+			i.setLocation(loc)
+			i.setFacingLocation(f)
+
+	def rotateCounterClockwise(self):
+		""" Rotates map counterclockwise by 90 degrees """
+		currot = self._camera.getRotation()
+		self._camera.setRotation((currot + 270) % 360)
+		
+	def rotateClockwise(self):
+		""" Rotates map clockwise by 90 degrees """
+		currot = self._camera.getRotation()
+		self._camera.setRotation((currot + 90) % 360)
+		
+	def getZoom(self):
+		""" Returns camera zoom """
+		if not self._camera: 
+			if self.debug: print 'No camera bind yet, cannot get zoom'
+			return 0
+		return self._camera.getZoom()
+		
+	def setZoom(self, zoom):
+		""" Sets camera zoom """
+		if not self._camera: 
+			if self.debug: print 'No camera bind yet, cannot get zoom'
+			return
+		self._camera.setZoom(zoom)
+
+	def moveCamera(self, screen_x, screen_y):
+		""" Move camera (scroll) by screen_x, screen_y """
+		coords = self._camera.getLocationRef().getMapCoordinates()
+		z = self._camera.getZoom()
+		r = self._camera.getRotation()
+		if screen_x:
+			coords.x -= screen_x / z * math.cos(r / 180.0 * math.pi) / 100;
+			coords.y -= screen_x / z * math.sin(r / 180.0 * math.pi) / 100;
+		if screen_y:
+			coords.x -= screen_y / z * math.sin(-r / 180.0 * math.pi) / 100;
+			coords.y -= screen_y / z * math.cos(-r / 180.0 * math.pi) / 100;
+		coords = self._camera.getLocationRef().setMapCoordinates(coords)
+		self._camera.refresh()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/mapview.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,99 @@
+import fife
+import editor
+import loaders, savers
+from events import events
+from mapcontroller import MapController
+
+class MapView:
+	""" MapView represents a map in the editor. 
+	
+	It handles opening, saving and closing of a map,
+	as well as displaying it on screen.
+	"""
+	def __init__(self, map):
+		self._map = map
+		self._editor = editor.getEditor()
+		self._controller = MapController(self._map)
+		
+		if map is None:
+			print "No map passed to MapView!"
+		else:
+			if not self._map.getLayers():
+				raise AttributeError('Editor error: map ' + self._map.getId() + ' has no layers. Cannot edit.')
+
+		self.importlist = []
+		if hasattr(map, "importDirs"):
+			self.importlist.extend(map.importDirs)
+		
+	# Copied from mapeditor.py
+	def show(self):
+		""" Sets up the camera to display the map. Size of the camera is equal to the
+		screen size """
+		_camera = None
+		
+		engine = self._editor.getEngine()
+		engine.getView().resetRenderers()
+		for cam in engine.getView().getCameras():
+			cam.setEnabled(False)
+
+		for cam in engine.getView().getCameras():
+			if cam.getLocationRef().getMap().getId() == self._map.getId():
+				rb = engine.getRenderBackend()
+				cam.setViewPort(fife.Rect(0, 0, rb.getScreenWidth(), rb.getScreenHeight()))
+				cam.setEnabled(True)
+				self._camera = cam
+				break
+		else:
+			raise AttributeError('No cameras found associated with this map: ' + self._map.getId())
+
+	def getCamera(self):
+		return self._camera
+	
+	def getController(self):
+		return self._controller
+		
+	def getMap(self):
+		return self._map
+		
+	def save(self):
+		""" Saves the map using the previous filename.
+		
+		Emits preSave and postSave signals.
+		"""
+		curname = ""
+		try:
+			curname = self._map.getResourceLocation().getFilename()
+		except RuntimeError:
+			print "Map has no filename yet, can't save."
+			return
+
+		events.preSave.send(sender=self, mapview=self)
+		savers.saveMapFile(curname, self._editor.getEngine(), self._map, importList=self.importlist)
+		events.postSave.send(sender=self, mapview=self)
+		
+	def saveAs(self, filename):
+		""" Saves the map using a specified filename.
+		
+		Emits preSave and postSave signals.
+		"""
+		events.preSave.send(sender=self, mapview=self)
+		savers.saveMapFile(str(filename), self._editor.getEngine(), self._map, importList=self.importlist)
+		events.postSave.send(sender=self, mapview=self)
+		
+	def close(self):
+		""" Closes the mapview """
+		pass
+		
+		
+	def importFile(self, path):
+		""" Imports a file supported by the provided loaders. """
+		loaders.loadImportFile(path, self._editor.getEngine())
+		
+	def importDir(self, path, recursive=True):
+		""" Imports an entire directory supported by the provided loaders. """
+		self.importlist.append(path)
+		if recursive is True:
+			loaders.loadImportDirRec(path, self._editor.getEngine())
+		else:
+			loaders.loadImportDir(path, self._editor.getEngine())
+	
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/plugin.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,74 @@
+import os
+import editor
+
+class PluginManager:
+	""" Currently, pluginmanager iterates through the plugin directory
+	adding the plugins if they are set to True in settings.xml. If a plugin
+	isn't set in settings.xml it assumes it is not to be loaded, and saves it
+	as False in settings.xml.
+	
+	If a plugin fails to load due to exceptions, they are caught and a line
+	of the error is printed to console.
+	"""
+	def __init__(self, *args, **kwargs):
+		self._settings = editor.getEditor().getSettings()
+		
+		self._pluginDir = "plugins"
+		self._plugins = []
+		
+		files = []
+		for f in os.listdir(self._pluginDir):
+			path = os.path.join(self._pluginDir, f)
+			if os.path.isfile(path) and os.path.splitext(f)[1] == ".py" and f != "__init__.py":
+				files.append(os.path.splitext(f)[0])
+				
+		for f in files:
+			importPlugin = self._settings.get("Plugins", f, False)
+			if importPlugin:
+				try:
+					print "Importing plugin:", f
+					exec "import plugins."+f
+					plugin = eval("plugins."+f+"."+f+"()")
+					if isinstance(plugin, Plugin) is False:
+						print f+" is not an instance of Plugin!"
+					else:
+						plugin.enable()
+						self._plugins.append(plugin)
+				except BaseException, error:
+					print "Error: ", error
+					print "Invalid plugin:", f
+			else:
+				print "Not importing plugin:", f
+				
+			self._settings.set("Plugins", f, importPlugin)
+			
+		self._settings.saveSettings()
+
+		
+class Plugin:
+	""" The base class for all plugins. All plugins should override these functions. """
+	
+	def enable(self):
+		raise NotImplementedError, "Plugin has not implemented the enable() function!"
+
+	def disable(self):
+		raise NotImplementedError, "Plugin has not implemented the disable() function!"
+
+	def isEnabled(self):
+		raise NotImplementedError, "Plugin has not implemented the isEnabled() function!"
+
+	def getName(self):
+		raise NotImplementedError, "Plugin has not implemented the getName() function!"
+		
+	#--- These are not so important ---#
+	def getAuthor(self):
+		return "Unknown"
+		
+	def getDescription(self):
+		return ""
+
+	def getLicense(self):
+		return ""
+		
+	def getVersion(self):
+		return "0.1"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/settings.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,218 @@
+import shutil
+import os
+try:
+	import xml.etree.cElementTree as ET
+except:
+	import xml.etree.ElementTree as ET
+
+class Settings(object):
+	""" This class provides an interface for retrieving and putting settings
+	to an XML-file. 
+	
+	Only one instance of this class may exist, or a RuntimeWarning exception
+	will be raised. Use Settings.instance or Editor.getSettings() to retrieve
+	an instance of this class.
+	"""
+	instance = None # Points to the first initialized instance of this class
+	def __init__(self, *args, **kwargs):
+		if Settings.instance is not None:
+			raise RuntimeWarning("Settings instance has already been initialized! Use Editor.getSettings instead")
+			
+		Settings.instance = self
+	
+		if os.path.exists('settings.xml') is False:
+			shutil.copyfile('settings-dist.xml', 'settings.xml')
+			
+		self.tree = ET.parse('settings.xml')
+		self.root_element = self.tree.getroot()
+		self.validateTree()
+		
+	def validateTree(self):
+		""" Iterates the settings tree and prints warning when an invalid tag is found """
+		for c in self.root_element.getchildren():
+			if c.tag != "Module":
+				print "Invalid tag in settings.xml. Expected Module, got: ", c.tag
+			elif c.get("name", "") == "":
+				print "Invalid tag in settings.xml. Module name is empty."
+			else:
+				for e in c.getchildren():
+					if e.tag != "Setting":
+						print "Invalid tag in settings.xml in module: ",c.tag,
+						print ". Expected Setting, got: ", e.tag
+					elif c.get("name", "") == "":
+						print "Invalid tag in settings.xml in module: ",c.tag,
+						print ". Setting name is empty", e.tag
+		
+	def getModuleTree(self, module):
+		""" Returns a module element from the settings tree. If no module with the specified
+		name exists, a new element will be created. """
+		if not isinstance(module, str) and not isinstance(module, unicode):
+			raise AttributeError("Settings:getModuleTree: Invalid type for module argument.")
+			
+		for c in self.root_element.getchildren():
+			if c.tag == "Module" and c.get("name", "") == module:
+				return c
+	
+		# Create module
+		return ET.SubElement(self.root_element, "Module", {"name":module})
+
+	def get(self, module, name, defaultValue=None):
+		""" Gets the value of a specified setting
+		
+		defaultValue is returned if the setting does not exist
+		"""
+		if not isinstance(name, str) and not isinstance(name, unicode):
+			raise AttributeError("Settings:get: Invalid type for name argument.")
+		
+		moduleTree = self.getModuleTree(module)
+		element = None
+		for e in moduleTree.getchildren():
+			if e.tag == "Setting" and e.get("name", "") == name:
+				element = e
+				break
+		else: 
+			return defaultValue
+		
+		e_value = element.text
+		e_strip = element.get("strip", "1").strip().lower()
+		e_type	= str(element.get("type", "str")).strip()
+		
+		if e_value is None: 
+			return defaultValue
+		
+		# Strip value
+		if e_strip == "" or e_strip == "false" or e_strip == "no" or e_strip == "0":
+			e_strip = False
+		else: e_strip = True
+		
+		if e_type == "str" or e_type == "unicode":
+			if e_strip: e_value = e_value.strip()
+		else:
+			e_value = e_value.strip()
+		
+		# Return value
+		if e_type == 'int':
+			return int(e_value)
+		elif e_type == 'float':
+			return float(e_value)
+		elif e_type == 'bool':
+			e_value = e_value.lower()
+			if e_value == "" or e_value == "false" or e_value == "no" or e_value == "0":
+				return False
+			else:
+				return True
+		elif e_type == 'str':
+			return str(e_value)
+		elif e_type == 'unicode':
+			return unicode(e_value)
+		elif e_type == 'list':
+			return self._deserializeList(e_value)
+		elif e_type == 'dict':
+			return self._deserializeDict(e_value)
+
+	def set(self, module, name, value, extra_attrs={}):
+		"""
+		Sets a setting to specified value.
+		
+		Parameters:
+		module (str|unicode): Module where the setting should be set
+		name (str|unicode): Name of setting
+		value: Value to assign to setting
+		extra_attrs (dict): Extra attributes to be stored in the XML-file
+		"""
+		if not isinstance(name, str) and not isinstance(name, unicode):
+			raise AttributeError("Settings:set: Invalid type for name argument.")
+			
+		moduleTree = self.getModuleTree(module)
+		e_type = "str"
+		
+		if isinstance(value, bool): # This must be before int
+			e_type = "bool"
+			value = str(value)
+		elif isinstance(value, int):
+			e_type = "int"
+			value = str(value)
+		elif isinstance(value, float):
+			e_type = "float"
+			value = str(value)
+		elif isinstance(value, unicode):
+			e_type = "unicode"
+			value = unicode(value)
+		elif isinstance(value, list):
+			e_type = "list"
+			value = self._serializeList(value)
+		elif isinstance(value, dict):
+			e_type = "dict"
+			value = self._serializeDict(value)
+		else:
+			e_type = "str"
+			value = str(value)
+		
+		for e in moduleTree.getchildren():
+			if e.tag != "Setting": continue
+			if e.get("name", "") == name:
+				e.text = value
+				break
+		else:
+			attrs = {"name":name, "type":e_type}
+			for k in extra_attrs:
+				if k not in attrs:
+					attrs[k] = extra_args[k]
+			elm = ET.SubElement(moduleTree, "Setting", attrs)
+			elm.text = value
+
+	def saveSettings(self):
+		""" Save settings into settings.xml """
+		self._indent(self.root_element)
+		self.tree.write('settings.xml', 'UTF-8')
+		
+	def _indent(self, elem, level=0):
+		""" 
+		Adds whitespace, so the resulting XML-file is properly indented.
+		Shamelessly stolen from http://effbot.org/zone/element-lib.htm 
+		"""
+		i = "\n" + level*"  "
+		if len(elem):
+			if not elem.text or not elem.text.strip():
+				elem.text = i + "  "
+			if not elem.tail or not elem.tail.strip():
+				elem.tail = i
+			for elem in elem:
+				self._indent(elem, level+1)
+			if not elem.tail or not elem.tail.strip():
+				elem.tail = i
+		else:
+			if level and (not elem.tail or not elem.tail.strip()):
+				elem.tail = i
+		
+	# FIXME:
+	# These serialization functions are not reliable at all
+	# This will only serialize the first level of a dict or list
+	# It will not check the types nor the content for conflicts.
+	# Perhaps we should add a small serialization library?
+	def _serializeList(self, list):
+		""" Serializes a list, so it can be stored in a text file """
+		return " ; ".join(list)
+		
+	def _deserializeList(self, string):
+		""" Deserializes a list back into a list object """
+		return string.split(" ; ")
+
+	def _serializeDict(self, dict):
+		""" Serializes a list, so it can be stored in a text file """
+		serial = ""
+		for key in dict:
+			value = dict[key]
+			if serial != "": serial += " ; "
+			serial += str(key)+" : "+str(value)
+			
+		return serial
+	
+	def _deserializeDict(self, serial):
+		""" Deserializes a list back into a dict object """
+		dict = {}
+		items = serial.split(" ; ")
+		for i in items:
+			kv_pair = i.split(" : ")
+			dict[kv_pair[0]] = kv_pair[1]
+		return dict
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/tests/__init__.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,3 @@
+""" This folder contains unit tests for various parts of the editor.
+	It's mostly here because I don't want to rewrite this code =)
+"""
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/tests/undotest.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,113 @@
+import scripts.undomanager as undomanager
+
+print "Testing undomanager:"
+umanager = undomanager.UndoManager(branchedMode=False)
+
+print "--- Testing basic undo/redo ---"
+
+print "Adding 3 actions:"
+def undoPrint(msg):
+	print msg
+def newAction(nr):
+	undo = lambda: undoPrint("Undo: "+str(nr))
+	redo = lambda: undoPrint("Redo: "+str(nr))
+	return undomanager.UndoObject(undo, redo, "Name: "+str(nr), "Description: "+str(nr))
+
+
+umanager.addAction(newAction("1"))
+umanager.addAction(newAction("2"))
+umanager.addAction(newAction("3"))
+
+print "Undoing 3 actions:"
+umanager.undo(3);
+
+print "Redoing 3 actions, individually:"
+umanager.redo()
+umanager.redo()
+umanager.redo()
+
+print "--- Testing undogroups ---"
+print "10 actions, 2 undogroups nested within 1 undogroup"
+umanager.startGroup("ActionGroup 1", "Desc: AG1")
+umanager.addAction(newAction("1"))
+umanager.addAction(newAction("2"))
+umanager.addAction(newAction("3"))
+umanager.startGroup("ActionGroup 2", "AG2")
+umanager.addAction(newAction("4"))
+umanager.addAction(newAction("5"))
+umanager.addAction(newAction("6"))
+umanager.endGroup()
+umanager.startGroup("ActionGroup 2", "AG2")
+umanager.addAction(newAction("7"))
+umanager.addAction(newAction("8"))
+umanager.addAction(newAction("9"))
+umanager.addAction(newAction("10"))
+umanager.endGroup()
+umanager.endGroup()
+
+print "Undoing group:"
+umanager.undo()
+
+print "Redo"
+umanager.redo()
+
+print "--- Testing branches (without branch mode) ---"
+print "Branch mode:", umanager.getBranchMode()
+
+print "Setting up actions"
+umanager.addAction(newAction("1-1"))
+umanager.addAction(newAction("1-2"))
+umanager.addAction(newAction("1-3"))
+umanager.undo(3)
+umanager.addAction(newAction("2-1"))
+umanager.addAction(newAction("2-2"))
+umanager.addAction(newAction("2-3"))
+umanager.undo(3)
+umanager.addAction(newAction("3-1"))
+umanager.addAction(newAction("3-2"))
+umanager.addAction(newAction("3-3"))
+umanager.undo(3)
+print "Branches", umanager.getBranches()
+print "Next branch"
+umanager.nextBranch()
+umanager.redo(3)
+umanager.undo(3)
+print "Prev branch"
+umanager.previousBranch()
+umanager.redo(3)
+
+print "--- Testing branches (with branch mode) ---"
+print "Activating branch mode:"
+umanager.setBranchMode(True)
+print "Branch mode:", umanager.getBranchMode()
+
+print "Setting up actions"
+umanager.addAction(newAction("1-1"))
+umanager.addAction(newAction("1-2"))
+umanager.addAction(newAction("1-3"))
+umanager.undo(3)
+umanager.addAction(newAction("2-1"))
+umanager.addAction(newAction("2-2"))
+umanager.addAction(newAction("2-3"))
+umanager.undo(3)
+umanager.addAction(newAction("3-1"))
+umanager.addAction(newAction("3-2"))
+umanager.addAction(newAction("3-3"))
+umanager.undo(3)
+print "Branches", umanager.getBranches()
+print "Next branch"
+umanager.nextBranch()
+umanager.redo(3)
+umanager.undo(3)
+print "Prev branch"
+umanager.previousBranch()
+umanager.redo(3)
+
+import gc
+print "--- Testing clear() function ---"
+print "Checking garbage:", gc.collect(), "Uncollectable:", len(gc.garbage)
+print "Clearing history"
+umanager.clear()
+print "Checking garbage:", gc.collect(), "Uncollectable:", len(gc.garbage)
+
+print "--- Done ---"
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/scripts/undomanager.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,324 @@
+from events.signal import Signal
+import collections
+import pdb
+
+actionAdded = Signal(providing_args=["action"])
+preUndo = Signal()
+postUndo = Signal()
+preRedo = Signal()
+postRedo = Signal()
+cleared = Signal()
+modeChanged = Signal(providing_args=["mode"])
+changed = Signal()
+
+class UndoManager:
+	"""
+	The undo manager provides advanced undo functionality.
+	
+	Add actions with addAction. If you want to add a lot of actions and 
+	group them, use startGroup and endGroup. When you undo a group, you will
+	undo all the actions within it.
+	
+	If branched mode is enabled, you will not overwrite the redostack when
+	adding an action. Instead, a new branch will be created with the new actions.
+	To navigate in branches, you can use nextBranch and redoBranch.
+	
+	Example
+	=======
+	# Init undomanager
+	undomanager = UndoManager()
+	
+	def doSomething():
+		# Adds an action to the undomanager
+		undocallback = lambda: doSomethingElse()
+		redocallback = lambda: doSomething()
+		action = UndoObject("Did something", "Something was done somewhere in the program.", "icon.png")
+		undomanager.addAction(action)
+	
+	def doLotOfActions():
+		# Starts an actiongroup and adds three actions
+		undomanager.startGroup("Did lot of actions", "Lot of actions was done somewhere in the program")
+		doSomething()
+		doSomething()
+		doSomething()
+		undomanager.endGroup()
+	
+	# This will create an actiongroup with three actions, and undo it
+	doLotOfActions()
+	undomanager.undo()
+	"""
+
+	def __init__(self, branchedMode = False):
+		self._groups = []
+		self._branched_mode = False
+		
+		def warn(msg):
+			print "Warning: ",msg
+		self.first_item = UndoStackItem(UndoObject(None, None))
+		self.first_item.object.name = "First item"
+		self.first_item.object.description = "First item in stack. Placeholder"
+		self.first_item.object.undoCallback = lambda: warn("Tried to undo first item")
+		self.first_item.object.redoCallback = lambda: warn("Tried to redo first item")
+		
+		self.current_item = self.first_item
+
+	def startGroup(self, name="", description="", icon=""):
+		"""
+		Starts an undogroup. Subsequent items will be added to the group
+		until endGroup is called. Undogroups can be nested.
+		
+		name, description and icon are information that can be used by
+		scripts which analyze the undostack.
+		"""
+		undogroup = UndoGroup(name, description, icon)
+		self._groups.append(undogroup)
+		return undogroup
+	
+	def endGroup(self):
+		"""
+		Ends the undogroup.
+		"""
+		if len(self._groups) <= 0:
+			print "Warning: UndoManager: No groups to end!"
+			return
+			
+		group = self._groups.pop()
+		self.addAction(group)
+
+	def addAction(self, action):
+		""" 
+		Adds an action to the stack.
+		
+		If the redostack is not empty and branchmode is enabed,
+		a new branch will be created. If branchmod is disabled,
+		the redo branch will be cleared.
+		"""
+		
+		if len(self._groups) > 0:
+			self._groups[len(self._groups)-1].addObject(action)
+		else:
+			stackitem = UndoStackItem(action)
+			
+			stackitem.previous = self.current_item
+			if self._branched_mode:
+				stackitem.previous.addBranch(stackitem)
+			else:
+				stackitem.previous.next = stackitem
+			
+			self.current_item = stackitem
+			
+			actionAdded.send(sender=self, action=action)
+			changed.send(sender=self)
+
+	def clear(self):
+		"""
+		Clears the undostack.
+		"""
+		self._groups = []
+		self.first_item.clearBranches()
+		self.current_item = self.first_item
+		
+		cleared.send(sender=self)
+		changed.send(sender=self)
+
+	# Linear undo
+	def undo(self, amount=1): 
+		""" Undo [amount] items """
+		if amount <= 0:
+			return
+	
+		preUndo.send(sender=self)
+		for i in range(amount):
+			if self.current_item == self.first_item:
+				print "Warning: UndoManager: Tried to undo non-existing action."
+				break
+			
+			self.current_item.object.undo()
+			self.current_item = self.current_item.previous
+			
+		postUndo.send(sender=self)
+		changed.send(sender=self)
+			
+			
+	def redo(self, amount=1):
+		""" Redo [amount] items. """
+		if amount <= 0:
+			return
+		
+		preRedo.send(sender=self)
+		for i in range(amount):
+			if self.current_item.next is None:
+				print "Warning: UndoManager: Tried to redo non-existing action."
+				break
+				
+			self.current_item = self.current_item.next
+			self.current_item.object.redo()
+				
+		postRedo.send(sender=self)
+		changed.send(sender=self)
+
+	def getBranchMode(self): 
+		""" Returns true if branch mode is enabled """
+		return self._branched_mode
+		
+	def setBranchMode(self, enable):
+		""" Enable or disable branch mode """
+		self._branched_mode = enable
+		changed.send(sender=self)
+
+	def getBranches(self): 
+		""" Returns branches from current stack item. """
+		return self.current_item.getBranches()
+	def nextBranch(self): 
+		""" Switch to next branch in current item """
+		self.current_item.nextBranch()
+		changed.send(sender=self)
+		
+	def previousBranch(self):
+		""" Switch previous branch in current item """
+		self.current_item.previousBranch()
+		changed.send(sender=self)
+
+class UndoObject:
+	""" UndoObject contains all the information that is needed to undo or redo an action,
+		as well as representation of it.
+		
+		ATTRIBUTES
+		==========
+		- name: Name used by scripts analyzing the undostack to represent this item
+		- description: Description of this item
+		- icon: Icon used to represent this item
+		- redoCallback: Function used to redo this item
+		- undoCallback: Function used to undo
+		
+	"""
+	def __init__(self, undoCallback, redoCallback, name="", description="", icon=""):
+		self.name = name
+		self.redoCallback = redoCallback
+		self.undoCallback = undoCallback
+		self.description = description
+		self.icon = icon
+		
+		self.undone = False
+
+	def undo(self): 
+		""" Undoes the action. Do not use directly! """
+		if self.undone is True:
+			print "Tried to undo already undone action!"
+			return
+			
+		self.undone = True
+		self.undoCallback()
+		
+	def redo(self): 
+		""" Redoes the action. Do not use directly! """
+		if self.undone is False:
+			print "Tried to redo already redone action!"
+			return
+			
+		self.undone = False
+		self.redoCallback()
+		
+class UndoGroup:
+	""" 
+	Contains a list of actions. Used to group actions together.
+	
+	Use UndoManager.startGroup and UndoManager.endGroup.
+	
+	"""
+	def __init__(self, name="", description="", icon=""):
+		self.name = name
+		self.description = description
+		self.icon = icon
+		
+		self.undoobjects = []
+		
+	def addObject(self, object):
+		""" Adds an action to the list """
+		self.undoobjects.append(object)
+	
+	def getObjects(self):
+		""" Returns a list of the actions contained """
+		return self.undoobjects
+		
+	def undo(self):
+		""" Undoes all actions. """
+		for action in reversed(self.undoobjects):
+			action.undo()
+		
+	def redo(self):
+		""" Redoes all actions. """
+		for action in self.undoobjects:
+			action.redo()
+
+class UndoStackItem:
+	""" Represents an action or actiongroup in the undostack. Do not use directly! """
+	def __init__(self, object):
+		self._branches = []
+		self._currentbranch = -1
+		
+		self.parent = None
+		self.object = object
+		self.previous = None
+		self.next = None
+		
+	def getBranches(self):
+		""" Returns a list of the branches """
+		return self._branches;
+	
+	def addBranch(self, item):
+		""" Adds a branch to the list and sets this item to point to that branch """
+		self._branches.append(item)
+		
+		self._currentbranch += 1
+		self.next = self._branches[self._currentbranch]
+		self.next.parent = self
+		
+	def nextBranch(self):
+		""" Sets this item to point to next branch """
+		if len(self._branches) <= 0:
+			return
+		self._currentbranch += 1
+		if self._currentbranch >= len(self._branches):
+			self._currentbranch = 0
+		self.next = self._branches[self._currentbranch]
+		changed.send(sender=self)
+			
+	def previousBranch(self):
+		""" Sets this item to point to previous branch """
+		if len(self._branches) <= 0:
+			return
+			
+		self._currentbranch -= 1
+		if self._currentbranch < 0:
+			self._currentbranch = len(self._branches)-1
+		self.next = self._branches[self._currentbranch]
+		changed.send(sender=self)
+		
+	def setBranchIndex(self, index):
+		""" Set this item to point to branches[index] """
+		if index < 0 or index >= len(self._branches):
+			return
+		self._currentbranch = index
+		changed.send(sender=self)
+		self.next = self._branches[self._currentbranch]
+		
+	def clearBranches(self):
+		""" Removes all branches """
+		self._branches = []
+		self._currentbranch = -1
+		self._next = None
+		changed.send(sender=self)
+	
+	def setBranch(self, branch):
+		""" Set this item to point to branch. Returns True on success. """
+		for b in range(len(self._branches)):
+			if self._branches[b] == branch:
+				self._currentbranch = b
+				self.next = self._branches[self._currentbranch]
+				changed.send(sender=self)
+				return True
+		else:
+			print "Didn't find branch!"
+			return False
+	
\ No newline at end of file
--- a/clients/editor/selection.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-import pychan
-import pychan.widgets as widgets
-
-class Selection():
-	"""
-	Selection displays a list of options for the user to select from. The result is passed to onSelection.
-	list - the list to select from
-	onSelection - the function to call when a selection is made. Accepts one argument: an element of the list.
-	"""
-	def __init__(self, list, onSelection):
-		self.list = list
-		self._callback = onSelection
-
-		self._widget = pychan.loadXML('gui/selection.xml')
-
-		self._widget.mapEvents({
-			'okButton'     : self._selected,
-			'cancelButton' : self._widget.hide
-		})
-
-		self._widget.distributeInitialData({
-			'optionDrop' : list
-		})
-		self._widget.show()
-
-	def _selected(self):
-		selection = self._widget.collectData('optionDrop')
-		if selection < 0: return
-		self._callback(self.list[selection])
-		self._widget.hide()
-
-class ClickSelection():
-	"""
-	ClickSelection displays a list of options for the user to select from. The result is passed to onSelection.
-	Differs from Selection: the selection is made when a list element is clicked, rather than when the box is closed.	
-	list - the list to select from
-	onSelection - the function to call when a selection is made. Accepts one argument: an element of the list.
-	"""
-	def __init__(self, list, onSelection):
-		self.list = list
-		self._callback = onSelection
-
-		self._widget = pychan.loadXML('gui/selection.xml')
-
-		self._widget.mapEvents({
-			'okButton'     : self._widget.hide,
-			'cancelButton' : self._widget.hide,
-			'optionDrop'   : self._selected
-		})
-
-		self._widget.distributeInitialData({
-			'optionDrop' : list
-		})
-		self._widget.show()
-
-	def _selected(self):
-		selection = self._widget.collectData('optionDrop')
-		if selection < 0: return
-		self._callback(self.list[selection])
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/editor/settings-dist.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,28 @@
+<?xml version='1.0' encoding='UTF-8'?>
+<Settings>
+	<Module name="FIFE">
+		<Setting name="FullScreen" type="int"> 0 </Setting>
+		<Setting name="PlaySounds" type="int"> 1 </Setting>
+		<Setting name="RenderBackend" type="str"> OpenGL </Setting>
+		<Setting name="ScreenWidth" type="int"> 1024 </Setting>
+		<Setting name="ScreenHeight" type="int"> 768 </Setting>
+		<Setting name="BitsPerPixel" type="int"> 0 </Setting>
+		<Setting name="InitialVolume" type="float"> 5.0 </Setting>
+		<Setting name="SDLRemoveFakeAlpha" type="int"> 1 </Setting>
+		<Setting name="WindowTitle" type="str"> FIFE - Editor </Setting>
+		<Setting name="WindowIcon" type="str"></Setting>
+		<Setting name="MapFile" type="str"> maps/shrine.xml </Setting>
+		<Setting name="Font" type="str"> fonts/FreeSans.ttf </Setting>
+		<Setting name="FontGlyphs" strip="0" type="str"> abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?-+/():;%&amp;`'*#=[]\"</Setting>
+		<Setting name="LogModules" type="list"> controller</Setting>
+		<Setting name="PychanDebug" type="bool"> False </Setting>
+		<Setting name="LogToPrompt" type="int"> 1 </Setting>
+		<Setting name="LogToFile" type="int"> 0 </Setting>
+	</Module>
+	<Module name="Plugins">
+		<Setting name="HistoryManager" type="bool">True</Setting>
+		<Setting name="LayerTool" type="bool">True</Setting>
+		<Setting name="ObjectEdit" type="bool">True</Setting>
+		<Setting name="ObjectSelector" type="bool">True</Setting>
+	</Module>
+</Settings>
--- a/clients/editor/settings.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,18 +0,0 @@
-BitsPerPixel        = 0
-FullScreen          = 0
-InitialVolume       = 5
-PlaySounds          = 1
-RenderBackend       = "OpenGL"
-SDLRemoveFakeAlpha  = 1
-ScreenWidth         = 1024
-ScreenHeight        = 768
-
-Font                = 'fonts/FreeSans.ttf'
-FontGlyphs          = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?-+/():;%&`'*#=[]\""
-
-LogModules          = ['controller']
-LogToPrompt         = 1
-LogToFile           = 0
-
-WindowTitle			= 'FIFE - Editor client'
-WindowIcon			= ''
--- a/clients/pychan_demo/dynamic.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/pychan_demo/dynamic.py	Mon Jun 08 16:00:02 2009 +0000
@@ -1,53 +1,18 @@
 #!/usr/bin/env python
-# coding: utf-8
-
-import sys, os, re
-
-def _jp(path):
-	return os.path.sep.join(path.split('/'))
-
-_paths = ('../../engine/swigwrappers/python', '../../engine/extensions')
-for p in _paths:
-	if p not in sys.path:
-		sys.path.append(_jp(p))
-
-import fife
-import fifelog
-import settings
+# -*- coding: utf-8 -*-
 
 import pychan
-
 from pychan_test import PyChanExample
 
-class Test(object):
-	def __init__(self,button,text1,text2):
-		super(Test,self).__init__()
-		self.button = button
-		self.text1 = text1
-		self.text2 = text2
-		self.button.text = text1
-
-	def mouseEntered(self, event):
-		print event
-		self.button.text = self.text2
-
-	def mouseExited(self, event):
-		print event
-		self.button.text = self.text1
-
-
 class DynamicExample(PyChanExample):
 	def __init__(self):
 		super(DynamicExample,self).__init__('gui/dynamic.xml')
 		
 	def start(self):
 		self.widget = pychan.loadXML(self.xmlFile)
-		self.l = Test(self.widget.findChild(name="okButton"),"Ok?","Ok!")
 		self.widget.mapEvents({
 			'okButton'   :self.stop,
 			'addButton'  :self.addLabel,
-			'okButton/mouseEntered' : self.l.mouseEntered,
-			'okButton/mouseExited'  : self.l.mouseExited
 		})
 		self.labelBox = self.widget.findChild(name="labelBox")
 		self.widget.show()
@@ -61,5 +26,5 @@
 		self.widget.adaptLayout()
 		
 	def removeLabel(self,widget=None):
-		widget._parent.removeChild(widget)
+		widget.parent.removeChild(widget)
 		self.widget.adaptLayout()
--- a/clients/pychan_demo/gui/demoapp.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/pychan_demo/gui/demoapp.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,30 +1,22 @@
-<Window title="This is the PyChan demo application. "><!--[PyChanã®ã¦ã™ã¨ã§ã™: ÜÖÄ]"-->
-	<HBox>
-		<Slider size="100,15" name="slider" orientation="0" scale_start="0.1" scale_end="1.5" base_color="93,161,102" />
-		<Label name="slider_desc" text="Move the slider!" />
-		<TextBox name="slider_value" text="0" base_color="0,0,0,0"/>
-	</HBox>
-	<VBox>
-		<HBox>
-			<ScrollArea size="200,500">
-				<ListBox name="demoList" min_size="100,100" />
+<VBox hexpand="1"><!--[PyChanã®ã¦ã™ã¨ã§ã™: ÜÖÄ]"-->
+	<Label text="This is the PyChan demo application. "/>
+	<HBox vexpand="1">
+		<ScrollArea>
+			<ListBox name="demoList"/>
+		</ScrollArea>
+		<VBox margins="0,0" hexpand="2">
+			<Label text=" Select one example from the listbox."/>
+			<Label text=" You can also execute this script with an Gui XML file as argument."/>
+-			<HBox margins="0,0">
+				<Button text="XML code"/>
+			</HBox>
+			<ScrollArea>
+				<TextBox name="xmlSource" font="FreeMono" filename="gui/all_widgets.xml"/>
 			</ScrollArea>
-			<VBox margins="0,0">
-				<Label text=" Select one example from the listbox and press 'Start Example'"/>
-				<Label text=" You can also execute this script with an Gui XML file as argument."/>
-				<HBox margins="0,0">
-					<Button text="XML code"/>
-				</HBox>
-				<ScrollArea size="600,200">
-					<TextBox name="xmlSource" filename="gui/all_widgets.xml"/>
-				</ScrollArea>
-				<Spacer />
-			</VBox>
-		</HBox>
-		<HBox>
-			<ClickLabel name="creditsLink" text="Credits"/>
-			<Spacer />
-			<Button name="closeButton" text="Quit Demo"/>
-		</HBox>
-	</VBox>
-</Window>
+		</VBox>
+	</HBox>
+	<HBox>
+		<ClickLabel name="creditsLink" text="Credits"/>
+		<Button name="closeButton" text="Quit Demo"/>
+	</HBox>
+</VBox>
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/pychan_demo/gui/slider.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,11 @@
+<Window title="Slider Example">
+	<Container size="300,300">
+		<Icon name="icon" image="gui/icons/pychan_logo.png"/>
+	</Container>
+	<Label text="Move the sliders around to position the icon."/>
+	<HBox><Label hexpand="0" text="X:"/><Label name="xvalue"/></HBox>
+	<Slider name="xslider" scale_start="0" scale_end="300"/>
+	<HBox><Label hexpand="0" text="Y:"/><Label name="yvalue"/></HBox>
+	<Slider name="yslider" scale_start="0" scale_end="300"/>
+	<Button name="closeButton" text="Close"/>
+</Window>
--- a/clients/pychan_demo/gui/styling.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/pychan_demo/gui/styling.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -1,5 +1,5 @@
 <Window title="Styling" style="greenzone">
-	<Label text="These guys contributed to FIFE. Man!" />
+	<Label text="Select a style and click on 'Test Style'[br]The window on the left should adapt accordingly." />
 	<ScrollArea size="400,400">
 		<ListBox name="styleList"/>
 	</ScrollArea>
--- a/clients/pychan_demo/pychan_test.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/pychan_demo/pychan_test.py	Mon Jun 08 16:00:02 2009 +0000
@@ -1,6 +1,5 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
-# coding: utf-8
 # This is the pychan demo client for FIFE.
 
 import sys, os, re
@@ -17,98 +16,118 @@
 import fifelog
 import basicapplication
 import pychan
+from pychan.dialogs import trace
 
 class PyChanExample(object):
+	"""
+	Example class.
+	"""
 	def __init__(self,xmlFile):
 		self.xmlFile = xmlFile
 		self.widget = None
 	
 	def start(self):
+		"""
+		The Example Protocoll: start
+		"""
+		# For simplicity the most basic examples should define
+		# a okButton and/or a closeButton. Those are mapped
+		# to the stop handler.
 		self.widget = pychan.loadXML(self.xmlFile)
 		eventMap = {
 			'closeButton':self.stop,
 			'okButton'   :self.stop
 		}
+		# Since the basic example are not required to
+		# supply close and ok button, we 'ignoreMissing'
 		self.widget.mapEvents(eventMap, ignoreMissing = True)
 		self.widget.show()
 
 	def stop(self):
+		"""
+		The Example Protocoll: stop
+		"""
 		if self.widget:
 			self.widget.hide()
 		self.widget = None
 
-#def testTimer():
-	#import timer
-	#timer.init( pychan.manager.hook.engine.getTimeManager() )
-	#def spam():
-		#print "SPAM SPAM"
-		#return 1
-	#repeater = timer.repeatCall(500,spam)
-	#def stop_spam():
-		#repeater.stop()
-		#print "BACON EGGS AND SPAM"
-	#timer.delayCall(5000,stop_spam)
-
-
 class DemoApplication(basicapplication.ApplicationBase):
 	def __init__(self):
+		# Let the ApplicationBase initialise FIFE
 		super(DemoApplication,self).__init__()
-		
+
+		# Init Pychan
 		pychan.init(self.engine,debug=False)
 		pychan.loadFonts("fonts/freefont.fontdef")
 		pychan.manager.setDefaultFont("FreeSans")
-		#pychan.manager.setDefaultFont("Kochi")
 		pychan.setupModalExecution(self.mainLoop,self.breakFromMainLoop)
-		
+
+		# Build the main GUI
 		self.gui = pychan.loadXML('gui/demoapp.xml')
-		self.gui.findChild(name="xmlSource").font = "FreeMono"
+		self.gui.min_size = self.engine.getRenderBackend().getScreenWidth(),self.engine.getRenderBackend().getScreenHeight()
 
 		eventMap = {
 			'creditsLink'  : self.showCredits,
 			'closeButton'  : self.quit,
 			'demoList' : self.selectExample,
-			'slider': self.test_slider
 		}
 		self.gui.mapEvents(eventMap)
+
+		# A simple hover-effect for the credits label
 		credits = self.gui.findChild(name="creditsLink")
-		credits.setEnterCallback(lambda w : credits._setText("CREDITS"))
-		credits.capture(lambda : credits._setText("Credits"), event_name="mouseExited")
-		def pr(event=None):
-			print event
-		self.gui.capture(pr,event_name="keyPressed")
+		# setEnterCallback is deprecated - we use it here to test it.
+		credits.setEnterCallback(lambda w : credits._setText(u"CREDITS"))
+		# Note that we can't simply write:
+		# credits.capture(credits._setText(u"Credits"), event_name="mouseExited")
+		# that's because that would call credits._setText _NOW_ and we want to call
+		# it later.
+		credits.capture(lambda : credits._setText(u"Credits"), event_name="mouseExited")
 
+		# Our list of examples
+		# We keep a dictionary of these and fill
+		# the ListBox on the left with its names.
 		from dynamic import DynamicExample
 		from styling import StylingExample
-		
+		from sliders import SliderExample
 		self.examples = {
 			'Absolute Positioning' : PyChanExample('gui/absolute.xml'),
 			'All Widgets' : PyChanExample('gui/all_widgets.xml'),
 			'Basic Styling' : StylingExample(),
 			'Dynamic Widgets' : DynamicExample(),
+			'Sliders' : SliderExample(),
 			'ScrollArea' : PyChanExample('gui/scrollarea.xml'),
 		}
 		self.demoList = self.gui.findChild(name='demoList')
-		self.demoList.items += self.examples.keys()
+		self.demoList.items = sorted(self.examples.keys())
+
+		# Finally show the main GUI
 		self.gui.show()
 		
-		self.slider = self.gui.findChild(name='slider')
-		self.slider_value = self.gui.findChild(name='slider_value')
-		
 		self.currentExample = None
 		self.creditsWidget = None
 
+	# We use the trace decorator which can help debugging the examples.
+	# mostly it's for show though :-)
+	@trace
 	def selectExample(self):
-		if self.demoList.selected_item is None: return
-		print "selected",self.demoList.selected_item
-		if self.currentExample: self.currentExample.stop()
+		"""
+		Callback handler for clicking on the example list.
+		"""
+		if self.demoList.selected_item is None:
+			return
+		#print "selected",self.demoList.selected_item
+		if self.currentExample:
+			self.currentExample.stop()
 		self.currentExample = self.examples[self.demoList.selected_item]
-		self.gui.findChild(name="xmlSource").text = open(self.currentExample.xmlFile).read()
+		self.gui.findChild(name="xmlSource").text = unicode(open(self.currentExample.xmlFile).read(), 'utf8')
 		self.currentExample.start()
 
 	def showCredits(self):
-		print pychan.loadXML('gui/credits.xml').execute({ 'okButton' : "Yay!" })
-	def test_slider(self):
-		self.slider_value._setText( str(self.slider.getValue()) )
+		"""
+		Callback handler from the credits link/label.
+		"""
+		# We use PyChan's synchronous execution feature here.
+		pychan.loadXML('gui/credits.xml').execute({ 'okButton' : "Yay!" })
 
 class TestXMLApplication(basicapplication.ApplicationBase):
 	"""
@@ -118,6 +137,9 @@
 	def __init__(self,xmlfile):
 		super(TestXMLApplication,self).__init__()
 		pychan.init(self.engine,debug=True)
+		self.start()
+	@trace
+	def start(self):
 		self.widget = pychan.loadXML(xmlfile)
 		self.widget.show()
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/clients/pychan_demo/sliders.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+
+from pychan_test import PyChanExample
+import pychan
+
+class SliderExample(PyChanExample):
+	def __init__(self):
+		super(SliderExample,self).__init__('gui/slider.xml')
+	def start(self):
+		self.widget = pychan.loadXML(self.xmlFile)
+		self.widget.mapEvents({
+			'xslider': self.update,
+			'yslider': self.update,
+			'closeButton':self.stop,
+		})
+		self.update()
+		self.widget.show()
+	def update(self):
+		"""
+		Update Icon position from the sliders.
+		"""
+		icon = self.widget.findChild(name="icon")
+		# sliders have floats, guichan is picky and wants ints
+		# so we convert here.
+		icon.position = map(int, self.widget.collectData('xslider','yslider'))
+		# we distribute to the labels with the x,y value.
+		# That's user visible 'text' - so pychan wants unicode.
+		self.widget.distributeInitialData({
+			'xvalue' : unicode(icon.x),
+			'yvalue' : unicode(icon.y),
+		})
--- a/clients/pychan_demo/styling.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/pychan_demo/styling.py	Mon Jun 08 16:00:02 2009 +0000
@@ -1,19 +1,8 @@
 #!/usr/bin/env python
-# coding: utf-8
-
-import sys, os, re
+# -*- coding: utf-8 -*-
 
-def _jp(path):
-	return os.path.sep.join(path.split('/'))
-
-_paths = ('../../engine/swigwrappers/python', '../../engine/extensions')
-for p in _paths:
-	if p not in sys.path:
-		sys.path.append(_jp(p))
 
 import fife
-import fifelog
-import settings
 
 import pychan
 
@@ -77,6 +66,9 @@
 			'base_color': fife.Color(80,200,80) ,
 			'background_color': fife.Color(200,250,200),
 			},
+		'Window' : {
+			'titlebar_height' : 30,
+		},
 		'ListBox' : {
 			'font' : 'samanata_large'
 			}
@@ -94,7 +86,12 @@
 		pychan.loadFonts("fonts/samanata.fontdef")
 
 	def start(self):
-		self.styledCredits = None
+		self.styledCredits = pychan.loadXML('gui/all_widgets.xml')
+		self.styledCredits.distributeInitialData({
+			'demoList' : map(lambda x:unicode(x,'utf8'),dir(pychan)),
+			'demoText' : unicode(pychan.__doc__,'utf8')
+		})
+
 		self.widget = pychan.loadXML(self.xmlFile)
 		self.widget.mapEvents({
 			'testStyle' : self.testStyle,
@@ -103,7 +100,10 @@
 		self.widget.distributeInitialData({
 			'styleList' : self.styles
 		})
+		self.widget.position_technique = 'right-20:center'
+		self.styledCredits.position_technique = 'left+20:center'
 		self.widget.show()
+		self.styledCredits.show()
 
 	def stop(self):
 		super(StylingExample,self).stop()
@@ -114,11 +114,5 @@
 		style = self.styles[self.widget.collectData('styleList')]
 		if self.styledCredits:
 			self.styledCredits.hide()
-		self.styledCredits = pychan.loadXML('gui/all_widgets.xml')
-		self.styledCredits.distributeInitialData({
-			'demoList' : dir(pychan),
-			'demoText' : pychan.__doc__
-		})
 		self.styledCredits.stylize(style)
-		self.styledCredits.mapEvents({'okButton':self.styledCredits.hide})
 		self.styledCredits.show()
--- a/clients/rio_de_hola/maps/shrine.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/rio_de_hola/maps/shrine.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -6110,8 +6110,8 @@
 			<i x="-19.0" o="beach_bar" z="0.0" y="2.0" r="180"></i>
 		</instances>
 	</layer>
-	<camera ref_cell_width="64" zoom="1.0" tilt="-42.0" id="main" ref_layer_id="TechdemoMapGroundObjectLayer" ref_cell_height="48" rotation="45.0">
+	<camera ref_cell_width="64" zoom="1.0" tilt="-42.0" id="shrine_main" ref_layer_id="TechdemoMapGroundObjectLayer" ref_cell_height="48" rotation="45.0">
 	</camera>
-	<camera ref_cell_width="128" zoom="1.0" tilt="-42.0" viewport="10,10,400,250" id="small" ref_layer_id="TechdemoMapTileLayer" ref_cell_height="96" rotation="45.0">
+	<camera ref_cell_width="128" zoom="1.0" tilt="-42.0" viewport="10,10,400,250" id="shrine_small" ref_layer_id="TechdemoMapTileLayer" ref_cell_height="96" rotation="45.0">
 	</camera>
 </map>
--- a/clients/rio_de_hola/maps/tourist_beach.xml	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/rio_de_hola/maps/tourist_beach.xml	Mon Jun 08 16:00:02 2009 +0000
@@ -4792,8 +4792,8 @@
 		<instances>
 		</instances>
 	</layer>
-	<camera ref_cell_width="128" zoom="0.822702474792" tilt="-42.0" viewport="0,0,1280,960" id="main" ref_layer_id="TileLayer" ref_cell_height="96" rotation="45.0">
+	<camera ref_cell_width="128" zoom="0.822702474792" tilt="-42.0" viewport="0,0,1280,960" id="tourist_beach_main" ref_layer_id="TileLayer" ref_cell_height="96" rotation="45.0">
 	</camera>
-	<camera ref_cell_width="128" zoom="1.0" tilt="-42.0" viewport="10,10,400,250" id="small" ref_layer_id="TileLayer" ref_cell_height="96" rotation="45.0">
+	<camera ref_cell_width="128" zoom="1.0" tilt="-42.0" viewport="10,10,400,250" id="tourist_beach_small" ref_layer_id="TileLayer" ref_cell_height="96" rotation="45.0">
 	</camera>
 </map>
--- a/clients/rio_de_hola/scripts/world.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/clients/rio_de_hola/scripts/world.py	Mon Jun 08 16:00:02 2009 +0000
@@ -172,8 +172,14 @@
 		For this techdemo two cameras are used. One follows the hero(!) via 'attach'
 		the other one scrolls around a bit (see the pump function).
 		"""
+		camera_prefix = self.filename.rpartition('.')[0] # Remove file extension
+		camera_prefix = camera_prefix.rpartition('/')[2] # Remove path
+		camera_prefix += '_'
+		
 		for cam in self.view.getCameras():
-			self.cameras[cam.getId()] = cam
+			camera_id = cam.getId().replace(camera_prefix, '')
+			self.cameras[camera_id] = cam
+			
 		self.cameras['main'].attach(self.hero.agent)
 
 		self.view.resetRenderers()
--- a/engine/core/gui/guimanager.cpp	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/gui/guimanager.cpp	Mon Jun 08 16:00:02 2009 +0000
@@ -25,6 +25,7 @@
 // 3rd party library includes
 #include <boost/filesystem/convenience.hpp>
 #include <guichan/sdl/sdlinput.hpp>
+#include <guichan/key.hpp>
 #include <guichan/focushandler.hpp>
 #include <guichan.hpp>
 
@@ -41,6 +42,7 @@
 #include "video/fonts/fontbase.h"
 #include "video/fonts/truetypefont.h"
 #include "video/fonts/subimagefont.h"
+#include "eventchannel/key/ec_key.h"
 #include "eventchannel/key/ec_keyevent.h"
 #include "eventchannel/mouse/ec_mouseevent.h"
 
@@ -238,7 +240,13 @@
 		keyevt.setAltPressed(gcnevt.isAltPressed());
 		keyevt.setMetaPressed(gcnevt.isMetaPressed());
 		keyevt.setNumericPad(gcnevt.isNumericPad());
-		keyevt.setKey(Key(static_cast<Key::KeyType>(gcnevt.getKey().getValue()), gcnevt.getKey().getValue()));
+
+		// Convert from guichan keyval to FIFE keyval
+		int keyval = gcnevt.getKey().getValue();
+		keyval = convertGuichanKeyToFifeKey(keyval);
+		
+		keyevt.setKey(Key(static_cast<Key::KeyType>(keyval), keyval));
+
 		return keyevt;
 	}
 
@@ -300,4 +308,162 @@
 		return mouseevt;
 	}
 
+
+	int GUIManager::convertGuichanKeyToFifeKey(int value) {
+
+		switch (value) {
+			case gcn::Key::TAB:
+				value = Key::TAB;
+				break;
+			case gcn::Key::LEFT_ALT:
+				value = Key::LEFT_ALT;
+				break;
+			case gcn::Key::RIGHT_ALT:
+				value = Key::RIGHT_ALT;
+				break;
+			case gcn::Key::LEFT_SHIFT:
+				value = Key::LEFT_SHIFT;
+				break;
+			case gcn::Key::RIGHT_SHIFT:
+				value = Key::RIGHT_SHIFT;
+				break;
+			case gcn::Key::LEFT_CONTROL:
+				value = Key::LEFT_CONTROL;
+				break;
+			case gcn::Key::RIGHT_CONTROL:
+				value = Key::RIGHT_CONTROL;
+				break;
+			case gcn::Key::BACKSPACE:
+				value = Key::BACKSPACE;
+				break;
+			case gcn::Key::PAUSE:
+				value = Key::PAUSE;
+				break;
+			case gcn::Key::SPACE:
+				value = Key::SPACE;
+				break;
+			case gcn::Key::ESCAPE:
+				value = Key::ESCAPE;
+				break;
+			case gcn::Key::DELETE:
+				value = Key::DELETE;
+				break;
+			case gcn::Key::INSERT:
+				value = Key::INSERT;
+				break;
+			case gcn::Key::HOME:
+				value = Key::HOME;
+				break;
+			case gcn::Key::END:
+				value = Key::END;
+				break;
+			case gcn::Key::PAGE_UP:
+				value = Key::PAGE_UP;
+				break;
+			case gcn::Key::PRINT_SCREEN:
+				value = Key::PRINT_SCREEN;
+				break;
+			case gcn::Key::PAGE_DOWN:
+				value = Key::PAGE_DOWN;
+				break;
+			case gcn::Key::F1:
+				value = Key::F1;
+				break;
+			case gcn::Key::F2:
+				value = Key::F2;
+				break;
+			case gcn::Key::F3:
+				value = Key::F3;
+				break;
+			case gcn::Key::F4:
+				value = Key::F4;
+				break;
+			case gcn::Key::F5:
+				value = Key::F5;
+				break;
+			case gcn::Key::F6:
+				value = Key::F6;
+				break;
+			case gcn::Key::F7:
+				value = Key::F7;
+				break;
+			case gcn::Key::F8:
+				value = Key::F8;
+				break;
+			case gcn::Key::F9:
+				value = Key::F9;
+				break;
+			case gcn::Key::F10:
+				value = Key::F10;
+				break;
+			case gcn::Key::F11:
+				value = Key::F11;
+				break;
+			case gcn::Key::F12:
+				value = Key::F12;
+				break;
+			case gcn::Key::F13:
+				value = Key::F13;
+				break;
+			case gcn::Key::F14:
+				value = Key::F14;
+				break;
+			case gcn::Key::F15:
+				value = Key::F15;
+				break;
+			case gcn::Key::NUM_LOCK:
+				value = Key::NUM_LOCK;
+				break;
+			case gcn::Key::CAPS_LOCK:
+				value = Key::CAPS_LOCK;
+				break;
+			case gcn::Key::SCROLL_LOCK:
+				value = Key::SCROLL_LOCK;
+				break;
+			case gcn::Key::RIGHT_META:
+				value = Key::RIGHT_META;
+				break;
+			case gcn::Key::LEFT_META:
+				value = Key::LEFT_META;
+				break;
+			case gcn::Key::LEFT_SUPER:
+				value = Key::LEFT_SUPER;
+				break;
+			case gcn::Key::RIGHT_SUPER:
+				value = Key::RIGHT_SUPER;
+				break;
+			case gcn::Key::ALT_GR:
+				value = Key::ALT_GR;
+				break;
+			case gcn::Key::UP:
+				value = Key::UP;
+				break;
+			case gcn::Key::DOWN:
+				value = Key::DOWN;
+				break;
+			case gcn::Key::LEFT:
+				value = Key::LEFT;
+				break;
+			case gcn::Key::RIGHT:
+				value = Key::RIGHT;
+				break;
+			case gcn::Key::ENTER:
+				value = Key::ENTER;
+				break;
+
+			default:
+				// Convert from unicode to lowercase letters
+				if (value >= 1 && value <= 26) {
+					// Control characters
+					value = value - 1 + 'a';
+				} else if (value >= 'A' && value <= 'Z') {
+					value = value - 'A' + 'a';
+				}
+
+				// FIXME: Accented characters (á) will not get converted properly.
+				break;
+		}
+
+		return value;
+	}
 }
--- a/engine/core/gui/guimanager.h	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/gui/guimanager.h	Mon Jun 08 16:00:02 2009 +0000
@@ -141,6 +141,9 @@
 			KeyEvent translateKeyEvent(const gcn::KeyEvent& evt);
 			MouseEvent translateMouseEvent(const gcn::MouseEvent& evt);
 
+		protected:
+			int convertGuichanKeyToFifeKey(int value);
+
 		private:
 			// The Guichan GUI.
 			gcn::Gui* m_gcn_gui;
--- a/engine/core/gui/widgets/clicklabel.cpp	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/gui/widgets/clicklabel.cpp	Mon Jun 08 16:00:02 2009 +0000
@@ -88,6 +88,8 @@
 		int textX = 0;
 		int textY = 0;
 
+		graphics->setColor(getBackgroundColor());
+		graphics->fillRectangle(Rectangle(1, 1, getDimension().width-1, getHeight() - 1));
 		if (mGuiFont) {
 			if( isTextWrapping() ) {
 				mGuiFont->drawMultiLineString(graphics, mWrappedText, textX, textY);
--- a/engine/core/gui/widgets/togglebutton.cpp	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/gui/widgets/togglebutton.cpp	Mon Jun 08 16:00:02 2009 +0000
@@ -181,6 +181,12 @@
 			w = std::max(m_hoverImage->getWidth(), w);
 			h = std::max(m_hoverImage->getHeight(), h);
 		}
+
+		if( mCaption.length() > 0 ) {
+			w = std::max(static_cast<int>(getFont()->getWidth(mCaption)+2*mSpacing), w);
+			h = std::max(static_cast<int>(getFont()->getHeight()+2*mSpacing), h);
+		}
+
 		setWidth(w);
 		setHeight(h);
 	}
--- a/engine/core/gui/widgets/togglebutton.h	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/gui/widgets/togglebutton.h	Mon Jun 08 16:00:02 2009 +0000
@@ -68,7 +68,7 @@
 			void draw(Graphics *graphics);
 
 			/**
-			 * Adjust size to fit image
+			 * Adjust size to fit image and caption
 			 */
 			void adjustSize();
 
--- a/engine/core/gui/widgets/widgets.i	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/gui/widgets/widgets.i	Mon Jun 08 16:00:02 2009 +0000
@@ -209,6 +209,8 @@
 		virtual const std::string& getCaption() const;
 		virtual void setAlignment(Graphics::Alignment alignment);
 		virtual Graphics::Alignment getAlignment() const;
+		void setSpacing(unsigned int spacing);
+		unsigned int getSpacing() const;
 		void setUpImage(Image* image);
 		void setDownImage(Image* image);
 		void setHoverImage(Image* image);
--- a/engine/core/model/structures/layer.cpp	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/model/structures/layer.cpp	Mon Jun 08 16:00:02 2009 +0000
@@ -145,6 +145,25 @@
 		return matching_instances;
 	}
 
+	std::vector<Instance*> Layer::getInstancesAt(Location& loc, bool use_exactcoordinates) {
+		std::vector<Instance*> matching_instances;
+		std::vector<Instance*>::iterator it = m_instances.begin();
+
+		for(; it != m_instances.end(); ++it) {
+			if (use_exactcoordinates) {
+				if ((*it)->getLocationRef().getExactLayerCoordinatesRef() == loc.getExactLayerCoordinatesRef()) {
+					matching_instances.push_back(*it);
+				}
+			} else {
+				if ((*it)->getLocationRef().getLayerCoordinates() == loc.getLayerCoordinates()) {
+					matching_instances.push_back(*it);
+				}
+			}
+		}
+
+		return matching_instances;
+	}
+
 	void Layer::getMinMaxCoordinates(ModelCoordinate& min, ModelCoordinate& max, const Layer* layer) const {
 		if (!layer) {
 			layer = this;
--- a/engine/core/model/structures/layer.h	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/model/structures/layer.h	Mon Jun 08 16:00:02 2009 +0000
@@ -149,6 +149,12 @@
 			 */
 			std::vector<Instance*> getInstances(const std::string& id);
 
+			/** Returns instances that match given location.
+			 * @param loc location where to fetch instances from
+			 * @param use_exactcoordinates if true, comparison is done using exact coordinates. if not, cell coordinates are used
+			 */
+			std::vector<Instance*> getInstancesAt(Location& loc, bool use_exactcoordinates=false);
+
 			/** Get the first instance on this layer with the given identifier.
 			 */
 			Instance* getInstance(const std::string& identifier);
--- a/engine/core/model/structures/layer.i	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/model/structures/layer.i	Mon Jun 08 16:00:02 2009 +0000
@@ -71,6 +71,7 @@
 
 			const std::vector<Instance*>& getInstances() const;
 			std::vector<Instance*> getInstances(const std::string& identifier);
+			std::vector<Instance*> getInstancesAt(Location& loc, bool use_exactcoordinates=false);
 			Instance* getInstance(const std::string& id);
 
 			void setInstancesVisible(bool vis);
--- a/engine/core/util/time/timemanager.cpp	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/util/time/timemanager.cpp	Mon Jun 08 16:00:02 2009 +0000
@@ -68,7 +68,7 @@
 		// It is very important to NOT use iterators (over a vector)
 		// here, as an event might add enough events to resize the vector.
 		// -> Ugly segfault
-		for (size_t i = 0; i != m_events_list.size(); ++i) {
+		for (size_t i = 0; i < m_events_list.size(); ++i) {
 			TimeEvent* event = m_events_list[ i ];
 			if( event ) {
 				event->managerUpdateEvent(m_current_time);
@@ -88,10 +88,10 @@
 
 	void TimeManager::unregisterEvent(TimeEvent* event) {
 		// Unregister.
-		std::vector<TimeEvent*>::iterator i = m_events_list.begin();
-		for(; i != m_events_list.end(); ++i) {
-			if( (*i) == event ) {
-				*i = 0;
+		for (size_t i = 0; i < m_events_list.size(); ++i) {
+			TimeEvent*& event_i = m_events_list[ i ];
+			if( event_i == event) {
+				event_i = 0;
 				return;
 			}
 		}
--- a/engine/core/video/cursor.cpp	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/video/cursor.cpp	Mon Jun 08 16:00:02 2009 +0000
@@ -20,6 +20,14 @@
  ***************************************************************************/
 
 // Standard C++ library includes
+#if defined( WIN32 )
+#include <windows.h>
+#include <sdl.h>
+#endif
+
+#if defined( __linux__ )
+#include <X11/Xcursor/Xcursor.h>
+#endif
 
 // 3rd party library includes
 
@@ -29,6 +37,7 @@
 // Second block: files included from the same folder
 #include "util/structures/rect.h"
 #include "util/time/timemanager.h"
+#include "util/log/logger.h"
 
 #include "imagepool.h"
 #include "animationpool.h"
@@ -37,10 +46,36 @@
 #include "renderbackend.h"
 #include "cursor.h"
 
+#if defined( WIN32 )
+
+// From SDL_sysmouse.c
+struct WMcursor {
+	HCURSOR curs;
+#ifndef _WIN32_WCE
+	Uint8 *ands;
+	Uint8 *xors;
+#endif
+};
+
+#endif
+
+#if defined( __linux__ )
+
+// Stops the compiler from confusing it with FIFE:Cursor
+typedef Cursor XCursor;
+
+// From SDL_x11mouse.c
+struct WMcursor {
+	Cursor x_cursor;
+};
+
+#endif
+
 namespace FIFE {
-	
+	static Logger _log(LM_GUI); // We should have a log module for cursor
+
 	Cursor::Cursor(ImagePool* imgpool, AnimationPool* animpool, RenderBackend* renderbackend):
-		m_cursor_id(0),
+		m_cursor_id(NC_ARROW),
 		m_drag_id(0),
 		m_cursor_type(CURSOR_NATIVE),
 		m_drag_type(CURSOR_NONE),
@@ -51,8 +86,10 @@
 		m_drag_animtime(0),
 		m_drag_offset_x(0),
 		m_drag_offset_y(0),
+		m_native_cursor(NULL),
 		m_timemanager(TimeManager::instance()) {
 		assert(m_timemanager);
+		set(m_cursor_type, m_cursor_id);
 	}
 	
 	void Cursor::set(MouseCursorType ctype, unsigned int cursor_id) {
@@ -60,6 +97,7 @@
 		m_cursor_type = ctype;
 		if (ctype == CURSOR_NATIVE) {
 			SDL_ShowCursor(1);
+			setNativeCursor(cursor_id);
 		} else {
 			SDL_ShowCursor(0);
 			if (ctype == CURSOR_ANIMATION) {
@@ -120,4 +158,169 @@
 			m_renderbackend->popClipArea();
 		}
 	}
+
+	unsigned int Cursor::getNativeId(unsigned int cursor_id) {
+#if defined( WIN32 )
+		switch (cursor_id) {
+			case NC_ARROW:
+				return 32512; // IDC_ARROW;
+			case NC_IBEAM:
+				return 32513; // IDC_IBEAM;
+			case NC_WAIT:
+				return 32514; // IDC_WAIT;
+			case NC_CROSS:
+				return 32515; // IDC_CROSS;
+			case NC_UPARROW:
+				return 32516; // IDC_UPARROW;
+			case NC_RESIZESE:
+				return 32642; // IDC_SIZENWSE;
+			case NC_RESIZESW:
+				return 32643; // IDC_SIZENESW;
+			case NC_RESIZEE:
+				return 32644; // IDC_SIZEWE;
+			case NC_RESIZES:
+				return 32645; // IDC_SIZENS;
+			case NC_RESIZENW:
+				return 32642; // IDC_SIZENWSE;
+			case NC_RESIZENE:
+				return 32643; // IDC_SIZENESW;
+			case NC_RESIZEW:
+				return 32644; // IDC_SIZEWE;
+			case NC_RESIZEN:
+				return 32645; // IDC_SIZENS;
+			case NC_RESIZEALL:
+				return 32646; // IDC_SIZEALL;
+			case NC_NO:
+				return 32648; // IDC_NO;
+			case NC_HAND:
+				return 32649; // IDC_HAND;
+			case NC_APPSTARTING:
+				return 32650; // IDC_APPSTARTING;
+			case NC_HELP:
+				return 32651; // IDC_HELP;
+			default:
+				break;
+		}
+
+#elif defined( __linux__ )
+		switch (cursor_id) {
+			case NC_ARROW:
+				return 68;
+			case NC_IBEAM:
+				return 152;
+			case NC_WAIT:
+				return 150;
+			case NC_CROSS:
+				return 130;
+			case NC_UPARROW:
+				return 22;
+			case NC_RESIZESE:
+				return 14;
+			case NC_RESIZESW:
+				return 12;
+			case NC_RESIZEE:
+				return 96;
+			case NC_RESIZES:
+				return 16;
+			case NC_RESIZENW:
+				return 134;
+			case NC_RESIZENE:
+				return 136;
+			case NC_RESIZEW:
+				return 70;
+			case NC_RESIZEN:
+				return 138;
+			case NC_RESIZEALL:
+				return 52;
+			case NC_NO:
+				return 0;
+			case NC_HAND:
+				return 60;
+			case NC_APPSTARTING:
+				return 150;
+			case NC_HELP:
+				return 92;
+			default:
+				break;
+		}
+#endif
+		return cursor_id;
+	}
+
+	void Cursor::setNativeCursor(unsigned int cursor_id) {
+#if !defined( WIN32 ) && !defined(__linux__)
+		return;
+#endif
+
+		// Check if a value in NativeCursors is requested
+		cursor_id = getNativeId(cursor_id);
+
+		// Load cursor
+#if defined( __linux__ )
+		static Display* dsp = XOpenDisplay(NULL);
+		XCursor xCursor = XcursorShapeLoadCursor(dsp, cursor_id);
+		if (xCursor == 0) {
+			if (m_native_cursor != NULL) {
+				SDL_FreeCursor(m_native_cursor);
+				m_native_cursor = NULL;
+			}
+			FL_WARN(_log, "Cursor: No cursor matching cursor_id was found.");
+			return;
+		}
+#elif defined( WIN32 )
+		// Load native cursor
+		HCURSOR hIcon = LoadCursor(NULL, MAKEINTRESOURCE(cursor_id));
+		if (hIcon == static_cast<HCURSOR>(0)) {
+			if (m_native_cursor != NULL) {
+				SDL_FreeCursor(m_native_cursor);
+				m_native_cursor = NULL;
+			}
+			FL_WARN(_log, "Cursor: No cursor matching cursor_id was found.");
+			return;
+		}
+#endif
+
+		WMcursor *cursor;
+		SDL_Cursor *curs2;
+
+		// Allocate memory. Use SDL_FreeCursor to free cursor memory
+		cursor = (WMcursor *)SDL_malloc(sizeof(*cursor));
+		curs2 = (SDL_Cursor *)SDL_malloc(sizeof *curs2);
+
+		//-- Set up some default values --
+		curs2->wm_cursor = cursor;
+		curs2->data = NULL;
+		curs2->mask = NULL;
+		curs2->save[0] = NULL;
+		curs2->save[1] = NULL;
+		curs2->area.x = 0;
+		curs2->area.y = 0;
+		curs2->area.w = 32;
+		curs2->area.h = 32;
+		curs2->hot_x = 0;
+		curs2->hot_y = 0;
+
+#if defined(WIN32)
+		cursor->curs = hIcon;
+#ifndef _WIN32_WCE
+		cursor->ands = NULL;
+		cursor->xors = NULL;
+#endif
+
+		// Get hot spot
+		ICONINFO iconinfo;
+		if (GetIconInfo(hIcon, &iconinfo)) {
+			curs2->hot_x = static_cast<Sint16>(iconinfo.xHotspot);
+			curs2->hot_y = static_cast<Sint16>(iconinfo.yHotspot);
+		}
+
+#elif defined(__linux__)
+		cursor->x_cursor = xCursor;
+		XSync(dsp, false);
+#endif
+		
+		m_native_cursor = curs2;
+		SDL_SetCursor(curs2);
+
+	}
 }
--- a/engine/core/video/cursor.h	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/video/cursor.h	Mon Jun 08 16:00:02 2009 +0000
@@ -31,6 +31,8 @@
 // First block: files included from the FIFE root src directory
 // Second block: files included from the same folder
 
+struct SDL_Cursor;
+
 namespace FIFE {
 
 	class ImagePool;
@@ -49,7 +51,34 @@
 		CURSOR_IMAGE,
 		CURSOR_ANIMATION
 	};
-	
+
+	/** Defines some common native cursors between platforms.
+	  * In addition to these, you can use the values in:
+	  * Windows: http://msdn.microsoft.com/en-us/library/ms648391(VS.85).aspx
+	  * X11: http://fife.pastebin.com/f5b89dd6b
+	  */
+	enum NativeCursor {
+		// Start on 1000000 to avoid id-clashes with X11 and windows
+		NC_ARROW = 1000000, // Standard arrow
+		NC_IBEAM,			// I-beam for text selection
+		NC_WAIT,			// Hourglass
+		NC_CROSS,			// Crosshair
+		NC_UPARROW,			// Vertical arrow
+		NC_RESIZENW,		// Cursor for resize in northwest corner
+		NC_RESIZESE,		// 
+		NC_RESIZESW,		// 
+		NC_RESIZENE,		// 
+		NC_RESIZEE,			// 
+		NC_RESIZEW,			// 
+		NC_RESIZEN,			// 
+		NC_RESIZES,			// 
+		NC_RESIZEALL,		// Four-pointed arrow pointing north, south, east, and west
+		NC_NO,				// Slashed circle
+		NC_HAND,			// Hand. Common for links, etc.
+		NC_APPSTARTING,		// Standard arrow and small hourglass
+		NC_HELP				// Arrow and question mark
+	};
+
 	/**  Cursor class manages mouse cursor handling
 	 */
 	class Cursor {
@@ -68,7 +97,7 @@
 
 		/** Sets the current mouse cursor type and possible pool value
 		 * @param ctype cursor type
-		 * @param cursor_id pool id for the cursor (either image or animation)
+		 * @param cursor_id Pool id to image or animation. For native cursors, this is the resource id to native cursor, or one of the values in NativeCursor
 		 */
 		void set(MouseCursorType ctype, unsigned int cursor_id=0);
 			
@@ -94,13 +123,29 @@
 		/** Gets the current mouse cursor pool id
 		 */
 		unsigned int getDragId() const { return m_drag_id; }
-		
+
+	protected:
+		/** Sets the cursor to a native type.
+		  * @param cursor_id Resource id to native cursor, or one of the values in NativeCursor
+		  */
+		void setNativeCursor(unsigned int cursor_id);
+
+		/** To get some consistancy between platforms, this function checks
+		  * if cursor_id matches any of the values in NativeCursor, and
+		  * returns the resource ID specific to the running platform.
+		  * If no match is found, cursor_id is returned.
+		  *
+		  * @param One of the values in NativeCursor
+		  */
+		unsigned int getNativeId(unsigned int cursor_id);
 	
 	private:
 		unsigned int m_cursor_id;
 		unsigned int m_drag_id;
 		MouseCursorType m_cursor_type;
 		MouseCursorType m_drag_type;
+
+		SDL_Cursor* m_native_cursor;
 		
 		RenderBackend* m_renderbackend;
 		ImagePool* m_imgpool;
--- a/engine/core/video/video.i	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/video/video.i	Mon Jun 08 16:00:02 2009 +0000
@@ -167,6 +167,27 @@
 		CURSOR_IMAGE,
 		CURSOR_ANIMATION
 	};
+
+	enum NativeCursor {
+		NC_ARROW = 1000000,
+		NC_IBEAM,
+		NC_WAIT,
+		NC_CROSS,
+		NC_UPARROW,
+		NC_RESIZENW,
+		NC_RESIZESE,
+		NC_RESIZESW,
+		NC_RESIZENE,
+		NC_RESIZEE,
+		NC_RESIZEW,
+		NC_RESIZEN,
+		NC_RESIZES,
+		NC_RESIZEALL,
+		NC_NO,
+		NC_HAND,
+		NC_APPSTARTING,
+		NC_HELP
+	};
 	
 	class Cursor {
 	public:
--- a/engine/core/view/renderers/cellselectionrenderer.cpp	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/view/renderers/cellselectionrenderer.cpp	Mon Jun 08 16:00:02 2009 +0000
@@ -43,14 +43,12 @@
 	static Logger _log(LM_VIEWVIEW);
 
 	CellSelectionRenderer::CellSelectionRenderer(RenderBackend* renderbackend, int position):
-		RendererBase(renderbackend, position),
-		m_loc(NULL) {
+		RendererBase(renderbackend, position) {
 		setEnabled(true);
 	}
 
  	CellSelectionRenderer::CellSelectionRenderer(const CellSelectionRenderer& old):
-		RendererBase(old),
-		m_loc(NULL) {
+		RendererBase(old) {
 		setEnabled(true);
 	}
 
@@ -66,48 +64,63 @@
 	}
 	
 	void CellSelectionRenderer::reset() {
-		delete m_loc;
-		m_loc = NULL;
+		m_locations.clear();
 	}
 
-	void CellSelectionRenderer::selectLocation(Location* loc) {
-		delete m_loc;
-		m_loc = NULL;
+	void CellSelectionRenderer::selectLocation(const Location* loc) {
 		if (loc) {
-			m_loc = new Location(*loc);
+			std::vector<Location>::const_iterator it = m_locations.begin();
+			for (; it != m_locations.end(); it++) {
+				if (*it == *loc) return;
+			}
+
+			m_locations.push_back(Location(*loc));
+		}
+	}
+
+	void CellSelectionRenderer::deselectLocation(const Location* loc) {
+		if (loc) {
+			std::vector<Location>::iterator it = m_locations.begin();
+			for (; it != m_locations.end(); it++) {
+				if (*it == *loc) {
+					m_locations.erase(it);
+					break;
+				}
+			}
 		}
 	}
 
 	void CellSelectionRenderer::render(Camera* cam, Layer* layer, std::vector<Instance*>& instances) {
-		if (!m_loc) {
-			return;
-		}
-		
-		if (layer != m_loc->getLayer()) {
-			return;
-		}
-		
-		CellGrid* cg = layer->getCellGrid();
-		if (!cg) {
-			FL_WARN(_log, "No cellgrid assigned to layer, cannot draw selection");
-			return;
-		}
+		std::vector<Location>::const_iterator locit = m_locations.begin();
+
+		for (; locit != m_locations.end(); locit++) {
+			const Location loc = *locit;
+			if (layer != loc.getLayer()) {
+				continue;
+			}
+			
+			CellGrid* cg = layer->getCellGrid();
+			if (!cg) {
+				FL_WARN(_log, "No cellgrid assigned to layer, cannot draw selection");
+				continue;
+			}
 
-		std::vector<ExactModelCoordinate> vertices;
-		cg->getVertices(vertices, m_loc->getLayerCoordinates());
-		std::vector<ExactModelCoordinate>::const_iterator it = vertices.begin();
-		ScreenPoint firstpt = cam->toScreenCoordinates(cg->toMapCoordinates(*it));
-		Point pt1(firstpt.x, firstpt.y);
-		Point pt2;
-		++it;
-		for (; it != vertices.end(); it++) {
-			ScreenPoint pts = cam->toScreenCoordinates(cg->toMapCoordinates(*it));
-			pt2.x = pts.x; pt2.y = pts.y;
-			Point cpt1 = pt1;
-			Point cpt2 = pt2;
-			m_renderbackend->drawLine(cpt1, cpt2, 255, 0, 0);
-			pt1 = pt2;
+			std::vector<ExactModelCoordinate> vertices;
+			cg->getVertices(vertices, loc.getLayerCoordinates());
+			std::vector<ExactModelCoordinate>::const_iterator it = vertices.begin();
+			ScreenPoint firstpt = cam->toScreenCoordinates(cg->toMapCoordinates(*it));
+			Point pt1(firstpt.x, firstpt.y);
+			Point pt2;
+			++it;
+			for (; it != vertices.end(); it++) {
+				ScreenPoint pts = cam->toScreenCoordinates(cg->toMapCoordinates(*it));
+				pt2.x = pts.x; pt2.y = pts.y;
+				Point cpt1 = pt1;
+				Point cpt2 = pt2;
+				m_renderbackend->drawLine(cpt1, cpt2, 255, 0, 0);
+				pt1 = pt2;
+			}
+			m_renderbackend->drawLine(pt2, Point(firstpt.x, firstpt.y), 255, 0, 0);
 		}
-		m_renderbackend->drawLine(pt2, Point(firstpt.x, firstpt.y), 255, 0, 0);
 	}
 }
--- a/engine/core/view/renderers/cellselectionrenderer.h	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/view/renderers/cellselectionrenderer.h	Mon Jun 08 16:00:02 2009 +0000
@@ -31,10 +31,13 @@
 // First block: files included from the FIFE root src directory
 // Second block: files included from the same folder
 #include "view/rendererbase.h"
+#include "model/structures/location.h"
 
 namespace FIFE {
 	class RenderBackend;
 
+	/** CellSelectionRenderer renders a frame around selected cells.
+	 */
 	class CellSelectionRenderer: public RendererBase {
 	public:
 		/** constructor.
@@ -52,22 +55,31 @@
 
 		void render(Camera* cam, Layer* layer, std::vector<Instance*>& instances);
 
+		/** Returns the renderer name */
 		std::string getName() { return "CellSelectionRenderer"; }
 
 		/** returns instance used in given view
 		 */
 		static CellSelectionRenderer* getInstance(IRendererContainer* cnt);
 		
+		/** Deselects all locations */
 		void reset();
 		
 		/** Selects given location on map
-		 * If NULL is given, deselects
+		 */
+		void selectLocation(const Location* loc);
+
+		/** Deselects given location on map
 		 */
-		void selectLocation(Location* loc);
+		void deselectLocation(const Location* loc);
+
+		/** Returns selected locations
+		 */
+		const std::vector<Location> getLocations() const { return m_locations; }
 		
 	private:
-		// selected location
-		Location* m_loc;
+		// selected locations
+		std::vector<Location> m_locations;
 	};
 
 }
--- a/engine/core/view/renderers/cellselectionrenderer.i	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/core/view/renderers/cellselectionrenderer.i	Mon Jun 08 16:00:02 2009 +0000
@@ -32,7 +32,10 @@
 		virtual ~CellSelectionRenderer();
 		std::string getName();
 		static CellSelectionRenderer* getInstance(IRendererContainer* cnt);
-		void selectLocation(Location* loc);
+		void reset();
+		void selectLocation(const Location* loc);
+		void deselectLocation(const Location* loc);
+		const std::vector<Location> getLocations() const;
 		
 	private:
 		CellSelectionRenderer(RenderBackend* renderbackend, int position);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/engine/extensions/fife_timer.py	Mon Jun 08 16:00:02 2009 +0000
@@ -0,0 +1,110 @@
+# -*- coding: utf-8 -*-
+import fife
+
+"""
+Convenient timers
+=================
+
+Usage::
+  import timer
+  timer.init( my_fife_engine.getTimeManager() )
+  def spam():
+     print "SPAM SPAM ",
+     return "More spam?" # a string is a true value, so it's repeated.
+  repeater = timer.repeatCall(500,spam)
+  def stop_spam():
+     repeater.stop()
+     print "BACON EGGS AND SPAM"
+  timer.delayCall(50000,stop_spam)
+
+"""
+
+
+_manager = None
+_alltimers = {}
+
+def init(timemanager):
+	"""
+	Initialize timers.
+
+	@param timemanager: A L{fife.TimeManager} as retuned by L{fife.Engine.getTimeManager}.
+	"""
+	global _manager
+	_manager = timemanager
+
+class Timer(fife.TimeEvent):
+	def __init__(self,delay=0,callback=None):
+		super(Timer,self).__init__(0)
+		self.manager = _manager
+		self.is_registered = False
+		self.callback = callback
+		self.setPeriod(delay)
+
+	def start(self):
+		if self.is_registered:
+			return
+		self.is_registered = True
+		global _alltimers
+		_alltimers[self]=1
+		self.manager.registerEvent(self)
+
+	def stop(self):
+		if not self.is_registered:
+			return
+		self.is_registered = False
+		global _alltimers
+		del _alltimers[self]
+		self.manager.unregisterEvent(self)
+
+	def updateEvent(self,delta):
+		if callable(self.callback):
+			self.callback()
+
+def delayCall(delay,callback):
+	"""
+	Delay a function call by a number of milliseconds.
+
+	@param delay Delay in milliseconds.
+	@param callback The function to call.
+
+	@return The timer.
+	"""
+	timer = Timer(delay)
+	def cbwa(c, *args):
+		c(*args)
+	def real_callback(c, t):
+		t.stop()
+		c()
+		
+	timer.callback = cbwa(real_callback, callback, timer)
+	timer.start()
+	return timer
+
+from traceback import print_exc
+def repeatCall(period,callback):
+	"""
+	Repeat a function call.
+
+	@param period Period between calls in milliseconds.
+	@param callback The function to call.
+
+	@return The timer.
+
+	The call is repeated until the callback returns a False
+	value (i.e. None) or the timer is stopped.
+	"""
+	timer = Timer(period)
+	def real_callback():
+		try:
+			if not callback():
+				timer.stop()
+		except Exception:
+			print_exc()
+			timer.stop()
+
+	timer.callback = real_callback
+	timer.start()
+	return timer
+
+__all__ = [init,Timer,delayCall,repeatCall]
+
--- a/engine/extensions/filebrowser.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/filebrowser.py	Mon Jun 08 16:00:02 2009 +0000
@@ -39,7 +39,7 @@
 		})
 		self._setDirectory()
 		if self.savefile:
-			self._file_entry = widgets.TextField(name='saveField', text='')	
+			self._file_entry = widgets.TextField(name='saveField', text=u'')	
 			self._widget.findChild(name="fileColumn").addChild(self._file_entry)
 		self._widget.show()
 
--- a/engine/extensions/pychan/__init__.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/__init__.py	Mon Jun 08 16:00:02 2009 +0000
@@ -253,18 +253,20 @@
 ### Initialisation ###
 
 manager = None
-def init(engine,debug=False):
+def init(engine,debug=False, compat_layout=False):
 	"""
 	This has to be called before any other pychan methods can be used.
 	It sets up a manager object which is available under pychan.manager.
 
 	@param engine: The FIFE engine object.
+	@param debug: bool - Enables and disables debugging output. Default is False.
+	@param compat_layout: bool - Enables and disables compat layout. Default is False.
 	"""
 	from compat import _munge_engine_hook
 	from internal import Manager
 	global manager
 
-	manager = Manager(_munge_engine_hook(engine),debug)
+	manager = Manager(_munge_engine_hook(engine),debug,compat_layout)
 
 # XML Loader
 
@@ -357,7 +359,7 @@
 		self.indent = self.indent[:-4]
 		if manager.debug: print self.indent + "</%s>" % name
 		if self.stack.pop() in ('gui_element','spacer'):
-			self.root = self.root._parent or self.root
+			self.root = self.root.parent or self.root
 
 def loadXML(filename_or_stream):
 	"""
--- a/engine/extensions/pychan/dialogs.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/dialogs.py	Mon Jun 08 16:00:02 2009 +0000
@@ -1,4 +1,4 @@
-# coding: utf-8
+# -*- coding: utf-8 -*-
 
 from pychan import loadXML
 import pychan.tools
@@ -68,7 +68,7 @@
 <Label wrap_text="1" text="$MESSAGE" name="message" vexpanding="1"/>
 </ScrollArea>
 <HBox>
-<Spacer/><Button min_width="50" name="okButton" text="OK"/>
+<Spacer/><Button min_size="50,0" name="okButton" text="OK"/>
 </HBox>
 </Window>
 """
@@ -80,8 +80,8 @@
 </ScrollArea>
 <HBox>
 <Spacer/>
-<Button min_width="50" name="yesButton" text="Yes"/>
-<Button min_width="50" name="noButton" text="No"/>
+<Button min_size="50,0" name="yesButton" text="Yes"/>
+<Button min_size="50,0" name="noButton" text="No"/>
 </HBox>
 </Window>
 """
@@ -117,8 +117,8 @@
 
 EXCEPTION_CATCHER_XML="""\
 <Window name="window" title="An exception occurred - what now?">
-  <VBox hexpanding="1">
-    <Label wrap_text="1" max_width="400" text="$MESSAGE" name="message"/>
+  <VBox hexpand="1">
+    <Label wrap_text="1" max_size="400,90000" text="$MESSAGE" name="message"/>
     <ScrollArea>
     <Label text="$MESSAGE" name="traceback"/>
     </ScrollArea>
--- a/engine/extensions/pychan/events.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/events.py	Mon Jun 08 16:00:02 2009 +0000
@@ -46,6 +46,7 @@
 import tools
 import traceback
 import weakref
+import fife_timer as timer
 
 EVENTS = [
 	"mouseEntered",
@@ -132,7 +133,9 @@
 			if name in self.events:
 				if self.debug: print "-"*self.indent, name
 				for f in self.events[name].itervalues():
-					f( event )
+					def delayed_f():
+						f( event )
+					timer.delayCall(0,delayed_f)
 
 		except:
 			print name, repr(event)
@@ -279,9 +282,9 @@
 			
 		self.callbacks[group_name][event_name] = callback
 			
-		
 		def captured_f(event):
-			tools.applyOnlySuitable(self_ref().callbacks[group_name][event_name],event=event,widget=self_ref().widget_ref())
+			if self_ref() is not None:
+				tools.applyOnlySuitable(self_ref().callbacks[group_name][event_name],event=event,widget=self_ref().widget_ref())
 
 		listener = self.getListener(event_name)
 
--- a/engine/extensions/pychan/internal.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/internal.py	Mon Jun 08 16:00:02 2009 +0000
@@ -2,6 +2,7 @@
 
 from compat import guichan, in_fife
 import widgets
+import fife_timer as timer
 import fonts
 from exceptions import *
 from traceback import print_exc
@@ -24,10 +25,11 @@
 class Manager(object):
 	manager = None
 
-	def __init__(self, hook, debug = False):
+	def __init__(self, hook, debug = False, compat_layout = False):
 		super(Manager,self).__init__()
 		self.hook = hook
 		self.debug = debug
+		self.compat_layout = compat_layout
 		self.unicodePolicy = ('ignore',)
 
 		if in_fife:
@@ -35,6 +37,7 @@
 				raise InitializationError("No event manager installed.")
 			if not hook.engine.getGuiManager():
 				raise InitializationError("No GUI manager installed.")
+		timer.init(hook.engine.getTimeManager())
 
 		self.fonts = {}
 		#glyphs = ' abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?-+/:();%`\'*#=[]"'
@@ -43,13 +46,16 @@
 		self.styles = {}
 		self.addStyle('default',DEFAULT_STYLE)
 
-                Manager.manager = self
+		Manager.manager = self
 
 		# Setup synchronous dialogs
 		self.mainLoop = None
 		self.breakFromMainLoop = None
 		self.can_execute = False
 
+		import weakref
+		self.allWidgets = weakref.WeakKeyDictionary()
+
 		# Autopos
 		from autoposition import placeWidget
 		self.placeWidget = placeWidget
@@ -67,6 +73,8 @@
 		Shows a widget on screen. Used by L{Widget.show} - do not use directly.
 		"""
 		self.placeWidget(widget, widget.position_technique)
+		assert widget not in self.allWidgets
+		self.allWidgets[ widget ] = 1
 		self.hook.add_widget( widget.real_widget )
 
 	def hide(self,widget):
@@ -74,6 +82,7 @@
 		Hides a widget again. Used by L{Widget.hide} - do not use directly.
 		"""
 		self.hook.remove_widget( widget.real_widget )
+		del self.allWidgets[ widget ]
 
 	def setDefaultFont(self,name):
 		self.fonts['default'] = self.getFont(name)
@@ -133,6 +142,9 @@
 					setattr(widget,k,v)
 
 	def _remapStyleKeys(self,style):
+		"""
+		Translate style selectors to tuples of widget classes. (internal)
+		"""
 		# Remap class names, create copy:
 		def _toClass(class_):
 			if class_ == "default":
@@ -168,6 +180,7 @@
 		'foreground_color' : guichan.Color(255,255,255),
 		'background_color' : guichan.Color(50,50,50),
 		'selection_color' : guichan.Color(80,80,80),
+		'font' : 'default'
 	},
 	'Button' : {
 		'border_size': 2,
@@ -183,6 +196,7 @@
 	},
 	'Label' : {
 		'border_size': 0,
+		'background_color' : guichan.Color(50,50,50,0)
 	},
 	'ClickLabel' : {
 		'border_size': 0,
@@ -194,10 +208,9 @@
 		'border_size': 0,
 		'margins': (5,5),
 		'opaque' : 1,
+		'padding':2,
 		'titlebar_height' : 12,
-		'vexpanding' : 1,
-		#'background_image' : 'gui/backgrounds/background.png',
-		#'font' : 'samanata_large'
+		'background_image' : None,
 	},
 	'TextBox' : {
 	},
@@ -205,6 +218,7 @@
 		'border_size': 0,
 		'margins': (0,0),
 		'padding':2,
-		#'background_image' : 'gui/backgrounds/background.png',
+		'opaque' : 1,
+		'background_image' : None,
 	}
 }
\ No newline at end of file
--- a/engine/extensions/pychan/layout.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,278 +0,0 @@
-# coding: utf-8
-
-from attrs import IntAttr
-
-AlignTop, AlignBottom, AlignLeft, AlignRight, AlignCenter = range(5)
-def isLayouted(widget):
-	return isinstance(widget,LayoutBase)
-
-class LayoutBase(object):
-	"""
-	This class is at the core of the layout engine. The two MixIn classes L{VBoxLayoutMixin}
-	and L{HBoxLayoutMixin} specialise on this by reimplementing the C{resizeToContent} and
-	the C{expandContent} methods.
-
-	Dynamic Layouting
-	-----------------
-
-	The layout is calculated in the L{Widget.show} method. Thus if you modify the layout,
-	by adding or removing child widgets for example, you have to call L{widgets.Widget.adaptLayout}
-	so that the changes ripple through the widget hierachy.
-
-	Internals
-	---------
-
-	At the core the layout engine works in two passes:
-
-	Before a root widget loaded by the XML code is shown, its resizeToContent method
-	is called recursively (walking the widget containment relation in post order).
-	This shrinks all HBoxes and VBoxes to their minimum heigt and width.
-	After that the expandContent method is called recursively in the same order,
-	which will re-align the widgets if there is space left AND if a Spacer is contained.
-
-	Inside bare Container instances (without a Layout MixIn) absolute positioning
-	can be used.
-	"""
-	def __init__(self,align = (AlignLeft,AlignTop), **kwargs):
-		self.align = align
-		self.spacer = []
-		super(LayoutBase,self).__init__(**kwargs)
-
-	def addSpacer(self,spacer):
-		self.spacer.append(spacer)
-		spacer.index = len(self.children)
-
-	def xdelta(self,widget):return 0
-	def ydelta(self,widget):return 0
-
-	def _applyHeight(self, spacers = []):
-		y = self.border_size + self.margins[1]
-		ydelta = map(self.ydelta,self.children)
-		for index, child in enumerate(self.children):
-			while spacers and spacers[0].index == index:
-				y += spacers.pop(0).size
-			child.y = y
-			y += ydelta.pop(0)
-
-	def _adjustHeightWithSpacer(self):
-		pass
-
-	def _applyWidth(self, spacers = []):
-		x = self.border_size + self.margins[0]
-		xdelta = map(self.xdelta,self.children)
-		for index, child in enumerate(self.children):
-			while spacers and spacers[0].index == index:
-				x += spacers.pop(0).size
-			child.x = x
-			x += xdelta.pop(0)
-
-	def _expandWidthSpacer(self):
-		xdelta = map(self.xdelta,self.children)
-		xdelta += [spacer.min_size for spacer in self.spacer]
-
-		available_space = self.width - 2*self.margins[0] - 2*self.border_size - self._extra_border[0]
-
-		used_space = sum(xdelta)
-		if self.children:
-			used_space -= self.padding
-		if used_space >= available_space:
-			return
-
-		expandable_items = self._getExpanders(vertical=False)
-		#print "AS/US - before",self,[o.width for o in expandable_items]
-		#print "SPACERS",self.spacer
-
-		index = 0
-		while used_space < available_space and expandable_items:
-			index = index % len(expandable_items)
-
-			expander = expandable_items[index]
-			old_width = expander.width
-			expander.width += 1
-			if old_width == expander.width:
-				expandable_items.pop(index)
-			else:
-				used_space += 1
-				index += 1
-
-		#print "AS/US - after",self,[o.width for o in expandable_items]
-		#print "SPACERS",self.spacer
-		self._applyWidth(spacers = self.spacer[:])
-
-	def _expandHeightSpacer(self):
-		ydelta = map(self.ydelta,self.children)
-		ydelta += [spacer.min_size for spacer in self.spacer]
-
-		available_space = self.height - 2*self.margins[1] - 2*self.border_size - self._extra_border[1]
-
-		used_space = sum(ydelta)
-		if self.children:
-			used_space -= self.padding
-
-		if used_space >= available_space:
-			return
-
-		expandable_items = self._getExpanders(vertical=True)
-		#print "AS/US - before",self,[o.height for o in expandable_items]
-
-		index = 0
-		while used_space < available_space and expandable_items:
-			index = index % len(expandable_items)
-
-			expander = expandable_items[index]
-			old_width = expander.height
-			expander.height += 1
-			if old_width == expander.height:
-				expandable_items.pop(index)
-			else:
-				used_space += 1
-				index += 1
-
-		#print "AS/US - after",self,[o.height for o in expandable_items]
-		self._applyHeight(spacers = self.spacer[:])
-
-
-	def _getExpanders(self,vertical=True):
-		expanders = []
-		spacers = self.spacer[:]
-		for index, child in enumerate(self.children):
-			if spacers and spacers[0].index == index:
-				expanders.append( spacers.pop(0) )
-			#print self,child,child.expanding
-			if child.vexpanding and vertical:
-				expanders.append( child )
-			if child.hexpanding and not vertical:
-				expanders.append( child )
-		return expanders + spacers
-
-	def _resetSpacers(self):
-		for spacer in self.spacer:
-			spacer.size = 0
-
-class VBoxLayoutMixin(LayoutBase):
-	"""
-	A mixin class for a vertical layout. Do not use directly.
-	"""
-	def __init__(self,**kwargs):
-		super(VBoxLayoutMixin,self).__init__(**kwargs)
-
-	def resizeToContent(self, recurse = True):
-		self._resetSpacers()
-
-		max_w = self.getMaxChildrenWidth()
-		x = self.margins[0] + self.border_size
-		y = self.margins[1] + self.border_size
-		for widget in self.children:
-			widget.width = max_w
-			y += widget.height + self.padding
-
-		if self.children:
-			y -= self.padding
-
-		y += sum([spacer.min_size for spacer in self.spacer])
-
-		self.height = y + self.margins[1] + self.border_size + self._extra_border[1]
-		self.width = max_w + 2*x + self._extra_border[0]
-
-		self._applyHeight(spacers = self.spacer[:])
-		self._applyWidth()
-
-	def expandContent(self):
-		self._expandHeightSpacer()
-		if not self.hexpanding:return
-		for widget in self.children:
-			widget.width = self.width - 2*self.margins[0] - 2*self.border_size - self._extra_border[0]
-
-
-	def ydelta(self,widget):return widget.height + self.padding
-
-class HBoxLayoutMixin(LayoutBase):
-	"""
-	A mixin class for a horizontal layout. Do not use directly.
-	"""
-	def __init__(self,**kwargs):
-		super(HBoxLayoutMixin,self).__init__(**kwargs)
-
-	def resizeToContent(self, recurse = True):
-		self._resetSpacers()
-
-		max_h = self.getMaxChildrenHeight()
-		x = self.margins[0] + self.border_size
-		y = self.margins[1] + self.border_size
-		for widget in self.children:
-			widget.height = max_h
-			x += widget.width + self.padding
-		if self.children:
-			x -= self.padding
-		x += sum([spacer.min_size for spacer in self.spacer])
-
-		self.width = x + self.margins[0] + self._extra_border[0]
-		self.height = max_h + 2*y + self._extra_border[1]
-
-		self._applyHeight()
-		self._applyWidth(spacers = self.spacer[:])
-
-	def expandContent(self):
-		self._expandWidthSpacer()
-		if not self.vexpanding:return
-		for widget in self.children:
-			widget.height = self.height - 2*self.margins[1] - 2*self.border_size - self._extra_border[1]
-
-	def xdelta(self,widget):return widget.width + self.padding
-
-class Spacer(object):
-	""" A spacer represents expandable or fixed 'whitespace' in the GUI.
-
-	In a XML file you can get this by adding a <Spacer /> inside a VBox or
-	HBox element (Windows implicitly are VBox elements).
-
-	Attributes
-	----------
-
-	As with widgets a number of attributes can be set on a spacer (inside the XML definition).
-	
-	  - min_size: Int: The minimal size this Spacer is allowed to have.
-	  - max_size: Int: The maximal size this Spacer is allowed to have.
-	  - fixed_size: Int: Set min_size and max_size to the same vale - effectively a Fixed size spacer. 
-
-	"""
-
-	ATTRIBUTES = [
-		IntAttr('min_size'), IntAttr('size'), IntAttr('max_size'),
-		IntAttr('fixed_size'),
-	]
-
-	def __init__(self,parent=None,**kwargs):
-		self._parent = parent
-		self.min_size = 0
-		self.max_size = 1000
-		self.size = 0
-
-	def __str__(self):
-		return "Spacer(parent.name='%s')" % getattr(self._parent,'name','None')
-
-	def __repr__(self):
-		return "<Spacer(parent.name='%s') at %x>" % (getattr(self._parent,'name','None'),id(self))
-
-	def _getSize(self):
-		self.size = self._size
-		return self._size
-	def _setSize(self,size):
-		self._size = max(self.min_size, min(self.max_size,size))
-	size = property(_getSize,_setSize)
-
-	# Alias for size
-	width = property(_getSize,_setSize)
-	height = property(_getSize,_setSize)
-
-	def _setFixedSize(self,size):
-		self.min_size = self.max_size = size
-		self.size = size
-	fixed_size = property(fset=_setFixedSize)
-
-	def _isExpanding(self):
-		if self.min_size < self.max_size:
-			return 1
-		return 0
-	vexpanding = property(_isExpanding)
-	hexpanding = property(_isExpanding)
--- a/engine/extensions/pychan/widgets/__init__.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/__init__.py	Mon Jun 08 16:00:02 2009 +0000
@@ -9,7 +9,8 @@
 
 from widget import Widget
 
-from containers import Container, VBox, HBox, Window, Spacer
+from layout import Spacer
+from containers import Container, VBox, HBox, Window
 from label import Label, ClickLabel
 from icon import Icon
 from buttons import Button, ToggleButton, ImageButton
--- a/engine/extensions/pychan/widgets/basictextwidget.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/basictextwidget.py	Mon Jun 08 16:00:02 2009 +0000
@@ -19,6 +19,8 @@
 	"""
 
 	ATTRIBUTES = Widget.ATTRIBUTES + [UnicodeAttr('text')]
+	DEFAULT_HEXPAND = 1
+	DEFAULT_VEXPAND = 0
 
 	def __init__(self, text = u"",**kwargs):
 		self.margins = (5,5)
--- a/engine/extensions/pychan/widgets/buttons.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/buttons.py	Mon Jun 08 16:00:02 2009 +0000
@@ -100,9 +100,9 @@
 		return (self.real_widget.getDownXOffset(), self.real_widget.getDownYOffset())
 	offset = property(_getOffset,_setOffset)
 
-	def resizeToContent(self):
+	def resizeToContent(self, recurse=True):
 		self.height = max(self._upimage.getHeight(),self._downimage.getHeight(),self._hoverimage.getHeight()) + self.margins[1]*2
-		self.width = max(self._upimage.getWidth(),self._downimage.getWidth(),self._hoverimage.getWidth()) + self.margins[1]*2
+		self.width = max(self._upimage.getWidth(),self._downimage.getWidth(),self._hoverimage.getWidth()) + self.margins[0]*2
 
 class ToggleButton(BasicTextWidget):
 	"""
@@ -186,6 +186,8 @@
 		return (self.real_widget.getDownXOffset(), self.real_widget.getDownYOffset())
 	offset = property(_getOffset,_setOffset)
 
-	def resizeToContent(self):
-		self.height = max(self._upimage.getHeight(),self._downimage.getHeight(),self._hoverimage.getHeight()) + self.margins[1]*2
-		self.width = max(self._upimage.getWidth(),self._downimage.getWidth(),self._hoverimage.getWidth()) + self.margins[1]*2
+	def resizeToContent(self, recurse=True):
+		th = self.real_font.getHeight()+self.real_widget.getSpacing()
+		tw = self.real_font.getWidth(text2gui(self.text))+self.real_widget.getSpacing()
+		self.height = max(self._upimage.getHeight(),self._downimage.getHeight(),self._hoverimage.getHeight(),th) + self.margins[1]*2
+		self.width = max(self._upimage.getWidth(),self._downimage.getWidth(),self._hoverimage.getWidth(),tw) + self.margins[0]*2
--- a/engine/extensions/pychan/widgets/containers.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/containers.py	Mon Jun 08 16:00:02 2009 +0000
@@ -38,6 +38,26 @@
 		self.children.append(widget)
 		self.real_widget.add(widget.real_widget)
 
+	def insertChild(self, widget, position):
+		if position > len(self.children) or 0-position > len(self.children):
+			print "insertChild: Warning: Index overflow.",
+			if position >= 0:
+				self.addChild(widget)
+			else:
+				self.insertChild(widget, 0)
+			return
+		
+		children = self.children[0:position]+[widget]+self.children[position:]
+		#assert len(children) == len(self.children) + 1
+		self.removeAllChildren()
+		for child in children:
+			self.addChild(child)
+
+	def insertChildBefore(self, widget, before):
+		if before not in self.children:
+			raise RuntimeError("Couldn't find widget %s as child of %s - in insertChildBefore" % (str(widget),str(before)))
+		self.insertChild(widget, self.children.index(before))
+
 	def removeChild(self,widget):
 		if not widget in self.children:
 			raise RuntimeError("%s does not have %s as direct child widget." % (str(self),str(widget)))
@@ -57,10 +77,14 @@
 		if not self.children: return 0
 		return max(widget.height for widget in self.children)
 
-	def deepApply(self,visitorFunc):
-		for child in self.children:
-			child.deepApply(visitorFunc)
+	def deepApply(self,visitorFunc, leaves_first = True):
+		if leaves_first:
+			for child in self.children:
+				child.deepApply(visitorFunc, leaves_first = leaves_first)
 		visitorFunc(self)
+		if not leaves_first:
+			for child in self.children:
+				child.deepApply(visitorFunc, leaves_first = leaves_first)
 
 	def beforeShow(self):
 		self._resetTiling()
@@ -97,7 +121,7 @@
 			self._background_image = image
 			map(self.real_widget.remove,self._background)
 			self._background = []
-
+			return
 		# Background generation is done in _resetTiling
 
 		if not isinstance(image, fife.GuiImage):
@@ -124,6 +148,9 @@
 	widgets above the spacer are aligned to the top, while widgets below the spacer
 	are aligned to the bottom.
 	"""
+	DEFAULT_HEXPAND = 0
+	DEFAULT_VEXPAND = 1
+
 	def __init__(self,padding=5,**kwargs):
 		super(VBox,self).__init__(**kwargs)
 		self.padding = padding
@@ -135,6 +162,9 @@
 
 	Please see L{VBox} for details - just change the directions :-).
 	"""
+	DEFAULT_HEXPAND = 1
+	DEFAULT_VEXPAND = 0
+
 	def __init__(self,padding=5,**kwargs):
 		super(HBox,self).__init__(**kwargs)
 		self.padding = padding
@@ -180,25 +210,3 @@
 	def _getHeight(self): return self.real_widget.getHeight() - self.titlebar_height
 	height = property(_getHeight,_setHeight)
 
-
-# Spacer
-
-class Spacer(object):
-	""" A spacer represents expandable 'whitespace' in the GUI.
-
-	In a XML file you can get this by adding a <Spacer /> inside a VBox or
-	HBox element (Windows implicitly are VBox elements).
-
-	The effect is, that elements before the spacer will be left (top)
-	and elements after the spacer will be right (bottom) aligned.
-
-	There can only be one spacer in VBox (HBox).
-	"""
-	def __init__(self,parent=None,**kwargs):
-		self._parent = parent
-
-	def __str__(self):
-		return "Spacer(parent.name='%s')" % getattr(self._parent,'name','None')
-
-	def __repr__(self):
-		return "<Spacer(parent.name='%s') at %x>" % (getattr(self._parent,'name','None'),id(self))
--- a/engine/extensions/pychan/widgets/label.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/label.py	Mon Jun 08 16:00:02 2009 +0000
@@ -26,7 +26,7 @@
 		self.wrap_text = wrap_text
 		super(Label,self).__init__(**kwargs)
 
-	def resizeToContent(self):
+	def resizeToContent(self, recurse=True):
 		self.real_widget.setWidth( self.max_size[0] )
 		self.real_widget.adjustSize()
 		self.height = self.real_widget.getHeight() + self.margins[1]*2
--- a/engine/extensions/pychan/widgets/layout.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/layout.py	Mon Jun 08 16:00:02 2009 +0000
@@ -1,6 +1,10 @@
 # -*- coding: utf-8 -*-
 
-from common import *
+from pychan.attrs import IntAttr
+
+AlignTop, AlignBottom, AlignLeft, AlignRight, AlignCenter = range(5)
+def isLayouted(widget):
+	return isinstance(widget,LayoutBase)
 
 class LayoutBase(object):
 	"""
@@ -12,7 +16,7 @@
 	-----------------
 
 	The layout is calculated in the L{Widget.show} method. Thus if you modify the layout,
-	by adding or removing child widgets for example, you have to call L{Widget.adaptLayout}
+	by adding or removing child widgets for example, you have to call L{widgets.Widget.adaptLayout}
 	so that the changes ripple through the widget hierachy.
 
 	Internals
@@ -31,67 +35,118 @@
 	"""
 	def __init__(self,align = (AlignLeft,AlignTop), **kwargs):
 		self.align = align
-		self.spacer = None
+		self.spacer = []
 		super(LayoutBase,self).__init__(**kwargs)
 
 	def addSpacer(self,spacer):
-		if self.spacer:
-			raise RuntimeException("Already a Spacer in %s!" % str(self))
-		self.spacer = spacer
+		self.spacer.append(spacer)
 		spacer.index = len(self.children)
 
 	def xdelta(self,widget):return 0
 	def ydelta(self,widget):return 0
 
-	def _adjustHeight(self):
-		if self.align[1] == AlignTop:return #dy = 0
-		if self.align[1] == AlignBottom:
-			y = self.height - self.childarea[1] - self.border_size - self.margins[1]
-		else:
-			y = (self.height - self.childarea[1] - self.border_size - self.margins[1])/2
-		for widget in self.children:
-			widget.y = y
-			y += self.ydelta(widget)
+	def _applyHeight(self, spacers = []):
+		y = self.border_size + self.margins[1]
+		ydelta = map(self.ydelta,self.children)
+		for index, child in enumerate(self.children):
+			while spacers and spacers[0].index == index:
+				y += spacers.pop(0).size
+			child.y = y
+			y += ydelta.pop(0)
 
 	def _adjustHeightWithSpacer(self):
 		pass
 
-	def _adjustWidth(self):
-		if self.align[0] == AlignLeft:return #dx = 0
-		if self.align[0] == AlignRight:
-			x = self.width - self.childarea[0] - self.border_size - self.margins[0]
-		else:
-			x = (self.width - self.childarea[0] - self.border_size - self.margins[0])/2
-		for widget in self.children:
-			widget.x = x
-			x += self.xdelta(widget)
-
-	def _expandWidthSpacer(self):
+	def _applyWidth(self, spacers = []):
 		x = self.border_size + self.margins[0]
 		xdelta = map(self.xdelta,self.children)
-
-		for widget in self.children[:self.spacer.index]:
-			widget.x = x
+		for index, child in enumerate(self.children):
+			while spacers and spacers[0].index == index:
+				x += spacers.pop(0).size
+			child.x = x
 			x += xdelta.pop(0)
 
-		x = self.width - sum(xdelta) - self.border_size - self.margins[0]
-		for widget in self.children[self.spacer.index:]:
-			widget.x = x
-			x += xdelta.pop(0)
+	def _expandWidthSpacer(self):
+		xdelta = map(self.xdelta,self.children)
+		xdelta += [spacer.min_size for spacer in self.spacer]
+
+		available_space = self.width - 2*self.margins[0] - 2*self.border_size - self._extra_border[0]
+
+		used_space = sum(xdelta)
+		if self.children:
+			used_space -= self.padding
+		if used_space >= available_space:
+			return
+
+		expandable_items = self._getExpanders(vertical=False)
+		#print "AS/US - before",self,[o.width for o in expandable_items]
+		#print "SPACERS",self.spacer
+
+		index = 0
+		while used_space < available_space and expandable_items:
+			index = index % len(expandable_items)
+
+			expander = expandable_items[index]
+			old_width = expander.width
+			expander.width += 1
+			if old_width == expander.width:
+				expandable_items.pop(index)
+			else:
+				used_space += 1
+				index += 1
+
+		#print "AS/US - after",self,[o.width for o in expandable_items]
+		#print "SPACERS",self.spacer
+		self._applyWidth(spacers = self.spacer[:])
 
 	def _expandHeightSpacer(self):
-		y = self.border_size + self.margins[1]
 		ydelta = map(self.ydelta,self.children)
+		ydelta += [spacer.min_size for spacer in self.spacer]
+
+		available_space = self.height - 2*self.margins[1] - 2*self.border_size - self._extra_border[1]
+
+		used_space = sum(ydelta)
+		if self.children:
+			used_space -= self.padding
+
+		if used_space >= available_space:
+			return
+
+		expandable_items = self._getExpanders(vertical=True)
+		#print "AS/US - before",self,[o.height for o in expandable_items]
+
+		index = 0
+		while used_space < available_space and expandable_items:
+			index = index % len(expandable_items)
 
-		for widget in self.children[:self.spacer.index]:
-			widget.y = y
-			y += ydelta.pop(0)
+			expander = expandable_items[index]
+			old_width = expander.height
+			expander.height += 1
+			if old_width == expander.height:
+				expandable_items.pop(index)
+			else:
+				used_space += 1
+				index += 1
+
+		#print "AS/US - after",self,[o.height for o in expandable_items]
+		self._applyHeight(spacers = self.spacer[:])
+
 
-		y = self.height - sum(ydelta) - self.border_size - self.margins[1]
-		for widget in self.children[self.spacer.index:]:
-			widget.y = y
-			y += ydelta.pop(0)
+	def _getExpanders(self,vertical=True):
+		expanders = []
+		spacers = self.spacer[:]
+		for index, child in enumerate(self.children):
+			if spacers and spacers[0].index == index:
+				expanders.append( spacers.pop(0) )
+			if child.vexpand and vertical:
+				expanders += [child]*child.vexpand
+			if child.hexpand and not vertical:
+				expanders += [child]*child.hexpand
+		return expanders + spacers
 
+	def _resetSpacers(self):
+		for spacer in self.spacer:
+			spacer.size = 0
 
 class VBoxLayoutMixin(LayoutBase):
 	"""
@@ -101,29 +156,32 @@
 		super(VBoxLayoutMixin,self).__init__(**kwargs)
 
 	def resizeToContent(self, recurse = True):
+		self._resetSpacers()
+
 		max_w = self.getMaxChildrenWidth()
 		x = self.margins[0] + self.border_size
 		y = self.margins[1] + self.border_size
 		for widget in self.children:
-			widget.x = x
-			widget.y = y
 			widget.width = max_w
 			y += widget.height + self.padding
 
-		#Add the padding for the spacer.
-		if self.spacer:
-			y += self.padding
+		if self.children:
+			y -= self.padding
+
+		y += sum([spacer.min_size for spacer in self.spacer])
 
-		self.height = y + self.margins[1] - self.padding
-		self.width = max_w + 2*x
-		self.childarea = max_w, y - self.padding - self.margins[1]
+		self.height = y + self.margins[1] + self.border_size + self._extra_border[1]
+		self.width = max_w + 2*x + self._extra_border[0]
 
-		self._adjustHeight()
-		self._adjustWidth()
+		self._applyHeight(spacers = self.spacer[:])
+		self._applyWidth()
 
 	def expandContent(self):
-		if self.spacer:
-			self._expandHeightSpacer()
+		self._expandHeightSpacer()
+		if not self.hexpand and self.parent:return
+		for widget in self.children:
+			widget.width = self.width - 2*self.margins[0] - 2*self.border_size - self._extra_border[0]
+
 
 	def ydelta(self,widget):return widget.height + self.padding
 
@@ -135,28 +193,85 @@
 		super(HBoxLayoutMixin,self).__init__(**kwargs)
 
 	def resizeToContent(self, recurse = True):
+		self._resetSpacers()
+
 		max_h = self.getMaxChildrenHeight()
 		x = self.margins[0] + self.border_size
 		y = self.margins[1] + self.border_size
 		for widget in self.children:
-			widget.x = x
-			widget.y = y
 			widget.height = max_h
 			x += widget.width + self.padding
-
-		#Add the padding for the spacer.
-		if self.spacer:
-			x += self.padding
+		if self.children:
+			x -= self.padding
+		x += sum([spacer.min_size for spacer in self.spacer])
 
-		self.width = x + self.margins[0] - self.padding
-		self.height = max_h + 2*y
-		self.childarea = x - self.margins[0] - self.padding, max_h
+		self.width = x + self.margins[0] + self._extra_border[0]
+		self.height = max_h + 2*y + self._extra_border[1]
 
-		self._adjustHeight()
-		self._adjustWidth()
+		self._applyHeight()
+		self._applyWidth(spacers = self.spacer[:])
 
 	def expandContent(self):
-		if self.spacer:
-			self._expandWidthSpacer()
+		self._expandWidthSpacer()
+		if not self.vexpand and self.parent:return
+		for widget in self.children:
+			widget.height = self.height - 2*self.margins[1] - 2*self.border_size - self._extra_border[1]
 
 	def xdelta(self,widget):return widget.width + self.padding
+
+class Spacer(object):
+	""" A spacer represents expandable or fixed 'whitespace' in the GUI.
+
+	In a XML file you can get this by adding a <Spacer /> inside a VBox or
+	HBox element (Windows implicitly are VBox elements).
+
+	Attributes
+	----------
+
+	As with widgets a number of attributes can be set on a spacer (inside the XML definition).
+	
+	  - min_size: Int: The minimal size this Spacer is allowed to have.
+	  - max_size: Int: The maximal size this Spacer is allowed to have.
+	  - fixed_size: Int: Set min_size and max_size to the same vale - effectively a Fixed size spacer. 
+
+	"""
+
+	ATTRIBUTES = [
+		IntAttr('min_size'), IntAttr('size'), IntAttr('max_size'),
+		IntAttr('fixed_size'),
+	]
+
+	def __init__(self,parent=None,**kwargs):
+		self.parent = parent
+		self.min_size = 0
+		self.max_size = 1000
+		self.size = 0
+
+	def __str__(self):
+		return "Spacer(parent.name='%s')" % getattr(self.__parent,'name','None')
+
+	def __repr__(self):
+		return "<Spacer(parent.name='%s') at %x>" % (getattr(self.__parent,'name','None'),id(self))
+
+	def _getSize(self):
+		self.size = self._size
+		return self._size
+	def _setSize(self,size):
+		self._size = max(self.min_size, min(self.max_size,size))
+	size = property(_getSize,_setSize)
+
+	# Alias for size
+	width = property(_getSize,_setSize)
+	height = property(_getSize,_setSize)
+
+	def _setFixedSize(self,size):
+		self.min_size = self.max_size = size
+		self.size = size
+	fixed_size = property(fset=_setFixedSize)
+
+	def _isExpanding(self):
+		if self.min_size < self.max_size:
+			return 1
+		return 0
+	vexpand = property(_isExpanding)
+	hexpand = property(_isExpanding)
--- a/engine/extensions/pychan/widgets/listbox.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/listbox.py	Mon Jun 08 16:00:02 2009 +0000
@@ -40,6 +40,9 @@
 	The selected attribute can be read and set via L{distributeData} and L{collectData}.
 	The list items can be set via L{distributeInitialData}.
 	"""
+	DEFAULT_HEXPAND = 1
+	DEFAULT_VEXPAND = 1
+
 	def __init__(self,items=[],**kwargs):
 		self._items = GenericListmodel(*items)
 		self.real_widget = fife.ListBox(self._items)
--- a/engine/extensions/pychan/widgets/scrollarea.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/scrollarea.py	Mon Jun 08 16:00:02 2009 +0000
@@ -17,6 +17,8 @@
 	"""
 
 	ATTRIBUTES = Widget.ATTRIBUTES + [ BoolAttr("vertical_scrollbar"),BoolAttr("horizontal_scrollbar") ]
+	DEFAULT_HEXPAND = 1
+	DEFAULT_VEXPAND = 1
 
 	def __init__(self,**kwargs):
 		self.real_widget = fife.ScrollArea()
@@ -42,16 +44,18 @@
 	def _getContent(self): return self._content
 	content = property(_getContent,_setContent)
 
-	def deepApply(self,visitorFunc):
-		if self._content: self._content.deepApply(visitorFunc)
+	def deepApply(self,visitorFunc, leaves_first = True):
+		if leaves_first:
+			if self._content: self._content.deepApply(visitorFunc, leaves_first = leaves_first)
 		visitorFunc(self)
+		if not leaves_first:
+			if self._content: self._content.deepApply(visitorFunc, leaves_first = leaves_first)
 
 	def resizeToContent(self,recurse=True):
 		if self._content is None: return
 		if recurse:
-			self.content.resizeToContent(recurse=True)
-		self.content.width = max(self.content.width,self.width-5)
-		self.content.height = max(self.content.height,self.height-5)
+			self.content.resizeToContent(recurse=recurse)
+		self.size = self.min_size
 
 	def _visibilityToScrollPolicy(self,visibility):
 		if visibility:
@@ -74,6 +78,11 @@
 
 	def _getVerticalScrollbar(self):
 		return self._scrollPolicyToVisibility( self.real_widget.getVerticalScrollPolicy() )
+		
+	def sizeChanged(self):
+		if self.content:
+			self.content.width = max(self.content.width,self.width-5)
+			self.content.height = max(self.content.height,self.height-5)
 
 	vertical_scrollbar = property(_getVerticalScrollbar,_setVerticalScrollbar)
 	horizontal_scrollbar = property(_getHorizontalScrollbar,_setHorizontalScrollbar)
--- a/engine/extensions/pychan/widgets/slider.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/slider.py	Mon Jun 08 16:00:02 2009 +0000
@@ -25,12 +25,18 @@
 	VERTICAL = fife.Slider.VERTICAL
 
 	ATTRIBUTES = Widget.ATTRIBUTES + [IntAttr('orientation'), FloatAttr('scale_start'), FloatAttr('scale_end')]
+	DEFAULT_HEXPAND = 1
+	DEFAULT_VEXPAND = 0
 
-	def __init__(self, scaleStart=0.0, scaleEnd=1.0, orientation=HORIZONTAL, **kwargs):
+	def __init__(self, scaleStart=0.0, scaleEnd=1.0, orientation=HORIZONTAL, min_size=(10,10),**kwargs):
 		self.real_widget = fife.Slider(scaleStart, scaleEnd)
 		self.orientation = orientation
 		self.setOrientation(self.orientation)
-		super(Slider, self).__init__(**kwargs)
+		super(Slider, self).__init__(min_size=min_size,**kwargs)
+
+		self.accepts_data = True
+		self._realSetData = self.setValue
+		self._realGetData = self.getValue
 
 	def _setScale(self, start, end):
 		"""setScale(self, double scaleStart, double scaleEnd)"""
--- a/engine/extensions/pychan/widgets/textbox.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/textbox.py	Mon Jun 08 16:00:02 2009 +0000
@@ -19,6 +19,8 @@
 	"""
 
 	ATTRIBUTES = Widget.ATTRIBUTES + [UnicodeAttr('text'),Attr('filename')]
+	DEFAULT_HEXPAND = 1
+	DEFAULT_VEXPAND = 1
 
 	def __init__(self,text=u"",filename = "", **kwargs):
 		self.real_widget = fife.TextBox()
--- a/engine/extensions/pychan/widgets/textfield.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/textfield.py	Mon Jun 08 16:00:02 2009 +0000
@@ -18,8 +18,10 @@
 	"""
 
 	ATTRIBUTES = Widget.ATTRIBUTES + [UnicodeAttr('text')]
+	DEFAULT_HEXPAND = 1
+	DEFAULT_VEXPAND = 0
 
-	def __init__(self,text=u"", **kwargs):
+	def __init__(self,text=u"",**kwargs):
 		self.real_widget = fife.TextField()
 		self.text = text
 		super(TextField,self).__init__(**kwargs)
--- a/engine/extensions/pychan/widgets/widget.py	Wed Jun 03 19:29:52 2009 +0000
+++ b/engine/extensions/pychan/widgets/widget.py	Mon Jun 08 16:00:02 2009 +0000
@@ -34,6 +34,8 @@
 	  - position_technique: This can be either "automatic" or "explicit" - only L{Window} has this set to "automatic" which
 	  results in new windows being centered on screen (for now).
 	  If it is set to "explicit" the position attribute will not be touched.
+	  - vexpand: Integer: >= 0. Proportion to expand this widget vertically.
+	  - hexpand: Integer: >= 0. Proportion to expand this widget horizontally.
 
 	Convenience Attributes
 	======================
@@ -59,34 +61,50 @@
 		PointAttr('min_size'), PointAttr('size'), PointAttr('max_size'),
 		ColorAttr('base_color'),ColorAttr('background_color'),ColorAttr('foreground_color'),ColorAttr('selection_color'),
 		Attr('style'), Attr('font'),IntAttr('border_size'),Attr('position_technique'),
-		UnicodeAttr('helptext'), BoolAttr('is_focusable') 
+		IntAttr('vexpand'),IntAttr('hexpand'),
+		UnicodeAttr('helptext'), BoolAttr('is_focusable')
 		]
 
 	DEFAULT_NAME = '__unnamed__'
+	DEFAULT_HEXPAND = 0
+	DEFAULT_VEXPAND = 0
+	DEFAULT_MAX_SIZE = 500000, 500000
 
 	HIDE_SHOW_ERROR = """\
 		You can only show/hide the top widget of a hierachy.
 		Use 'addChild' or 'removeChild' to add/remove labels for example.
 		"""
 
+
 	def __init__(self,parent = None, name = DEFAULT_NAME,
-				 size = (-1,-1), min_size=(0,0), max_size=(5000,5000),
+				 size = (-1,-1), min_size=(0,0), max_size=DEFAULT_MAX_SIZE,
 				 helptext=u"",
+				 position = (0,0),
 				 style = None, **kwargs):
 
 		assert( hasattr(self,'real_widget') )
 		self.event_mapper = events.EventMapper(self)
 		self._visible = False
+		self._extra_border = (0,0)
+		self.hexpand = kwargs.get("hexpand",self.DEFAULT_HEXPAND)
+		self.vexpand = kwargs.get("vexpand",self.DEFAULT_VEXPAND)
+		# Simple way to get at least some compat layout:
+		if get_manager().compat_layout:
+			self.hexpand, self.vexpand = 0,0
 
 		# Data distribution & retrieval settings
 		self.accepts_data = False
 		self.accepts_initial_data = False
 
+		# Parent attribute makes sure we only have one parent,
+		# that tests self.__parent - so make sure we have the attr here.
+		self.__parent = None
 		self.parent = parent
 
 		# This will also set the _event_id and call real_widget.setActionEventId
 		self.name = name
 
+		self.position = position
 		self.min_size = min_size
 		self.max_size = max_size
 		self.size = size
@@ -122,14 +140,14 @@
 		"""
 		if not get_manager().can_execute:
 			raise RuntimeError("Synchronous execution is not set up!")
-		if self._parent:
+		if self.__parent:
 			raise RuntimeError("You can only 'execute' root widgets, not %s!" % str(self))
 
 		for name,returnValue in bind.items():
 			def _quitThisDialog(returnValue = returnValue ):
 				get_manager().breakFromMainLoop( returnValue )
 				self.hide()
-			self.findChild(name=name).capture( _quitThisDialog )
+			self.findChild(name=name).capture( _quitThisDialog , group_name = "__execute__" )
 		self.show()
 		return get_manager().mainLoop()
 
@@ -175,7 +193,7 @@
 		"""
 		Show the widget and all contained widgets.
 		"""
-		if self._parent:
+		if self.parent:
 			raise RuntimeError(Widget.HIDE_SHOW_ERROR)
 		if self._visible: return
 		self.adaptLayout()
@@ -187,7 +205,7 @@
 		"""
 		Hide the widget and all contained widgets.
 		"""
-		if self._parent:
+		if self.parent:
 			raise RuntimeError(Widget.HIDE_SHOW_ERROR)
 		if not self._visible: return
 
@@ -202,8 +220,8 @@
 		either directly or as part of a container widget.
 		"""
 		widget = self
-		while widget._parent:
-			widget = widget._parent
+		while widget.parent:
+			widget = widget.parent
 		return widget._visible
 
 	def adaptLayout(self,recurse=True):
@@ -286,6 +304,23 @@
 		"""
 		raise RuntimeError("Trying to add a widget to %s, which doesn't allow this." % repr(self))
 
+	def insertChild(self, widget, position):
+		"""
+		This function inserts a widget a given index in the child list.
+		
+		See L{addChild} and L{insertChildBefore} 
+		"""
+		raise RuntimeError("Trying to insert a widget to %s, which doesn't allow this." % repr(self))
+		
+	def insertChildBefore(self, widget, before):
+		"""
+		Inserts a child widget before a given widget. If the widget isn't found,
+		the widget is appended to the children list.
+		
+		See L{addChild} and L{insertChild} 
+		"""
+		raise RuntimeError("Trying to insert a widget to %s, which doesn't allow this." % repr(self))
+
 	def addChildren(self,*widgets):
 		"""
 		Add multiple widgets as children.
@@ -508,7 +543,7 @@
 		"""
 		def _printNamedWidget(widget):
 			if widget.name != Widget.DEFAULT_NAME:
-				print widget.name.ljust(20),repr(widget).ljust(50),repr(widget._parent)
+				print widget.name.ljust(20),repr(widget).ljust(50),repr(widget.__parent)
 		print "Named child widgets of ",repr(self)
 		print "name".ljust(20),"widget".ljust(50),"parent"
 		self.deepApply(_printNamedWidget)
@@ -552,13 +587,26 @@
 		def _callExpandContent(widget):
 			#print "ETC:",widget
 			widget.expandContent()
-		self.deepApply(_callExpandContent)
+		self.deepApply(_callExpandContent, leaves_first=False)
 
-	def deepApply(self,visitorFunc):
+	def deepApply(self,visitorFunc, leaves_first = True):
 		"""
 		Recursively apply a callable to all contained widgets and then the widget itself.
 		"""
 		visitorFunc(self)
+		
+	def getAbsolutePos(self):
+		"""
+		Get absolute position on screen
+		"""
+		absX = self.x
+		absY = self.y
+		parent = self.parent
+		while parent is not None:
+			absX += parent.x
+			absY += parent.y
+			parent = parent.parent
+		return (absX, absY)
 
 	def sizeChanged(self):
 		pass
@@ -611,6 +659,19 @@
 
 	def _getHeight(self): return self.real_widget.getHeight()
 
+	def _getMinWidth(self): return self.min_size[0]
+	def _getMaxWidth(self): return self.max_size[0]
+	def _getMinHeight(self): return self.min_size[1]
+	def _getMaxHeight(self): return self.max_size[1]
+	def _setMinWidth(self,w):
+		self.min_size = w, self.min_size[1]
+	def _setMaxWidth(self,w):
+		self.max_size = w, self.max_size[1]
+	def _setMinHeight(self,h):
+		self.min_size = self.min_size[0],h
+	def _setMaxHeight(self,h):
+		self.max_size = self.max_size[0],h
+
 	def _setFont(self, font):
 		self._font = font
 		self.real_font = get_manager().getFont(font)
@@ -632,13 +693,19 @@
 		get_manager().stylize(self,style)
 	style = property(_getStyle,_setStyle)
 
-	def _getParent(self): return self._parent
+	def _getParent(self): return self.__parent
 	def _setParent(self,parent):
-		self._parent = parent
+		if self.__parent is not parent:
+			if self.__parent and parent is not None:
+				print "Widget containment fumble:", self, self.__parent, parent
+				self.__parent.removeChild(self)
+		self.__parent = parent
 	parent = property(_getParent,_setParent)
 
 	def _setName(self,name): self._name = name
-	def _getName(self): return self._name
+	def _getName(self):
+		# __str__ relies on self.name
+		return getattr(self,'_name','__no_name_yet__')
 	name = property(_getName,_setName)
 
 	def _setFocusable(self, b): self.real_widget.setFocusable(b)
@@ -649,6 +716,10 @@
 	y = property(_getY,_setY)
 	width = property(_getWidth,_setWidth)
 	height = property(_getHeight,_setHeight)
+	min_width = property(_getMinWidth,_setMinWidth)
+	min_height = property(_getMinHeight,_setMinHeight)
+	max_width = property(_getMaxWidth,_setMaxWidth)
+	max_height = property(_getMaxHeight,_setMaxHeight)
 	size = property(_getSize,_setSize)
 	position = property(_getPosition,_setPosition)
 	font = property(_getFont,_setFont)
--- a/engine/extensions/timer.py	Wed Jun 03 19:29:52 2009 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,107 +0,0 @@
-import fife
-
-"""
-Convenient timers
-=================
-
-Usage::
-  import timer
-  timer.init( my_fife_engine.getTimeManager() )
-  def spam():
-     print "SPAM SPAM ",
-     return "More spam?" # a string is a true value, so it's repeated.
-  repeater = timer.repeatCall(500,spam)
-  def stop_spam():
-     repeater.stop()
-     print "BACON EGGS AND SPAM"
-  timer.delayCall(50000,stop_spam)
-
-"""
-
-
-_manager = None
-_alltimers = {}
-
-def init(timemanager):
-	"""
-	Initialize timers.
-
-	@param timemanager: A L{fife.TimeManager} as retuned by L{fife.Engine.getTimeManager}.
-	"""
-	global _manager
-	_manager = timemanager
-
-class Timer(fife.TimeEvent):
-	def __init__(self,delay=0,callback=None):
-		super(Timer,self).__init__(0)
-		self.is_registered = False
-		self.callback = callback
-		self.setPeriod(delay)
-
-	def start(self):
-		if self.is_registered:
-			return
-		self.is_registered = True
-		global _alltimers
-		_alltimers[self]=1
-	    
-		_manager.registerEvent(self)
-
-	def stop(self):
-		if not self.is_registered:
-			return
-		self.is_registered = False
-		global _alltimers
-		del _alltimers[self]
-
-		_manager.unregisterEvent(self)
-
-	def updateEvent(self,delta):
-		if callable(self.callback):
-			self.callback()
-
-def delayCall(delay,callback):
-	"""
-	Delay a function call by a number of milliseconds.
-
-	@param delay Delay in milliseconds.
-	@param callback The function to call.
-
-	@return The timer.
-	"""
-	timer = Timer(delay)
-	def real_callback():
-		timer.stop()
-		callback()
-	timer.callback = real_callback
-	timer.start()
-	return timer
-
-from traceback import print_exc
-def repeatCall(period,callback):
-	"""
-	Repeat a function call.
-
-	@param period Period between calls in milliseconds.
-	@param callback The function to call.
-
-	@return The timer.
-
-	The call is repeated until the callback returns a False
-	value (i.e. None) or the timer is stopped.
-	"""
-	timer = Timer(period)
-	def real_callback():
-		try:
-			if not callback():
-				timer.stop()
-		except Exception:
-			print_exc()
-			timer.stop()
-
-	timer.callback = real_callback
-	timer.start()
-	return timer
-
-__all__ = [init,Timer,delayCall,repeatCall]
-