Hello,
I send you a new patch (for revison 503):

* I moved the class GPodderStatusIcon in it´s own file (it's easier to maintain)
* it uses python-notify instead of dbus
* during download, the status icon shows a small arrow
* during download tooltip and notification also list successfully dowloaded files (idea from Pieter De Decker)
* notification and icon for feed update

Thank you for you comments. See mine bellow:

Pieter De Decker a écrit :
I have two of things to say about the notifications:
- Try to make them a little more descriptive. I'm sure that you've already thought of that and that you're working on it, but there's more. What if a certain episode is called "My super duper special podcast episode with a special guest host for December 21st 2007"? You could solve this by limiting the episode name to the first 25 (or so) characters, followed by "(...)". Example: "'My super duper special po(...)' has been downloaded successfully".
You'll probably like this version. I don't  notify every file download because it's very annoying having to much popup windows, but the tooltip displays during dowload already dowloaded files, and the notify also lists them


nikosapi a écrit :
Attached is a very basic autoupdate addition to your patch. What it does, is 
every 20min it runs update_feed_cache. This behaviour is controlled by a 
configuration option in the Preferences window.
  
It sound good, but I was unfortunately unable to apply the patch . I just integrated manualy the thinks you modifed in GPodderStatusIcon. Can somebody, merge it with my attached patch?
Shouldn't also the 20min delay be configurable in the preference window?

Paul Rudkin a écrit :
I love this, thanks for the time and effort you put into creating this patch.
  
Thanks for trying it, Thanks to all developpers, Thomas first,  for gPodder.

Thomas Perl a écrit :
  * You could use python-notify instead of directly accessing d-bus

For the python-notify (pynotify) usage: I think you can then use the 
"set_timeout()" function on a Notification object to add the "coutdown 
timer" to the notification windows, as Pieter De Decker suggested in his 
mail.
The "countdown timer" actually appears when you add an action (a button within the bubble).
I add actions, unfortunately It doesn't work, so I commented them.  The buttons appears, but my callback function is not called. It certainly due to my poor experience in python but I can't figure out why. Could you (or somebody else) have a look at my code. See the commented lines "send_notification" and "__action_callback" methods of the GPodderStatusIcon class
get_tree_icon() from gpodder.util could be helpful here. This is the 
function that is used to add the bullet or padlock to the icons in the 
episode list. You could extend this function and be able to get the 
gPodder icon with some "downloading" (e.g. arrow down) or checking (e.g. 
the "refresh" icon) in the lower right-hand corner of the icon.
  
I used you code as inspiration but I had my own method for more flexibility (I use a dictionary, it's quite easy to add as status)
  
* error handling: what to do in case of error? 
    - critical errors where the use can't help (bug, missing dependency...).
    - troubleshouting where the user might do something (server down, xml 
invalid, disk full, ...).
    

Basically, the user is already notified about errors. What needs to be 
done is to redirect the show_notification() (and probably also 
show_confirmation()?) calls to the tray icon (and therefore libnotify) 
if the gPodder window has been hidden and only the tray icon is visible.m
  
I´ll check this






Index: src/gpodder/statusIcon.py
===================================================================
--- src/gpodder/statusIcon.py	(révision 0)
+++ src/gpodder/statusIcon.py	(révision 0)
@@ -0,0 +1,271 @@
+# -*- coding: utf-8 -*-
+#
+# gPodder - A media aggregator and podcast client
+# Copyright (C) 2005-2007 Thomas Perl <thp at perli.net>
+#
+# gPodder 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 3 of the License, or
+# (at your option) any later version.
+#
+# gPodder 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, see <http://www.gnu.org/licenses/>.
+#
+
+import pynotify
+import gtk
+import gui
+
+from gpodder import services
+
+from liblogger import log
+
+class GPodderStatusIcon(gtk.StatusIcon):
+    """ this class display a status icon in the system tray
+    this icon serves to show or hide gPodder, notify dowload status 
+    and provide a popupmenu for quick acces to some 
+    gPodder functionalities
+       
+    author: Jérôme Chabod (JCH) <jerome.chabod at ifrance.com>
+    12/20/2007 JCH: first release
+    12/22/2007 JCH: 
+        uses python-notify instead of dbus
+        during download, the status icon shows a small arrow
+        tooltip and notification also list successfully dowloaded files (idea from Pieter De Decker)
+        notification and icon for feed update
+       
+    """
+
+    def  __init__( self, gpodder):
+        gtk.StatusIcon.__init__(self)
+        log("create status icon",  sender = self)
+
+        # reference to gPodder main class
+        self.__gpodder = gpodder
+        
+        self.__is_downloading = False
+        # this list store url successfully downloaded for notification
+        self.__url_successfully_downloaded = []
+        
+        # status
+        self.__status = {"downloading": ["dowloading", gtk.STOCK_GO_DOWN],
+                         "checkingNewEpisodes": ["looking for new episodes", gtk.STOCK_REFRESH] }
+        self.__smallStatusIconsCache = {} # will cache the small icons once generated
+                    
+
+        # try getting the icon
+        try:
+            self.__icon = gtk.gdk.pixbuf_new_from_file(gui.scalable_dir)
+        except Exception, detail:
+            log( "Warning: Cannot load gPodder icon, will use the default icon.\nerror is:%s", detail, sender =self)
+            self.__icon = gtk.icon_theme_get_default().load_icon(gtk.STOCK_DIALOG_QUESTION, 30, 30)
+        self.set_from_pixbuf(self.__icon)
+        
+        self.set_tooltip("gPodder")
+  
+        # build and connect the popup menu
+        menu = gtk.Menu()
+        menuItem = gtk.ImageMenuItem("Check for Updates")
+        menuItem.connect('activate',  self.__gpodder.on_itemUpdate_activate)
+        menu.append(menuItem)        
+        menuItem = gtk.ImageMenuItem("Download all new episodes")
+        menuItem.connect('activate',  self.__gpodder.on_itemDownloadAllNew_activate)
+        menu.append(menuItem)        
+        menu.append( gtk.SeparatorMenuItem())
+        menuItem = gtk.ImageMenuItem(gtk.STOCK_PREFERENCES)
+        menuItem.connect('activate',  self.__gpodder.on_itemPreferences_activate)
+        menu.append(menuItem)
+        menuItem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
+        menuItem.connect('activate',  self.__gpodder.on_itemAbout_activate)
+        menu.append(menuItem)
+        menu.append( gtk.SeparatorMenuItem())
+        menuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
+        menuItem.connect('activate',  self.__gpodder.close_gpodder, self)
+        menu.append(menuItem)
+        self.connect('popup-menu', self.__on_popup_menu, menu)
+
+        self.connect('activate', self.__on_activate)
+        self.set_visible(True)
+        
+        # initialise pynotify
+        if not pynotify.init("gPodder"):
+           log("Error: unable to initialise pynotify",  sender = self) 
+           #TODO: disable notification
+
+        # register the status icon to be notified 
+        # by the download status manager
+        dsm = services.download_status_manager
+        dsm.register( 'progress-changed', self.__download_progress_changed)
+        dsm.register( 'download-completed', self.__download_completed)
+        log("status icon created",  sender = self)
+
+    def __on_popup_menu(self, widget, button, time, data = None):
+        """ Triggered when the popup menu is called, 
+        generaly by right clicking on the icon.
+        Display the popup menu created in cronstructor.
+        
+        """
+        log("popup systray menu",  sender = self)
+        data.show_all()
+        data.popup(None, None, None, 3, time)
+
+    def __on_activate(self, widget, data=None):
+        """ Triggered when the icon is activated, 
+        generaly by left clicking on the icon.
+        Iconify or deiconify the gpodder gui. 
+        When iconified, the application is hidden from taskbar
+           
+        """
+        if  self.is_iconified():
+            self.uniconify_main_window()
+        else:
+            self.iconify_main_window()  
+            
+
+    def __download_completed(self, url):
+        """ callback by dowload manager during dowloading. 
+            track files succesfully dowload for notification
+        """
+        log("added url %s to successfully dowloaded", url, sender = self)
+        self.__url_successfully_downloaded.append(url)
+        
+    def __format_list_downloaded(self):
+        """ format the list of already dowloaded files for displaying
+            in tooltip or notification.
+            return the formated string
+        """
+        result = ""
+        count = 0
+        for url in self.__url_successfully_downloaded:
+             count=+1
+             #limit the list to 20 items
+             if count > 20: 
+                result += "\n(...)"
+                break
+             title = self.__gpodder.get_episode_by_url(url).title
+             # cut title if too long
+             if len(title) > 100: title=title[0:47] + "(...)" + title[-47:0]
+             result += "\n%s" % title
+        return result
+        
+    def __download_progress_changed( self, count, percentage):
+        """ callback by download manager during dowloading.
+        It updates the tooltip with information on how many 
+        files are dowloaded and the percentage of dowload
+        
+        """
+        toolType  = ""
+        if count == 0:
+            # dowload finished
+            toolType = "gPodder"
+            self.__is_downloading = False
+            self.change_status(None)
+            msg = "%i episodes downloaded:" % len(self.__url_successfully_downloaded)
+            msg += self.__format_list_downloaded()
+            self.send_notification(msg, "gPodder - download finished")	
+            self.__url_successfully_downloaded = []
+        else:
+            if not self.__is_downloading:
+                # change the icon to status downloading
+                self.__is_downloading = True
+                self.change_status("downloading")
+            if count == 1:
+                toolType = "downloading one file"
+            else:
+                toolType = "downloading %d files" % (count)
+            toolType += " - %d%% accomplished" % (percentage)
+            if len(self.__url_successfully_downloaded) > 0:
+                toolType += "\nalready downloaded:" + self.__format_list_downloaded()
+        self.set_tooltip(toolType)
+        
+    def __get_icon_for_status( self, status):
+        """ Return an icon
+        """
+        # is in cache ?
+        if status in self.__smallStatusIconsCache.keys():
+            log("small status icon found in cache for status %s", status, sender = self)
+            return self.__smallStatusIconsCache[status]
+        
+        # is there in icon for this status 
+        if status in self.__status:
+            log("Creating small status icon for status %s", status, sender = self)
+            
+            # transform
+            try:
+                new_icon = self.__icon.copy()
+                emblem = gtk.icon_theme_get_default().load_icon(self.__status[status][1], new_icon.get_width()/1.5, 0)
+                size = emblem.get_width()                
+                pos = new_icon.get_width() - size
+                emblem.composite(new_icon, pos, pos, size, size, pos, pos, 1, 1, gtk.gdk.INTERP_BILINEAR, 255)
+            
+                self.__smallStatusIconsCache[status] = new_icon
+                return new_icon
+            except Exception, detail:
+                log( "Warning: Cannot load small status icon.\nerror is:%s", detail, sender =self)
+        
+        # no icon for this status, return the normal one
+        log("no small icon found for status %s", status, sender = self)
+        return self.__icon
+
+    def __action_callback(self, n, action):
+        """ call back when a button is clicked in a notify bubble """
+        log("action triggered %s", action, sender = self)
+        if action == "show": 
+            self.uniconify_main_window
+        elif action == "close": 
+            self.__gpodder.close_gpodder
+        elif action == "Ignore": 
+            pass
+        else:
+            log("Warning: don't know what to do with action %s", action, sender = self)
+            
+    def send_notification( self, message, title = "gPodder"):
+        """ Use the gnome notifier system to display a notification message 
+        
+        """
+        log(' systray icon notifies: "%s"', message,  sender = self)
+        #TODO:  a configuration flag to make this optional
+        notification = pynotify.Notification(title, message, gui.scalable_dir)
+        #FIXME: following action do not work. the call back function is never called
+        #notification.add_action("show", "show", self.__action_callback)
+        #notification.add_action("exit", "exit gPodder", self.__action_callback)
+        #notification.add_action("ignore", "ignore", self.__action_callback)
+        if not notification.show():
+            log("Error: enable to send notification %s", notification)
+            
+    def change_status(self, status = None):
+        if status is None:
+            self.set_from_pixbuf(self.__icon)
+            self.set_tooltip("gPodder")
+        elif not status in self.__status.keys():
+            log("status %s is unknown", status, sender = self)
+            self.set_from_pixbuf(self.__icon)
+            self.set_tooltip(status)
+        else:
+            self.set_from_pixbuf(self.__get_icon_for_status(status))
+            self.set_tooltip(self.__status[status][0])
+      
+    def is_iconified(self):
+        return not self.__gpodder.gpodder_main_window.get_property('is-active')
+        
+    def uniconify_main_window(self):
+        log("gpodder restored",  sender = self)
+        if self.is_iconified():
+            self.__gpodder.gpodder_main_window.deiconify()
+            self.__gpodder.gpodder_main_window.set_skip_taskbar_hint(False) 
+        
+    def iconify_main_window(self):
+        log("gpodder iconified",  sender = self)
+        if not self.is_iconified():
+            self.__gpodder.gpodder_main_window.set_skip_taskbar_hint(True)
+            self.__gpodder.gpodder_main_window.iconify()
+            
+               
+
+
+
Index: src/gpodder/download.py
===================================================================
--- src/gpodder/download.py	(révision 503)
+++ src/gpodder/download.py	(copie de travail)
@@ -153,6 +153,7 @@
                 self.downloader.retrieve( self.episode.url, self.tempname, reporthook = self.status_updated)
                 shutil.move( self.tempname, self.filename)
                 self.channel.addDownloadedItem( self.episode)
+                services.download_status_manager.notify_download_completed( self.download_id)
             finally:
                 services.download_status_manager.remove_download_id( self.download_id)
                 services.download_status_manager.s_release( acquired)
Index: src/gpodder/services.py
===================================================================
--- src/gpodder/services.py	(révision 503)
+++ src/gpodder/services.py	(copie de travail)
@@ -82,7 +82,7 @@
         self.tree_model = gtk.ListStore( *self.COLUMN_TYPES)
         self.tree_model_lock = threading.Lock()
         
-        signal_names = ['list-changed', 'progress-changed', 'progress-detail']
+        signal_names = ['list-changed', 'progress-changed', 'progress-detail','download-completed']
         ObservableService.__init__(self, signal_names)
 
     def notify_progress( self):
@@ -170,6 +170,10 @@
             self.notify( 'progress-detail', self.status_list[id]['url'], kwargs['progress'], kwargs['speed'])
 
         self.notify_progress()
+        
+    def notify_download_completed( self, id):
+        if id in self.status_list:
+            self.notify( 'download-completed', self.status_list[id]['url'])
 
     def request_progress_detail( self, url):
         for status in self.status_list.values():
Index: src/gpodder/gui.py
===================================================================
--- src/gpodder/gui.py	(révision 503)
+++ src/gpodder/gui.py	(copie de travail)
@@ -54,6 +54,8 @@
 
 from libtagupdate import tagging_supported
 
+from statusIcon import GPodderStatusIcon
+
 app_name = "gpodder"
 app_version = "unknown" # will be set in main() call
 app_authors = [ 'Thomas Perl <[EMAIL PROTECTED]' ]
@@ -163,7 +165,10 @@
         self.uar = None
 
         gl = gPodderLib()
-
+        
+         #TODO: use a configuration flag to make it optional
+        self.status_icon = GPodderStatusIcon(self)
+        
         gl.config.connect_gtk_window( self.gPodder)
         gl.config.connect_gtk_paned( 'paned_position', self.channelPaned)
 
@@ -289,6 +294,12 @@
                 self.delete_episode_list(old_episodes, confirm=False)
                 self.updateComboBox()
 
+    def get_episode_by_url(self, url):
+        for channel in self.channels:
+            episode = channel.find_episode(url)
+            if episode is not None: return episode
+        log("warning: no episode found for url %s", url, sender = self)
+        return url
 
     def treeview_channels_query_tooltip(self, treeview, x, y, keyboard_tooltip, tooltip):
         # FIXME: Do not hardcode treeview header height
@@ -757,6 +768,7 @@
         title = _('Downloading podcast feeds')
         heading = _('Downloading feeds')
         body = _('Podcast feeds contain channel metadata and information about current episodes.')
+        self.status_icon.change_status("checkingNewEpisodes")
         
         please_wait = gtk.Dialog( title, self.gPodder, gtk.DIALOG_MODAL, ( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, ))
         please_wait.set_transient_for( self.gPodder)
@@ -808,6 +820,7 @@
 
         please_wait.run()
         please_wait.destroy()
+        self.status_icon.change_status(None)
 
         self.updateComboBox()
         
@@ -2189,9 +2202,9 @@
     def on_btnCancel_clicked( self, widget):
         self.gPodderEpisodeSelector.destroy()
         if self.callback is not None:
-            self.callback([])
- 
+            self.callback([]) 
 
+
 def main():
     gobject.threads_init()
     gtk.window_set_default_icon_name( 'gpodder')
_______________________________________________
gpodder-devel mailing list
[email protected]
https://lists.berlios.de/mailman/listinfo/gpodder-devel

Reply via email to