Release 8.
[jlew:xo-file-distro.git] / FileShare.activity / GuiView.py
1 # Copyright (C) 2009, Justin Lewis  (jtl1728@rit.edu)
2 #
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 # GNU General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
16
17 import gtk
18 import FileInfo
19 import threading
20 from gettext import gettext as _
21
22
23 from sugar.activity.activity import ActivityToolbox
24 from sugar.graphics.toolbutton import ToolButton
25 from sugar.graphics.objectchooser import ObjectChooser
26 from sugar.graphics.alert import NotifyAlert, Alert
27
28 from MyExceptions import InShareException, FileUploadFailure, ServerRequestFailure, NoFreeTubes
29 import logging
30 _logger = logging.getLogger('fileshare-activity')
31
32 class GuiHandler():
33     def __init__(self, activity, tree, handle):
34         self.activity = activity
35         self.treeview = tree
36         self.tb_alert = None
37         self.guiView = handle
38
39     def requestAddFile(self, widget, data=None):
40         _logger.info('Requesting to add file')
41
42         chooser = ObjectChooser()
43         if chooser.run() == gtk.RESPONSE_ACCEPT:
44             # get object and build file
45             jobject = chooser.get_selected_object()
46
47             self.show_throbber(True, _("Packaging Object") )
48             try:
49                 file_obj = self.activity.build_file( jobject )
50             except InShareException:
51                 self._alert(_("Object Not Added"), _("Object already shared"))
52                 self.show_throbber( False )
53                 return
54
55             # No problems continue
56             self.show_throbber( False )
57
58             # Add To UI
59             self._addFileToUIList( file_obj.id, file_obj )
60
61             # Register File with activity share list
62             self.activity._registerShareFile( file_obj.id, file_obj )
63
64             # Upload to server?
65             if data and data.has_key('upload'):
66                 self.show_throbber(True, _("Uploading Object to server"))
67                 def send():
68                     try:
69                         self.activity.send_file_to_server( file_obj.id, file_obj )
70                     except FileUploadFailure:
71                         self._alert( _("Failed to upload object") )
72                         self._remFileFromUIList( file_obj.id )
73                         self.activity.delete_file( file_obj.id )
74                     self.show_throbber( False )
75                 threading.Thread(target=send).start()
76
77         chooser.destroy()
78         del chooser
79
80     def requestInsFile(self, widget, data=None):
81         _logger.info('Requesting to install file back to journal')
82
83         model, iterlist = self.treeview.get_selection().get_selected_rows()
84         for path in iterlist:
85             iter = model.get_iter(path)
86             key = model.get_value(iter, 0)
87
88             # Attempt to remove file from system
89             bundle_path = os.path.join(self._filepath, '%s.xoj' % key)
90
91             self.activity._installBundle( bundle_path )
92             self._alert(_("Installed bundle to Jorunal"))
93
94     def requestRemFile(self, widget, data=None):
95         """Removes file from memory then calls rem file from ui"""
96         _logger.info('Requesting to delete file')
97
98         model, iterlist = self.treeview.get_selection().get_selected_rows()
99         for path in iterlist:
100             iter = model.get_iter(path)
101             key = model.get_value(iter, 0)
102
103             # DO NOT DELETE IF TRANSFER IN PROGRESS/COMPLETE
104             if model.get_value(iter, 1).aquired == 0 or self.activity.server_ui_del_overide():
105
106                 # Remove file from UI
107                 self._remFileFromUIList(key)
108
109                 # UnRegister File with activity share list
110                 self.activity._unregisterShareFile( key )
111
112                 # Attempt to remove file from system
113                 self.activity.delete_file( key )
114
115                 # If added by rem from server button, data will have remove key
116                 if data and data.has_key('remove'):
117                     def call():
118                         try:
119                             self.activity.remove_file_from_server( key )
120                         except ServerRequestFailure:
121                             self._alert( _("Failed to send remove request to server") )
122                         self.show_throbber( False )
123                     self.show_throbber(True, _("Sending request to server"))
124                     threading.Thread(target=call).start()
125
126     def requestDownloadFile(self, widget, data=None):
127         _logger.info('Requesting to Download file')
128         if self.treeview.get_selection().count_selected_rows() != 0:
129             model, iterlist = self.treeview.get_selection().get_selected_rows()
130             for path in iterlist:
131                 iter = model.get_iter(path)
132                 fi = model.get_value(iter, 1)
133                 def do_down():
134                     if fi.aquired == 0:
135                         if self.activity._mode == 'SERVER':
136                             self.activity._server_download_document( str( model.get_value(iter, 0)) )
137                         else:
138                             try:
139                                 self.activity._get_document(str( model.get_value(iter, 0)))
140                             except NoFreeTubes:
141                                 self._alert(_("All tubes are busy, file download cannot start"),_("Please wait and try again"))
142                     else:
143                         self._alert(_("Object has already or is currently being downloaded"))
144                 threading.Thread(target=do_down).start()
145         else:
146             self._alert(_("You must select an object to download"))
147
148
149     def _addFileToUIList(self, fileid, fileinfo):
150         modle = self.treeview.get_model()
151         modle.append( None, [fileid, fileinfo])
152
153     def _remFileFromUIList(self, id):
154         model = self.treeview.get_model()
155         iter = model.get_iter_first()
156         while iter:
157             if model.get_value( iter, 0 ) == id:
158                 break
159             iter = model.iter_next( iter )
160         model.remove( iter )
161
162
163
164     def show_throbber(self, show, mesg="", addon=None):
165         if show:
166             #Build Throbber
167             throbber = gtk.VBox()
168             img = gtk.Image()
169             img.set_from_file('throbber.gif')
170             throbber.pack_start(img)
171             throbber.pack_start(gtk.Label(mesg))
172
173             if addon:
174                 throbber.pack_start( addon )
175
176             self.activity.set_canvas(throbber)
177             self.activity.show_all()
178
179             self.activity.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH))
180             self.activity.set_sensitive(False)
181
182         else:
183             self.activity.set_canvas(self.activity.disp)
184             self.activity.show_all()
185
186             self.activity.window.set_cursor(None)
187             self.activity.set_sensitive(True)
188
189         while gtk.events_pending():
190             gtk.main_iteration()
191
192     def _alert(self, title, text=None, timeout=5):
193         alert = NotifyAlert(timeout=timeout)
194         alert.props.title = title
195         alert.props.msg = text
196         self.activity.add_alert(alert)
197         alert.connect('response', self._alert_cancel_cb)
198         alert.show()
199
200     def _alert_cancel_cb(self, alert, response_id):
201         self.activity.remove_alert(alert)
202
203     def switch_to_server(self, widget, data=None):
204         self.activity.switch_to_server()
205
206     def showAdmin(self, widget, data=None):
207         def call():
208             try:
209                 userList = self.activity.get_server_user_list()
210             except ServerRequestFailure:
211                 self._alert(_("Failed to get user list from server"))
212                 self.show_throbber( False )
213             else:
214                 self.show_throbber( False )
215                 level = [_("Download Only"), _("Upload/Remove"), _("Admin")]
216
217                 myTable = gtk.Table(10, 1, False)
218                 hbbox = gtk.HButtonBox()
219                 returnBut = gtk.Button(_("Return to Main Screen"))
220                 returnBut.connect("clicked",self.restore_view, None)
221                 hbbox.add(returnBut)
222
223                 listbox = gtk.VBox()
224
225                 for key in userList:
226                     holder = gtk.HBox()
227                     label = gtk.Label(userList[key][0])
228                     label.set_alignment(0, 0)
229                     holder.pack_start(label)
230
231                     if key == self.activity._user_key_hash:
232                         mode_box = gtk.Label(level[userList[key][1]])
233                         mode_box.set_alignment(1,0)
234                     else:
235                         mode_box = gtk.combo_box_new_text()
236                         for option in level:
237                             mode_box.append_text( option )
238
239                         mode_box.set_active(userList[key][1])
240                         mode_box.connect("changed", self.user_changed, key)
241
242                     holder.pack_start(mode_box, False, False, 0)
243                     listbox.pack_start(holder, False, False, 0)
244
245                 window = gtk.ScrolledWindow()
246                 window.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC )
247                 window.add_with_viewport(listbox)
248
249                 myTable.attach(hbbox,0,1,0,1)
250                 myTable.attach(window,0,1,1,10)
251
252                 self.lockout_action_menu(True)
253                 self.activity.set_canvas(myTable)
254                 self.activity.show_all()
255
256         self.show_throbber(True, _("Requesting user list from server"))
257         threading.Thread(target=call).start()
258
259
260     def user_changed(self, widget, id):
261         widget.set_sensitive(False)
262         def change():
263             try:
264                 self.activity.change_server_user(id, widget.get_active())
265                 widget.set_sensitive(True)
266             except ServerRequestFailure:
267                 parent = widget.get_parent()
268                 parent.remove(widget)
269                 lbl = gtk.Label(_("User Change Failed"))
270                 lbl.set_alignment(1,0)
271                 lbl.show()
272                 parent.add( lbl )
273
274         threading.Thread(target=change).start()
275
276     def restore_view(self, widget, data = None):
277         self.lockout_action_menu(False)
278         self.activity.set_canvas(self.activity.disp)
279         #self.show_throbber( False )
280
281     def lockout_action_menu(self, set_lock = True):
282         self.guiView.action_bar.set_sensitive(not set_lock)
283
284 class GuiView(gtk.ScrolledWindow):
285     """
286     This class is used to just remove the table setup from the main file
287     """
288     def __init__(self, activity):
289         gtk.ScrolledWindow.__init__(self)
290         self.set_policy( gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC )
291         self.activity = activity
292         self.treeview = gtk.TreeView(gtk.TreeStore(str,object))
293         self.guiHandler = GuiHandler( activity, self.treeview, self )
294         #self.build_table(activity)
295
296     def build_toolbars(self):
297         self.action_buttons = {}
298
299         # BUILD CUSTOM TOOLBAR
300         self.action_bar = gtk.Toolbar()
301         self.action_buttons['add'] = ToolButton('fs_gtk-add')
302         self.action_buttons['add'].set_tooltip(_("Add Object"))
303
304         self.action_buttons['rem'] = ToolButton('fs_gtk-remove')
305         self.action_buttons['rem'].set_tooltip(_("Remove Object(s)"))
306
307         self.action_buttons['save'] = ToolButton('filesave')
308         self.action_buttons['save'].set_tooltip( _("Copy Object(s) to Journal") )
309
310
311         self.action_buttons['down'] = ToolButton('epiphany-download')
312         self.action_buttons['down'].set_tooltip( _('Download Object(s)') )
313
314         self.action_buttons['admin'] = ToolButton('gtk-network')
315         self.action_buttons['admin'].set_tooltip( _('Server Permissions') )
316
317         self.action_buttons['server'] = ToolButton('gaim-link')
318         self.action_buttons['server'].set_tooltip( _('Connect to Server') )
319         self.action_buttons['server'].set_sensitive( False )
320
321         if self.activity.isServer:
322             self.action_buttons['add'].connect("clicked", self.guiHandler.requestAddFile, None)
323             self.action_buttons['save'].connect("clicked", self.guiHandler.requestInsFile, None)
324             self.action_buttons['rem'].connect("clicked", self.guiHandler.requestRemFile, None)
325             self.action_buttons['server'].connect("clicked", self.guiHandler.switch_to_server, None)
326
327             self.action_bar.insert(self.action_buttons['add'], -1)
328             self.action_bar.insert(self.action_buttons['save'], -1)
329             self.action_bar.insert(self.action_buttons['rem'], -1)
330             self.action_bar.insert(self.action_buttons['server'], -1)
331
332             # Check for server, if found activate connect link
333             def check_server_status():
334                 try:
335                     if self.activity.check_for_server():
336                         self.action_buttons['server'].set_sensitive( True )
337                 except ServerRequestFailure:
338                     pass
339             threading.Thread(target=check_server_status).start()
340
341         else:
342             self.action_buttons['down'].connect("clicked", self.guiHandler.requestDownloadFile, None)
343             self.action_bar.insert(self.action_buttons['down'], -1)
344
345             if self.activity._mode == 'SERVER' and self.activity._user_permissions != 0:
346                 self.action_buttons['add'].connect("clicked", self.guiHandler.requestAddFile, {'upload':True})
347                 self.action_buttons['rem'].connect("clicked", self.guiHandler.requestRemFile, {'remove':True})
348
349                 self.action_bar.insert(self.action_buttons['add'], -1)
350                 self.action_bar.insert(self.action_buttons['rem'], -1)
351
352                 if self.activity._user_permissions == 2:
353                     self.action_buttons['admin'].connect("clicked", self.guiHandler.showAdmin, None)
354                     self.action_bar.insert(self.action_buttons['admin'], -1)
355
356         self.action_bar.show_all()
357
358         self.toolbar_set_selection( False )
359
360         # Create Toolbox
361         self.toolbox = ActivityToolbox(self.activity)
362
363         self.toolbox.add_toolbar(_("Actions"), self.action_bar)
364
365         self.activity.set_toolbox(self.toolbox)
366         self.toolbox.show()
367
368     def on_selection_changed(self, selection):
369         if selection.count_selected_rows() == 0:
370             self.toolbar_set_selection(False)
371         else:
372             self.toolbar_set_selection(True)
373
374     def toolbar_set_selection(self, selected):
375         require_selection = ['save', 'rem', 'down']
376         for key in require_selection:
377             if selected:
378                 self.action_buttons[key].set_sensitive( True )
379             else:
380                 self.action_buttons[key].set_sensitive( False )
381
382     def build_table(self):
383         # Create File Tree
384         ##################
385
386         #       Name            Cell_data_Func      Expand  Cell Renderer
387         text_cells = [
388             [ _('Name'),   FileInfo.file_name, False,  gtk.CellRendererText()],
389             [ _('Description'), FileInfo.file_desc, True,   gtk.CellRendererText()],
390             [ _('Tags'),        FileInfo.file_tags, False,  gtk.CellRendererText()],
391             [ _('Size'),   FileInfo.file_size, False,  gtk.CellRendererText()],
392             [ '',               FileInfo.load_bar,  False,  gtk.CellRendererProgress()]
393         ]
394
395         for col_data in text_cells:
396             cell = col_data[3]
397             colName = gtk.TreeViewColumn(col_data[0], cell)
398             colName.set_cell_data_func(cell, col_data[1])
399
400             # Should the col expand
401             colName.set_expand(col_data[2])
402
403             # Add to tree
404             self.treeview.append_column(colName)
405
406         # make it searchable by name
407         self.treeview.set_search_column(1)
408
409         # Allow Multiple Selections
410         self.treeview.get_selection().set_mode( gtk.SELECTION_MULTIPLE )
411         self.treeview.get_selection().connect('changed', self.on_selection_changed )
412
413         # Put table into scroll window to allow it to scroll
414         self.add_with_viewport(self.treeview)
415
416     def clear_files(self, deleteFile = True):
417         model = self.treeview.get_model()
418         iter = model.get_iter_root()
419         while iter:
420             key = model.get_value(iter, 0)
421
422             # Remove file from UI
423             self.guiHandler._remFileFromUIList(key)
424
425             # UnRegister File with activity share list
426             self.activity._unregisterShareFile( key )
427
428             # Attempt to remove file from system
429             if deleteFile:
430                 self.activity.delete_file( key )
431
432             iter = model.iter_next(iter)
433
434     def update_progress(self, id, bytes ):
435         model = self.treeview.get_model()
436         iter = model.get_iter_first()
437         while iter:
438             if model.get_value( iter, 0 ) == id:
439                 break
440             iter = model.iter_next( iter )
441
442         if iter:
443             obj = model.get_value( iter, 1 )
444             obj.update_aquired( bytes )
445
446             # Store updated versoin of the object
447             self.activity.updateFileObj( id, obj )
448             model.set_value( iter, 1, obj)
449
450             model.row_changed(model.get_path(iter), iter)
451
452     def set_installed( self, id, sucessful=True ):
453         model = self.treeview.get_model()
454         iter = model.get_iter_first()
455         while iter:
456             if model.get_value( iter, 0 ) == id:
457                 break
458             iter = model.iter_next( iter )
459
460         if iter:
461             obj = model.get_value( iter, 1 )
462             if sucessful:
463                 obj.set_installed()
464             else:
465                 obj.set_failed()
466
467             # Store updated versoin of the object
468             self.activity.updateFileObj( id, obj )
469             model.set_value( iter, 1, obj)
470             model.row_changed(model.get_path(iter), iter)