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