root/trunk/umit/plugin/PluginPage.py @ 4240

Revision 4240, 16.9 kB (checked in by gpolo, 4 years ago)

Restructuring umit, second step. See ticket #229.

Line 
1# -*- coding: utf-8 -*-
2# Copyright (C) 2008 Adriano Monteiro Marques
3#
4# Author: Francesco Piccinno <stack.box@gmail.com>
5#
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20import os
21import gtk
22import gobject
23
24from umit.core.I18N import _
25from umit.core.Paths import Path
26from umit.core.UmitLogging import log
27
28from higwidgets.higbuttons import HIGButton
29from higwidgets.higdialogs import HIGAlertDialog
30from higwidgets.higrichlists import HIGRichList, PluginRow
31
32from umit.plugin.Core import Core
33from umit.plugin.Update import FILE_GETTING, FILE_CHECKING
34from umit.plugin.Update import LATEST_GETTED, LATEST_ERROR, LATEST_GETTING
35
36class PluginPage(gtk.VBox):
37    def __init__(self, parent):
38        gtk.VBox.__init__(self, False, 2)
39
40        self.p_window = parent
41        self.menu_enabled = True
42
43        self.__create_widgets()
44        self.__pack_widgets()
45       
46        self.install_updates_btn.hide()
47
48    def __create_widgets(self):
49        self.set_spacing(4)
50
51        self.richlist = HIGRichList()
52
53        self.hbbox = gtk.HButtonBox()
54        self.hbbox.set_layout(gtk.BUTTONBOX_END)
55
56        self.find_updates_btn = \
57            HIGButton(_('Find updates'), gtk.STOCK_REFRESH)
58        self.install_updates_btn = \
59            HIGButton(_('Install updates'), gtk.STOCK_APPLY)
60        self.skip_install_btn = \
61            HIGButton(_('Skip'), gtk.STOCK_CANCEL)
62        self.restart_btn = \
63            HIGButton(_('Restart UMIT'), gtk.STOCK_REFRESH)
64   
65    def __pack_widgets(self):
66        self.hbbox.pack_start(self.find_updates_btn)
67        self.hbbox.pack_start(self.skip_install_btn)
68        self.hbbox.pack_start(self.install_updates_btn)
69        self.hbbox.pack_start(self.restart_btn)
70       
71        self.pack_start(self.richlist)
72        self.pack_start(self.hbbox, False, False, 0)
73       
74        self.find_updates_btn.connect('clicked', self.__on_find_updates)
75        self.install_updates_btn.connect('clicked', self.__on_install_updates)
76        self.skip_install_btn.connect('clicked', self.__on_skip_updates)
77        self.restart_btn.connect('clicked', self.__on_restart)
78
79        self.show_all()
80   
81    def clear(self, include_loaded=True):
82        if include_loaded:
83            self.richlist.clear()
84            return
85
86        def remove(row, richlist):
87            if not row.reader.enabled:
88                richlist.remove_row(row)
89
90        self.richlist.foreach(remove, self.richlist)
91        return self.richlist.get_rows()
92
93    def populate(self):
94        "Populate the richlist using avaiable_plugins field"
95
96        # We need a list of present plugin row to check for dup
97        presents = []
98
99        def add_to_list(row, list):
100            list.append(row)
101
102        self.richlist.foreach(add_to_list, presents)
103
104        warn_reboot = False
105
106        # We have to load avaiable_plugins from engine
107        for reader in self.p_window.engine.avaiable_plugins:
108
109            # Check if it's already present then remove the original
110            # and add the new in case something is getting update.
111            row = PluginRow(self.richlist, reader)
112
113            for old in presents:
114                # FIXME? we need to check also for version equality
115                # and if are different just ignore the addition and
116                # continue with the loop
117                if old.reader.get_path() == row.reader.get_path():
118                    self.richlist.remove_row(old)
119                    row.enabled = True
120                    warn_reboot = True
121
122            # Connect the various buttons
123            row.action_btn.connect('clicked', self.__on_row_action, row)
124            row.uninstall_btn.connect('clicked', self.__on_row_uninstall, row)
125            row.preference_btn.connect('clicked', self.__on_row_preference, row)
126
127            row.connect('clicked', self.__on_row_preference, row)
128            row.connect('popup', self.__on_row_popup)
129
130            self.richlist.append_row(row)
131
132        if warn_reboot:
133            # Warn the user
134            self.p_window.animated_bar.label = \
135                _('Remember that you have to restart UMIT to make new version' \
136                  ' of plugins to be loaded correctly.')
137            self.p_window.animated_bar.start_animation(True)
138
139    def __on_restart(self, widget):
140        "Called when the user click on the restart button"
141
142        # TODO: implement me
143        log.critical("Restart is not implemented!")
144
145    def __on_skip_updates(self, widget):
146        "Called when the user click on the skip button"
147
148        # We need to repopulate the tree
149        self.richlist.clear()
150        self.populate()
151
152        self.p_window.toolbar.unset_status()
153        self.p_window.animated_bar.label = \
154                _('Update skipped')
155        self.p_window.animated_bar.start_animation(True)
156
157        self.skip_install_btn.hide()
158        self.install_updates_btn.hide()
159        self.find_updates_btn.show()
160
161        self.menu_enabled = True
162   
163    def __on_install_updates(self, widget):
164        """
165        Called when the user click on 'install updates' button
166
167        This function call the start_download() of UpdateEngine
168        and then add a timeout callback (__refresh_row_download) to
169        update the gui at interval of 300 milliseconds.
170        """
171
172        lst = []
173        for obj in self.p_window.update_eng.list:
174            if obj.status != LATEST_GETTED:
175                self.richlist.remove_row(obj.object)
176           
177            if obj.object.show_include:
178                lst.append(obj)
179           
180            obj.object.show_include = False
181       
182        self.install_updates_btn.set_sensitive(False)
183        self.skip_install_btn.set_sensitive(False)
184
185        self.p_window.update_eng.list = lst
186        self.p_window.update_eng.start_download()
187
188        self.p_window.toolbar.show_message( \
189            _("<b>Downloading updates ...</b>"), \
190            file=os.path.join(Path.pixmaps_dir, "Throbber.gif") \
191        )
192        gobject.timeout_add(300, self.__refresh_row_download)
193   
194    def __refresh_row_download(self):
195        """
196        This is the timeout callback called to update the gui
197        in the download phase (the last)
198        """
199
200        working = False
201       
202        for obj in self.p_window.update_eng.list:
203            obj.lock.acquire()
204
205            try:
206                if obj.status == FILE_GETTING or \
207                   obj.status == FILE_CHECKING:
208                    working = True
209                   
210                row = obj.object
211                row.message = obj.label
212                row.progress = obj.fract
213            finally:
214                obj.lock.release()
215       
216        if not working:
217           
218            for obj in self.p_window.update_eng.list:
219                row = obj.object
220                row.message = obj.label
221                row.progress = None
222           
223            # Only warn the user about changes take effects on restart
224            # on restart just move the plugins stored in home directory
225            # in the proper location
226           
227            self.p_window.animated_bar.label = \
228                _('Update phase complete. Now restart ' \
229                  'UMIT to changes make effects.')
230            self.p_window.animated_bar.start_animation(True)
231           
232            self.p_window.toolbar.unset_status()
233           
234            self.install_updates_btn.hide()
235            self.skip_install_btn.hide()
236
237            self.restart_btn.show()
238       
239        return working
240   
241    def __on_find_updates(self, widget):
242        """
243        Called when the user click on 'find updates' button
244
245        This function call the start_update() of UpdateEngine
246        and then add a timeout callback (__update_rich_list) to
247        update the gui at interval of 300 milliseconds.
248        """
249
250        self.find_updates_btn.set_sensitive(False)
251        self.menu_enabled = False
252       
253        lst = []
254
255        def append(row, lst):
256            if row.reader.update:
257                row.message = _("Waiting ...")
258                row.activatable = False
259                row.enabled = True
260                lst.append(row)
261            else:
262                self.richlist.remove_row(row)
263
264        self.richlist.foreach(append, lst)
265
266        if not lst:
267            self.p_window.toolbar.unset_status()
268
269            self.p_window.animated_bar.label = \
270                _("No plugins provides update url. Cannot procede.")
271            self.p_window.animated_bar.image = gtk.STOCK_DIALOG_ERROR
272            self.p_window.animated_bar.start_animation(True)
273
274            self.p_window.update_eng.updating = False
275            self.find_updates_btn.set_sensitive(True)
276            self.menu_enabled = True
277
278            self.populate()
279            return
280
281        # We can now begin the update phase, so warn the user.
282
283        self.p_window.toolbar.show_message( \
284            _("<b>Looking for %d updates ...</b>") % len(lst), \
285            file=os.path.join(Path.pixmaps_dir, "Throbber.gif") \
286        )
287
288        self.p_window.update_eng.list = lst
289        self.p_window.update_eng.start_update()
290
291        # Add a timeout function to update label
292        gobject.timeout_add(300, self.__update_rich_list)
293
294    def __update_rich_list(self):
295        """
296        This is the timeout callback called to update the gui
297        in the find phase
298        """
299
300        working = False
301       
302        for upd_obj in self.p_window.update_eng.list:
303            upd_obj.lock.acquire()
304
305            try:
306                # Check if we are working
307                if upd_obj.status == LATEST_GETTING:
308                    working = True
309               
310                # Update the row
311                row = upd_obj.object
312                row.message = upd_obj.label
313            finally:
314                upd_obj.lock.release()
315       
316        # No locking from here we have finished
317
318        if not working:
319            # Mark as finished
320            self.p_window.update_eng.stop()
321            self.find_updates_btn.set_sensitive(True)
322           
323            lst = filter( \
324                lambda x: (x.status == LATEST_GETTED) and (x) or (None),\
325                self.p_window.update_eng.list \
326            )
327            elst = filter( \
328                lambda x: (x.status == LATEST_ERROR) and (x) or (None),\
329                self.p_window.update_eng.list \
330            )
331           
332            if not lst and not elst:
333                self.p_window.toolbar.unset_status()
334
335                self.p_window.animated_bar.label = \
336                    _('<b>No updates found</b>')
337                self.p_window.animated_bar.start_animation(True)
338               
339                self.richlist.clear()
340                self.populate()
341
342                self.menu_enabled = True
343            else:
344                # Now prepare the download page
345
346                if not elst:
347                    self.p_window.toolbar.show_message( \
348                        _("<b>Updates found: %d</b>") % len(lst), \
349                        stock=gtk.STOCK_APPLY \
350                    )
351                else:
352                    self.p_window.toolbar.show_message( \
353                        _('<b>Updates found: %d with %d errors</b>')  \
354                        % (len(lst), len(elst)), stock=gtk.STOCK_APPLY \
355                    )
356
357                for obj in self.p_window.update_eng.list:
358                    row = obj.object
359                    active = (obj.status == LATEST_GETTED)
360                   
361                    if active:
362                        row.show_include = True
363                        row.message = obj.label
364                    else:
365                        row.saturate = True
366               
367                if lst:
368                    self.install_updates_btn.show()
369                   
370                self.find_updates_btn.hide()
371                self.skip_install_btn.show()
372
373        return working
374   
375    def __on_row_popup(self, row, evt):
376        "Popup menu"
377
378        if not self.menu_enabled or not row.activatable:
379            return
380
381        menu = gtk.Menu()
382
383        stocks = (
384            gtk.STOCK_PREFERENCES,
385            gtk.STOCK_HOME,
386            gtk.STOCK_ABOUT,
387            None,
388            (gtk.STOCK_MEDIA_PLAY, gtk.STOCK_MEDIA_STOP),
389            gtk.STOCK_CLEAR
390        )
391
392        labels = (
393            _('<b>Preferences</b>'),
394            _('Visit homepage'),
395            _('About %s') % row.reader.name,
396            None,
397            (_('Enable'), _('Disable')),
398            _('Uninstall')
399        )
400
401        callbacks = (
402            self.__on_row_preference,
403            self.__on_row_homepage,
404            self.__on_row_about,
405            None,
406            self.__on_row_action,
407            self.__on_row_uninstall
408        )
409
410        for stock, label, cb in zip(stocks, labels, callbacks):
411            if not label:
412                menu.append(gtk.SeparatorMenuItem())
413                continue
414
415            # Get the right stock, label choosing from the row state
416            if isinstance(label, tuple):
417                if row.enabled:
418                    stock, label = stock[1], label[1]
419                else:
420                    stock, label = stock[0], label[0]
421
422
423            act = gtk.Action(None, label, '', stock)
424
425            item = act.create_menu_item()
426            item.get_child().set_use_markup(True)
427            item.connect('activate', cb, row)
428
429            menu.append(item)
430
431        menu.show_all()
432        menu.popup(None, None, None, evt.button, evt.time)
433
434    def __on_row_action(self, widget, row):
435        "Enable/Disable menu/button callback"
436
437        if not row.enabled:
438            func = self.p_window.engine.load_plugin
439        else:
440            func = self.p_window.engine.unload_plugin
441
442        ret, errmsg = func(row.reader)
443
444        if not ret:
445            dialog = HIGAlertDialog(
446                self.p_window,
447                gtk.DIALOG_MODAL,
448                gtk.MESSAGE_ERROR,
449                message_format=errmsg,
450                secondary_text=errmsg.summary
451            )
452            dialog.run()
453            dialog.destroy()
454        else:
455            row.enabled = not row.enabled
456
457    def __on_row_uninstall(self, widget, row):
458        "Uninstall button callback"
459
460        # If it's enabled we must disable and then disinstall
461
462        def dialog(txt, sec):
463            return HIGAlertDialog(self.p_window, 0, gtk.MESSAGE_QUESTION, \
464                gtk.BUTTONS_YES_NO, txt, sec)
465
466        if row.enabled:
467            d = dialog( \
468                _('Disabling Plugin'), \
469                _('Do you want to disable %s plugin?') % row.reader.name \
470            )
471
472            r = d.run()
473            d.hide()
474            d.destroy()
475
476            if r == gtk.RESPONSE_YES:
477                r, err = self.p_window.engine.unload_plugin(row.reader)
478               
479                if not r:
480                    d = dialog( \
481                        _('Can not disable Plugin'), \
482                        _('%s\nDo you want to force '
483                          'the unload phase for %s plugin?') % \
484                            (err.summary, row.reader.name) \
485                    )
486
487                    r = d.run()
488                    d.hide()
489                    d.destroy()
490
491                    if r == gtk.RESPONSE_YES:
492                        self.p_window.engine.unload_plugin(row.reader, True)
493                        self.__uninstall(row)
494                else:
495                    self.__uninstall(row)
496        else:
497            self.__uninstall(row)
498
499    def __uninstall(self, row):
500        "Uninstall semi-low level function"
501
502        row.activatable = False
503       
504        if self.p_window.engine.uninstall_plugin(row.reader):
505            del row
506            self.richlist.clear()
507            self.p_window.plug_page.populate()
508        else:
509            row.activatable = True
510           
511            self.p_window.animated_bar.label = \
512                _('Unable to uninstall %s plugin.') % row.reader
513            self.p_window.animated_bar.start_animation(True)
514
515    def __on_row_preference(self, widget, row):
516        "Preference button callback"
517       
518        if not self.p_window.engine.tree.show_preferences(row.reader):
519            self.p_window.animated_bar.label = \
520                _('No preferences for %s') % row.reader.name
521            self.p_window.animated_bar.start_animation(True)
522
523    def __on_row_about(self, widget, row):
524        "About menu callback"
525        self.p_window.engine.tree.show_about(row.reader)
526
527    def __on_row_homepage(self, widget, row):
528        "Homepage menu callback"
529        Core().open_url(row.reader.url)
Note: See TracBrowser for help on using the browser.