Logo Search packages:      
Sourcecode: m17n-im-config version File versions  Download package

mim-config.c

/* mim-config.c -- M17N input method configuration
   Copyright (C) 2007
     National Institute of Advanced Industrial Science and Technology (AIST)
     Registration Number H15PRO112

   This file is part of the m17n-im-config package; a sub-part of the
   m17n library.

   The m17n library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public License
   as published by the Free Software Foundation; either version 2.1 of
   the License, or (at your option) any later version.

   The m17n library 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
   Lesser General Public License for more details.

   You should have received a copy of the GNU Lesser General Public
   License along with the m17n library; if not, write to the Free
   Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
   02111-1307, USA.  */

#include <stdlib.h>
#include <string.h>
#include <libintl.h>
#include <locale.h>
#include <m17n.h>
#include <m17n-misc.h>
#include <gtk/gtk.h>
#include <gdk/gdk.h>
#include <gdk/gdkkeysyms.h>
#include <config.h>
#include "m17n-im-config.h"

#define _(String) dgettext (PACKAGE, String)

#define CONFIG_CALLBACK_DATA " config-callback-data"
#define CONFIG_STATUS_DATA " config-status-data"
#define CONFIG_TREE_VIEW " config-tree-view"

typedef void (*MimConfigCallbackFunc) (GtkWidget *widget, gpointer data);

typedef struct _MimConfigCallback
{
  GtkWidget *widget;
  MimConfigCallbackFunc func;
  gpointer data;
} MimConfigCallback;

typedef struct _MimConfigStatus
{
  /* Number of available input methods.  */
  gint num_im;
  /* Number of modified input methods.  */
  gint num_modified;
} MimConfigStatus;

/* Status of variables and commands of an input method.  */

enum MimStatus
  {
    MIM_STATUS_DEFAULT,
    MIM_STATUS_CUSTOMIZED,
    MIM_STATUS_MODIFIED,
    MIM_STATUS_NO,
    MIM_STATUS_MAX
  };

static char *mim_status_str[MIM_STATUS_MAX];

enum MimStatus
get_mim_status (MSymbol lang, MSymbol name)
{
  MPlist *plist;
  enum MimStatus status = MIM_STATUS_NO;

  for (plist = minput_get_variable (lang, name, Mnil);
       plist && mplist_key (plist) != Mnil; plist = mplist_next (plist))
    {
      MPlist *p = mplist_value (plist);
      MSymbol status_symbol;

      p = mplist_next (mplist_next (p));
      status_symbol = mplist_value (p);
      if (status_symbol == Mconfigured)
      return MIM_STATUS_MODIFIED;
      if (status_symbol == Mcustomized)
      status = MIM_STATUS_CUSTOMIZED;
      else if (status == MIM_STATUS_NO)
      status = MIM_STATUS_DEFAULT;
    }
  for (plist = minput_get_command (lang, name, Mnil);
       plist && mplist_key (plist) != Mnil; plist = mplist_next (plist))
    {
      MPlist *p = mplist_value (plist);
      MSymbol status_symbol;

      p = mplist_next (mplist_next (p));
      status_symbol = mplist_value (p);
      if (status_symbol == Mconfigured)
      return MIM_STATUS_MODIFIED;
      if (status_symbol == Mcustomized)
      status = MIM_STATUS_CUSTOMIZED;
      else if (status == MIM_STATUS_NO)
      status = MIM_STATUS_DEFAULT;
    }
  return status;
}

/* Columns of each row.  */
enum
  {
    /* parent: language name
        child: IM name  */
    COL_TAG = 0,
    /* parent: NULL or "modified"
        child: "default", "customized", or "modified"  */
    COL_STATUS_STR,
    /* parent: num of modified children
        child: enum MimStatus */
    COL_STATUS,
    /* parent: Mnil
        child: symbolic language name.  */
    COL_LANG,
    /* parent: Mnil
        child: symbolic IM name.  */
    COL_NAME,
    /* number of columns  */
    NUM_COLS
  };

/* Called when a row is expanded.  We may have to initialize
   children.  */
static void
tree_expanded_cb (GtkTreeView *tree, GtkTreeIter *parent,
              GtkTreePath *path, gpointer data)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  MSymbol lang, name;

  model = gtk_tree_view_get_model (tree);
  if (gtk_tree_model_iter_children (model, &iter, parent))
    {
      gchar *status_str;

      gtk_tree_model_get (model, &iter, COL_STATUS_STR, &status_str, -1);
      if (! status_str)
      {
        /* The first child is not yet initialized, and that means
           the remaining children are not initialized either.  */
        gtk_tree_model_get (model, &iter, COL_LANG, &lang, -1);
        do {
          enum MimStatus im_status;

          gtk_tree_model_get (model, &iter, COL_NAME, &name, -1);
          im_status = get_mim_status (lang, name);
          gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
                        COL_STATUS_STR, mim_status_str[im_status],
                        COL_STATUS, im_status,
                        -1);
        } while (gtk_tree_model_iter_next (model, &iter));
      }
    }
}

static void config_im (GtkTreeView *tree, MSymbol lang, MSymbol name);

static void
update_child_row (GtkTreeModel *model, GtkTreeIter *iter,
              enum MimStatus status, MimConfigStatus *config_status,
              GtkTreeView *tree)
{
  GtkTreeIter parent;
  gint inc_modified;

  inc_modified = (status == MIM_STATUS_MODIFIED ? 1 : -1);

  gtk_tree_store_set (GTK_TREE_STORE (model), iter,
                  COL_STATUS_STR, mim_status_str[status],
                  COL_STATUS, status, -1);
  if (gtk_tree_model_iter_parent (model, &parent, iter))
    {
      gint num_modified;
      gchar *status_str;

      gtk_tree_model_get (model, &parent, COL_STATUS, &num_modified, -1);
      num_modified += inc_modified;
      gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
                    COL_STATUS, num_modified, -1);
      if (num_modified <= 1)
      {
        status_str = (status == MIM_STATUS_MODIFIED
                  ? mim_status_str[MIM_STATUS_MODIFIED] : NULL);
        gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
                        COL_STATUS_STR, status_str);
      }
    }
      
  if (! config_status)
    config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
  config_status->num_modified += inc_modified;
  if (tree && config_status->num_modified <= 1)
    {
      MimConfigCallback *callback;

      callback = g_object_get_data (G_OBJECT (tree), CONFIG_CALLBACK_DATA);
      if (callback)
      callback->func (callback->widget, callback->data);
    }
}

static void
tree_activated_cb (GtkTreeView *tree, GtkTreePath *path,
               GtkTreeViewColumn *column, gpointer data)
{
  GtkTreeModel *model;
  GtkTreeIter iter;

  model = gtk_tree_view_get_model (tree);
  if (gtk_tree_model_get_iter (model, &iter, path))
    {
      MSymbol lang, name;

      gtk_tree_model_get (model, &iter, COL_LANG, &lang, COL_NAME, &name, -1);
      if (lang != Mnil)
      {
        /* child row for an IM */
        enum MimStatus old, new;

        old = get_mim_status (lang, name);
        config_im (tree, lang, name);
        new = get_mim_status (lang, name);
        if (old != new)
          update_child_row (model, &iter, new, NULL, tree);
      }
      else
      {
        /* parent row for a language */
        if (gtk_tree_view_row_expanded (tree, path))
          gtk_tree_view_collapse_row (tree, path);
        else
          gtk_tree_view_expand_row (tree, path, TRUE);
      }
    }
}

typedef struct _MimTable
{
  int lang_in_locale;
  gchar *encoded_lang;
  gchar *lang;
  gchar *name;
  MSymbol symlang;
  MSymbol symname;
} MimTable;

static int
sort_im (const void *p1, const void *p2)
{
  const MimTable *t1 = p1;
  const MimTable *t2 = p2;
  int result;

  if (t1->symlang == t2->symlang)
    result = 0;
  else
    {
      if (t1->lang_in_locale != t2->lang_in_locale)
      return (t1->lang_in_locale ? -1 : 1);
      if ((! t1->encoded_lang) != (! t2->encoded_lang))
      return (t1->encoded_lang ? -1 : 1);
      if (t1->encoded_lang)
      result = strcoll (t1->encoded_lang, t2->encoded_lang);
      else if ((! t1->lang) != (! t2->lang))
      return (t1->lang ? -1 : 1);
      else
      result = strcmp (t1->lang, t2->lang);
    }

  return (result ? result : strcmp (t1->name, t2->name));
}

static GtkTreeStore *
make_store_for_input_methods ()
{
  GtkTreeStore *store;
  MPlist *imlist, *p;
  int i;
  MimTable *imtable;
  char *lang, *other = _("Other");
  GtkTreeIter iter1, iter2;
  enum MimStatus status;
  MimConfigStatus *config_status;
  unsigned char conv_buf[256];
  MConverter *converter;
  int locale_is_utf8 = 0;
  static MSymbol Meng;

  if (! Meng)
    Meng = msymbol ("eng");
  store = gtk_tree_store_new (NUM_COLS,
                        G_TYPE_STRING,  /* COL_TAG */
                        G_TYPE_STRING,  /* COL_STATUS_STR */
                        G_TYPE_UINT,    /* COL_STATUS */
                        G_TYPE_POINTER, /* COL_LANG */
                        G_TYPE_POINTER  /* COL_NAME */
                        );

  config_status = g_new0 (MimConfigStatus, 1);
  gtk_tree_store_append (store, &iter1, NULL);
  status = get_mim_status (Mt, Mnil);
  gtk_tree_store_set (store, &iter1,
                  COL_TAG, _("global"),
                  COL_STATUS_STR, mim_status_str[status],
                  COL_STATUS, status, 
                  COL_LANG, Mt,
                  COL_NAME, Mnil,
                  -1);

  imlist = mdatabase_list (msymbol ("input-method"), Mnil, Mnil, Mnil);
  config_status->num_im = mplist_length (imlist);
  imtable = g_newa (MimTable, config_status->num_im);

  {
    MLocale *locale = mlocale_set (LC_MESSAGES, NULL);
    MSymbol coding = locale ? mlocale_get_prop (locale, Mcoding) : Mnil;

    if (coding == Mnil)
      converter = NULL;
    else if (coding == msymbol ("utf-8"))
      {
      converter = NULL;
      locale_is_utf8 = 1;
      }
    else
      {
      converter = mconv_buffer_converter (coding, conv_buf, sizeof conv_buf);
      if (converter)
        converter->last_block = 1;
      }
  }

  for (i = 0, p = imlist; mplist_key (p) != Mnil; p = mplist_next (p))
    {
      MDatabase *mdb = (MDatabase *) mplist_value (p);
      MSymbol *tag = mdatabase_tag (mdb);
      MPlist *pl;

      if (tag[1] == Mnil || tag[2] == Mnil)
      continue;

      imtable[i].symlang = tag[1];
      imtable[i].symname = tag[2];
      imtable[i].name = msymbol_name (tag[2]);
      imtable[i].lang = imtable[i].encoded_lang = NULL;
      imtable[i].lang_in_locale = 0;

      if (tag[1] != Mt)
      {
        pl = mlanguage_name_list (tag[1], Mnil, Mnil, Mnil);
        if (pl)
          imtable[i].lang_in_locale = 1;
        else
          pl = mlanguage_name_list (tag[1], Meng, Mnil, Mnil);
        if (pl)
          {
            MText *mt = mplist_value (pl);
            int nbytes;

            if (converter)
            {
              mconv_reset_converter (converter);
              nbytes = mconv_encode (converter, mt);
              if (converter->result == MCONVERSION_RESULT_SUCCESS)
                {
                  imtable[i].encoded_lang = alloca (nbytes + 1);
                  if (imtable[i].encoded_lang)
                  {
                    memcpy (imtable[i].encoded_lang, conv_buf, nbytes);
                    imtable[i].encoded_lang[nbytes] = '\0';
                  }
                }
            }
            else if (locale_is_utf8)
            imtable[i].encoded_lang
              = mtext_data (mt, NULL, NULL, NULL, NULL);
            imtable[i].lang = mtext_data (mt, NULL, NULL, NULL, NULL);
          }
        else
          imtable[i].lang = msymbol_name (tag[1]);
      }
      i++;
    }
  if (converter)
    mconv_free_converter (converter);
  m17n_object_unref (imlist);
  config_status->num_im = i;
  qsort (imtable, config_status->num_im, sizeof (MimTable), sort_im);

  for (lang = NULL, i = 0; i < config_status->num_im; i++)
    {
      gchar *langname = imtable[i].lang;

      if (! langname)
      langname = other;
      if (lang != langname)
      {
        gtk_tree_store_append (store, &iter1, NULL);
        gtk_tree_store_set (store, &iter1,
                        COL_TAG, langname,
                        COL_STATUS_STR, NULL,
                        COL_STATUS, 0,
                        COL_LANG, Mnil,
                        COL_NAME, Mnil,
                        -1);
        lang = langname;
      }
      gtk_tree_store_append (store, &iter2, &iter1);
      gtk_tree_store_set (store, &iter2,
                    COL_TAG, imtable[i].name,
                    COL_STATUS_STR, NULL,
                    COL_LANG, imtable[i].symlang,
                    COL_NAME, imtable[i].symname,
                    -1);
    }
  config_status->num_modified = 0;
  g_object_set_data_full (G_OBJECT (store), CONFIG_STATUS_DATA,
                    config_status, g_free);
  return store;
}

static gboolean
reset_to_default (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
              gpointer data)
{
  enum MimStatus status, new_status;
  MSymbol lang, name;
  MimConfigStatus *config_status = data;
  MPlist *empty = mplist ();

  gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
  if (lang == Mnil)
    return FALSE;
  gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);
  if (status == MIM_STATUS_DEFAULT)
    return FALSE;
  minput_config_variable (lang, name, Mnil, empty);
  minput_config_command (lang, name, Mnil, empty);
  new_status = get_mim_status (lang, name);
  if (status != new_status)
    update_child_row (model, iter, new_status, config_status, NULL);
  return FALSE;
}

static gboolean
revert_to_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
             gpointer data)
{
  enum MimStatus status;
  MSymbol lang, name;
  MimConfigStatus *config_status = data;

  gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
  if (lang == Mnil)
    return FALSE;
  gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
  if (status != MIM_STATUS_MODIFIED)
    return FALSE;
  minput_config_variable (lang, name, Mnil, NULL);
  minput_config_command (lang, name, Mnil, NULL);
  status = get_mim_status (lang, name);
  update_child_row (model, iter, status, config_status, NULL);
  return FALSE;
}

static gboolean
set_as_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
            gpointer data)
{
  enum MimStatus status;
  MSymbol lang, name;
  MimConfigStatus *config_status = data;

  gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
  if (lang == Mnil)
    return FALSE;
  gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
  if (status != MIM_STATUS_MODIFIED)
    return FALSE;
  status = get_mim_status (lang, name);
  update_child_row (model, iter, status, config_status, NULL);
  return FALSE;
}

static int initialized = 0;

static void
destroy_cb (GtkWidget *widget, gpointer data)
{
  M17N_FINI ();
  initialized = 0;
}


/****************************************************/
/* Configuration of a specific variable or command. */
/****************************************************/

/* Common staffs to variable and command */

struct ConfigControl
{
  /* Data type name ("Value" or "Key bindings").  */
  gchar *data_type_name;
  MSymbol lang, name, item;
  /* Fill in widgets in DIALOG for configuring a specific variable or
     command.  */
  void (*setup_dialog) (GtkWidget *dialog, struct ConfigControl *control);
  /* Update the contents of DATA widget.  */
  void (*update_data) (struct ConfigControl *control);
  /* Convert PLIST to string.  PLIST is a variable value or command
     key sequeneses.  */
  GString *(*data_string) (MPlist *plist);
  /* minput_get_variable or minput_get_command.  */
  MPlist *(*get) (MSymbol, MSymbol, MSymbol);
  /* minput_config_variable or minput_config_command.  */
  int (*config) (MSymbol, MSymbol, MSymbol, MPlist *);
  /* If non-NULL, a function to call before finishing a dialog.  */
  gboolean (*config_on_ok) (struct ConfigControl *control);

  /* Widget showing the current data (value or key bindings) */
  GtkWidget *data;

  /* Button widget to configure the data to the default.  */
  GtkWidget *default_;

  /* Button widget to cancel the configuration.  */
  GtkWidget *revert;

  /* Label widget showing the current status.  */
  GtkWidget *status;
};

struct CommandControl
{
  struct ConfigControl control;
  GtkWidget *entry;
  GtkWidget *clear;
  GtkWidget *add;
  GtkWidget *delete;
};

enum WidgetType
  {
    ENTRY_WIDGET,
    COMBO_BOX_WIDGET,
    SPIN_BUTTON_WIDGET
  };

struct VariableControl
{
  struct ConfigControl control;

  /* type of current variable: Minteger, Msymbol, or Mtext */
  MSymbol vtype;

  /* type of widget */
  enum WidgetType wtype;
};

#define CONFIG_CONTROL(control) ((struct ConfigControl *) (control))
#define COMMAND_CONTROL(control) ((struct CommandControl *) (control))
#define VARIABLE_CONTROL(control) ((struct VariableControl *) (control))

#define CURRENT_DATA    \
  (mplist_next                \
   (mplist_next               \
    (mplist_next        \
     (mplist_value            \
      (control->get (control->lang, control->name, control->item))))))

#define CURRENT_STATUS        \
  (mplist_value               \
   (mplist_next               \
    (mplist_next        \
     (mplist_value            \
      (control->get (control->lang, control->name, control->item))))))

#define CURRENT_DESCRIPTION                           \
  (mtext_data                                         \
   (mplist_value                                \
    (mplist_next                                \
     (mplist_value                                    \
      (control->get (control->lang, control->name, control->item)))),   \
    NULL,  NULL,  NULL,  NULL))

#define CONFIG_DATA(plist)                            \
  control->config (control->lang, control->name, control->item,   \
               (plist))

static MPlist *entry_keyseq;

static void
update_status (struct ConfigControl *control)
{
  MSymbol status = CURRENT_STATUS;

  if (status == Mconfigured)
    {
      gtk_label_set_text (GTK_LABEL (control->status),
                    mim_status_str[MIM_STATUS_MODIFIED]);
      gtk_widget_set_sensitive (control->default_, TRUE);
      gtk_widget_set_sensitive (control->revert, TRUE);
    }
  else if (status == Mcustomized)
    {
      gtk_label_set_text (GTK_LABEL (control->status),
                    mim_status_str[MIM_STATUS_CUSTOMIZED]);
      gtk_widget_set_sensitive (control->default_, TRUE);
      gtk_widget_set_sensitive (control->revert, FALSE);
    }
  else
    {
      gtk_label_set_text (GTK_LABEL (control->status),
                    mim_status_str[MIM_STATUS_DEFAULT]);
      gtk_widget_set_sensitive (control->default_, FALSE);
      gtk_widget_set_sensitive (control->revert, FALSE);
    }
}

static void
help_cb (GtkButton *button, gpointer data)
{
  struct ConfigControl *control = data;
  GtkWidget *msg;

  msg = gtk_message_dialog_new (GTK_WINDOW
                        (gtk_widget_get_toplevel (GTK_WIDGET (button))),
                        GTK_DIALOG_DESTROY_WITH_PARENT,
                        GTK_MESSAGE_INFO,
                        GTK_BUTTONS_CLOSE,
                        CURRENT_DESCRIPTION);
  gtk_dialog_run (GTK_DIALOG (msg));
  gtk_widget_destroy (msg);
}

static void
default_cb (GtkButton *button, gpointer data)
{
  MPlist *empty = mplist ();
  struct ConfigControl *control = data;

  CONFIG_DATA (empty);
  m17n_object_unref (empty);
  control->update_data (control);
  update_status (control);
  control->config_on_ok = NULL;
}

static void
revert_cb (GtkButton *button, gpointer data)
{
  struct ConfigControl *control = data;

  CONFIG_DATA (NULL);
  control->update_data (control);
  update_status (control);
  control->config_on_ok = NULL;
}

static void
ok_cb (GtkButton *button, gpointer data)
{
  struct ConfigControl *control = data;

  if (control->config_on_ok)
    {
      if (! control->config_on_ok (control))
      {
        revert_cb (NULL, control);
        return;
      }
      control->config_on_ok = NULL;
    }
  if (control->config == minput_config_command)
    m17n_object_unref (entry_keyseq);
  gtk_dialog_response (GTK_DIALOG
                   (gtk_widget_get_toplevel (GTK_WIDGET (button))),
                   GTK_RESPONSE_OK);
}

enum
  {
    /* Variable or command name */
    CONFIG_COL_ITEM,
    /* Status (default, modified, or customized).  */
    CONFIG_COL_STATUS,
    /* Variable value or command key bindings. */
    CONFIG_COL_DATA,
    /* Number of columns of list store.  */
    NUM_CONFIG_COLS
  };


static void
set_list_element (GtkListStore *store, GtkTreeIter *iter,
              struct ConfigControl *control, MPlist *plist)
{
  MSymbol status;
  gchar *status_str;

  if (! plist)
    plist = mplist_value (control->get (control->lang, control->name,
                              control->item));
  plist = mplist_next (mplist_next (plist));
  
  status = mplist_value (plist);
  if (status == Mconfigured)
    status_str = mim_status_str[MIM_STATUS_MODIFIED];
  else if (status == Mcustomized)
    status_str = mim_status_str[MIM_STATUS_CUSTOMIZED];
  else
    status_str = mim_status_str[MIM_STATUS_DEFAULT];
  plist = mplist_next (plist);
  gtk_list_store_set (store, iter,
                  CONFIG_COL_ITEM, msymbol_name (control->item),
                  CONFIG_COL_STATUS, status_str,
                  CONFIG_COL_DATA, control->data_string (plist)->str,
                  -1);
}

/* Called when an item (command or variable) name is activated.
   Create a dialog widget to config that itme.  */

static void
item_activated_cb (GtkTreeView *parent, GtkTreePath *path,
               GtkTreeViewColumn *col, gpointer data)
{
  GtkTreeModel *model;
  GtkTreeIter iter;
  GtkWidget *dialog, *label, *help, *hbox, *ok;
  gchar *item;
  struct ConfigControl *control = CONFIG_CONTROL (data);

  model = gtk_tree_view_get_model (parent);
  if (! gtk_tree_model_get_iter (model, &iter, path))
    return;
  gtk_tree_model_get (model, &iter, CONFIG_COL_ITEM, &item, -1);
  control->item = msymbol (item);

  dialog = (gtk_dialog_new_with_buttons
          (msymbol_name (control->item),
           GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (parent))),
           GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
           NULL));
  gtk_button_box_set_layout (GTK_BUTTON_BOX (GTK_DIALOG (dialog)->action_area),
                       GTK_BUTTONBOX_EDGE);
  gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);

  hbox = gtk_hbox_new (FALSE, 12);
  gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
  label = gtk_label_new (_("Status"));
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  label = gtk_label_new (": ");
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  control->status = gtk_label_new (NULL);
  gtk_box_pack_start (GTK_BOX (hbox), control->status, FALSE, FALSE, 0);
  gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox),
                hbox, FALSE, FALSE, 0);

  help = gtk_button_new_from_stock (GTK_STOCK_HELP);
  g_signal_connect (G_OBJECT (help), "clicked",
                G_CALLBACK (help_cb), control);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
                  help, FALSE, FALSE, 0);
  ok = gtk_button_new_from_stock (GTK_STOCK_OK);
  g_signal_connect (G_OBJECT (ok), "clicked",
                G_CALLBACK (ok_cb), control);
  gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->action_area),
                ok, FALSE, FALSE, 0);

  control->setup_dialog (dialog, control);

  update_status (control);
  gtk_widget_show_all (dialog);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_tree_model_get_iter (model, &iter, path);
  set_list_element (GTK_LIST_STORE (model), &iter, control, NULL);
  gtk_widget_destroy (dialog);
}


/* Create a list view widget listing variable or command names with
   their current status and data.  */

GtkWidget *
create_item_list (MSymbol lang, MSymbol name, struct ConfigControl *control)
{
  GtkListStore *store;
  GtkWidget *view;
  MPlist *plist;

  plist = control->get (lang, name, Mnil);
  /* plist == ((command/variable description status data ...) ...) */
  if (! plist)
    return gtk_label_new (_("No customizable item."));
  store = gtk_list_store_new (NUM_CONFIG_COLS,
                        G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
  for (; plist && mplist_key (plist) == Mplist; plist = mplist_next (plist))
    {
      GtkTreeIter iter;
      MPlist *pl;

      pl = mplist_value (plist);
      /* pl == (command/variable description status data ...) */
      control->item = mplist_value (pl);
      gtk_list_store_append (store, &iter);
      set_list_element (store, &iter, control, pl);
    }

  view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
  g_object_unref (G_OBJECT (store));
  gtk_tree_view_insert_column_with_attributes
    (GTK_TREE_VIEW (view), -1, _("Name"), gtk_cell_renderer_text_new (),
     "text", CONFIG_COL_ITEM, NULL);
  gtk_tree_view_insert_column_with_attributes
    (GTK_TREE_VIEW (view), -1, _("Status"), gtk_cell_renderer_text_new (),
     "text", CONFIG_COL_STATUS, NULL);
  gtk_tree_view_insert_column_with_attributes
    (GTK_TREE_VIEW (view), -1, control->data_type_name,
     gtk_cell_renderer_text_new (),
     "text", CONFIG_COL_DATA, NULL);
  g_signal_connect (G_OBJECT (view), "row-activated",
                G_CALLBACK (item_activated_cb), control);

  return view;
}

static struct VariableControl var;
static struct CommandControl cmd;

static void
config_im (GtkTreeView *tree, MSymbol lang, MSymbol name)
{
  GtkWidget *dialog, *notebook, *scrolled, *vbox, *label;

  var.control.lang = cmd.control.lang = lang;
  var.control.name = cmd.control.name = name;
  var.control.config_on_ok = cmd.control.config_on_ok = NULL;

  dialog = (gtk_dialog_new_with_buttons
          (name == Mnil ? "global" : msymbol_name (name),
           GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tree))),
           GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
           GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
           NULL));
  gtk_widget_set_size_request (dialog, 500, 300);
  gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);

  vbox = gtk_vbox_new (FALSE, 0);
  gtk_container_set_border_width (GTK_CONTAINER(vbox), 5);
  gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), vbox);

  notebook = gtk_notebook_new ();
  gtk_container_add (GTK_CONTAINER (vbox), notebook);

  /* Variables' page */
  scrolled = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
                          GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  label = gtk_label_new_with_mnemonic (_("_Variables"));
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled, label);
  vbox = gtk_vbox_new (FALSE, 0);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
                               vbox);
  gtk_box_pack_start (GTK_BOX (vbox),
                  create_item_list (lang, name, CONFIG_CONTROL (&var)),
                  FALSE, FALSE, 0);

  /* Commands' pages */
  scrolled = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
                          GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  label = gtk_label_new_with_mnemonic (_("Co_mmands"));
  gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled, label);
  vbox = gtk_vbox_new (FALSE, 0);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
                               vbox);
  gtk_box_pack_start (GTK_BOX (vbox),
                  create_item_list (lang, name, CONFIG_CONTROL (&cmd)),
                  FALSE, FALSE, 0);

  gtk_widget_show_all (dialog);
  gtk_dialog_run (GTK_DIALOG (dialog));
  gtk_widget_destroy (dialog);
}


/* Staffs for variable configuration.  */

void
variable_update_data (struct ConfigControl *control)
{
  MPlist *plist;
  MSymbol key;
  void *value;

  plist = CURRENT_DATA;
  /* plist == (value [valid-value ...]) */
  key = mplist_key (plist);
  value = mplist_value (plist);

  if (VARIABLE_CONTROL (control)->wtype == ENTRY_WIDGET)
    {
      if (key == Msymbol)
      gtk_entry_set_text (GTK_ENTRY (control->data),
                      msymbol_name ((MSymbol) value));
      else if (key == Mtext)        
      /* Fixme : Assuming the return value is in UTF-8 */
      gtk_entry_set_text (GTK_ENTRY (control->data),
                      mtext_data ((MText *) value,
                              NULL, NULL, NULL, NULL));
      else              /* key == Minteger */
      {
        gchar buf[32];
        g_snprintf (buf, sizeof (buf), "%d", (gint) value);
        gtk_entry_set_text (GTK_ENTRY (control->data), buf);
      }
    }
  else if (VARIABLE_CONTROL (control)->wtype == COMBO_BOX_WIDGET)
    {
      gint i;

      for (i = 0, plist = mplist_next (plist);
         plist && mplist_key (plist) == key;
         i++, plist = mplist_next (plist))
      if (mplist_value (plist) == value)
        break;
      gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), i);
    }
  else                        /* ci->wtype == SPIN_BUTTON_WIDGET */
    gtk_spin_button_set_value (GTK_SPIN_BUTTON (control->data),
                         (gdouble) (int) value);
}

static gboolean
config_with_entry (struct ConfigControl *control)
{
  const gchar *text = gtk_entry_get_text (GTK_ENTRY (control->data));
  MPlist *plist = mplist ();
  gboolean ret = TRUE;

  if (VARIABLE_CONTROL (control)->vtype == Msymbol)
    {
      mplist_add (plist, Msymbol, msymbol (text));
      CONFIG_DATA (plist);
    }
  else if (VARIABLE_CONTROL (control)->vtype == Mtext)
    {
      MText *mt;

      mt = mconv_decode_buffer (Mcoding_utf_8, (guchar *) text, strlen (text));
      mplist_add (plist, Mtext, mt);
      CONFIG_DATA (plist);
      m17n_object_unref (mt);
    }
  else               /* VARIABLE_CONTROL (control)->vtype == Minteger */
    {
      int i;

      if (sscanf (text, "%d", &i) == 1)
      {
        mplist_add (plist, Minteger, (void *) i);
        CONFIG_DATA (plist);
      }
      else
      {
        GtkWidget *msg;

        msg = gtk_message_dialog_new (GTK_WINDOW
                              (gtk_widget_get_toplevel (control->data)),
                              GTK_DIALOG_DESTROY_WITH_PARENT,
                              GTK_MESSAGE_ERROR,
                              GTK_BUTTONS_CLOSE,
                              _("The value must be an integer."));
        gtk_dialog_run (GTK_DIALOG (msg));
        gtk_widget_destroy (msg);
        ret = FALSE;
      }
    }

  m17n_object_unref (plist);
  return ret;
}

static gboolean
config_with_combo (struct ConfigControl *control)
{
  gchar *text = gtk_combo_box_get_active_text (GTK_COMBO_BOX (control->data));
  MPlist *plist = mplist ();

  if (VARIABLE_CONTROL (control)->vtype == Msymbol)
    {
      mplist_add (plist, Msymbol, msymbol (text));
      CONFIG_DATA (plist);
    }
  else if (VARIABLE_CONTROL (control)->vtype == Mtext)
    {
      MText *mt;

      mt = mconv_decode_buffer (Mcoding_utf_8, (guchar *) text, strlen (text));
      mplist_add (plist, Mtext, mt);
      CONFIG_DATA (plist);
      m17n_object_unref (mt);
    }
  else               /* VARIABLE_CONTROL (control)->vtype == Minteger */
    {
      int i;

      sscanf (text, "%d", &i);
      mplist_add (plist, Minteger, (void *) i);
      CONFIG_DATA (plist);
    }
  m17n_object_unref (plist);
  return TRUE;
}

static gboolean
config_with_spin (struct ConfigControl *control)
{
  gint i = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (control->data));
  MPlist *plist = mplist ();

  mplist_add (plist, Minteger, (void *) i);
  CONFIG_DATA (plist);
  m17n_object_unref (plist);
  return TRUE;
}

static void
changed_cb (GtkEntry *entry, gpointer data)
{
  struct ConfigControl *control = data;

  gtk_widget_set_sensitive (control->default_, TRUE);
  gtk_widget_set_sensitive (control->revert, TRUE);
  gtk_label_set_text (GTK_LABEL (control->status), _("modified"));
  if (VARIABLE_CONTROL (control)->wtype == ENTRY_WIDGET)
    control->config_on_ok = config_with_entry;
  else if (VARIABLE_CONTROL (control)->wtype == COMBO_BOX_WIDGET)
    control->config_on_ok = config_with_combo;
  else
    control->config_on_ok = config_with_spin;
}

static GString *
variable_data_string (MPlist *plist)
{
  static GString *str;

  if (! str)
    str = g_string_sized_new (80);  
  else
    g_string_truncate (str, 0);

  if (mplist_key (plist) == Msymbol)
    g_string_append (str, msymbol_name ((MSymbol) mplist_value (plist)));
  else if (mplist_key (plist) == Mtext)
    /* Fixme : Assuming the return value is in UTF-8 */
    g_string_append (str, mtext_data ((MText *) mplist_value (plist),
                              NULL, NULL, NULL, NULL));
  else /* mplist_key (plist) == Minteger */
    g_string_append_printf (str, "%d", (gint) mplist_value (plist));
  return str;
}

static void
variable_setup_dialog (GtkWidget *dialog, struct ConfigControl *control)
{
  MPlist *plist; 
  void *value;
  GtkWidget *hbox, *vbox;

  plist = CURRENT_DATA;
  VARIABLE_CONTROL (control)->vtype = mplist_key (plist);
  value = mplist_value (plist);
  plist = mplist_next (plist);

  if (VARIABLE_CONTROL (control)->vtype == Msymbol)
    {
      if (mplist_key (plist) == Msymbol)
      {
        gint i, nth;

        control->data = gtk_combo_box_new_text ();
        VARIABLE_CONTROL (control)->wtype = COMBO_BOX_WIDGET;
        for (i = 0; mplist_key (plist) == Msymbol;
             plist = mplist_next (plist), i++)
          {
            if (mplist_value (plist) == value)
            nth = i;
            gtk_combo_box_append_text
            (GTK_COMBO_BOX (control->data),
             msymbol_name ((MSymbol) mplist_value (plist)));
          }
        gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), nth);
        g_signal_connect (GTK_OBJECT (control->data), "changed",
                      G_CALLBACK (changed_cb), control);
      }
      else
      {
        control->data = gtk_entry_new ();
        VARIABLE_CONTROL (control)->wtype = ENTRY_WIDGET;
        gtk_entry_set_text (GTK_ENTRY (control->data), msymbol_name (value));
        gtk_editable_set_editable (GTK_EDITABLE (control->data), TRUE);
        g_signal_connect (GTK_OBJECT (control->data), "changed",
                      G_CALLBACK (changed_cb), control);
        g_signal_connect (GTK_OBJECT (control->data), "activate",
                      G_CALLBACK (ok_cb), control);
      }
    }
  else if (VARIABLE_CONTROL (control)->vtype == Mtext)
    {
      if (plist && mplist_key (plist) == Mtext)
      {
        gint i, nth;

        control->data = gtk_combo_box_new_text ();
        VARIABLE_CONTROL (control)->wtype = COMBO_BOX_WIDGET;
        for (i = 0; plist && mplist_key (plist) == Mtext;
             plist = mplist_next (plist), i++)
          {
            if (! mtext_cmp ((MText *) mplist_value (plist),
                         (MText *) value))
            nth = i;
            /* Fixme : Assuming the return value is in UTF-8 */
            gtk_combo_box_append_text
            (GTK_COMBO_BOX (control->data),
             mtext_data ((MText *) mplist_value (plist),
                       NULL, NULL, NULL, NULL));
          }
        gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), nth);
        g_signal_connect (GTK_OBJECT (control->data), "changed",
                      G_CALLBACK (changed_cb), control);
      }
      else
      {
        control->data = gtk_entry_new ();
        VARIABLE_CONTROL (control)->wtype = ENTRY_WIDGET;
        /* Fixme : Assuming the return value is in UTF-8 */
        gtk_entry_set_text (GTK_ENTRY (control->data),
                        mtext_data (value, NULL, NULL, NULL, NULL));
        gtk_editable_set_editable (GTK_EDITABLE (control->data), TRUE);
        g_signal_connect (GTK_OBJECT (control->data), "changed",
                      G_CALLBACK (changed_cb), control);
        g_signal_connect (GTK_OBJECT (control->data), "activate",
                      G_CALLBACK (ok_cb), control);
      }
    }
  else                        /* control->vtype == Minteger */
    {
      if (plist && mplist_key (plist) == Minteger)
      {
        gint i, nth;

        control->data = gtk_combo_box_new_text ();
        VARIABLE_CONTROL (control)->wtype = COMBO_BOX_WIDGET;
        for (i = 0; plist && mplist_key (plist) == Minteger;
             plist = mplist_next (plist), i++)
          {
            gchar buf[32];

            if (mplist_value (plist) == value)
            nth = i;
            g_snprintf (buf, sizeof (buf), "%d",
                    (gint) mplist_value (plist));
            gtk_combo_box_append_text (GTK_COMBO_BOX (control->data), buf);
          }
        gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), nth);
        g_signal_connect (GTK_OBJECT (control->data), "changed",
                      G_CALLBACK (changed_cb), control);
      }
      else if (plist && mplist_key (plist) == Mplist)
      {
        GtkObject *adj;
        gdouble lower, upper;

        plist = mplist_value (plist);
        lower = (gdouble) (int) mplist_value (plist);
        upper = (gdouble) (int) mplist_value (mplist_next (plist));
        adj = gtk_adjustment_new ((gdouble) (int) value, lower, upper,
                            1.0, 10.0, 0);
        control->data = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 0, 0);
        VARIABLE_CONTROL (control)->wtype = SPIN_BUTTON_WIDGET;
        gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (control->data), TRUE);
        gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (control->data),
                                   GTK_UPDATE_ALWAYS);
        g_signal_connect (GTK_OBJECT (control->data), "changed",
                      G_CALLBACK (changed_cb), control);
      }
      else
      {
        gchar buf[32];

        control->data = gtk_entry_new ();
        VARIABLE_CONTROL (control)->wtype = ENTRY_WIDGET;
        g_snprintf (buf, sizeof (buf), "%d", (gint) value);
        gtk_entry_set_text (GTK_ENTRY (control->data), buf);
        gtk_editable_set_editable (GTK_EDITABLE (control->data), TRUE);
        g_signal_connect (GTK_OBJECT (control->data), "changed",
                      G_CALLBACK (changed_cb), control);
        g_signal_connect (GTK_OBJECT (control->data), "activate",
                      G_CALLBACK (ok_cb), control);
      }
    }

  control->default_ = gtk_button_new_from_stock (_("_Default"));
  g_signal_connect (G_OBJECT (control->default_), "clicked",
                G_CALLBACK (default_cb), control);

  control->revert = gtk_button_new_from_stock (GTK_STOCK_REVERT_TO_SAVED);
  g_signal_connect (G_OBJECT (control->revert), "clicked",
                G_CALLBACK (revert_cb), control);

  hbox = gtk_hbutton_box_new ();
  gtk_box_set_spacing (GTK_BOX (hbox), 6);
  gtk_container_add (GTK_CONTAINER (hbox), control->default_);
  gtk_container_add (GTK_CONTAINER (hbox), control->revert);
  vbox = gtk_vbox_new (FALSE, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
  gtk_box_pack_start (GTK_BOX (vbox), control->data, FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
                  vbox, FALSE, FALSE, 0);
  gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 6);
}


/* Staffs for command configuration.  */

static void
selection_cb (GtkTreeSelection *selection, gpointer data)
{
  gtk_widget_set_sensitive
    (COMMAND_CONTROL (data)->delete,
     gtk_tree_selection_count_selected_rows (selection) ? TRUE : FALSE);
}

static void
delete_cb (GtkButton *button, gpointer data)
{
  GtkTreeSelection *selection;
  GtkTreeModel *model;
  GtkTreeIter iter;
  MPlist *pl, *new;
  struct ConfigControl *control = data;

  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (control->data));
  model = gtk_tree_view_get_model (GTK_TREE_VIEW (control->data));

  if (! gtk_tree_model_get_iter_first (model, &iter))
    return;

  new = mplist ();
  for (pl = CURRENT_DATA; mplist_key (pl) != Mnil; pl = mplist_next (pl))
    {
      if (! gtk_tree_selection_iter_is_selected (selection, &iter))
      mplist_add (new, Mplist, mplist_value (pl));
      gtk_tree_model_iter_next (model, &iter);
    }
  CONFIG_DATA (new);
  m17n_object_unref (new);
  control->update_data (control);
  update_status (control);
}

static GtkWidget *
create_deleting_section (struct ConfigControl *control)
{
  struct CommandControl *cmd_control = COMMAND_CONTROL (control);
  GtkListStore *store;
  GtkWidget *label, *scrolled, *hbox, *vbox;
  GtkTreeViewColumn *column;
  GtkCellRenderer *renderer;
  GtkTreeSelection *selection;

  label = gtk_label_new (_("Current key bindings:"));

  store = gtk_list_store_new (1, G_TYPE_STRING);
  control->data = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
  g_object_unref (G_OBJECT (store));
  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (control->data), FALSE);
  selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (control->data));
  gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
  g_signal_connect (G_OBJECT (selection), "changed",
                G_CALLBACK (selection_cb), control);

  scrolled = gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
                          GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
                               control->data);

  column = gtk_tree_view_column_new ();
  gtk_tree_view_append_column (GTK_TREE_VIEW (control->data), column);
  renderer = gtk_cell_renderer_text_new ();
  gtk_tree_view_column_pack_start (column, renderer, TRUE);
  gtk_tree_view_column_set_attributes (column, renderer, "text", 0, NULL);

  control->update_data (control);

  control->default_ = gtk_button_new_from_stock (_("_Default"));
  g_signal_connect (G_OBJECT (control->default_), "clicked",
                G_CALLBACK (default_cb), control);

  control->revert = gtk_button_new_from_stock (GTK_STOCK_REVERT_TO_SAVED);
  g_signal_connect (G_OBJECT (control->revert), "clicked",
                G_CALLBACK (revert_cb), control);

  cmd_control->delete = gtk_button_new_from_stock (GTK_STOCK_DELETE);
  gtk_widget_set_sensitive (cmd_control->delete, FALSE);
  g_signal_connect (G_OBJECT (cmd_control->delete), "clicked",
                G_CALLBACK (delete_cb), control);

  vbox = gtk_vbox_new (FALSE, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);

  hbox = gtk_hbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  gtk_container_add (GTK_CONTAINER (vbox), hbox);

  gtk_container_add (GTK_CONTAINER (vbox), scrolled);

  hbox = gtk_hbutton_box_new ();
  gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
  gtk_box_set_spacing (GTK_BOX (hbox), 6);
  gtk_container_add (GTK_CONTAINER (hbox), control->default_);
  gtk_container_add (GTK_CONTAINER (hbox), control->revert);
  gtk_container_add (GTK_CONTAINER (hbox), cmd_control->delete);
  gtk_container_add (GTK_CONTAINER (vbox), hbox);

  return vbox;
}

static unsigned modifier_state = 0;

enum KeyMaskBit {
  META_MASK_BIT = 1,
  ALT_MASK_BIT = META_MASK_BIT << 1,
  SUPER_MASK_BIT = ALT_MASK_BIT << 1,
  HYPER_MASK_BIT = SUPER_MASK_BIT << 1
};

static void
update_entry (GtkEntry *entry)
{
  if (mplist_key (entry_keyseq) == Mnil)
    gtk_entry_set_text (entry, "");
  else
    {
      MPlist *p;
      gchar *name;

      name = msymbol_name ((MSymbol) mplist_value (entry_keyseq));
      gtk_entry_set_text (entry, name);
      for (p = mplist_next (entry_keyseq); mplist_key (p) != Mnil;
         p = mplist_next (p))
      {
        name = msymbol_name ((MSymbol) mplist_value (p));
        gtk_entry_append_text (entry, " ");
        gtk_entry_append_text (entry, name);
      }
      gtk_editable_set_position (GTK_EDITABLE (entry), -1);
    }
}

static gboolean
key_pressed_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
{
  guint c;
  MText *mt;
  char buf[32];
  char *name;
  int nbytes, i;
  struct CommandControl *cmd_control = data;

  c = gdk_keyval_to_unicode (event->keyval);
  if (c == 0)
    {
      switch (event->keyval)
      {
      case GDK_Meta_L: case GDK_Meta_R:
        modifier_state |= META_MASK_BIT; return TRUE;
      case GDK_Alt_L: case GDK_Alt_R:
        modifier_state |= ALT_MASK_BIT; return TRUE;
      case GDK_Super_L: case GDK_Super_R:
        modifier_state |= SUPER_MASK_BIT; return TRUE;
      case GDK_Hyper_L: case GDK_Hyper_R:
        modifier_state |= HYPER_MASK_BIT; return TRUE;
      default:
        if (event->keyval >= GDK_Shift_L && event->keyval <= GDK_Shift_Lock)
          return TRUE;
      }
      name = gdk_keyval_name (event->keyval);
      if (! name)
      return TRUE;
      nbytes = strlen (name);
    }
  else
    {
      name = alloca (8);
      mt = mtext ();
      mtext_cat_char (mt, c);
      nbytes = mconv_encode_buffer (msymbol ("utf-8"), mt,
                            (unsigned char *) name, 32);
      m17n_object_unref (mt);
    }
  i = 0;
  if (c == 0 && event->state & GDK_SHIFT_MASK)
    buf[i++] = 'S', buf[i++] = '-';
  if (event->state & GDK_CONTROL_MASK)
    buf[i++] = 'C', buf[i++] = '-';
  if (modifier_state & META_MASK_BIT)
    buf[i++] = 'M', buf[i++] = '-';
  if (modifier_state & ALT_MASK_BIT)
    buf[i++] = 'A', buf[i++] = '-';
  if (modifier_state & SUPER_MASK_BIT)
    buf[i++] = 's', buf[i++] = '-';
  if (modifier_state & HYPER_MASK_BIT)
    buf[i++] = 'H', buf[i++] = '-';
  strncpy (buf + i, name, nbytes);
  buf[i + nbytes] = 0;
  mplist_add (entry_keyseq, Msymbol, msymbol (buf));
  update_entry (entry);
  gtk_widget_set_sensitive (cmd_control->clear, TRUE);
  gtk_widget_set_sensitive (cmd_control->add, TRUE);
  return TRUE;
}

static gboolean
key_released_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
{
  guint c;

  c = gdk_keyval_to_unicode (event->keyval);
  if (c == 0)
    {
      switch (event->keyval)
      {
      case GDK_Meta_L: case GDK_Meta_R:
        modifier_state &= ~META_MASK_BIT; break;
      case GDK_Alt_L: case GDK_Alt_R:
        modifier_state &= ~ALT_MASK_BIT; break;
      case GDK_Super_L: case GDK_Super_R:
        modifier_state &= ~SUPER_MASK_BIT; break;
      case GDK_Hyper_L: case GDK_Hyper_R:
        modifier_state &= ~HYPER_MASK_BIT; break;
      }
    }
  return FALSE;
}

static void
clear_cb (GtkButton *button, gpointer data)
{
  struct CommandControl *cmd_control = data;

  mplist_set (entry_keyseq, Mnil, NULL);
  gtk_widget_grab_focus (cmd_control->entry);
  update_entry (GTK_ENTRY (cmd_control->entry));
  gtk_widget_set_sensitive (cmd_control->clear, FALSE);
  gtk_widget_set_sensitive (cmd_control->add, FALSE);
}

static void
add_cb (GtkButton *button, gpointer data)
{
  MPlist *new;
  GtkTreeModel *model;
  GtkTreeIter iter;
  struct ConfigControl *control = data;

  if (mplist_length (entry_keyseq) == 0)
    return;
  model = gtk_tree_view_get_model (GTK_TREE_VIEW (control->data));
  if (gtk_tree_model_get_iter_first (model, &iter))
    {
      gchar *keyseq = control->data_string (entry_keyseq)->str;
      gchar *str;

      do {
      gtk_tree_model_get (model, &iter, 0, &str, -1);
      if (strcmp (keyseq, str) == 0)
        /* entry_keyseq is already registered. */
        return;
      } while (gtk_tree_model_iter_next (model, &iter));
    }
  new = mplist_copy (CURRENT_DATA);
  mplist_add (new, Mplist, entry_keyseq);
  CONFIG_DATA (new);
  m17n_object_unref (new);
  control->update_data (control);
  update_status (control);
  clear_cb (NULL, control);
}

static GtkWidget *
create_adding_section (struct ConfigControl *control)
{
  struct CommandControl *cmd_control = COMMAND_CONTROL (control);
  GtkWidget *label, *hbox, *vbox;

  label = gtk_label_new (_("New key binding:"));

  entry_keyseq = mplist ();
  cmd_control->entry = gtk_entry_new ();
  g_signal_connect (G_OBJECT (cmd_control->entry), "key-press-event",
                G_CALLBACK (key_pressed_cb), cmd_control);
  g_signal_connect (G_OBJECT (cmd_control->entry), "key-release-event",
                G_CALLBACK (key_released_cb), cmd_control);

  cmd_control->clear = gtk_button_new_from_stock (GTK_STOCK_CLEAR);
  gtk_widget_set_sensitive (cmd_control->clear, FALSE);
  g_signal_connect (G_OBJECT (cmd_control->clear), "clicked",
                G_CALLBACK (clear_cb), cmd_control);

  cmd_control->add = gtk_button_new_from_stock (GTK_STOCK_ADD);
  gtk_widget_set_sensitive (cmd_control->add, FALSE);
  g_signal_connect (G_OBJECT (cmd_control->add), "clicked",
                G_CALLBACK (add_cb), cmd_control);

  vbox = gtk_vbox_new (FALSE, 12);
  gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);

  hbox = gtk_hbox_new (FALSE, 6);
  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
  gtk_container_add (GTK_CONTAINER (vbox), hbox);

  gtk_container_add (GTK_CONTAINER (vbox), cmd_control->entry);

  hbox = gtk_hbutton_box_new ();
  gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
  gtk_box_set_spacing (GTK_BOX (hbox), 6);
  gtk_container_add (GTK_CONTAINER (hbox), cmd_control->clear);
  gtk_container_add (GTK_CONTAINER (hbox), cmd_control->add);
  gtk_container_add (GTK_CONTAINER (vbox), hbox);

  return vbox;
}

static void
append_key_sequence (GString *str, MPlist *keyseq)
{
  static MSymbol space_symbol;
  MPlist *p;

  if (! space_symbol)
    space_symbol = msymbol (" ");

  for (p = keyseq ; mplist_key (p) != Mnil; p = mplist_next (p))
    {
      MSymbol key = (MSymbol) mplist_value (p);

      if (p != keyseq)
      g_string_append_c (str, ' ');
      if (key == space_symbol)
      g_string_append (str, "Space");
      else
      g_string_append (str, msymbol_name (key));
    }
}

static GString *
command_data_string (MPlist *plist)
{
  static GString *str;

  if (! str)
    str = g_string_sized_new (80);  
  else
    g_string_truncate (str, 0);

  if (mplist_key (plist) == Mplist)
    {
      MPlist *pl;

      /* PLIST == ((KEY KEY ...) ...) */
      for (pl = plist; mplist_key (pl) != Mnil; pl = mplist_next (pl))
      {
        if (pl != plist)
          g_string_append (str, ", ");
        append_key_sequence (str, mplist_value (pl));
      }
    }
  else
    {
      /* PLIST == (KEY KEY ...) */
      append_key_sequence (str, plist);
    }
  return str;
}

static void
command_update_data (struct ConfigControl *control)
{
  GtkTreeView *tree = GTK_TREE_VIEW (control->data);
  GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (tree));
  GtkTreeIter iter;
  MPlist *pl;

  gtk_list_store_clear (store);
  for (pl = CURRENT_DATA; mplist_key (pl) != Mnil; pl = mplist_next (pl))
    {
      gtk_list_store_append (store, &iter);
      gtk_list_store_set (store, &iter,
                    0, control->data_string (mplist_value (pl))->str,
                    -1);
    }
}

static void
command_setup_dialog (GtkWidget *dialog, struct ConfigControl *control)
{
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
                  create_deleting_section (control), FALSE, FALSE, 0);
  gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
                  create_adding_section (control), FALSE, FALSE, 0);
  gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 6);
}


/* Public API */

GtkWidget *
mim_config_new (GCallback func, gpointer data)
{
  GtkWidget *tree, *config;
  GtkTreeStore *store;
  GtkCellRenderer *renderer;
  GtkTreeViewColumn *column;

  if (initialized)
    return NULL;
  M17N_INIT ();
  if (merror_code < 0)
    return NULL;

  initialized = 1;

#if ENABLE_NLS
  bindtextdomain ("m17n-im-config", GETTEXTDIR);
  bind_textdomain_codeset ("m17n-im-config", "UTF-8");
#endif

  mim_status_str[MIM_STATUS_DEFAULT] = _("default");
  mim_status_str[MIM_STATUS_CUSTOMIZED] = _("customized");
  mim_status_str[MIM_STATUS_MODIFIED] = _("modified");
  mim_status_str[MIM_STATUS_NO] = _("uncustomizable");

  var.control.data_type_name = _("Value");
  var.control.setup_dialog = variable_setup_dialog;
  var.control.update_data = variable_update_data;
  var.control.data_string = variable_data_string;
  var.control.get = minput_get_variable;
  var.control.config = minput_config_variable;

  cmd.control.data_type_name = _("Key Bindings");
  cmd.control.setup_dialog = command_setup_dialog;
  cmd.control.update_data = command_update_data;
  cmd.control.data_string = command_data_string;
  cmd.control.get = minput_get_command;
  cmd.control.config = minput_config_command;

  store = make_store_for_input_methods ();
  tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
  g_object_unref (G_OBJECT (store));

  renderer = gtk_cell_renderer_text_new ();
  column = (gtk_tree_view_column_new_with_attributes
          (_("Input Method"), renderer, "text", COL_TAG, NULL));
  gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);

  renderer = gtk_cell_renderer_text_new ();
  column = (gtk_tree_view_column_new_with_attributes
          (_("Status"), renderer, "text", COL_STATUS_STR, NULL));
  gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);

  g_signal_connect (G_OBJECT (tree), "row-expanded",
                G_CALLBACK (tree_expanded_cb), NULL);
  g_signal_connect (G_OBJECT (tree), "row-activated",
                G_CALLBACK (tree_activated_cb), NULL);

  config =gtk_scrolled_window_new (NULL, NULL);
  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (config),
                          GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (config), tree);
  g_signal_connect (G_OBJECT (config), "destroy",
                G_CALLBACK (destroy_cb), NULL);

  g_object_set_data (G_OBJECT (config), CONFIG_TREE_VIEW, tree);
  if (func)
    {
      MimConfigCallback *callback;

      callback = g_new (MimConfigCallback, 1);
      callback->widget = config;
      callback->func = (MimConfigCallbackFunc) func;
      callback->data = data;
      g_object_set_data_full (G_OBJECT (tree), CONFIG_CALLBACK_DATA,
                        callback, g_free);
    }

  return config;
}

gboolean
mim_config_modified (GtkWidget *config)
{
  GtkTreeView *tree;
  GtkTreeModel *model;
  MimConfigStatus *config_status;

  tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
  model = gtk_tree_view_get_model (tree);
  config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);

  return (config_status->num_modified > 0 ? TRUE : FALSE);
}

gboolean
mim_config_default (GtkWidget *config)
{
  GtkTreeView *tree;
  GtkTreeModel *model;
  MimConfigStatus *config_status;

  tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
  model = gtk_tree_view_get_model (tree);
  config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
  gtk_tree_model_foreach (model, reset_to_default, config_status);
  return TRUE;
}

gboolean
mim_config_revert (GtkWidget *config)
{
  GtkTreeView *tree;
  GtkTreeModel *model;
  MimConfigStatus *config_status;

  tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
  model = gtk_tree_view_get_model (tree);
  config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);

  if (config_status->num_modified == 0)
    return FALSE;
  gtk_tree_model_foreach (model, revert_to_saved, config_status);
  return TRUE;
}

gboolean
mim_config_save (GtkWidget *config)
{
  GtkTreeView *tree;
  GtkTreeModel *model;
  MimConfigStatus *config_status;

  tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
  model = gtk_tree_view_get_model (tree);
  config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);

  if (config_status->num_modified == 0)
    return FALSE;
  minput_save_config ();
  gtk_tree_model_foreach (model, set_as_saved, config_status);
  return TRUE;
}

GtkTreeView *
mim_config_get_tree_view (GtkWidget *config)
{
  GtkTreeView *tree;

  tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
  return tree;
}

Generated by  Doxygen 1.6.0   Back to index