# 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