#!/usr/pkg/bin/python2.7
# -*- coding: utf-8 -*-

# Copyright (C) 2009 The Tegaki project contributors
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

# Contributors to this file:
# - Mathieu Blondel
# - Christoph Burgmer

import os
from optparse import OptionParser

import glib
import gtk
from gtk import gdk

from tegaki.character import Character
from tegaki.charcol import CharacterCollection
from tegaki.trainer import Trainer

from tegakigtk.iconview import WritingIconView, _WritingPixbufRenderer
from tegakigtk.canvas import Canvas

VERSION = '0.3.1'

PREDEFINED_SETS = {
"Digits" : [[48,57]],
"Letters" : [[97,122]],
"Hiragana" : [[0x3041, 0x3096]],
"Katakana" : [[0x30A1, 0x30F7]]
}

class AddCharacterSetDialog(gtk.Dialog):

    def __init__(self, parent):
        gtk.Dialog.__init__(self)
        self._init_dialog(parent)
        self._create_ui()

    def _init_dialog(self, parent):
        self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
        self.set_default_response(gtk.RESPONSE_OK)
        self.set_has_separator(True)
        self.set_transient_for(parent)
        self.set_border_width(6)
        self.set_modal(True)
        self.set_title("Add characters")

    def _create_ui(self):
        # Add from char
        self._char_vbox = gtk.VBox(spacing=5)
        self._char_radio = gtk.RadioButton(group=None, 
                                           label="Characters")
        self._char_entry = gtk.Entry()
        label = gtk.Label()
        label.set_alignment(0.0, 0.0)
        ex = "<i>Examples:\na\na, b, c\n0x65..0x70, 0x72</i>"
        label.set_markup(ex)
        self._char_vbox.pack_start(self._char_entry)
        self._char_vbox.pack_start(label)

        # Add from predefined set
        self._set_vbox = gtk.VBox(spacing=5)
        self._set_radio = gtk.RadioButton(group=self._char_radio, 
                                          label="Predefined set")
        self._create_set_combo()
        self._set_vbox.pack_start(self._set_combo)
        self._set_vbox.set_sensitive(False)

        # Add from file
        self._file_vbox = gtk.VBox(spacing=5)
        self._file_radio = gtk.RadioButton(group=self._char_radio, 
                                           label="From UTF8 file")
        self._file_button = gtk.FileChooserButton("Choose file")
        exp = "<i>All the characters found in this file will be added.</i>"
        label = gtk.Label()
        label.set_alignment(0.0, 0.0)
        label.set_markup(exp)

        self._file_vbox.pack_start(self._file_button)
        self._file_vbox.pack_start(label)
        self._file_vbox.set_sensitive(False)

        # Main vbox
        main_vbox = self.get_child()
        main_vbox.set_spacing(10)
        main_vbox.pack_start(self._char_radio)
        main_vbox.pack_start(self._char_vbox)
        main_vbox.pack_start(self._set_radio)
        main_vbox.pack_start(self._set_vbox)
        main_vbox.pack_start(self._file_radio)
        main_vbox.pack_start(self._file_vbox)

        self._char_radio.connect("toggled", self._on_toggle_option)
        self._set_radio.connect("toggled", self._on_toggle_option)
        self._file_radio.connect("toggled", self._on_toggle_option)

        self.show_all()

    def _on_toggle_option(self, widget):
        for radio, vbox in [[self._char_radio, self._char_vbox],
                            [self._set_radio, self._set_vbox],
                            [self._file_radio, self._file_vbox]]:
            vbox.set_sensitive(radio.get_active())

    def _create_set_combo(self):
        self._set_combo = gtk.ComboBox(gtk.ListStore(str))
        cell = gtk.CellRendererText()
        self._set_combo.pack_start(cell, True)
        self._set_combo.add_attribute(cell, 'text', 0)
        keys = PREDEFINED_SETS.keys()
        keys.sort()
        for k in keys:
            self._set_combo.append_text(k)
        self._set_combo.set_active(0)

    def get_charsets(self):
        if self._char_radio.get_active():
            return self._get_charsets_from_list()
        elif self._set_radio.get_active():
            return self._get_charsets_from_predefined_set()
        elif self._file_radio.get_active():
            return self._get_charsets_from_file()

    def _get_char_from_str(self, s):
        try:
            return self._get_utf8_from_int(self._get_int_from_str(s))
        except ValueError:
            return s

    def _get_int_from_str(self, s):
        if s.startswith("0x"):
            return int(s, 16)
        else:
            return int(s)

    def _get_utf8_from_int(self, i):
        return unichr(i).encode("utf-8")

    def _get_charsets_from_list(self):
        elements = self._char_entry.get_text().strip().split(",")
        chars = []
        for element in elements:
            element = element.strip()
            if ".." in element: # this is a range
                val = [s.strip() for s in element.split("..")]
                inf = self._get_int_from_str(val[0])
                sup = self._get_int_from_str(val[1])
                if sup < inf:
                    continue
                chars += [self._get_utf8_from_int(i) for i in range(inf, sup+1)]
            else: # this is an individual character
                chars.append(self._get_char_from_str(element))
        return chars

    def _get_charsets_from_predefined_set(self):
        keys = PREDEFINED_SETS.keys()
        keys.sort()
        k = keys[self._set_combo.get_active()]
        characters = []
        for val in PREDEFINED_SETS[k]:
            if isinstance(val, int):
                characters.append(self._get_utf8_from_int(val))
            elif isinstance(val, list):
                for code in range(val[0], val[1]+1):
                    characters.append(self._get_utf8_from_int(code))           
   
        return characters

    def _get_charsets_from_file(self):
        filename = self._file_button.get_filename()
        if not filename:
            return []
        f = open(filename)
        buf = f.read()
        f.close()
        try:
            unicodestr = unicode(buf, "utf-8")
        except UnicodeDecodeError:
            return []
        unicodestr = unicodestr.replace("\t", "")
        unicodestr = unicodestr.replace("\n", "")
        unicodestr = unicodestr.replace("\r", "")
        unicodestr = unicodestr.replace(" ", "")
        d = {}
        for char in unicodestr:
            d[char] = 1
        return d.keys()

class TrainDialog(gtk.Dialog):

    def __init__(self, parent):
        gtk.Dialog.__init__(self)
        self._init_dialog(parent)
        self._create_ui()

    def _init_dialog(self, parent):
        self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
        self.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK)
        self.set_response_sensitive(gtk.RESPONSE_OK, False)
        self.set_default_response(gtk.RESPONSE_OK)
        self.set_has_separator(True)
        self.set_transient_for(parent)
        self.set_border_width(6)
        self.set_modal(True)
        self.set_title("Train")

    def _create_ui(self):
        self._table = gtk.Table(rows=4, columns=2)
        self._table.set_row_spacings(8)
        self._table.set_col_spacings(5)

        self._table.attach(gtk.Label("Engine"), 0, 1, 0, 1)
        self._create_engine_combo()
        self._table.attach(self._engine_combo, 1, 2, 0, 1)

        self._table.attach(gtk.Label("Name"), 0, 1, 1, 2)
        self._name_entry = gtk.Entry()
        self._name_entry.connect("changed", self._on_entry_changed)
        self._table.attach(self._name_entry, 1, 2, 1, 2)

        self._table.attach(gtk.Label("Short name"), 0, 1, 2, 3)
        self._shortname_entry = gtk.Entry()
        self._shortname_entry.connect("changed", self._on_entry_changed)
        self._table.attach(self._shortname_entry, 1, 2, 2, 3)

        self._table.attach(gtk.Label("Language"), 0, 1, 3, 4)
        self._create_lang_comboentry()
        self._table.attach(self._lang_comboentry, 1, 2, 3, 4)

        # Main vbox
        main_vbox = self.get_child()
        main_vbox.set_spacing(10)
        main_vbox.pack_start(self._table)
        #msg = "<i>The model is going to be saved in\n%s.</i>" % \
            #os.path.join(os.environ["HOME"], ".tegaki", "models")
        #label = gtk.Label()
        #label.set_markup(msg)
        #main_vbox.pack_start(label)
        self.show_all()

    def _on_entry_changed(self, widget):
        if len(Trainer.get_available_trainers()) == 0:
            self.set_response_sensitive(gtk.RESPONSE_OK, False)
            return

        for entry in [self._name_entry, self._shortname_entry]:
            if entry.get_text().strip() == "":
                self.set_response_sensitive(gtk.RESPONSE_OK, False)
                return

        self.set_response_sensitive(gtk.RESPONSE_OK, True)

    def _create_engine_combo(self):
        self._engine_combo = gtk.ComboBox(gtk.ListStore(str))
        cell = gtk.CellRendererText()
        self._engine_combo.pack_start(cell, True)
        self._engine_combo.add_attribute(cell, 'text', 0)
        for engine in Trainer.get_available_trainers():
            self._engine_combo.append_text(engine)
        self._engine_combo.set_active(0)

    def _create_lang_comboentry(self):
        self._lang_comboentry = gtk.ComboBoxEntry(gtk.ListStore(str))
        for lang in ["ja", "zh_CN", "zh_TW", "None"]:
            self._lang_comboentry.append_text(lang)
        self._lang_comboentry.set_active(0)

    def get_name(self):
        return self._name_entry.get_text().strip()

    def get_shortname(self):
        return self._shortname_entry.get_text().strip()

    def get_lang(self):
        lang = self._lang_comboentry.child.get_text().strip()
        if lang == "None":
            lang = None
        return lang

    def get_meta(self):
        return { "name" : self.get_name(),
                 "shortname" : self.get_shortname(),
                 "language" : self.get_lang() }

    def get_engine(self):
        i = self._engine_combo.get_active()
        keys = Trainer.get_available_trainers().keys()
        return keys[i]

class EditCharacterSampleWindow(gtk.Window):

    def __init__(self, parent):
        gtk.Window.__init__(self)
        self._init_window(parent)

    def _init_window(self, parent):
        vbox = gtk.VBox(spacing=2)

        hbox = gtk.HBox(spacing=3)
        label = gtk.Label()
        label.set_markup("<b>Character value:</b>")
        hbox.pack_start(label, expand=False)
        self._entry = gtk.Entry()
        hbox.pack_start(self._entry)
        vbox.pack_start(hbox)

        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        self._canvas = Canvas()
        frame.add(self._canvas)
        vbox.pack_start(frame)

        hbox = gtk.HBox()
        self._revert_button = gtk.Button(stock=gtk.STOCK_UNDO)
        self._revert_button.connect("clicked", self._on_revert)
        hbox.pack_start(self._revert_button)
        self._clear_button = gtk.Button(stock=gtk.STOCK_CLEAR)
        self._clear_button.connect("clicked", self._on_clear)
        hbox.pack_start(self._clear_button)
        self._replay_button = gtk.Button(stock=gtk.STOCK_MEDIA_PLAY)
        self._replay_button.connect("clicked", self._on_replay)
        hbox.pack_start(self._replay_button)  
        vbox.pack_start(hbox)      

        align = gtk.Alignment(xalign=0.5, yalign=0.0, xscale=0.0, yscale=0.0)
        vbox2 = gtk.VBox()
        self._center_button = gtk.CheckButton("Center")
        self._center_button.set_active(True)
        vbox2.pack_start(self._center_button)
        self._norm_button = gtk.CheckButton("Normalize")
        self._norm_button.set_active(False)
        vbox2.pack_start(self._norm_button)
        self._smooth_button = gtk.CheckButton("Smooth")
        self._smooth_button.set_active(False)
        vbox2.pack_start(self._smooth_button)
        align.add(vbox2)
        vbox.pack_start(align)
        
        self.add(vbox)
        self.set_default_size(400, 500)
        self.set_title("Edit character sample")
        self.set_transient_for(parent)
        self.set_position(gtk.WIN_POS_CENTER_ON_PARENT)
        self.set_modal(True)

    def _on_revert(self, widget):
        self._canvas.revert_stroke()

    def _on_clear(self, widget):
        self._canvas.clear()

    def _on_replay(self, widget):
        self._canvas.replay()

    def get_character_value(self):
        return self._entry.get_text().strip()

    def set_character_value(self, value):
        self._entry.set_text(value.strip())

    def get_writing(self):
        writing = self._canvas.get_writing()

        if self._center_button.get_active():
            writing .normalize_position()

        if self._norm_button.get_active():
            writing .normalize()

        if self._smooth_button.get_active():
            writing .smooth()

        return writing

    def set_writing(self, writing):
        self._canvas.set_writing(writing)

    def get_character(self):
        utf8 = self.get_character_value()
        writing = self.get_writing()
        character = Character()
        character.set_utf8(utf8)
        character.set_writing(writing)
        return character

    def set_character(self, character):
        self.set_character_value(character.get_utf8())
        self.set_writing(character.get_writing())

class CharacterSetListView(gtk.TreeView):

    def __init__(self, parent):
        self._model = gtk.ListStore(str)
        gtk.TreeView.__init__(self, self._model)
        self._init()
        self._parentw = parent
        self._charsets = {}

    def _init(self):
        column = gtk.TreeViewColumn("Character sets")
        cell = gtk.CellRendererText()
        column.pack_start(cell, expand=True)
        column.set_min_width(100)
        column.add_attribute(cell, 'text', 0)
        column.set_sort_column_id(0)
        self.set_search_column(0)
        self.set_headers_visible(False)
        self.set_reorderable(True)
        self.append_column(column)
        cell = gtk.CellRendererText()
        column = gtk.TreeViewColumn(None, cell)
        column.set_cell_data_func(cell, self._set_to_cell)
        self.append_column(column)

    def _set_to_cell(self, column, cell, model, iter):
        charset = model[iter][0]
        n_chars = self._parentw._charcol.get_n_characters(charset)
        cell.props.text = str(n_chars)

    def append_charset(self, charset):
        if not self.includes_charset(charset):
            self._model.append((charset,))
            self._charsets[charset] = 1

    def remove_selected_charset(self):
        seliter = self.get_selection().get_selected()[1]
        charset = self.get_model().get_value(seliter, 0)
        self.get_model().remove(seliter)
        del self._charsets[charset]
        return charset

    def includes_charset(self, charset):
        return charset in self._charsets

    def get_selected_charset(self):
        if self.get_selection().count_selected_rows() == 0:
            return None
        else:
            seliter = self.get_selection().get_selected()[1]
            return self._model.get_value(seliter, 0)

class CharacterFileChooserDialog(gtk.FileChooserDialog):

    def __init__(self, title, parent, action=gtk.FILE_CHOOSER_ACTION_OPEN):
        if action == gtk.FILE_CHOOSER_ACTION_OPEN:
            buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                       gtk.STOCK_OPEN, gtk.RESPONSE_OK)
        else:
            buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                       gtk.STOCK_SAVE, gtk.RESPONSE_OK)
        gtk.FileChooserDialog.__init__(self, title, parent, action, buttons)
        self._init_dialog()

    def _init_dialog(self):
        self.set_default_size(400, 400)
        
        for name, pat in [["XML", "*.xml"], 
                          ["Gzip-compressed XML", "*.xml.gz"],
                          ["Bz2-compressed XML", "*.xml.bz2"],
                          ["All files", "*.*"]]:
            filt = gtk.FileFilter()
            filt.set_name(name)
            filt.add_pattern(pat)
            self.add_filter(filt)

        self.set_default_response(gtk.RESPONSE_OK)

        self.set_preview_widget(gtk.Image())
        self.connect("selection-changed", self._selection_changed)

    def _selection_changed(self, widget):
        filename = self.get_preview_filename()

        if filename:
            char = self._get_character(filename)
            
            if char:
                image = self.get_preview_widget()
                writing = char.get_writing()
                writing.smooth()
                renderer = _WritingPixbufRenderer(writing, 200, 200)
                renderer.set_draw_annotations(False)
                renderer.draw_background()
                #renderer.draw_axis()
                renderer.draw_writing()
                pixbuf = renderer.get_pixbuf()
                image.set_from_pixbuf(pixbuf)
                self.set_preview_widget_active(True)
            else:
                self.set_preview_widget_active(False)

    def get_character(self):
        return self._get_character(self.get_filename())

    def _get_character(self, filename):
        char = Character()

        try:
            if filename.endswith(".gz"):
                char.read(filename, gzip=True)
            elif filename.endswith(".bz2"):
                char.read(filename, bz2=True)
            else:
                char.read(filename)
        except ValueError: # incorrect XML
            return None

        return char

    def set_character(self, char):
        filename = self.get_filename()
        if filename.endswith(".gz"):
            char.write(filename, gzip=True)
        elif filename.endswith(".bz2"):
            char.write(filename, bz2=True)
        else:
            char.write(filename)

class CharacterCollectionFileChooserDialog(gtk.FileChooserDialog):

    def __init__(self, title, parent, action=gtk.FILE_CHOOSER_ACTION_OPEN):
        if action == gtk.FILE_CHOOSER_ACTION_OPEN:
            buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                       gtk.STOCK_OPEN, gtk.RESPONSE_OK)
        else:
            buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                       gtk.STOCK_SAVE, gtk.RESPONSE_OK)
        gtk.FileChooserDialog.__init__(self, title, parent, action, buttons)
        self._init_dialog()

    def _init_dialog(self):
        self.set_default_size(400, 400)
        
        alltypes = ["*.charcol", "*.charcol.gz", "*.charcol.bz2", "*.chardb"]

        for name, pats in [["All supported files", alltypes],
                           ["Character collection (DB)", alltypes[3]],
                           ["Character collection (XML)", alltypes[0]], 
                           ["Gzip-compressed Character collection (XML)",
                            alltypes[1]],
                           ["Bz2-compressed Character collection (XML)",
                            alltypes[2]],
                           ["All files", "*.*"]]:
            filt = gtk.FileFilter()
            filt.set_name(name)
            if not isinstance(pats, list):
                pats = [pats]
            for pat in pats:
                filt.add_pattern(pat)
            self.add_filter(filt)

        self.set_default_response(gtk.RESPONSE_OK)

    def get_character_collection(self):
        return CharacterCollectionFileChooserDialog. \
            open_character_collection(self.get_filename ())

    @staticmethod
    def open_character_collection(filename):
        try:
            return CharacterCollection(filename)
        except ValueError:
            return None

    def set_character_collection(self, charcol):
        filename = self.get_filename()
        CharacterCollectionFileChooserDialog. \
            save_character_collection(charcol, filename)

    @staticmethod
    def save_character_collection(charcol, filename):
        charcol.save(filename)

class QuestionDialog(gtk.MessageDialog):

    def __init__(self, parent, msg):
        gtk.MessageDialog.__init__(self, parent, gtk.DIALOG_MODAL,
                                   gtk.MESSAGE_QUESTION, 
                                   gtk.BUTTONS_YES_NO, msg)

class TegakiTrainError(Exception):
    pass

class TegakiTrain(object):

    def __init__(self, options, args):
        self._charcol = CharacterCollection()
        self._create_window()
        self._update_title()

        if args:
            if len(args) != 1:
                raise TegakiTrainError("More than one input specified")
            charcol = CharacterCollectionFileChooserDialog\
                .open_character_collection(args[0])

            current_folder, filename = os.path.split(args[0])
            self._load_file(charcol, current_folder, args[0])

    def _load_file(self, charcol, current_folder, filename):
        self._curr_folder = current_folder
        self._curr_file = filename
        self._update_title()
        if charcol:
            self._add_recent(self._curr_file)
            self._charcol = charcol
            self._iconview.get_model().clear()
            self._refresh_treeview()
            self._disable_save()
            self._display_charcol_stats()

    def _on_new(self, widget):
        if not self._save_is_enabled() or self._confirm_continue():
            if "_curr_file" in self.__dict__:
                del self.__dict__["_curr_file"]
            self._update_title()
            self._charcol = CharacterCollection()
            self._treeview.get_model().clear()
            self._iconview.get_model().clear()

    def _display_charcol_stats(self):
        msg = "%d sets (%d characters) loaded!" % \
            (self._charcol.get_n_sets(), self._charcol.get_total_n_characters())
        self._statusbar_push(msg, sec=5)

    def _on_open(self, widget):
        if not self._save_is_enabled() or self._confirm_continue():
            dialog = CharacterCollectionFileChooserDialog(
                                           "Open Character Collection",
                                           self._window,
                                           action=gtk.FILE_CHOOSER_ACTION_OPEN)

            if "_curr_folder" in self.__dict__ and self._curr_folder:
                dialog.set_current_folder(self._curr_folder)

            response = dialog.run()
            if response == gtk.RESPONSE_OK:
                charcol = dialog.get_character_collection()
                self._load_file(charcol, dialog.get_current_folder(),
                                dialog.get_filename())

            dialog.destroy()

    def _on_recent_activated(self, widget):
        filename = self._recent_menu.get_current_item().get_uri_display()
        self._curr_file = filename
        self._update_title()
        self._charcol = CharacterCollectionFileChooserDialog.\
                            open_character_collection(filename)           
        self._iconview.get_model().clear()
        self._refresh_treeview()
        self._disable_save()
        self._display_charcol_stats()

    def _add_recent(self, filename):
        meta = {'mime_type':'text/xml', 'app_name':'tegaki-train',
                'app_exec':'tegaki-train'}
        if not filename.startswith("file://"):
            filename = "file://" + filename
        self._recent_manager.add_full(filename, meta)

    def _update_title(self):
        if "_curr_file" in self.__dict__:
            basename = os.path.basename(self._curr_file)
            self._window.set_title("Tegaki Train - %s" % basename)
        else:
            self._window.set_title("Tegaki Train - Unsaved file")

    def _refresh_treeview(self):
        self._treeview.get_model().clear()
        for charset in self._charcol.get_set_list():
            self._treeview.append_charset(charset)

    def _confirm_continue(self):
        dialog = QuestionDialog(self._window,
"Some changes have not been saved yet, continue anyway?")
        response = dialog.run()
        dialog.destroy()
        return response == gtk.RESPONSE_YES

    def _on_save(self, widget):
        if "_curr_file" in self.__dict__:
            CharacterCollectionFileChooserDialog. \
                save_character_collection(self._charcol, self._curr_file)
            self._disable_save()
            self._statusbar_push("File saved!", sec=5)
        else:
            self._on_save_as(widget)

    def _on_save_as(self, widget):
        dialog = CharacterCollectionFileChooserDialog(
                                            "Save Character Collection",
                                            self._window,
                                            action=gtk.FILE_CHOOSER_ACTION_SAVE)

        if "_curr_folder" in self.__dict__:
            dialog.set_current_folder(self._curr_folder)

        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            self._curr_folder = dialog.get_current_folder()
            self._curr_file = dialog.get_filename()
            self._update_title()
            self._add_recent(self._curr_file)
            dialog.set_character_collection(self._charcol)
            self._disable_save()
            self._statusbar_push("File saved!", sec=5)

        dialog.destroy()

    def _enable_save(self):
        self._global_actions.get_action("Save").set_sensitive(True)

    def _disable_save(self):
        self._global_actions.get_action("Save").set_sensitive(False)

    def _save_is_enabled(self):
        return self._global_actions.get_action("Save").get_sensitive()

    def _on_train(self, widget):
        dialog = TrainDialog(self._window)
        dialog.connect("response", self._on_train_done)
        dialog.run()

    def _on_train_done(self, dialog, response):
        if response == gtk.RESPONSE_OK:
            if self._charcol.get_total_n_characters() > 0:
                meta = dialog.get_meta()
                engine = dialog.get_engine()
                trainer_class = Trainer.get_available_trainers()[engine]
                trainer = trainer_class()
                trainer.train(self._charcol, meta)
                self._statusbar_push("Model trained!", sec=5)

        dialog.destroy()

    def _on_quit(self, widget):
        if not self._save_is_enabled() or self._confirm_continue():
            gtk.main_quit()
        else:
            return True

    #def _on_copy(self, widget):
        #pass

    #def _on_select_all(self, widget):
        #pass

    def _on_toggle_statusbar(self, widget):
        active = self._global_actions.get_action("Statusbar").get_active()
        self._statusbar.set_property("visible", active)

    def _on_toggle_toolbar(self, widget):
        active = self._global_actions.get_action("Toolbar").get_active()
        self._toolbar.set_property("visible", active)

    def _on_toggle_icon_text(self, widget):
        active = self._global_actions.get_action("IconText").get_active()
        if active:
            self._iconview.show_icon_text()
        else:
            self._iconview.hide_icon_text()

    def _on_icon_large(self, widget):
        self._set_icon_size(150)

    def _on_icon_medium(self, widget):
        self._set_icon_size(100)

    def _on_icon_small(self, widget):
        self._set_icon_size(50)

    def _set_icon_size(self, size):
        self._iconview.set_item_width(size)
        charset = self._treeview.get_selected_charset()
        if charset:
            characters = self._charcol.get_characters(charset)
            self._iconview.set_characters(characters)

    def _on_add_charset(self, widget):
        dialog = AddCharacterSetDialog(self._window)
        dialog.connect("response", self._on_add_charset_done)
        dialog.run()

    def _on_add_charset_done(self, dialog, response):
        if response == gtk.RESPONSE_OK:
            charsets = dialog.get_charsets()
            for charset in charsets:
                self._treeview.append_charset(charset)
            self._charcol.add_sets(charsets)
            self._enable_save()

        dialog.destroy()

    def _on_remove_charset(self, widget):
        charset = self._treeview.remove_selected_charset()
        self._charcol.remove_set(charset)
        self._enable_save()

    def _on_charset_selection_changed(self, widget):
        if self._treeview.get_selection().count_selected_rows() >= 1:
            sensitive = True
            charset = self._treeview.get_selected_charset()
            characters = self._charcol.get_characters(charset)
        else:
            sensitive = False
            characters = []

        # Sensitize button accordingly
        self._remove_charset_button.set_sensitive(sensitive)
        for s in ["AddSample", "AddSampleFromFile"]:
            self._global_actions.get_action(s).set_sensitive(sensitive)

        # Iconview
        self._iconview.set_characters(characters)

        # Status bar msg
        self._statusbar_push("%d samples loaded!" % len(characters), sec=5)

    def _on_add_sample(self, widget):
        window = EditCharacterSampleWindow(self._window)
        charset = self._treeview.get_selected_charset()
        if self._charcol.get_n_characters(charset) == 0:
            charvalue = charset
        else:
            char = self._charcol.get_characters(charset, limit=1)
            charvalue = char[0].get_utf8()
        window.set_character_value(charvalue)
        window.connect("delete-event", self._on_add_sample_done)
        window.show_all()

    def _on_add_sample_done(self, window, event):
        charset = self._treeview.get_selected_charset()
        character = window.get_character()
        self._charcol.append_character(charset, character)
        # FIXME; update the corresponding character only
        self._iconview.set_characters(self._charcol.get_characters(charset))
        self._enable_save()

    def _on_icon_selection_changed(self, widget):
        selitems = widget.get_selected_items()
        if len(selitems) == 0:
            sensitive = False
        else:
            sensitive = True

        for s in ["EditSample", "SaveSampleToFile", "RemoveSample"]:
            self._global_actions.get_action(s).set_sensitive(sensitive)

    def _on_add_sample_from_file(self, widget):
        dialog = CharacterFileChooserDialog("Open Character",
                                            self._window)

        if "_curr_folder" in self.__dict__:
            dialog.set_current_folder(self._curr_folder)

        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            self._curr_folder = dialog.get_current_folder()
            char = dialog.get_character()
            if char:
                charset = self._treeview.get_selected_charset()
                self._charcol.append_character(charset, char)
                chars = self._charcol.get_characters(charset)
                self._iconview.set_characters(chars)
                self._enable_save()

        dialog.destroy()

    def _on_edit_sample(self, widget, path=None):
        window = EditCharacterSampleWindow(self._window)
        charset = self._treeview.get_selected_charset()
        selitems = self._iconview.get_selected_items()
        charindex = selitems[0][0]
        char = self._charcol.get_characters(charset, limit=1, offset=charindex)
        window.set_character(char[0])
        window.connect("delete-event", self._on_edit_sample_done)
        window.show_all()

    def _on_edit_sample_done(self, window, event):
        charset = self._treeview.get_selected_charset()
        character = window.get_character()
        selitems = self._iconview.get_selected_items()
        charindex = selitems[0][0]
        self._charcol.replace_character(charset, charindex, character)
        self._iconview.set_characters(self._charcol.get_characters(charset))
        self._enable_save()

    def _on_remove_sample(self, widget):
        selitems = self._iconview.get_selected_items()
        charset = self._treeview.get_selected_charset()
        charindex = selitems[0][0]
        self._charcol.remove_character(charset, charindex)
        chars = self._charcol.get_characters(charset)
        self._iconview.set_characters(chars)
        self._enable_save()

    def _on_save_sample_to_file(self, widget):
        dialog = CharacterFileChooserDialog("Save Character",
                                            self._window,
                                            action=gtk.FILE_CHOOSER_ACTION_SAVE)

        if "_curr_folder" in self.__dict__:
            dialog.set_current_folder(self._curr_folder)

        response = dialog.run()
        if response == gtk.RESPONSE_OK:
            self._curr_folder = dialog.get_current_folder()
            charset = self._treeview.get_selected_charset()
            selitems = self._iconview.get_selected_items()
            charindex = selitems[0][0]
            char = self._charcol.get_characters(charset, limit=1,
                                                offset=charindex)
            dialog.set_character(char[0])

        dialog.destroy()

    def _statusbar_push(self, msg, sec=-1):
        msgid = self._statusbar.push(0, msg)
        if sec != -1:
            def myfunc():
                self._statusbar.remove(0, msgid)
                return False
            glib.timeout_add_seconds(sec, myfunc)

    def _create_menu(self):
        self._uimanager = gtk.UIManager()
        menu_xml = \
"""
<ui>
    <menubar name="MainMenubar">
        <menu action="FileMenu">
            <menuitem action="New"/>
            <menuitem action="Open"/>
            <menu action="OpenRecent">
            </menu>
            <separator/>
            <menuitem action="Save"/>
            <menuitem action="SaveAs"/>
            <separator/>
            <menuitem action="Train"/>
            <separator/>
            <menuitem action="Quit"/>
        </menu>
        <menu action="EditMenu">
            <menuitem action="AddSample"/>
            <menuitem action="AddSampleFromFile"/>
            <separator/>
            <menuitem action="EditSample"/>
            <menuitem action="SaveSampleToFile"/>
            <menuitem action="RemoveSample"/>
            <!-- <separator/>
            <menuitem action="Copy"/>
            <menuitem action="SelectAll"/> -->
        </menu>
        <menu action="ViewMenu">
            <menu action="IconSizeMenu">
                <menuitem action="IconLarge"/>
                <menuitem action="IconMedium"/>
                <menuitem action="IconSmall"/>
            </menu>
            <menuitem action="IconText"/>
            <separator/>
            <menuitem action="Statusbar"/>
            <menuitem action="Toolbar"/>
        </menu>
        <!-- <menu action="HelpMenu">
            <menuitem action="Help"/>
            <separator/>
            <menuitem action="About"/>
        </menu> -->
    </menubar>
</ui>
"""

        toolbar_xml = \
"""
<ui>
    <toolbar name="Toolbar">
            <toolitem action="New"/>
            <toolitem action="Open"/>
            <separator/>
            <toolitem action="Save"/>
            <toolitem action="SaveAs"/>
            <separator/>
            <toolitem action="Train"/>
            <separator/>
            <toolitem action="AddSample"/>
            <toolitem action="EditSample"/>
            <toolitem action="RemoveSample"/>
    </toolbar>
</ui>
"""

        # [(name, stock_id, label, accelerator, tooltip, proc), ... ]
        standard_actions = [
("FileMenu", None, "_File"),
("New", gtk.STOCK_NEW, None, None, None, self._on_new),
("Open", gtk.STOCK_OPEN, None, None, None, self._on_open),
("OpenRecent", None, "Open Recent"),
("Save", gtk.STOCK_SAVE, None, None, None, self._on_save),
("SaveAs", gtk.STOCK_SAVE_AS, None, None, None, self._on_save_as),
("Train", gtk.STOCK_EXECUTE, "_Train", None, None, self._on_train),
("Quit", gtk.STOCK_QUIT, None, None, None, self._on_quit),

("EditMenu", None, "_Edit"),
("AddSample", gtk.STOCK_ADD, "Add sample", None, None, self._on_add_sample),
("AddSampleFromFile", None, "Add sample from file", None, None, 
    self._on_add_sample_from_file),
("EditSample", gtk.STOCK_EDIT, "Edit sample", None, None, self._on_edit_sample),
("SaveSampleToFile", None, "Save sample to file", None, None, 
    self._on_save_sample_to_file),
("RemoveSample", gtk.STOCK_REMOVE, "Remove sample", None, None,
    self._on_remove_sample),
#("Copy", gtk.STOCK_COPY, None, None, None, self._on_copy),
#("SelectAll", None, "Select _All", "<ctrl>A", None, self._on_select_all),

("ViewMenu", None, "_View"),
("IconSizeMenu", None, "Icon size"),
("IconLarge", None, "Large", None, None, self._on_icon_large),
("IconMedium", None, "Medium", None, None, self._on_icon_medium),
("IconSmall", None, "Small", None, None, self._on_icon_small),
        ]

        toggle_actions = [
("IconText", None, "Icon text", None, None, self._on_toggle_icon_text, 1),
("Statusbar", None, "_Statusbar", None, None, self._on_toggle_statusbar, 1),
("Toolbar", None, "_Toolbar", None, None, self._on_toggle_toolbar, 1),
        ]

        self._global_actions = gtk.ActionGroup("Standard actions")
        self._global_actions.add_actions(standard_actions)
        self._global_actions.add_toggle_actions(toggle_actions)

        # Default to False
        for s in ["AddSample", "AddSampleFromFile",
                  "EditSample", "SaveSampleToFile", "RemoveSample",
                  "Save"]:
            self._global_actions.get_action(s).set_sensitive(False)

        self._uimanager.insert_action_group(self._global_actions, 0)
        self._uimanager.add_ui_from_string(menu_xml)
        self._uimanager.add_ui_from_string(toolbar_xml)
        
        self._menubar = self._uimanager.get_widget("/MainMenubar")
        self._toolbar = self._uimanager.get_widget("/Toolbar")
        self._toolbar.set_border_width(0)

        # Recent files
        self._recent_manager = gtk.recent_manager_get_default()
        self._recent_menu = gtk.RecentChooserMenu(self._recent_manager)
        self._recent_menu.connect("item-activated", self._on_recent_activated)
        self._recent_menu.set_show_not_found(False)
        self._recent_menu.set_local_only(True)
        self._recent_menu.set_show_tips(False)
        self._recent_menu.set_sort_type(gtk.RECENT_SORT_MRU)
        filt = gtk.RecentFilter()
        filt.add_application("tegaki-train")
        #filt.add_pattern("*.charcol")
        #filt.add_pattern("*.charcol.gz")
        #filt.add_pattern("*.charcol.bz2")
        self._recent_menu.add_filter(filt)
        self._recent_menu.set_limit(15)
        open_r = self._uimanager.get_widget("/MainMenubar/FileMenu/OpenRecent")
        open_r.set_submenu(self._recent_menu)
        open_r.show()

    def _create_left_pane(self):
        self._leftvbox = gtk.VBox()

        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        label = gtk.Label("Character sets")
        frame.add(label)
        self._leftvbox.pack_start(frame, expand=False)

        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        scrolledwindow = gtk.ScrolledWindow()
        scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self._treeview = CharacterSetListView(self)
        selection = self._treeview.get_selection()
        selection.connect("changed", self._on_charset_selection_changed)
        scrolledwindow.add(self._treeview)
        frame.add(scrolledwindow)
        self._leftvbox.pack_start(frame)

        hbox = gtk.HBox()
        self._add_charset_button = gtk.Button(stock=gtk.STOCK_ADD)
        self._add_charset_button.connect("clicked", self._on_add_charset)
        self._remove_charset_button = gtk.Button(stock=gtk.STOCK_REMOVE)
        self._remove_charset_button.connect("clicked", self._on_remove_charset)
        self._remove_charset_button.set_sensitive(False)
        hbox.pack_start(self._add_charset_button)
        hbox.pack_start(self._remove_charset_button)

        self._leftvbox.pack_start(hbox, expand=False)

    def _create_window(self):
        self._window = gtk.Window()
        self._window.connect("delete-event", lambda w,e: self._on_quit(w))

        self._create_menu()
        self._window.add_accel_group(self._uimanager.get_accel_group())

        main_vbox = gtk.VBox()
        main_vbox.pack_start(self._menubar, expand=False)
        main_vbox.pack_start(self._toolbar, expand=False)

        self._sidepane = gtk.HPaned()

        self._create_left_pane()
        self._sidepane.add1(self._leftvbox)

        frame = gtk.Frame()
        frame.set_shadow_type(gtk.SHADOW_ETCHED_IN)
        scrolledwindow = gtk.ScrolledWindow()
        scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
        self._iconview = WritingIconView()
        self._iconview.connect("selection-changed",
                               self._on_icon_selection_changed)
        self._iconview.connect("item-activated",
                               self._on_edit_sample)
        self._global_actions.get_action("IconText").set_active(False)
        
        scrolledwindow.add(self._iconview)
        frame.add(scrolledwindow)
        self._sidepane.add2(frame)

        main_vbox.pack_start(self._sidepane, padding=2)

        self._statusbar = gtk.Statusbar()
        main_vbox.pack_start(self._statusbar, expand=False, padding=2)

        self._window.add(main_vbox)
        self._window.set_default_size(650, 500)
        self._window.show_all()

    def run(self):
        gtk.main()

usage = "Usage: %prog [options] [charcol-file]"
parser = OptionParser(usage=usage, version="%prog " + VERSION)

(options, args) = parser.parse_args()

try:
    TegakiTrain(options, args).run()
except TegakiTrainError, e:
    sys.stderr.write(str(e) + "\n\n")
    parser.print_help()
    sys.exit(1)
