1 #!/usr/bin/env python2.5
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.
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.
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/>.
19 # ============================================================================
21 # Author : Yves Marcoz
23 # Description : Simple RSS Reader
24 # ============================================================================
29 from htmlentitydefs import name2codepoint
31 import gtk, pickle, gobject, dbus
32 import hildondesktop, hildon
33 #from rss import Listing
35 # Create a session bus.
37 from dbus.mainloop.glib import DBusGMainLoop
38 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
39 #bus = dbus.SessionBus()
41 from os import environ, remove
42 bus = dbus.bus.BusConnection(environ["DBUS_SESSION_BUS_ADDRESS"])
43 from os.path import isfile
44 from cgi import escape
46 settings = gtk.settings_get_default()
47 color_style = gtk.rc_get_style_by_paths( settings, 'GtkButton', 'osso-logical-colors', gtk.Button)
48 active_color = color_style.lookup_color('ActiveTextColor')
49 default_color = color_style.lookup_color('DefaultTextColor')
50 font_desc = gtk.rc_get_style_by_paths(settings, 'HomeSystemFont', None, None).font_desc
54 CONFIGDIR="/home/user/.feedingit/"
55 SOURCE=CONFIGDIR + "source"
57 #DBusConnection *hd_home_plugin_item_get_dbus_connection ( HDHomePluginItem *item, DBusBusType type, DBusError *error);
59 #libc = ctypes.CDLL('libc.so.6')
60 #libc.printf('Hello world!')
62 def get_font_desc(logicalfontname):
63 settings = gtk.settings_get_default()
64 font_style = gtk.rc_get_style_by_paths(settings, logicalfontname, \
66 font_desc = font_style.font_desc
76 return unichr(int(text[3:-1], 16))
78 return unichr(int(text[2:-1]))
84 text = unichr(name2codepoint[text[1:-1]])
87 return text # leave as is
88 return sub("&#?\w+;", fixup, text)
91 return escape(unescape(title).replace("<em>","").replace("</em>","").replace("<nobr>","").replace("</nobr>","").replace("<wbr>",""))
94 class FeedingItHomePlugin(hildondesktop.HomePluginItem):
97 'destroy' : 'override'
101 hildondesktop.HomePluginItem.__init__(self)
102 self.set_settings(True)
103 self.connect("show-settings", self.show_settings)
106 self.status = 0 # 0=Showing feeds, 1=showing articles
107 self.updateStatus = 0 # 0=Not updating, 1=Update in progress
111 self.autoupdateId = int(file.read())
114 self.autoupdateId=False
116 vbox = gtk.VBox(False, 0)
118 ## Prepare the main HBox
119 self.hbox1 = gtk.HBox(False, 0)
120 #self.button = gtk.Button()
121 self.buttonApp = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
122 #self.buttonApp.set_text("FeedingIt","")
123 #self.button.set_sensitive(False)
124 #self.label1 = self.buttonApp.child.child.get_children()[0].get_children()[0]
125 #self.label2 = self.button.child.child.get_children()[0].get_children()[1]
126 #self.label1.modify_fg(gtk.STATE_INSENSITIVE, default_color)
127 #self.label1.modify_font(font_desc)
128 #self.label2.modify_fg(gtk.STATE_INSENSITIVE, active_color)
129 icon_theme = gtk.icon_theme_get_default()
130 pixbuf = icon_theme.load_icon("feedingit", 20, gtk.ICON_LOOKUP_USE_BUILTIN )
132 image.set_from_pixbuf(pixbuf)
133 self.buttonApp.set_image(image)
134 self.buttonApp.set_image_position(gtk.POS_RIGHT)
135 #button = gtk.Button("Update")
136 self.buttonApp.connect("clicked", self.button_clicked)
138 self.buttonUpdate = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
139 self.buttonUpdate.set_image(gtk.image_new_from_icon_name('general_refresh', gtk.ICON_SIZE_BUTTON))
140 self.buttonUpdate.connect("clicked", self.buttonUpdate_clicked)
142 self.buttonUp = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
143 self.buttonUp.set_image(gtk.image_new_from_icon_name('keyboard_move_up', gtk.ICON_SIZE_BUTTON))
144 self.buttonUp.set_sensitive(False)
145 self.buttonUp.connect("clicked", self.buttonUp_clicked)
147 self.buttonDown = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
148 self.buttonDown.set_image(gtk.image_new_from_icon_name('keyboard_move_down', gtk.ICON_SIZE_BUTTON))
149 self.buttonDown.set_sensitive(False)
150 self.buttonDown.connect("clicked", self.buttonDown_clicked)
152 self.hbox1.pack_start(self.buttonUpdate, expand=False)
153 self.hbox1.pack_start(self.buttonUp, expand=False)
154 self.hbox1.pack_start(self.buttonDown, expand=False)
155 self.hbox1.pack_start(self.buttonApp, expand=False)
160 #for feed in ["Slashdot", "Engadget", "Cheez"]:
161 # self.treestore.append([feed, "0"])
162 self.treeview = gtk.TreeView()
164 name_renderer = gtk.CellRendererText()
165 name_renderer.set_property("font-desc", font_desc)
166 name_renderer.set_property('background', "#333333")
167 self.unread_renderer = gtk.CellRendererText()
168 self.unread_renderer.set_property("font-desc", font_desc)
169 self.unread_renderer.set_property("xalign", 1.0)
170 self.unread_renderer.set_property('background', "#333333")
171 column_unread = gtk.TreeViewColumn('Unread Items', self.unread_renderer, text = 1)
172 column_unread.set_expand(False)
173 column_name = gtk.TreeViewColumn('Feed Name', name_renderer, text = 0)
174 column_name.set_expand(True)
175 self.treeview.append_column(column_name)
176 self.treeview.append_column(column_unread)
177 #selection = self.treeview.get_selection()
178 #selection.set_mode(gtk.SELECTION_NONE)
179 #self.treeview.get_selection().set_mode(gtk.SELECTION_NONE)
180 #hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_NORMAL)
182 vbox.pack_start(self.treeview)
183 vbox.pack_start(self.hbox1, expand=False)
186 self.treeview.connect("hildon-row-tapped", self.row_activated)
187 #self.treeview.connect("cursor-changed", self.cursor_changed)
190 #gobject.timeout_add_seconds(30*60, self.update_list)
193 file = open("/home/user/feedingit_widget.log", "a")
194 traceback.print_exc(file=file)
197 def do_destroy(self):
198 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
199 #file.write("Do_destroy: ")
200 if (not self.autoupdateId==False):
201 gobject.source_remove(self.autoupdateId)
202 self.autoupdateId=False
203 #file.write("Destroyed %s\n" %self.autoupdateId)
205 hildondesktop.HomePluginItem.do_destroy(self)
206 #file.write("End destroy\n")
209 def button_clicked(self, *widget):
210 #self.button.set_sensitive(False)
211 #self.label1.modify_fg(gtk.STATE_NORMAL, default_color)
212 #self.label2.modify_fg(gtk.STATE_NORMAL, active_color)
213 #self.update_label("Stopping")
215 remote_object = bus.get_object("org.maemo.feedingit", # Connection name
216 "/org/maemo/feedingit" # Object's path
218 iface = dbus.Interface(remote_object, 'org.maemo.feedingit')
223 self.buttonUp.set_sensitive(False)
224 self.buttonDown.set_sensitive(False)
225 self.treeview.append_column(gtk.TreeViewColumn('Unread Items', self.unread_renderer, text = 1))
229 def buttonUpdate_clicked(self, *widget):
230 remote_object = bus.get_object("org.marcoz.feedingit", # Connection name
231 "/org/marcoz/feedingit/update" # Object's path
233 iface = dbus.Interface(remote_object, 'org.marcoz.feedingit')
234 if self.updateStatus == 0:
239 def buttonUp_clicked(self, *widget):
240 if self.pageStatus > 0:
244 def buttonDown_clicked(self, *widget):
248 def update_label(self, value=None):
250 self.buttonApp.set_title(str(value))
252 self.buttonApp.set_title("")
254 #def row_activated(self, treeview, treepath): #, column):
255 # (model, iter) = self.treeview.get_selection().get_selected()
256 # key = model.get_value(iter, 2)
257 # Create an object that will proxy for a particular remote object.
258 # remote_object = bus.get_object("org.maemo.feedingit", # Connection name
259 # "/org/maemo/feedingit" # Object's path
261 # iface = dbus.Interface(remote_object, 'org.maemo.feedingit')
262 # iface.OpenFeed(key)
264 def show_articles(self):
265 db = sqlite3.connect(CONFIGDIR+self.key+".d/"+self.key+".db")
266 count = db.execute("SELECT count(*) FROM feed WHERE read=0;").fetchone()[0]
269 if self.pageStatus > maxPage:
270 self.pageStatus = maxPage
271 rows = db.execute("SELECT id, title FROM feed WHERE read=0 ORDER BY date DESC LIMIT 10 OFFSET ?;", (self.pageStatus*10,) )
272 treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
274 title = fix_title(row[1][0:32])
276 treestore.append((title, id))
277 self.treeview.set_model(treestore)
279 def row_activated(self, treeview, treepath):
283 (model, iter) = self.treeview.get_selection().get_selected()
284 self.key = model.get_value(iter, 2)
285 treeviewcolumn = self.treeview.get_column(1)
286 self.treeview.remove_column(treeviewcolumn)
288 self.buttonApp.set_image(gtk.image_new_from_icon_name('general_back', gtk.ICON_SIZE_BUTTON))
289 self.buttonUp.set_sensitive(True)
290 self.buttonDown.set_sensitive(True)
292 (model, iter) = self.treeview.get_selection().get_selected()
293 id = model.get_value(iter, 1)
294 # Create an object that will proxy for a particular remote object.
295 remote_object = bus.get_object("org.maemo.feedingit", # Connection name
296 "/org/maemo/feedingit" # Object's path
298 iface = dbus.Interface(remote_object, 'org.maemo.feedingit')
299 iface.OpenArticle(self.key, id)
302 def update_list(self, *widget):
303 #listing = Listing(CONFIGDIR)
305 treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
307 if self.feed_list == {}:
310 if self.feed_list == None:
311 treestore.append(["No feeds added yet", "", None])
312 treestore.append(["Start Application", "", None])
313 #self.update_label("No feeds added yet")
314 self.treeview.set_model(treestore)
318 oldtotal = self.total
320 #for key in listOfFeeds["feedingit-order"]:
321 db = sqlite3.connect(CONFIGDIR+"feeds.db")
322 for key in self.feed_list.keys():
324 countUnread = db.execute("SELECT unread FROM feeds WHERE id=?;", (key,)).fetchone()[0]
325 list.append([self.feed_list[key][0:25], countUnread, key])
326 self.total += countUnread
329 list = sorted(list, key=lambda item: item[1], reverse=True)
331 for item in list[0:10]:
333 treestore.append(item)
334 for i in range(count, 10):
335 treestore.append( ("", "", None) )
336 self.treeview.set_model(treestore)
337 self.buttonApp.set_image(gtk.image_new_from_icon_name('feedingit', gtk.ICON_SIZE_BUTTON))
338 #self.update_label(self.total)
341 def create_selector(self, choices, setting):
342 #self.pickerDialog = hildon.PickerDialog(self.parent)
343 selector = hildon.TouchSelector(text=True)
346 iter = selector.append_text(str(item))
347 if str(self.autoupdate) == str(item):
348 selector.set_active(0, index)
350 selector.connect("changed", self.selection_changed, setting)
351 #self.pickerDialog.set_selector(selector)
354 def selection_changed(self, selector, button, setting):
355 tmp = selector.get_current_text()
356 if tmp == "Disabled":
359 self.autoupdate = tmp
360 #current_selection = selector.get_current_text()
361 #if current_selection:
362 # self.config[setting] = current_selection
363 #gobject.idle_add(self.updateButton, setting)
366 def create_autoupdate_picker(self):
367 picker = hildon.PickerButton(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
368 selector = self.create_selector(["Disabled", 0.5, 1, 2, 4, 12, 24], "autoupdate")
369 picker.set_selector(selector)
370 picker.set_title("Frequency of updates from the widget")
371 picker.set_text("Setup Feed Auto-updates","Update every %s hours" %str(self.autoupdate) )
372 picker.set_name('HildonButton-finger')
373 picker.set_alignment(0,0,1,1)
374 #self.buttons[setting] = picker
375 #vbox.pack_start(picker, expand=False)
378 def show_settings(self, widget):
379 if isfile(CONFIGDIR+"feeds.db"):
380 db = sqlite3.connect(CONFIGDIR+"feeds.db")
382 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))
384 self.pannableArea = hildon.PannableArea()
386 #self.treestore_settings = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
387 self.treeview_settings = gtk.TreeView()
389 self.treeview_settings.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
390 hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview_settings, gtk.HILDON_UI_MODE_EDIT)
391 dialog.vbox.pack_start(self.pannableArea)
393 self.treeview_settings.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
394 self.treestore_settings = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
395 self.treeview_settings.set_model(self.treestore_settings)
397 feeds = db.execute("SELECT title, id FROM feeds;")
400 # feed is (id, title)
401 item = self.treestore_settings.append(feed)
402 if feed[1] in self.feed_list:
403 self.treeview_settings.get_selection().select_iter(item)
405 self.pannableArea.add(self.treeview_settings)
406 self.pannableArea.show_all()
407 dialog.set_default_size(-1, 600)
409 dialog.action_area.pack_start(self.create_autoupdate_picker())
412 response = dialog.run()
414 if response == gtk.RESPONSE_ACCEPT:
415 self.feed_list = self.getItems()
420 dialog = gtk.Dialog("Please add feeds first", None, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
421 label = gtk.Label("Please add feeds through the main application")
422 dialog.vbox.pack_start(label)
424 response = dialog.run()
426 #self.treeview_settings.get_selection().select_all()
430 treeselection = self.treeview_settings.get_selection()
431 (model, pathlist) = treeselection.get_selected_rows()
432 for path in pathlist:
433 list[model.get_value(model.get_iter(path),1)] = model.get_value(model.get_iter(path),0)
437 bus.add_signal_receiver(self.update_list, dbus_interface="org.marcoz.feedingit",
438 signal_name="ArticleCountUpdated", path="/org/marcoz/feedingit/update")
439 bus.add_signal_receiver(self.update_started, dbus_interface="org.marcoz.feedingit",
440 signal_name="UpdateStarted", path="/org/marcoz/feedingit/update")
441 bus.add_signal_receiver(self.update_finished, dbus_interface="org.marcoz.feedingit",
442 signal_name="UpdateFinished", path="/org/marcoz/feedingit/update")
444 def update_started(self, *widget):
445 self.buttonUpdate.set_image(gtk.image_new_from_icon_name('general_stop', gtk.ICON_SIZE_BUTTON))
446 self.updateStatus = 1
448 def update_finished(self, *widget):
449 self.updateStatus = 0
450 self.buttonUpdate.set_image(gtk.image_new_from_icon_name('general_refresh', gtk.ICON_SIZE_BUTTON))
452 def start_update(self):
454 if self.autoupdate >0:
455 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
456 #from time import localtime, strftime
458 #file.write("Widget: pid:%s ppid:%s time:%s\n" % (os.getpid(), os.getppid(), strftime("%a, %d %b %Y %H:%M:%S +0000", localtime())))
460 remote_object = bus.get_object("org.marcoz.feedingit", # Connection name
461 "/org/marcoz/feedingit/update" # Object's path
463 iface = dbus.Interface(remote_object, 'org.marcoz.feedingit')
468 file = open("/home/user/.feedingit/feedingit_widget.log", "a")
469 traceback.print_exc(file=file)
472 def save_config(self):
473 from os.path import isdir
474 if not isdir(CONFIGDIR):
477 file = open(CONFIGDIR+"widget", "w")
478 pickle.dump(self.feed_list, file )
479 pickle.dump(self.autoupdate, file)
481 self.setup_autoupdate()
483 def setup_autoupdate(self):
484 if (float(self.autoupdate) > 0):
485 if (not self.autoupdateId==False):
486 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
487 #file.write("Disabling %s\n" % self.autoupdateId)
489 gobject.source_remove(self.autoupdateId)
491 self.autoupdateId = gobject.timeout_add_seconds(int(float(self.autoupdate)*3600), self.start_update)
492 file = open(SOURCE, "w")
493 file.write(str(self.autoupdateId))
495 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
496 #file.write("Started %s\n" % self.autoupdateId)
499 if (not self.autoupdateId==False):
500 gobject.source_remove(self.autoupdateId)
501 self.autoupdateId=False
504 def load_config(self):
505 if isfile(CONFIGDIR+"widget"):
506 file = open(CONFIGDIR+"widget", "r")
507 self.feed_list = pickle.load( file )
508 self.autoupdate = pickle.load( file )
510 self.setup_autoupdate()
511 elif isfile(CONFIGDIR+"feeds.db"):
512 db = sqlite3.connect(CONFIGDIR+"feeds.db")
513 feeds = db.execute("SELECT id, title FROM feeds;")
516 self.feed_list[feed[0]] = feed[1]
519 self.feed_list = None
522 hd_plugin_type = FeedingItHomePlugin
524 # The code below is just for testing purposes.
525 # It allows to run the widget as a standalone process.
526 if __name__ == "__main__":
528 gobject.type_register(hd_plugin_type)
529 obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")