a1dcc08c77ca2ee0edbdda6109265bcde5cb6b5c
[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), 6);
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 }
1163
1164 \f
1165 /* Staffs for command configuration.  */
1166
1167 static void
1168 selection_cb (GtkTreeSelection *selection, gpointer data)
1169 {
1170   gtk_widget_set_sensitive
1171     (COMMAND_CONTROL (data)->delete,
1172      gtk_tree_selection_count_selected_rows (selection) ? TRUE : FALSE);
1173 }
1174
1175 static void
1176 delete_cb (GtkButton *button, gpointer data)
1177 {
1178   GtkTreeSelection *selection;
1179   GtkTreeModel *model;
1180   GtkTreeIter iter;
1181   MPlist *pl, *new;
1182   struct ConfigControl *control = data;
1183
1184   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (control->data));
1185   model = gtk_tree_view_get_model (GTK_TREE_VIEW (control->data));
1186
1187   if (! gtk_tree_model_get_iter_first (model, &iter))
1188     return;
1189
1190   new = mplist ();
1191   for (pl = CURRENT_DATA; mplist_key (pl) != Mnil; pl = mplist_next (pl))
1192     {
1193       if (! gtk_tree_selection_iter_is_selected (selection, &iter))
1194         mplist_add (new, Mplist, mplist_value (pl));
1195       gtk_tree_model_iter_next (model, &iter);
1196     }
1197   CONFIG_DATA (new);
1198   m17n_object_unref (new);
1199   control->update_data (control);
1200   update_status (control);
1201 }
1202
1203 static GtkWidget *
1204 create_deleting_section (struct ConfigControl *control)
1205 {
1206   struct CommandControl *cmd_control = COMMAND_CONTROL (control);
1207   GtkListStore *store;
1208   GtkWidget *label, *scrolled, *hbox, *vbox;
1209   GtkTreeViewColumn *column;
1210   GtkCellRenderer *renderer;
1211   GtkTreeSelection *selection;
1212
1213   label = gtk_label_new (_("Current key bindings:"));
1214
1215   store = gtk_list_store_new (1, G_TYPE_STRING);
1216   control->data = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
1217   g_object_unref (G_OBJECT (store));
1218   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (control->data), FALSE);
1219   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (control->data));
1220   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1221   g_signal_connect (G_OBJECT (selection), "changed",
1222                     G_CALLBACK (selection_cb), control);
1223
1224   scrolled = gtk_scrolled_window_new (NULL, NULL);
1225   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
1226                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1227   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
1228                                          control->data);
1229
1230   column = gtk_tree_view_column_new ();
1231   gtk_tree_view_append_column (GTK_TREE_VIEW (control->data), column);
1232   renderer = gtk_cell_renderer_text_new ();
1233   gtk_tree_view_column_pack_start (column, renderer, TRUE);
1234   gtk_tree_view_column_set_attributes (column, renderer, "text", 0, NULL);
1235
1236   control->update_data (control);
1237
1238   control->default_ = gtk_button_new_from_stock (_("_Default"));
1239   g_signal_connect (G_OBJECT (control->default_), "clicked",
1240                     G_CALLBACK (default_cb), control);
1241
1242   control->revert = gtk_button_new_from_stock (GTK_STOCK_REVERT_TO_SAVED);
1243   g_signal_connect (G_OBJECT (control->revert), "clicked",
1244                     G_CALLBACK (revert_cb), control);
1245
1246   cmd_control->delete = gtk_button_new_from_stock (GTK_STOCK_DELETE);
1247   gtk_widget_set_sensitive (cmd_control->delete, FALSE);
1248   g_signal_connect (G_OBJECT (cmd_control->delete), "clicked",
1249                     G_CALLBACK (delete_cb), control);
1250
1251   vbox = gtk_vbox_new (FALSE, 5);
1252   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
1253
1254   hbox = gtk_hbox_new (FALSE, 6);
1255   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 6);
1256   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1257
1258   gtk_container_add (GTK_CONTAINER (vbox), scrolled);
1259
1260   hbox = gtk_hbutton_box_new ();
1261   gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
1262   gtk_box_set_spacing (GTK_BOX (hbox), 6);
1263   gtk_container_add (GTK_CONTAINER (hbox), control->default_);
1264   gtk_container_add (GTK_CONTAINER (hbox), control->revert);
1265   gtk_container_add (GTK_CONTAINER (hbox), cmd_control->delete);
1266   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1267
1268   return vbox;
1269 }
1270
1271 static unsigned modifier_state = 0;
1272
1273 enum KeyMaskBit {
1274   META_MASK_BIT = 1,
1275   ALT_MASK_BIT = META_MASK_BIT << 1,
1276   SUPER_MASK_BIT = ALT_MASK_BIT << 1,
1277   HYPER_MASK_BIT = SUPER_MASK_BIT << 1
1278 };
1279
1280 static void
1281 update_entry (GtkEntry *entry)
1282 {
1283   if (mplist_key (entry_keyseq) == Mnil)
1284     gtk_entry_set_text (entry, "");
1285   else
1286     {
1287       MPlist *p;
1288       gchar *name;
1289
1290       name = msymbol_name ((MSymbol) mplist_value (entry_keyseq));
1291       gtk_entry_set_text (entry, name);
1292       for (p = mplist_next (entry_keyseq); mplist_key (p) != Mnil;
1293            p = mplist_next (p))
1294         {
1295           name = msymbol_name ((MSymbol) mplist_value (p));
1296           gtk_entry_append_text (entry, " ");
1297           gtk_entry_append_text (entry, name);
1298         }
1299       gtk_editable_set_position (GTK_EDITABLE (entry), -1);
1300     }
1301 }
1302
1303 static gboolean
1304 key_pressed_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
1305 {
1306   guint c;
1307   MText *mt;
1308   char buf[32];
1309   char *name;
1310   int nbytes, i;
1311   struct CommandControl *cmd_control = data;
1312
1313   c = gdk_keyval_to_unicode (event->keyval);
1314   if (c == 0)
1315     {
1316       switch (event->keyval)
1317         {
1318         case GDK_Meta_L: case GDK_Meta_R:
1319           modifier_state |= META_MASK_BIT; return TRUE;
1320         case GDK_Alt_L: case GDK_Alt_R:
1321           modifier_state |= ALT_MASK_BIT; return TRUE;
1322         case GDK_Super_L: case GDK_Super_R:
1323           modifier_state |= SUPER_MASK_BIT; return TRUE;
1324         case GDK_Hyper_L: case GDK_Hyper_R:
1325           modifier_state |= HYPER_MASK_BIT; return TRUE;
1326         default:
1327           if (event->keyval >= GDK_Shift_L && event->keyval <= GDK_Shift_Lock)
1328             return TRUE;
1329         }
1330       name = gdk_keyval_name (event->keyval);
1331       if (! name)
1332         return TRUE;
1333       nbytes = strlen (name);
1334     }
1335   else
1336     {
1337       name = alloca (8);
1338       mt = mtext ();
1339       mtext_cat_char (mt, c);
1340       nbytes = mconv_encode_buffer (msymbol ("utf-8"), mt,
1341                                     (unsigned char *) name, 32);
1342       m17n_object_unref (mt);
1343     }
1344   i = 0;
1345   if (c == 0 && event->state & GDK_SHIFT_MASK)
1346     buf[i++] = 'S', buf[i++] = '-';
1347   if (event->state & GDK_CONTROL_MASK)
1348     buf[i++] = 'C', buf[i++] = '-';
1349   if (modifier_state & META_MASK_BIT)
1350     buf[i++] = 'M', buf[i++] = '-';
1351   if (modifier_state & ALT_MASK_BIT)
1352     buf[i++] = 'A', buf[i++] = '-';
1353   if (modifier_state & SUPER_MASK_BIT)
1354     buf[i++] = 's', buf[i++] = '-';
1355   if (modifier_state & HYPER_MASK_BIT)
1356     buf[i++] = 'H', buf[i++] = '-';
1357   strncpy (buf + i, name, nbytes);
1358   buf[i + nbytes] = 0;
1359   mplist_add (entry_keyseq, Msymbol, msymbol (buf));
1360   update_entry (entry);
1361   gtk_widget_set_sensitive (cmd_control->clear, TRUE);
1362   gtk_widget_set_sensitive (cmd_control->add, TRUE);
1363   return TRUE;
1364 }
1365
1366 static gboolean
1367 key_released_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
1368 {
1369   guint c;
1370
1371   c = gdk_keyval_to_unicode (event->keyval);
1372   if (c == 0)
1373     {
1374       switch (event->keyval)
1375         {
1376         case GDK_Meta_L: case GDK_Meta_R:
1377           modifier_state &= ~META_MASK_BIT; break;
1378         case GDK_Alt_L: case GDK_Alt_R:
1379           modifier_state &= ~ALT_MASK_BIT; break;
1380         case GDK_Super_L: case GDK_Super_R:
1381           modifier_state &= ~SUPER_MASK_BIT; break;
1382         case GDK_Hyper_L: case GDK_Hyper_R:
1383           modifier_state &= ~HYPER_MASK_BIT; break;
1384         }
1385     }
1386   return FALSE;
1387 }
1388
1389 static void
1390 clear_cb (GtkButton *button, gpointer data)
1391 {
1392   struct CommandControl *cmd_control = data;
1393
1394   mplist_set (entry_keyseq, Mnil, NULL);
1395   gtk_widget_grab_focus (cmd_control->entry);
1396   update_entry (GTK_ENTRY (cmd_control->entry));
1397   gtk_widget_set_sensitive (cmd_control->clear, FALSE);
1398   gtk_widget_set_sensitive (cmd_control->add, FALSE);
1399 }
1400
1401 static void
1402 add_cb (GtkButton *button, gpointer data)
1403 {
1404   MPlist *new;
1405   GtkTreeModel *model;
1406   GtkTreeIter iter;
1407   struct ConfigControl *control = data;
1408
1409   if (mplist_length (entry_keyseq) == 0)
1410     return;
1411   model = gtk_tree_view_get_model (GTK_TREE_VIEW (control->data));
1412   if (gtk_tree_model_get_iter_first (model, &iter))
1413     {
1414       gchar *keyseq = control->data_string (entry_keyseq)->str;
1415       gchar *str;
1416
1417       do {
1418         gtk_tree_model_get (model, &iter, 0, &str, -1);
1419         if (strcmp (keyseq, str) == 0)
1420           /* entry_keyseq is already registered. */
1421           return;
1422       } while (gtk_tree_model_iter_next (model, &iter));
1423     }
1424   new = mplist_copy (CURRENT_DATA);
1425   mplist_add (new, Mplist, entry_keyseq);
1426   CONFIG_DATA (new);
1427   m17n_object_unref (new);
1428   control->update_data (control);
1429   update_status (control);
1430   clear_cb (NULL, control);
1431 }
1432
1433 static GtkWidget *
1434 create_adding_section (struct ConfigControl *control)
1435 {
1436   struct CommandControl *cmd_control = COMMAND_CONTROL (control);
1437   GtkWidget *label, *hbox, *vbox;
1438
1439   label = gtk_label_new (_("New key binding:"));
1440
1441   entry_keyseq = mplist ();
1442   cmd_control->entry = gtk_entry_new ();
1443   g_signal_connect (G_OBJECT (cmd_control->entry), "key-press-event",
1444                     G_CALLBACK (key_pressed_cb), cmd_control);
1445   g_signal_connect (G_OBJECT (cmd_control->entry), "key-release-event",
1446                     G_CALLBACK (key_released_cb), cmd_control);
1447
1448   cmd_control->clear = gtk_button_new_from_stock (GTK_STOCK_CLEAR);
1449   gtk_widget_set_sensitive (cmd_control->clear, FALSE);
1450   g_signal_connect (G_OBJECT (cmd_control->clear), "clicked",
1451                     G_CALLBACK (clear_cb), cmd_control);
1452
1453   cmd_control->add = gtk_button_new_from_stock (GTK_STOCK_ADD);
1454   gtk_widget_set_sensitive (cmd_control->add, FALSE);
1455   g_signal_connect (G_OBJECT (cmd_control->add), "clicked",
1456                     G_CALLBACK (add_cb), cmd_control);
1457
1458   vbox = gtk_vbox_new (FALSE, 5);
1459   gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
1460
1461   hbox = gtk_hbox_new (FALSE, 6);
1462   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 6);
1463   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1464
1465   gtk_container_add (GTK_CONTAINER (vbox), cmd_control->entry);
1466
1467   hbox = gtk_hbutton_box_new ();
1468   gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
1469   gtk_box_set_spacing (GTK_BOX (hbox), 6);
1470   gtk_container_add (GTK_CONTAINER (hbox), cmd_control->clear);
1471   gtk_container_add (GTK_CONTAINER (hbox), cmd_control->add);
1472   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1473
1474   return vbox;
1475 }
1476
1477 static void
1478 append_key_sequence (GString *str, MPlist *keyseq)
1479 {
1480   static MSymbol space_symbol;
1481   MPlist *p;
1482
1483   if (! space_symbol)
1484     space_symbol = msymbol (" ");
1485
1486   for (p = keyseq ; mplist_key (p) != Mnil; p = mplist_next (p))
1487     {
1488       MSymbol key = (MSymbol) mplist_value (p);
1489
1490       if (p != keyseq)
1491         g_string_append_c (str, ' ');
1492       if (key == space_symbol)
1493         g_string_append (str, "Space");
1494       else
1495         g_string_append (str, msymbol_name (key));
1496     }
1497 }
1498
1499 static GString *
1500 command_data_string (MPlist *plist)
1501 {
1502   static GString *str;
1503
1504   if (! str)
1505     str = g_string_sized_new (80);  
1506   else
1507     g_string_truncate (str, 0);
1508
1509   if (mplist_key (plist) == Mplist)
1510     {
1511       MPlist *pl;
1512
1513       /* PLIST == ((KEY KEY ...) ...) */
1514       for (pl = plist; mplist_key (pl) != Mnil; pl = mplist_next (pl))
1515         {
1516           if (pl != plist)
1517             g_string_append (str, ", ");
1518           append_key_sequence (str, mplist_value (pl));
1519         }
1520     }
1521   else
1522     {
1523       /* PLIST == (KEY KEY ...) */
1524       append_key_sequence (str, plist);
1525     }
1526   return str;
1527 }
1528
1529 static void
1530 command_update_data (struct ConfigControl *control)
1531 {
1532   GtkTreeView *tree = GTK_TREE_VIEW (control->data);
1533   GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (tree));
1534   GtkTreeIter iter;
1535   MPlist *pl;
1536
1537   gtk_list_store_clear (store);
1538   for (pl = CURRENT_DATA; mplist_key (pl) != Mnil; pl = mplist_next (pl))
1539     {
1540       gtk_list_store_append (store, &iter);
1541       gtk_list_store_set (store, &iter,
1542                           0, control->data_string (mplist_value (pl))->str,
1543                           -1);
1544     }
1545 }
1546
1547 static void
1548 command_setup_dialog (GtkWidget *dialog, struct ConfigControl *control)
1549 {
1550   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1551                       create_deleting_section (control), FALSE, FALSE, 0);
1552   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1553                       create_adding_section (control), FALSE, FALSE, 0);
1554 }
1555
1556 \f
1557 /* Public API */
1558
1559 GtkWidget *
1560 mim_config_new (GCallback func, gpointer data)
1561 {
1562   GtkWidget *tree, *config;
1563   GtkTreeStore *store;
1564   GtkCellRenderer *renderer;
1565   GtkTreeViewColumn *column;
1566
1567   if (initialized)
1568     return NULL;
1569   M17N_INIT ();
1570   if (merror_code < 0)
1571     return NULL;
1572
1573   initialized = 1;
1574
1575 #if ENABLE_NLS
1576   bindtextdomain ("m17n-im-config", GETTEXTDIR);
1577   bind_textdomain_codeset ("m17n-im-config", "UTF-8");
1578 #endif
1579
1580   mim_status_str[MIM_STATUS_DEFAULT] = _("default");
1581   mim_status_str[MIM_STATUS_CUSTOMIZED] = _("customized");
1582   mim_status_str[MIM_STATUS_MODIFIED] = _("modified");
1583   mim_status_str[MIM_STATUS_NO] = _("uncustomizable");
1584
1585   var.control.data_type_name = _("Value");
1586   var.control.setup_dialog = variable_setup_dialog;
1587   var.control.update_data = variable_update_data;
1588   var.control.data_string = variable_data_string;
1589   var.control.get = minput_get_variable;
1590   var.control.config = minput_config_variable;
1591
1592   cmd.control.data_type_name = _("Key Bindings");
1593   cmd.control.setup_dialog = command_setup_dialog;
1594   cmd.control.update_data = command_update_data;
1595   cmd.control.data_string = command_data_string;
1596   cmd.control.get = minput_get_command;
1597   cmd.control.config = minput_config_command;
1598
1599   store = make_store_for_input_methods ();
1600   tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
1601   g_object_unref (G_OBJECT (store));
1602
1603   renderer = gtk_cell_renderer_text_new ();
1604   column = (gtk_tree_view_column_new_with_attributes
1605             (_("Input Method"), renderer, "text", COL_TAG, NULL));
1606   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
1607
1608   renderer = gtk_cell_renderer_text_new ();
1609   column = (gtk_tree_view_column_new_with_attributes
1610             (_("Status"), renderer, "text", COL_STATUS_STR, NULL));
1611   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
1612
1613   g_signal_connect (G_OBJECT (tree), "row-expanded",
1614                     G_CALLBACK (tree_expanded_cb), NULL);
1615   g_signal_connect (G_OBJECT (tree), "row-activated",
1616                     G_CALLBACK (tree_activated_cb), NULL);
1617
1618   config =gtk_scrolled_window_new (NULL, NULL);
1619   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (config),
1620                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1621   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (config), tree);
1622   g_signal_connect (G_OBJECT (config), "destroy",
1623                     G_CALLBACK (destroy_cb), NULL);
1624
1625   g_object_set_data (G_OBJECT (config), CONFIG_TREE_VIEW, tree);
1626   if (func)
1627     {
1628       MimConfigCallback *callback;
1629
1630       callback = g_new (MimConfigCallback, 1);
1631       callback->widget = config;
1632       callback->func = (MimConfigCallbackFunc) func;
1633       callback->data = data;
1634       g_object_set_data_full (G_OBJECT (tree), CONFIG_CALLBACK_DATA,
1635                               callback, g_free);
1636     }
1637
1638   return config;
1639 }
1640
1641 gboolean
1642 mim_config_modified (GtkWidget *config)
1643 {
1644   GtkTreeView *tree;
1645   GtkTreeModel *model;
1646   MimConfigStatus *config_status;
1647
1648   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1649   model = gtk_tree_view_get_model (tree);
1650   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1651
1652   return (config_status->num_modified > 0 ? TRUE : FALSE);
1653 }
1654
1655 gboolean
1656 mim_config_revert (GtkWidget *config)
1657 {
1658   GtkTreeView *tree;
1659   GtkTreeModel *model;
1660   MimConfigStatus *config_status;
1661
1662   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1663   model = gtk_tree_view_get_model (tree);
1664   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1665
1666   if (config_status->num_modified == 0)
1667     return FALSE;
1668   gtk_tree_model_foreach (model, revert_to_saved, config_status);
1669   return TRUE;
1670 }
1671
1672 gboolean
1673 mim_config_save (GtkWidget *config)
1674 {
1675   GtkTreeView *tree;
1676   GtkTreeModel *model;
1677   MimConfigStatus *config_status;
1678
1679   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1680   model = gtk_tree_view_get_model (tree);
1681   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1682
1683   if (config_status->num_modified == 0)
1684     return FALSE;
1685   minput_save_config ();
1686   gtk_tree_model_foreach (model, set_as_saved, config_status);
1687   return TRUE;
1688 }
1689
1690 GtkTreeView *
1691 mim_config_get_tree_view (GtkWidget *config)
1692 {
1693   GtkTreeView *tree;
1694
1695   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1696   return tree;
1697 }