0.5.3-4 extras-devel 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.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_color = 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, imageCache=self.config.getImageCache() )
172         else:
173             self.listing.updateFeed(self.key, self.config.getExpiry(), imageCache=self.config.getImageCache() )
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, feed, id, key, config, listing):
374         hildon.StackableWindow.__init__(self)
375         #self.imageDownloader = ImageDownloader()
376         self.feed = feed
377         self.listing=listing
378         self.key = key
379         self.id = id
380         self.set_title(feed.getTitle(id))
381         self.config = config
382         
383         # Init the article display
384         #if self.config.getWebkitSupport():
385         self.view = webkit.WebView()
386             #self.view.set_editable(False)
387         #else:
388         #    import gtkhtml2
389         #    self.view = gtkhtml2.View()
390         #    self.document = gtkhtml2.Document()
391         #    self.view.set_document(self.document)
392         #    self.document.connect("link_clicked", self._signal_link_clicked)
393         self.pannable_article = hildon.PannableArea()
394         self.pannable_article.add(self.view)
395         #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
396         #self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
397
398         #if self.config.getWebkitSupport():
399         contentLink = self.feed.getContentLink(self.id)
400         self.feed.setEntryRead(self.id)
401         #if key=="ArchivedArticles":
402         self.view.open("file://" + contentLink)
403         self.view.connect("motion-notify-event", lambda w,ev: True)
404
405         #else:
406         #self.view.load_html_string(self.text, contentLink) # "text/html", "utf-8", self.link)
407         self.view.set_zoom_level(float(config.getArtFontSize())/10.)
408         #else:
409         #    if not key == "ArchivedArticles":
410                 # Do not download images if the feed is "Archived Articles"
411         #        self.document.connect("request-url", self._signal_request_url)
412             
413         #    self.document.clear()
414         #    self.document.open_stream("text/html")
415         #    self.document.write_stream(self.text)
416         #    self.document.close_stream()
417         
418         menu = hildon.AppMenu()
419         # Create a button and add it to the menu
420         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
421         button.set_label("Allow Horizontal Scrolling")
422         button.connect("clicked", self.horiz_scrolling_button)
423         menu.append(button)
424         
425         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
426         button.set_label("Open in Browser")
427         button.connect("clicked", self._signal_link_clicked, self.feed.getExternalLink(self.id))
428         menu.append(button)
429         
430         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
431         button.set_label("Add to Archived Articles")
432         button.connect("clicked", self.archive_button)
433         menu.append(button)
434         
435         self.set_app_menu(menu)
436         menu.show_all()
437         
438         #self.event_box = gtk.EventBox()
439         #self.event_box.add(self.pannable_article)
440         self.add(self.pannable_article)
441         
442         
443         self.pannable_article.show_all()
444
445         self.destroyId = self.connect("destroy", self.destroyWindow)
446         
447         self.view.connect("button_press_event", self.button_pressed)
448         self.gestureId = self.view.connect("button_release_event", self.button_released)
449         #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
450
451     def button_pressed(self, window, event):
452         #print event.x, event.y
453         self.coords = (event.x, event.y)
454         
455     def button_released(self, window, event):
456         x = self.coords[0] - event.x
457         y = self.coords[1] - event.y
458         
459         if (abs(y) < 30):
460             if (x > 15):
461                 self.emit("article-previous", self.id)
462             elif (x<-15):
463                 self.emit("article-next", self.id)   
464         #print x, y
465         #print "Released"
466
467     #def gesture(self, widget, direction, startx, starty):
468     #    if (direction == 3):
469     #        self.emit("article-next", self.index)
470     #    if (direction == 2):
471     #        self.emit("article-previous", self.index)
472         #print startx, starty
473         #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
474
475     def destroyWindow(self, *args):
476         self.disconnect(self.destroyId)
477         self.emit("article-closed", self.id)
478         #self.imageDownloader.stopAll()
479         self.destroy()
480         
481     def horiz_scrolling_button(self, *widget):
482         self.pannable_article.disconnect(self.gestureId)
483         self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
484         
485     def archive_button(self, *widget):
486         # Call the listing.addArchivedArticle
487         self.listing.addArchivedArticle(self.key, self.id)
488         
489     #def reloadArticle(self, *widget):
490     #    if threading.activeCount() > 1:
491             # Image thread are still running, come back in a bit
492     #        return True
493     #    else:
494     #        for (stream, imageThread) in self.images:
495     #            imageThread.join()
496     #            stream.write(imageThread.data)
497     #            stream.close()
498     #        return False
499     #    self.show_all()
500
501     def _signal_link_clicked(self, object, link):
502         bus = dbus.SessionBus()
503         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
504         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
505         iface.open_new_window(link)
506
507     #def _signal_request_url(self, object, url, stream):
508         #print url
509     #    self.imageDownloader.queueImage(url, stream)
510         #imageThread = GetImage(url)
511         #imageThread.start()
512         #self.images.append((stream, imageThread))
513
514
515 class DisplayFeed(hildon.StackableWindow):
516     def __init__(self, listing, feed, title, key, config):
517         hildon.StackableWindow.__init__(self)
518         self.listing = listing
519         self.feed = feed
520         self.feedTitle = title
521         self.set_title(title)
522         self.key=key
523         self.config = config
524         
525         self.downloadDialog = False
526         
527         self.listing.setCurrentlyDisplayedFeed(self.key)
528         
529         self.disp = False
530         
531         menu = hildon.AppMenu()
532         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
533         button.set_label("Update Feed")
534         button.connect("clicked", self.button_update_clicked)
535         menu.append(button)
536         
537         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
538         button.set_label("Mark All As Read")
539         button.connect("clicked", self.buttonReadAllClicked)
540         menu.append(button)
541         self.set_app_menu(menu)
542         menu.show_all()
543         
544         self.displayFeed()
545         
546         self.connect("destroy", self.destroyWindow)
547         
548     def destroyWindow(self, *args):
549         self.feed.saveUnread(CONFIGDIR)
550         self.listing.updateUnread(self.key, self.feed.getNumberOfUnreadItems())
551         self.emit("feed-closed", self.key)
552         self.destroy()
553         #gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
554         self.listing.closeCurrentlyDisplayedFeed()
555
556     def displayFeed(self):
557         self.vboxFeed = gtk.VBox(False, 10)
558         self.pannableFeed = hildon.PannableArea()
559         self.pannableFeed.add_with_viewport(self.vboxFeed)
560         self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
561         self.buttons = {}
562         for id in self.feed.getIds():
563             button = gtk.Button(self.feed.getTitle(id))
564             button.set_alignment(0,0)
565             label = button.child
566             if self.feed.isEntryRead(id):
567                 #label.modify_font(pango.FontDescription("sans 16"))
568                 label.modify_font(pango.FontDescription(self.config.getReadFont()))
569                 label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
570             else:
571                 #print self.listing.getFont() + " bold"
572                 label.modify_font(pango.FontDescription(self.config.getUnreadFont()))
573                 #label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("SkyBlue"))
574                 #fg_color = fg_button.child.get_children()[0].get_children()[0].get_children()[1].get_style().fg[gtk.STATE_NORMAL]
575                 label.modify_fg(gtk.STATE_NORMAL, fg_color)
576                 #label.modify_font(pango.FontDescription("sans bold 23"))
577                 #"sans bold 16"
578             label.set_line_wrap(True)
579             
580             label.set_size_request(self.get_size()[0]-50, -1)
581             button.connect("clicked", self.button_clicked, id)
582             self.buttons[id] = button
583             
584             self.vboxFeed.pack_start(button, expand=False)
585
586         self.add(self.pannableFeed)
587         self.show_all()
588         
589     def clear(self):
590         self.pannableFeed.destroy()
591         #self.remove(self.pannableFeed)
592
593     def button_clicked(self, button, index, previous=False, next=False):
594         #newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing, self.config)
595         newDisp = DisplayArticle(self.feed, index, self.key, self.config, self.listing)
596         stack = hildon.WindowStack.get_default()
597         if previous:
598             tmp = stack.peek()
599             stack.pop_and_push(1, newDisp, tmp)
600             newDisp.show()
601             gobject.timeout_add(200, self.destroyArticle, tmp)
602             #print "previous"
603             self.disp = newDisp
604             
605             #stack.push(tmp)
606             #if not self.disp == False:
607             #   self.disp.destroyWindow()
608         elif next:
609             #print type(self.disp).__name__
610
611                 #self.disp.destroyWindow()
612                 #stack.pop_and_push(1,newDisp)
613             #else:
614             #    stack.push(newDisp)
615             #self.disp = newDisp
616             newDisp.show_all()
617             if type(self.disp).__name__ == "DisplayArticle":
618                 gobject.timeout_add(200, self.destroyArticle, self.disp)
619             self.disp = newDisp
620             #self.disp.show_all()
621             #if not self.disp == False:
622             #    self.disp.destroyWindow()
623         else:
624             self.disp = newDisp
625             self.disp.show_all()
626         
627         self.ids = []
628         self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
629         self.ids.append(self.disp.connect("article-next", self.nextArticle))
630         self.ids.append(self.disp.connect("article-previous", self.previousArticle))
631
632     def destroyArticle(self, handle):
633         handle.destroyWindow()
634
635     def nextArticle(self, object, index):
636         label = self.buttons[index].child
637         label.modify_font(pango.FontDescription(self.config.getReadFont()))
638         label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
639         id = self.feed.getNextId(index)
640         self.button_clicked(object, id, next=True)
641
642     def previousArticle(self, object, index):
643         label = self.buttons[index].child
644         label.modify_font(pango.FontDescription(self.config.getReadFont()))
645         label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
646         id = self.feed.getPreviousId(index)
647         self.button_clicked(object, id, previous=True)
648
649     def onArticleClosed(self, object, index):
650         label = self.buttons[index].child
651         label.modify_font(pango.FontDescription(self.config.getReadFont()))
652         label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
653         self.buttons[index].show()
654
655     def button_update_clicked(self, button):
656         #bar = DownloadBar(self, self.listing, [self.key,], self.config ) 
657         if not type(self.downloadDialog).__name__=="DownloadBar":
658             self.pannableFeed.destroy()
659             self.vbox = gtk.VBox(False, 10)
660             self.downloadDialog = DownloadBar(self.window, self.listing, [self.key,], self.config, single=True )
661             self.downloadDialog.connect("download-done", self.onDownloadsDone)
662             self.vbox.pack_start(self.downloadDialog, expand=False, fill=False)
663             self.add(self.vbox)
664             self.show_all()
665             
666     def onDownloadsDone(self, *widget):
667         self.vbox.destroy()
668         self.feed = self.listing.getFeed(self.key)
669         self.displayFeed()
670         #self.feed.updateFeed()
671     #    self.clear()
672     #    self.displayFeed()
673         
674     def buttonReadAllClicked(self, button):
675         for index in self.feed.getIds():
676             self.feed.setEntryRead(index)
677             label = self.buttons[index].child
678             label.modify_font(pango.FontDescription(self.config.getReadFont()))
679             label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
680             self.buttons[index].show()
681
682
683 class FeedingIt:
684     def __init__(self):
685         # Init the windows
686         self.window = hildon.StackableWindow()
687         self.window.set_title("FeedingIt")
688         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
689         self.mainVbox = gtk.VBox(False,10)
690         self.pannableListing = gtk.Label("Loading...")
691         self.mainVbox.pack_start(self.pannableListing)
692         self.window.add(self.mainVbox)
693         self.window.show_all()
694         self.config = Config(self.window, CONFIGDIR+"config.ini")
695         gobject.idle_add(self.createWindow)
696         
697     def createWindow(self):
698         self.listing = Listing(CONFIGDIR)
699         
700         self.downloadDialog = False
701         self.orientation = FremantleRotation("FeedingIt", main_window=self.window, app=self)
702         self.orientation.set_mode(self.config.getOrientation())
703         
704         menu = hildon.AppMenu()
705         # Create a button and add it to the menu
706         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
707         button.set_label("Update All Feeds")
708         button.connect("clicked", self.button_update_clicked, "All")
709         menu.append(button)
710         
711         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
712         button.set_label("Mark All As Read")
713         button.connect("clicked", self.button_markAll)
714         menu.append(button)
715         
716         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
717         button.set_label("Organize Feeds")
718         button.connect("clicked", self.button_organize_clicked)
719         menu.append(button)
720
721         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
722         button.set_label("Preferences")
723         button.connect("clicked", self.button_preferences_clicked)
724         menu.append(button)
725        
726         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
727         button.set_label("Import Feeds")
728         button.connect("clicked", self.button_import_clicked)
729         menu.append(button)
730         
731         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
732         button.set_label("Export Feeds")
733         button.connect("clicked", self.button_export_clicked)
734         menu.append(button)
735         
736         self.window.set_app_menu(menu)
737         menu.show_all()
738         
739         self.feedWindow = hildon.StackableWindow()
740         self.articleWindow = hildon.StackableWindow()
741
742         self.displayListing()
743         self.autoupdate = False
744         self.checkAutoUpdate()
745         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
746         gobject.idle_add(self.enabelDbus)
747         
748     def enableDbus(self):
749         dbusHandler = ServerObject(self)
750
751     def button_markAll(self, button):
752         for key in self.listing.getListOfFeeds():
753             feed = self.listing.getFeed(key)
754             for id in feed.getIds():
755                 feed.setEntryRead(id)
756             feed.saveUnread(CONFIGDIR)
757             self.listing.updateUnread(key, feed.getNumberOfUnreadItems())
758         self.refreshList()
759
760     def button_export_clicked(self, button):
761         opml = ExportOpmlData(self.window, self.listing)
762         
763     def button_import_clicked(self, button):
764         opml = GetOpmlData(self.window)
765         feeds = opml.getData()
766         for (title, url) in feeds:
767             self.listing.addFeed(title, url)
768         self.displayListing()
769
770     def addFeed(self, urlIn="http://"):
771         wizard = AddWidgetWizard(self.window, urlIn)
772         ret = wizard.run()
773         if ret == 2:
774             (title, url) = wizard.getData()
775             if (not title == '') and (not url == ''): 
776                self.listing.addFeed(title, url)
777         wizard.destroy()
778         self.displayListing()
779
780     def button_organize_clicked(self, button):
781         org = SortList(self.window, self.listing)
782         org.run()
783         org.destroy()
784         self.listing.saveConfig()
785         self.displayListing()
786         
787     def button_update_clicked(self, button, key):
788         if not type(self.downloadDialog).__name__=="DownloadBar":
789             self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
790             self.downloadDialog.connect("download-done", self.onDownloadsDone)
791             self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
792             self.mainVbox.show_all()
793         #self.displayListing()
794
795     def onDownloadsDone(self, *widget):
796         self.downloadDialog.destroy()
797         self.downloadDialog = False
798         #self.displayListing()
799         self.refreshList()
800
801     def button_preferences_clicked(self, button):
802         dialog = self.config.createDialog()
803         dialog.connect("destroy", self.prefsClosed)
804
805     def show_confirmation_note(self, parent, title):
806         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
807
808         retcode = gtk.Dialog.run(note)
809         note.destroy()
810         
811         if retcode == gtk.RESPONSE_OK:
812             return True
813         else:
814             return False
815         
816     def displayListing(self):
817         try:
818             self.mainVbox.remove(self.pannableListing)
819         except:
820             pass
821         self.vboxListing = gtk.VBox(False,10)
822         self.pannableListing = hildon.PannableArea()
823         self.pannableListing.add_with_viewport(self.vboxListing)
824
825         self.buttons = {}
826         list = self.listing.getListOfFeeds()[:]
827         #list.reverse()
828         for key in list:
829             #button = gtk.Button(item)
830             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
831                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
832             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
833                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
834             button.set_alignment(0,0,1,1)
835             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
836             self.vboxListing.pack_start(button, expand=False)
837             self.buttons[key] = button
838      
839         self.mainVbox.pack_start(self.pannableListing)
840         self.window.show_all()
841
842     def refreshList(self):
843         for key in self.listing.getListOfFeeds():
844             if self.buttons.has_key(key):
845                 button = self.buttons[key]
846                 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
847                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
848             else:
849                 self.displayListing()
850                 break
851
852     def buttonFeedClicked(widget, button, self, window, key):
853         self.disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
854         self.disp.connect("feed-closed", self.onFeedClosed)
855
856     def onFeedClosed(self, object, key):
857         #self.displayListing()
858         self.listing.saveConfig()
859         self.refreshList()
860         #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
861         #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
862         #self.buttons[key].show()
863      
864     def run(self):
865         self.window.connect("destroy", gtk.main_quit)
866         gtk.main()
867         #for key in self.listing.getListOfFeeds():
868         #    self.listing.getFeed(key).saveFeed(CONFIGDIR)
869         self.listing.saveConfig()
870
871     def prefsClosed(self, *widget):
872         self.orientation.set_mode(self.config.getOrientation())
873         self.checkAutoUpdate()
874
875     def checkAutoUpdate(self, *widget):
876         interval = int(self.config.getUpdateInterval()*3600000)
877         if self.config.isAutoUpdateEnabled():
878             if self.autoupdate == False:
879                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
880                 self.autoupdate = interval
881             elif not self.autoupdate == interval:
882                 # If auto-update is enabled, but not at the right frequency
883                 gobject.source_remove(self.autoupdateId)
884                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
885                 self.autoupdate = interval
886         else:
887             if not self.autoupdate == False:
888                 gobject.source_remove(self.autoupdateId)
889                 self.autoupdate = False
890
891     def automaticUpdate(self, *widget):
892         # Need to check for internet connection
893         # If no internet connection, try again in 10 minutes:
894         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
895         self.button_update_clicked(None, None)
896         return True
897     
898     def getStatus(self):
899         status = ""
900         for key in self.listing.getListOfFeeds():
901             if self.listing.getFeedNumberOfUnreadItems(key) > 0:
902                 status += self.listing.getFeedTitle(key) + ": \t" +  str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items\n"
903         if status == "":
904             status = "No unread items"
905         return status
906
907 if __name__ == "__main__":
908     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
909     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
910     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
911     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
912     gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
913     gobject.threads_init()
914     if not isdir(CONFIGDIR):
915         try:
916             mkdir(CONFIGDIR)
917         except:
918             print "Error: Can't create configuration directory"
919             sys.exit(1)
920     app = FeedingIt()
921     app.run()