Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/actions.py
diff options
context:
space:
mode:
Diffstat (limited to 'tutorius/actions.py')
-rw-r--r--tutorius/actions.py324
1 files changed, 324 insertions, 0 deletions
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
+