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
231
232
233
234
235
236
237
238
239
240
241
242
243
|
# 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_fail(self, *args):
"""
Callback to prevent the animation that brings back the dragged item to
its initial position.
"""
LOGGER.debug("DragWrapper :: Drag fail event was raised")
return True
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-failed", self._drag_fail))
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"""
source = TStringProperty(None, null=True)
def __init__(self, **kwargs):
super(Action, self).__init__(**kwargs)
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)
# FIXME remove position when it's not required.
# Changes will also be required in the overview and DragWrapper.
if not hasattr(self, 'position'):
self.position = 0, 0
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()
|