# HG changeset patch
# User cheesesucker@33b003aa-7bff-0310-803a-e67f0ece8222
# Date 1244476802 0
# Node ID 51cc05d862f2adf7ccdd1e65ff90238e2f640e7b
# Parent 10b5f7f36dd42da84dab840b90e2c3d1d338c4b3
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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 build/linux2-config-dist.py
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/__init__.py
--- 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' ]
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/fifedit.py
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/cameraedit.xml
--- 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 @@
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/eleveditor.xml
--- 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 @@
-
+
@@ -8,4 +8,4 @@
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/error.xml
--- /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 @@
+
+
+
+
+
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/filebrowser.xml
--- 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 @@
-
-
-
+
+
+
-
+
-
+
-
+
-
+
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/help.xml
--- 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 @@
-
-
+
+
+
+
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/add_instance.png
Binary file clients/editor/gui/icons/add_instance.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/add_layer.png
Binary file clients/editor/gui/icons/add_layer.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/cycle_styles.png
Binary file clients/editor/gui/icons/cycle_styles.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/delete_layer.png
Binary file clients/editor/gui/icons/delete_layer.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/erase_instance.png
Binary file clients/editor/gui/icons/erase_instance.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/help.png
Binary file clients/editor/gui/icons/help.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/is_visible.png
Binary file clients/editor/gui/icons/is_visible.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/load_map.png
Binary file clients/editor/gui/icons/load_map.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/map_wizard.png
Binary file clients/editor/gui/icons/map_wizard.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/move_instance.png
Binary file clients/editor/gui/icons/move_instance.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/new_map.png
Binary file clients/editor/gui/icons/new_map.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/next_branch.png
Binary file clients/editor/gui/icons/next_branch.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/objectpicker.png
Binary file clients/editor/gui/icons/objectpicker.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/previous_branch.png
Binary file clients/editor/gui/icons/previous_branch.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/quit.png
Binary file clients/editor/gui/icons/quit.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/redo.png
Binary file clients/editor/gui/icons/redo.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/rotate_clockwise.png
Binary file clients/editor/gui/icons/rotate_clockwise.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/rotate_countercw.png
Binary file clients/editor/gui/icons/rotate_countercw.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/save_allmaps.png
Binary file clients/editor/gui/icons/save_allmaps.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/save_map.png
Binary file clients/editor/gui/icons/save_map.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/save_mapas.png
Binary file clients/editor/gui/icons/save_mapas.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/select_instance.png
Binary file clients/editor/gui/icons/select_instance.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/take_screenshot.png
Binary file clients/editor/gui/icons/take_screenshot.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/undo.png
Binary file clients/editor/gui/icons/undo.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/zoom_default.png
Binary file clients/editor/gui/icons/zoom_default.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/zoom_in.png
Binary file clients/editor/gui/icons/zoom_in.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/icons/zoom_out.png
Binary file clients/editor/gui/icons/zoom_out.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/layertool.xml
--- 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 @@
-
+
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/mapeditor.xml
--- 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 @@
-
-
-
-
-
-
-
-
-
-
-
-
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/objectedit.xml
--- 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 @@
-
+
+
@@ -15,45 +16,24 @@
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
@@ -70,8 +50,6 @@
-
-
-
+
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/objectselector.xml
--- 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 @@
-
-
+
+
-
-
+
+
-
+
@@ -20,8 +20,8 @@
-
+
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/gui/selection.xml
--- 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 @@
-
+
-
+
@@ -8,4 +8,4 @@
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/add_instance.png
Binary file clients/editor/icons/add_instance.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/add_layer.png
Binary file clients/editor/icons/add_layer.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/delete_layer.png
Binary file clients/editor/icons/delete_layer.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/eraser.png
Binary file clients/editor/icons/eraser.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/hand.png
Binary file clients/editor/icons/hand.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/help.png
Binary file clients/editor/icons/help.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/is_visible.png
Binary file clients/editor/icons/is_visible.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/load_map.png
Binary file clients/editor/icons/load_map.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/map_wizard.png
Binary file clients/editor/icons/map_wizard.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/new_map.png
Binary file clients/editor/icons/new_map.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/quit.png
Binary file clients/editor/icons/quit.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/save_map.png
Binary file clients/editor/icons/save_map.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/select_layer.png
Binary file clients/editor/icons/select_layer.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/take_screenshot.png
Binary file clients/editor/icons/take_screenshot.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/zoom_default.png
Binary file clients/editor/icons/zoom_default.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/zoom_in.png
Binary file clients/editor/icons/zoom_in.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/icons/zoom_out.png
Binary file clients/editor/icons/zoom_out.png has changed
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/input.py
--- 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()
-
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/lang/infotext.txt
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/listener.py
--- 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
-
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/misc/infotext.txt
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/HistoryManager.py
--- /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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/LayerTool.py
--- /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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/ObjectEdit.py
--- /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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/ObjectSelector.py
--- /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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/importer.py
--- 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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/layertool.py
--- 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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/mapeditor.py
--- 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())
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/maploader.py
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/mapwizard.py
--- 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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/objectedit.py
--- 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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/objectselector.py
--- 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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/plugins/plugin.py
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/run.py
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/__init__.py
--- /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.
+"""
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/editor.py
--- /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.,!?-+/():;%&`'*#=[]\\\""
+ 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/events/__init__.py
--- /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 *
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/events/events.py
--- /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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/events/saferef.py
--- /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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/events/signal.py
--- /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]
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/__init__.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/action.py
--- /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)
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/dockarea.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/error.py
--- /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
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/faketabwidget.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/filemanager.py
--- /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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/input.py
--- /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()
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/mainwindow.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/mapeditor.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/menubar.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/panel.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/resizablebase.py
--- /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 = {}
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/selection.py
--- /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])
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/statusbar.py
--- /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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/gui/toolbar.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/mapcontroller.py
--- /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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/mapview.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/plugin.py
--- /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"
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/settings.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/tests/__init__.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/tests/undotest.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/scripts/undomanager.py
--- /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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/selection.py
--- 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])
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/settings-dist.xml
--- /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 @@
+
+
+
+ 0
+ 1
+ OpenGL
+ 1024
+ 768
+ 0
+ 5.0
+ 1
+ FIFE - Editor
+
+ maps/shrine.xml
+ fonts/FreeSans.ttf
+ abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.,!?-+/():;%&`'*#=[]\"
+ controller
+ False
+ 1
+ 0
+
+
+ True
+ True
+ True
+ True
+
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/editor/settings.py
--- 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 = ''
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/pychan_demo/dynamic.py
--- 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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/pychan_demo/gui/demoapp.xml
--- 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 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+-
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/pychan_demo/gui/slider.xml
--- /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 @@
+
+
+
+
+
+
+
+
+
+
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/pychan_demo/gui/styling.xml
--- 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 @@
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/pychan_demo/pychan_test.py
--- 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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/pychan_demo/sliders.py
--- /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),
+ })
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/pychan_demo/styling.py
--- 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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/rio_de_hola/maps/shrine.xml
--- 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 @@
-
+
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/rio_de_hola/maps/tourist_beach.xml
--- 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 @@
-
+
-
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 clients/rio_de_hola/scripts/world.py
--- 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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/gui/guimanager.cpp
--- 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
#include
+#include
#include
#include
@@ -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(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(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;
+ }
}
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/gui/guimanager.h
--- 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;
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/gui/widgets/clicklabel.cpp
--- 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);
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/gui/widgets/togglebutton.cpp
--- 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(getFont()->getWidth(mCaption)+2*mSpacing), w);
+ h = std::max(static_cast(getFont()->getHeight()+2*mSpacing), h);
+ }
+
setWidth(w);
setHeight(h);
}
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/gui/widgets/togglebutton.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();
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/gui/widgets/widgets.i
--- 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);
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/model/structures/layer.cpp
--- 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 Layer::getInstancesAt(Location& loc, bool use_exactcoordinates) {
+ std::vector matching_instances;
+ std::vector::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;
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/model/structures/layer.h
--- 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 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 getInstancesAt(Location& loc, bool use_exactcoordinates=false);
+
/** Get the first instance on this layer with the given identifier.
*/
Instance* getInstance(const std::string& identifier);
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/model/structures/layer.i
--- 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& getInstances() const;
std::vector getInstances(const std::string& identifier);
+ std::vector getInstancesAt(Location& loc, bool use_exactcoordinates=false);
Instance* getInstance(const std::string& id);
void setInstancesVisible(bool vis);
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/util/time/timemanager.cpp
--- 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::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;
}
}
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/video/cursor.cpp
--- 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
+#include
+#endif
+
+#if defined( __linux__ )
+#include
+#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(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(iconinfo.xHotspot);
+ curs2->hot_y = static_cast(iconinfo.yHotspot);
+ }
+
+#elif defined(__linux__)
+ cursor->x_cursor = xCursor;
+ XSync(dsp, false);
+#endif
+
+ m_native_cursor = curs2;
+ SDL_SetCursor(curs2);
+
+ }
}
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/video/cursor.h
--- 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;
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/video/video.i
--- 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:
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/view/renderers/cellselectionrenderer.cpp
--- 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::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::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& 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::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 vertices;
- cg->getVertices(vertices, m_loc->getLayerCoordinates());
- std::vector::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 vertices;
+ cg->getVertices(vertices, loc.getLayerCoordinates());
+ std::vector::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);
}
}
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/view/renderers/cellselectionrenderer.h
--- 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& 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 getLocations() const { return m_locations; }
private:
- // selected location
- Location* m_loc;
+ // selected locations
+ std::vector m_locations;
};
}
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/core/view/renderers/cellselectionrenderer.i
--- 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 getLocations() const;
private:
CellSelectionRenderer(RenderBackend* renderbackend, int position);
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/fife_timer.py
--- /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]
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/filebrowser.py
--- 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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/__init__.py
--- 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):
"""
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/dialogs.py
--- 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 @@
-
+
"""
@@ -80,8 +80,8 @@
-
-
+
+
"""
@@ -117,8 +117,8 @@
EXCEPTION_CATCHER_XML="""\
-
-
+
+
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/events.py
--- 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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/internal.py
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/layout.py
--- 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 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 "" % (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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/__init__.py
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/basictextwidget.py
--- 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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/buttons.py
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/containers.py
--- 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 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 "" % (getattr(self._parent,'name','None'),id(self))
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/label.py
--- 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
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/layout.py
--- 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 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 "" % (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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/listbox.py
--- 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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/scrollarea.py
--- 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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/slider.py
--- 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)"""
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/textbox.py
--- 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()
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/textfield.py
--- 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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/pychan/widgets/widget.py
--- 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)
diff -r 10b5f7f36dd4 -r 51cc05d862f2 engine/extensions/timer.py
--- 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]
-