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