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 urllib2 import install_opener, build_opener
51 from socket import setdefaulttimeout
53 setdefaulttimeout(timeout)
56 color_style = gtk.rc_get_style_by_paths(gtk.settings_get_default() , 'GtkButton', 'osso-logical-colors', gtk.Button)
57 unread_color = color_style.lookup_color('ActiveTextColor')
58 read_color = color_style.lookup_color('DefaultTextColor')
61 CONFIGDIR="/home/user/.feedingit/"
62 LOCK = CONFIGDIR + "update.lock"
65 from htmlentitydefs import name2codepoint
68 # Removes HTML or XML character references and entities from a text string.
70 # @param text The HTML (or XML) source text.
71 # @return The plain text, as a Unicode string, if necessary.
72 # http://effbot.org/zone/re-sub.htm#unescape-html
80 return unichr(int(text[3:-1], 16))
82 return unichr(int(text[2:-1]))
88 text = unichr(name2codepoint[text[1:-1]])
91 return text # leave as is
92 return sub("&#?\w+;", fixup, text)
95 class AddWidgetWizard(hildon.WizardDialog):
97 def __init__(self, parent, urlIn, titleIn=None):
99 self.notebook = gtk.Notebook()
101 self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
102 self.nameEntry.set_placeholder("Enter Feed Name")
103 vbox = gtk.VBox(False,10)
104 label = gtk.Label("Enter Feed Name:")
105 vbox.pack_start(label)
106 vbox.pack_start(self.nameEntry)
107 if not titleIn == None:
108 self.nameEntry.set_text(titleIn)
109 self.notebook.append_page(vbox, None)
111 self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
112 self.urlEntry.set_placeholder("Enter a URL")
113 self.urlEntry.set_text(urlIn)
114 self.urlEntry.select_region(0,-1)
116 vbox = gtk.VBox(False,10)
117 label = gtk.Label("Enter Feed URL:")
118 vbox.pack_start(label)
119 vbox.pack_start(self.urlEntry)
120 self.notebook.append_page(vbox, None)
122 labelEnd = gtk.Label("Success")
124 self.notebook.append_page(labelEnd, None)
126 hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
128 # Set a handler for "switch-page" signal
129 #self.notebook.connect("switch_page", self.on_page_switch, self)
131 # Set a function to decide if user can go to next page
132 self.set_forward_page_func(self.some_page_func)
137 return (self.nameEntry.get_text(), self.urlEntry.get_text())
139 def on_page_switch(self, notebook, page, num, dialog):
142 def some_page_func(self, nb, current, userdata):
143 # Validate data for 1st page
145 return len(self.nameEntry.get_text()) != 0
147 # Check the url is not null, and starts with http
148 return ( (len(self.urlEntry.get_text()) != 0) and (self.urlEntry.get_text().lower().startswith("http")) )
154 class Download(Thread):
155 def __init__(self, listing, key, config):
156 Thread.__init__(self)
157 self.listing = listing
162 (use_proxy, proxy) = self.config.getProxy()
163 key_lock = get_lock(self.key)
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)
184 (use_proxy, proxy) = self.config.getProxy()
186 opener = build_opener(proxy)
187 opener.addheaders = [('User-agent', 'Mozilla/5.0 (compatible; Maemo 5;) FeedingIt 0.6.1')]
188 install_opener(opener)
190 opener = build_opener()
191 opener.addheaders = [('User-agent', 'Mozilla/5.0 (compatible; Maemo 5;) FeedingIt 0.6.1')]
192 install_opener(opener)
195 self.set_text("Updating...")
197 self.set_fraction(self.fraction)
200 self.timeout_handler_id = gobject.timeout_add(50, self.update_progress_bar)
202 def update_progress_bar(self):
203 #self.progress_bar.pulse()
204 if activeCount() < 4:
205 x = activeCount() - 1
206 k = len(self.listOfKeys)
207 fin = self.total - k - x
208 fraction = float(fin)/float(self.total) + float(x)/(self.total*2.)
209 #print x, k, fin, fraction
210 self.set_fraction(fraction)
212 if len(self.listOfKeys)>0:
213 self.current = self.current+1
214 key = self.listOfKeys.pop()
215 #if self.single == True:
216 # Check if the feed is being displayed
217 download = Download(self.listing, key, self.config)
220 elif activeCount() > 1:
223 #self.waitingWindow.destroy()
229 self.emit("download-done", "success")
234 class SortList(gtk.Dialog):
235 def __init__(self, parent, listing):
236 gtk.Dialog.__init__(self, "Organizer", parent)
237 self.listing = listing
239 self.vbox2 = gtk.VBox(False, 10)
241 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
242 button.set_label("Move Up")
243 button.connect("clicked", self.buttonUp)
244 self.vbox2.pack_start(button, expand=False, fill=False)
246 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
247 button.set_label("Move Down")
248 button.connect("clicked", self.buttonDown)
249 self.vbox2.pack_start(button, expand=False, fill=False)
251 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
252 button.set_label("Add Feed")
253 button.connect("clicked", self.buttonAdd)
254 self.vbox2.pack_start(button, expand=False, fill=False)
256 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
257 button.set_label("Edit Feed")
258 button.connect("clicked", self.buttonEdit)
259 self.vbox2.pack_start(button, expand=False, fill=False)
261 button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
262 button.set_label("Delete")
263 button.connect("clicked", self.buttonDelete)
264 self.vbox2.pack_start(button, expand=False, fill=False)
266 #button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
267 #button.set_label("Done")
268 #button.connect("clicked", self.buttonDone)
269 #self.vbox.pack_start(button)
270 self.hbox2= gtk.HBox(False, 10)
271 self.pannableArea = hildon.PannableArea()
272 self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
273 self.treeview = gtk.TreeView(self.treestore)
274 self.hbox2.pack_start(self.pannableArea, expand=True)
276 self.hbox2.pack_end(self.vbox2, expand=False)
277 self.set_default_size(-1, 600)
278 self.vbox.pack_start(self.hbox2)
281 #self.connect("destroy", self.buttonDone)
283 def displayFeeds(self):
284 self.treeview.destroy()
285 self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
286 self.treeview = gtk.TreeView()
288 self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
289 hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_EDIT)
291 self.treeview.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
293 self.pannableArea.add(self.treeview)
297 def refreshList(self, selected=None, offset=0):
298 #rect = self.treeview.get_visible_rect()
299 #y = rect.y+rect.height
300 self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
301 for key in self.listing.getListOfFeeds():
302 item = self.treestore.append([self.listing.getFeedTitle(key), key])
305 self.treeview.set_model(self.treestore)
306 if not selected == None:
307 self.treeview.get_selection().select_iter(selectedItem)
308 self.treeview.scroll_to_cell(self.treeview.get_model().get_path(selectedItem))
309 self.pannableArea.show_all()
311 def getSelectedItem(self):
312 (model, iter) = self.treeview.get_selection().get_selected()
315 return model.get_value(iter, 1)
317 def findIndex(self, key):
321 for row in self.treestore:
323 return (before, row.iter)
324 if key == list(row)[0]:
328 return (before, None)
330 def buttonUp(self, button):
331 key = self.getSelectedItem()
333 self.listing.moveUp(key)
334 self.refreshList(key, -10)
336 def buttonDown(self, button):
337 key = self.getSelectedItem()
339 self.listing.moveDown(key)
340 self.refreshList(key, 10)
342 def buttonDelete(self, button):
343 key = self.getSelectedItem()
345 self.listing.removeFeed(key)
348 def buttonEdit(self, button):
349 key = self.getSelectedItem()
351 wizard = AddWidgetWizard(self, self.listing.getFeedUrl(key), self.listing.getFeedTitle(key))
354 (title, url) = wizard.getData()
355 if (not title == '') and (not url == ''):
356 self.listing.editFeed(key, title, url)
360 def buttonDone(self, *args):
363 def buttonAdd(self, button, urlIn="http://"):
364 wizard = AddWidgetWizard(self, urlIn)
367 (title, url) = wizard.getData()
368 if (not title == '') and (not url == ''):
369 self.listing.addFeed(title, url)
374 class DisplayArticle(hildon.StackableWindow):
375 def __init__(self, feed, id, key, config, listing):
376 hildon.StackableWindow.__init__(self)
377 #self.imageDownloader = ImageDownloader()
382 #self.set_title(feed.getTitle(id))
383 self.set_title(self.listing.getFeedTitle(key))
385 self.set_for_removal = False
387 # Init the article display
388 #if self.config.getWebkitSupport():
389 self.view = WebView()
390 #self.view.set_editable(False)
393 # self.view = gtkhtml2.View()
394 # self.document = gtkhtml2.Document()
395 # self.view.set_document(self.document)
396 # self.document.connect("link_clicked", self._signal_link_clicked)
397 self.pannable_article = hildon.PannableArea()
398 self.pannable_article.add(self.view)
399 #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
400 #self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
402 #if self.config.getWebkitSupport():
403 contentLink = self.feed.getContentLink(self.id)
404 self.feed.setEntryRead(self.id)
405 #if key=="ArchivedArticles":
406 self.view.open("file://" + contentLink)
407 self.view.connect("motion-notify-event", lambda w,ev: True)
408 self.view.connect('load-started', self.load_started)
409 self.view.connect('load-finished', self.load_finished)
412 #self.view.load_html_string(self.text, contentLink) # "text/html", "utf-8", self.link)
413 self.view.set_zoom_level(float(config.getArtFontSize())/10.)
415 # if not key == "ArchivedArticles":
416 # Do not download images if the feed is "Archived Articles"
417 # self.document.connect("request-url", self._signal_request_url)
419 # self.document.clear()
420 # self.document.open_stream("text/html")
421 # self.document.write_stream(self.text)
422 # self.document.close_stream()
424 menu = hildon.AppMenu()
425 # Create a button and add it to the menu
426 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
427 button.set_label("Allow Horizontal Scrolling")
428 button.connect("clicked", self.horiz_scrolling_button)
431 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
432 button.set_label("Open in Browser")
433 button.connect("clicked", self._signal_link_clicked, self.feed.getExternalLink(self.id))
436 if key == "ArchivedArticles":
437 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
438 button.set_label("Remove from Archived Articles")
439 button.connect("clicked", self.remove_archive_button)
441 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
442 button.set_label("Add to Archived Articles")
443 button.connect("clicked", self.archive_button)
446 self.set_app_menu(menu)
449 #self.event_box = gtk.EventBox()
450 #self.event_box.add(self.pannable_article)
451 self.add(self.pannable_article)
454 self.pannable_article.show_all()
456 self.destroyId = self.connect("destroy", self.destroyWindow)
458 self.view.connect("button_press_event", self.button_pressed)
459 self.gestureId = self.view.connect("button_release_event", self.button_released)
460 #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
462 def load_started(self, *widget):
463 hildon.hildon_gtk_window_set_progress_indicator(self, 1)
465 def load_finished(self, *widget):
466 hildon.hildon_gtk_window_set_progress_indicator(self, 0)
468 def button_pressed(self, window, event):
469 #print event.x, event.y
470 self.coords = (event.x, event.y)
472 def button_released(self, window, event):
473 x = self.coords[0] - event.x
474 y = self.coords[1] - event.y
476 if (2*abs(y) < abs(x)):
478 self.emit("article-previous", self.id)
480 self.emit("article-next", self.id)
484 #def gesture(self, widget, direction, startx, starty):
485 # if (direction == 3):
486 # self.emit("article-next", self.index)
487 # if (direction == 2):
488 # self.emit("article-previous", self.index)
489 #print startx, starty
490 #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
492 def destroyWindow(self, *args):
493 self.disconnect(self.destroyId)
494 if self.set_for_removal:
495 self.emit("article-deleted", self.id)
497 self.emit("article-closed", self.id)
498 #self.imageDownloader.stopAll()
501 def horiz_scrolling_button(self, *widget):
502 self.pannable_article.disconnect(self.gestureId)
503 self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
505 def archive_button(self, *widget):
506 # Call the listing.addArchivedArticle
507 self.listing.addArchivedArticle(self.key, self.id)
509 def remove_archive_button(self, *widget):
510 self.set_for_removal = True
512 #def reloadArticle(self, *widget):
513 # if threading.activeCount() > 1:
514 # Image thread are still running, come back in a bit
517 # for (stream, imageThread) in self.images:
519 # stream.write(imageThread.data)
524 def _signal_link_clicked(self, object, link):
526 bus = dbus.SessionBus()
527 proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
528 iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
529 iface.open_new_window(link)
531 #def _signal_request_url(self, object, url, stream):
533 # self.imageDownloader.queueImage(url, stream)
534 #imageThread = GetImage(url)
536 #self.images.append((stream, imageThread))
539 class DisplayFeed(hildon.StackableWindow):
540 def __init__(self, listing, feed, title, key, config, updateDbusHandler):
541 hildon.StackableWindow.__init__(self)
542 self.listing = listing
544 self.feedTitle = title
545 self.set_title(title)
548 self.updateDbusHandler = updateDbusHandler
550 self.downloadDialog = False
552 #self.listing.setCurrentlyDisplayedFeed(self.key)
556 menu = hildon.AppMenu()
557 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
558 button.set_label("Update Feed")
559 button.connect("clicked", self.button_update_clicked)
562 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
563 button.set_label("Mark All As Read")
564 button.connect("clicked", self.buttonReadAllClicked)
567 if key=="ArchivedArticles":
568 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
569 button.set_label("Purge Read Articles")
570 button.connect("clicked", self.buttonPurgeArticles)
573 self.set_app_menu(menu)
578 self.connect("destroy", self.destroyWindow)
580 def destroyWindow(self, *args):
581 #self.feed.saveUnread(CONFIGDIR)
582 gobject.idle_add(self.feed.saveUnread, CONFIGDIR)
583 self.listing.updateUnread(self.key, self.feed.getNumberOfUnreadItems())
584 self.emit("feed-closed", self.key)
586 #gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
587 #self.listing.closeCurrentlyDisplayedFeed()
589 def displayFeed(self):
590 self.vboxFeed = gtk.VBox(False, 10)
591 self.pannableFeed = hildon.PannableArea()
592 self.pannableFeed.add_with_viewport(self.vboxFeed)
593 self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
595 for id in self.feed.getIds():
596 title = self.feed.getTitle(id)
598 esc_title = unescape(title).replace("<em>","").replace("</em>","")
599 #title.replace("<em>","").replace("</em>","").replace("&","&").replace("—", "-").replace("’", "'")
600 button = gtk.Button(esc_title)
601 button.set_alignment(0,0)
604 if self.feed.isEntryRead(id):
605 #label.modify_font(FontDescription("sans 16"))
606 label.modify_font(FontDescription(self.config.getReadFont()))
607 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
609 #print self.listing.getFont() + " bold"
610 label.modify_font(FontDescription(self.config.getUnreadFont()))
611 label.modify_fg(gtk.STATE_NORMAL, unread_color)
612 label.set_line_wrap(True)
614 label.set_size_request(self.get_size()[0]-50, -1)
615 button.connect("clicked", self.button_clicked, id)
616 self.buttons[id] = button
618 self.vboxFeed.pack_start(button, expand=False)
620 self.add(self.pannableFeed)
624 self.pannableFeed.destroy()
625 #self.remove(self.pannableFeed)
627 def button_clicked(self, button, index, previous=False, next=False):
628 #newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing, self.config)
629 newDisp = DisplayArticle(self.feed, index, self.key, self.config, self.listing)
630 stack = hildon.WindowStack.get_default()
633 stack.pop_and_push(1, newDisp, tmp)
635 gobject.timeout_add(200, self.destroyArticle, tmp)
640 if type(self.disp).__name__ == "DisplayArticle":
641 gobject.timeout_add(200, self.destroyArticle, self.disp)
648 if self.key == "ArchivedArticles":
649 self.ids.append(self.disp.connect("article-deleted", self.onArticleDeleted))
650 self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
651 self.ids.append(self.disp.connect("article-next", self.nextArticle))
652 self.ids.append(self.disp.connect("article-previous", self.previousArticle))
654 def buttonPurgeArticles(self, *widget):
656 self.feed.purgeReadArticles()
657 self.feed.saveUnread(CONFIGDIR)
658 self.feed.saveFeed(CONFIGDIR)
661 def destroyArticle(self, handle):
662 handle.destroyWindow()
664 def nextArticle(self, object, index):
665 label = self.buttons[index].child
666 label.modify_font(FontDescription(self.config.getReadFont()))
667 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
668 id = self.feed.getNextId(index)
669 self.button_clicked(object, id, next=True)
671 def previousArticle(self, object, index):
672 label = self.buttons[index].child
673 label.modify_font(FontDescription(self.config.getReadFont()))
674 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
675 id = self.feed.getPreviousId(index)
676 self.button_clicked(object, id, previous=True)
678 def onArticleClosed(self, object, index):
679 label = self.buttons[index].child
680 label.modify_font(FontDescription(self.config.getReadFont()))
681 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
682 self.buttons[index].show()
684 def onArticleDeleted(self, object, index):
686 self.feed.removeArticle(index)
687 self.feed.saveUnread(CONFIGDIR)
688 self.feed.saveFeed(CONFIGDIR)
691 def button_update_clicked(self, button):
692 #bar = DownloadBar(self, self.listing, [self.key,], self.config )
693 if not type(self.downloadDialog).__name__=="DownloadBar":
694 self.pannableFeed.destroy()
695 self.vbox = gtk.VBox(False, 10)
696 self.downloadDialog = DownloadBar(self.window, self.listing, [self.key,], self.config, single=True )
697 self.downloadDialog.connect("download-done", self.onDownloadsDone)
698 self.vbox.pack_start(self.downloadDialog, expand=False, fill=False)
702 def onDownloadsDone(self, *widget):
704 self.feed = self.listing.getFeed(self.key)
706 self.updateDbusHandler.ArticleCountUpdated()
708 def buttonReadAllClicked(self, button):
709 for index in self.feed.getIds():
710 self.feed.setEntryRead(index)
711 label = self.buttons[index].child
712 label.modify_font(FontDescription(self.config.getReadFont()))
713 label.modify_fg(gtk.STATE_NORMAL, read_color) # gtk.gdk.color_parse("white"))
714 self.buttons[index].show()
720 self.window = hildon.StackableWindow()
721 self.window.set_title("FeedingIt")
722 hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
723 self.mainVbox = gtk.VBox(False,10)
724 self.pannableListing = gtk.Label("Loading...")
725 self.mainVbox.pack_start(self.pannableListing)
726 self.window.add(self.mainVbox)
727 self.window.show_all()
728 self.config = Config(self.window, CONFIGDIR+"config.ini")
729 gobject.idle_add(self.createWindow)
731 def createWindow(self):
732 self.app_lock = get_lock("app_lock")
733 if self.app_lock == None:
734 self.pannableListing.set_label("Update in progress, please wait.")
735 gobject.timeout_add_seconds(3, self.createWindow)
737 self.listing = Listing(CONFIGDIR)
739 self.downloadDialog = False
740 self.orientation = FremantleRotation("FeedingIt", main_window=self.window, app=self)
741 self.orientation.set_mode(self.config.getOrientation())
743 menu = hildon.AppMenu()
744 # Create a button and add it to the menu
745 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
746 button.set_label("Update All Feeds")
747 button.connect("clicked", self.button_update_clicked, "All")
750 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
751 button.set_label("Mark All As Read")
752 button.connect("clicked", self.button_markAll)
755 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
756 button.set_label("Organize Feeds")
757 button.connect("clicked", self.button_organize_clicked)
760 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
761 button.set_label("Preferences")
762 button.connect("clicked", self.button_preferences_clicked)
765 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
766 button.set_label("Import Feeds")
767 button.connect("clicked", self.button_import_clicked)
770 button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
771 button.set_label("Export Feeds")
772 button.connect("clicked", self.button_export_clicked)
775 self.window.set_app_menu(menu)
778 self.feedWindow = hildon.StackableWindow()
779 self.articleWindow = hildon.StackableWindow()
781 self.displayListing()
782 self.autoupdate = False
783 self.checkAutoUpdate()
784 hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
785 gobject.idle_add(self.enableDbus)
787 def enableDbus(self):
788 self.dbusHandler = ServerObject(self)
789 self.updateDbusHandler = UpdateServerObject(self)
791 def button_markAll(self, button):
792 for key in self.listing.getListOfFeeds():
793 feed = self.listing.getFeed(key)
794 for id in feed.getIds():
795 feed.setEntryRead(id)
796 feed.saveUnread(CONFIGDIR)
797 self.listing.updateUnread(key, feed.getNumberOfUnreadItems())
800 def button_export_clicked(self, button):
801 opml = ExportOpmlData(self.window, self.listing)
803 def button_import_clicked(self, button):
804 opml = GetOpmlData(self.window)
805 feeds = opml.getData()
806 for (title, url) in feeds:
807 self.listing.addFeed(title, url)
808 self.displayListing()
810 def addFeed(self, urlIn="http://"):
811 wizard = AddWidgetWizard(self.window, urlIn)
814 (title, url) = wizard.getData()
815 if (not title == '') and (not url == ''):
816 self.listing.addFeed(title, url)
818 self.displayListing()
820 def button_organize_clicked(self, button):
821 org = SortList(self.window, self.listing)
824 self.listing.saveConfig()
825 self.displayListing()
827 def button_update_clicked(self, button, key):
828 if not type(self.downloadDialog).__name__=="DownloadBar":
829 self.updateDbusHandler.UpdateStarted()
830 self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
831 self.downloadDialog.connect("download-done", self.onDownloadsDone)
832 self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
833 self.mainVbox.show_all()
834 #self.displayListing()
836 def onDownloadsDone(self, *widget):
837 self.downloadDialog.destroy()
838 self.downloadDialog = False
839 #self.displayListing()
841 self.updateDbusHandler.UpdateFinished()
842 self.updateDbusHandler.ArticleCountUpdated()
844 def button_preferences_clicked(self, button):
845 dialog = self.config.createDialog()
846 dialog.connect("destroy", self.prefsClosed)
848 def show_confirmation_note(self, parent, title):
849 note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
851 retcode = gtk.Dialog.run(note)
854 if retcode == gtk.RESPONSE_OK:
859 def displayListing(self):
861 self.mainVbox.remove(self.pannableListing)
864 self.vboxListing = gtk.VBox(False,10)
865 self.pannableListing = hildon.PannableArea()
866 self.pannableListing.add_with_viewport(self.vboxListing)
869 list = self.listing.getListOfFeeds()[:]
871 icon_theme = gtk.icon_theme_get_default()
873 #button = gtk.Button(item)
874 unreadItems = self.listing.getFeedNumberOfUnreadItems(key)
875 button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
876 hildon.BUTTON_ARRANGEMENT_VERTICAL)
877 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / "
878 + str(unreadItems) + " Unread Items")
879 icon = self.listing.getFavicon(key)
882 pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(icon, 32, 32)
884 pixbuf = icon_theme.load_icon("feedingit", 32, gtk.ICON_LOOKUP_USE_BUILTIN )
886 image.set_from_pixbuf(pixbuf)
887 button.set_image(image)
888 button.set_image_position(gtk.POS_LEFT)
889 button.set_alignment(0,0,1,1)
890 button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
891 self.vboxListing.pack_start(button, expand=False)
892 self.buttons[key] = button
894 self.mainVbox.pack_start(self.pannableListing)
895 self.window.show_all()
896 gobject.idle_add(self.refreshList)
898 def refreshList(self):
899 for key in self.listing.getListOfFeeds():
900 if self.buttons.has_key(key):
901 button = self.buttons[key]
902 unreadItems = self.listing.getFeedNumberOfUnreadItems(key)
903 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / "
904 + str(unreadItems) + " Unread Items")
905 label = button.child.child.get_children()[1].get_children()[1]
907 label.modify_fg(gtk.STATE_NORMAL, read_color)
909 label.modify_fg(gtk.STATE_NORMAL, unread_color)
911 self.displayListing()
914 def buttonFeedClicked(widget, button, self, window, key):
918 # If feed_lock doesn't exist, we can open the feed, else we do nothing
919 self.feed_lock = get_lock(key)
920 self.disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config, self.updateDbusHandler)
921 self.disp.connect("feed-closed", self.onFeedClosed)
923 def onFeedClosed(self, object, key):
924 #self.listing.saveConfig()
926 gobject.idle_add(self.onFeedClosedTimeout)
928 #self.updateDbusHandler.ArticleCountUpdated()
930 def onFeedClosedTimeout(self):
931 self.listing.saveConfig()
933 self.updateDbusHandler.ArticleCountUpdated()
936 self.window.connect("destroy", gtk.main_quit)
938 self.listing.saveConfig()
941 def prefsClosed(self, *widget):
942 self.orientation.set_mode(self.config.getOrientation())
943 self.checkAutoUpdate()
945 def checkAutoUpdate(self, *widget):
946 interval = int(self.config.getUpdateInterval()*3600000)
947 if self.config.isAutoUpdateEnabled():
948 if self.autoupdate == False:
949 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
950 self.autoupdate = interval
951 elif not self.autoupdate == interval:
952 # If auto-update is enabled, but not at the right frequency
953 gobject.source_remove(self.autoupdateId)
954 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
955 self.autoupdate = interval
957 if not self.autoupdate == False:
958 gobject.source_remove(self.autoupdateId)
959 self.autoupdate = False
961 def automaticUpdate(self, *widget):
962 # Need to check for internet connection
963 # If no internet connection, try again in 10 minutes:
964 # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
965 file = open("/home/user/.feedingit/feedingit_widget.log", "a")
966 from time import localtime, strftime
967 file.write("App: %s\n" % strftime("%a, %d %b %Y %H:%M:%S +0000", localtime()))
969 self.button_update_clicked(None, None)
972 def stopUpdate(self):
973 # Not implemented in the app (see update_feeds.py)
975 self.downloadDialog.listOfKeys = []
981 for key in self.listing.getListOfFeeds():
982 if self.listing.getFeedNumberOfUnreadItems(key) > 0:
983 status += self.listing.getFeedTitle(key) + ": \t" + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items\n"
985 status = "No unread items"
988 if __name__ == "__main__":
989 gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
990 gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
991 gobject.signal_new("article-deleted", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
992 gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
993 gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
994 gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
995 gobject.threads_init()
996 if not isdir(CONFIGDIR):
1000 print "Error: Can't create configuration directory"
1001 from sys import exit