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