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