42c7cf3b45a7ac94c3700700f3db5d0f9292f458
[m17n/m17n-im-config.git] / src / mim-config.c
1 /* mim-config.c -- M17N input method configuration
2    Copyright (C) 2007
3      National Institute of Advanced Industrial Science and Technology (AIST)
4      Registration Number H15PRO112
5
6    This file is part of the m17n-im-config package; a sub-part of the
7    m17n library.
8
9    The m17n library is free software; you can redistribute it and/or
10    modify it under the terms of the GNU Lesser General Public License
11    as published by the Free Software Foundation; either version 2.1 of
12    the License, or (at your option) any later version.
13
14    The m17n library is distributed in the hope that it will be useful,
15    but WITHOUT ANY WARRANTY; without even the implied warranty of
16    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17    Lesser General Public License for more details.
18
19    You should have received a copy of the GNU Lesser General Public
20    License along with the m17n library; if not, write to the Free
21    Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22    02111-1307, USA.  */
23
24 #include <stdlib.h>
25 #include <string.h>
26 #include <libintl.h>
27 #include <locale.h>
28 #include <m17n.h>
29 #include <m17n-misc.h>
30 #include <gtk/gtk.h>
31 #include <gdk/gdk.h>
32 #include <gdk/gdkkeysyms.h>
33 #include <config.h>
34 #include "m17n-im-config.h"
35
36 #define _(String) dgettext (PACKAGE, String)
37
38 #define CONFIG_CALLBACK_DATA " config-callback-data"
39 #define CONFIG_STATUS_DATA " config-status-data"
40 #define CONFIG_TREE_VIEW " config-tree-view"
41
42 typedef void (*MimConfigCallbackFunc) (GtkWidget *widget, gpointer data);
43
44 typedef struct _MimConfigCallback
45 {
46   GtkWidget *widget;
47   MimConfigCallbackFunc func;
48   gpointer data;
49 } MimConfigCallback;
50
51 typedef struct _MimConfigStatus
52 {
53   /* Number of available input methods.  */
54   gint num_im;
55   /* Number of modified input methods.  */
56   gint num_modified;
57 } MimConfigStatus;
58
59 /* Status of variables and commands of an input method.  */
60
61 enum MimStatus
62   {
63     MIM_STATUS_DEFAULT,
64     MIM_STATUS_CUSTOMIZED,
65     MIM_STATUS_MODIFIED,
66     MIM_STATUS_NO,
67     MIM_STATUS_MAX
68   };
69
70 static char *mim_status_str[MIM_STATUS_MAX];
71
72 enum MimStatus
73 get_mim_status (MSymbol lang, MSymbol name)
74 {
75   MPlist *plist;
76   enum MimStatus status = MIM_STATUS_NO;
77
78   for (plist = minput_get_variable (lang, name, Mnil);
79        plist && mplist_key (plist) != Mnil; plist = mplist_next (plist))
80     {
81       MPlist *p = mplist_value (plist);
82       MSymbol status_symbol;
83
84       p = mplist_next (mplist_next (p));
85       status_symbol = mplist_value (p);
86       if (status_symbol == Mconfigured)
87         return MIM_STATUS_MODIFIED;
88       if (status_symbol == Mcustomized)
89         status = MIM_STATUS_CUSTOMIZED;
90       else if (status == MIM_STATUS_NO)
91         status = MIM_STATUS_DEFAULT;
92     }
93   for (plist = minput_get_command (lang, name, Mnil);
94        plist && mplist_key (plist) != Mnil; plist = mplist_next (plist))
95     {
96       MPlist *p = mplist_value (plist);
97       MSymbol status_symbol;
98
99       p = mplist_next (mplist_next (p));
100       status_symbol = mplist_value (p);
101       if (status_symbol == Mconfigured)
102         return MIM_STATUS_MODIFIED;
103       if (status_symbol == Mcustomized)
104         status = MIM_STATUS_CUSTOMIZED;
105       else if (status == MIM_STATUS_NO)
106         status = MIM_STATUS_DEFAULT;
107     }
108   return status;
109 }
110
111 /* Columns of each row.  */
112 enum
113   {
114     /* parent: language name
115         child: IM name  */
116     COL_TAG = 0,
117     /* parent: NULL or "modified"
118         child: "default", "customized", or "modified"  */
119     COL_STATUS_STR,
120     /* parent: num of modified children
121         child: enum MimStatus */
122     COL_STATUS,
123     /* parent: Mnil
124         child: symbolic language name.  */
125     COL_LANG,
126     /* parent: Mnil
127         child: symbolic IM name.  */
128     COL_NAME,
129     /* number of columns  */
130     NUM_COLS
131   };
132
133 /* Called when a row is expanded.  We may have to initialize
134    children.  */
135 static void
136 tree_expanded_cb (GtkTreeView *tree, GtkTreeIter *parent,
137                   GtkTreePath *path, gpointer data)
138 {
139   GtkTreeModel *model;
140   GtkTreeIter iter;
141   MSymbol lang, name;
142
143   model = gtk_tree_view_get_model (tree);
144   if (gtk_tree_model_iter_children (model, &iter, parent))
145     {
146       gchar *status_str;
147
148       gtk_tree_model_get (model, &iter, COL_STATUS_STR, &status_str, -1);
149       if (! status_str)
150         {
151           /* The first child is not yet initialized, and that means
152              the remaining children are not initialized either.  */
153           gtk_tree_model_get (model, &iter, COL_LANG, &lang, -1);
154           do {
155             enum MimStatus im_status;
156
157             gtk_tree_model_get (model, &iter, COL_NAME, &name, -1);
158             im_status = get_mim_status (lang, name);
159             gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
160                                 COL_STATUS_STR, mim_status_str[im_status],
161                                 COL_STATUS, im_status,
162                                 -1);
163           } while (gtk_tree_model_iter_next (model, &iter));
164         }
165     }
166 }
167
168 static void config_im (GtkTreeView *tree, MSymbol lang, MSymbol name);
169
170 static void
171 update_child_row (GtkTreeModel *model, GtkTreeIter *iter,
172                   enum MimStatus status, MimConfigStatus *config_status,
173                   GtkTreeView *tree)
174 {
175   GtkTreeIter parent;
176   gint inc_modified;
177
178   inc_modified = (status == MIM_STATUS_MODIFIED ? 1 : -1);
179
180   gtk_tree_store_set (GTK_TREE_STORE (model), iter,
181                       COL_STATUS_STR, mim_status_str[status],
182                       COL_STATUS, status, -1);
183   if (gtk_tree_model_iter_parent (model, &parent, iter))
184     {
185       gint num_modified;
186       gchar *status_str;
187
188       gtk_tree_model_get (model, &parent, COL_STATUS, &num_modified, -1);
189       num_modified += inc_modified;
190       gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
191                           COL_STATUS, num_modified, -1);
192       if (num_modified <= 1)
193         {
194           status_str = (status == MIM_STATUS_MODIFIED
195                         ? mim_status_str[MIM_STATUS_MODIFIED] : NULL);
196           gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
197                               COL_STATUS_STR, status_str);
198         }
199     }
200       
201   if (! config_status)
202     config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
203   config_status->num_modified += inc_modified;
204   if (tree && config_status->num_modified <= 1)
205     {
206       MimConfigCallback *callback;
207
208       callback = g_object_get_data (G_OBJECT (tree), CONFIG_CALLBACK_DATA);
209       if (callback)
210         callback->func (callback->widget, callback->data);
211     }
212 }
213
214 static void
215 tree_activated_cb (GtkTreeView *tree, GtkTreePath *path,
216                    GtkTreeViewColumn *column, gpointer data)
217 {
218   GtkTreeModel *model;
219   GtkTreeIter iter;
220
221   model = gtk_tree_view_get_model (tree);
222   if (gtk_tree_model_get_iter (model, &iter, path))
223     {
224       MSymbol lang, name;
225
226       gtk_tree_model_get (model, &iter, COL_LANG, &lang, COL_NAME, &name, -1);
227       if (lang != Mnil)
228         {
229           /* child row for an IM */
230           enum MimStatus old, new;
231
232           old = get_mim_status (lang, name);
233           config_im (tree, lang, name);
234           new = get_mim_status (lang, name);
235           if (old != new)
236             update_child_row (model, &iter, new, NULL, tree);
237         }
238       else
239         {
240           /* parent row for a language */
241           if (gtk_tree_view_row_expanded (tree, path))
242             gtk_tree_view_collapse_row (tree, path);
243           else
244             gtk_tree_view_expand_row (tree, path, TRUE);
245         }
246     }
247 }
248
249 typedef struct _MimTable
250 {
251   int lang_in_locale;
252   gchar *encoded_lang;
253   gchar *lang;
254   gchar *name;
255   MSymbol symlang;
256   MSymbol symname;
257 } MimTable;
258
259 static int
260 sort_im (const void *p1, const void *p2)
261 {
262   const MimTable *t1 = p1;
263   const MimTable *t2 = p2;
264   int result;
265
266   if (t1->symlang == t2->symlang)
267     result = 0;
268   else
269     {
270       if (t1->lang_in_locale != t2->lang_in_locale)
271         return (t1->lang_in_locale ? -1 : 1);
272       if ((! t1->encoded_lang) != (! t2->encoded_lang))
273         return (t1->encoded_lang ? -1 : 1);
274       if (t1->encoded_lang)
275         result = strcoll (t1->encoded_lang, t2->encoded_lang);
276       else
277         result = strcmp (t1->lang, t2->lang);
278     }
279
280   return (result ? result : strcmp (t1->name, t2->name));
281 }
282
283 static GtkTreeStore *
284 make_store_for_input_methods ()
285 {
286   GtkTreeStore *store;
287   MPlist *imlist, *p;
288   int i;
289   MimTable *imtable;
290   char *lang, *other = _("Other");
291   GtkTreeIter iter1, iter2;
292   enum MimStatus status;
293   MimConfigStatus *config_status;
294   unsigned char conv_buf[256];
295   MConverter *converter;
296   int locale_is_utf8 = 0;
297   static MSymbol Meng;
298
299   if (! Meng)
300     Meng = msymbol ("eng");
301   store = gtk_tree_store_new (NUM_COLS,
302                               G_TYPE_STRING,  /* COL_TAG */
303                               G_TYPE_STRING,  /* COL_STATUS_STR */
304                               G_TYPE_UINT,    /* COL_STATUS */
305                               G_TYPE_POINTER, /* COL_LANG */
306                               G_TYPE_POINTER  /* COL_NAME */
307                               );
308
309   config_status = g_new0 (MimConfigStatus, 1);
310   gtk_tree_store_append (store, &iter1, NULL);
311   status = get_mim_status (Mt, Mnil);
312   gtk_tree_store_set (store, &iter1,
313                       COL_TAG, _("global"),
314                       COL_STATUS_STR, mim_status_str[status],
315                       COL_STATUS, status, 
316                       COL_LANG, Mt,
317                       COL_NAME, Mnil,
318                       -1);
319
320   imlist = mdatabase_list (msymbol ("input-method"), Mnil, Mnil, Mnil);
321   config_status->num_im = mplist_length (imlist);
322   imtable = g_newa (MimTable, config_status->num_im);
323
324   {
325     MLocale *locale = mlocale_set (LC_MESSAGES, NULL);
326     MSymbol coding = locale ? mlocale_get_prop (locale, Mcoding) : Mnil;
327
328     if (coding == Mnil)
329       converter = NULL;
330     else if (coding == msymbol ("utf-8"))
331       {
332         converter = NULL;
333         locale_is_utf8 = 1;
334       }
335     else
336       {
337         converter = mconv_buffer_converter (coding, conv_buf, sizeof conv_buf);
338         if (converter)
339           converter->last_block = 1;
340       }
341   }
342
343   for (i = 0, p = imlist; mplist_key (p) != Mnil; p = mplist_next (p))
344     {
345       MDatabase *mdb = (MDatabase *) mplist_value (p);
346       MSymbol *tag = mdatabase_tag (mdb);
347       MPlist *pl;
348
349       if (tag[1] == Mnil || tag[2] == Mnil)
350         continue;
351
352       imtable[i].symlang = tag[1];
353       imtable[i].symname = tag[2];
354       imtable[i].name = msymbol_name (tag[2]);
355       imtable[i].lang = imtable[i].encoded_lang = NULL;
356       imtable[i].lang_in_locale = 0;
357
358       if (tag[1] != Mt)
359         {
360           pl = mlanguage_name_list (tag[1], Mnil, Mnil, Mnil);
361           if (pl)
362             imtable[i].lang_in_locale = 1;
363           else
364             pl = mlanguage_name_list (tag[1], Meng, Mnil, Mnil);
365           if (pl)
366             {
367               MText *mt = mplist_value (pl);
368               int nbytes;
369
370               if (converter)
371                 {
372                   mconv_reset_converter (converter);
373                   nbytes = mconv_encode (converter, mt);
374                   if (converter->result == MCONVERSION_RESULT_SUCCESS)
375                     {
376                       imtable[i].encoded_lang = alloca (nbytes + 1);
377                       if (imtable[i].encoded_lang)
378                         {
379                           memcpy (imtable[i].encoded_lang, conv_buf, nbytes);
380                           imtable[i].encoded_lang[nbytes] = '\0';
381                         }
382                     }
383                 }
384               else if (locale_is_utf8)
385                 imtable[i].encoded_lang
386                   = mtext_data (mt, NULL, NULL, NULL, NULL);
387               imtable[i].lang = mtext_data (mt, NULL, NULL, NULL, NULL);
388             }
389           else
390             imtable[i].lang = msymbol_name (tag[1]);
391         }
392       i++;
393     }
394   if (converter)
395     mconv_free_converter (converter);
396   m17n_object_unref (imlist);
397   config_status->num_im = i;
398   qsort (imtable, config_status->num_im, sizeof (MimTable), sort_im);
399
400   for (lang = NULL, i = 0; i < config_status->num_im; i++)
401     {
402       gchar *langname = imtable[i].lang;
403
404       if (! langname)
405         langname = other;
406       if (lang != langname)
407         {
408           gtk_tree_store_append (store, &iter1, NULL);
409           gtk_tree_store_set (store, &iter1,
410                               COL_TAG, langname,
411                               COL_STATUS_STR, NULL,
412                               COL_STATUS, 0,
413                               COL_LANG, Mnil,
414                               COL_NAME, Mnil,
415                               -1);
416           lang = langname;
417         }
418       gtk_tree_store_append (store, &iter2, &iter1);
419       gtk_tree_store_set (store, &iter2,
420                           COL_TAG, imtable[i].name,
421                           COL_STATUS_STR, NULL,
422                           COL_LANG, imtable[i].symlang,
423                           COL_NAME, imtable[i].symname,
424                           -1);
425     }
426   config_status->num_modified = 0;
427   g_object_set_data_full (G_OBJECT (store), CONFIG_STATUS_DATA,
428                           config_status, g_free);
429   return store;
430 }
431
432 static gboolean
433 revert_to_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
434                gpointer data)
435 {
436   enum MimStatus status;
437   MSymbol lang, name;
438   MimConfigStatus *config_status = data;
439
440   gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
441   if (lang == Mnil)
442     return FALSE;
443   gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
444   if (status != MIM_STATUS_MODIFIED)
445     return FALSE;
446   minput_config_variable (lang, name, Mnil, NULL);
447   minput_config_command (lang, name, Mnil, NULL);
448   status = get_mim_status (lang, name);
449   update_child_row (model, iter, status, config_status, NULL);
450   return FALSE;
451 }
452
453 static gboolean
454 set_as_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
455            gpointer data)
456 {
457   enum MimStatus status;
458   MSymbol lang, name;
459   MimConfigStatus *config_status = data;
460
461   gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
462   if (lang == Mnil)
463     return FALSE;
464   gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
465   if (status != MIM_STATUS_MODIFIED)
466     return FALSE;
467   status = get_mim_status (lang, name);
468   update_child_row (model, iter, status, config_status, NULL);
469   return FALSE;
470 }
471
472 static int initialized = 0;
473
474 static void
475 destroy_cb (GtkWidget *widget, gpointer data)
476 {
477   M17N_FINI ();
478   initialized = 0;
479 }
480
481 \f
482 /****************************************************/
483 /* Configuration of a specific variable or command. */
484 /****************************************************/
485
486 /* Common staffs to variable and command */
487
488 struct ConfigControl
489 {
490   /* Data type name ("Value" or "Key bindings").  */
491   gchar *data_type_name;
492   MSymbol lang, name, item;
493   /* Fill in widgets in DIALOG for configuring a specific variable or
494      command.  */
495   void (*setup_dialog) (GtkWidget *dialog, struct ConfigControl *control);
496   /* Update the contents of DATA widget.  */
497   void (*update_data) (struct ConfigControl *control);
498   /* Convert PLIST to string.  PLIST is a variable value or command
499      key sequeneses.  */
500   GString *(*data_string) (MPlist *plist);
501   /* minput_get_variable or minput_get_command.  */
502   MPlist *(*get) (MSymbol, MSymbol, MSymbol);
503   /* minput_config_variable or minput_config_command.  */
504   int (*config) (MSymbol, MSymbol, MSymbol, MPlist *);
505   /* If non-NULL, a function to call before finishing a dialog.  */
506   gboolean (*config_on_ok) (struct ConfigControl *control);
507
508   /* Widget showing the current data (value or key bindings) */
509   GtkWidget *data;
510
511   /* Button widget to configure the data to the default.  */
512   GtkWidget *default_;
513
514   /* Button widget to cancel the configuration.  */
515   GtkWidget *revert;
516
517   /* Label widget showing the current status.  */
518   GtkWidget *status;
519 };
520
521 struct CommandControl
522 {
523   struct ConfigControl control;
524   GtkWidget *entry;
525   GtkWidget *clear;
526   GtkWidget *add;
527   GtkWidget *delete;
528 };
529
530 enum WidgetType
531   {
532     ENTRY_WIDGET,
533     COMBO_BOX_WIDGET,
534     SPIN_BUTTON_WIDGET
535   };
536
537 struct VariableControl
538 {
539   struct ConfigControl control;
540
541   /* type of current variable: Minteger, Msymbol, or Mtext */
542   MSymbol vtype;
543
544   /* type of widget */
545   enum WidgetType wtype;
546 };
547
548 #define CONFIG_CONTROL(control) ((struct ConfigControl *) (control))
549 #define COMMAND_CONTROL(control) ((struct CommandControl *) (control))
550 #define VARIABLE_CONTROL(control) ((struct VariableControl *) (control))
551
552 #define CURRENT_DATA    \
553   (mplist_next                  \
554    (mplist_next                 \
555     (mplist_next                \
556      (mplist_value              \
557       (control->get (control->lang, control->name, control->item))))))
558
559 #define CURRENT_STATUS          \
560   (mplist_value                 \
561    (mplist_next                 \
562     (mplist_next                \
563      (mplist_value              \
564       (control->get (control->lang, control->name, control->item))))))
565
566 #define CURRENT_DESCRIPTION                                     \
567   (mtext_data                                                   \
568    (mplist_value                                                \
569     (mplist_next                                                \
570      (mplist_value                                              \
571       (control->get (control->lang, control->name, control->item)))),   \
572     NULL,  NULL,  NULL,  NULL))
573
574 #define CONFIG_DATA(plist)                                      \
575   control->config (control->lang, control->name, control->item, \
576                    (plist))
577
578 static MPlist *entry_keyseq;
579
580 static void
581 update_status (struct ConfigControl *control)
582 {
583   MSymbol status = CURRENT_STATUS;
584
585   if (status == Mconfigured)
586     {
587       gtk_label_set_text (GTK_LABEL (control->status),
588                           mim_status_str[MIM_STATUS_MODIFIED]);
589       gtk_widget_set_sensitive (control->default_, TRUE);
590       gtk_widget_set_sensitive (control->revert, TRUE);
591     }
592   else if (status == Mcustomized)
593     {
594       gtk_label_set_text (GTK_LABEL (control->status),
595                           mim_status_str[MIM_STATUS_CUSTOMIZED]);
596       gtk_widget_set_sensitive (control->default_, TRUE);
597       gtk_widget_set_sensitive (control->revert, FALSE);
598     }
599   else
600     {
601       gtk_label_set_text (GTK_LABEL (control->status),
602                           mim_status_str[MIM_STATUS_DEFAULT]);
603       gtk_widget_set_sensitive (control->default_, FALSE);
604       gtk_widget_set_sensitive (control->revert, FALSE);
605     }
606 }
607
608 static void
609 help_cb (GtkButton *button, gpointer data)
610 {
611   struct ConfigControl *control = data;
612   GtkWidget *msg;
613
614   msg = gtk_message_dialog_new (GTK_WINDOW
615                                 (gtk_widget_get_toplevel (GTK_WIDGET (button))),
616                                 GTK_DIALOG_DESTROY_WITH_PARENT,
617                                 GTK_MESSAGE_INFO,
618                                 GTK_BUTTONS_CLOSE,
619                                 CURRENT_DESCRIPTION);
620   gtk_dialog_run (GTK_DIALOG (msg));
621   gtk_widget_destroy (msg);
622 }
623
624 static void
625 default_cb (GtkButton *button, gpointer data)
626 {
627   MPlist *empty = mplist ();
628   struct ConfigControl *control = data;
629
630   CONFIG_DATA (empty);
631   m17n_object_unref (empty);
632   control->update_data (control);
633   update_status (control);
634   control->config_on_ok = NULL;
635 }
636
637 static void
638 revert_cb (GtkButton *button, gpointer data)
639 {
640   struct ConfigControl *control = data;
641
642   CONFIG_DATA (NULL);
643   control->update_data (control);
644   update_status (control);
645   control->config_on_ok = NULL;
646 }
647
648 static void
649 ok_cb (GtkButton *button, gpointer data)
650 {
651   struct ConfigControl *control = data;
652
653   if (control->config_on_ok)
654     {
655       if (! control->config_on_ok (control))
656         {
657           revert_cb (NULL, control);
658           return;
659         }
660       control->config_on_ok = NULL;
661     }
662   if (control->config == minput_config_command)
663     m17n_object_unref (entry_keyseq);
664   gtk_dialog_response (GTK_DIALOG
665                        (gtk_widget_get_toplevel (GTK_WIDGET (button))),
666                        GTK_RESPONSE_OK);
667 }
668
669 enum
670   {
671     /* Variable or command name */
672     CONFIG_COL_ITEM,
673     /* Status (default, modified, or customized).  */
674     CONFIG_COL_STATUS,
675     /* Variable value or command key bindings. */
676     CONFIG_COL_DATA,
677     /* Number of columns of list store.  */
678     NUM_CONFIG_COLS
679   };
680
681
682 static void
683 set_list_element (GtkListStore *store, GtkTreeIter *iter,
684                   struct ConfigControl *control, MPlist *plist)
685 {
686   MSymbol status;
687   gchar *status_str;
688
689   if (! plist)
690     plist = mplist_value (control->get (control->lang, control->name,
691                                         control->item));
692   plist = mplist_next (mplist_next (plist));
693   
694   status = mplist_value (plist);
695   if (status == Mconfigured)
696     status_str = mim_status_str[MIM_STATUS_MODIFIED];
697   else if (status == Mcustomized)
698     status_str = mim_status_str[MIM_STATUS_CUSTOMIZED];
699   else
700     status_str = mim_status_str[MIM_STATUS_DEFAULT];
701   plist = mplist_next (plist);
702   gtk_list_store_set (store, iter,
703                       CONFIG_COL_ITEM, msymbol_name (control->item),
704                       CONFIG_COL_STATUS, status_str,
705                       CONFIG_COL_DATA, control->data_string (plist)->str,
706                       -1);
707 }
708
709 /* Called when an item (command or variable) name is activated.
710    Create a dialog widget to config that itme.  */
711
712 static void
713 item_activated_cb (GtkTreeView *parent, GtkTreePath *path,
714                    GtkTreeViewColumn *col, gpointer data)
715 {
716   GtkTreeModel *model;
717   GtkTreeIter iter;
718   GtkWidget *dialog, *label, *help, *hbox, *ok;
719   gchar *item;
720   struct ConfigControl *control = CONFIG_CONTROL (data);
721
722   model = gtk_tree_view_get_model (parent);
723   if (! gtk_tree_model_get_iter (model, &iter, path))
724     return;
725   gtk_tree_model_get (model, &iter, CONFIG_COL_ITEM, &item, -1);
726   control->item = msymbol (item);
727
728   dialog = (gtk_dialog_new_with_buttons
729             (msymbol_name (control->item),
730              GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (parent))),
731              GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
732              NULL));
733   gtk_button_box_set_layout (GTK_BUTTON_BOX (GTK_DIALOG (dialog)->action_area),
734                              GTK_BUTTONBOX_EDGE);
735   gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
736
737   hbox = gtk_hbox_new (FALSE, 12);
738   gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
739   label = gtk_label_new (_("Status"));
740   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
741   label = gtk_label_new (": ");
742   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
743   control->status = gtk_label_new (NULL);
744   gtk_box_pack_start (GTK_BOX (hbox), control->status, FALSE, FALSE, 0);
745   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox),
746                     hbox, FALSE, FALSE, 0);
747
748   help = gtk_button_new_from_stock (GTK_STOCK_HELP);
749   g_signal_connect (G_OBJECT (help), "clicked",
750                     G_CALLBACK (help_cb), control);
751   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
752                       help, FALSE, FALSE, 0);
753   ok = gtk_button_new_from_stock (GTK_STOCK_OK);
754   g_signal_connect (G_OBJECT (ok), "clicked",
755                     G_CALLBACK (ok_cb), control);
756   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->action_area),
757                     ok, FALSE, FALSE, 0);
758
759   control->setup_dialog (dialog, control);
760
761   update_status (control);
762   gtk_widget_show_all (dialog);
763   gtk_dialog_run (GTK_DIALOG (dialog));
764   gtk_tree_model_get_iter (model, &iter, path);
765   set_list_element (GTK_LIST_STORE (model), &iter, control, NULL);
766   gtk_widget_destroy (dialog);
767 }
768
769
770 /* Create a list view widget listing variable or command names with
771    their current status and data.  */
772
773 GtkWidget *
774 create_item_list (MSymbol lang, MSymbol name, struct ConfigControl *control)
775 {
776   GtkListStore *store;
777   GtkWidget *view;
778   MPlist *plist;
779
780   plist = control->get (lang, name, Mnil);
781   /* plist == ((command/variable description status data ...) ...) */
782   if (! plist)
783     return gtk_label_new (_("No customizable item."));
784   store = gtk_list_store_new (NUM_CONFIG_COLS,
785                               G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
786   for (; plist && mplist_key (plist) == Mplist; plist = mplist_next (plist))
787     {
788       GtkTreeIter iter;
789       MPlist *pl;
790
791       pl = mplist_value (plist);
792       /* pl == (command/variable description status data ...) */
793       control->item = mplist_value (pl);
794       gtk_list_store_append (store, &iter);
795       set_list_element (store, &iter, control, pl);
796     }
797
798   view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
799   g_object_unref (G_OBJECT (store));
800   gtk_tree_view_insert_column_with_attributes
801     (GTK_TREE_VIEW (view), -1, _("Name"), gtk_cell_renderer_text_new (),
802      "text", CONFIG_COL_ITEM, NULL);
803   gtk_tree_view_insert_column_with_attributes
804     (GTK_TREE_VIEW (view), -1, _("Status"), gtk_cell_renderer_text_new (),
805      "text", CONFIG_COL_STATUS, NULL);
806   gtk_tree_view_insert_column_with_attributes
807     (GTK_TREE_VIEW (view), -1, control->data_type_name,
808      gtk_cell_renderer_text_new (),
809      "text", CONFIG_COL_DATA, NULL);
810   g_signal_connect (G_OBJECT (view), "row-activated",
811                     G_CALLBACK (item_activated_cb), control);
812
813   return view;
814 }
815
816 static struct VariableControl var;
817 static struct CommandControl cmd;
818
819 static void
820 config_im (GtkTreeView *tree, MSymbol lang, MSymbol name)
821 {
822   GtkWidget *dialog, *notebook, *scrolled, *vbox, *label;
823
824   var.control.lang = cmd.control.lang = lang;
825   var.control.name = cmd.control.name = name;
826   var.control.config_on_ok = cmd.control.config_on_ok = NULL;
827
828   dialog = (gtk_dialog_new_with_buttons
829             (name == Mnil ? "global" : msymbol_name (name),
830              GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tree))),
831              GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
832              GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
833              NULL));
834   gtk_widget_set_size_request (dialog, 500, 300);
835   gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
836
837   vbox = gtk_vbox_new (FALSE, 0);
838   gtk_container_set_border_width (GTK_CONTAINER(vbox), 5);
839   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), vbox);
840
841   notebook = gtk_notebook_new ();
842   gtk_container_add (GTK_CONTAINER (vbox), notebook);
843
844   /* Variables' page */
845   scrolled = gtk_scrolled_window_new (NULL, NULL);
846   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
847                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
848   label = gtk_label_new_with_mnemonic (_("_Variables"));
849   gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled, label);
850   vbox = gtk_vbox_new (FALSE, 0);
851   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
852                                          vbox);
853   gtk_box_pack_start (GTK_BOX (vbox),
854                       create_item_list (lang, name, CONFIG_CONTROL (&var)),
855                       FALSE, FALSE, 0);
856
857   /* Commands' pages */
858   scrolled = gtk_scrolled_window_new (NULL, NULL);
859   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
860                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
861   label = gtk_label_new_with_mnemonic (_("Co_mmands"));
862   gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled, label);
863   vbox = gtk_vbox_new (FALSE, 0);
864   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
865                                          vbox);
866   gtk_box_pack_start (GTK_BOX (vbox),
867                       create_item_list (lang, name, CONFIG_CONTROL (&cmd)),
868                       FALSE, FALSE, 0);
869
870   gtk_widget_show_all (dialog);
871   gtk_dialog_run (GTK_DIALOG (dialog));
872   gtk_widget_destroy (dialog);
873 }
874
875 \f
876 /* Staffs for variable configuration.  */
877
878 void
879 variable_update_data (struct ConfigControl *control)
880 {
881   MPlist *plist;
882   MSymbol key;
883   void *value;
884
885   plist = CURRENT_DATA;
886   /* plist == (value [valid-value ...]) */
887   key = mplist_key (plist);
888   value = mplist_value (plist);
889
890   if (VARIABLE_CONTROL (control)->wtype == ENTRY_WIDGET)
891     {
892       if (key == Msymbol)
893         gtk_entry_set_text (GTK_ENTRY (control->data),
894                             msymbol_name ((MSymbol) value));
895       else if (key == Mtext)            
896         /* Fixme : Assuming the return value is in UTF-8 */
897         gtk_entry_set_text (GTK_ENTRY (control->data),
898                             mtext_data ((MText *) value,
899                                         NULL, NULL, NULL, NULL));
900       else                      /* key == Minteger */
901         {
902           gchar buf[32];
903           g_snprintf (buf, sizeof (buf), "%d", (gint) value);
904           gtk_entry_set_text (GTK_ENTRY (control->data), buf);
905         }
906     }
907   else if (VARIABLE_CONTROL (control)->wtype == COMBO_BOX_WIDGET)
908     {
909       gint i;
910
911       for (i = 0, plist = mplist_next (plist);
912            plist && mplist_key (plist) == key;
913            i++, plist = mplist_next (plist))
914         if (mplist_value (plist) == value)
915           break;
916       gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), i);
917     }
918   else                          /* ci->wtype == SPIN_BUTTON_WIDGET */
919     gtk_spin_button_set_value (GTK_SPIN_BUTTON (control->data),
920                                (gdouble) (int) value);
921 }
922
923 static gboolean
924 config_with_entry (struct ConfigControl *control)
925 {
926   const gchar *text = gtk_entry_get_text (GTK_ENTRY (control->data));
927   MPlist *plist = mplist ();
928   gboolean ret = TRUE;
929
930   if (VARIABLE_CONTROL (control)->vtype == Msymbol)
931     {
932       mplist_add (plist, Msymbol, msymbol (text));
933       CONFIG_DATA (plist);
934     }
935   else if (VARIABLE_CONTROL (control)->vtype == Mtext)
936     {
937       MText *mt;
938
939       mt = mconv_decode_buffer (Mcoding_utf_8, (guchar *) text, strlen (text));
940       mplist_add (plist, Mtext, mt);
941       CONFIG_DATA (plist);
942       m17n_object_unref (mt);
943     }
944   else             /* VARIABLE_CONTROL (control)->vtype == Minteger */
945     {
946       int i;
947
948       if (sscanf (text, "%d", &i) == 1)
949         {
950           mplist_add (plist, Minteger, (void *) i);
951           CONFIG_DATA (plist);
952         }
953       else
954         {
955           GtkWidget *msg;
956
957           msg = gtk_message_dialog_new (GTK_WINDOW
958                                         (gtk_widget_get_toplevel (control->data)),
959                                         GTK_DIALOG_DESTROY_WITH_PARENT,
960                                         GTK_MESSAGE_ERROR,
961                                         GTK_BUTTONS_CLOSE,
962                                         _("The value must be an integer."));
963           gtk_dialog_run (GTK_DIALOG (msg));
964           gtk_widget_destroy (msg);
965           ret = FALSE;
966         }
967     }
968
969   m17n_object_unref (plist);
970   return ret;
971 }
972
973 static gboolean
974 config_with_combo (struct ConfigControl *control)
975 {
976   gchar *text = gtk_combo_box_get_active_text (GTK_COMBO_BOX (control->data));
977   MPlist *plist = mplist ();
978
979   if (VARIABLE_CONTROL (control)->vtype == Msymbol)
980     {
981       mplist_add (plist, Msymbol, msymbol (text));
982       CONFIG_DATA (plist);
983     }
984   else if (VARIABLE_CONTROL (control)->vtype == Mtext)
985     {
986       MText *mt;
987
988       mt = mconv_decode_buffer (Mcoding_utf_8, (guchar *) text, strlen (text));
989       mplist_add (plist, Mtext, mt);
990       CONFIG_DATA (plist);
991       m17n_object_unref (mt);
992     }
993   else             /* VARIABLE_CONTROL (control)->vtype == Minteger */
994     {
995       int i;
996
997       sscanf (text, "%d", &i);
998       mplist_add (plist, Minteger, (void *) i);
999       CONFIG_DATA (plist);
1000     }
1001   m17n_object_unref (plist);
1002   return TRUE;
1003 }
1004
1005 static gboolean
1006 config_with_spin (struct ConfigControl *control)
1007 {
1008   gint i = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (control->data));
1009   MPlist *plist = mplist ();
1010
1011   mplist_add (plist, Minteger, (void *) i);
1012   CONFIG_DATA (plist);
1013   m17n_object_unref (plist);
1014   return TRUE;
1015 }
1016
1017 static void
1018 changed_cb (GtkEntry *entry, gpointer data)
1019 {
1020   struct ConfigControl *control = data;
1021
1022   gtk_widget_set_sensitive (control->default_, TRUE);
1023   gtk_widget_set_sensitive (control->revert, TRUE);
1024   gtk_label_set_text (GTK_LABEL (control->status), _("modified"));
1025   if (VARIABLE_CONTROL (control)->wtype == ENTRY_WIDGET)
1026     control->config_on_ok = config_with_entry;
1027   else if (VARIABLE_CONTROL (control)->wtype == COMBO_BOX_WIDGET)
1028     control->config_on_ok = config_with_combo;
1029   else
1030     control->config_on_ok = config_with_spin;
1031 }
1032
1033 static GString *
1034 variable_data_string (MPlist *plist)
1035 {
1036   static GString *str;
1037
1038   if (! str)
1039     str = g_string_sized_new (80);  
1040   else
1041     g_string_truncate (str, 0);
1042
1043   if (mplist_key (plist) == Msymbol)
1044     g_string_append (str, msymbol_name ((MSymbol) mplist_value (plist)));
1045   else if (mplist_key (plist) == Mtext)
1046     /* Fixme : Assuming the return value is in UTF-8 */
1047     g_string_append (str, mtext_data ((MText *) mplist_value (plist),
1048                                       NULL, NULL, NULL, NULL));
1049   else /* mplist_key (plist) == Minteger */
1050     g_string_append_printf (str, "%d", (gint) mplist_value (plist));
1051   return str;
1052 }
1053
1054 static void
1055 variable_setup_dialog (GtkWidget *dialog, struct ConfigControl *control)
1056 {
1057   MPlist *plist; 
1058   void *value;
1059   GtkWidget *hbox, *vbox;
1060
1061   plist = CURRENT_DATA;
1062   VARIABLE_CONTROL (control)->vtype = mplist_key (plist);
1063   value = mplist_value (plist);
1064   plist = mplist_next (plist);
1065
1066   if (VARIABLE_CONTROL (control)->vtype == Msymbol)
1067     {
1068       if (mplist_key (plist) == Msymbol)
1069         {
1070           gint i, nth;
1071
1072           control->data = gtk_combo_box_new_text ();
1073           VARIABLE_CONTROL (control)->wtype = COMBO_BOX_WIDGET;
1074           for (i = 0; mplist_key (plist) == Msymbol;
1075                plist = mplist_next (plist), i++)
1076             {
1077               if (mplist_value (plist) == value)
1078                 nth = i;
1079               gtk_combo_box_append_text
1080                 (GTK_COMBO_BOX (control->data),
1081                  msymbol_name ((MSymbol) mplist_value (plist)));
1082             }
1083           gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), nth);
1084           g_signal_connect (GTK_OBJECT (control->data), "changed",
1085                             G_CALLBACK (changed_cb), control);
1086         }
1087       else
1088         {
1089           control->data = gtk_entry_new ();
1090           VARIABLE_CONTROL (control)->wtype = ENTRY_WIDGET;
1091           gtk_entry_set_text (GTK_ENTRY (control->data), msymbol_name (value));
1092           gtk_editable_set_editable (GTK_EDITABLE (control->data), TRUE);
1093           g_signal_connect (GTK_OBJECT (control->data), "changed",
1094                             G_CALLBACK (changed_cb), control);
1095           g_signal_connect (GTK_OBJECT (control->data), "activate",
1096                             G_CALLBACK (ok_cb), control);
1097         }
1098     }
1099   else if (VARIABLE_CONTROL (control)->vtype == Mtext)
1100     {
1101       if (plist && mplist_key (plist) == Mtext)
1102         {
1103           gint i, nth;
1104
1105           control->data = gtk_combo_box_new_text ();
1106           VARIABLE_CONTROL (control)->wtype = COMBO_BOX_WIDGET;
1107           for (i = 0; plist && mplist_key (plist) == Mtext;
1108                plist = mplist_next (plist), i++)
1109             {
1110               if (! mtext_cmp ((MText *) mplist_value (plist),
1111                                (MText *) value))
1112                 nth = i;
1113               /* Fixme : Assuming the return value is in UTF-8 */
1114               gtk_combo_box_append_text
1115                 (GTK_COMBO_BOX (control->data),
1116                  mtext_data ((MText *) mplist_value (plist),
1117                              NULL, NULL, NULL, NULL));
1118             }
1119           gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), nth);
1120           g_signal_connect (GTK_OBJECT (control->data), "changed",
1121                             G_CALLBACK (changed_cb), control);
1122         }
1123       else
1124         {
1125           control->data = gtk_entry_new ();
1126           VARIABLE_CONTROL (control)->wtype = ENTRY_WIDGET;
1127           /* Fixme : Assuming the return value is in UTF-8 */
1128           gtk_entry_set_text (GTK_ENTRY (control->data),
1129                               mtext_data (value, NULL, NULL, NULL, NULL));
1130           gtk_editable_set_editable (GTK_EDITABLE (control->data), TRUE);
1131           g_signal_connect (GTK_OBJECT (control->data), "changed",
1132                             G_CALLBACK (changed_cb), control);
1133           g_signal_connect (GTK_OBJECT (control->data), "activate",
1134                             G_CALLBACK (ok_cb), control);
1135         }
1136     }
1137   else                          /* control->vtype == Minteger */
1138     {
1139       if (plist && mplist_key (plist) == Minteger)
1140         {
1141           gint i, nth;
1142
1143           control->data = gtk_combo_box_new_text ();
1144           VARIABLE_CONTROL (control)->wtype = COMBO_BOX_WIDGET;
1145           for (i = 0; plist && mplist_key (plist) == Minteger;
1146                plist = mplist_next (plist), i++)
1147             {
1148               gchar buf[32];
1149
1150               if (mplist_value (plist) == value)
1151                 nth = i;
1152               g_snprintf (buf, sizeof (buf), "%d",
1153                           (gint) mplist_value (plist));
1154               gtk_combo_box_append_text (GTK_COMBO_BOX (control->data), buf);
1155             }
1156           gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), nth);
1157           g_signal_connect (GTK_OBJECT (control->data), "changed",
1158                             G_CALLBACK (changed_cb), control);
1159         }
1160       else if (plist && mplist_key (plist) == Mplist)
1161         {
1162           GtkObject *adj;
1163           gdouble lower, upper;
1164
1165           plist = mplist_value (plist);
1166           lower = (gdouble) (int) mplist_value (plist);
1167           upper = (gdouble) (int) mplist_value (mplist_next (plist));
1168           adj = gtk_adjustment_new ((gdouble) (int) value, lower, upper,
1169                                     1.0, 10.0, 0);
1170           control->data = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 0, 0);
1171           VARIABLE_CONTROL (control)->wtype = SPIN_BUTTON_WIDGET;
1172           gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (control->data), TRUE);
1173           gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (control->data),
1174                                              GTK_UPDATE_ALWAYS);
1175           g_signal_connect (GTK_OBJECT (control->data), "changed",
1176                             G_CALLBACK (changed_cb), control);
1177         }
1178       else
1179         {
1180           gchar buf[32];
1181
1182           control->data = gtk_entry_new ();
1183           VARIABLE_CONTROL (control)->wtype = ENTRY_WIDGET;
1184           g_snprintf (buf, sizeof (buf), "%d", (gint) value);
1185           gtk_entry_set_text (GTK_ENTRY (control->data), buf);
1186           gtk_editable_set_editable (GTK_EDITABLE (control->data), TRUE);
1187           g_signal_connect (GTK_OBJECT (control->data), "changed",
1188                             G_CALLBACK (changed_cb), control);
1189           g_signal_connect (GTK_OBJECT (control->data), "activate",
1190                             G_CALLBACK (ok_cb), control);
1191         }
1192     }
1193
1194   control->default_ = gtk_button_new_from_stock (_("_Default"));
1195   g_signal_connect (G_OBJECT (control->default_), "clicked",
1196                     G_CALLBACK (default_cb), control);
1197
1198   control->revert = gtk_button_new_from_stock (GTK_STOCK_REVERT_TO_SAVED);
1199   g_signal_connect (G_OBJECT (control->revert), "clicked",
1200                     G_CALLBACK (revert_cb), control);
1201
1202   hbox = gtk_hbutton_box_new ();
1203   gtk_box_set_spacing (GTK_BOX (hbox), 6);
1204   gtk_container_add (GTK_CONTAINER (hbox), control->default_);
1205   gtk_container_add (GTK_CONTAINER (hbox), control->revert);
1206   vbox = gtk_vbox_new (FALSE, 12);
1207   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
1208   gtk_box_pack_start (GTK_BOX (vbox), control->data, FALSE, FALSE, 0);
1209   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
1210   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1211                       vbox, FALSE, FALSE, 0);
1212   gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 6);
1213 }
1214
1215 \f
1216 /* Staffs for command configuration.  */
1217
1218 static void
1219 selection_cb (GtkTreeSelection *selection, gpointer data)
1220 {
1221   gtk_widget_set_sensitive
1222     (COMMAND_CONTROL (data)->delete,
1223      gtk_tree_selection_count_selected_rows (selection) ? TRUE : FALSE);
1224 }
1225
1226 static void
1227 delete_cb (GtkButton *button, gpointer data)
1228 {
1229   GtkTreeSelection *selection;
1230   GtkTreeModel *model;
1231   GtkTreeIter iter;
1232   MPlist *pl, *new;
1233   struct ConfigControl *control = data;
1234
1235   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (control->data));
1236   model = gtk_tree_view_get_model (GTK_TREE_VIEW (control->data));
1237
1238   if (! gtk_tree_model_get_iter_first (model, &iter))
1239     return;
1240
1241   new = mplist ();
1242   for (pl = CURRENT_DATA; mplist_key (pl) != Mnil; pl = mplist_next (pl))
1243     {
1244       if (! gtk_tree_selection_iter_is_selected (selection, &iter))
1245         mplist_add (new, Mplist, mplist_value (pl));
1246       gtk_tree_model_iter_next (model, &iter);
1247     }
1248   CONFIG_DATA (new);
1249   m17n_object_unref (new);
1250   control->update_data (control);
1251   update_status (control);
1252 }
1253
1254 static GtkWidget *
1255 create_deleting_section (struct ConfigControl *control)
1256 {
1257   struct CommandControl *cmd_control = COMMAND_CONTROL (control);
1258   GtkListStore *store;
1259   GtkWidget *label, *scrolled, *hbox, *vbox;
1260   GtkTreeViewColumn *column;
1261   GtkCellRenderer *renderer;
1262   GtkTreeSelection *selection;
1263
1264   label = gtk_label_new (_("Current key bindings:"));
1265
1266   store = gtk_list_store_new (1, G_TYPE_STRING);
1267   control->data = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
1268   g_object_unref (G_OBJECT (store));
1269   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (control->data), FALSE);
1270   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (control->data));
1271   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1272   g_signal_connect (G_OBJECT (selection), "changed",
1273                     G_CALLBACK (selection_cb), control);
1274
1275   scrolled = gtk_scrolled_window_new (NULL, NULL);
1276   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
1277                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1278   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
1279                                          control->data);
1280
1281   column = gtk_tree_view_column_new ();
1282   gtk_tree_view_append_column (GTK_TREE_VIEW (control->data), column);
1283   renderer = gtk_cell_renderer_text_new ();
1284   gtk_tree_view_column_pack_start (column, renderer, TRUE);
1285   gtk_tree_view_column_set_attributes (column, renderer, "text", 0, NULL);
1286
1287   control->update_data (control);
1288
1289   control->default_ = gtk_button_new_from_stock (_("_Default"));
1290   g_signal_connect (G_OBJECT (control->default_), "clicked",
1291                     G_CALLBACK (default_cb), control);
1292
1293   control->revert = gtk_button_new_from_stock (GTK_STOCK_REVERT_TO_SAVED);
1294   g_signal_connect (G_OBJECT (control->revert), "clicked",
1295                     G_CALLBACK (revert_cb), control);
1296
1297   cmd_control->delete = gtk_button_new_from_stock (GTK_STOCK_DELETE);
1298   gtk_widget_set_sensitive (cmd_control->delete, FALSE);
1299   g_signal_connect (G_OBJECT (cmd_control->delete), "clicked",
1300                     G_CALLBACK (delete_cb), control);
1301
1302   vbox = gtk_vbox_new (FALSE, 12);
1303   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
1304
1305   hbox = gtk_hbox_new (FALSE, 6);
1306   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
1307   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1308
1309   gtk_container_add (GTK_CONTAINER (vbox), scrolled);
1310
1311   hbox = gtk_hbutton_box_new ();
1312   gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
1313   gtk_box_set_spacing (GTK_BOX (hbox), 6);
1314   gtk_container_add (GTK_CONTAINER (hbox), control->default_);
1315   gtk_container_add (GTK_CONTAINER (hbox), control->revert);
1316   gtk_container_add (GTK_CONTAINER (hbox), cmd_control->delete);
1317   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1318
1319   return vbox;
1320 }
1321
1322 static unsigned modifier_state = 0;
1323
1324 enum KeyMaskBit {
1325   META_MASK_BIT = 1,
1326   ALT_MASK_BIT = META_MASK_BIT << 1,
1327   SUPER_MASK_BIT = ALT_MASK_BIT << 1,
1328   HYPER_MASK_BIT = SUPER_MASK_BIT << 1
1329 };
1330
1331 static void
1332 update_entry (GtkEntry *entry)
1333 {
1334   if (mplist_key (entry_keyseq) == Mnil)
1335     gtk_entry_set_text (entry, "");
1336   else
1337     {
1338       MPlist *p;
1339       gchar *name;
1340
1341       name = msymbol_name ((MSymbol) mplist_value (entry_keyseq));
1342       gtk_entry_set_text (entry, name);
1343       for (p = mplist_next (entry_keyseq); mplist_key (p) != Mnil;
1344            p = mplist_next (p))
1345         {
1346           name = msymbol_name ((MSymbol) mplist_value (p));
1347           gtk_entry_append_text (entry, " ");
1348           gtk_entry_append_text (entry, name);
1349         }
1350       gtk_editable_set_position (GTK_EDITABLE (entry), -1);
1351     }
1352 }
1353
1354 static gboolean
1355 key_pressed_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
1356 {
1357   guint c;
1358   MText *mt;
1359   char buf[32];
1360   char *name;
1361   int nbytes, i;
1362   struct CommandControl *cmd_control = data;
1363
1364   c = gdk_keyval_to_unicode (event->keyval);
1365   if (c == 0)
1366     {
1367       switch (event->keyval)
1368         {
1369         case GDK_Meta_L: case GDK_Meta_R:
1370           modifier_state |= META_MASK_BIT; return TRUE;
1371         case GDK_Alt_L: case GDK_Alt_R:
1372           modifier_state |= ALT_MASK_BIT; return TRUE;
1373         case GDK_Super_L: case GDK_Super_R:
1374           modifier_state |= SUPER_MASK_BIT; return TRUE;
1375         case GDK_Hyper_L: case GDK_Hyper_R:
1376           modifier_state |= HYPER_MASK_BIT; return TRUE;
1377         default:
1378           if (event->keyval >= GDK_Shift_L && event->keyval <= GDK_Shift_Lock)
1379             return TRUE;
1380         }
1381       name = gdk_keyval_name (event->keyval);
1382       if (! name)
1383         return TRUE;
1384       nbytes = strlen (name);
1385     }
1386   else
1387     {
1388       name = alloca (8);
1389       mt = mtext ();
1390       mtext_cat_char (mt, c);
1391       nbytes = mconv_encode_buffer (msymbol ("utf-8"), mt,
1392                                     (unsigned char *) name, 32);
1393       m17n_object_unref (mt);
1394     }
1395   i = 0;
1396   if (c == 0 && event->state & GDK_SHIFT_MASK)
1397     buf[i++] = 'S', buf[i++] = '-';
1398   if (event->state & GDK_CONTROL_MASK)
1399     buf[i++] = 'C', buf[i++] = '-';
1400   if (modifier_state & META_MASK_BIT)
1401     buf[i++] = 'M', buf[i++] = '-';
1402   if (modifier_state & ALT_MASK_BIT)
1403     buf[i++] = 'A', buf[i++] = '-';
1404   if (modifier_state & SUPER_MASK_BIT)
1405     buf[i++] = 's', buf[i++] = '-';
1406   if (modifier_state & HYPER_MASK_BIT)
1407     buf[i++] = 'H', buf[i++] = '-';
1408   strncpy (buf + i, name, nbytes);
1409   buf[i + nbytes] = 0;
1410   mplist_add (entry_keyseq, Msymbol, msymbol (buf));
1411   update_entry (entry);
1412   gtk_widget_set_sensitive (cmd_control->clear, TRUE);
1413   gtk_widget_set_sensitive (cmd_control->add, TRUE);
1414   return TRUE;
1415 }
1416
1417 static gboolean
1418 key_released_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
1419 {
1420   guint c;
1421
1422   c = gdk_keyval_to_unicode (event->keyval);
1423   if (c == 0)
1424     {
1425       switch (event->keyval)
1426         {
1427         case GDK_Meta_L: case GDK_Meta_R:
1428           modifier_state &= ~META_MASK_BIT; break;
1429         case GDK_Alt_L: case GDK_Alt_R:
1430           modifier_state &= ~ALT_MASK_BIT; break;
1431         case GDK_Super_L: case GDK_Super_R:
1432           modifier_state &= ~SUPER_MASK_BIT; break;
1433         case GDK_Hyper_L: case GDK_Hyper_R:
1434           modifier_state &= ~HYPER_MASK_BIT; break;
1435         }
1436     }
1437   return FALSE;
1438 }
1439
1440 static void
1441 clear_cb (GtkButton *button, gpointer data)
1442 {
1443   struct CommandControl *cmd_control = data;
1444
1445   mplist_set (entry_keyseq, Mnil, NULL);
1446   gtk_widget_grab_focus (cmd_control->entry);
1447   update_entry (GTK_ENTRY (cmd_control->entry));
1448   gtk_widget_set_sensitive (cmd_control->clear, FALSE);
1449   gtk_widget_set_sensitive (cmd_control->add, FALSE);
1450 }
1451
1452 static void
1453 add_cb (GtkButton *button, gpointer data)
1454 {
1455   MPlist *new;
1456   GtkTreeModel *model;
1457   GtkTreeIter iter;
1458   struct ConfigControl *control = data;
1459
1460   if (mplist_length (entry_keyseq) == 0)
1461     return;
1462   model = gtk_tree_view_get_model (GTK_TREE_VIEW (control->data));
1463   if (gtk_tree_model_get_iter_first (model, &iter))
1464     {
1465       gchar *keyseq = control->data_string (entry_keyseq)->str;
1466       gchar *str;
1467
1468       do {
1469         gtk_tree_model_get (model, &iter, 0, &str, -1);
1470         if (strcmp (keyseq, str) == 0)
1471           /* entry_keyseq is already registered. */
1472           return;
1473       } while (gtk_tree_model_iter_next (model, &iter));
1474     }
1475   new = mplist_copy (CURRENT_DATA);
1476   mplist_add (new, Mplist, entry_keyseq);
1477   CONFIG_DATA (new);
1478   m17n_object_unref (new);
1479   control->update_data (control);
1480   update_status (control);
1481   clear_cb (NULL, control);
1482 }
1483
1484 static GtkWidget *
1485 create_adding_section (struct ConfigControl *control)
1486 {
1487   struct CommandControl *cmd_control = COMMAND_CONTROL (control);
1488   GtkWidget *label, *hbox, *vbox;
1489
1490   label = gtk_label_new (_("New key binding:"));
1491
1492   entry_keyseq = mplist ();
1493   cmd_control->entry = gtk_entry_new ();
1494   g_signal_connect (G_OBJECT (cmd_control->entry), "key-press-event",
1495                     G_CALLBACK (key_pressed_cb), cmd_control);
1496   g_signal_connect (G_OBJECT (cmd_control->entry), "key-release-event",
1497                     G_CALLBACK (key_released_cb), cmd_control);
1498
1499   cmd_control->clear = gtk_button_new_from_stock (GTK_STOCK_CLEAR);
1500   gtk_widget_set_sensitive (cmd_control->clear, FALSE);
1501   g_signal_connect (G_OBJECT (cmd_control->clear), "clicked",
1502                     G_CALLBACK (clear_cb), cmd_control);
1503
1504   cmd_control->add = gtk_button_new_from_stock (GTK_STOCK_ADD);
1505   gtk_widget_set_sensitive (cmd_control->add, FALSE);
1506   g_signal_connect (G_OBJECT (cmd_control->add), "clicked",
1507                     G_CALLBACK (add_cb), cmd_control);
1508
1509   vbox = gtk_vbox_new (FALSE, 12);
1510   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
1511
1512   hbox = gtk_hbox_new (FALSE, 6);
1513   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
1514   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1515
1516   gtk_container_add (GTK_CONTAINER (vbox), cmd_control->entry);
1517
1518   hbox = gtk_hbutton_box_new ();
1519   gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
1520   gtk_box_set_spacing (GTK_BOX (hbox), 6);
1521   gtk_container_add (GTK_CONTAINER (hbox), cmd_control->clear);
1522   gtk_container_add (GTK_CONTAINER (hbox), cmd_control->add);
1523   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1524
1525   return vbox;
1526 }
1527
1528 static void
1529 append_key_sequence (GString *str, MPlist *keyseq)
1530 {
1531   static MSymbol space_symbol;
1532   MPlist *p;
1533
1534   if (! space_symbol)
1535     space_symbol = msymbol (" ");
1536
1537   for (p = keyseq ; mplist_key (p) != Mnil; p = mplist_next (p))
1538     {
1539       MSymbol key = (MSymbol) mplist_value (p);
1540
1541       if (p != keyseq)
1542         g_string_append_c (str, ' ');
1543       if (key == space_symbol)
1544         g_string_append (str, "Space");
1545       else
1546         g_string_append (str, msymbol_name (key));
1547     }
1548 }
1549
1550 static GString *
1551 command_data_string (MPlist *plist)
1552 {
1553   static GString *str;
1554
1555   if (! str)
1556     str = g_string_sized_new (80);  
1557   else
1558     g_string_truncate (str, 0);
1559
1560   if (mplist_key (plist) == Mplist)
1561     {
1562       MPlist *pl;
1563
1564       /* PLIST == ((KEY KEY ...) ...) */
1565       for (pl = plist; mplist_key (pl) != Mnil; pl = mplist_next (pl))
1566         {
1567           if (pl != plist)
1568             g_string_append (str, ", ");
1569           append_key_sequence (str, mplist_value (pl));
1570         }
1571     }
1572   else
1573     {
1574       /* PLIST == (KEY KEY ...) */
1575       append_key_sequence (str, plist);
1576     }
1577   return str;
1578 }
1579
1580 static void
1581 command_update_data (struct ConfigControl *control)
1582 {
1583   GtkTreeView *tree = GTK_TREE_VIEW (control->data);
1584   GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (tree));
1585   GtkTreeIter iter;
1586   MPlist *pl;
1587
1588   gtk_list_store_clear (store);
1589   for (pl = CURRENT_DATA; mplist_key (pl) != Mnil; pl = mplist_next (pl))
1590     {
1591       gtk_list_store_append (store, &iter);
1592       gtk_list_store_set (store, &iter,
1593                           0, control->data_string (mplist_value (pl))->str,
1594                           -1);
1595     }
1596 }
1597
1598 static void
1599 command_setup_dialog (GtkWidget *dialog, struct ConfigControl *control)
1600 {
1601   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1602                       create_deleting_section (control), FALSE, FALSE, 0);
1603   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1604                       create_adding_section (control), FALSE, FALSE, 0);
1605   gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 6);
1606 }
1607
1608 \f
1609 /* Public API */
1610
1611 GtkWidget *
1612 mim_config_new (GCallback func, gpointer data)
1613 {
1614   GtkWidget *tree, *config;
1615   GtkTreeStore *store;
1616   GtkCellRenderer *renderer;
1617   GtkTreeViewColumn *column;
1618
1619   if (initialized)
1620     return NULL;
1621   M17N_INIT ();
1622   if (merror_code < 0)
1623     return NULL;
1624
1625   initialized = 1;
1626
1627 #if ENABLE_NLS
1628   bindtextdomain ("m17n-im-config", GETTEXTDIR);
1629   bind_textdomain_codeset ("m17n-im-config", "UTF-8");
1630 #endif
1631
1632   mim_status_str[MIM_STATUS_DEFAULT] = _("default");
1633   mim_status_str[MIM_STATUS_CUSTOMIZED] = _("customized");
1634   mim_status_str[MIM_STATUS_MODIFIED] = _("modified");
1635   mim_status_str[MIM_STATUS_NO] = _("uncustomizable");
1636
1637   var.control.data_type_name = _("Value");
1638   var.control.setup_dialog = variable_setup_dialog;
1639   var.control.update_data = variable_update_data;
1640   var.control.data_string = variable_data_string;
1641   var.control.get = minput_get_variable;
1642   var.control.config = minput_config_variable;
1643
1644   cmd.control.data_type_name = _("Key Bindings");
1645   cmd.control.setup_dialog = command_setup_dialog;
1646   cmd.control.update_data = command_update_data;
1647   cmd.control.data_string = command_data_string;
1648   cmd.control.get = minput_get_command;
1649   cmd.control.config = minput_config_command;
1650
1651   store = make_store_for_input_methods ();
1652   tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
1653   g_object_unref (G_OBJECT (store));
1654
1655   renderer = gtk_cell_renderer_text_new ();
1656   column = (gtk_tree_view_column_new_with_attributes
1657             (_("Input Method"), renderer, "text", COL_TAG, NULL));
1658   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
1659
1660   renderer = gtk_cell_renderer_text_new ();
1661   column = (gtk_tree_view_column_new_with_attributes
1662             (_("Status"), renderer, "text", COL_STATUS_STR, NULL));
1663   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
1664
1665   g_signal_connect (G_OBJECT (tree), "row-expanded",
1666                     G_CALLBACK (tree_expanded_cb), NULL);
1667   g_signal_connect (G_OBJECT (tree), "row-activated",
1668                     G_CALLBACK (tree_activated_cb), NULL);
1669
1670   config =gtk_scrolled_window_new (NULL, NULL);
1671   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (config),
1672                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1673   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (config), tree);
1674   g_signal_connect (G_OBJECT (config), "destroy",
1675                     G_CALLBACK (destroy_cb), NULL);
1676
1677   g_object_set_data (G_OBJECT (config), CONFIG_TREE_VIEW, tree);
1678   if (func)
1679     {
1680       MimConfigCallback *callback;
1681
1682       callback = g_new (MimConfigCallback, 1);
1683       callback->widget = config;
1684       callback->func = (MimConfigCallbackFunc) func;
1685       callback->data = data;
1686       g_object_set_data_full (G_OBJECT (tree), CONFIG_CALLBACK_DATA,
1687                               callback, g_free);
1688     }
1689
1690   return config;
1691 }
1692
1693 gboolean
1694 mim_config_modified (GtkWidget *config)
1695 {
1696   GtkTreeView *tree;
1697   GtkTreeModel *model;
1698   MimConfigStatus *config_status;
1699
1700   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1701   model = gtk_tree_view_get_model (tree);
1702   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1703
1704   return (config_status->num_modified > 0 ? TRUE : FALSE);
1705 }
1706
1707 gboolean
1708 mim_config_revert (GtkWidget *config)
1709 {
1710   GtkTreeView *tree;
1711   GtkTreeModel *model;
1712   MimConfigStatus *config_status;
1713
1714   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1715   model = gtk_tree_view_get_model (tree);
1716   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1717
1718   if (config_status->num_modified == 0)
1719     return FALSE;
1720   gtk_tree_model_foreach (model, revert_to_saved, config_status);
1721   return TRUE;
1722 }
1723
1724 gboolean
1725 mim_config_save (GtkWidget *config)
1726 {
1727   GtkTreeView *tree;
1728   GtkTreeModel *model;
1729   MimConfigStatus *config_status;
1730
1731   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1732   model = gtk_tree_view_get_model (tree);
1733   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1734
1735   if (config_status->num_modified == 0)
1736     return FALSE;
1737   minput_save_config ();
1738   gtk_tree_model_foreach (model, set_as_saved, config_status);
1739   return TRUE;
1740 }
1741
1742 GtkTreeView *
1743 mim_config_get_tree_view (GtkWidget *config)
1744 {
1745   GtkTreeView *tree;
1746
1747   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1748   return tree;
1749 }