0.2.3-2 OPML import/export plus font config
authorYves <ymarcoz@n900-sdk.(none)>
Mon, 18 Jan 2010 16:02:25 +0000 (08:02 -0800)
committerYves <ymarcoz@n900-sdk.(none)>
Mon, 18 Jan 2010 16:02:25 +0000 (08:02 -0800)
Makefile
debian/changelog
debian/control
src/FeedingIt.py
src/opml.py
src/rss.py

index ec3788c..afd916f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,6 +12,7 @@ install:
        install src/feedparser.py ${DESTDIR}/opt/FeedingIt
        install src/portrait.py ${DESTDIR}/opt/FeedingIt
        install src/rss.py ${DESTDIR}/opt/FeedingIt
+       install src/opml.py ${DESTDIR}/opt/FeedingIt
        install src/feedingitdbus.py ${DESTDIR}/opt/FeedingIt
        install -d ${DESTDIR}/usr/share/applications/hildon
        install src/FeedingIt.desktop ${DESTDIR}/usr/share/applications/hildon
index 2167aa0..56c5823 100644 (file)
@@ -1,3 +1,22 @@
+feedingit (0.2.3-2) unstable; urgency=low
+
+  * Fixed missing python-osso dependency
+  
+ -- Yves <yves@marcoz.org>  Sat, 17 Jan 2010 13:31:19 -0800
+
+feedingit (0.2.3-1) unstable; urgency=low
+
+  * Added Font size option for feeds
+  * Added option to enable horizontal scrolling of articles (disabling gestures)
+  
+ -- Yves <yves@marcoz.org>  Sat, 17 Jan 2010 13:31:19 -0800
+
+feedingit (0.2.2-1) unstable; urgency=low
+
+  * Added support for Import/Export of OPML files
+  
+ -- Yves <yves@marcoz.org>  Sat, 16 Jan 2010 14:38:19 -0800
+
 feedingit (0.2.1-2) unstable; urgency=low
 
   * Fixed issue with feeds without dates (#5019)
index c6d0da0..e422bb8 100644 (file)
@@ -9,7 +9,7 @@ XSBC-Bugtracker: https://garage.maemo.org/tracker/?func=browse&group_id=1202&ati
 
 Package: feedingit
 Architecture: any
-Depends: ${shlibs:Depends}, ${misc:Depends}, python-gtkhtml2, python, python-hildon, libgtkhtml2-0, python-dbus
+Depends: ${shlibs:Depends}, ${misc:Depends}, python-gtkhtml2, python, python-hildon, libgtkhtml2-0, python-dbus, python-osso
 Description: Simple RSS Reader
  Simple RSS Reader, with portrait mode support, and swipe gestures in articles
 XB-Maemo-Icon-26:
index 5a654dd..3a57906 100644 (file)
@@ -42,6 +42,7 @@ import thread
 from feedingitdbus import ServerObject
 
 from rss import *
+from opml import GetOpmlData, ExportOpmlData
    
 class AddWidgetWizard(hildon.WizardDialog):
     
@@ -154,7 +155,7 @@ class DownloadDialog():
             fraction = float(fin)/float(self.total) + float(x)/(self.total*2.)
             #print x, k, fin, fraction
             self.progress.set_fraction(fraction)
-            
+
             if len(self.listOfKeys)>0:
                 self.current = self.current+1
                 key = self.listOfKeys.pop()
@@ -223,10 +224,8 @@ class SortList(gtk.Dialog):
         #self.show_all()
 
     def refreshList(self, selected=None, offset=0):
-        #x = self.treeview.get_visible_rect().x
         rect = self.treeview.get_visible_rect()
         y = rect.y+rect.height
-        #self.pannableArea.jump_to(-1, 0)
         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
         for key in self.listing.getListOfFeeds():
             item = self.treestore.append([self.listing.getFeedTitle(key), key])
@@ -236,7 +235,6 @@ class SortList(gtk.Dialog):
         if not selected == None:
             self.treeview.get_selection().select_iter(selectedItem)
             self.treeview.scroll_to_cell(self.treeview.get_model().get_path(selectedItem))
-            #self.pannableArea.jump_to(-1, y+offset)
         self.pannableArea.show_all()
 
     def getSelectedItem(self):
@@ -263,26 +261,12 @@ class SortList(gtk.Dialog):
         if not key == None:
             self.listing.moveUp(key)
             self.refreshList(key, -10)
-        #placement = self.pannableArea.get_placement()
-        #placement = self.treeview.get_visible_rect().y
-        #self.displayFeeds(key)
-        
-        #self.treeview.scroll_to_point(-1, y)
-        #self.pannableArea.set_placement(placement)
 
     def buttonDown(self, button):
         key = self.getSelectedItem()
         if not key == None:
             self.listing.moveDown(key)
-            #(before, after) = self.findIndex(key)
-            #self.treestore.move_after(iter, after)
-            #self.treeview.set_model(self.treestore)
-            #self.treeview.show_all()
             self.refreshList(key, 10)
-            
-        #placement = self.pannableArea.get_placement()
-        #self.displayFeeds(key)
-        #self.pannableArea.set_placement(placement)
 
     def buttonDelete(self, button):
         key = self.getSelectedItem()
@@ -307,7 +291,7 @@ class DisplayArticle(hildon.StackableWindow):
         self.pannable_article = hildon.PannableArea()
         self.pannable_article.add(self.view)
         #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
-        self.pannable_article.connect('horizontal-movement', self.gesture)
+        self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
         self.document = gtkhtml2.Document()
         self.view.set_document(self.document)
         self.document.connect("link_clicked", self._signal_link_clicked)
@@ -320,8 +304,8 @@ class DisplayArticle(hildon.StackableWindow):
         menu = hildon.AppMenu()
         # Create a button and add it to the menu
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Display Images")
-        button.connect("clicked", self.reloadArticle)
+        button.set_label("Allow Horizontal Scrolling")
+        button.connect("clicked", self.horiz_scrolling_button)
         
         menu.append(button)
         self.set_app_menu(menu)
@@ -330,10 +314,10 @@ class DisplayArticle(hildon.StackableWindow):
         self.add(self.pannable_article)
         
         self.show_all()
-        
+
         self.destroyId = self.connect("destroy", self.destroyWindow)
         self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
-        
+
     def gesture(self, widget, direction, startx, starty):
         if (direction == 3):
             self.emit("article-next", self.index)
@@ -345,6 +329,10 @@ class DisplayArticle(hildon.StackableWindow):
         self.emit("article-closed", self.index)
         self.destroy()
         
+    def horiz_scrolling_button(self, *widget):
+        self.pannable_article.disconnect(self.gestureId)
+        self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
+        
     def reloadArticle(self, *widget):
         if threading.activeCount() > 1:
             # Image thread are still running, come back in a bit
@@ -410,9 +398,13 @@ class DisplayFeed(hildon.StackableWindow):
             button.set_alignment(0,0)
             label = button.child
             if self.feed.isEntryRead(index):
-                label.modify_font(pango.FontDescription("sans 16"))
+                #label.modify_font(pango.FontDescription("sans 16"))
+                label.modify_font(pango.FontDescription(self.listing.getReadFont()))
             else:
-                label.modify_font(pango.FontDescription("sans bold 16"))
+                #print self.listing.getFont() + " bold"
+                label.modify_font(pango.FontDescription(self.listing.getUnreadFont()))
+                #label.modify_font(pango.FontDescription("sans bold 23"))
+                #"sans bold 16"
             label.set_line_wrap(True)
             
             label.set_size_request(self.get_size()[0]-50, -1)
@@ -437,19 +429,19 @@ class DisplayFeed(hildon.StackableWindow):
 
     def nextArticle(self, object, index):
         label = self.buttons[index].child
-        label.modify_font(pango.FontDescription("sans 16"))
+        label.modify_font(pango.FontDescription(self.listing.getReadFont()))
         index = (index+1) % self.feed.getNumberOfEntries()
         self.button_clicked(object, index)
 
     def previousArticle(self, object, index):
         label = self.buttons[index].child
-        label.modify_font(pango.FontDescription("sans 16"))
+        label.modify_font(pango.FontDescription(self.listing.getReadFont()))
         index = (index-1) % self.feed.getNumberOfEntries()
         self.button_clicked(object, index)
 
     def onArticleClosed(self, object, index):
         label = self.buttons[index].child
-        label.modify_font(pango.FontDescription("sans 16"))
+        label.modify_font(pango.FontDescription(self.listing.getReadFont()))
         self.buttons[index].show()
 
     def button_update_clicked(self, button):
@@ -462,7 +454,7 @@ class DisplayFeed(hildon.StackableWindow):
         for index in range(self.feed.getNumberOfEntries()):
             self.feed.setEntryRead(index)
             label = self.buttons[index].child
-            label.modify_font(pango.FontDescription("sans 16"))
+            label.modify_font(pango.FontDescription(self.listing.getReadFont()))
             self.buttons[index].show()
 
 
@@ -486,13 +478,23 @@ class FeedingIt:
         menu.append(button)
         
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Delete Feed")
-        button.connect("clicked", self.button_delete_clicked)
+        button.set_label("Organize Feeds")
+        button.connect("clicked", self.button_organize_clicked)
+        menu.append(button)
+
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label("Listing Font Size")
+        button.connect("clicked", self.button_font_clicked)
+        menu.append(button)
+       
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label("Import Feeds")
+        button.connect("clicked", self.button_import_clicked)
         menu.append(button)
         
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Organize Feeds")
-        button.connect("clicked", self.button_organize_clicked)
+        button.set_label("Export Feeds")
+        button.connect("clicked", self.button_export_clicked)
         menu.append(button)
         
         self.window.set_app_menu(menu)
@@ -502,7 +504,17 @@ class FeedingIt:
         self.articleWindow = hildon.StackableWindow()
 
         self.displayListing() 
+
+    def button_export_clicked(self, button):
+        opml = ExportOpmlData(self.window, self.listing)
         
+    def button_import_clicked(self, button):
+        opml = GetOpmlData(self.window)
+        feeds = opml.getData()
+        for (title, url) in feeds:
+            self.listing.addFeed(title, url)
+        self.displayListing()
+
     def button_organize_clicked(self, button):
         org = SortList(self.window, self.listing)
         org.run()
@@ -524,10 +536,11 @@ class FeedingIt:
         disp = DownloadDialog(self.window, self.listing, self.listing.getListOfFeeds() )           
         self.displayListing()
 
-    def button_delete_clicked(self, button):
+    def button_font_clicked(self, button):
         self.pickerDialog = hildon.PickerDialog(self.window)
         #HildonPickerDialog
-        self.pickerDialog.set_selector(self.create_selector())
+        selector = self.create_selector()
+        self.pickerDialog.set_selector(selector)
         self.pickerDialog.show_all()
         
     def create_selector(self):
@@ -535,25 +548,22 @@ class FeedingIt:
         # Selection multiple
         #selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE)
         self.mapping = {}
-        selector.connect("changed", self.selection_changed)
-
-        for key in self.listing.getListOfFeeds():
-            title=self.listing.getFeedTitle(key)
-            selector.append_text(title)
-            self.mapping[title]=key
 
+        current_size = self.listing.getFontSize()
+        index = 0
+        for size in range(12,24):
+            iter = selector.append_text(str(size))
+            if str(size) == current_size: 
+                selector.set_active(0, index)
+            index += 1
+        selector.connect("changed", self.selection_changed)
         return selector
 
     def selection_changed(self, widget, data):
         current_selection = widget.get_current_text()
-        #print 'Current selection: %s' % current_selection
-        #print "To Delete: %s" % self.mapping[current_selection]
+        if current_selection:
+            self.listing.setFont(current_selection)
         self.pickerDialog.destroy()
-        if self.show_confirmation_note(self.window, current_selection):
-            self.listing.removeFeed(self.mapping[current_selection])
-            self.listing.saveConfig()
-        del self.mapping
-        self.displayListing()
 
     def show_confirmation_note(self, parent, title):
         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
index fb4d937..8593479 100644 (file)
 # ============================================================================
 
 from xml.dom.minidom import parse, parseString
+import urllib2
+import gtk
+import hildon
+import gobject
+import time
+from os.path import isfile, dirname
+import gobject
+
+class ExportOpmlData():
+    def __init__(self, parent, listing):
+        fs = hildon.FileSystemModel()
+        dialog = hildon.FileChooserDialog(parent, gtk.FILE_CHOOSER_ACTION_SAVE, fs)
+                               #(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+                                #gtk.STOCK_SAVE, gtk.RESPONSE_OK))
+                               #)
+        #dialog = gobject.new(hildon.FileChooserDialog, \
+        #            action=gtk.FILE_CHOOSER_ACTION_SAVE)
+        #dialog.set_default_response(gtk.RESPONSE_OK)
+        #dialog.set_property('autonaming',False)
+        #dialog.set_property('show-files',True)
+        dialog.set_current_folder('/home/user/MyDocs/')
+        dialog.set_current_name('feedingit-export')
+        dialog.set_extension('opml')
+        response = dialog.run()
+        dialog.hide()
+        if response == gtk.RESPONSE_OK:
+            filename = dialog.get_filename()
+            print filename
+            try:
+
+                cont = True
+                if isfile(filename):
+                    note = "File already exists. Aborted"
+                    confirm = hildon.Note ("confirmation", parent, "File already exists. Are you sure you want to overwrite it?", gtk.STOCK_DIALOG_WARNING )
+                    confirm.set_button_texts ("Yes", "Cancel")
+                    response = confirm.run()
+                    confirm.destroy()
+                    if response == gtk.RESPONSE_OK:
+                        cont = True
+                    else:
+                        note = "Operation cancelled."
+                        cont = False
+                if cont:
+                    file = open(filename, "w")
+                    file.write(self.getOpmlText(listing))
+                    file.close()
+                    note = "Feeds exported to %s" %filename
+            except:
+                note = "Failed to export feeds"
+            
+            dialog.destroy()
+            dialog = hildon.Note ("information", parent, note , gtk.STOCK_DIALOG_INFO )
+            dialog.run()
+            dialog.destroy()
+        elif response == gtk.RESPONSE_CANCEL:
+            dialog.destroy()  
+
+    def getOpmlText(self, listing):
+        time_now = time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())
+        opml_text = """<?xml version="1.0" encoding="UTF-8"?>
+<opml version="1.0">
+<head>
+    <title>Feeding It Export</title>
+</head>
+<body>
+"""
+        for key in listing.getListOfFeeds():
+            title = listing.getFeedTitle(key)
+            url = listing.getFeedUrl(key)
+            opml_text += """\n\t\t<outline  type="rss" text="%s" title="%s" xmlUrl="%s"/>""" % (title, title, url)
+        opml_text += """\n</body>\n</opml>\n"""
+        return opml_text
+        
+
+class GetOpmlData():
+    def __init__(self, parent):
+        self.parent = parent
+        dialog = hildon.Note ("confirmation", parent, "What type of OPML?", gtk.STOCK_DIALOG_WARNING )
+        dialog.set_button_texts ("File", "URL")
+        response = dialog.run()
+        dialog.destroy()
+    
+        if response == gtk.RESPONSE_OK:
+            # Choose a file
+            self.data = self.askForFile()
+        else:
+            # Download a URL
+            self.data = self.downloadFile()
+            
+    def getData(self):
+        if not self.data == None:
+               dialog = OpmlDialog(self.parent, self.data)
+               response = dialog.run()
+               if response == gtk.RESPONSE_ACCEPT:
+                   items = dialog.getItems()
+               else:
+                   items = []
+               dialog.destroy()
+               return items
+        return []
+
+    def downloadFile(self):
+        dlg = gtk.Dialog("OPML Import", self.parent, gtk.DIALOG_DESTROY_WITH_PARENT,
+                     (gtk.STOCK_OK, gtk.RESPONSE_OK,
+                      gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
+        lbl = gtk.Label("Enter the URL of the OPML file:")
+        lbl.show()
+        dlg.vbox.pack_start(lbl)
+        entry = gtk.Entry()
+        entry.set_text("http://")
+        entry.select_region(0,-1)
+        entry.show()
+        dlg.vbox.pack_start(entry, False)
+
+        resp = dlg.run()
+        url = entry.get_text()
+        dlg.destroy()
+        if resp == gtk.RESPONSE_CANCEL:
+            return None
+        try:
+            f = urllib2.urlopen(url)
+            data = f.read()
+            f.close()
+        except:
+            #Show error note
+            return None
+        return data
+
+    def askForFile(self):
+        #dialog = hildon.FileChooserDialog(self.parent,
+        #                       gtk.FILE_CHOOSER_ACTION_OPEN)
+        #dialog = gobject.new(hildon.FileChooserDialog, \
+        #            action=gtk.FILE_CHOOSER_ACTION_OPEN)
+        #dialog.set_default_response(gtk.RESPONSE_OK)
+        fs = hildon.FileSystemModel()
+        dialog = hildon.FileChooserDialog(self.parent, gtk.FILE_CHOOSER_ACTION_OPEN, fs)
+        
+        filter = gtk.FileFilter()
+        filter.set_name("All files")
+        filter.add_pattern("*")
+        dialog.add_filter(filter)
+
+        filter = gtk.FileFilter()
+        filter.set_name("OPML")
+        filter.add_pattern("*.xml")
+        filter.add_pattern("*.opml")
+        dialog.add_filter(filter)
+
+        response = dialog.run()
+        if response == gtk.RESPONSE_OK:
+            file = open(dialog.get_filename())
+            data = file.read()
+            file.close()
+            dialog.destroy()
+            return data
+        elif response == gtk.RESPONSE_CANCEL:
+            dialog.destroy()
+            return None
+
 
 class OpmlDialog(gtk.Dialog):
     def parse(self, opmlData):
-       self.feeds = []
-       dom1 = parseString(self.opmlData)
+        self.feeds = []
+        dom1 = parseString(opmlData)
        
-       outlines = dom1.getElementsByTagName('outline')
-       for outline in outlines:
-         title = outline.getAttribute('text')
-         url = outline.getAttribute('xmlUrl')
-         if url == "":
-           url = outline.getAttribute('htmlUrl')
-         self.feeds.append(title, url)
+        outlines = dom1.getElementsByTagName('outline')
+        for outline in outlines:
+            title = outline.getAttribute('text')
+            url = outline.getAttribute('xmlUrl')
+            if url == "":
+                url = outline.getAttribute('htmlUrl')
+            if not url == "":
+                self.feeds.append( (title, url) )
        
     def getFeedLinks(self):
-       return self.feeds
+        return self.feeds
        
-    def __init__(self, parent, opmlData)
-       self.parse(opmlData)
-    
-       gtk.Dialog.__init__(self, "Import OPML Feeds",  parent)
-        
-        self.vbox2 = gtk.VBox(False, 10)
+    def __init__(self, parent, opmlData):
+        self.parse(opmlData)
+        gtk.Dialog.__init__(self, "Select OPML Feeds",  parent, gtk.DIALOG_DESTROY_WITH_PARENT, (gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
         
-        self.hbox2= gtk.HBox(False, 10)
         self.pannableArea = hildon.PannableArea()
         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
         self.treeview = gtk.TreeView(self.treestore)
-        self.hbox2.pack_start(self.pannableArea, expand=True)
+
         self.displayFeeds()
-        self.hbox2.pack_end(self.vbox2, expand=False)
+
         self.set_default_size(-1, 600)
-        self.vbox.pack_start(self.hbox2)
+        self.vbox.pack_start(self.pannableArea)
+        
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label("Select All")
+        button.connect("clicked", self.button_select_all_clicked)
+        self.action_area.pack_end(button)
+        
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label("Unselect All")
+        button.connect("clicked", self.button_select_none_clicked)
+        self.action_area.pack_end(button)
         
         self.show_all()
         
+    def button_select_all_clicked(self, button):
+        self.treeview.get_selection().select_all()
+        
+    def button_select_none_clicked(self, button):
+        self.treeview.get_selection().unselect_all()
+        
     def displayFeeds(self):
         self.treeview.destroy()
         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
         self.treeview = gtk.TreeView()
         
-        self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
+        self.treeview.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
         hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_EDIT)
         self.refreshList()
         self.treeview.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
 
         self.pannableArea.add(self.treeview)
-
+        self.pannableArea.show_all()
+        self.treeview.get_selection().select_all()
 
     def refreshList(self, selected=None, offset=0):
-        #x = self.treeview.get_visible_rect().x
         rect = self.treeview.get_visible_rect()
         y = rect.y+rect.height
-        #self.pannableArea.jump_to(-1, 0)
         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
-        for (title, url) in self.feeds():
-            item = self.treestore.append([title, url])
-           self.treeview.get_selection().select_iter(item)
         self.treeview.set_model(self.treestore)
+        for (title, url) in self.feeds:
+            item = self.treestore.append([title, url])
+            self.treeview.get_selection().select_iter(item)
+        #self.treeview.get_selection().select_all()
         self.pannableArea.show_all()
 
-    def getSelectedItem(self):
-        (model, iter) = self.treeview.get_selection().get_selected()
-        if not iter:
-            return None
-        return model.get_value(iter, 1)
-
-    def findIndex(self, key):
-        after = None
-        before = None
-        found = False
-        for row in self.treestore:
-            if found:
-                return (before, row.iter)
-            if key == list(row)[0]:
-                found = True
-            else:
-                before = row.iter
-        return (before, None)
+    def getItems(self):
+        list = []
+        treeselection = self.treeview.get_selection()
+        (model, pathlist) = treeselection.get_selected_rows()
+        for path in pathlist:
+            list.append( (model.get_value(model.get_iter(path),0), model.get_value(model.get_iter(path),1)) )
+        return list
 
-    def buttonDone(self, *args):
-        self.destroy()
+def showOpmlData(widget, parent, button):
+    dialog = GetOpmlData(parent)
+    print dialog.getData()
+    #dialog.destroy()
 
+if __name__ == "__main__":
+    window = hildon.Window()
+    window.set_title("Test App")
 
+    
+    button = gtk.Button("Click to confirm.")
+    window.add(button)
+    button.connect("clicked", showOpmlData, window, button)
+    window.connect("destroy", gtk.main_quit)
+    window.show_all()
 
+    gtk.main()
+    window.destroy()
index fef2ad1..6d70219 100644 (file)
@@ -40,12 +40,20 @@ class Feed:
     # Contains all the info about a single feed (articles, ...), and expose the data
     def __init__(self, name, url):
         self.entries = []
+        self.fontSize = "+0"
         self.readItems = {}
         self.countUnread = 0
         self.name = name
         self.url = url
         self.updateTime = "Never"
 
+    def getFontSize(self):
+        try:
+            return self.fontSize
+        except:
+            self.fontSize = "+0"
+            return self.fontSize
+
     def saveFeed(self):
         file = open(CONFIGDIR+getId(self.name), "w")
         pickle.dump(self, file )
@@ -136,10 +144,10 @@ class Feed:
             date = ""
         #text = '''<div style="color: black; background-color: white;">'''
         text = '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>'
-        text = text + '<div><a href=\"' + link + '\">' + title + "</a>"
-        text = text + "<BR /><small><i>Date: " + date + "</i></small></div>"
-        text = text + "<BR /><BR />"
-        text = text + content
+        text += '<div><a href=\"' + link + '\">' + title + "</a>"
+        text += "<BR /><small><i>Date: " + date + "</i></small></div>"
+        text += "<BR /><BR />"
+        text += content
         return text    
 
 
@@ -153,22 +161,43 @@ class Listing:
             file.close()
         else:
             self.listOfFeeds = {getId("Slashdot"):{"title":"Slashdot", "url":"http://rss.slashdot.org/Slashdot/slashdot"}, }
+        if not self.listOfFeeds.has_key("font"):
+            self.setFont("16")
         if self.listOfFeeds.has_key("feedingit-order"):
             self.sortedKeys = self.listOfFeeds["feedingit-order"]
         else:
             self.sortedKeys = self.listOfFeeds.keys()
+            if "font" in self.sortedKeys:
+                self.sortedKeys.remove("font")
             self.sortedKeys.sort(key=lambda obj: self.getFeedTitle(obj))
         for key in self.sortedKeys:
-            self.loadFeed(key)
+            try:
+                self.loadFeed(key)
+            except:
+                self.sortedKeys.remove(key)
         #self.saveConfig()
         
+    def getFontSize(self):
+        return self.listOfFeeds["font"]
+    
+    def getReadFont(self):
+        return "sans %s" % self.listOfFeeds["font"]
+    
+    def getUnreadFont(self):
+        return "sans bold %s" % self.listOfFeeds["font"]
+    
+    def setFont(self, fontname):
+        self.listOfFeeds["font"] = fontname
+        
     def loadFeed(self, key):
             if isfile(CONFIGDIR+key):
                 file = open(CONFIGDIR+key)
                 self.feeds[key] = pickle.load(file)
                 file.close()
             else:
-                self.feeds[key] = Feed(self.listOfFeeds[key]["title"], self.listOfFeeds[key]["url"])
+                title = self.listOfFeeds[key]["title"]
+                url = self.listOfFeeds[key]["url"]
+                self.feeds[key] = Feed(title, url)
         
     def updateFeeds(self):
         for key in self.getListOfFeeds():
@@ -196,10 +225,11 @@ class Listing:
         return self.sortedKeys
     
     def addFeed(self, title, url):
-        self.listOfFeeds[getId(title)] = {"title":title, "url":url}
-        self.sortedKeys.append(getId(title))
-        self.saveConfig()
-        self.feeds[getId(title)] = Feed(title, url)
+        if not self.listOfFeeds.has_key(getId(title)):
+            self.listOfFeeds[getId(title)] = {"title":title, "url":url}
+            self.sortedKeys.append(getId(title))
+            self.saveConfig()
+            self.feeds[getId(title)] = Feed(title, url)
         
     def removeFeed(self, key):
         del self.listOfFeeds[key]