#!/usr/local/bin/python2.5
'''GutenPy

A comfortable reader and catalog browser for Project Gutenberg.'''
# GutenPy - gutenpy.py
# Copyright (C) 2006 Lee Bigelow <ligelowbee@yahoo.com> 
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

import sys, os, re, cPickle, webbrowser
import gtk, gobject, pango
import gzip, zipfile, bz2
from platform import system
from GutenPy import dictclient, rdfparse, catalog
from GutenPy.rdfparse import Ebook
from GutenPy.widgets import *
from GutenPy.bookmarks import *
from GutenPy.charset_widget import *

TEXT_RE = re.compile('.*\.txt$', re.I)
GZIP_RE = re.compile('.*\.gz$', re.I)
BZ2_RE = re.compile('.*\.bz2$', re.I)
ZIP_RE = re.compile('.*\.zip$', re.I)

(FONT, BG, FG, BOOKDIR, CFGDIR, WEBPAGE,
 MIRROR, CATALOG, DICT_SERV, DICT_PORT) = range(10)
(CONFIRM_QUIT, AUTO_SAVE, REMEMBER_OPEN) = range(3)

UI_INFO='''
<ui>
  <menubar name='MenuBar'>
    <menu action='FileMenu'>
      <menuitem action='Open'/>
      <menuitem action='ViewBookmarks'/>
      <menuitem action='Charset'/>
      <menuitem action='Close'/>
      <separator/>
      <menuitem action='Quit'/>
    </menu>
    <menu action='BookmarkMenu'>
      <menuitem action='ViewBookmarks'/>
      <menuitem action='AddBookmark'/>
    </menu>
    <menu action='NavMenu'>
      <menuitem action='Find'/>
      <menuitem action='PageTop'/>
      <menuitem action='PageUp'/>
      <menuitem action='PageDown'/>
      <menuitem action='PageBottom'/>
    </menu>
    <menu action='ToolsMenu'>
      <menuitem action='Dict'/>
      <menuitem action='Web'/>
      <menuitem action='Cat'/>
      <menuitem action='UpdateCat'/>
      <menuitem action='Prefs'/>
    </menu>
    <menu action='ViewMenu'>
      <menuitem action='FullScreen'/>
      <menuitem action='ShowToolbar'/>
      <menuitem action='ShowSidebar'/>
      <menuitem action='WrapLines'/>
      <menuitem action='ShowSep'/>
      <menuitem action='LineNum'/>
    </menu>
    <menu action='HelpMenu'>
      <menuitem action='About'/>
      <menuitem action='Help'/>
    </menu>
  </menubar>
  <toolbar  name='ToolBar'>
    <toolitem action='Open'/>
    <toolitem action='ShowSidebar'/>
    <toolitem action='Charset'/>
    <toolitem action='Close'/>
    <separator />
    <toolitem action='AddBookmark'/>
    <toolitem action='ViewBookmarks'/>
    <separator />
    <toolitem action='PageTop'/>
    <toolitem action='PageUp'/>
    <toolitem action='PageDown'/>
    <toolitem action='PageBottom'/>
    <toolitem action='Find'/>
    <separator />
    <toolitem action='Dict'/>
    <toolitem action='Web'/>
    <toolitem action='Cat'/>
    <toolitem action='Prefs'/>
    <separator />
    <toolitem action='Quit'/>
  </toolbar>
</ui>
'''
class AppPrefs:
    def __init__(self):
        self.width = 650
        self.height = 550
        self.bg = (65535,65535,65535)
        self.fg = (0,0,0)
        self.webpage = "http://www.gutenberg.org/catalog"
        self.mirror = "http://www.gutenberg.org/dirs"
        self.catalog = "http://www.gutenberg.org/feeds/catalog.rdf.bz2"
        self.dict_serv = 'www.dict.org'
        self.dict_port = 2628
        self.wrap_lines = True
        self.number_lines = False
        self.show_separator = False
        self.confirm_quit = True
        self.auto_save_bookmarks = False
        self.remember_open_files = True
        self.open_filelist = []
        self.remember_author = True
        self.remember_title = False
        self.remember_subject = False
        self.remember_lang = False
        self.author_filter = '^a'
        self.title_filter = ''
        self.subject_filter = ''
        self.lang_filter = ''
        if 'Windows' in system():
            self.font = "Arial 12"
            self.bookdir = os.path.expanduser("~\\My Documents\\My eBooks")
            self.cfgdir = os.path.expanduser("~\\Application Data\\Gutenpy")
        else:
            self.font = "Sans 12"
            self.bookdir = os.path.expanduser("~/ebooks")
            self.cfgdir = os.path.expanduser("~/.gutenpy")

        
class AppMainWindow(gtk.Window):
    def __init__(self, argv, parent=None):
        gtk.Window.__init__(self)
        self.connect('delete-event', self.quit_cb)
        self.connect('key-press-event', self.main_key_press_event_handler)
        self.connect('configure-event', self.configure_event_handler)
        self.app_name = 'GutenPy'
        self.app_version = '0.3.0'
        self.set_title('%s %s' % (self.app_name, self.app_version))
        self.search_string=''
        self.catalog_dialog=None

        filelist=[]
        for arg in argv[1:]:
            filelist.append(os.path.realpath(arg))

        # if run from a symlink change to directory main script is run from
        if not (os.path.isdir('GutenPy') or os.path.isfile('gutenpy.exe')):
            os.chdir(os.path.dirname(os.path.realpath(argv[0])))
        # check if running from source directory
        if os.path.isdir('GutenPy') or os.path.isfile('gutenpy.exe'):
            icon_dir = 'icons'
            doc_dir = 'docs'
        # other wise check for installed directories
        else:
            icon_dir = os.path.join(sys.prefix, 'share/pixmaps/gutenpy')
            doc_dir = os.path.join(sys.prefix, 'share/doc/gutenpy')
        if not os.path.isdir(doc_dir):
            print "Error, doc directory not found:", doc_dir
        if not os.path.isdir(icon_dir):
            print "Error, icon directory not found:", icon_dir
        
        self.helpfile = os.path.join(doc_dir, 'help.txt')
        self.license =  os.path.join(doc_dir, 'COPYING.txt')

        # load up the icons
        gtk.window_set_default_icon_list(
            gtk.gdk.pixbuf_new_from_file(os.path.join(icon_dir,'book-lg.png')),
            gtk.gdk.pixbuf_new_from_file(os.path.join(icon_dir,'book-sm.png')))
        factory = gtk.IconFactory()
        factory.add('gutenberg_org',
          gtk.IconSet(gtk.gdk.pixbuf_new_from_file(
            os.path.join(icon_dir,'gutenberg_org.png'))))
        factory.add('bookmark',
          gtk.IconSet(gtk.gdk.pixbuf_new_from_file(
            os.path.join(icon_dir,'bookmark.png'))))
        factory.add('sidebar',
          gtk.IconSet(gtk.gdk.pixbuf_new_from_file(
            os.path.join(icon_dir, 'sidebar.png'))))
        factory.add('dict_org',
            gtk.IconSet(gtk.gdk.pixbuf_new_from_file(
            os.path.join(icon_dir, 'dict_org.png'))))
        factory.add_default()            

        self.prefs = AppPrefs()
        self.bookmarks = gtk.TreeStore(str, str, int, str)

        cfgfile = os.path.join(self.prefs.cfgdir, "config.pickle")
        if os.path.isfile(cfgfile):
            self.load_prefs(cfgfile)

        bmfile = os.path.join(self.prefs.cfgdir, "bookmarks.pickle")
        if os.path.isfile(bmfile):
            self.load_bookmarks(bmfile)
            
        for directory in [self.prefs.bookdir, self.prefs.cfgdir]:
            if not os.path.isdir(directory):
                os.makedirs(directory)

        
        self.uimanager = gtk.UIManager()
        self.set_data("ui-manager", self.uimanager)
        self.uimanager.insert_action_group(self.__create_action_group(), 0)
        self.add_accel_group(self.uimanager.get_accel_group())

        try:
            self.uimanagerid = self.uimanager.add_ui_from_string(UI_INFO)
        except gobject.GError, msg:
            print "building menus failed: %s" % msg

        vbox = gtk.VBox()
        self.add(vbox)

        bar = self.uimanager.get_widget("/MenuBar")
        vbox.pack_start(bar, False)

        toolbar = self.uimanager.get_widget("/ToolBar")
        toolbar.set_tooltips(True)
        toolbar.set_show_arrow(True)
        toolbar.set_style(gtk.TOOLBAR_ICONS)
        vbox.pack_start(toolbar, False)

        self.hpane = gtk.HPaned()
        
        vbox.pack_start(self.hpane)
        self.hpane.set_position(250)
        
        self.sidebar = self.get_sidebar()
        self.hpane.pack1(self.sidebar)

        frame = gtk.Frame()
        self.hpane.pack2(frame)
        frame.set_shadow_type(gtk.SHADOW_IN)
        self.notebook = gtk.Notebook()
        self.notebook.set_scrollable(True)
        frame.add(self.notebook)
        
        self.set_default_size(self.prefs.width, self.prefs.height)
        self.show_all()
        self.sidebar.hide()
        
        if filelist:
            for filename in filelist:
                self.open_func(filename)
        elif self.prefs.open_filelist and self.prefs.remember_open_files:
            for filename in self.prefs.open_filelist:
                self.open_func(filename)


    def __create_action_group(self):
        entries = (
          ( 'FileMenu', None, '_File' ),
          ( 'BookmarkMenu', None, '_Bookmarks' ),
          ( 'NavMenu', None, '_Navigation' ),
          ( 'ToolsMenu', None, '_Tools' ),
          ( 'ViewMenu', None, '_View' ),
          ( 'HelpMenu', None, '_Help' ),
          ( 'Open', gtk.STOCK_OPEN,
            '_Open','O',
            'Open a file',
            self.open_cb ),
          ( 'Charset', gtk.STOCK_SELECT_FONT,
            'Change Charset', None,
            'Change charset used to decode current file',
            self.charset_cb ),
          ( 'Close', gtk.STOCK_CLOSE,
            'Close file','Q',
            'Close current file window',
            self.close_cb ),
          ( 'Quit', gtk.STOCK_QUIT,
            '_Quit Gutenpy', '<control>Q',
            'Quit Gutenpy',
            self.quit_cb ),
          ( 'ViewBookmarks', 'bookmark',
            'View _Bookmarks','B',
            'View Bookmarks',
            self.view_bookmarks_cb ),
          ( 'AddBookmark', gtk.STOCK_ADD,
            '_Add Bookmark', 'A',
            'Add a bookmark for the current file',
            self.add_bookmark_cb ),
          ( 'Find', gtk.STOCK_FIND,
            'Find text', 'slash',
            'Find text in file',
            self.find_text_cb ),
          ( 'PageTop', gtk.STOCK_GOTO_TOP,
            'Top', 'Home',
            'Goto top of file',
            self.scroll_cb ),
          ( 'PageUp', gtk.STOCK_GO_UP,
            'Up', 'BackSpace',
            'Move up one page',
            self.scroll_cb ),
          ( 'PageDown', gtk.STOCK_GO_DOWN,
            'Down', 'space',
            'Move down one page',
            self.scroll_cb ),
          ( 'PageBottom', gtk.STOCK_GOTO_BOTTOM,
            'Bottom', 'End',
            'Goto bottom of file',
            self.scroll_cb ),
          ( 'Prefs', gtk.STOCK_PREFERENCES,
            '_Preferences', '<control>P',
            'Change Gutenpy preferences',
            self.preferences_cb ),
          ( 'Web', 'gutenberg_org',
            'Visit Project _Gutenberg webpage', '<control>g',
            'Visit Project Gutenberg webpage',
            self.goto_webpage_cb),
          ( 'Cat', gtk.STOCK_INDEX,
            'Browse Cached _Catalog', '<control>c',
            'Browse the cached Project Gutenberg catalog',
            self.browse_catalog_cb),
          ( 'UpdateCat', None,
            '_Update the cached catalog', '<control>u',
            'Download and update the cached catalog',
            self.update_catalog_cb),
          ( 'Dict' , 'dict_org',
            '_Definition from Dict server', 'd',
            'Look up a word definiton from a dict server',
            self.dict_lookup_cb),
          ( 'About', gtk.STOCK_ABOUT,
            'About', None,
            'About',
            self.about_cb ),
          ( 'Help', gtk.STOCK_HELP,
            'View _Help', 'h',
            'View help in webbrowser',
            self.help_cb ),
        )

        toggle_entries = (
          ( 'LineNum' , None,
            'Number _lines', 'l',
            'Look up a word definiton from a dict server',
            self.number_lines_cb,
            self.prefs.number_lines),
          ( 'WrapLines' , None,
            '_Wrap long lines', 'w',
            'Wrap long lines of text',
            self.wrap_lines_cb,
            self.prefs.wrap_lines),
          ( 'ShowToolbar' , None,
            'Show _Toolbar', 't',
            'Toggle display of toolbar',
            self.show_toolbar_cb,
            1),
          ( 'ShowSidebar' , 'sidebar',
            'Show _Sidebar', 's',
            'Toggle display of file/bookmark sidebar',
            self.show_sidebar_cb,
            0),
          ( 'ShowSep' , None,
            'Show _separator when paging down', '<control>s',
            'Show separation line when pageing down',
            self.show_separator_cb,
            self.prefs.show_separator),
          ( 'FullScreen' , None,
            '_Fullscreen', 'f',
            'Switch to fullscreen',
            self.fullscreen_cb,
            0),
          )
             
        action_group = gtk.ActionGroup('AppWindowActions')
        action_group.add_actions(entries)
        action_group.add_toggle_actions(toggle_entries)

        return action_group


    def get_sidebar(self):
        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_IN)
        nb = gtk.Notebook()
        frame.add(nb)
        
        label = gtk.Label('Files')
        fc = gtk.FileChooserWidget(gtk.FILE_CHOOSER_ACTION_OPEN)
        nb.append_page(fc, label)
        #object,main_vbox,paned,favs_vbox
        fc_favs = fc.get_children()[0].get_children()[0].get_children()[0].get_children()[0]
        fc_favs.hide()
        fc.set_current_folder(self.prefs.bookdir)
        fc.connect("file_activated", self.sidebar_file_activated)

        filter = gtk.FileFilter()
        filter.set_name('Only Supported')
        for pat in ['*.zip', '*.txt.gz', '*.txt.bz2', '*.txt']:
            filter.add_pattern(pat)
            filter.add_pattern(pat.upper())
        fc.add_filter(filter)

        button = gtk.Button(stock=gtk.STOCK_DELETE)
        fc.set_extra_widget(button)
        button.connect('clicked', self.delete_file_cb, fc)
        
        label = gtk.Label('Bookmarks')
        vbox = gtk.VBox()
        nb.append_page(vbox, label)
        
        scrollw = gtk.ScrolledWindow()
        vbox.pack_start(scrollw)
        tv = BookmarkTreeview(self.bookmarks)
        scrollw.add(tv)
        scrollw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        tv.connect("row_activated", self.sidebar_bookmark_activated)

        button = gtk.Button(stock=gtk.STOCK_DELETE)
        vbox.pack_start(button, False, False)
        button.connect('clicked', delete_bookmark_cb, tv)
        
        return frame

    def sidebar_file_activated(self, fc, udata=None):
        fname = fc.get_filename()
        self.open_func(fname)
        
    def sidebar_bookmark_activated(self, treeview, path=None, column=None):
        goto_bookmark(self, treeview)

    def delete_file_cb(self, widget, udata=None):
        if udata: fc = udata
        else: fc = widget

        fname = fc.get_filename()
        #make unicode 'filename', 'fname' will be filesystem encoded 
        filename, punt, enc = decode_filename_heuristically(fname)
        if not enc: return
        fname = filename.encode(sys.getfilesystemencoding())
        if os.path.isdir(fname):
            mesg = ("You've selected a directory.  "
            "It's entire contents will "
            "be deleted.  Be careful.")
        else:
            mesg = ""
        dialog = YesNoDialog("Delete:\n%s\n\n%s"
                              % (filename ,mesg), self)
        response = dialog.run()
        dialog.destroy()
        if response == gtk.RESPONSE_NO: return
        if os.path.isdir(fname):
            for root, dirs, files in os.walk(fname, topdown=False):
                for name in files:
                    os.remove(os.path.join(root, name))
                for name in dirs:
                    os.rmdir(os.path.join(root, name))
            os.rmdir(fname)
        else:
            os.remove(fname)
        self.sidebar.hide()
        self.sidebar.show()
        
    # Callbacks
    #################
        
    def open_cb(self, action):
        dialog = gtk.FileChooserDialog('Select a File', self,
                                       gtk.FILE_CHOOSER_ACTION_OPEN,
                                       (gtk.STOCK_OPEN, gtk.RESPONSE_OK,
                                        gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
        dialog.set_current_folder(self.prefs.bookdir)
        filter = gtk.FileFilter()
        filter.set_name('Only Supported')
        for pat in ['*.zip', '*.txt.gz', '*.txt.bz2', '*.txt']:
            filter.add_pattern(pat)
            filter.add_pattern(pat.upper())
        dialog.add_filter(filter)

        filter = gtk.FileFilter()
        filter.set_name('All Files')
        filter.add_pattern('*')
        dialog.add_filter(filter)
        
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            fname = dialog.get_filename()
            dialog.destroy()
            self.open_func(fname)
        else:
            dialog.destroy()

    def charset_cb(self, action, scrollw=None):
        if scrollw:
            page = self.notebook.page_num(scrollw)
        else:
            page = self.notebook.get_current_page()
        if page < 0: return
        textview = self.notebook.get_nth_page(page).get_child()
        current_enc = textview.charset
        filename = textview.filename
        dialog = gtk.Dialog("Select Charset", self, 0,
                        (gtk.STOCK_OK, gtk.RESPONSE_OK,
                         gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL))
        dialog.set_default_size(400,300)
        enc_model, current_enc_path = get_charset_model(current_enc)
        scrollw = gtk.ScrolledWindow()
        scrollw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        dialog.vbox.pack_start(scrollw)
        treeview = get_charset_treeview(enc_model)
        scrollw.add(treeview)
        sel = treeview.get_selection()
        if current_enc_path:
            sel.select_path(current_enc_path)
            treeview.scroll_to_cell(current_enc_path)
        dialog.vbox.show_all()
        response = dialog.run()

        if response == gtk.RESPONSE_OK:
            model, it = sel.get_selected()
            if it:
                enc = model.get_value(it, 0)
                textbuffer = textview.get_buffer()
                add_bookmark(self, page, True)
                textbuffer.set_text('')
                text = self.get_file_text(textview.filename)
                text, punt, h_enc = decode_heuristically(text, enc)
                textbuffer.set_text(text)
                textview.charset = h_enc
                self.set_textmarks(textbuffer, textview.filename)
                goto_bookmark(self, None, textview, textview.filename)
                if not textview.charset == enc:
                    self.message("The '%s' charset didn't work so well,\n"
                                 "fell back to '%s' charset" %
                                 (enc, textview.charset))
        dialog.destroy()
    
        
    def close_cb(self, action, scrollw=None):
        if scrollw:
            page = self.notebook.page_num(scrollw)
        else:
            page = self.notebook.get_current_page()
        add_bookmark(self, page, self.prefs.auto_save_bookmarks)
        self.notebook.remove_page(page)
        if page < 0:
            self.quit_cb(None)

        
    def quit_cb(self, action, data=None):
        if self.prefs.confirm_quit:
            dialog = YesNoDialog('Quit Gutenpy?', self)
            response = dialog.run()
            dialog.destroy()
            # If no, return True so 'delete_event' won't call 'destroy'
            if response == gtk.RESPONSE_NO: return True

        self.prefs.open_filelist = []
        pages = self.notebook.get_n_pages()
        if pages > 0:
            if self.prefs.remember_open_files:
                for page in range(pages):
                    self.remember_page(page)
                response = gtk.RESPONSE_YES
            elif self.prefs.auto_save_bookmarks:
                response = gtk.RESPONSE_YES
            else:
                dialog = YesNoDialog('Save bookmarks for open files?', self)
                response = dialog.run()
                dialog.destroy()
            if response == gtk.RESPONSE_YES:
                for page in range(pages):
                    add_bookmark(self, page, self.prefs.auto_save_bookmarks)
                    
        self.save_prefs()
        self.save_bookmarks()
        gtk.main_quit()


    def view_bookmarks_cb(self, action):
        bookmarkd = BookmarkDialog(self)
        again = True
        while again:
            response = bookmarkd.run()
            if response == gtk.RESPONSE_OK:
                again = False
                goto_bookmark(self, bookmarkd.treeview)
                bookmarkd.destroy()
            elif response == gtk.RESPONSE_REJECT:
                delete_bookmark_cb(bookmarkd.treeview)
            else:
                bookmarkd.destroy()
                again = False


    def add_bookmark_cb(self, action, page=None):
        add_bookmark(self, page)
        self.save_bookmarks()


    def find_text_cb(self, action):
        page = self.notebook.get_current_page()
        if not page >= 0: return
        textview = self.notebook.get_nth_page(page).get_child()
        self.set_start_search_mark(textview)
        dialog = EntryDialog(self, 'Search Text',
                             'Text to search for:',
                             gtk.DIALOG_DESTROY_WITH_PARENT,
                             (gtk.STOCK_GO_DOWN, gtk.DIR_DOWN,
                              gtk.STOCK_GO_UP,   gtk.DIR_UP,
                              gtk.STOCK_OK, gtk.RESPONSE_OK,
                              gtk.STOCK_CANCEL,  gtk.RESPONSE_CANCEL))
        dialog.set_default_response(gtk.DIR_DOWN)
        dialog.entry.set_text(self.search_string)
        again = True
        up, down = [True, False]
        while again:
            response = dialog.run()
            string = dialog.entry.get_text()
            if not string == self.search_string:
                self.make_search_tags(textview, string)
            if response == gtk.DIR_DOWN:
                if not self.search(textview, string, down):
                    self.message('Search hit bottom of page.', dialog)
            elif response == gtk.DIR_UP:
                if not self.search(textview, string, up):
                    self.message('Search hit top of page.', dialog)
            elif response == gtk.RESPONSE_OK:
                self.remove_start_search_mark(textview)
                dialog.destroy()
                again = False
            elif response == gtk.RESPONSE_CANCEL:
                self.make_search_tags(textview, None)
                self.search(textview, None)
                self.remove_start_search_mark(textview)
                dialog.destroy()
                again = False

        
    def scroll_cb(self, action, dir=None):
        scrollw = self.notebook.get_nth_page(self.notebook.get_current_page())
        if not scrollw: return
        if not dir:
            name = action.get_name()
            if name == 'PageTop': dir = gtk.SCROLL_START
            elif name == 'PageUp': dir = gtk.SCROLL_PAGE_UP
            elif name == 'PageDown': dir = gtk.SCROLL_PAGE_DOWN
            elif name == 'PageBottom': dir = gtk.SCROLL_END
            else: return
        scrollw.emit('scroll-child', dir, False) 


    def dict_lookup_cb(self, action=None, word=''):
        dialog = EntryDialog(self, 'Dictionary Lookup',
                             'Word to lookup:')
        dialog.set_default_response(gtk.RESPONSE_OK)
        dialog.entry.set_text(word)
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            dialog.destroy()
            word = dialog.entry.get_text()
            if word:
                def_notebook = self.dict_lookup(word)
                if def_notebook:
                    self.show_widget('Definition of: %s' % word,
                                     def_notebook)
                else:
                    self.message('No definition for %s found' % word)
        else:
            dialog.destroy()


    def goto_webpage_cb(self, action):
        webbrowser.open(self.prefs.webpage)


    def browse_catalog_cb(self, action):
        if self.catalog_dialog:
            self.catalog_dialog.present()
            return
        pickle_path = os.path.join(self.prefs.cfgdir, 'catalog.pickle')
        if os.path.isfile(pickle_path):
            dialog = gtk.MessageDialog(parent=self,
                        message_format="Loading Catalog...")
            dialog.show_all()
            while gtk.events_pending():
                gtk.main_iteration()
            try:
                f = open(pickle_path, 'rb')
                catalog_cache = cPickle.load(f)
                f.close()
            except IOError, msg:
                print msg
                dialog.destroy()
                self.message("Error loading catalog.pickle")
            try:
                self.catalog_dialog = catalog.CatalogDialog(
                    self, catalog_cache)
                dialog.destroy()
                self.catalog_dialog.show_all()
            except:
                dialog.destroy()
                self.message("Error creating catalog dialog")
        else:
            dialog = YesNoDialog("No catalog cache found.\n"
                                 "Shall we update the catalog?",
                                 self)
            response = dialog.run()
            dialog.destroy()
            if response == gtk.RESPONSE_NO: return
            self.update_catalog_cb(None)
        

    def update_catalog_cb(self, action):
        dialog = YesNoDialog('Update Catalog Cache?\n'
                             'This does take a few minutes.\n'
                             'If you say yes, you might want to\n'
                             'go make some toast.', self)
        response = dialog.run()
        dialog.destroy()
        if response == gtk.RESPONSE_NO: return
        pickle_path = os.path.join(self.prefs.cfgdir, 'catalog.pickle')
        mycat = rdfparse.Gutenberg(pickle_path, self.prefs.catalog)
        dialog = gtk.MessageDialog(parent=self,
                       message_format="Updating Catalog, Patience Please.")
        dialog.show_all()
        dialog.present()
        while gtk.events_pending():
            gtk.main_iteration()
        success = mycat.updatecatalogue()
        dialog.destroy()
        if not success:
            dialog = gtk.MessageDialog(parent=self,type=gtk.MESSAGE_ERROR,
                     buttons=gtk.BUTTONS_OK,
                     message_format="Problem fetching catalog:\n\n%s" %
                                       self.prefs.catalog)
            dialog.run()
            dialog.destroy()
        else:
            self.browse_catalog_cb(None)


    def wrap_lines_cb(self, action):
        self.prefs.wrap_lines = not self.prefs.wrap_lines
        num_pages = self.notebook.get_n_pages()
        if num_pages > 0:
            for i in range(num_pages):
                textview = self.notebook.get_nth_page(i).get_child()
                if self.prefs.wrap_lines:
                    textview.set_wrap_mode(gtk.WRAP_WORD)
                else:
                    textview.set_wrap_mode(gtk.WRAP_NONE)

                    
    def number_lines_cb(self, action):
        self.prefs.number_lines = not self.prefs.number_lines
        if self.prefs.number_lines:
            self.notebook.hide()
            self.notebook.show()
        else:
            num_pages = self.notebook.get_n_pages()
            if num_pages > 0:
                for i in range(num_pages):
                    textview = self.notebook.get_nth_page(i).get_child()
                    textview.set_border_window_size(gtk.TEXT_WINDOW_LEFT, 0)


    def show_separator_cb(self, action):
        self.prefs.show_separator = not self.prefs.show_separator


    def fullscreen_cb(self, action):
        fs_toggle = self.uimanager.get_widget("/MenuBar/ViewMenu/FullScreen")
        tb_toggle = self.uimanager.get_widget("/MenuBar/ViewMenu/ShowToolbar")
        sb_toggle = self.uimanager.get_widget("/MenuBar/ViewMenu/ShowSidebar")
        toolbar = self.uimanager.get_widget("/ToolBar")
        if fs_toggle.get_active():
            self.fullscreen()
            tb_toggle.set_active(False)
            self.show_toolbar_cb()
            sb_toggle.set_active(False)
            self.show_sidebar_cb()
            self.notebook.set_show_tabs(False)
        else:
            self.unfullscreen()
            tb_toggle.set_active(True)
            self.show_toolbar_cb()
            self.notebook.set_show_tabs(True)


    def show_toolbar_cb(self, action=None):
        tb_toggle = self.uimanager.get_widget("/MenuBar/ViewMenu/ShowToolbar")
        toolbar = self.uimanager.get_widget("/ToolBar")
        if not tb_toggle.get_active():
            toolbar.hide()
        else:
            toolbar.show()


    def show_sidebar_cb(self, action=None):
        sb_toggle = self.uimanager.get_widget("/MenuBar/ViewMenu/ShowSidebar")
        if sb_toggle.get_active():
            self.sidebar.show()
            if self.hpane.get_position() < 100:
                self.hpane.set_position(250)
        if not sb_toggle.get_active():
            self.sidebar.hide()

    def preferences_cb(self, action):
        main_pref_list = self.get_main_pref_list()
        behaviour_pref_list = self.get_behaviour_pref_list()
        dialog = PrefsDialog(main_pref_list, behaviour_pref_list, self)
        again = True
        while again:
            response = dialog.run()
            if response in [gtk.RESPONSE_OK, gtk.RESPONSE_APPLY]:
                self.set_main_prefs(main_pref_list)
                self.set_behaviour_prefs(behaviour_pref_list)
                self.apply_prefs()
                if response == gtk.RESPONSE_OK:
                    self.save_prefs()
                    dialog.destroy()
                    again = False
            else:
                dialog.destroy()
                again = False


    def about_cb(self, action):
        dialog = gtk.AboutDialog()
        gtk.about_dialog_set_url_hook(lambda d, l: webbrowser.open(l))
        dialog.set_name(self.app_name)
        dialog.set_version(self.app_version)
        dialog.set_copyright('\302\251 Copyright 2006 Lee Bigelow\n'
                             '<ligelowbee@yahoo.com>')
        dialog.set_comments('A comfortable text reader and '
                            'catalog browser for Project Gutenberg.'
                            '\nWritten with PyGTK 2.8')
        dialog.set_website_label('http://gutenpy.sourceforge.net')
        dialog.set_website('http://gutenpy.sourceforge.net')
        if os.path.isfile(self.license):
            f = open(self.license, 'r')
            text = f.read()
            f.close()
            dialog.set_license(text)
        dialog.connect ('response', lambda d, r: d.destroy())
        dialog.show()


    def help_cb(self, action):
        if os.path.isfile(self.helpfile):
            textview = self.find_textview_containing(self.helpfile)
            textview.grab_focus()

    # Event Handlers
    ################

    def main_key_press_event_handler(self, widget, event, user_data=None):
        # Some extra key bindings
        if event.string == '':
            self.open_cb(None)
            return True
        elif event.string == '':
            self.close_cb(None)
            return True
        elif event.string == '':
            self.view_bookmarks_cb(None)
            return True
        elif event.string == '':
            self.add_bookmark_cb(None)
            return True
        elif event.string in ['']:
            self.find_text_cb(None)
            return True
        elif event.string == '':
            self.dict_lookup_cb(None)
            return True
        return False


    def configure_event_handler(self, widget, event, user_data=None):
        # set prefs on resize
        self.prefs.width, self.prefs.height = self.get_size()


    def scroll_key_press_event_handler(self, textview, event, user_data=None):
        scrollw = textview.get_parent()
        if  ( ((event.string == ' ') and (event.state == gtk.gdk.SHIFT_MASK))
              or (event.keyval == gtk.keysyms.BackSpace) ):
            scrollw.emit('scroll-child', gtk.SCROLL_PAGE_UP, False)
            return True
        elif event.string == ' ':
            scrollw.emit('scroll-child', gtk.SCROLL_PAGE_DOWN, False)
            return True
        elif event.string in ['n', 'j']:
            scrollw.emit('scroll-child', gtk.SCROLL_STEP_DOWN, False)
            return True
        elif event.string in ['p','k']:
            scrollw.emit('scroll-child', gtk.SCROLL_STEP_UP, False)
            return True
        return False

    
    def mouse_button_press_event_handler(self, textview,
                                            event, user_data=None):
        if (event.type == gtk.gdk.BUTTON_PRESS) and (event.button == 2):
            textbuffer = textview.get_buffer()
            x,y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_WIDGET,
                                                   int(event.x), int(event.y))
            siter = textview.get_iter_at_location(x,y)
            word=''
            if siter.inside_word():
                siter.backward_word_start()
                eiter = textview.get_iter_at_location(x,y)
                eiter.forward_word_end()
                word = siter.get_text(eiter).lower()
            self.dict_lookup_cb(word=word)
            return True
        elif (event.type == gtk.gdk.BUTTON_PRESS) and (event.button == 3):
            textbuffer = textview.get_buffer()
            # If selection exists don't handle, let the drop down menu show
            if textbuffer.get_selection_bounds():
                return False
            else:
                return True
        elif (event.type == gtk.gdk._2BUTTON_PRESS) and (event.button == 1):
            self.scroll_cb(None, gtk.SCROLL_PAGE_DOWN)
            return True
        elif (event.type == gtk.gdk._2BUTTON_PRESS) and (event.button == 3):
            self.scroll_cb(None, gtk.SCROLL_PAGE_UP)
            return True
        

    def linenum_expose_event_handler(self, widget, event, user_data=None):
        # Number lines on expose event
        if not self.prefs.number_lines: return False
        textview = widget
        if not textview.get_border_window_size(gtk.TEXT_WINDOW_LEFT):
            textview.set_border_window_size(gtk.TEXT_WINDOW_LEFT, 50)

        left_win = textview.get_window(gtk.TEXT_WINDOW_LEFT)
        if event.window == left_win:
            type = gtk.TEXT_WINDOW_LEFT
            target = left_win
        else:
            return False
  
        first_y = event.area.y
        last_y = first_y + event.area.height

        x, first_y = textview.window_to_buffer_coords(type, 0, first_y)
        x, last_y = textview.window_to_buffer_coords(type, 0, last_y)

        numbers = []
        pixels = []
        count = self.get_lines(textview, first_y, last_y, pixels, numbers)
  
        layout = widget.create_pango_layout('')
        for i in range(count):
            x, pos = textview.buffer_to_window_coords(type, 0, pixels[i])
            str = '%d' % numbers[i]
            layout.set_text(str)
            widget.style.paint_layout(target, widget.state, False,
                                      None, widget, None, 2, pos + 2, layout)
        return False

    # General Dialogs
    #################
    
    def message(self, text, window=None):
        if not window: window = self
        dialog = gtk.MessageDialog(window,
                                   gtk.DIALOG_DESTROY_WITH_PARENT,
                                   gtk.MESSAGE_INFO,
                                   gtk.BUTTONS_CLOSE,
                                   text)
        dialog.run()
        dialog.destroy()

        
    def show_widget(self, title, widget):
        dialog = gtk.Dialog(title, None,
                            gtk.DIALOG_DESTROY_WITH_PARENT,
                            (gtk.STOCK_OK, gtk.RESPONSE_OK))
        dialog.set_default_response(gtk.RESPONSE_OK)
        dialog.set_default_size(550,350)
        dialog.vbox.pack_start(widget)
        dialog.vbox.show_all()
        dialog.show_all()
        dialog.connect('response', lambda w,r: w.destroy())
        
    # Page Textview functions
    #########################
    def add_page(self, filename, enc=None):
        text = self.get_file_text(filename)
        if not text: return None

        #get the encoding charset from bookmarks if available, and not passed
        if not enc:
            bm_treestore = self.bookmarks
            it = bm_treestore.get_iter_first()
            while it:
                bm_fname, bm_enc = bm_treestore.get(it, 0, 3)
                if bm_fname == filename:
                    enc = bm_enc
                    break
                it = bm_treestore.iter_next(it)
        
        text, punt, enc = decode_heuristically(text, enc)
        print "contents punt, encoding:", punt, ",", enc, "\n"
        textview = GutenpyTextView(self, filename, text, enc)
        textbuffer = textview.get_buffer()
        textbuffer.create_tag('separate',
                              underline=pango.UNDERLINE_LOW)
        textbuffer.create_tag('found',
                              foreground='red')
        self.set_textmarks(textbuffer, filename)
        textview.connect('expose-event', self.linenum_expose_event_handler)
        textview.connect('move-cursor', self.separator_move_cursor_cb)
        scrollw = gtk.ScrolledWindow()
        scrollw.connect('scroll-child', self.separator_scroll_child_cb)
        scrollw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        scrollw.add(textview)
        scrollw.show_all()
        label = self.make_page_label(filename, scrollw)
        self.notebook.append_page(scrollw, label)
        goto_bookmark(self, None, textview, filename)

        return textview

    def get_file_text(self, filename):
        filename = filename.encode(sys.getfilesystemencoding())
        text = None
        if ZIP_RE.match(filename):
            text = self.get_zip_text(filename)
            if text: infile = True
            else: infile = None
        elif TEXT_RE.match(filename):
            try: infile = open(filename)
            except: infile = None
        elif GZIP_RE.match(filename):
            try: infile = gzip.open(filename)
            except: infile = None
        elif BZ2_RE.match(filename):
            try: infile = bz2.BZ2File(filename)
            except: infile = None
        else:
            infile = None
            self.message('Format of file %s\nnot recognized' % filename)
        if not infile: return None
        if not text: 
            text = infile.read()
            infile.close()
        return text

        
    def get_zip_text(self, filename):
        text = None
        zfname = None
        # todo: catch execptions and report.
        try: zfile = zipfile.ZipFile(filename)
        except zipfile.error, msg:
            dialog = gtk.MessageDialog(parent=self, type=gtk.MESSAGE_ERROR,
                              buttons=gtk.BUTTONS_OK,
                              message_format="Zipfile object error:\n%s\n\n" %
                                       msg)
            dialog.run()
            dialog.destroy()
            return None
        for f in zfile.namelist():
            if TEXT_RE.match(f):
                zfname = f
                break
        if zfname:
            try: text = zfile.read(zfname)
            except zipfile.error, msg:
                dialog = gtk.MessageDialog(parent=self, type=gtk.MESSAGE_ERROR,
                           buttons=gtk.BUTTONS_OK,
                           message_format="Zipfile extract error:\n%s\n\n"
                           "If this is a compression method error the zipfile "
                           "was probably compressed with a non-standard "
                           "method.  Try unzipping and rezipping the file "
                           "with an external zip program.\n\n"
                           "If this is another kind of error, or the above "
                           "doesn't work, file a bug report (with the "
                           "problem file attached) here:\n\n"
                           "http://sourceforge.net/tracker/?group_id=170872"
                              % msg)
                dialog.run()
                dialog.destroy()
                return None
        zfile.close()
        return text


    def set_textmarks(self, textbuffer, filename):
        diter = self.bookmarks.get_iter_first()
        if not diter: return
        # look for default mark on main level
        while diter:
            bm_fname, line = self.bookmarks.get(diter,1,2)
            if filename == bm_fname:
                # set the default mark
                textiter = textbuffer.get_iter_at_line(line)
                textbuffer.create_mark(bm_fname, textiter, False)
                # get the named child marks
                niter = self.bookmarks.iter_children(diter)
                while niter:
                    bname, line = self.bookmarks.get(niter,1,2)
                    textiter = textbuffer.get_iter_at_line(line)
                    textbuffer.create_mark(bname, textiter, False)
                    niter = self.bookmarks.iter_next(niter)
                diter = None
            else:
                diter = self.bookmarks.iter_next(diter)
            

    def make_page_label(self, filename, scrollw):
        hbox = gtk.HBox(False, 2)
        label = gtk.Label(filename)
        label.set_max_width_chars(20)
        label.set_ellipsize(pango.ELLIPSIZE_START)
        hbox.pack_start(label, False, False, 0)

        button = gtk.Button()
        button.connect('clicked', self.close_cb, scrollw)
        image = gtk.Image()
        image.set_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
        image.set_size_request(8, 8)
        button.add(image)
        hbox.pack_start(button, False, False, 0)

        hbox.show_all()
        return hbox



    def get_lines(self, textview, first_y, last_y, buffer_coords, numbers):
        iter, top = textview.get_line_at_y(first_y)
        count = 0
        size = 0
        while not iter.is_end():
            y, height = textview.get_line_yrange(iter)
            buffer_coords.append(y)
            line_num = iter.get_line()
            numbers.append(line_num)
            count += 1
            if (y + height) >= last_y:
                break
            iter.forward_line()
        return count


    def make_search_tags(self, textview, string):
        textbuffer = textview.get_buffer()
        start, end = textbuffer.get_bounds()
        textbuffer.remove_tag_by_name('found', start, end)
        if string == None: return
        text = textbuffer.get_text(start, end, True)
        text = unicode(text, 'utf_8')
        regex = re.compile(string, re.I)

        found_iter = regex.finditer(text)
        del text
        try: match = found_iter.next()
        except: match = None
        while match:
            so, eo = match.span()
            si = textbuffer.get_iter_at_offset(so)
            ei = textbuffer.get_iter_at_offset(eo)
            textbuffer.apply_tag_by_name('found',si,ei)
            try: match = found_iter.next()
            except: match = None
        

    def set_start_search_mark(self, textview):
        textbuffer = textview.get_buffer()
        start_search_mark = textbuffer.get_mark('start_search')
        if not start_search_mark:
            x,y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,0,0)
            siter = textview.get_iter_at_location(x, y)
            textbuffer.create_mark('start_search', siter, False)


    def remove_start_search_mark(self, textview):
        textbuffer = textview.get_buffer()
        mark = textbuffer.get_mark('start_search')
        textbuffer.delete_mark(mark)
        

    def search(self, textview, string, up=None):
        textbuffer = textview.get_buffer()
        if string == None:
            mark = textbuffer.get_mark('start_search')
            textview.scroll_to_mark(mark, 0, True, 0.0, 0.0)
            return True

        last_search_mark = textbuffer.get_mark('last_search')
        if last_search_mark and (string == self.search_string):
            siter = textbuffer.get_iter_at_mark(last_search_mark)
        else:
            self.search_string = string
            x,y = textview.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT,0,0)
            siter = textview.get_iter_at_location(x,y)

        tagtable = textbuffer.get_tag_table()
        tag = tagtable.lookup('found')
        if up:
            siter.set_line_offset(0)
            if not siter.backward_to_tag_toggle(tag):
                return False
        else:
            if not siter.forward_to_tag_toggle(tag):
                return False
            siter.forward_to_line_end()

        mark = textbuffer.create_mark('last_search', siter, False)
        textview.scroll_to_mark(mark, 0, True, 0.0, 0.0)

        return True

    
    def find_textview_containing(self, filename):
        for page in range(self.notebook.get_n_pages()):
            textview = self.notebook.get_nth_page(page).get_child()
            if textview.filename == filename:
                self.notebook.set_current_page(page)
                return textview
        textview = self.open_func(filename)
        return textview
    

    def separator_move_cursor_cb(self, textview, step, count,
                                 ext_sel, udata=None):
        stype = None
        if (step == gtk.MOVEMENT_PAGES) and (count == 1):
            stype = gtk.SCROLL_PAGE_DOWN
        self.separator_redraw(textview, stype) 
        return False


    def separator_scroll_child_cb(self, scrollw, stype, horz, udata=None):
        textview = scrollw.get_child()
        self.separator_redraw(textview, stype)
        return False
    

    def separator_redraw(self, textview, stype):
        #always clear old stuff
        textbuffer = textview.get_buffer()
        rect = textview.get_visible_rect()
        s_iter = textview.get_iter_at_location(rect.x, rect.y)
        e_iter = textview.get_iter_at_location(rect.x + rect.width,
                                              rect.y + rect.height)
        textbuffer.remove_tag_by_name('separate', s_iter, e_iter)

        if self.prefs.show_separator:
            if stype == gtk.SCROLL_PAGE_DOWN:
                l_iter = textview.get_iter_at_location(rect.x,
                                                       rect.y + rect.height)
                l_iter.backward_line()
                r_iter = l_iter.copy()
                r_iter.forward_to_line_end()
                textbuffer.apply_tag_by_name('separate', l_iter, r_iter)

    # Other
    #######
    
    def open_func(self, fname):
        filename, punt, enc = decode_filename_heuristically(fname)
        if not enc: return
        print "open:"
        print "filename:", filename.encode(sys.getfilesystemencoding())
        print "filename punt, encoding:", punt, ",", enc
        textview = self.add_page(filename)
        if textview:
            self.notebook.set_current_page(-1)
            textview.grab_focus()
        return textview
    
    def dict_lookup(self, word):
        try: d = dictclient.Connection(self.prefs.dict_serv,
                                       self.prefs.dict_port)
        except:
            self.message('Could not connect to\nDict Server: %s\n'
                         'Dict Port: %s\n' %
                         (self.prefs.dict_serv, self.prefs.dict_port))
            return None
        d_defs = d.define('*', word)
        if len(d_defs) > 0:
            nb = gtk.Notebook()
            nb.set_scrollable(True)
            for d_def in d_defs:
                s=''
                db = d_def.getdb()
                s += '%s\n%s\n\n' % (db.getdescription(), '*'*30)
                s += '%s\n\n' % d_def.getdefstr()
                textview = GutenpyTextView(self, word, s, 'utf_8')
                scrollw = gtk.ScrolledWindow()
                scrollw.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
                scrollw.add(textview)
                label = gtk.Label(db.getname())
                nb.append_page(scrollw, label)
            return nb
        else:
            return None


    def remember_page(self, page):
        textview = self.notebook.get_nth_page(page).get_child()
        filename = textview.filename
        self.prefs.open_filelist.append(filename)


    def load_bookmarks(self, bmfile):
        try:
            pfile = open(bmfile, "rb")
            bm_list = cPickle.load(pfile)
            pfile.close()
        except:
            bm_list = []
            tmpfile = os.tempnam(self.prefs.cfgdir, "bookmarks")
            os.rename(bmfile, tmpfile)
            self.message("Trouble reading bookmark file:\n%s\n\n"
                         "Moved it to:\n%s\n\n"
                         "Creating a new bookmark file")
        not_found = []
        for marklist in bm_list:
            # allow old bookmarks without charset info
            if len(marklist[0]) == 3:
                fname, line, enc = marklist[0]
            else:
                fname, line = marklist[0]
                enc = None
            # if the filesystem encoded fname doesn't exist ignore it
            if not os.path.isfile(fname.encode(sys.getfilesystemencoding())):
                print "bookmark file not found:", fname.encode(
                    sys.getfilesystemencoding())
                fname = None
            else:
                piter = self.bookmarks.append(None, [fname, fname, line, enc])
            if fname:
                for mark in marklist[1:]:
                    bname, line = mark
                    self.bookmarks.append(piter, [fname, bname, line])

        
    def save_bookmarks(self):
        bm_list = []
        diter = self.bookmarks.get_iter_first()
        while diter:
            marklist = []
            fname, line, enc = self.bookmarks.get(diter,1,2,3)
            marklist.append([fname,line,enc])
            niter = self.bookmarks.iter_children(diter)
            while niter:
                fname,line = self.bookmarks.get(niter,1,2)
                marklist.append([fname,line])
                niter = self.bookmarks.iter_next(niter)
            bm_list.append(marklist)
            diter = self.bookmarks.iter_next(diter)
        cfgfile = os.path.join(self.prefs.cfgdir, 'bookmarks.pickle')
        pfile = open(cfgfile, 'wb')
        cPickle.dump(bm_list, pfile, -1)
        pfile.close()


    def load_prefs(self, cfgfile):
        try:
            pfile = open(cfgfile, "rb")
            prefs = cPickle.load(pfile)
            pfile.close()
        except:
            tmpfile = os.tempnam(self.prefs.cfgdir, "config")
            os.rename(cfgfile, tmpfile)
            self.message("Trouble reading config file:\n%s\n\n"
                         "Moved it to:\n%s\n\n"
                         "Creating a new config file" % (cfgfile,tmpfile))
            return
        for attr in self.prefs.__dict__.keys():
            if hasattr(prefs, attr):
                setattr(self.prefs, attr, getattr(prefs, attr))


    def save_prefs(self):
        cfgfile = os.path.join(self.prefs.cfgdir, 'config.pickle')
        pfile = open(cfgfile, 'wb')
        cPickle.dump(self.prefs, pfile, -1)
        pfile.close()


    def get_behaviour_pref_list(self):
        pref_list = range(3)

        item = gtk.CheckButton('Confirm Quit')
        if self.prefs.confirm_quit:
            item.set_active(True)
        pref_list[CONFIRM_QUIT] = item
        
        item = gtk.CheckButton('Auto Save Bookmarks on Quit')
        if self.prefs.auto_save_bookmarks:
            item.set_active(True)
        pref_list[AUTO_SAVE] = item

        item = gtk.CheckButton('Remember Open Files')
        if self.prefs.remember_open_files:
            item.set_active(True)
        pref_list[REMEMBER_OPEN] = item

        return pref_list

    
    def get_main_pref_list(self):
        pref_list=range(10)
        item = gtk.FontButton(self.prefs.font)
        item.set_title('Text Font')
        pref_list[FONT] = item

        r,g,b = self.prefs.bg
        item = gtk.ColorButton(gtk.gdk.Color(r,g,b))
        item.set_title('Background Colour')
        pref_list[BG] = item
        
        r,g,b = self.prefs.fg
        item = gtk.ColorButton(gtk.gdk.Color(r,g,b))
        item.set_title('Foreground Colour')
        pref_list[FG] = item

        item = gtk.FileChooserButton('Book Directory')
        item.set_current_folder(self.prefs.bookdir)
        item.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
        pref_list[BOOKDIR] = item
        
        item = gtk.FileChooserButton('Config Directory')
        item.set_current_folder(self.prefs.cfgdir)
        item.set_action(gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
        pref_list[CFGDIR] = item
        
        item = gtk.Entry()
        item.set_text(self.prefs.webpage)
        item.set_data('title','Gutenberg Webpage')
        pref_list[WEBPAGE] = item
        
        item = gtk.Entry()
        item.set_text(self.prefs.mirror)
        item.set_data('title','Gutenberg Mirror')
        pref_list[MIRROR] = item
        
        item = gtk.Entry()
        item.set_text(self.prefs.catalog)
        item.set_data('title','Gutenberg Catalog.rdf.bz2')
        pref_list[CATALOG] = item
        
        item = gtk.Entry()
        item.set_text(self.prefs.dict_serv)
        item.set_data('title','Dict Server')
        pref_list[DICT_SERV] = item
        
        item = gtk.Entry()
        item.set_text(str(self.prefs.dict_port))
        item.set_data('title', 'Dict Port')
        pref_list[DICT_PORT] = item

        return pref_list


    def set_behaviour_prefs(self, pref_list):
        self.prefs.confirm_quit = pref_list[CONFIRM_QUIT].get_active()
        self.prefs.auto_save_bookmarks = pref_list[AUTO_SAVE].get_active()
        self.prefs.remember_open_files = pref_list[REMEMBER_OPEN].get_active()

        
    def set_main_prefs(self, pref_list):
        self.prefs.font = pref_list[FONT].get_font_name()

        bg = pref_list[BG].get_color()
        self.prefs.bg = (bg.red, bg.green, bg.blue) 

        fg = pref_list[FG].get_color()
        self.prefs.fg = (fg.red, fg.green, fg.blue)

        self.prefs.bookdir = pref_list[BOOKDIR].get_filename()
        self.prefs.cfgdir = pref_list[CFGDIR].get_filename()

        webpage = pref_list[WEBPAGE].get_text()
        if not webpage: webpage = 'http://www.gutenberg.org/catalog'
        self.prefs.webpage = webpage

        mirror = pref_list[MIRROR].get_text()
        if not mirror:
            mirror='http://www.gutenberg.org/dirs'
        self.prefs.mirror = mirror

        catalog = pref_list[CATALOG].get_text()
        if not catalog:
            catalog='http://www.gutenberg.org/feeds/catalog.rdf.bz2'
        self.prefs.catalog = catalog

        dict_serv = pref_list[DICT_SERV].get_text()
        if not dict_serv: dict_serv = 'www.dict.org'
        self.prefs.dict_serv = dict_serv

        port = int(pref_list[DICT_PORT].get_text())
        if not port: port = 2628
        self.prefs.dict_port = port


    def apply_prefs(self):
        os.chdir(self.prefs.bookdir)
        font_desc = pango.FontDescription(self.prefs.font)
        r,g,b = self.prefs.bg
        bg = gtk.gdk.Color(r,g,b)
        r,g,b = self.prefs.fg
        fg = gtk.gdk.Color(r,g,b)
        num_pages = self.notebook.get_n_pages()
        if num_pages > 0:
            for i in range(num_pages):
                textview = self.notebook.get_nth_page(i).get_child()
                textview.modify_base(gtk.STATE_NORMAL,bg)
                textview.modify_text(gtk.STATE_NORMAL,fg)
                textview.modify_font(font_desc)

        
def main(argv=None):
    if not argv:
        argv = sys.argv
    AppMainWindow(argv)
    gtk.main()


if __name__ == '__main__':
    main()
