Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/actions.py
blob: 08f55cd5cfa2c0c0e70d5388616ba51216bb9686 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# 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
"""
import gtk

from gettext import gettext as _

from sugar.tutorius import addon
from sugar.tutorius.services import ObjectStore
from sugar.tutorius.properties import *
from sugar.graphics import icon

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()