File: sugar_tools.py

package info (click to toggle)
sugar-pippy-activity 36~dfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: squeeze
  • size: 792 kB
  • ctags: 632
  • sloc: python: 3,776; makefile: 16
file content (288 lines) | stat: -rw-r--r-- 11,529 bytes parent folder | download
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
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
# Copyright 2007 Collabora Ltd.
#
# 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

import logging
import telepathy

from sugar.activity.activity import Activity, ActivityToolbox
from sugar.presence import presenceservice

from sugar.presence.tubeconn import TubeConnection
from sugar.graphics.window import Window

import gtk
import gobject

import groupthink_base as groupthink

def exhaust_event_loop():
    while gtk.events_pending():
        gtk.main_iteration()

class GroupActivity(Activity):

    message_preparing = "Preparing user interface"
    message_loading = "Loading object from Journal"
    message_joining = "Joining shared activity"
    
    """Abstract Class for Activities using Groupthink"""
    def __init__(self, handle):
        # self.initiating indicates whether this instance has initiated sharing
        # it always starts false, but will be set to true if this activity
        # initiates sharing. In particular, if Activity.__init__ calls 
        # self.share(), self.initiating will be set to True.
        self.initiating = False
        # self._processed_share indicates whether when_shared() has been called
        self._processed_share = False
        # self.initialized tracks whether the Activity's display is up and running
        self.initialized = False
        
        self.early_setup()
        
        super(GroupActivity, self).__init__(handle)
        self.dbus_name = self.get_bundle_id()
        self.logger = logging.getLogger(self.dbus_name)
        
        self._handle = handle
        
        ##gobject.threads_init()
                
        self._sharing_completed = not self._shared_activity
        self._readfile_completed = not handle.object_id
        if self._shared_activity:
            self.message = self.message_joining
        elif handle.object_id:
            self.message = self.message_loading
        else:
            self.message = self.message_preparing

        # top toolbar with share and close buttons:
        toolbox = ActivityToolbox(self)
        self.set_toolbox(toolbox)
        toolbox.show()
        
        v = gtk.VBox()
        self.startup_label = gtk.Label(self.message)
        v.pack_start(self.startup_label)
        Window.set_canvas(self,v)
        self.show_all()
        
        # The show_all method queues up draw events, but they aren't executed
        # until the mainloop has a chance to process them.  We want to process
        # them immediately, because we need to show the waiting screen
        # before the waiting starts, not after.
        exhaust_event_loop()
        # exhaust_event_loop() provides the possibility that write_file could
        # be called at this time, so write_file is designed to trigger read_file
        # itself if that path occurs.
        
        self.tubebox = groupthink.TubeBox()
        self.timer = groupthink.TimeHandler("main", self.tubebox)
        self.cloud = groupthink.Group(self.tubebox)
        # self.cloud is extremely important.  It is the unified reference point
        # that contains all state in the system.  Everything else is stateless.
        # self.cloud has to be defined before the call to self.set_canvas, because
        # set_canvas can trigger almost anything, including pending calls to read_file,
        # which relies on self.cloud.
        
        # get the Presence Service
        self.pservice = presenceservice.get_instance()
        # Buddy object for you
        owner = self.pservice.get_owner()
        self.owner = owner

        self.connect('shared', self._shared_cb)
        self.connect('joined', self._joined_cb)
        if self.get_shared():
            if self.initiating:
                self._shared_cb(self)
            else:
                self._joined_cb(self)
        
        self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK)
        self.connect("visibility-notify-event", self._visible_cb)
        self.connect("notify::active", self._active_cb)
        
        if not self._readfile_completed:
            self.read_file(self._jobject.file_path)
        elif not self._shared_activity:
            gobject.idle_add(self._initialize_cleanstart)
    
    def _initialize_cleanstart(self):
        self.initialize_cleanstart()
        self._initialize_display()
        return False
    
    def initialize_cleanstart(self):
        """Any subclass that needs to take any extra action in the case where
        the activity is launched locally without a sharing context or input
        file should override this method"""
        pass
    
    def early_setup(self):
        """Any subclass that needs to take an action before any external interaction
        (e.g. read_file, write_file) occurs should place that code in early_setup"""
        pass
    
    def _initialize_display(self):
        main_widget = self.initialize_display()
        Window.set_canvas(self, main_widget)
        self.initialized = True
        if self._shared_activity and not self._processed_share:
            # We are joining a shared activity, but when_shared has not yet
            # been called
            self.when_shared()
            self._processed_share = True
        self.show_all()
    
    def initialize_display(self):
        """All subclasses must override this method, in order to display
        their GUI using self.set_canvas()"""
        raise NotImplementedError
        
    def share(self, private=False):
        """The purpose of this function is solely to permit us to determine
        whether share() has been called.  This is necessary because share() may
        be called during Activity.__init__, and thus emit the 'shared' signal
        before we have a chance to connect any signal handlers."""
        self.initiating = True
        super(GroupActivity, self).share(private)
        if self.initialized and not self._processed_share:
            self.when_shared()
            self._processed_share = True
    
    def when_shared(self):
        """Inheritors should override this method to perform any special
        operations when the user shares the session"""
        pass

    def _shared_cb(self, activity):
        self.logger.debug('My activity was shared')
        self.initiating = True
        self._sharing_setup()

        self.logger.debug('This is my activity: making a tube...')
        id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube(
            self.dbus_name, {})

    def _sharing_setup(self):
        if self._shared_activity is None:
            self.logger.error('Failed to share or join activity')
            return

        self.conn = self._shared_activity.telepathy_conn
        self.tubes_chan = self._shared_activity.telepathy_tubes_chan
        self.text_chan = self._shared_activity.telepathy_text_chan

        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube',
            self._new_tube_cb)

    def _list_tubes_reply_cb(self, tubes):
        self.logger.debug('Got %d tubes from ListTubes' % len(tubes))
        for tube_info in tubes:
            self._new_tube_cb(*tube_info)

    def _list_tubes_error_cb(self, e):
        self.logger.error('ListTubes() failed: %s', e)

    def _joined_cb(self, activity):
        if not self._shared_activity:
            return

        self.logger.debug('Joined an existing shared activity')
        self.initiating = False
        self._sharing_setup()

        self.logger.debug('This is not my activity: waiting for a tube...')
        self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes(
            reply_handler=self._list_tubes_reply_cb,
            error_handler=self._list_tubes_error_cb)

    def _new_tube_cb(self, id, initiator, type, service, params, state):
        self.logger.debug('New tube: ID=%d initator=%d type=%d service=%s '
                     'params=%r state=%d', id, initiator, type, service,
                     params, state)
        if (type == telepathy.TUBE_TYPE_DBUS and
            service == self.dbus_name):
            if state == telepathy.TUBE_STATE_LOCAL_PENDING:
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id)
            tube_conn = TubeConnection(self.conn,
                self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES],
                id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP])
            self.tubebox.insert_tube(tube_conn, self.initiating)
            self._sharing_completed = True
            if self._readfile_completed and not self.initialized:
                self._initialize_display()

    def read_file(self, file_path):
        self.cloud.loads(self.load_from_journal(file_path))
        self._readfile_completed = True
        if self._sharing_completed and not self.initialized:
            self._initialize_display()
        pass
        
    def load_from_journal(self, file_path):
        """This implementation of load_from_journal simply returns the contents
        of the file.  Any inheritor overriding this method must return the
        string provided to save_to_journal as cloudstring."""
	if file_path:
            f = file(file_path,'rb')
            s = f.read()
            f.close()
            return s
    
    def write_file(self, file_path):
        # There is a possibility that the user could trigger a write_file
        # action before read_file has occurred.  This could be dangerous,
        # potentially overwriting the journal entry with blank state.  To avoid
        # this, we ensure that read_file has been called (if there is a file to
        # read) before writing.
        if not self._readfile_completed:
            self.read_file(self._jobject.file_path)
        self.save_to_journal(file_path, self.cloud.dumps())            

    def save_to_journal(self, file_path, cloudstring):
        """This implementation of save_to_journal simply dumps the output of 
        self.cloud.dumps() to disk.  Any inheritor who wishes to control file
        output should override this method, and must 
        be sure to include cloudstring in its write_file."""
        f = file(file_path, 'wb')
        f.write(cloudstring)
        f.close()
        
    def _active_cb(self, widget, event):
        self.logger.debug("_active_cb")
        if self.props.active:
            self.resume()
        else:
            self.pause()
            
    def _visible_cb(self, widget, event):
        self.logger.debug("_visible_cb")
        if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED:
            self.pause()
        else:
            self.resume()
    
    def pause(self):
        """Subclasses should override this function to stop updating the display
        since it is not visible."""
        pass
    
    def resume(self):
        """Subclasses should override this function to resume updating the
        display, since it is now visible"""
        pass