0.5.3 - Image caching, first try
[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):
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         #else:
404         #self.view.load_html_string(self.text, contentLink) # "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.feed.getExternalLink(self.id))
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.id)
460             elif (x<-15):
461                 self.emit("article-next", self.id)   
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.id)
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.id)
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         newDisp = DisplayArticle(self.feed, index, self.key, self.config)
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             
602             #stack.push(tmp)
603             #if not self.disp == False:
604             #   self.disp.destroyWindow()
605         elif next:
606             #print type(self.disp).__name__
607
608                 #self.disp.destroyWindow()
609                 #stack.pop_and_push(1,newDisp)
610             #else:
611             #    stack.push(newDisp)
612             #self.disp = newDisp
613             newDisp.show_all()
614             if type(self.disp).__name__ == "DisplayArticle":
615                 gobject.timeout_add(200, self.destroyArticle, self.disp)
616             self.disp = newDisp
617             #self.disp.show_all()
618             #if not self.disp == False:
619             #    self.disp.destroyWindow()
620         else:
621             self.disp = newDisp
622             self.disp.show_all()
623         
624         self.ids = []
625         self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
626         self.ids.append(self.disp.connect("article-next", self.nextArticle))
627         self.ids.append(self.disp.connect("article-previous", self.previousArticle))
628
629     def destroyArticle(self, handle):
630         handle.destroyWindow()
631
632     def nextArticle(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, gtk.gdk.color_parse("white"))
636         id = self.feed.getNextId(index)
637         self.button_clicked(object, id, next=True)
638
639     def previousArticle(self, object, index):
640         label = self.buttons[index].child
641         label.modify_font(pango.FontDescription(self.config.getReadFont()))
642         label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
643         id = self.feed.getPreviousId(index)
644         self.button_clicked(object, id, previous=True)
645
646     def onArticleClosed(self, object, index):
647         label = self.buttons[index].child
648         label.modify_font(pango.FontDescription(self.config.getReadFont()))
649         label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
650         self.buttons[index].show()
651
652     def button_update_clicked(self, button):
653         #bar = DownloadBar(self, self.listing, [self.key,], self.config ) 
654         if not type(self.downloadDialog).__name__=="DownloadBar":
655             self.pannableFeed.destroy()
656             self.vbox = gtk.VBox(False, 10)
657             self.downloadDialog = DownloadBar(self.window, self.listing, [self.key,], self.config, single=True )
658             self.downloadDialog.connect("download-done", self.onDownloadsDone)
659             self.vbox.pack_start(self.downloadDialog, expand=False, fill=False)
660             self.add(self.vbox)
661             self.show_all()
662             
663     def onDownloadsDone(self, *widget):
664         self.vbox.destroy()
665         self.feed = self.listing.getFeed(self.key)
666         self.displayFeed()
667         #self.feed.updateFeed()
668     #    self.clear()
669     #    self.displayFeed()
670         
671     def buttonReadAllClicked(self, button):
672         for index in self.feed.getIds():
673             self.feed.setEntryRead(index)
674             label = self.buttons[index].child
675             label.modify_font(pango.FontDescription(self.config.getReadFont()))
676             label.modify_fg(gtk.STATE_NORMAL, gtk.gdk.color_parse("white"))
677             self.buttons[index].show()
678
679
680 class FeedingIt:
681     def __init__(self):
682         # Init the windows
683         self.window = hildon.StackableWindow()
684         self.window.set_title("FeedingIt")
685         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
686         self.mainVbox = gtk.VBox(False,10)
687         self.pannableListing = gtk.Label("Loading...")
688         self.mainVbox.pack_start(self.pannableListing)
689         self.window.add(self.mainVbox)
690         self.window.show_all()
691         self.config = Config(self.window, CONFIGDIR+"config.ini")
692         gobject.idle_add(self.createWindow)
693         
694     def createWindow(self):
695         self.listing = Listing(CONFIGDIR)
696         
697         self.downloadDialog = False
698         #self.orientation = FremantleRotation("FeedingIt", main_window=self.window)
699         #self.orientation.set_mode(self.config.getOrientation())
700         
701         menu = hildon.AppMenu()
702         # Create a button and add it to the menu
703         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
704         button.set_label("Update All Feeds")
705         button.connect("clicked", self.button_update_clicked, "All")
706         menu.append(button)
707         
708         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
709         button.set_label("Mark All As Read")
710         button.connect("clicked", self.button_markAll)
711         menu.append(button)
712         
713         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
714         button.set_label("Organize Feeds")
715         button.connect("clicked", self.button_organize_clicked)
716         menu.append(button)
717
718         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
719         button.set_label("Preferences")
720         button.connect("clicked", self.button_preferences_clicked)
721         menu.append(button)
722        
723         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
724         button.set_label("Import Feeds")
725         button.connect("clicked", self.button_import_clicked)
726         menu.append(button)
727         
728         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
729         button.set_label("Export Feeds")
730         button.connect("clicked", self.button_export_clicked)
731         menu.append(button)
732         
733         self.window.set_app_menu(menu)
734         menu.show_all()
735         
736         self.feedWindow = hildon.StackableWindow()
737         self.articleWindow = hildon.StackableWindow()
738
739         self.displayListing()
740         self.autoupdate = False
741         self.checkAutoUpdate()
742         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
743
744     def button_markAll(self, button):
745         for key in self.listing.getListOfFeeds():
746             feed = self.listing.getFeed(key)
747             for id in feed.getIds():
748                 feed.setEntryRead(id)
749             feed.saveUnread(CONFIGDIR)
750             self.listing.updateUnread(key, feed.getNumberOfUnreadItems())
751         self.refreshList()
752
753     def button_export_clicked(self, button):
754         opml = ExportOpmlData(self.window, self.listing)
755         
756     def button_import_clicked(self, button):
757         opml = GetOpmlData(self.window)
758         feeds = opml.getData()
759         for (title, url) in feeds:
760             self.listing.addFeed(title, url)
761         self.displayListing()
762
763     def addFeed(self, urlIn="http://"):
764         wizard = AddWidgetWizard(self.window, urlIn)
765         ret = wizard.run()
766         if ret == 2:
767             (title, url) = wizard.getData()
768             if (not title == '') and (not url == ''): 
769                self.listing.addFeed(title, url)
770         wizard.destroy()
771         self.displayListing()
772
773     def button_organize_clicked(self, button):
774         org = SortList(self.window, self.listing)
775         org.run()
776         org.destroy()
777         self.listing.saveConfig()
778         self.displayListing()
779         
780     def button_update_clicked(self, button, key):
781         if not type(self.downloadDialog).__name__=="DownloadBar":
782             self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
783             self.downloadDialog.connect("download-done", self.onDownloadsDone)
784             self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
785             self.mainVbox.show_all()
786         #self.displayListing()
787
788     def onDownloadsDone(self, *widget):
789         self.downloadDialog.destroy()
790         self.downloadDialog = False
791         #self.displayListing()
792         self.refreshList()
793
794     def button_preferences_clicked(self, button):
795         dialog = self.config.createDialog()
796         dialog.connect("destroy", self.prefsClosed)
797
798     def show_confirmation_note(self, parent, title):
799         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
800
801         retcode = gtk.Dialog.run(note)
802         note.destroy()
803         
804         if retcode == gtk.RESPONSE_OK:
805             return True
806         else:
807             return False
808         
809     def displayListing(self):
810         try:
811             self.mainVbox.remove(self.pannableListing)
812         except:
813             pass
814         self.vboxListing = gtk.VBox(False,10)
815         self.pannableListing = hildon.PannableArea()
816         self.pannableListing.add_with_viewport(self.vboxListing)
817
818         self.buttons = {}
819         list = self.listing.getListOfFeeds()[:]
820         #list.reverse()
821         for key in list:
822             #button = gtk.Button(item)
823             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
824                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
825             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
826                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
827             button.set_alignment(0,0,1,1)
828             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
829             self.vboxListing.pack_start(button, expand=False)
830             self.buttons[key] = button
831      
832         self.mainVbox.pack_start(self.pannableListing)
833         self.window.show_all()
834
835     def refreshList(self):
836         for key in self.listing.getListOfFeeds():
837             if self.buttons.has_key(key):
838                 button = self.buttons[key]
839                 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
840                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
841             else:
842                 self.displayListing()
843                 break
844
845     def buttonFeedClicked(widget, button, self, window, key):
846         disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
847         disp.connect("feed-closed", self.onFeedClosed)
848
849     def onFeedClosed(self, object, key):
850         #self.displayListing()
851         self.listing.saveConfig()
852         self.refreshList()
853         #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
854         #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
855         #self.buttons[key].show()
856      
857     def run(self):
858         self.window.connect("destroy", gtk.main_quit)
859         gtk.main()
860         #for key in self.listing.getListOfFeeds():
861         #    self.listing.getFeed(key).saveFeed(CONFIGDIR)
862         self.listing.saveConfig()
863
864     def prefsClosed(self, *widget):
865         self.orientation.set_mode(self.config.getOrientation())
866         self.checkAutoUpdate()
867
868     def checkAutoUpdate(self, *widget):
869         interval = int(self.config.getUpdateInterval()*3600000)
870         if self.config.isAutoUpdateEnabled():
871             if self.autoupdate == False:
872                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
873                 self.autoupdate = interval
874             elif not self.autoupdate == interval:
875                 # If auto-update is enabled, but not at the right frequency
876                 gobject.source_remove(self.autoupdateId)
877                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
878                 self.autoupdate = interval
879         else:
880             if not self.autoupdate == False:
881                 gobject.source_remove(self.autoupdateId)
882                 self.autoupdate = False
883
884     def automaticUpdate(self, *widget):
885         # Need to check for internet connection
886         # If no internet connection, try again in 10 minutes:
887         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
888         self.button_update_clicked(None, None)
889         return True
890     
891     def getStatus(self):
892         status = ""
893         for key in self.listing.getListOfFeeds():
894             if self.listing.getFeedNumberOfUnreadItems(key) > 0:
895                 status += self.listing.getFeedTitle(key) + ": \t" +  str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items\n"
896         if status == "":
897             status = "No unread items"
898         return status
899
900 if __name__ == "__main__":
901     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
902     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
903     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
904     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
905     gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
906     gobject.threads_init()
907     if not isdir(CONFIGDIR):
908         try:
909             mkdir(CONFIGDIR)
910         except:
911             print "Error: Can't create configuration directory"
912             sys.exit(1)
913     app = FeedingIt()
914     dbusHandler = ServerObject(app)
915     app.run()