#!/usr/bin/env python
# -*- 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, CharacterCollection
from tegaki.trainer import Trainer
from tegakigtk.iconview import WritingIconView, _WritingPixbufRenderer
from tegakigtk.canvas import Canvas

VERSION = '0.3'

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

    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 = len(self._parentw._charcol.get_characters(charset))
        cell.props.text = str(n_chars)

    def append_charset(self, charset):
        if not self.includes_charset(charset):
            self._model.append((charset,))

    def includes_charset(self, charset):
        for row in self._model:
            if row[0] == charset:
                return True
        return False

    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)
        
        for name, pat in [["Character collection", "*.charcol"], 
                          ["Gzip-compressed Character collection",
                           "*.charcol.gz"],
                          ["Bz2-compressed Character collection",
                           "*.charcol.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)

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

    @staticmethod
    def open_character_collection(filename):
        charcol = CharacterCollection()

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

        return charcol

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

    @staticmethod
    def save_character_collection(charcol, filename):
        if filename.endswith(".gz"):
            charcol.write(filename, gzip=True)
        elif filename.endswith(".bz2"):
            charcol.write(filename, bz2=True)
        else:
            charcol.write(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 len(self._charcol.get_all_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:
            for charset in dialog.get_charsets():
                self._treeview.append_charset(charset)
                self._charcol.add_set(charset)
                self._enable_save()

        dialog.destroy()

    def _on_remove_charset(self, widget):
        seliter = self._treeview.get_selection().get_selected()[1]
        charset = self._treeview.get_model().get_value(seliter, 0)
        self._treeview.get_model().remove(seliter)
        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)

    def _on_add_sample(self, widget):
        window = EditCharacterSampleWindow(self._window)
        charset = self._treeview.get_selected_charset()
        characters = self._charcol.get_characters(charset)
        if len(characters) == 0:
            charvalue = charset
        else:
            charvalue = characters[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)
        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)[charindex]
        window.set_character(char)
        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)[charindex]
            dialog.set_character(char)

        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)
