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