Modified TreeView for article listing, and for the widget
[feedingit] / src / feedingit_widget.py
1 #!/usr/bin/env python2.5
2
3
4 # Copyright (c) 2007-2008 INdT.
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 #  This program is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #  GNU Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public License
16 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 # ============================================================================
20 # Name        : FeedingIt.py
21 # Author      : Yves Marcoz
22 # Version     : 0.6.0
23 # Description : Simple RSS Reader
24 # ============================================================================
25 #import sys
26
27 import gtk, pickle, gobject, dbus
28 import hildondesktop, hildon
29 #from rss import Listing
30
31 # Create a session bus.
32 import dbus
33 from dbus.mainloop.glib import DBusGMainLoop
34 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
35 #bus = dbus.SessionBus()
36
37 from os import environ, remove
38 bus = dbus.bus.BusConnection(environ["DBUS_SESSION_BUS_ADDRESS"])
39 from os.path import isfile
40
41 settings = gtk.settings_get_default()
42 color_style = gtk.rc_get_style_by_paths( settings, 'GtkButton', 'osso-logical-colors', gtk.Button)
43 active_color = color_style.lookup_color('ActiveTextColor')
44 default_color = color_style.lookup_color('DefaultTextColor')
45 font_desc = gtk.rc_get_style_by_paths(settings, 'HomeSystemFont', None, None).font_desc
46
47 del color_style
48
49 CONFIGDIR="/home/user/.feedingit/"
50 SOURCE=CONFIGDIR + "source"
51
52 #DBusConnection *hd_home_plugin_item_get_dbus_connection ( HDHomePluginItem *item, DBusBusType type, DBusError *error);
53 #import ctypes
54 #libc = ctypes.CDLL('libc.so.6')
55 #libc.printf('Hello world!')
56
57 def get_font_desc(logicalfontname):
58     settings = gtk.settings_get_default()
59     font_style = gtk.rc_get_style_by_paths(settings, logicalfontname, \
60             None, None)
61     font_desc = font_style.font_desc
62     return font_desc
63
64 class FeedingItHomePlugin(hildondesktop.HomePluginItem):
65     def __init__(self):
66       __gsignals__ = {
67       'destroy' : 'override'
68       }
69
70       try:
71         hildondesktop.HomePluginItem.__init__(self)
72         self.set_settings(True)
73         self.connect("show-settings", self.show_settings)
74         self.feed_list = {}
75         self.total = 0
76         if isfile(SOURCE):
77             file = open(SOURCE)
78             self.autoupdateId = int(file.read())
79             file.close() 
80         else:
81             self.autoupdateId=False
82         
83         vbox = gtk.VBox(False, 0)
84         
85         #self.button = gtk.Button()
86         self.button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
87         self.button.set_text("FeedingIt","")
88         self.button.set_sensitive(False)
89         self.label1 = self.button.child.child.get_children()[0].get_children()[0]
90         self.label2 = self.button.child.child.get_children()[0].get_children()[1]
91         self.label1.modify_fg(gtk.STATE_INSENSITIVE, default_color)
92         self.label1.modify_font(font_desc)
93         self.label2.modify_fg(gtk.STATE_INSENSITIVE, active_color)
94         icon_theme = gtk.icon_theme_get_default()
95         pixbuf = icon_theme.load_icon("feedingit", 32, gtk.ICON_LOOKUP_USE_BUILTIN )
96         image = gtk.Image()
97         image.set_from_pixbuf(pixbuf)
98         self.button.set_image(image)
99         self.button.set_image_position(gtk.POS_LEFT)
100
101         #button = gtk.Button("Update")
102         self.button.connect("clicked", self.button_clicked)
103         #button.show_all()
104         vbox.pack_start(self.button, expand=False)        
105         
106         #for feed in ["Slashdot", "Engadget", "Cheez"]:
107         #    self.treestore.append([feed, "0"])
108         self.treeview = gtk.TreeView()
109         self.update_list()
110         name_renderer = gtk.CellRendererText()
111         name_renderer.set_property("font-desc", font_desc)
112         unread_renderer = gtk.CellRendererText()
113         unread_renderer.set_property("font-desc", font_desc)        
114         self.treeview.append_column(gtk.TreeViewColumn('Feed Name', name_renderer, text = 0))
115         self.treeview.append_column(gtk.TreeViewColumn('Unread Items', unread_renderer, text = 1))
116         selection = self.treeview.get_selection()
117         #selection.set_mode(gtk.SELECTION_NONE)
118         #self.treeview.get_selection().set_mode(gtk.SELECTION_NONE)
119         #hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_NORMAL)
120         
121         vbox.pack_start(self.treeview)
122         
123         self.add(vbox)
124         self.treeview.connect("hildon-row-tapped", self.row_activated)
125         #self.treeview.connect("cursor-changed", self.cursor_changed)
126         vbox.show_all()
127         self.setupDbus()
128         #gobject.timeout_add_seconds(30*60, self.update_list)
129       except:
130           import traceback
131           file = open("/home/user/.feedingit/feedingit_widget.log", "a")
132           traceback.print_exc(file=file)
133           file.close()
134           
135     def do_destroy(self):
136         #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
137         #file.write("Do_destroy: ")
138         if (not self.autoupdateId==False):
139             gobject.source_remove(self.autoupdateId)
140             self.autoupdateId=False
141             #file.write("Destroyed %s\n" %self.autoupdateId)
142             remove(SOURCE)
143         hildondesktop.HomePluginItem.do_destroy(self)
144         #file.write("End destroy\n")
145         #file.close()
146
147     def button_clicked(self, *widget):
148         self.button.set_sensitive(False)
149         self.label1.modify_fg(gtk.STATE_NORMAL, default_color)
150         self.label2.modify_fg(gtk.STATE_NORMAL, active_color)
151         self.update_label("Stopping")
152         remote_object = bus.get_object("org.marcoz.feedingit", # Connection name
153                                "/org/marcoz/feedingit/update" # Object's path
154                               )
155         iface = dbus.Interface(remote_object, 'org.marcoz.feedingit')
156         iface.StopUpdate()
157         
158     def update_label(self, title, value=None):
159         self.button.set_title(title)
160         if value != None:
161             self.button.set_value(value)
162         else:
163             self.button.set_value("")
164
165     def row_activated(self, treeview, treepath): #, column):
166         (model, iter) = self.treeview.get_selection().get_selected()
167         key = model.get_value(iter, 2)
168         # Create an object that will proxy for a particular remote object.
169         remote_object = bus.get_object("org.maemo.feedingit", # Connection name
170                                "/org/maemo/feedingit" # Object's path
171                               )
172         iface = dbus.Interface(remote_object, 'org.maemo.feedingit')
173         iface.OpenFeed(key)
174
175     def update_list(self, *widget):
176         #listing = Listing(CONFIGDIR)
177         treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
178         
179         if self.feed_list == {}:
180             self.load_config()
181
182         list = []
183         oldtotal = self.total
184         self.total = 0
185         #for key in listOfFeeds["feedingit-order"]:
186         for key in self.feed_list.keys():
187             try:
188                 file = open(CONFIGDIR+key+".d/unread", "r")
189                 readItems = pickle.load( file )
190                 file.close()
191                 countUnread = 0
192                 for id in readItems.keys():
193                     if readItems[id]==False:
194                         countUnread = countUnread + 1
195                 list.append([self.feed_list[key][0:18], countUnread, key])
196                 self.total += countUnread
197             except:
198                 pass
199         list = sorted(list, key=lambda item: item[1], reverse=True)
200         for item in list[0:8]:
201             treestore.append(item)
202         self.treeview.set_model(treestore)
203         if self.total > oldtotal:
204             self.update_label("%s Unread" %str(self.total), "%s more articles" %str(self.total-oldtotal))
205         else:
206             self.update_label("%s Unread" %str(self.total))
207         return True
208
209     def create_selector(self, choices, setting):
210         #self.pickerDialog = hildon.PickerDialog(self.parent)
211         selector = hildon.TouchSelector(text=True)
212         index = 0
213         for item in choices:
214             iter = selector.append_text(str(item))
215             if str(self.autoupdate) == str(item): 
216                 selector.set_active(0, index)
217             index += 1
218         selector.connect("changed", self.selection_changed, setting)
219         #self.pickerDialog.set_selector(selector)
220         return selector
221         
222     def selection_changed(self, selector, button, setting):
223         tmp = selector.get_current_text()
224         if tmp == "Disabled":
225             self.autoupdate = 0
226         else:
227             self.autoupdate = tmp
228         #current_selection = selector.get_current_text()
229         #if current_selection:
230         #    self.config[setting] = current_selection
231         #gobject.idle_add(self.updateButton, setting)
232         #self.saveConfig()
233         
234     def create_autoupdate_picker(self):
235             picker = hildon.PickerButton(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
236             selector = self.create_selector(["Disabled", 0.5, 1, 2, 4, 12, 24], "autoupdate")
237             picker.set_selector(selector)
238             picker.set_title("Frequency of updates from the widget")
239             picker.set_text("Setup Feed Auto-updates","Update every %s hours" %str(self.autoupdate) )
240             picker.set_name('HildonButton-finger')
241             picker.set_alignment(0,0,1,1)
242             #self.buttons[setting] = picker
243             #vbox.pack_start(picker, expand=False)
244             return picker
245         
246     def show_settings(self, widget):
247         file = open(CONFIGDIR+"feeds.pickle")
248         listOfFeeds = pickle.load(file)
249         file.close()
250         dialog = gtk.Dialog("Choose feeds to display", None, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
251
252         self.pannableArea = hildon.PannableArea()
253         
254         #self.treestore_settings = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
255         self.treeview_settings = gtk.TreeView()
256         
257         self.treeview_settings.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
258         hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview_settings, gtk.HILDON_UI_MODE_EDIT)
259         dialog.vbox.pack_start(self.pannableArea)
260         
261         self.treeview_settings.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
262         self.treestore_settings = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
263         self.treeview_settings.set_model(self.treestore_settings)
264         
265         for key in listOfFeeds["feedingit-order"]:
266             title = listOfFeeds[key]["title"]
267             item = self.treestore_settings.append([title, key])
268             if key in self.feed_list:
269                 self.treeview_settings.get_selection().select_iter(item)
270             
271         self.pannableArea.add(self.treeview_settings)
272         self.pannableArea.show_all()
273         dialog.set_default_size(-1, 600)
274         
275         dialog.action_area.pack_start(self.create_autoupdate_picker())
276         
277         dialog.show_all()
278         response = dialog.run()
279
280         if response == gtk.RESPONSE_ACCEPT:
281             self.feed_list = self.getItems()
282         dialog.destroy()
283         self.save_config()
284         self.update_list()
285         #self.treeview_settings.get_selection().select_all()
286         
287     def getItems(self):
288         list = {}
289         treeselection = self.treeview_settings.get_selection()
290         (model, pathlist) = treeselection.get_selected_rows()
291         for path in pathlist:
292             list[model.get_value(model.get_iter(path),1)] = model.get_value(model.get_iter(path),0)
293         return list
294         
295     def setupDbus(self):
296         bus.add_signal_receiver(self.update_list, dbus_interface="org.marcoz.feedingit",
297                         signal_name="ArticleCountUpdated", path="/org/marcoz/feedingit/update")
298         bus.add_signal_receiver(self.update_started, dbus_interface="org.marcoz.feedingit",
299                         signal_name="UpdateStarted", path="/org/marcoz/feedingit/update")
300         bus.add_signal_receiver(self.update_finished, dbus_interface="org.marcoz.feedingit",
301                         signal_name="UpdateFinished", path="/org/marcoz/feedingit/update")
302
303     def update_started(self, *widget):
304         self.button.set_sensitive(True)
305         self.update_label("Updating...", "Click to stop update")
306
307     def update_finished(self, *widget):
308         self.button.set_sensitive(False)
309         self.update_label("Update done")
310         
311     def start_update(self):
312         try:
313             if self.autoupdate >0:
314                 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
315                 #from time import localtime, strftime
316                 #import os
317                 #file.write("Widget: pid:%s ppid:%s time:%s\n" % (os.getpid(), os.getppid(), strftime("%a, %d %b %Y %H:%M:%S +0000", localtime())))
318                 #file.close()
319                 remote_object = bus.get_object("org.marcoz.feedingit", # Connection name
320                               "/org/marcoz/feedingit/update" # Object's path
321                               )
322                 iface = dbus.Interface(remote_object, 'org.marcoz.feedingit')
323                 iface.UpdateAll()
324             return True
325         except:
326             import traceback
327             file = open("/home/user/.feedingit/feedingit_widget.log", "a")
328             traceback.print_exc(file=file)
329             file.close()
330
331     def save_config(self):
332             from os.path import isdir
333             if not isdir(CONFIGDIR):
334                 from os import mkdir
335                 mkdir(CONFIGDIR)
336             file = open(CONFIGDIR+"widget", "w")
337             pickle.dump(self.feed_list, file )
338             pickle.dump(self.autoupdate, file)
339             file.close()
340             self.setup_autoupdate()
341
342     def setup_autoupdate(self):
343         if (float(self.autoupdate) > 0):
344             if (not self.autoupdateId==False):
345                 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
346                 #file.write("Disabling %s\n" % self.autoupdateId)
347                 #file.close()
348                 gobject.source_remove(self.autoupdateId)
349                 remove(SOURCE)
350             self.autoupdateId = gobject.timeout_add_seconds(int(float(self.autoupdate)*3600), self.start_update)
351             file = open(SOURCE, "w")
352             file.write(str(self.autoupdateId))
353             file.close()
354             #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
355             #file.write("Started %s\n" % self.autoupdateId)
356             #file.close()
357         else:
358             if (not self.autoupdateId==False):
359                 gobject.source_remove(self.autoupdateId)
360                 self.autoupdateId=False
361                 remove(SOURCE)
362
363     def load_config(self):
364             try:
365                 file = open(CONFIGDIR+"widget", "r")
366                 self.feed_list = pickle.load( file )
367                 self.autoupdate = pickle.load( file )
368                 file.close()
369                 self.setup_autoupdate()
370             except:
371                 file = open(CONFIGDIR+"feeds.pickle")
372                 listOfFeeds = pickle.load(file)
373                 file.close()
374             
375                 #self.feed_list = listOfFeeds["feedingit-order"]
376                 for key in listOfFeeds["feedingit-order"]:
377                     self.feed_list[key] = listOfFeeds[key]["title"]
378                 del listOfFeeds
379                 self.autoupdate = 0
380
381
382 hd_plugin_type = FeedingItHomePlugin
383
384 # The code below is just for testing purposes.
385 # It allows to run the widget as a standalone process.
386 if __name__ == "__main__":
387     import gobject
388     gobject.type_register(hd_plugin_type)
389     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
390     obj.show_all()
391     gtk.main()