From 0c3f127c86af818d260966d2292b199757087157 Mon Sep 17 00:00:00 2001 From: Simon Poirier Date: Sat, 11 Jul 2009 21:39:46 +0000 Subject: repackage --- (limited to 'tutorius/actions.py') diff --git a/tutorius/actions.py b/tutorius/actions.py new file mode 100644 index 0000000..4269cd7 --- /dev/null +++ b/tutorius/actions.py @@ -0,0 +1,324 @@ +# Copyright (C) 2009, Tutorius.org +# +# This program 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 +""" +This module defines Actions that can be done and undone on a state +""" +from gettext import gettext as _ + +from sugar.tutorius import gtkutils, addon +from dialog import TutoriusDialog +import overlayer +from sugar.tutorius.editor import WidgetIdentifier +from sugar.tutorius.services import ObjectStore +from sugar.tutorius.properties import * +from sugar.graphics import icon +import gtk.gdk + +class DragWrapper(object): + """Wrapper to allow gtk widgets to be dragged around""" + def __init__(self, widget, position, draggable=False): + """ + Creates a wrapper to allow gtk widgets to be mouse dragged, if the + parent container supports the move() method, like a gtk.Layout. + @param widget the widget to enhance with drag capability + @param position the widget's position. Will translate the widget if needed + @param draggable wether to enable the drag functionality now + """ + self._widget = widget + self._eventbox = None + self._drag_on = False # whether dragging is enabled + self._rel_pos = (0,0) # mouse pos relative to widget + self._handles = [] # event handlers + self._dragging = False # whether a drag is in progress + self.position = position # position of the widget + + self.draggable = draggable + + def _pressed_cb(self, widget, evt): + """Callback for start of drag event""" + self._eventbox.grab_add() + self._dragging = True + self._rel_pos = evt.get_coords() + + def _moved_cb(self, widget, evt): + """Callback for mouse drag events""" + if not self._dragging: + return + + # Focus on a widget before dragging another would + # create addititonal move event, making the widget jump unexpectedly. + # Solution found was to process those focus events before dragging. + if gtk.events_pending(): + return + + xrel, yrel = self._rel_pos + xparent, yparent = evt.get_coords() + xparent, yparent = widget.translate_coordinates(widget.parent, + xparent, yparent) + self.position = (xparent-xrel, yparent-yrel) + self._widget.parent.move(self._eventbox, *self.position) + self._widget.parent.move(self._widget, *self.position) + self._widget.parent.queue_draw() + + def _released_cb(self, *args): + """Callback for end of drag (mouse release).""" + self._eventbox.grab_remove() + self._dragging = False + + def _drag_end(self, *args): + """Callback for end of drag (stolen focus).""" + self._dragging = False + + def set_draggable(self, value): + """Setter for the draggable property""" + if bool(value) ^ bool(self._drag_on): + if value: + self._eventbox = gtk.EventBox() + self._eventbox.show() + self._eventbox.set_visible_window(False) + size = self._widget.size_request() + self._eventbox.set_size_request(*size) + self._widget.parent.put(self._eventbox, *self.position) + self._handles.append(self._eventbox.connect( + "button-press-event", self._pressed_cb)) + self._handles.append(self._eventbox.connect( + "button-release-event", self._released_cb)) + self._handles.append(self._eventbox.connect( + "motion-notify-event", self._moved_cb)) + self._handles.append(self._eventbox.connect( + "grab-broken-event", self._drag_end)) + else: + while len(self._handles): + handle = self._handles.pop() + self._eventbox.disconnect(handle) + self._eventbox.parent.remove(self._eventbox) + self._eventbox.destroy() + self._eventbox = None + self._drag_on = value + + def get_draggable(self): + """Getter for the draggable property""" + return self._drag_on + + draggable = property(fset=set_draggable, fget=get_draggable, \ + doc="Property to enable the draggable behaviour of the widget") + + def set_widget(self, widget): + """Setter for the widget property""" + if self._dragging or self._drag_on: + raise Exception("Can't change widget while dragging is enabled.") + + assert hasattr(widget, "parent"), "wrapped widget should have a parent" + parent = widget.parent + assert hasattr(parent, "move"), "container of widget need move method" + self._widget = widget + + def get_widget(self): + """Getter for the widget property""" + return self._widget + + widget = property(fset=set_widget, fget=get_widget) + +class Action(TPropContainer): + """Base class for Actions""" + def __init__(self): + TPropContainer.__init__(self) + self.position = (0,0) + self._drag = None + + def do(self, **kwargs): + """ + Perform the action + """ + raise NotImplementedError("Not implemented") + + def undo(self): + """ + Revert anything the action has changed + """ + pass #Should raise NotImplemented? + + def enter_editmode(self, **kwargs): + """ + Enters edit mode. The action should display itself in some way, + without affecting the currently running application. The default is + a small box with the action icon. + """ + meta = addon.get_addon_meta(type(self).__name__) + + actionicon = icon.Icon(icon_name=meta['icon'], + icon_size=gtk.ICON_SIZE_LARGE_TOOLBAR) + # Eventbox create a visible window for the icon, so it clips correctly + self.__edit_img = gtk.EventBox() + self.__edit_img.set_visible_window(True) + self.__edit_img.add(actionicon) + + x, y = self.position + + ObjectStore().activity._overlayer.put(self.__edit_img, x, y) + self.__edit_img.show_all() + self._drag = DragWrapper(self.__edit_img, self.position, True) + + def exit_editmode(self, **kwargs): + x, y = self._drag.position + self.position = [int(x), int(y)] + self.__edit_img.destroy() + +class OnceWrapper(Action): + """ + Wraps a class to perform an action once only + + This ConcreteActions's do() method will only be called on the first do() + and the undo() will be callable after do() has been called + """ + + _action = TAddonProperty() + + def __init__(self, action): + Action.__init__(self) + self._called = False + self._need_undo = False + self._action = action + + def do(self): + """ + Do the action only on the first time + """ + if not self._called: + self._called = True + self._action.do() + self._need_undo = True + + def undo(self): + """ + Undo the action if it's been done + """ + if self._need_undo: + self._action.undo() + self._need_undo = False + +class WidgetIdentifyAction(Action): + def __init__(self): + Action.__init__(self) + self.activity = None + self._dialog = None + + def do(self): + os = ObjectStore() + if os.activity: + self.activity = os.activity + + self._dialog = WidgetIdentifier(self.activity) + self._dialog.show() + + + def undo(self): + if self._dialog: + self._dialog.destroy() + +class ChainAction(Action): + """Utility class to allow executing actions in a specific order""" + def __init__(self, *actions): + """ChainAction(action1, ... ) builds a chain of actions""" + Action.__init__(self) + self._actions = actions + + def do(self,**kwargs): + """do() each action in the chain""" + for act in self._actions: + act.do(**kwargs) + + def undo(self): + """undo() each action in the chain, starting with the last""" + for act in reversed(self._actions): + act.undo() + +class DisableWidgetAction(Action): + def __init__(self, target): + """Constructor + @param target target treeish + """ + Action.__init__(self) + self._target = target + self._widget = None + + def do(self): + """Action do""" + os = ObjectStore() + if os.activity: + self._widget = gtkutils.find_widget(os.activity, self._target) + if self._widget: + self._widget.set_sensitive(False) + + def undo(self): + """Action undo""" + if self._widget: + self._widget.set_sensitive(True) + + +class TypeTextAction(Action): + """ + Simulate a user typing text in a widget + Work on any widget that implements a insert_text method + + @param widget The treehish representation of the widget + @param text the text that is typed + """ + def __init__(self, widget, text): + Action.__init__(self) + + self._widget = widget + self._text = text + + def do(self, **kwargs): + """ + Type the text + """ + widget = gtkutils.find_widget(ObjectStore().activity, self._widget) + if hasattr(widget, "insert_text"): + widget.insert_text(self._text, -1) + + def undo(self): + """ + no undo + """ + pass + +class ClickAction(Action): + """ + Action that simulate a click on a widget + Work on any widget that implements a clicked() method + + @param widget The threehish representation of the widget + """ + def __init__(self, widget): + Action.__init__(self) + self._widget = widget + + def do(self): + """ + click the widget + """ + widget = gtkutils.find_widget(ObjectStore().activity, self._widget) + if hasattr(widget, "clicked"): + widget.clicked() + + def undo(self): + """ + No undo + """ + pass + -- cgit v0.9.1