#!/usr/bin/python
# -*-Python-*- vim:ts=4 sw=4 expandtab

## Copyright (C) 2003-2007 Rubens Ramos <rubensr@users.sourceforge.net>

## gnochm 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; see the file COPYING.  If not,
## write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
## Boston, MA 02111-1307, USA

## $Id: gnochm.py.in,v 1.56 2007/09/15 12:20:23 rubensr Exp $

app = 'gnochm'
version = '0.9.11'
homedir = '/usr/share'

'''
   gnochm - a Gnome viewer for CHM (Html help) files.
   
'''

def N_(message): return message

from htmlentitydefs import entitydefs
from sgmllib import SGMLParser, SGMLParseError

import codecs
import ConfigParser
import getopt
import gettext
import locale
import mimetypes
import os
import os.path
import pickle
import re
import string
import sys
import urllib
import urlparse
import webbrowser
try:
    from chm import chm, chmlib

    import gconf
    import gobject
    import gtkhtml2
    import gtk
    import gnome
    import gnome.ui
    import gtk.glade
except ImportError, (err):
    mesg = 'You do not have all of the required Python modules to run gnochm.\nCheck the gnochm README file for tips on how to fix this.\nWhat follows is the error description highlighting the problematic module.\n\n'
    sys.stdout.write(mesg)
    sys.stderr.write(str(err) + '\n')
    sys.exit(1)

#import gc

html_charset='<meta http-equiv="Content-Type" content="text/html; charset="%s">'
html_text='<html><head></head><body><center>%s</center></body></html>'
syn_image_html='<html><head></head><body><img src="%s"></body></html>'

path_prefdialog = os.path.join(homedir, app, 'glade', 'prefdialog.glade')
path_mainwin = os.path.join(homedir, app, 'glade', 'gnochm.glade')
path_logo = os.path.join(homedir, 'pixmaps', 'gnochm_logo.png')
path_icon = os.path.join(homedir, 'pixmaps', 'gnochm.png')
path_help = 'gnochm.xml'
path_locale = os.path.join(homedir, 'locale')
path_user = os.path.expanduser('~/.gnochm')
path_log = os.path.join(path_user, 'gnochm.log')
path_rec = os.path.join(path_user, 'gnochm.recent')
path_bm = os.path.join(path_user, 'gnochm.bookmarks')
path_cfg = os.path.join(path_user, 'gnochm.cfg')

entityref = re.compile('&((?P<str>[a-zA-Z][a-zA-Z0-9]*)|#(?P<dec>[0-9]+)|#[xX](?P<hex>[0-9a-fA-F]+));')

instances = []

popt_table=[
#   (longName, shortName, type , default, flags, descrip , argDescrip)
    ("debug", 'd', None, None, 0, N_('enables debug data to be written to ~/.gnochm/gnochm.log'), 'debug')
]

# debug file
df = None

TARGET_STRING = 0
TARGET_ROOTWIN = 1

target = [
    ('STRING', 0, TARGET_STRING),
    ('text/plain', 0, TARGET_STRING),
    ('application/x-rootwin-drop', 0, TARGET_ROOTWIN)]

def print_log(data):
    if df != None:
        df.write(data + '\n')

def repl_entities(text):
    pos = 0
    while 1:
        match = entityref.search(text, pos)
        if match:
            ent = text[match.start(0): match.end(0)]
            pos = match.start(0)
            if match.group("str") and entitydefs.has_key(match.group("str")):
#                subs = entitydefs[match.group("str")]
                subs = entitydefs[match.group("str")].decode(
                    'latin-1').encode('utf-8')
                text = text.replace(ent, subs)
            elif match.group("dec"):
                subs = unichr(int(match.group("dec"))).encode('utf-8')
                text = text.replace(ent, subs)
            elif match.group("hex"):
                subs = unichr(int(match.group("hex"), 16)).encode('utf-8')
                text = text.replace(ent, subs)
            else:
                print_log('repl_entities: Could not convert %s' %
                          match.group("str"))
                pos = pos + 1
        else:
            break
    return text

def my_warning_dialog(text):
    try:
        mbox = gnome.ui.gnome_warning_dialog(text)
    except:
        mbox = gnome.ui.warning_dialog(text)
    return mbox

class Navigation:
    def __init__(self, xml):
        self.back = []
        self.forward = []
        self.f_adjust = []
        self.b_adjust = []
        self.current = None
        self.c_adjust = None
        self.bf = xml.get_widget('ForwardButton')
        self.bb = xml.get_widget('BackButton')
        self.mf = xml.get_widget('forward')
        self.mb = xml.get_widget('back')
        self.sw = xml.get_widget('HTMLSW')

    def set_current(self, data):
        data = os.path.normpath(urllib.unquote(data))
        #print 'SET_CURRENT: ', data, self.current
        if self.current == data:
            return
        if self.current != None:
            if self.back == []:
                self.bb.set_sensitive(True)
                self.mb.set_sensitive(True)
            self.back.append(self.current)
            self.b_adjust.append((self.sw.get_hadjustment().value,
                                  self.sw.get_vadjustment().value))
            #print "storing adj %f %f" % (self.sw.get_hadjustment().value,
            #                             self.sw.get_vadjustment().value)
        self.forward = []
        self.f_adjust = []
        self.bf.set_sensitive(False)
        self.mf.set_sensitive(False)
        self.current = data
        self.c_adjust = (0.0, 0.0)
        h_a = gtk.Adjustment()
        v_a = gtk.Adjustment()
        h_a.value = 0.0
        v_a.value = 0.0
        self.sw.set_hadjustment(h_a)
        self.sw.set_vadjustment(v_a)
        #print "setting adj %f %f" % (self.sw.get_hadjustment().value,
        #                             self.sw.get_vadjustment().value)
        #print 'B_Adjust:', self.b_adjust
        #print 'F_Adjust:', self.f_adjust
        #print 'Forward:', self.forward
        #print 'Back:', self.back

    def get_current(self):
        return self.current

    def update_adjustments(self):
        h, v = self.c_adjust
        h_a = self.sw.get_hadjustment()
        v_a = self.sw.get_vadjustment()
        h_a.value = h
        v_a.value = v
        #print "update_adjustments %f %f" % (h, v)
        self.sw.set_hadjustment(h_a)
        self.sw.set_vadjustment(v_a)
    
    def go_forward(self):
        if self.forward != []:
            data = self.forward.pop()
            ad = self.f_adjust.pop()
            if self.forward == []:
                self.bf.set_sensitive(False)
                self.mf.set_sensitive(False)
            if self.back == []:
                self.bb.set_sensitive(True)
                self.mb.set_sensitive(True)
            self.back.append(self.current)
            #print "forward adj %f %f" % (self.sw.get_hadjustment().value,
            #                             self.sw.get_vadjustment().value)
            self.b_adjust.append((self.sw.get_hadjustment().value,
                                  self.sw.get_vadjustment().value))
            self.current = data
            #print "forward cur %f %f" % (ad[0],ad[1])
            self.c_adjust = ad
        return self.current

    def go_back(self):
        #print 'GOBACK-----------'
        if self.back != []:
            data = self.back.pop()
            ad = self.b_adjust.pop()
            if self.back == []:
                self.bb.set_sensitive(False)
                self.mb.set_sensitive(False)
            if self.forward == []:
                self.bf.set_sensitive(True)
                self.mf.set_sensitive(True)
            self.forward.append(self.current)
            self.f_adjust.append((self.sw.get_hadjustment().value,
                                  self.sw.get_vadjustment().value))
            #print "backward adj %f %f" % (self.sw.get_hadjustment().value,
            #                              self.sw.get_vadjustment().value)
            self.current = data
            self.c_adjust = ad
            #print 'B_Adjust:', self.b_adjust
            #print 'F_Adjust:', self.f_adjust
            #print 'Forward:', self.forward
            #print 'Back:', self.back
        return self.current

    def clear(self):
        self.back = []
        self.forward = []
        self.f_adjust = []
        self.b_adjust = []
        self.current = None
        self.c_adjust = None
        self.mb.set_sensitive(False)
        self.mf.set_sensitive(False)
        self.bb.set_sensitive(False)
        self.bf.set_sensitive(False)
        
class Statusbar:
    def __init__(self, bar):
        self.bar = bar

    def push(self, mesg):
        self.pop()
        self.bar.push(mesg)

    def push_error(self, mesg):
        self.push(mesg)

    def pop(self):
        self.bar.pop()
        
class TopicsParser(SGMLParser):
    "A parser for a Topics file"
    
    ICON_FOLDER = gtk.STOCK_OPEN
    ICON_TOPIC  = gtk.STOCK_NEW
    ICON_NEW    = gtk.STOCK_HELP

    def __init__(self, model):
        SGMLParser.__init__(self)
        self.first = 1
        self.parent = None
        self.sibling = None
        self.in_obj = 0
        self.name = ""
        self.local = ""
        self.param = ""
        self.add_level = 0
        self.model = model
        self.icon  = self.ICON_TOPIC
        self.linklist = {}
        #self.column = 0

    def feed_and_get_links(self, data):
        self.feed(data)
        return self.linklist

    def unknown_starttag(self, tag, attrs):
        if (tag == "ul"):
            if self.first:
                self.first = 0
            else:
                self.add_level = 1
                #self.column = self.column + 1
            #print ' ' * self.column, '<ul>'
            #if self.parent:
            #        self.model.set_value(self.parent, 2, gtk.STOCK_OPEN)
        elif (tag == "object"):
            for x, y in attrs:
                if ((x.lower() == "type") and (y.lower() == "text/sitemap")):
                    self.in_obj = 1
        elif ((tag.lower() == "param") and (self.in_obj == 1)):
            for x, y in attrs:
                if (x.lower() == "name"):
                    self.param = y.lower()
                elif (x.lower() == "value"):
                    if (self.param == "name") and (self.name == ""):
                        self.name = y
                        #print '  Name=', self.name
                    elif (self.param == "local"):
                        self.local = y
                        #print ' ' * self.column, '  Local=', y
                    elif (self.param == "merge"):
                        self.in_obj = 0
                    elif (self.param == "new"):
                        self.icon = self.ICON_NEW
                    #elif (self.param == "imagenumber") and (int(y) % 2 == 0):
                    #    self.icon = self.ICON_NEW

    def unknown_endtag(self, tag):
        if (tag == "ul"):
            #print ' ' * self.column, '</ul>'
            #self.column = self.column - 1
            self.sibling = self.parent
            if self.parent:
                self.parent = self.model.iter_parent(self.parent)
        elif (tag == "object") and (self.in_obj == 1):
            if self.add_level == 1 and self.sibling:
                self.parent = self.sibling
                self.model.set_value(self.parent, 2, self.ICON_FOLDER)
                self.sibling = self.model.append(self.parent)
                self.add_level = 0
            else:
                self.sibling = self.model.append(self.parent)
            if self.local != None and len(self.local) > 0:
                if self.local[0] not in ('/', '#'):
                    self.linklist['/' + self.local] = self.sibling
                else:
                    self.linklist[self.local] = self.sibling
            name=repl_entities(self.name)
            if name.strip():
                self.model.set_value(self.sibling,0,name)
                self.model.set_value(self.sibling,1,repl_entities(self.local))
                self.model.set_value(self.sibling,2,self.icon)
            self.in_obj = 0
            self.name = ""
            self.local = ""
            self.icon = self.ICON_TOPIC
        gtk.main_iteration_do(False)

class CHMFS:
    def __init__(self, chmfile, directory):
        self.chmfile = chmfile
        self.directory = directory

class Configuration:
    def save(self):
        global recent_list
        
        pickle.dump(recent_list, open(path_rec, 'w'))

    def get_recent(self):
        global recent_list
        
        return recent_list
    
    def add_recent(self, name):
        global recent_list
        
        client = gconf.client_get_default ()
        length=client.get_int('/apps/gnochm/preferences/recent_size')
        if length < 1:
            length = 1
        if name in recent_list:
            recent_list.remove(name)
        recent_list.insert(0, name)
        length = min(length, 20)
        if len(recent_list) > length:
            recent_list = recent_list[0:length]

class Preferences:
    def __init__(self, xml):
        self.xml = xml
        self.client = gconf.client_get_default ()
        self.client.notify_add ('/apps/gnochm/preferences/toolbar_enable',
                                self.toolbar_enable_changed_cb)
        self.client.notify_add ('/apps/gnochm/preferences/toolbar_tooltips',
                                self.toolbar_tooltips_changed_cb)
        self.client.notify_add ('/apps/gnochm/preferences/html_bg_color',
                                self.html_bg_changed_cb)
        self.client.notify_add ('/apps/gnochm/preferences/whole_words',
                                self.whole_words_changed_cb)
        self.client.notify_add ('/apps/gnochm/preferences/titles_only',
                                self.titles_only_changed_cb)
        self.client.notify_add ('/apps/gnochm/preferences/search_previous',
                                self.search_previous_changed_cb)

    def search_previous_changed_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_BOOL:
                widget = self.xml.get_widget('SearchPrevCB')
                widget.set_active(entry.value.get_bool())
            else:
                print_log('search_previous_changed_cb: non-bool value')

    def whole_words_changed_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_BOOL:
                widget = self.xml.get_widget('WholeWordsCB')
                widget.set_active(entry.value.get_bool())
            else:
                print_log('whole_words_changed_cb: non-bool value')

    def titles_only_changed_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_BOOL:
                widget = self.xml.get_widget('TitlesOnlyCB')
                widget.set_active(entry.value.get_bool())
            else:
                print_log('titles_only_changed_cb: non-bool value')

    def html_bg_changed_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_STRING:
                widget = self.xml.get_widget('HTMLSW').get_child()
                color = gtk.gdk.color_parse(entry.value.get_string())
                widget.modify_bg(gtk.STATE_NORMAL, color)
                widget.modify_bg(gtk.STATE_PRELIGHT, color)
            else:
                print_log('html_bg_enable_changed_cb: non-string value')

    def toolbar_enable_changed_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_BOOL:
                widget = self.xml.get_widget('Toolbar')
                if entry.value.get_bool():
                    widget.show()
                else:
                    widget.hide()
            else:
                print_log('toolbar_enable_changed_cb: non-bool value')

    def toolbar_tooltips_changed_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_BOOL:
                widget = self.xml.get_widget('Tbar')
                widget.set_tooltips( entry.value.get_bool())
            else:
                print_log('toolbar_tooltips_changed_cb: non-bool value')

    def get_bool(self, key):
        return self.client.get_bool('/apps/gnochm/preferences/' + key)
        
    def get_string(self, key):
        return self.client.get_string('/apps/gnochm/preferences/' + key)

    def get_int(self, key):
        return self.client.get_int('/apps/gnochm/preferences/' + key)

class PrefDialog:
    def __init__(self, preferences):
        self.pref = preferences
        self.xml = gtk.glade.XML(path_prefdialog, None, app)
        dict = {}
        for key in dir(self.__class__):
            dict[key] = getattr(self, key)
        self.xml.signal_autoconnect(dict)
        self.dialog = self.xml.get_widget('PrefDialog')
        if (gtk.pygtk_version >= (2,4,0)):
            self.filebox = gtk.FileChooserDialog(
                action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
                buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,
                         gtk.STOCK_OPEN,gtk.RESPONSE_OK))
        else:
            self.filebox = self.xml.get_widget('FileSelection')
        self.client = gconf.client_get_default ()
        self.client.add_dir ("/apps/gnochm/preferences",
                             gconf.CLIENT_PRELOAD_NONE)
        self.client.notify_add ('/apps/gnochm/preferences/recent_size',
                                self.recent_size_changed_cb)
        self.client.notify_add ('/apps/gnochm/preferences/use_lcid',
                                self.use_lcid_cb)
        self.client.notify_add ('/apps/gnochm/preferences/save_path',
                                self.save_path_cb)
        self.client.notify_add ('/apps/gnochm/preferences/source_newwindow',
                                self.source_newwindow_cb)
        self.client.notify_add ('/apps/gnochm/preferences/sync_page',
                                self.sync_page_cb)

##        self.client.notify_add ('/apps/gnochm/preferences/glob_case_ins',
##                                self.glob_case_ins_cb)

    def update_prefs(self):
        widget = self.xml.get_widget('htmlbg')
        if self.pref.get_string('html_bg_color'):
            color = gtk.gdk.color_parse(self.pref.get_string('html_bg_color'))
            widget.set_i16(color.red, color.green, color.blue, 0)
        widget = self.xml.get_widget('source_newwindow')
        value = self.pref.get_bool('source_newwindow')
        widget.set_active(value)
        widget = self.xml.get_widget('treestate')
        value = self.pref.get_int('contents_tree')
        widget.set_history(value)
        widget = self.xml.get_widget('indexstate')
        value = self.pref.get_int('index_tree')
        widget.set_history(value)
        widget = self.xml.get_widget('toolbar_enable')
        value = self.pref.get_bool('toolbar_enable')
        widget.set_active(value)
        widget = self.xml.get_widget('tooltips_enable')
        value = self.pref.get_bool('toolbar_tooltips')
        widget.set_active(value)
        widget = self.xml.get_widget('searchpath')
        value = self.pref.get_string('chm_search_path')
        widget.set_text(value)
        widget = self.xml.get_widget('http_support')
        value = self.pref.get_bool('http_support')
        widget.set_active(value)
        if value == True:
            self.xml.get_widget('auto_raise').set_sensitive(True)
            self.xml.get_widget('new_window').set_sensitive(True)
        else:
            self.xml.get_widget('auto_raise').set_sensitive(False)
            self.xml.get_widget('new_window').set_sensitive(False)
        widget = self.xml.get_widget('auto_raise')
        value = self.pref.get_bool('auto_raise_window')
        widget.set_active(value)
        widget = self.xml.get_widget('new_window')
        value = self.pref.get_bool('new_browser_window')
        widget.set_active(value)
        widget = self.xml.get_widget('recent_spin')
        value = self.pref.get_int('recent_size')
        widget.set_value(value)
        widget = self.xml.get_widget('use_lcid')
        value = self.pref.get_bool('use_lcid')
        widget.set_active(value)
        widget = self.xml.get_widget('save_path')
        value = self.pref.get_bool('save_path')
        widget.set_active(value)
        widget = self.xml.get_widget('sync_page')
        value = self.pref.get_bool('sync_page')
        widget.set_active(value)
##        widget = self.xml.get_widget('glob_case_ins')
##        value = self.pref.get_bool('glob_case_ins')
##        widget.set_active(value)
        
    def run(self, parent):
        self.update_prefs()
        self.dialog.set_transient_for(parent)
        result = self.dialog.run()
        self.dialog.hide()
        return result

    def sync_page_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_BOOL:
                widget = self.xml.get_widget('sync_page')
                widget.set_active(entry.value.get_bool())
            else:
                print_log('sync_page_changed_cb: non-bool value')
				
    def source_newwindow_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_BOOL:
                widget = self.xml.get_widget('source_newwindow')
                widget.set_active(entry.value.get_bool())
            else:
                print_log('source_newwindow_cb: non-bool value')

    def use_lcid_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_BOOL:
                widget = self.xml.get_widget('use_lcid')
                widget.set_active(entry.value.get_bool())
            else:
                print_log('use_lcid_cb: non-bool value')

    def save_path_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_BOOL:
                widget = self.xml.get_widget('save_path')
                widget.set_active(entry.value.get_bool())
            else:
                print_log('save_path: non-bool value')

##    def glob_case_ins_cb(self, client, cnxn_id, entry, *args):
##        if entry.value:
##            if entry.value.type == gconf.VALUE_BOOL:
##                widget = self.xml.get_widget('glob_case_ins')
##                widget.set_active(entry.value.get_bool())
##            else:
##                print_log('glob_case_ins: non-bool value')

    def recent_size_changed_cb(self, client, cnxn_id, entry, *args):
        if entry.value:
            if entry.value.type == gconf.VALUE_INT:
                widget = self.xml.get_widget('recent_spin')
                widget.set_value(entry.value.get_int())
            else:
                print_log('recent_size_changed_cb: non-int value')
                
    def on_toolbar_enable_toggled(self, button):
        self.client.set_bool('/apps/gnochm/preferences/toolbar_enable',
                             button.get_active())
        if button.get_active():
            self.xml.get_widget('tooltips_enable').set_sensitive(True)
        else:
            self.xml.get_widget('tooltips_enable').set_sensitive(False)

    def on_http_support_toggled(self, button):
        self.client.set_bool('/apps/gnochm/preferences/http_support',
                             button.get_active())
        if button.get_active():
            self.xml.get_widget('auto_raise').set_sensitive(True)
            self.xml.get_widget('new_window').set_sensitive(True)
        else:
            self.xml.get_widget('auto_raise').set_sensitive(False)
            self.xml.get_widget('new_window').set_sensitive(False)

    def on_auto_raise_toggled(self, button):
        self.client.set_bool('/apps/gnochm/preferences/auto_raise_window',
                             button.get_active())

    def on_use_lcid_toggled(self, button):
        self.client.set_bool('/apps/gnochm/preferences/use_lcid',
                             button.get_active())

    def on_save_path_toggled(self, button):
        self.client.set_bool('/apps/gnochm/preferences/save_path',
                             button.get_active())

    def on_sync_page_toggled(self, button):
        self.client.set_bool('/apps/gnochm/preferences/sync_page',
                             button.get_active())

##    def on_glob_case_ins_toggled(self, button):
##        self.client.set_bool('/apps/gnochm/preferences/glob_case_ins',
##                             button.get_active())

    def on_source_newwindow_toggled(self, button):
        self.client.set_bool('/apps/gnochm/preferences/source_newwindow',
                             button.get_active())

    def on_new_window_toggled(self, button):
        self.client.set_bool('/apps/gnochm/preferences/new_browser_window',
                             button.get_active())

    def on_tooltips_enable_toggled(self, button):
        self.client.set_bool('/apps/gnochm/preferences/toolbar_tooltips',
                             button.get_active())

    def on_treestate_changed(self, menu):
        val = menu.get_history()
        self.client.set_int('/apps/gnochm/preferences/contents_tree', val)
        
    def on_indexstate_changed(self, menu):
        val = menu.get_history()
        self.client.set_int('/apps/gnochm/preferences/index_tree', val)
    
    def on_searchpath_changed(self, entry):
        val = entry.get_text()
        self.client.set_string('/apps/gnochm/preferences/chm_search_path', val)

    def on_htmlbg_color_set(self, picker, r, g, b, a):
        val = "#%04x%04x%04x" % (r, g, b)
        self.client.set_string('/apps/gnochm/preferences/html_bg_color', val)
        
    def on_chm_browse_clicked(self, button):
        self.filebox.set_transient_for(self.dialog)
        if (gtk.pygtk_version < (2,4,0)):
            self.filebox.file_list.get_parent().hide()
        response = self.filebox.run()
        self.filebox.hide()
        if response == gtk.RESPONSE_OK:
            directory = self.filebox.get_filename()
            if os.path.isdir(directory):
                widget = self.xml.get_widget('searchpath')
                widget.set_text(widget.get_text() + ':' + directory)

    def on_recent_spin_changed(self, spin):
        val = int(spin.get_value())
        self.client.set_int('/apps/gnochm/preferences/recent_size', val)
        
class AboutDialog:
    def __init__(self):
        logo = gtk.gdk.pixbuf_new_from_file(path_logo)
        translator = _('__Translator__')
        if translator == '__Translator__':
            translator = ''
        self.dialog = gnome.ui.About(
            _('CHM Viewer'),
            version,
            "Copyright (C) 2003-2007 Rubens Ramos <rubensr@users.sourceforge.net>",
            _('A viewer for Compressed HTML help archives'),
            ["Rubens Ramos <rubensr@users.sourceforge.net>"],
            ["Rubens Ramos <rubensr@users.sourceforge.net>"],
            translator,
            logo)
        self.dialog.connect('response', self.on_dialog_response)
        self.dialog.connect('close', self.on_dialog_close)
        self.dialog.connect('delete_event', self.on_dialog_close)

    def on_dialog_response(self, dialog, response, *args):
        # system-defined GtkDialog responses are always negative,
        # in which case we want to hide it
        if response < 0:
            dialog.hide()
            dialog.emit_stop_by_name('response')
                
    def on_dialog_close(self, widget, event=None):
        self.dialog.hide()
        return True
    
    def show(self):
        self.dialog.show()

class SourceWindow:
    def __init__(self, title, text, size):
        self.xml = gtk.glade.XML(path_mainwin, None, app)
        if (gtk.pygtk_version >= (2,4,0)):
            self.filebox = gtk.FileChooserDialog(
                buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,
                         gtk.STOCK_OPEN,gtk.RESPONSE_OK))
        else:
            self.filebox = self.xml.get_widget('FileSelection')
        self.window = self.xml.get_widget('SourceWindow')
        self.window.set_role('GNOCHMWIN')
        self.window.set_title(_('View source') + ' ' + title)
        (win_width, win_height) = size
        self.window.resize(win_width, win_height)
        icon = gtk.gdk.pixbuf_new_from_file(path_icon)
        self.window.set_icon(icon)
        self.text = self.xml.get_widget('source_textview')
        self.text.get_buffer().set_text(text)
        dict = {}
        for key in dir(self.__class__):
            dict[key] = getattr(self, key)
        self.xml.signal_autoconnect(dict)

    def show(self):
        self.window.show()

    def on_close_activate(self, *args):
        self.window.hide()
        self.window.destroy()

class MainApp:
    def __init__(self):
        self.FirstTime = 1
        self.chmfiles = []
        self.xml = gtk.glade.XML(path_mainwin, None, app)
        self.setup_gnome_app()
        self.pref = Preferences(self.xml)
        self.config = Configuration()
        self.aboutbox = AboutDialog()
        self.prefbox = PrefDialog(self.pref)
        if (gtk.pygtk_version >= (2,4,0)):
            self.filebox = gtk.FileChooserDialog(
                buttons=(gtk.STOCK_CANCEL,gtk.RESPONSE_CANCEL,
                         gtk.STOCK_OPEN,gtk.RESPONSE_OK))
        else:
            self.filebox = self.xml.get_widget('FileSelection')
        self.mainwin = self.xml.get_widget('MainWindow')
        self.mainwin.set_role('GNOCHMWIN')
        self.mainwin.show()
        self.htmlctxt = ctxt = self.xml.get_widget('HtmlMenu')
        if (gtk.pygtk_version < (2,2,0)):
            wcopy = self.xml.get_widget('copy1')
            wcopy.set_sensitive(False)
        self.statusbar = Statusbar(self.xml.get_widget('StatusBar'))
        self.currentUrl = ''
        icon = gtk.gdk.pixbuf_new_from_file(path_icon)
        self.mainwin.set_icon(icon)
        dict = {}
        for key in dir(self.__class__):
            dict[key] = getattr(self, key)
        self.xml.signal_autoconnect(dict)
        self.setup_tree_view()
        self.setup_htmlview()
        self.setup_navigation()
        self.setup_notebook()
        self.setup_bookmarks()
        self.viewsource_visible(False)
        self.showing_source = None
        self.set_preferences()
        self.statusbar.push(_('Welcome to GnoCHM!'))
        self.sidebarPos = self.xml.get_widget('HPaned').get_position()
        self.handlers = [
            ('\Ahttps?://',                  self.open_url),
            ('\Ajavascript:',                self.open_link_not_supported),
            ('\Amailto:',                    self.open_link_not_supported),
#            ('\A#[\w]+\Z',                   self.open_anchor),
            ('\A#',                   self.open_anchor),
            ('\ACHM=',                       self.open_link_not_supported),
            ('\AEXEC=',                      self.open_link_not_supported),
            ('\AHELP=',                      self.open_link_not_supported),
            (':',                            self.open_link),
            ('\A([\w/\. \-#&%]|[)]|[(])+\Z', self.open_chm),
            ]

    def setup_bookmarks(self):
        if os.path.exists(path_bm):
            self.bookmarks = pickle.load(open(path_bm, 'r'))
        else:
            self.bookmarks = {}

    def setup_gnome_app(self):
        tdock = self.xml.get_widget('Toolbar')
        tbar = self.xml.get_widget('Tbar')
        # prevent crash with gnome-pygtk2
        try:
            gnome.ui.app_setup_toolbar(tbar, tdock)
        except AttributeError:
            gnome.ui.gnome_app_setup_toolbar(tbar, tdock)
        mdock = self.xml.get_widget('Menubar')
        mbar = self.xml.get_widget('Mbar')
        #self.setup_notification_for_dock_item(mdock,
        #      '/desktop/gnome/interface/menubar_detachable')
        #self.setup_notification_for_dock_item(tdock,
        #      '/desktop/gnome/interface/toolbar_detachable')
        #self.mainwin.set_default_size(800,800)
        #self.mainwin.parse_geometry("500x200+100+100")
        #self.mainwin.resize(800,800)
        #self.mainwin.set_uposition(500,500)
        try:
            client = gnome.ui.gnome_master_client()
        except AttributeError:
            client = gnome.ui.master_client()
        if client:
            gobject.GObject.connect(client, 'save_yourself', self.session_save,
                                    sys.argv[0])
            gobject.GObject.connect(client, 'die', self.app_quit)
            
    def session_save(self, client, phase, save_style, is_shutdown,
                     interact_style, is_fast, *args):
        global df, instances
        print_log('session_save')
        argv = [args[0]]
        if df != None:
            argv.append('--debug')
        for instance in instances:
            for chmfile in instance.chmfiles:
                argv.append(chmfile.chmfile.filename)
        argc = len(argv)
        print_log('session_save: %s' % argv)
        client.set_clone_command(argc, argv)
        client.set_restart_command(argc, argv)
        if df != None:
            #print_log(str(gc.garbage))
            print_log('GnoCHM shutting down')
            df.close()
        return True
        
    def setup_notification_for_dock_item(self, dockitem, key):
        client = gconf.client_get_default ()
        notify_id = client.notify_add (key,
                                       self.dock_item_changed_notify,
                                       dockitem)
        dockitem.set_data('gnochm-app-gconf-notify-id', notify_id)
        dockitem.connect('destroy', self.remove_notification_cb)
        
    def dock_item_changed_notify(self, client, cnxn_id, entry, item):
        notify_id = item.get_data('gnochm-app-gconf-notify-id')
        if (notify_id == cnxn_id) and (entry.value.type == gconf.VALUE_BOOL):
            detachable = entry.value.get_bool()
            # Nothing is being done here, since this interface is private...
            #item.set_locked(not detachable)

    def remove_notification_cb(self, item):
        notify_id = item.get_data('gnochm-app-gconf-notify-id')
        if notify_id == 0:
            return
        client = gconf.client_get_default ()
        client.notify_remove(notify_id)
        client.unref()
        item.set_data('gnochm-app-gconf-notify-id', None)
    
    def setup_notebook(self):
        self.notebook = self.xml.get_widget('Notebook')
        self.update_notebook(None)
        
    def setup_htmlview(self):
        self.document = gtkhtml2.Document()
        self.document.connect('request_url', self.request_url)
        self.document.connect('link_clicked', self.link_clicked)
        self.sw = self.xml.get_widget('HTMLSW')
        self.htmlview = gtkhtml2.View()
        self.htmlview.set_document(self.document)
        self.htmlview.connect('request_object', self.request_object)
        self.htmlview.connect('on_url', self.on_url)
        self.htmlview.connect('button_press_event', self.html_context_menu)
        self.sw.add(self.htmlview)
        self.htmlview.show()
        self.htmlview.drag_dest_set(gtk.DEST_DEFAULT_ALL, target[:-1],
                                    gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_MOVE)
        self.htmlview.connect('drag_data_received',
                              self.on_chm_drag_data_received)

    # This came from:
    # http://www.pygtk.org/pygtk2tutorial/examples/treeviewcolumn.py
    # and it is supposed to make things work for gtk < 2.2
    # I've got no way of testing it... :(
    def make_pb(self, tvcolumn, cell, model, iter):
        stock = model.get_value(iter, 1)
        pb = self.treeview.render_icon(stock, gtk.ICON_SIZE_MENU, None)
        cell.set_property('pixbuf', pb)
        return

    def setup_tree_view(self):
        self.codecs = None
        self.linktable = {}
        # Contents
        self.cmodel = gtk.TreeStore(gobject.TYPE_STRING,
                                    gobject.TYPE_STRING,
				    gobject.TYPE_STRING)
        self.treeview = self.xml.get_widget('ContentsTView')
        self.treeview.set_model(self.cmodel)

        column1 = gtk.TreeViewColumn('Contents')
        cell0 = gtk.CellRendererPixbuf()
        column1.pack_start(cell0, True)
        # GTK+ 2.0 does not support "stock_id" property
        if gtk.gtk_version[1] < 2:
            column.set_cell_data_func(cell0, self.make_pb)
        else:
            column1.set_attributes(cell0, stock_id=2)

        cell1 = gtk.CellRendererText()
        column1.pack_start(cell1, True)
        column1.add_attribute(cell1, 'text', 0)

        self.treeview.append_column(column1)
        # Index
        self.imodel = gtk.TreeStore(gobject.TYPE_STRING,
                                    gobject.TYPE_STRING,
			            gobject.TYPE_STRING)
        self.indexview = self.xml.get_widget('IndexTView')
        self.indexview.set_model(self.imodel)
        cell2 = gtk.CellRendererText()
        column2 = gtk.TreeViewColumn('Index', cell2, text=0)
        self.indexview.append_column(column2)
        # Search
        self.smodel = gtk.ListStore(gobject.TYPE_STRING,
                                    gobject.TYPE_STRING)
        self.searchview = self.xml.get_widget('SearchTView')
        self.searchview.set_model(self.smodel)
        selection = self.searchview.get_selection()
        selection.set_mode(gtk.SELECTION_SINGLE)
        cell3 = gtk.CellRendererText()
        column3 = gtk.TreeViewColumn('Matches', cell3, text=0)
        self.searchview.append_column(column3)
        # Bookmarks
        self.bmodel = gtk.ListStore(gobject.TYPE_STRING)
        self.bmview = self.xml.get_widget('BookmarksTView')
        self.bmview.set_model(self.bmodel)
        selection = self.bmview.get_selection()
        selection.set_mode(gtk.SELECTION_SINGLE)
        cell4 = gtk.CellRendererText()
        column4 = gtk.TreeViewColumn('Bookmarks', cell4, text=0)
        self.bmview.append_column(column4)
        # Gconf stuff
        client = gconf.client_get_default ()
        state = client.get_bool('/apps/gnochm/preferences/titles_only')
        widget = self.xml.get_widget('TitlesOnlyCB')
        widget.set_active(state)
        state = client.get_bool('/apps/gnochm/preferences/whole_words')
        widget = self.xml.get_widget('WholeWordsCB')
        widget.set_active(state)
        state = client.get_bool('/apps/gnochm/preferences/search_previous')
        widget = self.xml.get_widget('SearchPrevCB')
        widget.set_active(state)
        self.xml.get_widget('SearchButton').set_sensitive(False)
        self.xml.get_widget('BookmarksAdd').set_sensitive(False)
        self.prev_matches = {}
        
    def setup_navigation(self):
        self.navigation = Navigation(self.xml)
        self.navigation.clear()
        self.xml.get_widget('HomeButton').set_sensitive(False)
        self.xml.get_widget('ZoomInButton').set_sensitive(False)
        self.xml.get_widget('ZoomOutButton').set_sensitive(False)
        self.xml.get_widget('ZoomFitButton').set_sensitive(False)
        self.xml.get_widget('home').set_sensitive(False)
        self.xml.get_widget('zoom_in').set_sensitive(False)
        self.xml.get_widget('zoom_out').set_sensitive(False)
        self.xml.get_widget('zoom_100').set_sensitive(False)

    def update_recent_files(self, *args):
        for inst in instances:
            inst.real_update_recent_files()
            
    def real_update_recent_files(self):
        root = self.xml.get_widget('recent_files')
        menu = gtk.Menu()
        root.remove_submenu()
        root.set_submenu(menu)
        i = 1
        for name in self.config.get_recent():
            aux = '%d.  %s' % (i, name)
            menuitem = gtk.MenuItem(aux.replace('_', '__'))
            menuitem.connect('activate', self.on_recent_file_activate, i)
            i = i + 1
            menu.add(menuitem)
            menuitem.show()
        menu.show()

    def set_preferences(self):
        widget = self.xml.get_widget('Tbar')
        widget.set_tooltips(self.pref.get_bool('toolbar_tooltips'))
        widget = self.xml.get_widget('Toolbar')
        if not self.pref.get_bool('toolbar_enable'):
            widget.hide()
        if self.pref.get_string('html_bg_color'):
            color = gtk.gdk.color_parse(self.pref.get_string('html_bg_color'))
            self.htmlview.modify_bg(gtk.STATE_NORMAL, color)
            self.htmlview.modify_bg(gtk.STATE_PRELIGHT, color)
        else:
            mbox = my_warning_dialog(_('\
It appears that gnochm has not been properly installed in your system. \
The README file included with the package contains instructions \
on how to fix this. Check the Troubleshooting section for the error:\n\n\
* TypeError: color_parse() argument 1 must be string, not None'))
            mbox.run()
            
    def to_utf8(self, text):
        if text:
            try:
                if self.codecs:
                    result, n = self.codecs[1](text, 'replace')
                    result = result.encode('utf-8', 'replace')
                else:
                    result = text.decode ('latin-1', 'replace')
                    result = result.encode('utf-8', 'replace')
                return result
            except UnicodeError:
                print_log('to_utf8: Error converting %s' % text)
                return text
        return text

    def get_html_charset(self, f):
        match = re.search('charset=(?P<cs>[a-zA-Z0-9_-]*)', f)
        if match:
            return match.group('cs')
        else:
            return None

    # Hack to remove nonascii characters in the title tag because of a bug in
    # gtkhtml2
    # http://bugzilla.gnome.org/buglist.cgi?query=154428
    def clean_title(self, f):
        p = re.compile('<title>.*</title>', re.IGNORECASE)
        match = p.search(f)
        if match:
            str = match.group()
            new_str = ''.join([ch for ch in str if ch in string.printable])
            return p.sub(new_str, f, count=1)
        else:
            return f

    def close_all(self):
        if len(self.chmfiles) > 0:
            if len(self.chmfiles) > 1:
                print_log('Oops - chmfiles length is %d' %
                          len(self.chmfiles))
            self.treeview.set_model(None)
            self.indexview.set_model(None)
            self.config.add_recent(self.chmfiles[-1].chmfile.filename)
            self.update_recent_files()
            print_log('CloseCHM= %s' % self.chmfiles[-1].chmfile.filename)
            self.chmfiles[-1].chmfile.CloseCHM()
            self.chmfiles = []
    
    def app_quit(self, *args):
        global gconfig
        
        self.close_all()
        self.config.save()
        pickle.dump(self.bookmarks, open(path_bm, 'w'))
        gconfig.write(open(path_cfg, 'w'))
        if df != None:
            #print_log(str(gc.garbage))
            print_log('GnoCHM shutting down')
            df.close()
        gtk.main_quit()

    def unwind_fs(self):
        if len(self.chmfiles) > 1:
            for cf in self.chmfiles[1:]:
                print_log('CloseCHM> %s' % cf.chmfile.filename)
                cf.chmfile.CloseCHM()
            del self.chmfiles[1:]

    def stream_cancel(self, *args):
        pass
    
    def request_url(self, document, link, stream):
        f, pathname = self.resolve_link(link, 1)
        if f:
            if (gtk.pygtk_version >= (2,5,90)):
                stream.set_cancel_func(self.stream_cancel)
            stream.write(f)
            if stream.get_written() < 100:
                stream.write(f)
            self.handle_anchors(pathname)
        else:
            print_log('request_url: Could not resolve %s' % link)
        self.unwind_fs()
        stream.close()

    def highlight_topic(self, pathname):
        if self.pref.get_bool('sync_page'):
            if pathname in self.linktable:
                treeiter = self.linktable[pathname]
                self.treeview.get_selection().select_iter(treeiter)

    def link_clicked(self, document, link):
        print_log('link_clicked: %s' % link)
        f, pathname = self.resolve_link(link)
        if f:
            self.chmfiles[-1].directory = os.path.dirname(pathname)
            self.document.open_stream('text/html')
            mime = mimetypes.guess_type(pathname,0)[0]
            if mime:
                ftype = mime.split('/')[0]
                if ftype == 'image':
                    f = syn_image_html % pathname
            self.document.write_stream(f)
            self.document.close_stream()
            self.chmfiles[-1].directory = os.path.dirname(pathname)
            self.navigation.set_current(pathname)
            self.handle_anchors(link)
            self.viewsource_visible()
            self.highlight_topic(pathname)
        else:
            self.viewsource_visible(False)
        self.unwind_fs()

    def viewsource_visible(self, force_state = None):
        menu_item = self.xml.get_widget('view_source')
        if force_state != None:
            menu_item.set_sensitive(force_state)
            return
        mime = mimetypes.guess_type(self.navigation.get_current())[0]
        if mime and not mime.startswith('text'):
            menu_item.set_sensitive(False)
        else:
            menu_item.set_sensitive(True)
            
    def request_object(self, view, bin):
        print_log('request_object: %s %s' % (str(view), str(bin)))
        return True

    def on_url(self, view, data):
        if data:
            self.statusbar.push(data)
        else:
            self.statusbar.pop()

    def html_context_menu(self, view, event):
        if (gtk.pygtk_version >= (2,2,0)):
            selection = gtkhtml2.html_selection_get_text(self.htmlview)
            if event.button == 3 and selection != None:
                self.htmlctxt.popup(None, None, None, event.button, event.time)
                return True
            else:
                return False
                
    def on_htmlcopy_activate(self, widget):
        selection = gtkhtml2.html_selection_get_text(self.htmlview)
        if selection != None:
            cb = widget.get_clipboard(gtk.gdk.SELECTION_CLIPBOARD)
            cb.set_text(selection)
        
    def find_file(self, link):
        client = gconf.client_get_default ()
        case_check = True
        if os.path.isfile(link):
            return link
        else:
            chmpaths = string.split(self.pref.get_string('chm_search_path'),
                                    ':')
            try:
                dirs = string.split(os.environ['CHMPATH'], ':')
            except KeyError:
                dirs = []
            dirs.extend(chmpaths)
            dirs.append(os.getcwd())
            for dir in dirs:
                if os.path.isdir(dir):
                    pathname = os.path.expanduser(os.path.join(dir, link))
                    if os.path.isfile(pathname):
                        return pathname
                    if os.path.isfile(dir + link):
                        return dir + link
                    if case_check:
                        allfiles = os.listdir(dir)
                        allfilesl = [x.lower() for x in allfiles]
                        head, tail = os.path.split(pathname)
                        pathname = os.path.join(head, tail.lower())
                        for file in allfilesl:
                            if os.path.join(dir, file) == pathname:
                                idx = allfilesl.index(file)
                                return os.path.join(dir, allfiles[idx])
                else:
                    print_log('find_file: dir does not exist %s' % dir)
        return None

    def open_url(self, link, internal):
        if not self.pref.get_bool('http_support'):
            self.statusbar.push_error (_('HTTP links not enabled.'))
            return (None, '')
        else:
            try:
                parts = urlparse.urlparse(link)
                if parts[0] or parts[1]:
                    uri = link
                    uri = urlparse.urljoin(self.currentUrl, link)
                    if internal:
                        try:
                            f = urllib.urlopen(uri)
                        except IOError:
                            print_log('open_url: Cannot open %s' % uri)
                        return (f.read(), uri)
                    else:
                        try:
                            webbrowser.open(
                                uri,
                                self.pref.get_bool('new_browser_window'),
                                self.pref.get_bool('auto_raise_window'))
                            self.statusbar.push_error (
                                _('Link opened in external browser'))
                        except Error:
                            print_log('open_url: webbrowser error %s' % uri)
                        return (None, '')
            except OSError:
                self.statusbar.push_error (_('Failed to open %s') % link)
                print_log('open_url: OS error %s' % link)
                return (None, '')
            except IOError, (errtyp, errtuple):
                errno, strerror = errtuple
                self.statusbar.push_error (strerror)
                print_log('open_url: %s %s' % (strerror, link))
                return (None, '')
            return (None, '')

    def open_chm(self, link, internal):
        print_log('open_chm: %s' % link)
        comps = string.split(link,'#')
        pathname = os.path.normpath(os.path.join(self.chmfiles[-1].directory,
                                                 comps[0]))
        result, ui = self.chmfiles[-1].chmfile.ResolveObject(pathname)
        if result == 0:
            size, text = self.chmfiles[-1].chmfile.RetrieveObject(ui)
            return (text, pathname)
        else:
            pathname = urllib.unquote(pathname)
            result, ui = self.chmfiles[-1].chmfile.ResolveObject(pathname)
            if result == 0:
                size, text = self.chmfiles[-1].chmfile.RetrieveObject(ui)
                return (text, pathname)
        print_log('open_chm: Failed to open %s' % link)
        return (None, '')
    
    def open_link(self, link, internal):
        print_log('open_link: %s' % link)
        val = string.split(link, ':')
        if val[-1] and val[-3]:
            cf = chm.CHMFile()
            filename = self.find_file(val[-3])
            if not filename:
                filename = self.find_file(val[-3].lower())
            if filename and cf.LoadCHM(filename):
                print_log('LoadCHM= %s' % filename)
                result, ui = cf.ResolveObject(val[-1])
                if result == 0:
                    size, text = cf.RetrieveObject(ui)
                    if size > 0:
                        directory = os.path.dirname(val[-1])
                        self.chmfiles.append(CHMFS(cf, directory))
                        return (text, val[-1])
                    else:
                        self.statusbar.push_error(
                            _('Could not open %s in %s') % (val[-1],val[-3]))
                        print_log('open_link: Could not open %s in %s' %
                                  (val[-1],val[-3]))
                else:
                    self.statusbar.push_error(
                        _('Failed to resolve %s') % val[-1])
                    print_log('Failed to resolve %s' % val[-1])
            else:
                self.statusbar.push_error(
                    _('Failed to load %s') % val[-3])
                print_log('Failed to load %s' % val[-3])
        else:
            self.statusbar.push_error(
                _('Invalid files %s %s') % (val[-3], val[-1]))
            print_log('Invalid files %s %s' % (val[-3], val[-1]))
        return (None, '')
    
    def handle_anchors(self, link):
        comps = string.split(link, '#')
        if len(comps) > 1:
            print_log('handle_anchors: [%s]' % comps[1])
            self.htmlview.jump_to_anchor(comps[1])

    def open_anchor(self, link, internal):
        print_log('open_anchor: [%s]' % link)
        self.htmlview.jump_to_anchor(link[1:])
        self.navigation.set_current(link)
        return (None, '')

    def open_link_not_supported(self, link, internal):
        self.statusbar.push_error (_('Link not supported: %s') % link)
        return (None, '')

    def find_handler(self, link):
        aux = unicode(repl_entities(self.to_utf8(link)))
        try:
            for handler in self.handlers:
                pattern, func = handler
                if re.compile(pattern, re.U).search(aux):
                    return func
            else:
                return self.open_chm
        except:
            print_log("find_handler: cannot do unicode: %s" % link)
        return None
        
    def resolve_link(self, link, internal=0):
        if link != '#':
            func = self.find_handler(link)
            if func:
                return func(link, internal)
            print_log('resolve_link: No handler for %s' % link)
        return (None, '')

    def internal_request_file(self, link):
        aux = []
        aux.append(repl_entities(link))
        aux.append(self.to_utf8(aux[0]))
        pref1 = locale.getdefaultlocale()[0]
        if pref1:
            pref2 = string.split(pref1, '_')[0]
            aux.append('/' + pref1 + aux[0])
            aux.append('/' + pref1 + aux[1])
            aux.append('/' + pref2 + aux[0])
            aux.append('/' + pref2 + aux[1])

        for flink in aux:
            f, pathname = self.resolve_link(flink)
            if f:
                return f, pathname, flink
        return None, None, None
    
    def request_file(self, link):
        f, pathname, flink = self.internal_request_file(link)
        if f:
            self.chmfiles[-1].directory = os.path.dirname(flink)
            self.document.open_stream('text/html')
            mime = mimetypes.guess_type(pathname,0)[0]
            if mime:
                ftype = mime.split('/')[0]
                if ftype == 'image':
                    f = syn_image_html % pathname
            if self.get_html_charset(f):
                f = self.clean_title(f)
            else:
                # Well - this causes some quirks in certain files, for
                # example the xhtml1 spec (some garbage shows up at the
                # top of the pages. It looks like this code does more
                # good than bad, so I'll leave it here for now.
                self.document.write_stream(html_charset % 'utf-8')
                f = self.to_utf8(f)
            self.document.write_stream(f)
            self.document.close_stream()
            self.handle_anchors(flink)
            self.statusbar.push(_('Opened %s') % pathname)
            self.navigation.set_current(link)
            self.viewsource_visible()
            self.unwind_fs()
            self.highlight_topic(pathname)
            return 1
        else:
            self.document.open_stream('text/html')
            self.document.write_stream(html_text % '')
            self.document.close_stream()
            print_log('request_file: cannot resolve %s' % link)
            self.viewsource_visible(False)
            self.unwind_fs()
            return 0

    def update_notebook(self, chmfile):
        if chmfile:
            self.mainwin.set_sensitive(False)
            self.cmodel.clear()
            self.imodel.clear()
            self.smodel.clear()
            self.bmodel.clear()
            chmfile.topics = self.to_utf8(chmfile.topics)
            topics = self.to_utf8(chmfile.GetTopicsTree())
            if topics:
                self.notebook.get_nth_page(0).show()
                try:
                    parser = TopicsParser(self.cmodel)
                    self.linktable = parser.feed_and_get_links(topics)
                    state = self.pref.get_int('contents_tree')
                    if state == 2:
                        self.treeview.expand_row((0), False)
                    elif state == 0:
                        self.treeview.expand_all()
                    else:
                        self.treeview.collapse_all()
                    used = 1
                except SGMLParseError:
                    print_log('update_notebook: Malformed contents file')
            else:
                self.notebook.get_nth_page(0).hide()
            chmfile.index = self.to_utf8(chmfile.index)
            index = self.to_utf8 (chmfile.GetIndex())
            if index:
                try:
                    #self.imodel.set_sort_column_id(0, gtk.SORT_ASCENDING)
                    TopicsParser(self.imodel).feed(index)
                    self.notebook.get_nth_page(1).show()
                    state = self.pref.get_int('index_tree')
                    if state == 2:
                        self.indexview.expand_row((0), False)
                    elif state == 0:
                        self.indexview.expand_all()
                    else:
                        self.indexview.collapse_all()
                    used = 1

                except SGMLParseError:
                    print_log('update_notebook: Malformed index file')
            else:
                self.notebook.get_nth_page(1).hide()
            if chmfile.IsSearchable():
                self.notebook.get_nth_page(2).show()
            else:
                self.notebook.get_nth_page(2).hide()
            self.xml.get_widget('BookmarksEntry').set_text('')
            self.xml.get_widget('BookmarksAdd').set_sensitive(False)
            if self.bookmarks.has_key(chmfile.filename):
                bm = self.bookmarks[chmfile.filename]
                keys = bm.keys()
                keys.sort()
                for key in keys:
                    iter = self.bmodel.append()
                    self.bmodel.set_value(iter, 0, key)
                self.searchview.get_selection().unselect_all()
            self.notebook.set_current_page(0)
            self.mainwin.set_sensitive(True)
        
    def open_file(self, filename):
        if os.path.isfile(filename):
            chmfile = chm.CHMFile()
            if chmfile.LoadCHM(filename):
                print_log('LoadCHM: %s' % filename)
                if len(self.chmfiles) > 0:
                    print_log('CloseCHM: %s' %
                              self.chmfiles[-1].chmfile.filename)
                    self.config.add_recent(self.chmfiles[-1].chmfile.filename)
                    self.update_recent_files()
                    self.chmfiles[-1].chmfile.CloseCHM()
                    self.chmfiles.pop()
                self.document.open_stream('text/html')
                #self.document.write_stream(html_text % _('Loading...'))
                self.document.write_stream(html_text % 'Loading...')
                self.document.close_stream()
                #self.document.clear()
                self.navigation.clear()
                self.codecs = None
                encoding = chmfile.GetEncoding()
                if encoding:
                    try:
                        self.codecs = codecs.lookup(encoding)
                        print_log('Using codecs %s' % encoding)
                    except:
                        print_log('Encoding error: %s' % encoding)
                client = gconf.client_get_default ()
                state = client.get_bool('/apps/gnochm/preferences/use_lcid')
                lcid = chmfile.GetLCID()
                if state and lcid:
                    if (not self.codecs) or (lcid != encoding):
                        try:
                            self.codecs = codecs.lookup(lcid[0])
                            print_log('Using LCID %s' % lcid[0])
                        except:
                            print_log('LCID error: %s' % lcid[0])
                            mbox = my_warning_dialog(
                                _('Your Python installation does not support %s (%s - %s). It is likely that the characters in the navigation tabs will not be correctly displayed.') % (lcid[1], lcid[2], lcid[0]))
                            mbox.run()
                self.update_notebook(chmfile)
                # hmm I feel we're actually hiding a pychm shortcoming here, but
                # I'm too lazy to go there...
                chmfile.home = self.to_utf8(chmfile.home)
                print_log('File Encoding: %s' % chmfile.encoding)
                print_log('File Locale: %s' % chmfile.lcid)
                print_log('Home: %s' % chmfile.home)
                self.chmfiles.append(CHMFS(chmfile, '/'))
                if self.chmfiles[-1].chmfile.home != '/':
                    if self.request_file(self.chmfiles[-1].chmfile.home):
                        self.chmfiles[-1].directory = os.path.dirname(
                            self.chmfiles[-1].chmfile.home)
                        self.navigation.set_current(
                            self.chmfiles[-1].chmfile.home)
                    self.xml.get_widget('HomeButton').set_sensitive(True)
                    self.xml.get_widget('home').set_sensitive(True)
                else:
                    self.xml.get_widget('HomeButton').set_sensitive(False)
                    self.xml.get_widget('home').set_sensitive(False)
                if chmfile.title:
                    self.mainwin.set_title(_('CHM Viewer - %s') %
                                           repl_entities(self.to_utf8
                                                         (chmfile.title)))
                else:
                    self.mainwin.set_title(_('CHM Viewer - %s') %
                                           os.path.basename(filename))
                if self.FirstTime:
                    self.xml.get_widget('zoom_in').set_sensitive(True)
                    self.xml.get_widget('zoom_out').set_sensitive(True)
                    self.xml.get_widget('zoom_100').set_sensitive(True)
                    self.xml.get_widget('ZoomInButton').set_sensitive(True)
                    self.xml.get_widget('ZoomOutButton').set_sensitive(True)
                    self.xml.get_widget('ZoomFitButton').set_sensitive(True)
                    self.FirstTime = 0
                self.prev_matches = {}
                return 1
            else:
                mbox = my_warning_dialog(
                    _('File not recognized: %s') % filename)
                mbox.run()
        else:
            mbox = my_warning_dialog(
                _('File not found: %s') % filename)
            mbox.run()
        return 0
            
    def on_delete_dialog(self, dialog, event):
        dialog.hide()
    
    def on_main_delete_event(self, *args):
        self.on_close_activate(args)

    def on_open_activate(self, *args):
        global gconfig
        
        self.filebox.set_transient_for(self.mainwin)
        client = gconf.client_get_default ()
        reuse_path = client.get_bool('/apps/gnochm/preferences/save_path')
        if reuse_path and gconfig.has_option('General', 'LastDir'):
            dir = gconfig.get('General', 'LastDir')
            if os.path.isdir(dir):
                if (gtk.pygtk_version >= (2,4,0)):
                    self.filebox.set_current_folder(dir)
                else:
                    self.filebox.set_filename(dir)
            else:
                print_log('on_open_activate: oops %s is not a dir!' % dir)
        response = self.filebox.run()
        self.filebox.hide()
        if response == gtk.RESPONSE_OK:
            filename = self.filebox.get_filename()
            self.open_file(filename)
            if not gconfig.has_section('General'):
                gconfig.add_section('General')
            gconfig.set('General', 'LastDir',
                        os.path.dirname(filename) + '/')
        
    def on_new_activate(self, *args):
        inst = MainApp()
        instances.append(inst)
        inst.real_update_recent_files()
        
    def on_close_activate(self, *args):
        if len(instances) > 1:
            self.close_all()
            self.mainwin.destroy()
            instances.remove(self)
        else:
            self.app_quit(args)
        
    def on_quit_activate(self, *args):
        self.app_quit(args)

    def on_preferences_activate(self, *args):
        self.prefbox.run(self.mainwin)

    def on_contents_activate(self, *args):
        gnome.help_display(path_help, 'index')
        
    def on_about_activate(self, *args):
        self.aboutbox.show()

    def on_back_activate (self, *args):
        file = self.navigation.go_back()
        if file[0] == '#':
            self.htmlview.jump_to_anchor(file[1:])
        else:
            self.request_file(file)
            self.chmfiles[-1].directory = os.path.dirname(file)
            selection = self.treeview.get_selection()
            selection.unselect_all()
            selection = self.indexview.get_selection()
            selection.unselect_all()
            self.navigation.update_adjustments()
    
    def on_home_activate(self, *args):
        if len(self.chmfiles) > 0 and self.chmfiles[-1].chmfile.home != '/':
            if self.request_file(self.chmfiles[-1].chmfile.home):
                self.chmfiles[-1].directory = os.path.dirname(
                    self.chmfiles[-1].chmfile.home)
                self.navigation.set_current(self.chmfiles[-1].chmfile.home)
            selection = self.treeview.get_selection()
            selection.unselect_all()
            selection = self.indexview.get_selection()
            selection.unselect_all()
    
    def on_forward_activate(self, *args):
        file = self.navigation.go_forward()
        if file[0] == '#':
            self.htmlview.jump_to_anchor(file[1:])
        else:
            self.request_file(file)
            self.chmfiles[-1].directory = os.path.dirname(file)
            selection = self.treeview.get_selection()
            selection.unselect_all()
            selection = self.indexview.get_selection()
            selection.unselect_all()
            self.navigation.update_adjustments()

    def on_zoomin_activate(self, *args):
        self.htmlview.zoom_in()
    
    def on_zoomreset_activate(self, *args):
        self.htmlview.zoom_reset()

    def on_zoomout_activate(self, *args):
        self.htmlview.zoom_out()

    def on_toggle_sidebar_activate(self, *args):
        if self.xml.get_widget('HPaned').get_position() == 0:
            self.xml.get_widget('HPaned').set_position(self.sidebarPos)
        else:
            self.sidebarPos = self.xml.get_widget('HPaned').get_position()
            self.xml.get_widget('HPaned').set_position(0)
        
    def on_viewsource_activate(self, *args):
        lasturl = self.navigation.get_current()
        if self.showing_source:
            self.request_file(lasturl)
            menu_label = self.xml.get_widget('view_source').get_child()
            menu_label.set_text_with_mnemonic(_('_View Source'))
            self.showing_source = None
        else:
            f, pathname, flink = self.internal_request_file(lasturl)
            if f:
                if (self.pref.get_bool('source_newwindow')):
                    source_window = SourceWindow(pathname, self.to_utf8(f),
                                                 self.mainwin.get_size())
                    source_window.show()
                else:
                    # [ag] TODO: Find a way to push the current url down the
                    #            history stack and have it removed as soon as
                    #            the original url gets shown
                    self.document.open_stream('text/html')
                    self.document.write_stream(html_charset % 'utf-8')
                    html_buffer = re.sub(r'&', r'&amp;', f)
                    html_buffer = re.sub(r'<', r'&lt;', html_buffer)
                    html_buffer = re.sub(r'>', r'&gt;', html_buffer)
                    html_buffer = re.sub(r"'", r'&apos;', html_buffer)
                    html_buffer = re.sub(r'"', r'&quot;', html_buffer)
                    self.document.write_stream(html_buffer)
                    self.document.close_stream()
                    self.statusbar.push(_('Viewing source for %s') % lasturl)
                    menu_label = self.xml.get_widget('view_source').get_child()
                    menu_label.set_text_with_mnemonic(_('_View HTML'))
                    self.showing_source = 1
            self.unwind_fs()

## Some preliminary code to move selection in tree view...
##    def on_tview_move_up(self, tview):
##        selection = tview.get_selection()
##        model, iter = selection.get_selected()
##        if model != None and iter != None:
##            path = model.get_path(iter)
##            if path != None:
##                if path[-1] > 0:
##                    path = path[0:-1] + ((path[-1] - 1),)
##                else if len(path) > 1:
##                    path = path[0:-1]
##                selection.select_path(path)
        
    def on_tview_follow(self, tview):
        selection = tview.get_selection()
        model, iter = selection.get_selected()
        #print model.get_path(iter)
        if iter:
            file = model.get_value(iter, 1)
            if file:
                if self.find_handler(file) == self.open_chm:
                    file = '/' + file
                if self.request_file(file):
                    self.navigation.set_current(file)
                    self.chmfiles[-1].directory = os.path.dirname(file)
        
    def on_contents_cursor_changed(self, *args):
        self.on_tview_follow(self.treeview)

    def on_IndexTView_row_activated(self, *args):
        self.on_tview_follow(self.indexview)

    def on_chm_drag_data_received(self, w, context, x, y,
                                  data, info, time):
        if data.format == 8:
            #print data.data
            names = string.split(data.data, '\r\n')
            for i in range(len(names)):
                escaped = urllib.unquote(names[i])
                aux = list(urlparse.urlparse(escaped))
                if aux[0] == 'file':
                    if i == 0 and len(self.chmfiles)==0:
                        self.open_file(aux[2])
                    else:
                        inst = MainApp()
                        instances.append(inst)
                        inst.open_file(aux[2])
            context.finish(True, False, time)
        else:
            context.finish(False, False, time)

    def on_TitlesOnlyCB_toggled(self, button):
        client = gconf.client_get_default ()
        client.set_bool('/apps/gnochm/preferences/titles_only',
                        button.get_active())
        
    def on_WholeWordsCB_toggled(self, button):
        client = gconf.client_get_default ()
        client.set_bool('/apps/gnochm/preferences/whole_words',
                        button.get_active())

    def on_SearchPrevCB_toggled(self, button):
        client = gconf.client_get_default ()
        client.set_bool('/apps/gnochm/preferences/search_previous',
                        button.get_active())

    def on_SearchGEntry_changed(self, gentry):
        text = self.xml.get_widget('SearchEntry').get_text()
        if len(text) > 1 and len(self.chmfiles) > 0:
            self.xml.get_widget('SearchButton').set_sensitive(True)
        else:
            self.xml.get_widget('SearchButton').set_sensitive(False)

    def on_SearchButton_clicked(self, button):
        if len(self.chmfiles) == 0:
            return
        self.statusbar.push(_('Searching, please wait...'))
        gtk.main_iteration_do(False)
        text = self.xml.get_widget('SearchEntry').get_text()
        file = self.chmfiles[-1].chmfile
        ww = self.xml.get_widget('WholeWordsCB').get_active()
        to = self.xml.get_widget('TitlesOnlyCB').get_active()
        pr = self.xml.get_widget('SearchPrevCB').get_active()
        matches = {}
        sentences = text.strip().replace(' AND ', ' ').split(' OR ')
        for sentence in sentences:
            words = sentence.split()
            partial, pmatches = file.Search(words[0], ww, to)
            #print partial, pmatches
            for word in words[1:]:
                if len(word) > 0:
                    partial, aux = file.Search(word, ww, to)
                    for k in pmatches.keys():
                        if k not in aux.keys():
                            del pmatches[k]
            matches.update(pmatches)
        self.smodel.clear()
        if len(matches) > 0:
            if pr and len(self.prev_matches) > 0:
                for k in matches.keys():
                    if not self.prev_matches.has_key(k):
                        del matches[k]
            keys = matches.keys()
            keys.sort()
            for key in keys:
                iter = self.smodel.append()
                self.smodel.set_value(iter, 0, self.to_utf8(key))
                self.smodel.set_value(iter, 1, matches[key])
            self.searchview.get_selection().unselect_all()
            self.prev_matches = matches
        else:
            mbox = my_warning_dialog(
                _('No matches for \'%s\' found') % text)
            mbox.run()
        self.statusbar.push(_('Search Done!'))

    def on_search_activate(self, *args):
        self.notebook.set_current_page(2)
        self.xml.get_widget('SearchEntry').grab_focus()

    def on_SearchDisplay_clicked(self, *args):
        self.on_tview_follow(self.searchview)

    def on_recent_file_activate(self, item, number):
        r = self.config.get_recent()
        self.open_file(r[number-1])

    def on_BookmarksTView_row_activated(self, *args):
        selection = self.bmview.get_selection()
        model, iter = selection.get_selected()
        if iter:
            key = model.get_value(iter, 0)
            filename = self.chmfiles[-1].chmfile.filename
            bm = self.bookmarks[filename]
            if bm.has_key(key):
                self.request_file(bm[key])
                self.navigation.set_current(bm[key])
            else:
                self.statusbar.push(_('Could not find %s') % key)
        
    def on_BookmarksTView_cursor_changed(self, *args):
        selection = self.bmview.get_selection()
        model, iter = selection.get_selected()
        if iter:
            text = model.get_value(iter, 0)
            self.xml.get_widget('BookmarksEntry').set_text(text)

    def on_BookmarksEntry_changed(self, entry):
        text = entry.get_text()
        if len(text) > 0 and len(self.chmfiles) > 0:
            self.xml.get_widget('BookmarksAdd').set_sensitive(True)
        else:
            self.xml.get_widget('BookmarksAdd').set_sensitive(False)

    def on_BookmarksAdd_clicked(self, button):
        key = self.xml.get_widget('BookmarksEntry').get_text()
        filename = self.chmfiles[-1].chmfile.filename
        try:
            bm = self.bookmarks[filename]
            if bm.has_key(key):
                d = gtk.MessageDialog(self.mainwin,
                                      gtk.DIALOG_MODAL,
                                      gtk.MESSAGE_QUESTION,
                                      gtk.BUTTONS_YES_NO,
                                      _('\'%s\' already exists, overwrite?') %
                                      key)
                resp = d.run()
                if resp == gtk.RESPONSE_YES:
                    bm[key] = self.navigation.current
                d.destroy()
                return
            bm[key] = self.navigation.current
        except KeyError:
            self.bookmarks[filename] = {key : self.navigation.current}
        iter = self.bmodel.append()
        self.bmodel.set_value(iter, 0, key)
        self.bmview.get_selection().unselect_all()
        self.xml.get_widget('BookmarksEntry').set_text('')
        
    def on_BookmarksRemove_clicked(self, button):
        selection = self.bmview.get_selection()
        model, iter = selection.get_selected()
        if iter:
            key = model.get_value(iter, 0)
            filename = self.chmfiles[-1].chmfile.filename
            bm = self.bookmarks[filename]
            if bm.has_key(key):
                del bm[key]
                self.bmodel.remove(iter)
                self.xml.get_widget('BookmarksEntry').set_text('')

def usage():
    license = N_('gnochm is free software; you can redistribute it and/or\n\
modify it under the terms of the GNU General Public License as\n\
published by the Free Software Foundation; either version 2 of the\n\
License, or (at your option) any later version.')

    print 'GnoCHM v', version
    print _('A CHM viewer for Gnome')
    print ''
    print _(license)
    print ''
    print _('Syntax: ')
    print _(' gnochm [options] [filename]...')
    print ''
    print _('Options:')
    print _(' -v | --version: prints the program version and exits')
    print _(' -h | --help: prints this help message and exits')
    print _(' -d | --debug: enables debug data to be written to ~/.gnochm/gnochm.log')

def parse_args_old(sysargs):
    global df
    try:
        opts, args = getopt.getopt(sys.argv[1:], ":hvd",
                                   ["help", "version", "debug"])
    except getopt.GetoptError:
        # This hack is to allow gnochm to accept session related
        # gnome command line parameters to be passed without errors
        #usage()
        #sys.exit(2)
        opts = []
        args = []
        pass
    output = None
    verbose = False
    for o, a in opts:
        if o in ("-h", "--help"):
            usage()
            sys.exit()
        if o in ("-v", "--version"):
            print version
            sys.exit()
        if o in ("-d", "--debug"):
            df = open(path_log, 'w',0)
            print_log ('Yet another debug session :)')
    return args

def parse_args():
    global program, df
    leftover_args, argdict = program.get_popt_args()
    if (argdict['debug'] == 1):
            df = open(path_log, 'w',0)
            print_log ('Yet another debug session :)')        
            print_log ('Leftover: %s' % leftover_args)        
            print_log ('Argdictr: %s' % argdict)
    return leftover_args
    
if __name__ == '__main__':
    global program
    #gc.set_debug(gc.DEBUG_LEAK)
    locale.setlocale(locale.LC_ALL, '')
    gettext.bindtextdomain(app, path_locale)
    gettext.textdomain(app)
    gtk.glade.bindtextdomain(app, path_locale)
    gettext.install(app, path_locale, unicode=1)
    if not os.path.exists(path_user):
        os.mkdir(path_user)
    gconfig = ConfigParser.ConfigParser()
    gconfig.read([path_cfg])
    if os.path.exists(path_rec):
        recent_list = pickle.load(open(path_rec, 'r'))
    else:
        recent_list = []

    try:
        if (gnome.gnome_python_version >= (2,4,0)):
            program = gnome.init(app, version,
                                 gnome.libgnome_module_info_get(),
                                 sys.argv, popt_table)
            args = parse_args()
        else:
            args = parse_args_old(sys.argv[1:])
            program = gnome.init(app, version)
    except:
        args = parse_args_old(sys.argv[1:])
        program = gnome.init(app, version)
        
    program.set_property(gnome.PARAM_APP_DATADIR, homedir)
    if len(args) > 0:
        for i in range(len(args)):
            inst = MainApp()
            instances.append(inst)
            inst.open_file(args[i])
    else:
        inst = MainApp()
        instances = [inst]
    instances[0].update_recent_files()
    gtk.main()

# vim: expandtab softtabstop=4
