Web   ·   Wiki   ·   Activities   ·   Blog   ·   Lists   ·   Chat   ·   Meeting   ·   Bugs   ·   Git   ·   Translate   ·   Archive   ·   People   ·   Donate
summaryrefslogtreecommitdiffstats
path: root/tutorius/actions.py
blob: a809980fbb0d1bc1b630e73efd920ec7a31fbe14 (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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# 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
import logging
from gettext import gettext as _

from sugar.graphics import icon

from . import addon
from .services import ObjectStore
from .properties import *
from .constants import *


import pickle

import logging

LOGGER = logging.getLogger("actions")

class DragWrapper(object):
    """Wrapper to allow gtk widgets to be dragged around"""

    fromImage = [ ( WIDGET_ID, 0, TARGET_TYPE_WIDGET ) ]

    LOGGER = logging.getLogger("sugar.tutorius.actions.DragWrapper")

    def __init__(self, widget, position, update_action_cb, 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 update_action_cb The callback to trigger 
        @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.position = position # position of the widget
        self.moved = False

        self.update_action_cb = update_action_cb
        self.draggable = draggable

    def _drag_begin(self, widget, drag_context, *args):
        """Callback for initialisation of drag and drop"""
        # Setup the drag icon to what the widget is rendering
        self.LOGGER.debug("%s drag-begin"%(str(widget)))
        width = self._widget.allocation.width
        height = self._widget.allocation.height
        x = self._widget.allocation.x
        y = self._widget.allocation.y
        depth = 24 # Should be set dynamically 

        pxbuf = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, width, height)
        pxbuf.fill(0xFFFFFFFF)
        
        px = gtk.gdk.Pixmap(None, width, height, depth) # source, size, colors
        px.set_colormap(gtk.gdk.colormap_get_system())
        ctxt = px.cairo_create()
        ctxt.set_source_pixbuf(pxbuf,0,0)
        ctxt.paint()

        # Compensate when drawing the icon for the context 
        # translation done to the position occupied by the widget 
        ctxt.translate(-x, -y)
        self._widget.draw_with_context(ctxt)

        pxbuf.get_from_drawable(px,gtk.gdk.colormap_get_system(), 0, 0, 0, 0, -1, -1)
        drag_context.set_icon_pixbuf(pxbuf,0,0)
    
    def _drag_end(self, widget, context, *args):
        """Callback for end of drag (stolen focus)."""
        xrel, yrel = self._rel_pos
        rootwin = widget.get_screen().get_root_window()
        xparent,yparent,mods = rootwin.get_pointer()

        self.position = (int(xparent-xrel), int(yparent-yrel))
        self.LOGGER.debug("%s drag-end pos: %s"%(str(widget),str(self.position)))
        self._widget.parent.move(self._eventbox, *self.position)
        self._widget.parent.move(self._widget, *self.position)
        self._widget.parent.queue_draw()

        self.update_action_cb('position', self.position)


    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)

                # Prepare the widget for drag and drop 
                self._eventbox.drag_source_set(gtk.gdk.BUTTON1_MASK,
                                               self.fromImage,
                                               gtk.gdk.ACTION_MOVE)
                self._handles.append(self._eventbox.connect(
                    "drag-begin", self._drag_begin))
                self._handles.append(self._eventbox.connect(
                    "drag-end", 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
        # The callback that will be triggered when the action is requested
        # to notify all its changes
        self._properties_updated_cb = 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 set_notification_cb(self, notif_cb):
        LOGGER.debug("Action :: Setting notification callback for creator...")
        self._properties_updated_cb = notif_cb
    
    def update_property(self, name, value):
        """
        Callback used in the wrapper to send a new value to an action.
        """
        LOGGER.debug("Action :: update_property on %s with value '%s'"%(name, str(value)))
        # Set the property itself - this will modify the diff dict and we will
        # be able to notify the owner with the new value
        self.__setattr__(name, value)

        # Send the notification to the creator
        self.notify()

    def notify(self):
        LOGGER.debug("Action :: Notifying creator with new values in dict : %s"%(str(self._diff_dict)))
        # If a notification callback was registered
        if self._properties_updated_cb:
            # Propagate it
            self._properties_updated_cb(self._diff_dict)
            # Empty the diff dict as we just synchronized with the creator
            self._diff_dict.clear()

    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, update_action_cb=self.update_property, draggable=True)

    def exit_editmode(self, **kwargs):
        x, y = self._drag.position
        self.position = [int(x), int(y)]
        self.__edit_img.destroy()