#!/usr/bin/env python
# -*- mode: python; coding: koi8-r; -*-
# (c) Con Radchenko, 2004. mailto:bofh@pochta.ru
#
# 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 Library 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
import os
import re
import shelve
from ConfigParser import ConfigParser

from threading import Thread, Lock
from Queue import Queue

from dictclient import Connection

import gtk, gtk.glade, pango, gobject
gdk = gtk.gdk
glade = gtk.glade

try:
    from pybookreader.config import charsets_list, default_encoding
except ImportError:
    charsets_list = ('koi8-r', 'cp1251', 'cp866', 'iso8859-5', 'utf-8')
    import locale
    locale.setlocale(locale.LC_ALL, '')
    default_encoding = locale.getlocale()[1]

dictclient_history_length = 100

def error_dialog(parent, msg):
    dialog = gtk.MessageDialog(parent, gtk.DIALOG_MODAL \
                               | gtk.DIALOG_DESTROY_WITH_PARENT,
                               gtk.MESSAGE_ERROR, gtk.BUTTONS_OK,
                               msg)
    dialog.run()
    dialog.destroy()



class PyGtkDict:

    COLUMN_DB_CHECK = 0
    COLUMN_DB_NAME = 1
    COLUMN_DB_DESC = 2

    COLUMN_STRAT_CHECK = 0
    COLUMN_STRAT_NAME = 1
    COLUMN_STRAT_DESC = 2

    properties = {
        'title_font'       : 'Normal',
        'word_font'        : 'Normal',
        'header_font'      : 'Normal',
        'body_font'        : 'Normal',
        'title_color'      : '#ff0000',
        'word_color'       : '#ff0000',
        'header_color'     : '#0000b0',
        'body_color'       : '#000000',
        'background_color' : '#ffffff',
        'width'            : 600,
        'height'           : 400,
        'servers_width'    : 400,
        'servers_height'   : 400,
        }

    current_server = '' # '' == all servers

    keysyms_up      = (gtk.keysyms.KP_Up,
                       gtk.keysyms.Up)
    keysyms_down    = (gtk.keysyms.KP_Down,
                       gtk.keysyms.Down)


    def __init__(self, glade_file, db_file, history_file):
        self.glade_file = glade_file
        self.db_file = db_file

        self.connects = {}
        conn = DictConnect('localhost', 2628)
        #conn.stratdescs = self.default_strat_list
        self.connects['local 1'] = conn

        self.history_file = history_file
        self.history = []
        self.history_index = -1

        self.read_db_file()

        self.create_widgets()

        self.words_queue = Queue(0)

        th = Thread(target=self.connects_thread)
        th.start()

        self.n_connects = 0
        self.xterm_cursor = gdk.Cursor(gdk.XTERM)
        self.watch_cursor = gdk.Cursor(gdk.WATCH)
        self.hand_cursor = gdk.Cursor(gdk.HAND2)
        self.current_cursor = self.xterm_cursor

        self.links_list = []


    def connects_thread(self):
        while 1:
            word = self.words_queue.get()

            if word is None:
                break

            gdk.threads_enter()
            end_iter = self.textbuffer.get_end_iter()
            self.textbuffer.insert_with_tags(end_iter, '%s\n\n' % word,
                                             self.title_tag)
            gdk.threads_leave()


            for conn in self.connects:

                server = self.connects[conn]
                if server.no_use and conn != self.current_server:
                    # установлен флаг no_use и не выбран в меню Servers
                    # пропускаем
                    continue
                if self.current_server and conn != self.current_server:
                    # В меню Server выбран не All
                    continue

                if self.n_connects == 0:
                    gdk.threads_enter()
                    w = self.textview.get_window(gtk.TEXT_WINDOW_TEXT)
                    w.set_cursor(self.watch_cursor)
                    gdk.threads_leave()

                self.n_connects += 1
                th = Thread(target=self.read_defs, args=(word, server))
                th.start()


    def read_defs(self, word, server):

        # convert request to server.encoding
        #word =  unicode(word, 'utf-8').encode(server.encoding, 'replace')

        try:
            defs = server.get_definitions(word)
        except Exception, err:
            print Exception, err
            self.restore_cursor()
            return
        #defs = server.get_definitions(word)

        for d in defs:
            if server.databases:
                dbname = '%s [%s]' \
                         % (server.available_databases[d.db.name], d.db.name)
            else:
                dbname = d.db.name
            dbname =  unicode(dbname, server.encoding, 'replace').encode('utf-8')
            s = unicode(d.defstr, server.encoding, 'replace').encode('utf-8')

            gdk.threads_enter()
            end_iter = self.textbuffer.get_end_iter
            # word
            word =  unicode(d.word, server.encoding, 'replace').encode('utf-8')
            self.textbuffer.insert_with_tags(end_iter(), word, self.header_1_tag)

            # other header
            header = ' from %s <%s>\n' % (dbname, server.host)
            self.textbuffer.insert_with_tags(end_iter(), header,
                                             self.header_2_tag)

            # definition
            # не подсвечивать гиперссылки
            #self.textbuffer.insert(end_iter(), '%s\n\n' % s.rstrip())

            # подсветка гиперссылок
            wl = (s.rstrip()+'\n\n').split('{')
            self.textbuffer.insert(end_iter(), wl[0])
            for l in wl[1:]:
                l1 = l.split('}', 1)
                # link
                self.links_list.append((end_iter().get_offset(), l1[0]))
                self.textbuffer.insert_with_tags(end_iter(), l1[0],
                                                 self.link_tag)
                # other
                if len(l1) > 1:
                    self.textbuffer.insert(end_iter(), l1[1])

            gdk.threads_leave()

        self.restore_cursor()

    def restore_cursor(self):
        self.n_connects -= 1
        if self.n_connects == 0: # restore cursor
            gdk.threads_enter()
            w = self.textview.get_window(gtk.TEXT_WINDOW_TEXT)
            w.set_cursor(self.xterm_cursor)
            gdk.threads_leave()


    def link_tag_event_cb(self, texttag, widget, event, iter):
        #print texttag, widget, event, iter
        if event.type == gdk.BUTTON_RELEASE:
            #print 'ok'
            offset = iter.get_offset()
            if offset > self.links_list[-1][0]:
                self.do_search_word(self.links_list[-1][1])
                return
            prev_link = (0, '')
            for link in self.links_list:
                #print offset, prev_link, link
                if prev_link[0] <= offset <= link[0]:
                    #print '>>', prev_link[1]
                    self.do_search_word(prev_link[1])
                    break
                prev_link = link

##----------------------------------------------------------------------

    def create_widgets(self):

        window_name='dict_client_window'
        wTree = glade.XML(self.glade_file, window_name)
        self.window = wTree.get_widget('dict_client_window')
        self.window.resize(self.properties['width'],
                           self.properties['height'])


        dic = {
            'dict_client_window_destroy_cb' : self.dict_client_window_destroy_cb,
            'dict_client_window_unrealize_cb': self.dict_client_window_unrealize_cb,
            'dict_client_quit_cb'           : (lambda w: self.window.destroy()),
            'dict_client_save_as_cb'        : self.dict_client_save_as_cb,
            'dict_client_servers_cb'        : self.dict_client_servers_cb,
            'find_clicked_cb'               : (lambda w: self.search_word()),
            'clipboard_clicked_cb'          : self.clipboard_clicked_cb,
            'word_entry_activate_cb'        : (lambda w: self.search_word()),
            'word_entry_key_press_cb'       : self.word_entry_key_press_cb,
            'dict_client_clear_clicked_cb'  : self.dict_client_clear_clicked_cb,
            'dict_client_close_clicked_cb'  : (lambda w: self.window.destroy()),
            'properties_activate_cb'        : self.properties_activate_cb,
            'selection_received_cb'         : self.selection_received_cb,
            }
        wTree.signal_autoconnect(dic)

        self.servers_treeview = wTree.get_widget('servers_treeview')

        self.word_entry = wTree.get_widget('word_entry')
        self.servers_menu = wTree.get_widget('servers_combo') #optionmenu')
##         self.list_store = gtk.ListStore(gobject.TYPE_STRING)
##         self.servers_menu.set_model(self.list_store)
        self.servers_menu.connect('changed', self.servers_changed_cb)



        self.textview = wTree.get_widget('definitions_textview')
        self.textview.connect('motion-notify-event', self.motion_notify_event_cb)

        self.textbuffer = self.textview.get_buffer()

        self.header_1_tag = self.textbuffer.create_tag('header_1')
        self.header_2_tag = self.textbuffer.create_tag('header_2')


        self.title_tag = self.textbuffer.create_tag('title_tag',
                                          weight=pango.WEIGHT_BOLD,
                                          justification=gtk.JUSTIFY_CENTER,
                                          wrap_mode=gtk.WRAP_WORD)
##                                           background='#000000',
##                                           foreground='#ffffff',

        self.apply_properties()

        self.update_servers_menu()

        self.servers_window = None

        self.window.show()

        self.link_tag = self.textbuffer.create_tag('link')
        self.link_tag.connect('event', self.link_tag_event_cb)
        self.link_tag.set_property('foreground', '#0000ff')
        self.link_tag.set_property('underline', pango.UNDERLINE_SINGLE)

        return

##----------------------------------------------------------------------

    def motion_notify_event_cb(self, widget, event):
        x, y, z = widget.window.get_pointer()
        x, y = widget.window_to_buffer_coords(gtk.TEXT_WINDOW_TEXT, x, y)
        tags = widget.get_iter_at_location(x, y).get_tags()
        if self.link_tag in tags:
            if self.current_cursor is self.xterm_cursor:
                self.current_cursor = self.hand_cursor
                window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
                window.set_cursor(self.current_cursor)
        else:
            if self.current_cursor is self.hand_cursor:
                self.current_cursor = self.xterm_cursor
                window = widget.get_window(gtk.TEXT_WINDOW_TEXT)
                window.set_cursor(self.current_cursor)
        return False

##----------------------------------------------------------------------

    def update_servers_menu(self):
        self.list_store = gtk.ListStore(gobject.TYPE_STRING)
        self.servers_menu.set_model(self.list_store)
        #self.list_store.clear()
        self.list_store.append(['All'])

        for conn in self.connects:
            self.list_store.append([conn])

        cell = gtk.CellRendererText()
        self.servers_menu.pack_start(cell, True)
        self.servers_menu.add_attribute(cell, 'text',0)

        if self.current_server in ['', 'All']:
            self.servers_menu.set_active(0)
        else:
            n=list(self.connects).index(self.current_server)+1
            self.servers_menu.set_active(n)

        return


    def servers_changed_cb(self, w):
        iter = w.get_active_iter()
        server = w.get_model().get_value(iter, 0)
        if server == 'All':
            self.current_server = ''
        else:
            self.current_server = server


##----------------------------------------------------------------------

    def read_db_file(self):
        if not os.path.exists(self.db_file): return

        db = shelve.open(self.db_file)
        self.connects = db['connects']
        for conn in self.connects:
            c = self.connects[conn]
            c.lock = Lock()
            # restore Connection
            c.connection = Connection(c.host, c.port)
            if c.available_databases:
                c.connection.dbdescs = c.available_databases
            if c.available_strategies:
                c.connection.stratdescs = c.available_strategies

        self.properties = db['properties']
        self.current_server = db['current_server']
        db.close()

        try:
            hf = open(self.history_file)
        except:
            pass
        else:
            for i in hf.readlines():
                self.history.append(i.rstrip())


    def save_db_file(self):

        db = shelve.open(self.db_file)
        for conn in self.connects:
            #self.connects[conn].connection = None # fileobj can't be saved
            del self.connects[conn].connection
            del self.connects[conn].lock
        db['connects'] = self.connects
        db['properties'] = self.properties
        db['current_server'] = self.current_server
        db.close()

        try:
            open(self.history_file, 'w').write('\n'.join(self.history[:dictclient_history_length]))
        except:
            pass

##         cf = ConfigParser()
##         cf.add_section('main')
##         for prop in self.properties:
##             cf.set('main', prop, self.properties[prop])
##         cf.set('main', 'current_server', self.current_server)
##         n = 0
##         for conn in self.connects.values():
##             section = 'server'+str(n)
##             cf.add_section(section)
##             cf.set(section, 'host', conn.host)
##             cf.set(section, 'port', conn.port)
##             cf.set(section, 'no_use', conn.no_use)
##             cf.set(section, 'encoding', conn.encoding)
##             cf.set(section, 'databases', conn.databases)
##             cf.set(section, 'available_databases', conn.available_databases)
##             cf.set(section, 'strategy', conn.strategy)
##             cf.set(section, 'available_strategies', conn.available_strategies)
##             n += 1
##         dictclient_conf_file =  os.path.expanduser('~/.pybr/dictclient.conf')
##         fd = open(dictclient_conf_file, 'w')
##         cf.write(fd)

##----------------------------------------------------------------------

##----------------------------------------------------------------------

    def properties_activate_cb(self, w):

        wTree = glade.XML(self.glade_file, 'properties_dialog')
        # move dialog to centre of parrent window
        dialog = wTree.get_widget('properties_dialog')
        dialog.set_transient_for(self.window)
        x, y = self.window.get_position()
        w, h = self.window.get_size()
        dw, dh = dialog.get_size()
        dx = x + (w-dw)/2
        dy = y + (h-dh)/2
        dialog.move(dx, dy)
        dialog.show()

        dic = {
            'properties_close_clicked_cb':
            (lambda w: dialog.destroy()),
            'title_font_clicked_cb' :
            (lambda w: self.change_font(dialog, 'title_font')),
            'word_font_clicked_cb' :
            (lambda w: self.change_font(dialog, 'word_font')),
            'header_font_clicked_cb' :
            (lambda w: self.change_font(dialog, 'header_font')),
            'body_font_clicked_cb' :
            (lambda w: self.change_font(dialog, 'body_font')),
            'title_color_clicked_cb' :
            (lambda w: self.change_color(dialog, 'title_color')),
            'word_color_clicked_cb' :
            (lambda w: self.change_color(dialog, 'word_color')),
            'header_color_clicked_cb' :
            (lambda w: self.change_color(dialog, 'header_color')),
            'body_color_clicked_cb' :
            (lambda w: self.change_color(dialog, 'body_color')),
            'background_color_clicked_cb' :
            (lambda w: self.change_color(dialog, 'background_color')),
            }

        wTree.signal_autoconnect(dic)


    def change_font(self, parrent, prop):

        dialog = gtk.FontSelectionDialog('Select font')
        dialog.set_transient_for(parrent)
        dialog.show()

        response = dialog.run()
        if response  == gtk.RESPONSE_OK:
            font = dialog.get_font_name()
            props = {prop: font}
            self.apply_properties(props)

        dialog.destroy()


    def change_color(self, parrent, prop):

        dialog = gtk.ColorSelectionDialog('Select color')

        dialog.colorsel.set_has_palette(True)
        dialog.set_transient_for(parrent)
        dialog.show()

        response = dialog.run()

        if response == gtk.RESPONSE_OK:

            color = dialog.colorsel.get_current_color()
            color_val = '#%02x%02x%02x' \
                        % (color.red>>8, color.green>>8, color.blue>>8)
            props = {prop: color_val}
            self.apply_properties(props)

        dialog.destroy()


    def apply_properties(self, props=None):
        if props is None: props = self.properties
        if props.has_key('body_font'):
            fd = pango.FontDescription(props['body_font'])
            self.textview.modify_font(fd)
        if props.has_key('body_color'):
            color = gdk.color_parse(props['body_color'])
            self.textview.modify_text(gtk.STATE_NORMAL, color)
        if props.has_key('background_color'):
            color = gdk.color_parse(props['background_color'])
            self.textview.modify_base(gtk.STATE_NORMAL, color)
        if props.has_key('title_color'):
            self.title_tag.set_property('foreground', props['title_color'])
        if props.has_key('title_font'):
            self.title_tag.set_property('font', props['title_font'])
        if props.has_key('word_color'):
            self.header_1_tag.set_property('foreground', props['word_color'])
        if props.has_key('word_font'):
            self.header_1_tag.set_property('font', props['word_font'])
        if props.has_key('header_color'):
            self.header_2_tag.set_property('foreground', props['header_color'])
        if props.has_key('header_font'):
            self.header_2_tag.set_property('font', props['header_font'])

        if not props is self.properties:
            self.properties.update(props)

        #self.link_tag.set_property('foreground', '#0000ff')
        #self.link_tag.set_property('underline', pango.UNDERLINE_SINGLE)


##----------------------------------------------------------------------

    def dict_client_save_as_cb(self, w):
        dialog = gtk.FileSelection()
        dialog.set_transient_for(self.window)
        dialog.show()
        while 1:
            response = dialog.run()
            if response == gtk.RESPONSE_OK:
                filename = dialog.get_filename()
                if os.path.isdir(filename):
                    model, iter = dialog.dir_list.get_selection().get_selected()
                    if iter:
                        dirname = os.path.normpath(filename
                                                   + model.get_value(iter, 0)) \
                                                   + os.sep
                    else:
                        dirname = os.path.normpath(filename) + os.sep
                    dialog.set_filename(dirname)
                    continue
                if os.path.exists(filename):
                    md = gtk.MessageDialog(dialog, gtk.DIALOG_MODAL \
                                           | gtk.DIALOG_DESTROY_WITH_PARENT,
                                           gtk.MESSAGE_WARNING,
                                           gtk.BUTTONS_OK_CANCEL,
                                           'File %s exists. Overwrite?' \
                                           % filename)

                    response = md.run()
                    md.destroy()
                    if response != gtk.RESPONSE_OK:
                        continue

                start, end = self.textbuffer.get_bounds()
                text = self.textbuffer.get_text(start, end, True)
                try:
                    open(filename, 'w').write(text)
                except IOError, err:
                    error_dialog(dialog, 'Can\'t write file %s: %s' \
                                 % (filename, err[1]))

                break

            else:
                break

        dialog.destroy()


    def dict_client_clear_clicked_cb(self, w):
        start, end = self.textbuffer.get_bounds()
        self.textbuffer.delete(start, end)
        self.links_list = []


    def dict_client_servers_cb(self, w):
        #print 'dict_client_servers_cb', self.servers_window
        if self.servers_window:
            self.servers_window.present()
            return

        wTree = glade.XML(self.glade_file, 'dict_client_servers_window')

        self.servers_window = wTree.get_widget('dict_client_servers_window')
        self.servers_window.resize(self.properties['servers_width'],
                                   self.properties['servers_height'])

        dic = {
            'servers_window_destroy_cb': self.servers_window_destroy_cb,
            'servers_window_unrealize_cb': self.servers_window_unrealize_cb,
            'close_clicked_cb': (lambda w: self.servers_window.destroy()),
            'update_databases_clicked_cb': self.update_databases_clicked_cb,
            'update_strategies_clicked_cb': self.update_strategies_clicked_cb,
            'add_server_clicked_cb': self.add_server_clicked_cb,
            'remove_server_clicked_cb': self.remove_server_clicked_cb,
            'no_use_server_toggled_cb': self.no_use_server_toggled_cb,
            }
        wTree.signal_autoconnect(dic)

        self.no_use_checkbutton = wTree.get_widget('no_use_checkbutton')

        # servers treeview
        treeview = wTree.get_widget('servers_treeview')
        model = gtk.ListStore(gobject.TYPE_STRING)

        for conn in self.connects:
            iter = model.append()
            model.set(iter, 0, conn)

        column = gtk.TreeViewColumn('Servers', gtk.CellRendererText(), text=0)
        treeview.append_column(column)

        treeview.set_model(model)

        selection = treeview.get_selection()
        selection.connect('changed', self.servers_treeview_selected_cb)

        self.servers_treeview = treeview

        # databases treeview
        treeview = wTree.get_widget('databases_treeview')
        model = gtk.ListStore(gobject.TYPE_BOOLEAN, # check
                              gobject.TYPE_STRING,  # name
                              gobject.TYPE_STRING,  # desc
                              )
        treeview.set_model(model)

        renderer = gtk.CellRendererToggle()
        renderer.connect('toggled', self.db_check_toggled, model)
        column = gtk.TreeViewColumn('', renderer, active=self.COLUMN_DB_CHECK)
        treeview.append_column(column)

        column = gtk.TreeViewColumn('Name', gtk.CellRendererText(),
                                    text=self.COLUMN_DB_NAME)
        column.set_resizable(True)
        column.set_sort_column_id(self.COLUMN_DB_NAME)
        treeview.append_column(column)

        column = gtk.TreeViewColumn('Description', gtk.CellRendererText(),
                                    text=self.COLUMN_DB_DESC)
        column.set_resizable(True)
        column.set_sort_column_id(self.COLUMN_DB_DESC)
        treeview.append_column(column)

        self.databases_treeview = treeview

        # strategies treeview
        treeview = wTree.get_widget('strategies_treeview')
        model = gtk.ListStore(gobject.TYPE_BOOLEAN, # check
                              gobject.TYPE_STRING,  # name
                              gobject.TYPE_STRING,  # desc
                              )
        treeview.set_model(model)

        renderer = gtk.CellRendererToggle()
        renderer.connect('toggled', self.strat_check_toggled, model)
        column = gtk.TreeViewColumn('', renderer, active=self.COLUMN_STRAT_CHECK)
        treeview.append_column(column)

        column = gtk.TreeViewColumn('Name', gtk.CellRendererText(),
                                    text=self.COLUMN_STRAT_NAME)
        column.set_resizable(True)
        column.set_sort_column_id(self.COLUMN_STRAT_NAME)
        treeview.append_column(column)

        column = gtk.TreeViewColumn('Description', gtk.CellRendererText(),
                                    text=self.COLUMN_STRAT_DESC)
        column.set_resizable(True)
        column.set_sort_column_id(self.COLUMN_STRAT_DESC)
        treeview.append_column(column)

        self.strategies_treeview = treeview

        # encoding menu
        encoding_menu = wTree.get_widget('encoding_optionmenu')
        encoding_submenu = gtk.Menu()
        for i in charsets_list:
            item = gtk.MenuItem(i)
            encoding_submenu.append(item)
            item.connect_object('activate', self.encodind_submenu_activate_cb, i)
            item.show()
        encoding_menu.set_menu(encoding_submenu)
        self.encoding_menu = encoding_menu
        self.encoding_submenu = encoding_submenu

        self.servers_window.show()

##----------------------------------------------------------------------

    def servers_window_destroy_cb(self, w):
        self.servers_window = None

    def servers_window_unrealize_cb(self, w):
        width, height = self.servers_window.get_size()
        self.properties['servers_width'] = width
        self.properties['servers_height'] = height

##----------------------------------------------------------------------

    def no_use_server_toggled_cb(self, w):
        selected_server = self.get_selected_server()
        if not selected_server: return
        selected_server.no_use = self.no_use_checkbutton.get_active()

##----------------------------------------------------------------------

    def add_server_clicked_cb(self, w):
        wTree = glade.XML(self.glade_file, 'new_server_dialog')
        dialog = wTree.get_widget('new_server_dialog')
        dialog.set_transient_for(self.servers_window)
        x, y = self.servers_window.get_position()
        w, h = self.servers_window.get_size()
        dw, dh = dialog.get_size()
        dx = x + (w-dw)/2
        dy = y + (h-dh)/2
        dialog.move(dx, dy)
        dialog.show()

        while 1:

            response = dialog.run()

            if response == gtk.RESPONSE_OK:
                name = wTree.get_widget('new_server_name_entry').get_text()
                host = wTree.get_widget('new_server_host_entry').get_text()
                port = wTree.get_widget('new_server_port_entry').get_text()
                if self.connects.has_key(name):
                    error_dialog(dialog, 'Name must be unique')
                    continue
                if not name.strip():
                    error_dialog(dialog, 'You must entry name')
                    continue
                try:
                    port = int(port)
                except:
                    error_dialog(dialog, 'Invalid port')
                    continue

                conn = DictConnect(host, port)
                self.connects[name] = conn
                self.update_servers_treeview()
                self.update_servers_menu()

                break

            if response in (gtk.RESPONSE_CANCEL, gtk.RESPONSE_DELETE_EVENT):
                break

        dialog.destroy()


    def remove_server_clicked_cb(self, w):
        model, iter = self.servers_treeview.get_selection().get_selected()
        if not iter: return
        selected_server = model.get_value(iter, 0)

        dialog = gtk.MessageDialog(self.servers_window,
                                   gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION,
                                   gtk.BUTTONS_OK_CANCEL,
                                   'Realy remove server "%s"?' % selected_server)
        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            del self.connects[selected_server]
            self.update_servers_treeview()

        dialog.destroy()

##----------------------------------------------------------------------

    def update_servers_treeview(self):
        treeview=self.servers_treeview
        model = treeview.get_model()
        model.clear()
        for conn in self.connects:
            iter = model.append()
            model.set(iter, 0, conn)


    def servers_treeview_selected_cb(self, w):
        selected_server = self.get_selected_server()
        if not selected_server: return

        self.no_use_checkbutton.set_active(selected_server.no_use)

        # update encoding menu
        n = 0
        for enc in charsets_list:
            if selected_server.encoding == enc:
                break
            n = n+1
        self.encoding_menu.remove_menu()
        self.encoding_submenu.set_active(n)
        self.encoding_menu.set_menu(self.encoding_submenu)

        # databases treeview
        self.update_databases_treeview(selected_server)
        # strategies treeview
        self.update_strategies_treeview(selected_server)


    def encodind_submenu_activate_cb(self, enc):
        server = self.get_selected_server()
        if not server:
            return
        server.encoding = enc

##----------------------------------------------------------------------

    def dict_client_window_unrealize_cb(self, w):
        width, height = self.window.get_size()
        self.properties['width'] = width
        self.properties['height'] = height


    def dict_client_window_destroy_cb(self, w):
        try:
            self.words_queue.put(None)
            self.save_db_file()
        finally:
            pass
            #gtk.main_quit()

##----------------------------------------------------------------------

    def update_databases_clicked_cb(self, w):
        selected_server = self.get_selected_server()
        if not selected_server: return
        try:
            selected_server.get_databases()
        except Exception, err:
            print err
            return
        self.update_databases_treeview(selected_server)


    def update_databases_treeview(self, server):

        model = self.databases_treeview.get_model()
        model.clear()

        if server.databases:
            for db in server.available_databases:

                if db in server.databases:
                    check = True
                else:
                    check = False
                iter = model.append()
                model.set(iter,
                          self.COLUMN_DB_CHECK, check,
                          self.COLUMN_DB_NAME, db,
                          self.COLUMN_DB_DESC, server.available_databases[db])


    def db_check_toggled(self, cell, path, model):
        iter = model.get_iter((int(path),))
        check = not model.get_value(iter, self.COLUMN_DB_CHECK)
        model.set(iter, self.COLUMN_DB_CHECK, check)
        db =  model.get_value(iter, self.COLUMN_DB_NAME)
        dict_conn = self.get_selected_server()
        if check:
            dict_conn.databases.append(db)
        else:
            dict_conn.databases.remove(db)

##----------------------------------------------------------------------

    def update_strategies_clicked_cb(self, w):

        selected_server = self.get_selected_server()
        if not selected_server: return
        try:
            selected_server.get_strategies()
        except Exception, err:
            print err
            return
        self.update_strategies_treeview(selected_server)


    def update_strategies_treeview(self, server):

        model = self.strategies_treeview.get_model()
        model.clear()

        # without strategy
        if server.strategy == '':
            check = True
        else:
            check = False
        iter = model.append()
        model.set(iter,
                  self.COLUMN_DB_CHECK, check,
                  self.COLUMN_DB_NAME, '',
                  self.COLUMN_DB_DESC, 'Without strategy')

        if server.available_strategies:
            for strat in server.available_strategies:

                if strat == server.strategy:
                    check = True
                else:
                    check = False
                iter = model.append()
                model.set(iter,
                          self.COLUMN_STRAT_CHECK, check,
                          self.COLUMN_STRAT_NAME, strat,
                          self.COLUMN_STRAT_DESC,
                          server.available_strategies[strat])


    def strat_check_toggled(self, cell, path, model):

        def uncheck_strategy(model, path, iter):
            if model.get_value(iter, self.COLUMN_STRAT_CHECK) == True:
                model.set(iter, self.COLUMN_STRAT_CHECK, False)
                return True # break
            return False # continue

        model.foreach(uncheck_strategy)

        iter = model.get_iter((int(path),))
        model.set(iter, self.COLUMN_STRAT_CHECK, True)
        strat =  model.get_value(iter, self.COLUMN_STRAT_NAME)

        selected_server = self.get_selected_server()
        selected_server.strategy = strat

##----------------------------------------------------------------------

    def get_selected_server(self):
        model, iter = self.servers_treeview.get_selection().get_selected()
        if not iter: return None
        selected_server = model.get_value(iter, 0)
        return self.connects[selected_server]


    def clipboard_clicked_cb(self, w):
        self.window.selection_convert('PRIMARY', 'UTF8_STRING', long(0))


    def selection_received_cb(self, widget, data, val):
        if data.data:
            self.do_search_word(data.data)


    def search_word(self):
        word = self.word_entry.get_text().strip()
        self.word_entry.set_text('')
        self.do_search_word(word)


    def do_search_word(self, word):
        word = re.sub('[\t\n ]+', ' ', word)
        if word:
            self.history.insert(0, word)
            self.history_index = -1
            self.words_queue.put(word)


    def word_entry_key_press_cb(self, w, event):

        if event.keyval in self.keysyms_up:
            if self.history and self.history_index < len(self.history)-1:
                self.history_index += 1
                self.word_entry.set_text(self.history[self.history_index])
            return True

        elif event.keyval in self.keysyms_down:
            if self.history and self.history_index >= 0:
                self.history_index -= 1
            if self.history and self.history_index >= 0:
                self.word_entry.set_text(self.history[self.history_index])
            else:
                self.word_entry.set_text('')
            return True

        return False


##----------------------------------------------------------------------

class DictConnect:

    # по RFC эти стратегии должны быть обязательно на любом dict-сервере
    default_strat_list = {
        'exact'  : 'Match words exactly',
        'prefix' : 'Match prefixes',
        }


    def __init__(self, host, port):
        self.host = host
        self.port = port
        self.no_use = 0
        self.encoding = 'utf-8'
        self.databases = None
        self.available_databases = None
        self.strategy = '' # Without strategy
        self.available_strategies = self.default_strat_list
        self.connection = Connection(self.host, self.port)
        self.connection.stratdescs = self.available_strategies
        self.lock = Lock()


    def get_definitions(self, word):
        self.lock.acquire()
        defs = []
        try:
            if self.databases: databases = self.databases
            else: databases = ['*']
            if self.strategy:
                matches = []
                for db in databases:
                    #print 'match', db, self.strategy, word
                    matches.extend(self.connection.match(db,
                                                         self.strategy,
                                                         word))
                for m in matches:
                    m.getdefstr()
                    #print 'getdefstr', m.db.name, m.word
                    defs.append(m)

            else: # without strategy
                #print 'databases:', databases
                for db in databases:
                    #print 'define', db, word, self.encoding
                    #for d in self.connection.define(db, word):
                    defs.extend(self.connection.define(db, word))

            self.connection.closeconnect()
        finally:
            self.lock.release()
        return defs

    def get_strategies(self):
        self.lock.acquire()
        try:
            if hasattr(self.connection, 'stratdescs'):
                del self.connection.stratdescs # remove cache
            self.available_strategies = self.connection.getstratdescs()
            self.connection.closeconnect()
        finally:
            self.lock.release()
        return self.available_strategies

    def get_databases(self):
        self.lock.acquire()
        try:
            if hasattr(self.connection, 'dbdescs'):
                del self.connection.dbdescs # remove cache
            self.available_databases = self.connection.getdbdescs()
            if not self.databases:
                self.databases = list(self.available_databases)
            else:
                for db in self.databases[:]:
                    if not self.available_databases.has_key(db):
                        self.databases.remove(db)
            self.connection.closeconnect()
        finally:
            self.lock.release()
        return self.available_databases


if __name__ == '__main__':
    dictclient_rc_file = os.path.expanduser('~/.pydictrc')
    # search glade file
    gf = 'pygtkdict.glade'
    dictclient_glade_file = None
    for d in sys.path:
        f = os.path.join(d, gf)
        if os.path.exists(f):
            dictclient_glade_file = f
            break

    if dictclient_glade_file:
        dict_client = PyGtkDict(dictclient_glade_file, dictclient_rc_file)
        dict_client.window.connect('destroy', lambda w: gtk.main_quit())
        gdk.threads_init()
        gtk.main()

    else:
        sys.exit('ERROR: glade file not found')
