# 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