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 pango import ELLIPSIZE_END
34 #from rss import Listing
36 # Create a session bus.
38 from dbus.mainloop.glib import DBusGMainLoop
39 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
40 #bus = dbus.SessionBus()
42 from os import environ, remove
43 bus = dbus.bus.BusConnection(environ["DBUS_SESSION_BUS_ADDRESS"])
44 from os.path import isfile
45 from cgi import escape
47 settings = gtk.settings_get_default()
48 color_style = gtk.rc_get_style_by_paths( settings, 'GtkButton', 'osso-logical-colors', gtk.Button)
49 active_color = color_style.lookup_color('ActiveTextColor')
50 default_color = color_style.lookup_color('DefaultTextColor')
51 font_desc = gtk.rc_get_style_by_paths(settings, 'HomeSystemFont', None, None).font_desc
55 CONFIGDIR="/home/user/.feedingit/"
56 SOURCE=CONFIGDIR + "source"
58 #DBusConnection *hd_home_plugin_item_get_dbus_connection ( HDHomePluginItem *item, DBusBusType type, DBusError *error);
60 #libc = ctypes.CDLL('libc.so.6')
61 #libc.printf('Hello world!')
63 def get_font_desc(logicalfontname):
64 settings = gtk.settings_get_default()
65 font_style = gtk.rc_get_style_by_paths(settings, logicalfontname, \
67 font_desc = font_style.font_desc
77 return unichr(int(text[3:-1], 16))
79 return unichr(int(text[2:-1]))
85 text = unichr(name2codepoint[text[1:-1]])
88 return text # leave as is
89 return sub("&#?\w+;", fixup, text)
92 return escape(unescape(title).replace("<em>","").replace("</em>","").replace("<nobr>","").replace("</nobr>","").replace("<wbr>",""))
95 class FeedingItHomePlugin(hildondesktop.HomePluginItem):
98 'destroy' : 'override'
102 hildondesktop.HomePluginItem.__init__(self)
103 self.set_settings(True)
104 self.connect("show-settings", self.show_settings)
105 self.initialized = False
107 self.status = 0 # 0=Showing feeds, 1=showing articles
108 self.updateStatus = 0 # 0=Not updating, 1=Update in progress
111 self.size = 1 # 1=Big widget, 0=small widget
115 self.autoupdateId = int(file.read())
118 self.autoupdateId=False
121 vbox = gtk.VBox(False, 0)
123 ## Prepare the main HBox
124 self.hbox1 = gtk.HBox(False, 0)
125 #self.button = gtk.Button()
126 self.buttonApp = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
127 #self.buttonApp.set_text("FeedingIt","")
128 #self.button.set_sensitive(False)
129 #self.label1 = self.buttonApp.child.child.get_children()[0].get_children()[0]
130 #self.label2 = self.button.child.child.get_children()[0].get_children()[1]
131 #self.label1.modify_fg(gtk.STATE_INSENSITIVE, default_color)
132 #self.label1.modify_font(font_desc)
133 #self.label2.modify_fg(gtk.STATE_INSENSITIVE, active_color)
134 icon_theme = gtk.icon_theme_get_default()
135 pixbuf = icon_theme.load_icon("feedingit", 20, gtk.ICON_LOOKUP_USE_BUILTIN )
137 image.set_from_pixbuf(pixbuf)
138 self.buttonApp.set_image(image)
139 self.buttonApp.set_image_position(gtk.POS_RIGHT)
140 #button = gtk.Button("Update")
141 self.buttonApp.connect("clicked", self.button_clicked)
143 self.buttonUpdate = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
144 self.buttonUpdate.set_image(gtk.image_new_from_icon_name('general_refresh', gtk.ICON_SIZE_BUTTON))
145 self.buttonUpdate.connect("clicked", self.buttonUpdate_clicked)
148 self.buttonUp = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
149 self.buttonUp.set_image(gtk.image_new_from_icon_name('keyboard_move_up', gtk.ICON_SIZE_BUTTON))
150 self.buttonUp.set_sensitive(False)
151 self.buttonUp.connect("clicked", self.buttonUp_clicked)
153 self.buttonDown = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
154 self.buttonDown.set_image(gtk.image_new_from_icon_name('keyboard_move_down', gtk.ICON_SIZE_BUTTON))
155 self.buttonDown.set_sensitive(False)
156 self.buttonDown.connect("clicked", self.buttonDown_clicked)
158 self.hbox1.pack_start(self.buttonUpdate, expand=False)
160 self.hbox1.pack_start(self.buttonUp, expand=False)
161 self.hbox1.pack_start(self.buttonDown, expand=False)
162 self.hbox1.pack_start(self.buttonApp, expand=False)
167 #for feed in ["Slashdot", "Engadget", "Cheez"]:
168 # self.treestore.append([feed, "0"])
169 self.treeview = gtk.TreeView()
171 name_renderer = gtk.CellRendererText()
172 name_renderer.set_property("font-desc", font_desc)
173 name_renderer.set_property('background', "#333333")
174 name_renderer.set_property('ellipsize', ELLIPSIZE_END)
175 self.unread_renderer = gtk.CellRendererText()
176 self.unread_renderer.set_property("font-desc", font_desc)
177 self.unread_renderer.set_property("xalign", 1.0)
178 self.unread_renderer.set_property('background', "#333333")
179 column_unread = gtk.TreeViewColumn('Unread Items', self.unread_renderer, text = 1)
180 column_unread.set_expand(False)
181 column_name = gtk.TreeViewColumn('Feed Name', name_renderer, markup = 0)
182 column_name.set_expand(True)
183 self.treeview.append_column(column_name)
184 self.treeview.append_column(column_unread)
185 #selection = self.treeview.get_selection()
186 #selection.set_mode(gtk.SELECTION_NONE)
187 #self.treeview.get_selection().set_mode(gtk.SELECTION_NONE)
188 #hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_NORMAL)
190 vbox.pack_start(self.treeview)
191 vbox.pack_start(self.hbox1, expand=False)
194 self.treeview.connect("hildon-row-tapped", self.row_activated)
195 #self.treeview.connect("cursor-changed", self.cursor_changed)
197 self.set_resize_mode(gtk.RESIZE_QUEUE)
199 #gobject.timeout_add_seconds(30*60, self.update_list)
202 file = open("/home/user/feedingit_widget.log", "a")
203 traceback.print_exc(file=file)
206 def do_destroy(self):
207 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
208 #file.write("Do_destroy: ")
209 if (not self.autoupdateId==False):
210 gobject.source_remove(self.autoupdateId)
211 self.autoupdateId=False
212 #file.write("Destroyed %s\n" %self.autoupdateId)
214 hildondesktop.HomePluginItem.do_destroy(self)
215 #file.write("End destroy\n")
218 def button_clicked(self, *widget):
219 #self.button.set_sensitive(False)
220 #self.label1.modify_fg(gtk.STATE_NORMAL, default_color)
221 #self.label2.modify_fg(gtk.STATE_NORMAL, active_color)
222 #self.update_label("Stopping")
224 remote_object = bus.get_object("org.maemo.feedingit", # Connection name
225 "/org/maemo/feedingit" # Object's path
227 iface = dbus.Interface(remote_object, 'org.maemo.feedingit')
233 self.buttonUp.set_sensitive(False)
234 self.buttonDown.set_sensitive(False)
235 self.treeview.append_column(gtk.TreeViewColumn('Unread Items', self.unread_renderer, text = 1))
239 def buttonUpdate_clicked(self, *widget):
240 remote_object = bus.get_object("org.marcoz.feedingit", # Connection name
241 "/org/marcoz/feedingit/update" # Object's path
243 iface = dbus.Interface(remote_object, 'org.marcoz.feedingit')
244 if self.updateStatus == 0:
249 def buttonUp_clicked(self, *widget):
250 if self.pageStatus > 0:
254 def buttonDown_clicked(self, *widget):
258 def update_label(self, value=None):
260 self.buttonApp.set_title(str(value))
262 self.buttonApp.set_title("")
264 #def row_activated(self, treeview, treepath): #, column):
265 # (model, iter) = self.treeview.get_selection().get_selected()
266 # key = model.get_value(iter, 2)
267 # Create an object that will proxy for a particular remote object.
268 # remote_object = bus.get_object("org.maemo.feedingit", # Connection name
269 # "/org/maemo/feedingit" # Object's path
271 # iface = dbus.Interface(remote_object, 'org.maemo.feedingit')
272 # iface.OpenFeed(key)
274 def show_articles(self):
275 db = sqlite3.connect(CONFIGDIR+self.key+".d/"+self.key+".db")
276 count = db.execute("SELECT count(*) FROM feed WHERE read=0;").fetchone()[0]
278 maxPage = count/self.pageSize
280 if self.pageStatus > maxPage:
281 self.pageStatus = maxPage
282 print self.pageStatus
283 rows = db.execute("SELECT id, title FROM feed WHERE read=0 ORDER BY date DESC LIMIT ? OFFSET ?;", (self.pageSize, self.pageStatus*self.pageSize,) )
284 treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
286 #title = fix_title(row[1][0:32])
287 title = fix_title(row[1])
289 treestore.append((title, id))
290 self.treeview.set_model(treestore)
292 def row_activated(self, treeview, treepath):
297 (model, iter) = self.treeview.get_selection().get_selected()
298 self.key = model.get_value(iter, 2)
299 treeviewcolumn = self.treeview.get_column(1)
300 self.treeview.remove_column(treeviewcolumn)
302 self.buttonApp.set_image(gtk.image_new_from_icon_name('general_back', gtk.ICON_SIZE_BUTTON))
303 self.buttonUp.set_sensitive(True)
304 self.buttonDown.set_sensitive(True)
306 (model, iter) = self.treeview.get_selection().get_selected()
307 id = model.get_value(iter, 2)
308 remote_object = bus.get_object("org.maemo.feedingit", # Connection name
309 "/org/maemo/feedingit" # Object's path
311 iface = dbus.Interface(remote_object, 'org.maemo.feedingit')
314 (model, iter) = self.treeview.get_selection().get_selected()
315 id = model.get_value(iter, 1)
316 # Create an object that will proxy for a particular remote object.
317 remote_object = bus.get_object("org.maemo.feedingit", # Connection name
318 "/org/maemo/feedingit" # Object's path
320 iface = dbus.Interface(remote_object, 'org.maemo.feedingit')
321 iface.OpenArticle(self.key, id)
323 def check_db(self, db):
324 table = db.execute("SELECT sql FROM sqlite_master").fetchone()
325 from string import find
326 if find(table[0], "widget")==-1:
327 db.execute("ALTER TABLE feeds ADD COLUMN widget int;")
328 db.execute("UPDATE feeds SET widget=1;")
331 def no_feeds_available(self, treestore):
332 treestore.append(["No feeds added yet", "", None])
333 treestore.append(["Start Application", "", None])
334 #self.update_label("No feeds added yet")
335 self.treeview.set_model(treestore)
337 def update_list(self, *widget):
338 #listing = Listing(CONFIGDIR)
341 treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
343 #if self.feed_list == {}:
344 if self.initialized == False:
347 db = sqlite3.connect(CONFIGDIR+"feeds.db")
349 self.no_feeds_available(treestore)
352 rows = db.execute("SELECT id, title, unread FROM feeds WHERE widget=1 ORDER BY unread DESC LIMIT 10;")
355 rows = db.execute("SELECT id, title, unread FROM feeds WHERE widget=1 ORDER BY unread DESC LIMIT 10;")
362 treestore.append([name, countUnread, id])
363 self.treeview.set_model(treestore)
364 self.buttonApp.set_image(gtk.image_new_from_icon_name('feedingit', gtk.ICON_SIZE_BUTTON))
368 file = open("/home/user/feedingit_widget.log", "a")
369 traceback.print_exc(file=file)
372 def create_selector(self, choices, setting):
373 #self.pickerDialog = hildon.PickerDialog(self.parent)
374 selector = hildon.TouchSelector(text=True)
377 iter = selector.append_text(str(item))
378 if str(self.autoupdate) == str(item):
379 selector.set_active(0, index)
381 selector.connect("changed", self.selection_changed, setting)
382 #self.pickerDialog.set_selector(selector)
385 def selection_changed(self, selector, button, setting):
386 tmp = selector.get_current_text()
387 if setting == "autoupdate":
388 if tmp == "Disabled":
391 self.autoupdate = tmp
392 elif setting == "sizing":
397 #current_selection = selector.get_current_text()
398 #if current_selection:
399 # self.config[setting] = current_selection
400 #gobject.idle_add(self.updateButton, setting)
403 def create_autoupdate_picker(self):
404 picker = hildon.PickerButton(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
405 selector = self.create_selector(["Disabled", 0.5, 1, 2, 4, 12, 24], "autoupdate")
406 picker.set_selector(selector)
407 picker.set_title("Frequency of updates from the widget")
408 picker.set_text("Setup Feed Auto-updates","Update every %s hours" %str(self.autoupdate) )
409 picker.set_name('HildonButton-finger')
410 picker.set_alignment(0,0,1,1)
411 #self.buttons[setting] = picker
412 #vbox.pack_start(picker, expand=False)
415 def create_size_picker(self):
416 picker = hildon.PickerButton(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
417 selector = self.create_selector(["Small", "Large"], "sizing")
418 picker.set_selector(selector)
419 picker.set_title("Default Size For The Widget")
424 picker.set_text("Widget Size (requires restart)", tmp)
425 picker.set_name('HildonButton-finger')
426 picker.set_alignment(0,0,1,1)
427 #self.buttons[setting] = picker
428 #vbox.pack_start(picker, expand=False)
431 def show_settings(self, widget):
432 if isfile(CONFIGDIR+"feeds.db"):
433 db = sqlite3.connect(CONFIGDIR+"feeds.db")
435 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))
437 self.pannableArea = hildon.PannableArea()
439 #self.treestore_settings = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
440 self.treeview_settings = gtk.TreeView()
442 self.treeview_settings.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
443 hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview_settings, gtk.HILDON_UI_MODE_EDIT)
444 dialog.vbox.pack_start(self.pannableArea)
446 self.treeview_settings.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
447 self.treestore_settings = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
448 self.treeview_settings.set_model(self.treestore_settings)
450 feeds = db.execute("SELECT title, id, widget FROM feeds;")
453 # feed is (id, title)
454 item = self.treestore_settings.append(feed[0:2])
456 self.treeview_settings.get_selection().select_iter(item)
458 self.pannableArea.add(self.treeview_settings)
459 self.pannableArea.show_all()
460 dialog.set_default_size(-1, 600)
462 dialog.action_area.pack_start(self.create_autoupdate_picker())
463 dialog.action_area.pack_start(self.create_size_picker())
466 response = dialog.run()
468 if response == gtk.RESPONSE_ACCEPT:
474 dialog = gtk.Dialog("Please add feeds first", None, gtk.DIALOG_DESTROY_WITH_PARENT | gtk.DIALOG_NO_SEPARATOR, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
475 label = gtk.Label("Please add feeds through the main application")
476 dialog.vbox.pack_start(label)
478 response = dialog.run()
480 #self.treeview_settings.get_selection().select_all()
484 treeselection = self.treeview_settings.get_selection()
485 (model, pathlist) = treeselection.get_selected_rows()
486 db = sqlite3.connect(CONFIGDIR+"feeds.db")
487 db.execute("UPDATE feeds SET widget=0;")
488 for path in pathlist:
489 id = model.get_value(model.get_iter(path),1)
490 db.execute("UPDATE feeds SET widget=1 WHERE id=?;", (id,) )
492 #title = model.get_value(model.get_iter(path),0)
493 #list[model.get_value(model.get_iter(path),1)] = model.get_value(model.get_iter(path),0)
497 bus.add_signal_receiver(self.update_list, dbus_interface="org.marcoz.feedingit",
498 signal_name="ArticleCountUpdated", path="/org/marcoz/feedingit/update")
499 bus.add_signal_receiver(self.update_started, dbus_interface="org.marcoz.feedingit",
500 signal_name="UpdateStarted", path="/org/marcoz/feedingit/update")
501 bus.add_signal_receiver(self.update_finished, dbus_interface="org.marcoz.feedingit",
502 signal_name="UpdateFinished", path="/org/marcoz/feedingit/update")
504 def update_started(self, *widget):
505 self.buttonUpdate.set_image(gtk.image_new_from_icon_name('general_stop', gtk.ICON_SIZE_BUTTON))
506 self.updateStatus = 1
508 def update_finished(self, *widget):
509 self.updateStatus = 0
510 self.buttonUpdate.set_image(gtk.image_new_from_icon_name('general_refresh', gtk.ICON_SIZE_BUTTON))
512 def start_update(self):
514 if self.autoupdate >0:
515 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
516 #from time import localtime, strftime
518 #file.write("Widget: pid:%s ppid:%s time:%s\n" % (os.getpid(), os.getppid(), strftime("%a, %d %b %Y %H:%M:%S +0000", localtime())))
520 remote_object = bus.get_object("org.marcoz.feedingit", # Connection name
521 "/org/marcoz/feedingit/update" # Object's path
523 iface = dbus.Interface(remote_object, 'org.marcoz.feedingit')
528 file = open("/home/user/.feedingit/feedingit_widget.log", "a")
529 traceback.print_exc(file=file)
532 def save_config(self):
533 from os.path import isdir
534 if not isdir(CONFIGDIR):
537 file = open(CONFIGDIR+"widget2", "w")
538 pickle.dump(self.size, file )
539 pickle.dump(self.autoupdate, file)
541 self.setup_autoupdate()
543 def setup_autoupdate(self):
544 if (float(self.autoupdate) > 0):
545 if (not self.autoupdateId==False):
546 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
547 #file.write("Disabling %s\n" % self.autoupdateId)
549 gobject.source_remove(self.autoupdateId)
551 self.autoupdateId = gobject.timeout_add_seconds(int(float(self.autoupdate)*3600), self.start_update)
552 file = open(SOURCE, "w")
553 file.write(str(self.autoupdateId))
555 #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
556 #file.write("Started %s\n" % self.autoupdateId)
559 if (not self.autoupdateId==False):
560 gobject.source_remove(self.autoupdateId)
561 self.autoupdateId=False
564 def load_config(self):
565 if isfile(CONFIGDIR+"widget2"):
566 file = open(CONFIGDIR+"widget2", "r")
567 self.size = pickle.load( file )
568 self.autoupdate = pickle.load( file )
570 self.setup_autoupdate()
571 elif isfile(CONFIGDIR+"widget"):
572 file = open(CONFIGDIR+"widget", "r")
573 feed_list = pickle.load( file )
574 self.autoupdate = pickle.load( file )
576 self.setup_autoupdate()
579 remove(CONFIGDIR+"widget")
581 #self.feed_list = None
584 self.initialized = True
587 hd_plugin_type = FeedingItHomePlugin
589 # The code below is just for testing purposes.
590 # It allows to run the widget as a standalone process.
591 if __name__ == "__main__":
593 gobject.type_register(hd_plugin_type)
594 obj = gobject.new(hd_plugin_type, plugin_id="plugin_id")