Package groupthink :: Module sugar_tools
[hide private]
[frames] | no frames]

Source Code for Module groupthink.sugar_tools

  1  """ 
  2  a module to make sharing easier in Sugar Activities. 
  3  """ 
  4   
  5  import logging 
  6  import telepathy 
  7   
  8  from sugar.activity.activity import Activity, ActivityToolbox 
  9  from sugar.presence import presenceservice 
 10   
 11  from sugar.presence.tubeconn import TubeConnection 
 12  from sugar.graphics.window import Window 
 13   
 14  import gtk 
 15  import gobject 
 16   
 17  import groupthink_base as groupthink 
 18   
19 -def exhaust_event_loop():
20 while gtk.events_pending(): 21 gtk.main_iteration()
22
23 -class GroupActivity(Activity):
24 """ 25 An abstract class for Activities using Groupthink. 26 Activity authors who are writing a shared Activity should consider 27 inheriting from GroupActivity instead of Sugar's standard Activity class. 28 GroupActivity automates (and hides) handling of Telepathy tubes. It also 29 initializes a L{Group} and a L{TimeHandler} allowing shared activities to 30 be written with minimal boilerplate. For example, the following is a 31 working shared text editor using GroupActivity:: 32 from groupthink import sugar_tools, gtk_tools 33 import sugar 34 class SharedTextDemoActivity(sugar_tools.GroupActivity): 35 def initialize_display(self): 36 self.cloud.textview = gtk_tools.SharedTextView() 37 return self.cloud.textview 38 In addition to sharing code, GroupActivity also provides subclasses with 39 more informative startup screens and optionally automated save/load to the 40 datastore. 41 42 Caution: The methods required of a subclass of GroupActivity differ 43 substantially from the methods required of a subclass of Activity. For 44 example, subclasses of GroupActivity typically do not need to implement 45 a __init__ method. 46 """ 47 48 message_preparing = "Preparing user interface" 49 message_loading = "Loading object from Journal" 50 message_joining = "Joining shared activity" 51
52 - def __init__(self, handle):
53 # self.initiating indicates whether this instance has initiated sharing 54 # it always starts false, but will be set to true if this activity 55 # initiates sharing. In particular, if Activity.__init__ calls 56 # self.share(), self.initiating will be set to True. 57 self.initiating = False 58 # self._processed_share indicates whether when_shared() has been called 59 self._processed_share = False 60 # self.initialized tracks whether the Activity's display is up and running 61 self.initialized = False 62 63 self.early_setup() 64 65 super(GroupActivity, self).__init__(handle) 66 self.dbus_name = self.get_bundle_id() 67 self.logger = logging.getLogger(self.dbus_name) 68 69 self._handle = handle 70 71 ##gobject.threads_init() 72 73 self._sharing_completed = not self._shared_activity 74 self._readfile_completed = not handle.object_id 75 if self._shared_activity: 76 self.message = self.message_joining 77 elif handle.object_id: 78 self.message = self.message_loading 79 else: 80 self.message = self.message_preparing 81 82 # top toolbar with share and close buttons: 83 toolbox = ActivityToolbox(self) 84 self.set_toolbox(toolbox) 85 toolbox.show() 86 87 v = gtk.VBox() 88 self.startup_label = gtk.Label(self.message) 89 v.pack_start(self.startup_label) 90 Window.set_canvas(self,v) 91 self.show_all() 92 93 # The show_all method queues up draw events, but they aren't executed 94 # until the mainloop has a chance to process them. We want to process 95 # them immediately, because we need to show the waiting screen 96 # before the waiting starts, not after. 97 exhaust_event_loop() 98 # exhaust_event_loop() provides the possibility that write_file could 99 # be called at this time, so write_file is designed to trigger read_file 100 # itself if that path occurs. 101 102 self.tubebox = groupthink.TubeBox() 103 self.timer = groupthink.TimeHandler("main", self.tubebox) 104 self.cloud = groupthink.Group(self.tubebox) 105 # self.cloud is extremely important. It is the unified reference point 106 # that contains all state in the system. Everything else is stateless. 107 # self.cloud has to be defined before the call to self.set_canvas, because 108 # set_canvas can trigger almost anything, including pending calls to read_file, 109 # which relies on self.cloud. 110 111 # get the Presence Service 112 self.pservice = presenceservice.get_instance() 113 # Buddy object for you 114 owner = self.pservice.get_owner() 115 self.owner = owner 116 117 self.connect('shared', self._shared_cb) 118 self.connect('joined', self._joined_cb) 119 if self.get_shared(): 120 if self.initiating: 121 self._shared_cb(self) 122 else: 123 self._joined_cb(self) 124 125 self.add_events(gtk.gdk.VISIBILITY_NOTIFY_MASK) 126 self.connect("visibility-notify-event", self._visible_cb) 127 self.connect("notify::active", self._active_cb) 128 129 if not self._readfile_completed: 130 self.read_file(self._jobject.file_path) 131 elif not self._shared_activity: 132 gobject.idle_add(self._initialize_cleanstart)
133
134 - def _initialize_cleanstart(self):
135 self.initialize_cleanstart() 136 self._initialize_display() 137 return False
138
139 - def initialize_cleanstart(self):
140 """ 141 Any subclass that needs to take any extra action in the case where 142 the activity is launched locally without a sharing context or input 143 file should override this method""" 144 pass
145
146 - def early_setup(self):
147 """ 148 Any subclass that needs to take an action before any external interaction 149 (e.g. read_file, write_file) occurs should place that code in early_setup""" 150 pass
151
152 - def _initialize_display(self):
153 main_widget = self.initialize_display() 154 Window.set_canvas(self, main_widget) 155 self.initialized = True 156 if self._shared_activity and not self._processed_share: 157 # We are joining a shared activity, but when_shared has not yet 158 # been called 159 self.when_shared() 160 self._processed_share = True 161 self.show_all()
162
163 - def initialize_display(self):
164 """ 165 All subclasses must override this method, which is the principal 166 means of initializing a GroupActivity. 167 @rtype: gtk.Widget 168 @return: The widget that will be the display for this activity (i.e. 169 the canvas).""" 170 raise NotImplementedError
171
172 - def share(self, private=False):
173 """ 174 The purpose of this function is solely to permit us to determine 175 whether share() has been called. This is necessary because share() may 176 be called during Activity.__init__, and thus emit the 'shared' signal 177 before we have a chance to connect any signal handlers.""" 178 self.initiating = True 179 super(GroupActivity, self).share(private) 180 if self.initialized and not self._processed_share: 181 self.when_shared() 182 self._processed_share = True
183
184 - def when_shared(self):
185 """ 186 Inheritors should override this method to perform any special 187 operations when the user shares the session""" 188 pass
189
190 - def when_initiating_sharing(self):
191 """ 192 Inheritors should override this method to perform any special 193 operations upon initiating sharing. This method will not be called 194 for "joiners", only for "sharers".""" 195 pass
196
197 - def _shared_cb(self, activity):
198 self.logger.debug('My activity was shared') 199 self.initiating = True 200 self._sharing_setup() 201 202 self.logger.debug('This is my activity: making a tube...') 203 id = self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].OfferDBusTube( 204 self.dbus_name, {}) 205 self.when_initiating_sharing()
206
207 - def _sharing_setup(self):
208 if self._shared_activity is None: 209 self.logger.error('Failed to share or join activity') 210 return 211 212 self.conn = self._shared_activity.telepathy_conn 213 self.tubes_chan = self._shared_activity.telepathy_tubes_chan 214 self.text_chan = self._shared_activity.telepathy_text_chan 215 216 self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].connect_to_signal('NewTube', 217 self._new_tube_cb)
218
219 - def _list_tubes_reply_cb(self, tubes):
220 self.logger.debug('Got %d tubes from ListTubes' % len(tubes)) 221 for tube_info in tubes: 222 self._new_tube_cb(*tube_info)
223
224 - def _list_tubes_error_cb(self, e):
225 self.logger.error('ListTubes() failed: %s', e)
226
227 - def _joined_cb(self, activity):
228 if not self._shared_activity: 229 return 230 231 self.logger.debug('Joined an existing shared activity') 232 self.initiating = False 233 self._sharing_setup() 234 235 self.logger.debug('This is not my activity: waiting for a tube...') 236 self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].ListTubes( 237 reply_handler=self._list_tubes_reply_cb, 238 error_handler=self._list_tubes_error_cb)
239
240 - def _new_tube_cb(self, id, initiator, type, service, params, state):
241 self.logger.debug('New tube: ID=%d initator=%d type=%d service=%s ' 242 'params=%r state=%d', id, initiator, type, service, 243 params, state) 244 if (type == telepathy.TUBE_TYPE_DBUS and 245 service == self.dbus_name): 246 if state == telepathy.TUBE_STATE_LOCAL_PENDING: 247 self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES].AcceptDBusTube(id) 248 tube_conn = TubeConnection(self.conn, 249 self.tubes_chan[telepathy.CHANNEL_TYPE_TUBES], 250 id, group_iface=self.text_chan[telepathy.CHANNEL_INTERFACE_GROUP]) 251 self.tubebox.insert_tube(tube_conn, self.initiating) 252 self._sharing_completed = True 253 if self._readfile_completed and not self.initialized: 254 self._initialize_display()
255
256 - def read_file(self, file_path):
257 self.cloud.loads(self.load_from_journal(file_path)) 258 self._readfile_completed = True 259 if self._sharing_completed and not self.initialized: 260 self._initialize_display() 261 pass
262
263 - def load_from_journal(self, file_path):
264 """ 265 Inheritors wishing to control file saving should override this method. 266 Any inheritor overriding this method must return the 267 string provided to save_to_journal as cloudstring. 268 The default implementation of load_from_journal simply returns the contents 269 of the file, matching the default implementation of save_to_journal. 270 @type file_path: str 271 @param file_path: path to the file to read 272 @rtype: str 273 @return: a string previously passed to save_to_journal as cloudstring""" 274 if file_path: 275 f = file(file_path,'rb') 276 s = f.read() 277 f.close() 278 return s
279
280 - def write_file(self, file_path):
281 # There is a possibility that the user could trigger a write_file 282 # action before read_file has occurred. This could be dangerous, 283 # potentially overwriting the journal entry with blank state. To avoid 284 # this, we ensure that read_file has been called (if there is a file to 285 # read) before writing. 286 if not self._readfile_completed: 287 self.read_file(self._jobject.file_path) 288 self.save_to_journal(file_path, self.cloud.dumps())
289
290 - def save_to_journal(self, file_path, cloudstring):
291 """Any inheritor who wishes to control file 292 output should override this method, and must 293 be sure to include cloudstring in its write_file. 294 The default implementation of save_to_journal simply dumps the output of 295 self.cloud.dumps() to disk. 296 @type file_path: str 297 @param file_path: the path to which the activity should write 298 @type cloudstring: str 299 @param cloudstring: an additional string representing the state of all 300 objects associated with self.cloud. This string may be saved 301 as the inheritor sees fit.""" 302 f = file(file_path, 'wb') 303 f.write(cloudstring) 304 f.close()
305
306 - def _active_cb(self, widget, event):
307 self.logger.debug("_active_cb") 308 if self.props.active: 309 self.resume() 310 else: 311 self.pause()
312
313 - def _visible_cb(self, widget, event):
314 self.logger.debug("_visible_cb") 315 if event.state == gtk.gdk.VISIBILITY_FULLY_OBSCURED: 316 self.pause() 317 else: 318 self.resume()
319
320 - def pause(self):
321 """ 322 This method will be called when the display is not visible. 323 Subclasses should override this function to stop updating the display 324 when it is not visible.""" 325 pass
326
327 - def resume(self):
328 """ 329 This method will be called when the display becomes visible. 330 Subclasses should override this function to resume updating the 331 display, since it is now visible""" 332 pass
333