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 # ============================================================================
27 from pango import FontDescription
31 from webkit import WebView
36 from os.path import isfile, isdir, exists
37 from os import mkdir, remove, stat
39 from portrait import FremantleRotation
40 from threading import Thread, activeCount
41 from feedingitdbus import ServerObject
42 from updatedbus import UpdateServerObject, get_lock
43 from config import Config
44 from cgi import escape
46 from rss import Listing
47 from opml import GetOpmlData, ExportOpmlData
49 from socket import setdefaulttimeout
51 setdefaulttimeout(timeout)
54 color_style = gtk.rc_get_style_by_paths(gtk.settings_get_default() , 'GtkButton', 'osso-logical-colors', gtk.Button)
55 unread_color = color_style.lookup_color('ActiveTextColor')
56 read_color = color_style.lookup_color('DefaultTextColor')
59 CONFIGDIR="/home/user/.feedingit/"
60 LOCK = CONFIGDIR + "update.lock"
63 from htmlentitydefs import name2codepoint
66 # Removes HTML or XML character references and entities from a text string.
68 # @param text The HTML (or XML) source text.
69 # @return The plain text, as a Unicode string, if necessary.
70 # http://effbot.org/zone/re-sub.htm#unescape-html
78 return unichr(int(text[3:-1], 16))
80 return unichr(int(text[2:-1]))
86 text = unichr(name2codepoint[text[1:-1]])
89 return text # leave as is
90 return sub("&#?\w+;", fixup, text)
93 class AddWidgetWizard(hildon.WizardDialog):
95 def __init__(self, parent, urlIn, titleIn=None):
97 self.notebook = gtk.Notebook()
99 self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
100 self.nameEntry.set_placeholder("Enter Feed Name")
101 vbox = gtk.VBox(False,10)
102 label = gtk.Label("Enter Feed Name:")
103 vbox.pack_start(label)
104 vbox.pack_start(self.nameEntry)
105 if not titleIn == None:
106 self.nameEntry.set_text(titleIn)
107 self.notebook.append_page(vbox, None)
109 self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
110 self.urlEntry.set_placeholder("Enter a URL")
111 self.urlEntry.set_text(urlIn)
112 self.urlEntry.select_region(0,-1)
114 vbox = gtk.VBox(False,10)
115 label = gtk.Label("Enter Feed URL:")
116 vbox.pack_start(label)
117 vbox.pack_start(self.urlEntry)
118 self.notebook.append_page(vbox, None)
120 labelEnd = gtk.Label("Success")
122 self.notebook.append_page(labelEnd, None)
124 hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
126 # Set a handler for "switch-page" signal
127 #self.notebook.connect("switch_page", self.on_page_switch, self)
129 # Set a function to decide if user can go to next page
130 self.set_forward_page_func(self.some_page_func)
135 return (self.nameEntry.get_text(), self.urlEntry.get_text())
137 def on_page_switch(self, notebook, page, num, dialog):
140 def some_page_func(self, nb, current, userdata):
141 # Validate data for 1st page
143 return len(self.nameEntry.get_text()) != 0
145 # Check the url is not null, and starts with http
146 return ( (len(self.urlEntry.get_text()) != 0) and (self.urlEntry.get_text().lower().startswith("http")) )
152 class Download(Thread):
153 def __init__(self, listing, key, config):
154 Thread.__init__(self)
155 self.listing = listing
160 (use_proxy, proxy) = self.config.getProxy()
161 key_lock = get_lock(self.key)
164 from urllib2 import install_opener, build_opener
165 install_opener(build_opener(proxy))
166 self.listing.updateFeed(self.key, self.config.getExpiry(), proxy=proxy, imageCache=self.config.getImageCache() )
168 self.listing.updateFeed(self.key, self.config.getExpiry(), imageCache=self.config.getImageCache() )
172 class DownloadBar(gtk.ProgressBar):
173 def __init__(self, parent, listing, listOfKeys, config, single=False):
175 update_lock = get_lock("update_lock")
176 if update_lock != None:
177 gtk.ProgressBar.__init__(self)
178 self.listOfKeys = listOfKeys[:]
179 self.listing = listing
180 self.total = len(self.listOfKeys)
186 self.set_text("Updating...")
188 self.set_fraction(self.fraction)
191 self.timeout_handler_id = gobject.timeout_add(50, self.update_progress_bar)
193 def update_progress_bar(self):
194 #self.progress_bar.pulse()
195 if activeCount() < 4:
196 x = activeCount() - 1
197 k = len(self.listOfKeys)
198 fin = self.total - k - x
199 fraction = float(fin)/float(self.total) + float(x)/(self.total*2.)
200 #print x, k, fin, fraction
201 self.set_fraction(fraction)
203 if len(self.listOfKeys)>0:
204 self.current = self.current+1
205 key = self.listOfKeys.pop()
206 #if self.single == True:
207 # Check if the feed is being displayed
208 download = Download(self.listing, key, self.config)
211 elif activeCount() > 1:
214 #self.waitingWindow.destroy()
220 self.emit("download-done", "success")
225 class SortList(gtk.Dialog):
226 def __init__(self, parent, listing):
227 gtk.Dialog.__init__(self, "Organizer", parent)
228 self.listing = listing
230 self.vbox2 = gtk.VBox(False, 10)
232 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
233 button.set_label("Move Up")
234 button.connect("clicked", self.buttonUp)
235 self.vbox2.pack_start(button, expand=False, fill=False)
237 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
238 button.set_label("Move Down")
239 button.connect("clicked", self.buttonDown)
240 self.vbox2.pack_start(button, expand=False, fill=False)
242 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
243 button.set_label("Add Feed")
244 button.connect("clicked", self.buttonAdd)
245 self.vbox2.pack_start(button, expand=False, fill=False)
247 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
248 button.set_label("Edit Feed")
249 button.connect("clicked", self.buttonEdit)
250 self.vbox2.pack_start(button, expand=False, fill=False)
252 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
253 button.set_label("Delete")
254 button.connect("clicked", self.buttonDelete)
255 self.vbox2.pack_start(button, expand=False, fill=False)
257 #button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
258 #button.set_label("Done")
259 #button.connect("clicked", self.buttonDone)
260 #self.vbox.pack_start(button)
261 self.hbox2= gtk.HBox(False, 10)
262 self.pannableArea = hildon.PannableArea()
263 self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
264 self.treeview = gtk.TreeView(self.treestore)
265 self.hbox2.pack_start(self.pannableArea, expand=True)
267 self.hbox2.pack_end(self.vbox2, expand=False)
268 self.set_default_size(-1, 600)
269 self.vbox.pack_start(self.hbox2)
272 #self.connect("destroy", self.buttonDone)
274 def displayFeeds(self):
275 self.treeview.destroy()
276 self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
277 self.treeview = gtk.TreeView()
279 self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
280 hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_EDIT)
282 self.treeview.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
284 self.pannableArea.add(self.treeview)
288 def refreshList(self, selected=None, offset=0):
289 #rect = self.treeview.get_visible_rect()
290 #y = rect.y+rect.height
291 self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
292 for key in self.listing.getListOfFeeds():
293 item = self.treestore.append([self.listing.getFeedTitle(key), key])
296 self.treeview.set_model(self.treestore)
297 if not selected == None:
298 self.treeview.get_selection().select_iter(selectedItem)
299 self.treeview.scroll_to_cell(self.treeview.get_model().get_path(selectedItem))
300 self.pannableArea.show_all()
302 def getSelectedItem(self):
303 (model, iter) = self.treeview.get_selection().get_selected()
306 return model.get_value(iter, 1)
308 def findIndex(self, key):
312 for row in self.treestore:
314 return (before, row.iter)
315 if key == list(row)[0]:
319 return (before, None)
321 def buttonUp(self, button):
322 key = self.getSelectedItem()
324 self.listing.moveUp(key)
325 self.refreshList(key, -10)
327 def buttonDown(self, button):
328 key = self.getSelectedItem()
330 self.listing.moveDown(key)
331 self.refreshList(key, 10)
333 def buttonDelete(self, button):
334 key = self.getSelectedItem()
336 self.listing.removeFeed(key)
339 def buttonEdit(self, button):
340 key = self.getSelectedItem()
342 wizard = AddWidgetWizard(self, self.listing.getFeedUrl(key), self.listing.getFeedTitle(key))
345 (title, url) = wizard.getData()
346 if (not title == '') and (not url == ''):
347 self.listing.editFeed(key, title, url)
351 def buttonDone(self, *args):
354 def buttonAdd(self, button, urlIn="http://"):
355 wizard = AddWidgetWizard(self, urlIn)
358 (title, url) = wizard.getData()
359 if (not title == '') and (not url == ''):
360 self.listing.addFeed(title, url)
365 class DisplayArticle(hildon.StackableWindow):
366 def __init__(self, feed, id, key, config, listing):
367 hildon.StackableWindow.__init__(self)
368 #self.imageDownloader = ImageDownloader()
373 #self.set_title(feed.getTitle(id))
374 self.set_title(self.listing.getFeedTitle(key))
377 # Init the article display
378 #if self.config.getWebkitSupport():
379 self.view = WebView()
380 #self.view.set_editable(False)
383 # self.view = gtkhtml2.View()
384 # self.document = gtkhtml2.Document()
385 # self.view.set_document(self.document)
386 # self.document.connect("link_clicked", self._signal_link_clicked)
387 self.pannable_article = hildon.PannableArea()
388 self.pannable_article.add(self.view)
389 #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
390 #self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
392 #if self.config.getWebkitSupport():
393 contentLink = self.feed.getContentLink(self.id)
394 self.feed.setEntryRead(self.id)
395 #if key=="ArchivedArticles":
396 self.view.open("file://" + contentLink)
397 self.view.connect("motion-notify-event", lambda w,ev: True)
398 self.view.connect('load-started', self.load_started)
399 self.view.connect('load-finished', self.load_finished)
402 #self.view.load_html_string(self.text, contentLink) # "text/html", "utf-8", self.link)
403 self.view.set_zoom_level(float(config.getArtFontSize())/10.)
405 # if not key == "ArchivedArticles":
406 # Do not download images if the feed is "Archived Articles"
407 # self.document.connect("request-url", self._signal_request_url)
409 # self.document.clear()
410 # self.document.open_stream("text/html")
411 # self.document.write_stream(self.text)
412 # self.document.close_stream()
414 menu = hildon.AppMenu()
415 # Create a button and add it to the menu
416 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
417 button.set_label("Allow Horizontal Scrolling")
418 button.connect("clicked", self.horiz_scrolling_button)
421 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
422 button.set_label("Open in Browser")
423 button.connect("clicked", self._signal_link_clicked, self.feed.getExternalLink(self.id))
426 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
427 button.set_label("Add to Archived Articles")
428 button.connect("clicked", self.archive_button)
431 self.set_app_menu(menu)
434 #self.event_box = gtk.EventBox()
435 #self.event_box.add(self.pannable_article)
436 self.add(self.pannable_article)
439 self.pannable_article.show_all()
441 self.destroyId = self.connect("destroy", self.destroyWindow)
443 self.view.connect("button_press_event", self.button_pressed)
444 self.gestureId = self.view.connect("button_release_event", self.button_released)
445 #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
447 def load_started(self, *widget):
448 hildon.hildon_gtk_window_set_progress_indicator(self, 1)
450 def load_finished(self, *widget):
451 hildon.hildon_gtk_window_set_progress_indicator(self, 0)
453 def button_pressed(self, window, event):
454 #print event.x, event.y
455 self.coords = (event.x, event.y)
457 def button_released(self, window, event):
458 x = self.coords[0] - event.x
459 y = self.coords[1] - event.y
461 if (2*abs(y) < abs(x)):
463 self.emit("article-previous", self.id)
465 self.emit("article-next", self.id)
469 #def gesture(self, widget, direction, startx, starty):
470 # if (direction == 3):
471 # self.emit("article-next", self.index)
472 # if (direction == 2):
473 # self.emit("article-previous", self.index)
474 #print startx, starty
475 #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
477 def destroyWindow(self, *args):
478 self.disconnect(self.destroyId)
479 self.emit("article-closed", self.id)
480 #self.imageDownloader.stopAll()
483 def horiz_scrolling_button(self, *widget):
484 self.pannable_article.disconnect(self.gestureId)
485 self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
487 def archive_button(self, *widget):
488 # Call the listing.addArchivedArticle
489 self.listing.addArchivedArticle(self.key, self.id)
491 #def reloadArticle(self, *widget):
492 # if threading.activeCount() > 1:
493 # Image thread are still running, come back in a bit
496 # for (stream, imageThread) in self.images:
498 # stream.write(imageThread.data)
503 def _signal_link_clicked(self, object, link):
505 bus = dbus.SessionBus()
506 proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
507 iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
508 iface.open_new_window(link)
510 #def _signal_request_url(self, object, url, stream):
512 # self.imageDownloader.queueImage(url, stream)
513 #imageThread = GetImage(url)
515 #self.images.append((stream, imageThread))
518 class DisplayFeed(hildon.StackableWindow):
519 def __init__(self, listing, feed, title, key, config):
520 hildon.StackableWindow.__init__(self)
521 self.listing = listing
523 self.feedTitle = title
524 self.set_title(title)
528 self.downloadDialog = False
530 self.listing.setCurrentlyDisplayedFeed(self.key)
534 menu = hildon.AppMenu()
535 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
536 button.set_label("Update Feed")
537 button.connect("clicked", self.button_update_clicked)
540 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
541 button.set_label("Mark All As Read")
542 button.connect("clicked", self.buttonReadAllClicked)
544 self.set_app_menu(menu)
549 self.connect("destroy", self.destroyWindow)
551 def destroyWindow(self, *args):
552 self.feed.saveUnread(CONFIGDIR)
553 self.listing.updateUnread(self.key, self.feed.getNumberOfUnreadItems())
554 self.emit("feed-closed", self.key)
556 #gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
557 self.listing.closeCurrentlyDisplayedFeed()
559 def displayFeed(self):
560 self.vboxFeed = gtk.VBox(False, 10)
561 self.pannableFeed = hildon.PannableArea()
562 self.pannableFeed.add_with_viewport(self.vboxFeed)
563 self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
565 for id in self.feed.getIds():
566 title = self.feed.getTitle(id)
567 esc_title = unescape(title)
568 #title.replace("<em>","").replace("</em>","").replace("&","&").replace("—", "-").replace("’", "'")
569 button = gtk.Button(esc_title)
570 button.set_alignment(0,0)
572 if self.feed.isEntryRead(id):
573 #label.modify_font(FontDescription("sans 16"))
574 label.modify_font(FontDescription(self.config.getReadFont()))
575 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
577 #print self.listing.getFont() + " bold"
578 label.modify_font(FontDescription(self.config.getUnreadFont()))
579 label.modify_fg(gtk.STATE_NORMAL, unread_color)
580 label.set_line_wrap(True)
582 label.set_size_request(self.get_size()[0]-50, -1)
583 button.connect("clicked", self.button_clicked, id)
584 self.buttons[id] = button
586 self.vboxFeed.pack_start(button, expand=False)
588 self.add(self.pannableFeed)
592 self.pannableFeed.destroy()
593 #self.remove(self.pannableFeed)
595 def button_clicked(self, button, index, previous=False, next=False):
596 #newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing, self.config)
597 newDisp = DisplayArticle(self.feed, index, self.key, self.config, self.listing)
598 stack = hildon.WindowStack.get_default()
601 stack.pop_and_push(1, newDisp, tmp)
603 gobject.timeout_add(200, self.destroyArticle, tmp)
608 if type(self.disp).__name__ == "DisplayArticle":
609 gobject.timeout_add(200, self.destroyArticle, self.disp)
616 self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
617 self.ids.append(self.disp.connect("article-next", self.nextArticle))
618 self.ids.append(self.disp.connect("article-previous", self.previousArticle))
620 def destroyArticle(self, handle):
621 handle.destroyWindow()
623 def nextArticle(self, object, index):
624 label = self.buttons[index].child
625 label.modify_font(FontDescription(self.config.getReadFont()))
626 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
627 id = self.feed.getNextId(index)
628 self.button_clicked(object, id, next=True)
630 def previousArticle(self, object, index):
631 label = self.buttons[index].child
632 label.modify_font(FontDescription(self.config.getReadFont()))
633 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
634 id = self.feed.getPreviousId(index)
635 self.button_clicked(object, id, previous=True)
637 def onArticleClosed(self, object, index):
638 label = self.buttons[index].child
639 label.modify_font(FontDescription(self.config.getReadFont()))
640 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
641 self.buttons[index].show()
643 def button_update_clicked(self, button):
644 #bar = DownloadBar(self, self.listing, [self.key,], self.config )
645 if not type(self.downloadDialog).__name__=="DownloadBar":
646 self.pannableFeed.destroy()
647 self.vbox = gtk.VBox(False, 10)
648 self.downloadDialog = DownloadBar(self.window, self.listing, [self.key,], self.config, single=True )
649 self.downloadDialog.connect("download-done", self.onDownloadsDone)
650 self.vbox.pack_start(self.downloadDialog, expand=False, fill=False)
654 def onDownloadsDone(self, *widget):
656 self.feed = self.listing.getFeed(self.key)
658 self.updateDbusHandler.ArticleCountUpdated()
660 def buttonReadAllClicked(self, button):
661 for index in self.feed.getIds():
662 self.feed.setEntryRead(index)
663 label = self.buttons[index].child
664 label.modify_font(FontDescription(self.config.getReadFont()))
665 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
666 self.buttons[index].show()
672 self.window = hildon.StackableWindow()
673 self.window.set_title("FeedingIt")
674 hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
675 self.mainVbox = gtk.VBox(False,10)
676 self.pannableListing = gtk.Label("Loading...")
677 self.mainVbox.pack_start(self.pannableListing)
678 self.window.add(self.mainVbox)
679 self.window.show_all()
680 self.config = Config(self.window, CONFIGDIR+"config.ini")
681 gobject.idle_add(self.createWindow)
683 def createWindow(self):
684 self.app_lock = get_lock("app_lock")
685 if self.app_lock == None:
686 self.pannableListing.set_label("Update in progress, please wait.")
687 gobject.timeout_add_seconds(3, self.createWindow)
689 self.listing = Listing(CONFIGDIR)
691 self.downloadDialog = False
692 self.orientation = FremantleRotation("FeedingIt", main_window=self.window, app=self)
693 self.orientation.set_mode(self.config.getOrientation())
695 menu = hildon.AppMenu()
696 # Create a button and add it to the menu
697 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
698 button.set_label("Update All Feeds")
699 button.connect("clicked", self.button_update_clicked, "All")
702 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
703 button.set_label("Mark All As Read")
704 button.connect("clicked", self.button_markAll)
707 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
708 button.set_label("Organize Feeds")
709 button.connect("clicked", self.button_organize_clicked)
712 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
713 button.set_label("Preferences")
714 button.connect("clicked", self.button_preferences_clicked)
717 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
718 button.set_label("Import Feeds")
719 button.connect("clicked", self.button_import_clicked)
722 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
723 button.set_label("Export Feeds")
724 button.connect("clicked", self.button_export_clicked)
727 self.window.set_app_menu(menu)
730 self.feedWindow = hildon.StackableWindow()
731 self.articleWindow = hildon.StackableWindow()
733 self.displayListing()
734 self.autoupdate = False
735 self.checkAutoUpdate()
736 hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
737 gobject.idle_add(self.enableDbus)
739 def enableDbus(self):
740 self.dbusHandler = ServerObject(self)
741 self.updateDbusHandler = UpdateServerObject(self)
743 def button_markAll(self, button):
744 for key in self.listing.getListOfFeeds():
745 feed = self.listing.getFeed(key)
746 for id in feed.getIds():
747 feed.setEntryRead(id)
748 feed.saveUnread(CONFIGDIR)
749 self.listing.updateUnread(key, feed.getNumberOfUnreadItems())
752 def button_export_clicked(self, button):
753 opml = ExportOpmlData(self.window, self.listing)
755 def button_import_clicked(self, button):
756 opml = GetOpmlData(self.window)
757 feeds = opml.getData()
758 for (title, url) in feeds:
759 self.listing.addFeed(title, url)
760 self.displayListing()
762 def addFeed(self, urlIn="http://"):
763 wizard = AddWidgetWizard(self.window, urlIn)
766 (title, url) = wizard.getData()
767 if (not title == '') and (not url == ''):
768 self.listing.addFeed(title, url)
770 self.displayListing()
772 def button_organize_clicked(self, button):
773 org = SortList(self.window, self.listing)
776 self.listing.saveConfig()
777 self.displayListing()
779 def button_update_clicked(self, button, key):
780 if not type(self.downloadDialog).__name__=="DownloadBar":
781 self.updateDbusHandler.UpdateStarted()
782 self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
783 self.downloadDialog.connect("download-done", self.onDownloadsDone)
784 self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
785 self.mainVbox.show_all()
786 #self.displayListing()
788 def onDownloadsDone(self, *widget):
789 self.downloadDialog.destroy()
790 self.downloadDialog = False
791 #self.displayListing()
793 self.updateDbusHandler.UpdateFinished()
794 self.updateDbusHandler.ArticleCountUpdated()
796 def button_preferences_clicked(self, button):
797 dialog = self.config.createDialog()
798 dialog.connect("destroy", self.prefsClosed)
800 def show_confirmation_note(self, parent, title):
801 note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
803 retcode = gtk.Dialog.run(note)
806 if retcode == gtk.RESPONSE_OK:
811 def displayListing(self):
813 self.mainVbox.remove(self.pannableListing)
816 self.vboxListing = gtk.VBox(False,10)
817 self.pannableListing = hildon.PannableArea()
818 self.pannableListing.add_with_viewport(self.vboxListing)
821 list = self.listing.getListOfFeeds()[:]
824 #button = gtk.Button(item)
825 unreadItems = self.listing.getFeedNumberOfUnreadItems(key)
826 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
827 hildon.BUTTON_ARRANGEMENT_VERTICAL)
828 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / "
829 + str(unreadItems) + " Unread Items")
830 button.set_alignment(0,0,1,1)
831 button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
832 self.vboxListing.pack_start(button, expand=False)
833 self.buttons[key] = button
835 self.mainVbox.pack_start(self.pannableListing)
836 self.window.show_all()
837 gobject.idle_add(self.refreshList)
839 def refreshList(self):
840 for key in self.listing.getListOfFeeds():
841 if self.buttons.has_key(key):
842 button = self.buttons[key]
843 unreadItems = self.listing.getFeedNumberOfUnreadItems(key)
844 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / "
845 + str(unreadItems) + " Unread Items")
846 label = button.child.child.get_children()[0].get_children()[1]
848 label.modify_fg(gtk.STATE_NORMAL, read_color)
850 label.modify_fg(gtk.STATE_NORMAL, unread_color)
852 self.displayListing()
855 def buttonFeedClicked(widget, button, self, window, key):
859 # If feed_lock doesn't exist, we can open the feed, else we do nothing
860 self.feed_lock = get_lock(key)
861 self.disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
862 self.disp.connect("feed-closed", self.onFeedClosed)
864 def onFeedClosed(self, object, key):
865 self.listing.saveConfig()
868 self.updateDbusHandler.ArticleCountUpdated()
871 self.window.connect("destroy", gtk.main_quit)
873 self.listing.saveConfig()
876 def prefsClosed(self, *widget):
877 self.orientation.set_mode(self.config.getOrientation())
878 self.checkAutoUpdate()
880 def checkAutoUpdate(self, *widget):
881 interval = int(self.config.getUpdateInterval()*3600000)
882 if self.config.isAutoUpdateEnabled():
883 if self.autoupdate == False:
884 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
885 self.autoupdate = interval
886 elif not self.autoupdate == interval:
887 # If auto-update is enabled, but not at the right frequency
888 gobject.source_remove(self.autoupdateId)
889 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
890 self.autoupdate = interval
892 if not self.autoupdate == False:
893 gobject.source_remove(self.autoupdateId)
894 self.autoupdate = False
896 def automaticUpdate(self, *widget):
897 # Need to check for internet connection
898 # If no internet connection, try again in 10 minutes:
899 # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
900 self.button_update_clicked(None, None)
903 def stopUpdate(self):
904 # Not implemented in the app (see update_feeds.py)
906 self.downloadDialog.listOfKeys = []
912 for key in self.listing.getListOfFeeds():
913 if self.listing.getFeedNumberOfUnreadItems(key) > 0:
914 status += self.listing.getFeedTitle(key) + ": \t" + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items\n"
916 status = "No unread items"
919 if __name__ == "__main__":
920 gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
921 gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
922 gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
923 gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
924 gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
925 gobject.threads_init()
926 if not isdir(CONFIGDIR):
930 print "Error: Can't create configuration directory"