Fixed widget messages when the application has never been run before
[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_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         if self.feed_list == None:
183             treestore.append(["Start Application", "", None])
184             self.update_label("No feeds added yet")
185             self.treeview.set_model(treestore)
186             
187         else:
188             list = []
189             oldtotal = self.total
190             self.total = 0
191             #for key in listOfFeeds["feedingit-order"]:
192             for key in self.feed_list.keys():
193                 try:
194                     file = open(CONFIGDIR+key+".d/unread", "r")
195                     readItems = pickle.load( file )
196                     file.close()
197                     countUnread = 0
198                     for id in readItems.keys():
199                         if readItems[id]==False:
200                             countUnread = countUnread + 1
201                     list.append([self.feed_list[key][0:18], countUnread, key])
202                     self.total += countUnread
203                 except:
204                     pass
205             list = sorted(list, key=lambda item: item[1], reverse=True)
206             for item in list[0:8]:
207                 treestore.append(item)
208             self.treeview.set_model(treestore)
209             if self.total > oldtotal:
210                 self.update_label("%s Unread" %str(self.total), "%s more articles" %str(self.total-oldtotal))
211             else:
212                 self.update_label("%s Unread" %str(self.total))
213         return True
214
215     def create_selector(self, choices, setting):
216         #self.pickerDialog = hildon.PickerDialog(self.parent)
217         selector = hildon.TouchSelector(text=True)
218         index = 0
219         for item in choices:
220             iter = selector.append_text(str(item))
221             if str(self.autoupdate) == str(item): 
222                 selector.set_active(0, index)
223             index += 1
224         selector.connect("changed", self.selection_changed, setting)
225         #self.pickerDialog.set_selector(selector)
226         return selector
227         
228     def selection_changed(self, selector, button, setting):
229         tmp = selector.get_current_text()
230         if tmp == "Disabled":
231             self.autoupdate = 0
232         else:
233             self.autoupdate = tmp
234         #current_selection = selector.get_current_text()
235         #if current_selection:
236         #    self.config[setting] = current_selection
237         #gobject.idle_add(self.updateButton, setting)
238         #self.saveConfig()
239         
240     def create_autoupdate_picker(self):
241             picker = hildon.PickerButton(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
242             selector = self.create_selector(["Disabled", 0.5, 1, 2, 4, 12, 24], "autoupdate")
243             picker.set_selector(selector)
244             picker.set_title("Frequency of updates from the widget")
245             picker.set_text("Setup Feed Auto-updates","Update every %s hours" %str(self.autoupdate) )
246             picker.set_name('HildonButton-finger')
247             picker.set_alignment(0,0,1,1)
248             #self.buttons[setting] = picker
249             #vbox.pack_start(picker, expand=False)
250             return picker
251         
252     def show_settings(self, widget):
253         if isfile(CONFIGDIR+"feeds.pickle"):
254             file = open(CONFIGDIR+"feeds.pickle")
255             listOfFeeds = pickle.load(file)
256             file.close()
257             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))
258     
259             self.pannableArea = hildon.PannableArea()
260             
261             #self.treestore_settings = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
262             self.treeview_settings = gtk.TreeView()
263             
264             self.treeview_settings.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
265             hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview_settings, gtk.HILDON_UI_MODE_EDIT)
266             dialog.vbox.pack_start(self.pannableArea)
267             
268             self.treeview_settings.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
269             self.treestore_settings = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
270             self.treeview_settings.set_model(self.treestore_settings)
271             
272             for key in listOfFeeds["feedingit-order"]:
273                 title = listOfFeeds[key]["title"]
274                 item = self.treestore_settings.append([title, key])
275                 if key in self.feed_list:
276                     self.treeview_settings.get_selection().select_iter(item)
277                 
278             self.pannableArea.add(self.treeview_settings)
279             self.pannableArea.show_all()
280             dialog.set_default_size(-1, 600)
281             
282             dialog.action_area.pack_start(self.create_autoupdate_picker())
283             
284             dialog.show_all()
285             response = dialog.run()
286     
287             if response == gtk.RESPONSE_ACCEPT:
288                 self.feed_list = self.getItems()
289             dialog.destroy()
290             self.save_config()
291             self.update_list()
292         else:
293             dialog = gtk.Dialog("Please add feeds first", None, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
294             label = gtk.Label("Please add feeds through the main application")
295             dialog.vbox.pack_start(label)
296             dialog.show_all()
297             response = dialog.run()
298             dialog.destroy()
299         #self.treeview_settings.get_selection().select_all()
300         
301     def getItems(self):
302         list = {}
303         treeselection = self.treeview_settings.get_selection()
304         (model, pathlist) = treeselection.get_selected_rows()
305         for path in pathlist:
306             list[model.get_value(model.get_iter(path),1)] = model.get_value(model.get_iter(path),0)
307         return list
308         
309     def setupDbus(self):
310         bus.add_signal_receiver(self.update_list, dbus_interface="org.marcoz.feedingit",
311                         signal_name="ArticleCountUpdated", path="/org/marcoz/feedingit/update")
312         bus.add_signal_receiver(self.update_started, dbus_interface="org.marcoz.feedingit",
313                         signal_name="UpdateStarted", path="/org/marcoz/feedingit/update")
314         bus.add_signal_receiver(self.update_finished, dbus_interface="org.marcoz.feedingit",
315                         signal_name="UpdateFinished", path="/org/marcoz/feedingit/update")
316
317     def update_started(self, *widget):
318         self.button.set_sensitive(True)
319         self.update_label("Updating...", "Click to stop update")
320
321     def update_finished(self, *widget):
322         self.button.set_sensitive(False)
323         self.update_label("Update done")
324         
325     def start_update(self):
326         try:
327             if self.autoupdate >0:
328                 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
329                 #from time import localtime, strftime
330                 #import os
331                 #file.write("Widget: pid:%s ppid:%s time:%s\n" % (os.getpid(), os.getppid(), strftime("%a, %d %b %Y %H:%M:%S +0000", localtime())))
332                 #file.close()
333                 remote_object = bus.get_object("org.marcoz.feedingit", # Connection name
334                               "/org/marcoz/feedingit/update" # Object's path
335                               )
336                 iface = dbus.Interface(remote_object, 'org.marcoz.feedingit')
337                 iface.UpdateAll()
338             return True
339         except:
340             import traceback
341             file = open("/home/user/.feedingit/feedingit_widget.log", "a")
342             traceback.print_exc(file=file)
343             file.close()
344
345     def save_config(self):
346             from os.path import isdir
347             if not isdir(CONFIGDIR):
348                 from os import mkdir
349                 mkdir(CONFIGDIR)
350             file = open(CONFIGDIR+"widget", "w")
351             pickle.dump(self.feed_list, file )
352             pickle.dump(self.autoupdate, file)
353             file.close()
354             self.setup_autoupdate()
355
356     def setup_autoupdate(self):
357         if (float(self.autoupdate) > 0):
358             if (not self.autoupdateId==False):
359                 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
360                 #file.write("Disabling %s\n" % self.autoupdateId)
361                 #file.close()
362                 gobject.source_remove(self.autoupdateId)
363                 remove(SOURCE)
364             self.autoupdateId = gobject.timeout_add_seconds(int(float(self.autoupdate)*3600), self.start_update)
365             file = open(SOURCE, "w")
366             file.write(str(self.autoupdateId))
367             file.close()
368             #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
369             #file.write("Started %s\n" % self.autoupdateId)
370             #file.close()
371         else:
372             if (not self.autoupdateId==False):
373                 gobject.source_remove(self.autoupdateId)
374                 self.autoupdateId=False
375                 remove(SOURCE)
376
377     def load_config(self):
378             if isfile(CONFIGDIR+"widget"):
379                 file = open(CONFIGDIR+"widget", "r")
380                 self.feed_list = pickle.load( file )
381                 self.autoupdate = pickle.load( file )
382                 file.close()
383                 self.setup_autoupdate()
384             elif isfile(CONFIGDIR+"feeds.pickle"):
385                 file = open(CONFIGDIR+"feeds.pickle")
386                 listOfFeeds = pickle.load(file)
387                 file.close()
388             
389                 #self.feed_list = listOfFeeds["feedingit-order"]
390                 for key in listOfFeeds["feedingit-order"]:
391                     self.feed_list[key] = listOfFeeds[key]["title"]
392                 del listOfFeeds
393                 self.autoupdate = 0
394             else:
395                 self.feed_list = None
396
397
398 hd_plugin_type = FeedingItHomePlugin
399
400 # The code below is just for testing purposes.
401 # It allows to run the widget as a standalone process.
402 if __name__ == "__main__":
403     import gobject
404     gobject.type_register(hd_plugin_type)
405     obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")
406     obj.show_all()
407     gtk.main()