0.5.2-3: fix for unread color from theme. Fix for mark all as read button.
[feedingit] / src / FeedingIt.py
1 #!/usr/bin/env python2.5
2
3
4 # Copyright (c) 2007-2008 INdT.
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 #  This program is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #  GNU Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public License
16 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 # ============================================================================
20 # Name        : FeedingIt.py
21 # Author      : Yves Marcoz
22 # Version     : 0.5.2
23 # Description : Simple RSS Reader
24 # ============================================================================
25
26 import gtk
27 import feedparser
28 import pango
29 import hildon
30 #import gtkhtml2
31 try:
32     import webkit
33     has_webkit=True
34 except:
35     import gtkhtml2
36     has_webkit=False
37 import time
38 import dbus
39 import pickle
40 from os.path import isfile, isdir
41 from os import mkdir
42 import sys   
43 import urllib2
44 import gobject
45 from portrait import FremantleRotation
46 import threading
47 import thread
48 from feedingitdbus import ServerObject
49 from config import Config
50
51 from rss import *
52 from opml import GetOpmlData, ExportOpmlData
53    
54 import socket
55 timeout = 5
56 socket.setdefaulttimeout(timeout)
57
58 color_style = gtk.rc_get_style_by_paths(gtk.settings_get_default() , 'GtkButton', 'osso-logical-colors', gtk.Button)
59 fg_colog = color_style.lookup_color('ActiveTextColor')
60 del color_style
61
62 CONFIGDIR="/home/user/.feedingit/"
63
64 class AddWidgetWizard(hildon.WizardDialog):
65     
66     def __init__(self, parent, urlIn, titleIn=None):
67         # Create a Notebook
68         self.notebook = gtk.Notebook()
69
70         self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
71         self.nameEntry.set_placeholder("Enter Feed Name")
72         vbox = gtk.VBox(False,10)
73         label = gtk.Label("Enter Feed Name:")
74         vbox.pack_start(label)
75         vbox.pack_start(self.nameEntry)
76         if not titleIn == None:
77             self.nameEntry.set_text(titleIn)
78         self.notebook.append_page(vbox, None)
79         
80         self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
81         self.urlEntry.set_placeholder("Enter a URL")
82         self.urlEntry.set_text(urlIn)
83         self.urlEntry.select_region(0,-1)
84         
85         vbox = gtk.VBox(False,10)
86         label = gtk.Label("Enter Feed URL:")
87         vbox.pack_start(label)
88         vbox.pack_start(self.urlEntry)
89         self.notebook.append_page(vbox, None)
90
91         labelEnd = gtk.Label("Success")
92         
93         self.notebook.append_page(labelEnd, None)      
94
95         hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
96    
97         # Set a handler for "switch-page" signal
98         #self.notebook.connect("switch_page", self.on_page_switch, self)
99    
100         # Set a function to decide if user can go to next page
101         self.set_forward_page_func(self.some_page_func)
102    
103         self.show_all()
104         
105     def getData(self):
106         return (self.nameEntry.get_text(), self.urlEntry.get_text())
107         
108     def on_page_switch(self, notebook, page, num, dialog):
109         return True
110    
111     def some_page_func(self, nb, current, userdata):
112         # Validate data for 1st page
113         if current == 0:
114             return len(self.nameEntry.get_text()) != 0
115         elif current == 1:
116             # Check the url is not null, and starts with http
117             return ( (len(self.urlEntry.get_text()) != 0) and (self.urlEntry.get_text().lower().startswith("http")) )
118         elif current != 2:
119             return False
120         else:
121             return True
122
123 class GetImage(threading.Thread):
124     def __init__(self, url, stream):
125         threading.Thread.__init__(self)
126         self.url = url
127         self.stream = stream
128     
129     def run(self):
130         f = urllib2.urlopen(self.url)
131         data = f.read()
132         f.close()
133         self.stream.write(data)
134         self.stream.close()
135
136 class ImageDownloader():
137     def __init__(self):
138         self.images = []
139         self.downloading = False
140         
141     def queueImage(self, url, stream):
142         self.images.append((url, stream))
143         if not self.downloading:
144             self.downloading = True
145             gobject.timeout_add(50, self.checkQueue)
146         
147     def checkQueue(self):
148         for i in range(4-threading.activeCount()):
149             if len(self.images) > 0:
150                 (url, stream) = self.images.pop() 
151                 GetImage(url, stream).start()
152         if len(self.images)>0:
153             gobject.timeout_add(200, self.checkQueue)
154         else:
155             self.downloading=False
156             
157     def stopAll(self):
158         self.images = []
159         
160         
161 class Download(threading.Thread):
162     def __init__(self, listing, key, config):
163         threading.Thread.__init__(self)
164         self.listing = listing
165         self.key = key
166         self.config = config
167         
168     def run (self):
169         (use_proxy, proxy) = self.config.getProxy()
170         if use_proxy:
171             self.listing.updateFeed(self.key, self.config.getExpiry(), proxy=proxy)
172         else:
173             self.listing.updateFeed(self.key, self.config.getExpiry())
174
175         
176 class DownloadBar(gtk.ProgressBar):
177     def __init__(self, parent, listing, listOfKeys, config, single=False):
178         gtk.ProgressBar.__init__(self)
179         self.listOfKeys = listOfKeys[:]
180         self.listing = listing
181         self.total = len(self.listOfKeys)
182         self.config = config
183         self.current = 0
184         self.single = single
185         
186         if self.total>0:
187             #self.progress = gtk.ProgressBar()
188             #self.waitingWindow = hildon.Note("cancel", parent, "Downloading",
189             #                     progressbar=self.progress)
190             self.set_text("Updating...")
191             self.fraction = 0
192             self.set_fraction(self.fraction)
193             self.show_all()
194             # Create a timeout
195             self.timeout_handler_id = gobject.timeout_add(50, self.update_progress_bar)
196             #self.waitingWindow.show_all()
197             #response = self.waitingWindow.run()
198             #self.listOfKeys = []
199             #while threading.activeCount() > 1:
200                 # Wait for current downloads to finish
201             #    time.sleep(0.1)
202             #self.waitingWindow.destroy()
203
204     def update_progress_bar(self):
205         #self.progress_bar.pulse()
206         if threading.activeCount() < 4:
207             x = threading.activeCount() - 1
208             k = len(self.listOfKeys)
209             fin = self.total - k - x
210             fraction = float(fin)/float(self.total) + float(x)/(self.total*2.)
211             #print x, k, fin, fraction
212             self.set_fraction(fraction)
213
214             if len(self.listOfKeys)>0:
215                 self.current = self.current+1
216                 key = self.listOfKeys.pop()
217                 if (not self.listing.getCurrentlyDisplayedFeed() == key) or (self.single == True):
218                     # Check if the feed is being displayed
219                     download = Download(self.listing, key, self.config)
220                     download.start()
221                 return True
222             elif threading.activeCount() > 1:
223                 return True
224             else:
225                 #self.waitingWindow.destroy()
226                 #self.destroy()
227                 self.emit("download-done", "success")
228                 return False 
229         return True
230     
231     
232 class SortList(gtk.Dialog):
233     def __init__(self, parent, listing):
234         gtk.Dialog.__init__(self, "Organizer",  parent)
235         self.listing = listing
236         
237         self.vbox2 = gtk.VBox(False, 10)
238         
239         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
240         button.set_label("Move Up")
241         button.connect("clicked", self.buttonUp)
242         self.vbox2.pack_start(button, expand=False, fill=False)
243         
244         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
245         button.set_label("Move Down")
246         button.connect("clicked", self.buttonDown)
247         self.vbox2.pack_start(button, expand=False, fill=False)
248
249         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
250         button.set_label("Add Feed")
251         button.connect("clicked", self.buttonAdd)
252         self.vbox2.pack_start(button, expand=False, fill=False)
253
254         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
255         button.set_label("Edit Feed")
256         button.connect("clicked", self.buttonEdit)
257         self.vbox2.pack_start(button, expand=False, fill=False)
258         
259         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
260         button.set_label("Delete")
261         button.connect("clicked", self.buttonDelete)
262         self.vbox2.pack_start(button, expand=False, fill=False)
263         
264         #button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
265         #button.set_label("Done")
266         #button.connect("clicked", self.buttonDone)
267         #self.vbox.pack_start(button)
268         self.hbox2= gtk.HBox(False, 10)
269         self.pannableArea = hildon.PannableArea()
270         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
271         self.treeview = gtk.TreeView(self.treestore)
272         self.hbox2.pack_start(self.pannableArea, expand=True)
273         self.displayFeeds()
274         self.hbox2.pack_end(self.vbox2, expand=False)
275         self.set_default_size(-1, 600)
276         self.vbox.pack_start(self.hbox2)
277         
278         self.show_all()
279         #self.connect("destroy", self.buttonDone)
280         
281     def displayFeeds(self):
282         self.treeview.destroy()
283         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
284         self.treeview = gtk.TreeView()
285         
286         self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
287         hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_EDIT)
288         self.refreshList()
289         self.treeview.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
290
291         self.pannableArea.add(self.treeview)
292
293         #self.show_all()
294
295     def refreshList(self, selected=None, offset=0):
296         rect = self.treeview.get_visible_rect()
297         y = rect.y+rect.height
298         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
299         for key in self.listing.getListOfFeeds():
300             item = self.treestore.append([self.listing.getFeedTitle(key), key])
301             if key == selected:
302                 selectedItem = item
303         self.treeview.set_model(self.treestore)
304         if not selected == None:
305             self.treeview.get_selection().select_iter(selectedItem)
306             self.treeview.scroll_to_cell(self.treeview.get_model().get_path(selectedItem))
307         self.pannableArea.show_all()
308
309     def getSelectedItem(self):
310         (model, iter) = self.treeview.get_selection().get_selected()
311         if not iter:
312             return None
313         return model.get_value(iter, 1)
314
315     def findIndex(self, key):
316         after = None
317         before = None
318         found = False
319         for row in self.treestore:
320             if found:
321                 return (before, row.iter)
322             if key == list(row)[0]:
323                 found = True
324             else:
325                 before = row.iter
326         return (before, None)
327
328     def buttonUp(self, button):
329         key  = self.getSelectedItem()
330         if not key == None:
331             self.listing.moveUp(key)
332             self.refreshList(key, -10)
333
334     def buttonDown(self, button):
335         key = self.getSelectedItem()
336         if not key == None:
337             self.listing.moveDown(key)
338             self.refreshList(key, 10)
339
340     def buttonDelete(self, button):
341         key = self.getSelectedItem()
342         if not key == None:
343             self.listing.removeFeed(key)
344         self.refreshList()
345
346     def buttonEdit(self, button):
347         key = self.getSelectedItem()
348         if not key == None:
349             wizard = AddWidgetWizard(self, self.listing.getFeedUrl(key), self.listing.getFeedTitle(key))
350             ret = wizard.run()
351             if ret == 2:
352                 (title, url) = wizard.getData()
353                 if (not title == '') and (not url == ''):
354                     self.listing.editFeed(key, title, url)
355             wizard.destroy()
356         self.refreshList()
357
358     def buttonDone(self, *args):
359         self.destroy()
360         
361     def buttonAdd(self, button, urlIn="http://"):
362         wizard = AddWidgetWizard(self, urlIn)
363         ret = wizard.run()
364         if ret == 2:
365             (title, url) = wizard.getData()
366             if (not title == '') and (not url == ''): 
367                self.listing.addFeed(title, url)
368         wizard.destroy()
369         self.refreshList()
370                
371
372 class DisplayArticle(hildon.StackableWindow):
373     def __init__(self, title, text, link, index, key, listing, config):
374         hildon.StackableWindow.__init__(self)
375         self.imageDownloader = ImageDownloader()
376         self.listing=listing
377         self.key = key
378         self.index = index
379         self.text = text
380         self.link = link
381         self.set_title(title)
382         self.config = config
383         self.images = []
384         
385         # Init the article display
386         if self.config.getWebkitSupport():
387             self.view = webkit.WebView()
388             #self.view.set_editable(False)
389         else:
390             import gtkhtml2
391             self.view = gtkhtml2.View()
392             self.document = gtkhtml2.Document()
393             self.view.set_document(self.document)
394             self.document.connect("link_clicked", self._signal_link_clicked)
395         self.pannable_article = hildon.PannableArea()
396         self.pannable_article.add(self.view)
397         #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
398         #self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
399
400         if self.config.getWebkitSupport():
401             if key=="ArchivedArticles":
402                 self.view.open("file://" + self.link)
403             else:
404                 self.view.load_html_string(self.text, self.link) # "text/html", "utf-8", self.link)
405             self.view.set_zoom_level(float(config.getArtFontSize())/10.)
406         else:
407             if not key == "ArchivedArticles":
408                 # Do not download images if the feed is "Archived Articles"
409                 self.document.connect("request-url", self._signal_request_url)
410             
411             self.document.clear()
412             self.document.open_stream("text/html")
413             self.document.write_stream(self.text)
414             self.document.close_stream()
415         
416         menu = hildon.AppMenu()
417         # Create a button and add it to the menu
418         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
419         button.set_label("Allow Horizontal Scrolling")
420         button.connect("clicked", self.horiz_scrolling_button)
421         menu.append(button)
422         
423         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
424         button.set_label("Open in Browser")
425         button.connect("clicked", self._signal_link_clicked, self.link)
426         menu.append(button)
427         
428         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
429         button.set_label("Add to Archived Articles")
430         button.connect("clicked", self.archive_button)
431         menu.append(button)
432         
433         self.set_app_menu(menu)
434         menu.show_all()
435         
436         #self.event_box = gtk.EventBox()
437         #self.event_box.add(self.pannable_article)
438         self.add(self.pannable_article)
439         
440         
441         self.pannable_article.show_all()
442
443         self.destroyId = self.connect("destroy", self.destroyWindow)
444         
445         self.view.connect("button_press_event", self.button_pressed)
446         self.gestureId = self.view.connect("button_release_event", self.button_released)
447         #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
448
449     def button_pressed(self, window, event):
450         #print event.x, event.y
451         self.coords = (event.x, event.y)
452         
453     def button_released(self, window, event):
454         x = self.coords[0] - event.x
455         y = self.coords[1] - event.y
456         
457         if (abs(y) < 30):
458             if (x > 15):
459                 self.emit("article-previous", self.index)
460             elif (x<-15):
461                 self.emit("article-next", self.index)   
462         #print x, y
463         #print "Released"
464
465     #def gesture(self, widget, direction, startx, starty):
466     #    if (direction == 3):
467     #        self.emit("article-next", self.index)
468     #    if (direction == 2):
469     #        self.emit("article-previous", self.index)
470         #print startx, starty
471         #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
472
473     def destroyWindow(self, *args):
474         self.disconnect(self.destroyId)
475         self.emit("article-closed", self.index)
476         self.imageDownloader.stopAll()
477         self.destroy()
478         
479     def horiz_scrolling_button(self, *widget):
480         self.pannable_article.disconnect(self.gestureId)
481         self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
482         
483     def archive_button(self, *widget):
484         # Call the listing.addArchivedArticle
485         self.listing.addArchivedArticle(self.key, self.index)
486         
487     #def reloadArticle(self, *widget):
488     #    if threading.activeCount() > 1:
489             # Image thread are still running, come back in a bit
490     #        return True
491     #    else:
492     #        for (stream, imageThread) in self.images:
493     #            imageThread.join()
494     #            stream.write(imageThread.data)
495     #            stream.close()
496     #        return False
497     #    self.show_all()
498
499     def _signal_link_clicked(self, object, link):
500         bus = dbus.SystemBus()
501         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
502         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
503         iface.load_url(link)
504
505     def _signal_request_url(self, object, url, stream):
506         #print url
507         self.imageDownloader.queueImage(url, stream)
508         #imageThread = GetImage(url)
509         #imageThread.start()
510         #self.images.append((stream, imageThread))
511
512
513 class DisplayFeed(hildon.StackableWindow):
514     def __init__(self, listing, feed, title, key, config):
515         hildon.StackableWindow.__init__(self)
516         self.listing = listing
517         self.feed = feed
518         self.feedTitle = title
519         self.set_title(title)
520         self.key=key
521         self.config = config
522         
523         self.downloadDialog = False
524         
525         self.listing.setCurrentlyDisplayedFeed(self.key)
526         
527         self.disp = False
528         
529         menu = hildon.AppMenu()
530         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
531         button.set_label("Update Feed")
532         button.connect("clicked", self.button_update_clicked)
533         menu.append(button)
534         
535         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
536         button.set_label("Mark All As Read")
537         button.connect("clicked", self.buttonReadAllClicked)
538         menu.append(button)
539         self.set_app_menu(menu)
540         menu.show_all()
541         
542         self.displayFeed()
543         
544         self.connect("destroy", self.destroyWindow)
545         
546     def destroyWindow(self, *args):
547         self.feed.saveUnread(CONFIGDIR)
548         self.listing.updateUnread(self.key, self.feed.getNumberOfUnreadItems())
549         self.emit("feed-closed", self.key)
550         self.destroy()
551         #gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
552         self.listing.closeCurrentlyDisplayedFeed()
553
554     def displayFeed(self):
555         self.vboxFeed = gtk.VBox(False, 10)
556         self.pannableFeed = hildon.PannableArea()
557         self.pannableFeed.add_with_viewport(self.vboxFeed)
558         self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
559         self.buttons = {}
560         for id in self.feed.getIds():
561             button = gtk.Button(self.feed.getTitle(id))
562             button.set_alignment(0,0)
563             label = button.child
564             if self.feed.isEntryRead(id):
565                 #label.modify_font(pango.FontDescription("sans 16"))
566                 label.modify_font(pango.FontDescription(self.config.getReadFont()))
567                 label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
568             else:
569                 #print self.listing.getFont() + " bold"
570                 label.modify_font(pango.FontDescription(self.config.getUnreadFont()))
571                 #label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("SkyBlue"))
572                 #fg_color = fg_button.child.get_children()[0].get_children()[0].get_children()[1].get_style().fg[gtk.STATE_NORMAL]
573                 label.modify_fg(gtk.STATE_NORMAL, fg_color)
574                 #label.modify_font(pango.FontDescription("sans bold 23"))
575                 #"sans bold 16"
576             label.set_line_wrap(True)
577             
578             label.set_size_request(self.get_size()[0]-50, -1)
579             button.connect("clicked", self.button_clicked, id)
580             self.buttons[id] = button
581             
582             self.vboxFeed.pack_start(button, expand=False)
583
584         self.add(self.pannableFeed)
585         self.show_all()
586         
587     def clear(self):
588         self.remove(self.pannableFeed)
589
590     def button_clicked(self, button, index, previous=False, next=False):
591         newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing, self.config)
592         stack = hildon.WindowStack.get_default()
593         if previous:
594             tmp = stack.peek()
595             stack.pop_and_push(1, newDisp, tmp)
596             newDisp.show()
597             gobject.timeout_add(200, self.destroyArticle, tmp)
598             #print "previous"
599             self.disp = newDisp
600             
601             #stack.push(tmp)
602             #if not self.disp == False:
603             #   self.disp.destroyWindow()
604         elif next:
605             #print type(self.disp).__name__
606
607                 #self.disp.destroyWindow()
608                 #stack.pop_and_push(1,newDisp)
609             #else:
610             #    stack.push(newDisp)
611             #self.disp = newDisp
612             newDisp.show_all()
613             if type(self.disp).__name__ == "DisplayArticle":
614                 gobject.timeout_add(200, self.destroyArticle, self.disp)
615             self.disp = newDisp
616             #self.disp.show_all()
617             #if not self.disp == False:
618             #    self.disp.destroyWindow()
619         else:
620             self.disp = newDisp
621             self.disp.show_all()
622         
623         self.ids = []
624         self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
625         self.ids.append(self.disp.connect("article-next", self.nextArticle))
626         self.ids.append(self.disp.connect("article-previous", self.previousArticle))
627
628     def destroyArticle(self, handle):
629         handle.destroyWindow()
630
631     def nextArticle(self, object, index):
632         label = self.buttons[index].child
633         label.modify_font(pango.FontDescription(self.config.getReadFont()))
634         label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
635         id = self.feed.getNextId(index)
636         self.button_clicked(object, id, next=True)
637
638     def previousArticle(self, object, index):
639         label = self.buttons[index].child
640         label.modify_font(pango.FontDescription(self.config.getReadFont()))
641         label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
642         id = self.feed.getPreviousId(index)
643         self.button_clicked(object, id, previous=True)
644
645     def onArticleClosed(self, object, index):
646         label = self.buttons[index].child
647         label.modify_font(pango.FontDescription(self.config.getReadFont()))
648         label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
649         self.buttons[index].show()
650
651     def button_update_clicked(self, button):
652         #bar = DownloadBar(self, self.listing, [self.key,], self.config ) 
653         if not type(self.downloadDialog).__name__=="DownloadBar":
654             self.pannableFeed.destroy()
655             self.vbox = gtk.VBox(False, 10)
656             self.downloadDialog = DownloadBar(self.window, self.listing, [self.key,], self.config, single=True )
657             self.downloadDialog.connect("download-done", self.onDownloadsDone)
658             self.vbox.pack_start(self.downloadDialog, expand=False, fill=False)
659             self.add(self.vbox)
660             self.show_all()
661             
662     def onDownloadsDone(self, *widget):
663         self.vbox.destroy()
664         self.feed = self.listing.getFeed(self.key)
665         self.displayFeed()
666         #self.feed.updateFeed()
667     #    self.clear()
668     #    self.displayFeed()
669         
670     def buttonReadAllClicked(self, button):
671         for index in self.feed.getIds():
672             self.feed.setEntryRead(index)
673             label = self.buttons[index].child
674             label.modify_font(pango.FontDescription(self.config.getReadFont()))
675             label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
676             self.buttons[index].show()
677
678
679 class FeedingIt:
680     def __init__(self):
681         # Init the windows
682         self.window = hildon.StackableWindow()
683         self.window.set_title("FeedingIt")
684         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
685         self.mainVbox = gtk.VBox(False,10)
686         self.pannableListing = gtk.Label("Loading...")
687         self.mainVbox.pack_start(self.pannableListing)
688         self.window.add(self.mainVbox)
689         self.window.show_all()
690         self.config = Config(self.window, CONFIGDIR+"config.ini", has_webkit)
691         gobject.idle_add(self.createWindow)
692         
693     def createWindow(self):
694         self.listing = Listing(CONFIGDIR)
695         
696         self.downloadDialog = False
697         self.orientation = FremantleRotation("FeedingIt", main_window=self.window)
698         self.orientation.set_mode(self.config.getOrientation())
699         
700         menu = hildon.AppMenu()
701         # Create a button and add it to the menu
702         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
703         button.set_label("Update All Feeds")
704         button.connect("clicked", self.button_update_clicked, "All")
705         menu.append(button)
706         
707         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
708         button.set_label("Mark All As Read")
709         button.connect("clicked", self.button_markAll)
710         menu.append(button)
711         
712         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
713         button.set_label("Organize Feeds")
714         button.connect("clicked", self.button_organize_clicked)
715         menu.append(button)
716
717         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
718         button.set_label("Preferences")
719         button.connect("clicked", self.button_preferences_clicked)
720         menu.append(button)
721        
722         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
723         button.set_label("Import Feeds")
724         button.connect("clicked", self.button_import_clicked)
725         menu.append(button)
726         
727         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
728         button.set_label("Export Feeds")
729         button.connect("clicked", self.button_export_clicked)
730         menu.append(button)
731         
732         self.window.set_app_menu(menu)
733         menu.show_all()
734         
735         #self.feedWindow = hildon.StackableWindow()
736         #self.articleWindow = hildon.StackableWindow()
737
738         self.displayListing()
739         self.autoupdate = False
740         self.checkAutoUpdate()
741         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
742
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         self.refreshList()
749
750     def button_export_clicked(self, button):
751         opml = ExportOpmlData(self.window, self.listing)
752         
753     def button_import_clicked(self, button):
754         opml = GetOpmlData(self.window)
755         feeds = opml.getData()
756         for (title, url) in feeds:
757             self.listing.addFeed(title, url)
758         self.displayListing()
759
760     def addFeed(self, urlIn="http://"):
761         wizard = AddWidgetWizard(self.window, urlIn)
762         ret = wizard.run()
763         if ret == 2:
764             (title, url) = wizard.getData()
765             if (not title == '') and (not url == ''): 
766                self.listing.addFeed(title, url)
767         wizard.destroy()
768         self.displayListing()
769
770     def button_organize_clicked(self, button):
771         org = SortList(self.window, self.listing)
772         org.run()
773         org.destroy()
774         self.listing.saveConfig()
775         self.displayListing()
776         
777     def button_update_clicked(self, button, key):
778         if not type(self.downloadDialog).__name__=="DownloadBar":
779             self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
780             self.downloadDialog.connect("download-done", self.onDownloadsDone)
781             self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
782             self.mainVbox.show_all()
783         #self.displayListing()
784
785     def onDownloadsDone(self, *widget):
786         self.downloadDialog.destroy()
787         self.downloadDialog = False
788         #self.displayListing()
789         self.refreshList()
790
791     def button_preferences_clicked(self, button):
792         dialog = self.config.createDialog()
793         dialog.connect("destroy", self.prefsClosed)
794
795     def show_confirmation_note(self, parent, title):
796         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
797
798         retcode = gtk.Dialog.run(note)
799         note.destroy()
800         
801         if retcode == gtk.RESPONSE_OK:
802             return True
803         else:
804             return False
805         
806     def displayListing(self):
807         try:
808             self.mainVbox.remove(self.pannableListing)
809         except:
810             pass
811         self.vboxListing = gtk.VBox(False,10)
812         self.pannableListing = hildon.PannableArea()
813         self.pannableListing.add_with_viewport(self.vboxListing)
814
815         self.buttons = {}
816         list = self.listing.getListOfFeeds()[:]
817         #list.reverse()
818         for key in list:
819             #button = gtk.Button(item)
820             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
821                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
822             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
823                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
824             button.set_alignment(0,0,1,1)
825             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
826             self.vboxListing.pack_start(button, expand=False)
827             self.buttons[key] = button
828      
829         self.mainVbox.pack_start(self.pannableListing)
830         self.window.show_all()
831
832     def refreshList(self):
833         for key in self.listing.getListOfFeeds():
834             if self.buttons.has_key(key):
835                 button = self.buttons[key]
836                 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
837                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
838             else:
839                 self.displayListing()
840                 break
841
842     def buttonFeedClicked(widget, button, self, window, key):
843         disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
844         disp.connect("feed-closed", self.onFeedClosed)
845
846     def onFeedClosed(self, object, key):
847         #self.displayListing()
848         self.listing.saveConfig()
849         self.refreshList()
850         #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
851         #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
852         #self.buttons[key].show()
853      
854     def run(self):
855         self.window.connect("destroy", gtk.main_quit)
856         gtk.main()
857         #for key in self.listing.getListOfFeeds():
858         #    self.listing.getFeed(key).saveFeed(CONFIGDIR)
859         self.listing.saveConfig()
860
861     def prefsClosed(self, *widget):
862         self.orientation.set_mode(self.config.getOrientation())
863         self.checkAutoUpdate()
864
865     def checkAutoUpdate(self, *widget):
866         interval = int(self.config.getUpdateInterval()*3600000)
867         if self.config.isAutoUpdateEnabled():
868             if self.autoupdate == False:
869                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
870                 self.autoupdate = interval
871             elif not self.autoupdate == interval:
872                 # If auto-update is enabled, but not at the right frequency
873                 gobject.source_remove(self.autoupdateId)
874                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
875                 self.autoupdate = interval
876         else:
877             if not self.autoupdate == False:
878                 gobject.source_remove(self.autoupdateId)
879                 self.autoupdate = False
880
881     def automaticUpdate(self, *widget):
882         # Need to check for internet connection
883         # If no internet connection, try again in 10 minutes:
884         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
885         self.button_update_clicked(None, None)
886         return True
887     
888     def getStatus(self):
889         status = ""
890         for key in self.listing.getListOfFeeds():
891             if self.listing.getFeedNumberOfUnreadItems(key) > 0:
892                 status += self.listing.getFeedTitle(key) + ": \t" +  str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items\n"
893         if status == "":
894             status = "No unread items"
895         return status
896
897 if __name__ == "__main__":
898     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
899     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
900     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
901     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
902     gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
903     gobject.threads_init()
904     if not isdir(CONFIGDIR):
905         try:
906             mkdir(CONFIGDIR)
907         except:
908             print "Error: Can't create configuration directory"
909             sys.exit(1)
910     app = FeedingIt()
911     dbusHandler = ServerObject(app)
912     app.run()