*** 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 reset_to_default (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
436                   gpointer data)
437 {
438   enum MimStatus status, new_status;
439   MSymbol lang, name;
440   MimConfigStatus *config_status = data;
441   MPlist *empty = mplist ();
442
443   gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
444   if (lang == Mnil)
445     return FALSE;
446   gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);
447   if (status == MIM_STATUS_DEFAULT)
448     return FALSE;
449   minput_config_variable (lang, name, Mnil, empty);
450   minput_config_command (lang, name, Mnil, empty);
451   new_status = get_mim_status (lang, name);
452   if (status != new_status)
453     update_child_row (model, iter, new_status, config_status, NULL);
454   return FALSE;
455 }
456
457 static gboolean
458 revert_to_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
459                  gpointer data)
460 {
461   enum MimStatus status;
462   MSymbol lang, name;
463   MimConfigStatus *config_status = data;
464
465   gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
466   if (lang == Mnil)
467     return FALSE;
468   gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
469   if (status != MIM_STATUS_MODIFIED)
470     return FALSE;
471   minput_config_variable (lang, name, Mnil, NULL);
472   minput_config_command (lang, name, Mnil, NULL);
473   status = get_mim_status (lang, name);
474   update_child_row (model, iter, status, config_status, NULL);
475   return FALSE;
476 }
477
478 static gboolean
479 set_as_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
480               gpointer data)
481 {
482   enum MimStatus status;
483   MSymbol lang, name;
484   MimConfigStatus *config_status = data;
485
486   gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
487   if (lang == Mnil)
488     return FALSE;
489   gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
490   if (status != MIM_STATUS_MODIFIED)
491     return FALSE;
492   status = get_mim_status (lang, name);
493   update_child_row (model, iter, status, config_status, NULL);
494   return FALSE;
495 }
496
497 static int initialized = 0;
498
499 static void
500 destroy_cb (GtkWidget *widget, gpointer data)
501 {
502   M17N_FINI ();
503   initialized = 0;
504 }
505
506 \f
507 /****************************************************/
508 /* Configuration of a specific variable or command. */
509 /****************************************************/
510
511 /* Common staffs to variable and command */
512
513 struct ConfigControl
514 {
515   /* Data type name ("Value" or "Key bindings").  */
516   gchar *data_type_name;
517   MSymbol lang, name, item;
518   /* Fill in widgets in DIALOG for configuring a specific variable or
519      command.  */
520   void (*setup_dialog) (GtkWidget *dialog, struct ConfigControl *control);
521   /* Update the contents of DATA widget.  */
522   void (*update_data) (struct ConfigControl *control);
523   /* Convert PLIST to string.  PLIST is a variable value or command
524      key sequeneses.  */
525   GString *(*data_string) (MPlist *plist);
526   /* minput_get_variable or minput_get_command.  */
527   MPlist *(*get) (MSymbol, MSymbol, MSymbol);
528   /* minput_config_variable or minput_config_command.  */
529   int (*config) (MSymbol, MSymbol, MSymbol, MPlist *);
530   /* If non-NULL, a function to call before finishing a dialog.  */
531   gboolean (*config_on_ok) (struct ConfigControl *control);
532
533   /* Widget showing the current data (value or key bindings) */
534   GtkWidget *data;
535
536   /* Button widget to configure the data to the default.  */
537   GtkWidget *default_;
538
539   /* Button widget to cancel the configuration.  */
540   GtkWidget *revert;
541
542   /* Label widget showing the current status.  */
543   GtkWidget *status;
544 };
545
546 struct CommandControl
547 {
548   struct ConfigControl control;
549   GtkWidget *entry;
550   GtkWidget *clear;
551   GtkWidget *add;
552   GtkWidget *delete;
553 };
554
555 enum WidgetType
556   {
557     ENTRY_WIDGET,
558     COMBO_BOX_WIDGET,
559     SPIN_BUTTON_WIDGET
560   };
561
562 struct VariableControl
563 {
564   struct ConfigControl control;
565
566   /* type of current variable: Minteger, Msymbol, or Mtext */
567   MSymbol vtype;
568
569   /* type of widget */
570   enum WidgetType wtype;
571 };
572
573 #define CONFIG_CONTROL(control) ((struct ConfigControl *) (control))
574 #define COMMAND_CONTROL(control) ((struct CommandControl *) (control))
575 #define VARIABLE_CONTROL(control) ((struct VariableControl *) (control))
576
577 #define CURRENT_DATA    \
578   (mplist_next                  \
579    (mplist_next                 \
580     (mplist_next                \
581      (mplist_value              \
582       (control->get (control->lang, control->name, control->item))))))
583
584 #define CURRENT_STATUS          \
585   (mplist_value                 \
586    (mplist_next                 \
587     (mplist_next                \
588      (mplist_value              \
589       (control->get (control->lang, control->name, control->item))))))
590
591 #define CURRENT_DESCRIPTION                                     \
592   (mtext_data                                                   \
593    (mplist_value                                                \
594     (mplist_next                                                \
595      (mplist_value                                              \
596       (control->get (control->lang, control->name, control->item)))),   \
597     NULL,  NULL,  NULL,  NULL))
598
599 #define CONFIG_DATA(plist)                                      \
600   control->config (control->lang, control->name, control->item, \
601                    (plist))
602
603 static MPlist *entry_keyseq;
604
605 static void
606 update_status (struct ConfigControl *control)
607 {
608   MSymbol status = CURRENT_STATUS;
609
610   if (status == Mconfigured)
611     {
612       gtk_label_set_text (GTK_LABEL (control->status),
613                           mim_status_str[MIM_STATUS_MODIFIED]);
614       gtk_widget_set_sensitive (control->default_, TRUE);
615       gtk_widget_set_sensitive (control->revert, TRUE);
616     }
617   else if (status == Mcustomized)
618     {
619       gtk_label_set_text (GTK_LABEL (control->status),
620                           mim_status_str[MIM_STATUS_CUSTOMIZED]);
621       gtk_widget_set_sensitive (control->default_, TRUE);
622       gtk_widget_set_sensitive (control->revert, FALSE);
623     }
624   else
625     {
626       gtk_label_set_text (GTK_LABEL (control->status),
627                           mim_status_str[MIM_STATUS_DEFAULT]);
628       gtk_widget_set_sensitive (control->default_, FALSE);
629       gtk_widget_set_sensitive (control->revert, FALSE);
630     }
631 }
632
633 static void
634 help_cb (GtkButton *button, gpointer data)
635 {
636   struct ConfigControl *control = data;
637   GtkWidget *msg;
638
639   msg = gtk_message_dialog_new (GTK_WINDOW
640                                 (gtk_widget_get_toplevel (GTK_WIDGET (button))),
641                                 GTK_DIALOG_DESTROY_WITH_PARENT,
642                                 GTK_MESSAGE_INFO,
643                                 GTK_BUTTONS_CLOSE,
644                                 CURRENT_DESCRIPTION);
645   gtk_dialog_run (GTK_DIALOG (msg));
646   gtk_widget_destroy (msg);
647 }
648
649 static void
650 default_cb (GtkButton *button, gpointer data)
651 {
652   MPlist *empty = mplist ();
653   struct ConfigControl *control = data;
654
655   CONFIG_DATA (empty);
656   m17n_object_unref (empty);
657   control->update_data (control);
658   update_status (control);
659   control->config_on_ok = NULL;
660 }
661
662 static void
663 revert_cb (GtkButton *button, gpointer data)
664 {
665   struct ConfigControl *control = data;
666
667   CONFIG_DATA (NULL);
668   control->update_data (control);
669   update_status (control);
670   control->config_on_ok = NULL;
671 }
672
673 static void
674 ok_cb (GtkButton *button, gpointer data)
675 {
676   struct ConfigControl *control = data;
677
678   if (control->config_on_ok)
679     {
680       if (! control->config_on_ok (control))
681         {
682           revert_cb (NULL, control);
683           return;
684         }
685       control->config_on_ok = NULL;
686     }
687   if (control->config == minput_config_command)
688     m17n_object_unref (entry_keyseq);
689   gtk_dialog_response (GTK_DIALOG
690                        (gtk_widget_get_toplevel (GTK_WIDGET (button))),
691                        GTK_RESPONSE_OK);
692 }
693
694 enum
695   {
696     /* Variable or command name */
697     CONFIG_COL_ITEM,
698     /* Status (default, modified, or customized).  */
699     CONFIG_COL_STATUS,
700     /* Variable value or command key bindings. */
701     CONFIG_COL_DATA,
702     /* Number of columns of list store.  */
703     NUM_CONFIG_COLS
704   };
705
706
707 static void
708 set_list_element (GtkListStore *store, GtkTreeIter *iter,
709                   struct ConfigControl *control, MPlist *plist)
710 {
711   MSymbol status;
712   gchar *status_str;
713
714   if (! plist)
715     plist = mplist_value (control->get (control->lang, control->name,
716                                         control->item));
717   plist = mplist_next (mplist_next (plist));
718   
719   status = mplist_value (plist);
720   if (status == Mconfigured)
721     status_str = mim_status_str[MIM_STATUS_MODIFIED];
722   else if (status == Mcustomized)
723     status_str = mim_status_str[MIM_STATUS_CUSTOMIZED];
724   else
725     status_str = mim_status_str[MIM_STATUS_DEFAULT];
726   plist = mplist_next (plist);
727   gtk_list_store_set (store, iter,
728                       CONFIG_COL_ITEM, msymbol_name (control->item),
729                       CONFIG_COL_STATUS, status_str,
730                       CONFIG_COL_DATA, control->data_string (plist)->str,
731                       -1);
732 }
733
734 /* Called when an item (command or variable) name is activated.
735    Create a dialog widget to config that itme.  */
736
737 static void
738 item_activated_cb (GtkTreeView *parent, GtkTreePath *path,
739                    GtkTreeViewColumn *col, gpointer data)
740 {
741   GtkTreeModel *model;
742   GtkTreeIter iter;
743   GtkWidget *dialog, *label, *help, *hbox, *ok;
744   gchar *item;
745   struct ConfigControl *control = CONFIG_CONTROL (data);
746
747   model = gtk_tree_view_get_model (parent);
748   if (! gtk_tree_model_get_iter (model, &iter, path))
749     return;
750   gtk_tree_model_get (model, &iter, CONFIG_COL_ITEM, &item, -1);
751   control->item = msymbol (item);
752
753   dialog = (gtk_dialog_new_with_buttons
754             (msymbol_name (control->item),
755              GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (parent))),
756              GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
757              NULL));
758   gtk_button_box_set_layout (GTK_BUTTON_BOX (GTK_DIALOG (dialog)->action_area),
759                              GTK_BUTTONBOX_EDGE);
760   gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
761
762   hbox = gtk_hbox_new (FALSE, 12);
763   gtk_container_set_border_width (GTK_CONTAINER (hbox), 5);
764   label = gtk_label_new (_("Status"));
765   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
766   label = gtk_label_new (": ");
767   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
768   control->status = gtk_label_new (NULL);
769   gtk_box_pack_start (GTK_BOX (hbox), control->status, FALSE, FALSE, 0);
770   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->vbox),
771                     hbox, FALSE, FALSE, 0);
772
773   help = gtk_button_new_from_stock (GTK_STOCK_HELP);
774   g_signal_connect (G_OBJECT (help), "clicked",
775                     G_CALLBACK (help_cb), control);
776   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->action_area),
777                       help, FALSE, FALSE, 0);
778   ok = gtk_button_new_from_stock (GTK_STOCK_OK);
779   g_signal_connect (G_OBJECT (ok), "clicked",
780                     G_CALLBACK (ok_cb), control);
781   gtk_box_pack_end (GTK_BOX (GTK_DIALOG (dialog)->action_area),
782                     ok, FALSE, FALSE, 0);
783
784   control->setup_dialog (dialog, control);
785
786   update_status (control);
787   gtk_widget_show_all (dialog);
788   gtk_dialog_run (GTK_DIALOG (dialog));
789   gtk_tree_model_get_iter (model, &iter, path);
790   set_list_element (GTK_LIST_STORE (model), &iter, control, NULL);
791   gtk_widget_destroy (dialog);
792 }
793
794
795 /* Create a list view widget listing variable or command names with
796    their current status and data.  */
797
798 GtkWidget *
799 create_item_list (MSymbol lang, MSymbol name, struct ConfigControl *control)
800 {
801   GtkListStore *store;
802   GtkWidget *view;
803   MPlist *plist;
804
805   plist = control->get (lang, name, Mnil);
806   /* plist == ((command/variable description status data ...) ...) */
807   if (! plist)
808     return gtk_label_new (_("No customizable item."));
809   store = gtk_list_store_new (NUM_CONFIG_COLS,
810                               G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING);
811   for (; plist && mplist_key (plist) == Mplist; plist = mplist_next (plist))
812     {
813       GtkTreeIter iter;
814       MPlist *pl;
815
816       pl = mplist_value (plist);
817       /* pl == (command/variable description status data ...) */
818       control->item = mplist_value (pl);
819       gtk_list_store_append (store, &iter);
820       set_list_element (store, &iter, control, pl);
821     }
822
823   view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
824   g_object_unref (G_OBJECT (store));
825   gtk_tree_view_insert_column_with_attributes
826     (GTK_TREE_VIEW (view), -1, _("Name"), gtk_cell_renderer_text_new (),
827      "text", CONFIG_COL_ITEM, NULL);
828   gtk_tree_view_insert_column_with_attributes
829     (GTK_TREE_VIEW (view), -1, _("Status"), gtk_cell_renderer_text_new (),
830      "text", CONFIG_COL_STATUS, NULL);
831   gtk_tree_view_insert_column_with_attributes
832     (GTK_TREE_VIEW (view), -1, control->data_type_name,
833      gtk_cell_renderer_text_new (),
834      "text", CONFIG_COL_DATA, NULL);
835   g_signal_connect (G_OBJECT (view), "row-activated",
836                     G_CALLBACK (item_activated_cb), control);
837
838   return view;
839 }
840
841 static struct VariableControl var;
842 static struct CommandControl cmd;
843
844 static void
845 config_im (GtkTreeView *tree, MSymbol lang, MSymbol name)
846 {
847   GtkWidget *dialog, *notebook, *scrolled, *vbox, *label;
848
849   var.control.lang = cmd.control.lang = lang;
850   var.control.name = cmd.control.name = name;
851   var.control.config_on_ok = cmd.control.config_on_ok = NULL;
852
853   dialog = (gtk_dialog_new_with_buttons
854             (name == Mnil ? "global" : msymbol_name (name),
855              GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tree))),
856              GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
857              GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE,
858              NULL));
859   gtk_widget_set_size_request (dialog, 500, 300);
860   gtk_container_set_border_width (GTK_CONTAINER (dialog), 5);
861
862   vbox = gtk_vbox_new (FALSE, 0);
863   gtk_container_set_border_width (GTK_CONTAINER(vbox), 5);
864   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), vbox);
865
866   notebook = gtk_notebook_new ();
867   gtk_container_add (GTK_CONTAINER (vbox), notebook);
868
869   /* Variables' page */
870   scrolled = gtk_scrolled_window_new (NULL, NULL);
871   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
872                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
873   label = gtk_label_new_with_mnemonic (_("_Variables"));
874   gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled, label);
875   vbox = gtk_vbox_new (FALSE, 0);
876   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
877                                          vbox);
878   gtk_box_pack_start (GTK_BOX (vbox),
879                       create_item_list (lang, name, CONFIG_CONTROL (&var)),
880                       FALSE, FALSE, 0);
881
882   /* Commands' pages */
883   scrolled = gtk_scrolled_window_new (NULL, NULL);
884   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
885                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
886   label = gtk_label_new_with_mnemonic (_("Co_mmands"));
887   gtk_notebook_append_page (GTK_NOTEBOOK (notebook), scrolled, label);
888   vbox = gtk_vbox_new (FALSE, 0);
889   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
890                                          vbox);
891   gtk_box_pack_start (GTK_BOX (vbox),
892                       create_item_list (lang, name, CONFIG_CONTROL (&cmd)),
893                       FALSE, FALSE, 0);
894
895   gtk_widget_show_all (dialog);
896   gtk_dialog_run (GTK_DIALOG (dialog));
897   gtk_widget_destroy (dialog);
898 }
899
900 \f
901 /* Staffs for variable configuration.  */
902
903 void
904 variable_update_data (struct ConfigControl *control)
905 {
906   MPlist *plist;
907   MSymbol key;
908   void *value;
909
910   plist = CURRENT_DATA;
911   /* plist == (value [valid-value ...]) */
912   key = mplist_key (plist);
913   value = mplist_value (plist);
914
915   if (VARIABLE_CONTROL (control)->wtype == ENTRY_WIDGET)
916     {
917       if (key == Msymbol)
918         gtk_entry_set_text (GTK_ENTRY (control->data),
919                             msymbol_name ((MSymbol) value));
920       else if (key == Mtext)            
921         /* Fixme : Assuming the return value is in UTF-8 */
922         gtk_entry_set_text (GTK_ENTRY (control->data),
923                             mtext_data ((MText *) value,
924                                         NULL, NULL, NULL, NULL));
925       else                      /* key == Minteger */
926         {
927           gchar buf[32];
928           g_snprintf (buf, sizeof (buf), "%d", (gint) value);
929           gtk_entry_set_text (GTK_ENTRY (control->data), buf);
930         }
931     }
932   else if (VARIABLE_CONTROL (control)->wtype == COMBO_BOX_WIDGET)
933     {
934       gint i;
935
936       for (i = 0, plist = mplist_next (plist);
937            plist && mplist_key (plist) == key;
938            i++, plist = mplist_next (plist))
939         if (mplist_value (plist) == value)
940           break;
941       gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), i);
942     }
943   else                          /* ci->wtype == SPIN_BUTTON_WIDGET */
944     gtk_spin_button_set_value (GTK_SPIN_BUTTON (control->data),
945                                (gdouble) (int) value);
946 }
947
948 static gboolean
949 config_with_entry (struct ConfigControl *control)
950 {
951   const gchar *text = gtk_entry_get_text (GTK_ENTRY (control->data));
952   MPlist *plist = mplist ();
953   gboolean ret = TRUE;
954
955   if (VARIABLE_CONTROL (control)->vtype == Msymbol)
956     {
957       mplist_add (plist, Msymbol, msymbol (text));
958       CONFIG_DATA (plist);
959     }
960   else if (VARIABLE_CONTROL (control)->vtype == Mtext)
961     {
962       MText *mt;
963
964       mt = mconv_decode_buffer (Mcoding_utf_8, (guchar *) text, strlen (text));
965       mplist_add (plist, Mtext, mt);
966       CONFIG_DATA (plist);
967       m17n_object_unref (mt);
968     }
969   else             /* VARIABLE_CONTROL (control)->vtype == Minteger */
970     {
971       int i;
972
973       if (sscanf (text, "%d", &i) == 1)
974         {
975           mplist_add (plist, Minteger, (void *) i);
976           CONFIG_DATA (plist);
977         }
978       else
979         {
980           GtkWidget *msg;
981
982           msg = gtk_message_dialog_new (GTK_WINDOW
983                                         (gtk_widget_get_toplevel (control->data)),
984                                         GTK_DIALOG_DESTROY_WITH_PARENT,
985                                         GTK_MESSAGE_ERROR,
986                                         GTK_BUTTONS_CLOSE,
987                                         _("The value must be an integer."));
988           gtk_dialog_run (GTK_DIALOG (msg));
989           gtk_widget_destroy (msg);
990           ret = FALSE;
991         }
992     }
993
994   m17n_object_unref (plist);
995   return ret;
996 }
997
998 static gboolean
999 config_with_combo (struct ConfigControl *control)
1000 {
1001   gchar *text = gtk_combo_box_get_active_text (GTK_COMBO_BOX (control->data));
1002   MPlist *plist = mplist ();
1003
1004   if (VARIABLE_CONTROL (control)->vtype == Msymbol)
1005     {
1006       mplist_add (plist, Msymbol, msymbol (text));
1007       CONFIG_DATA (plist);
1008     }
1009   else if (VARIABLE_CONTROL (control)->vtype == Mtext)
1010     {
1011       MText *mt;
1012
1013       mt = mconv_decode_buffer (Mcoding_utf_8, (guchar *) text, strlen (text));
1014       mplist_add (plist, Mtext, mt);
1015       CONFIG_DATA (plist);
1016       m17n_object_unref (mt);
1017     }
1018   else             /* VARIABLE_CONTROL (control)->vtype == Minteger */
1019     {
1020       int i;
1021
1022       sscanf (text, "%d", &i);
1023       mplist_add (plist, Minteger, (void *) i);
1024       CONFIG_DATA (plist);
1025     }
1026   m17n_object_unref (plist);
1027   return TRUE;
1028 }
1029
1030 static gboolean
1031 config_with_spin (struct ConfigControl *control)
1032 {
1033   gint i = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (control->data));
1034   MPlist *plist = mplist ();
1035
1036   mplist_add (plist, Minteger, (void *) i);
1037   CONFIG_DATA (plist);
1038   m17n_object_unref (plist);
1039   return TRUE;
1040 }
1041
1042 static void
1043 changed_cb (GtkEntry *entry, gpointer data)
1044 {
1045   struct ConfigControl *control = data;
1046
1047   gtk_widget_set_sensitive (control->default_, TRUE);
1048   gtk_widget_set_sensitive (control->revert, TRUE);
1049   gtk_label_set_text (GTK_LABEL (control->status), _("modified"));
1050   if (VARIABLE_CONTROL (control)->wtype == ENTRY_WIDGET)
1051     control->config_on_ok = config_with_entry;
1052   else if (VARIABLE_CONTROL (control)->wtype == COMBO_BOX_WIDGET)
1053     control->config_on_ok = config_with_combo;
1054   else
1055     control->config_on_ok = config_with_spin;
1056 }
1057
1058 static GString *
1059 variable_data_string (MPlist *plist)
1060 {
1061   static GString *str;
1062
1063   if (! str)
1064     str = g_string_sized_new (80);  
1065   else
1066     g_string_truncate (str, 0);
1067
1068   if (mplist_key (plist) == Msymbol)
1069     g_string_append (str, msymbol_name ((MSymbol) mplist_value (plist)));
1070   else if (mplist_key (plist) == Mtext)
1071     /* Fixme : Assuming the return value is in UTF-8 */
1072     g_string_append (str, mtext_data ((MText *) mplist_value (plist),
1073                                       NULL, NULL, NULL, NULL));
1074   else /* mplist_key (plist) == Minteger */
1075     g_string_append_printf (str, "%d", (gint) mplist_value (plist));
1076   return str;
1077 }
1078
1079 static void
1080 variable_setup_dialog (GtkWidget *dialog, struct ConfigControl *control)
1081 {
1082   MPlist *plist; 
1083   void *value;
1084   GtkWidget *hbox, *vbox;
1085
1086   plist = CURRENT_DATA;
1087   VARIABLE_CONTROL (control)->vtype = mplist_key (plist);
1088   value = mplist_value (plist);
1089   plist = mplist_next (plist);
1090
1091   if (VARIABLE_CONTROL (control)->vtype == Msymbol)
1092     {
1093       if (mplist_key (plist) == Msymbol)
1094         {
1095           gint i, nth;
1096
1097           control->data = gtk_combo_box_new_text ();
1098           VARIABLE_CONTROL (control)->wtype = COMBO_BOX_WIDGET;
1099           for (i = 0; mplist_key (plist) == Msymbol;
1100                plist = mplist_next (plist), i++)
1101             {
1102               if (mplist_value (plist) == value)
1103                 nth = i;
1104               gtk_combo_box_append_text
1105                 (GTK_COMBO_BOX (control->data),
1106                  msymbol_name ((MSymbol) mplist_value (plist)));
1107             }
1108           gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), nth);
1109           g_signal_connect (GTK_OBJECT (control->data), "changed",
1110                             G_CALLBACK (changed_cb), control);
1111         }
1112       else
1113         {
1114           control->data = gtk_entry_new ();
1115           VARIABLE_CONTROL (control)->wtype = ENTRY_WIDGET;
1116           gtk_entry_set_text (GTK_ENTRY (control->data), msymbol_name (value));
1117           gtk_editable_set_editable (GTK_EDITABLE (control->data), TRUE);
1118           g_signal_connect (GTK_OBJECT (control->data), "changed",
1119                             G_CALLBACK (changed_cb), control);
1120           g_signal_connect (GTK_OBJECT (control->data), "activate",
1121                             G_CALLBACK (ok_cb), control);
1122         }
1123     }
1124   else if (VARIABLE_CONTROL (control)->vtype == Mtext)
1125     {
1126       if (plist && mplist_key (plist) == Mtext)
1127         {
1128           gint i, nth;
1129
1130           control->data = gtk_combo_box_new_text ();
1131           VARIABLE_CONTROL (control)->wtype = COMBO_BOX_WIDGET;
1132           for (i = 0; plist && mplist_key (plist) == Mtext;
1133                plist = mplist_next (plist), i++)
1134             {
1135               if (! mtext_cmp ((MText *) mplist_value (plist),
1136                                (MText *) value))
1137                 nth = i;
1138               /* Fixme : Assuming the return value is in UTF-8 */
1139               gtk_combo_box_append_text
1140                 (GTK_COMBO_BOX (control->data),
1141                  mtext_data ((MText *) mplist_value (plist),
1142                              NULL, NULL, NULL, NULL));
1143             }
1144           gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), nth);
1145           g_signal_connect (GTK_OBJECT (control->data), "changed",
1146                             G_CALLBACK (changed_cb), control);
1147         }
1148       else
1149         {
1150           control->data = gtk_entry_new ();
1151           VARIABLE_CONTROL (control)->wtype = ENTRY_WIDGET;
1152           /* Fixme : Assuming the return value is in UTF-8 */
1153           gtk_entry_set_text (GTK_ENTRY (control->data),
1154                               mtext_data (value, NULL, NULL, NULL, NULL));
1155           gtk_editable_set_editable (GTK_EDITABLE (control->data), TRUE);
1156           g_signal_connect (GTK_OBJECT (control->data), "changed",
1157                             G_CALLBACK (changed_cb), control);
1158           g_signal_connect (GTK_OBJECT (control->data), "activate",
1159                             G_CALLBACK (ok_cb), control);
1160         }
1161     }
1162   else                          /* control->vtype == Minteger */
1163     {
1164       if (plist && mplist_key (plist) == Minteger)
1165         {
1166           gint i, nth;
1167
1168           control->data = gtk_combo_box_new_text ();
1169           VARIABLE_CONTROL (control)->wtype = COMBO_BOX_WIDGET;
1170           for (i = 0; plist && mplist_key (plist) == Minteger;
1171                plist = mplist_next (plist), i++)
1172             {
1173               gchar buf[32];
1174
1175               if (mplist_value (plist) == value)
1176                 nth = i;
1177               g_snprintf (buf, sizeof (buf), "%d",
1178                           (gint) mplist_value (plist));
1179               gtk_combo_box_append_text (GTK_COMBO_BOX (control->data), buf);
1180             }
1181           gtk_combo_box_set_active (GTK_COMBO_BOX (control->data), nth);
1182           g_signal_connect (GTK_OBJECT (control->data), "changed",
1183                             G_CALLBACK (changed_cb), control);
1184         }
1185       else if (plist && mplist_key (plist) == Mplist)
1186         {
1187           GtkObject *adj;
1188           gdouble lower, upper;
1189
1190           plist = mplist_value (plist);
1191           lower = (gdouble) (int) mplist_value (plist);
1192           upper = (gdouble) (int) mplist_value (mplist_next (plist));
1193           adj = gtk_adjustment_new ((gdouble) (int) value, lower, upper,
1194                                     1.0, 10.0, 0);
1195           control->data = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 0, 0);
1196           VARIABLE_CONTROL (control)->wtype = SPIN_BUTTON_WIDGET;
1197           gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (control->data), TRUE);
1198           gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (control->data),
1199                                              GTK_UPDATE_ALWAYS);
1200           g_signal_connect (GTK_OBJECT (control->data), "changed",
1201                             G_CALLBACK (changed_cb), control);
1202         }
1203       else
1204         {
1205           gchar buf[32];
1206
1207           control->data = gtk_entry_new ();
1208           VARIABLE_CONTROL (control)->wtype = ENTRY_WIDGET;
1209           g_snprintf (buf, sizeof (buf), "%d", (gint) value);
1210           gtk_entry_set_text (GTK_ENTRY (control->data), buf);
1211           gtk_editable_set_editable (GTK_EDITABLE (control->data), TRUE);
1212           g_signal_connect (GTK_OBJECT (control->data), "changed",
1213                             G_CALLBACK (changed_cb), control);
1214           g_signal_connect (GTK_OBJECT (control->data), "activate",
1215                             G_CALLBACK (ok_cb), control);
1216         }
1217     }
1218
1219   control->default_ = gtk_button_new_from_stock (_("_Default"));
1220   g_signal_connect (G_OBJECT (control->default_), "clicked",
1221                     G_CALLBACK (default_cb), control);
1222
1223   control->revert = gtk_button_new_from_stock (GTK_STOCK_REVERT_TO_SAVED);
1224   g_signal_connect (G_OBJECT (control->revert), "clicked",
1225                     G_CALLBACK (revert_cb), control);
1226
1227   hbox = gtk_hbutton_box_new ();
1228   gtk_box_set_spacing (GTK_BOX (hbox), 6);
1229   gtk_container_add (GTK_CONTAINER (hbox), control->default_);
1230   gtk_container_add (GTK_CONTAINER (hbox), control->revert);
1231   vbox = gtk_vbox_new (FALSE, 12);
1232   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
1233   gtk_box_pack_start (GTK_BOX (vbox), control->data, FALSE, FALSE, 0);
1234   gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
1235   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1236                       vbox, FALSE, FALSE, 0);
1237   gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 6);
1238 }
1239
1240 \f
1241 /* Staffs for command configuration.  */
1242
1243 static void
1244 selection_cb (GtkTreeSelection *selection, gpointer data)
1245 {
1246   gtk_widget_set_sensitive
1247     (COMMAND_CONTROL (data)->delete,
1248      gtk_tree_selection_count_selected_rows (selection) ? TRUE : FALSE);
1249 }
1250
1251 static void
1252 delete_cb (GtkButton *button, gpointer data)
1253 {
1254   GtkTreeSelection *selection;
1255   GtkTreeModel *model;
1256   GtkTreeIter iter;
1257   MPlist *pl, *new;
1258   struct ConfigControl *control = data;
1259
1260   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (control->data));
1261   model = gtk_tree_view_get_model (GTK_TREE_VIEW (control->data));
1262
1263   if (! gtk_tree_model_get_iter_first (model, &iter))
1264     return;
1265
1266   new = mplist ();
1267   for (pl = CURRENT_DATA; mplist_key (pl) != Mnil; pl = mplist_next (pl))
1268     {
1269       if (! gtk_tree_selection_iter_is_selected (selection, &iter))
1270         mplist_add (new, Mplist, mplist_value (pl));
1271       gtk_tree_model_iter_next (model, &iter);
1272     }
1273   CONFIG_DATA (new);
1274   m17n_object_unref (new);
1275   control->update_data (control);
1276   update_status (control);
1277 }
1278
1279 static GtkWidget *
1280 create_deleting_section (struct ConfigControl *control)
1281 {
1282   struct CommandControl *cmd_control = COMMAND_CONTROL (control);
1283   GtkListStore *store;
1284   GtkWidget *label, *scrolled, *hbox, *vbox;
1285   GtkTreeViewColumn *column;
1286   GtkCellRenderer *renderer;
1287   GtkTreeSelection *selection;
1288
1289   label = gtk_label_new (_("Current key bindings:"));
1290
1291   store = gtk_list_store_new (1, G_TYPE_STRING);
1292   control->data = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
1293   g_object_unref (G_OBJECT (store));
1294   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (control->data), FALSE);
1295   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (control->data));
1296   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
1297   g_signal_connect (G_OBJECT (selection), "changed",
1298                     G_CALLBACK (selection_cb), control);
1299
1300   scrolled = gtk_scrolled_window_new (NULL, NULL);
1301   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
1302                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1303   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
1304                                          control->data);
1305
1306   column = gtk_tree_view_column_new ();
1307   gtk_tree_view_append_column (GTK_TREE_VIEW (control->data), column);
1308   renderer = gtk_cell_renderer_text_new ();
1309   gtk_tree_view_column_pack_start (column, renderer, TRUE);
1310   gtk_tree_view_column_set_attributes (column, renderer, "text", 0, NULL);
1311
1312   control->update_data (control);
1313
1314   control->default_ = gtk_button_new_from_stock (_("_Default"));
1315   g_signal_connect (G_OBJECT (control->default_), "clicked",
1316                     G_CALLBACK (default_cb), control);
1317
1318   control->revert = gtk_button_new_from_stock (GTK_STOCK_REVERT_TO_SAVED);
1319   g_signal_connect (G_OBJECT (control->revert), "clicked",
1320                     G_CALLBACK (revert_cb), control);
1321
1322   cmd_control->delete = gtk_button_new_from_stock (GTK_STOCK_DELETE);
1323   gtk_widget_set_sensitive (cmd_control->delete, FALSE);
1324   g_signal_connect (G_OBJECT (cmd_control->delete), "clicked",
1325                     G_CALLBACK (delete_cb), control);
1326
1327   vbox = gtk_vbox_new (FALSE, 12);
1328   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
1329
1330   hbox = gtk_hbox_new (FALSE, 6);
1331   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
1332   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1333
1334   gtk_container_add (GTK_CONTAINER (vbox), scrolled);
1335
1336   hbox = gtk_hbutton_box_new ();
1337   gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
1338   gtk_box_set_spacing (GTK_BOX (hbox), 6);
1339   gtk_container_add (GTK_CONTAINER (hbox), control->default_);
1340   gtk_container_add (GTK_CONTAINER (hbox), control->revert);
1341   gtk_container_add (GTK_CONTAINER (hbox), cmd_control->delete);
1342   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1343
1344   return vbox;
1345 }
1346
1347 static unsigned modifier_state = 0;
1348
1349 enum KeyMaskBit {
1350   META_MASK_BIT = 1,
1351   ALT_MASK_BIT = META_MASK_BIT << 1,
1352   SUPER_MASK_BIT = ALT_MASK_BIT << 1,
1353   HYPER_MASK_BIT = SUPER_MASK_BIT << 1
1354 };
1355
1356 static void
1357 update_entry (GtkEntry *entry)
1358 {
1359   if (mplist_key (entry_keyseq) == Mnil)
1360     gtk_entry_set_text (entry, "");
1361   else
1362     {
1363       MPlist *p;
1364       gchar *name;
1365
1366       name = msymbol_name ((MSymbol) mplist_value (entry_keyseq));
1367       gtk_entry_set_text (entry, name);
1368       for (p = mplist_next (entry_keyseq); mplist_key (p) != Mnil;
1369            p = mplist_next (p))
1370         {
1371           name = msymbol_name ((MSymbol) mplist_value (p));
1372           gtk_entry_append_text (entry, " ");
1373           gtk_entry_append_text (entry, name);
1374         }
1375       gtk_editable_set_position (GTK_EDITABLE (entry), -1);
1376     }
1377 }
1378
1379 static gboolean
1380 key_pressed_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
1381 {
1382   guint c;
1383   MText *mt;
1384   char buf[32];
1385   char *name;
1386   int nbytes, i;
1387   struct CommandControl *cmd_control = data;
1388
1389   c = gdk_keyval_to_unicode (event->keyval);
1390   if (c == 0)
1391     {
1392       switch (event->keyval)
1393         {
1394         case GDK_Meta_L: case GDK_Meta_R:
1395           modifier_state |= META_MASK_BIT; return TRUE;
1396         case GDK_Alt_L: case GDK_Alt_R:
1397           modifier_state |= ALT_MASK_BIT; return TRUE;
1398         case GDK_Super_L: case GDK_Super_R:
1399           modifier_state |= SUPER_MASK_BIT; return TRUE;
1400         case GDK_Hyper_L: case GDK_Hyper_R:
1401           modifier_state |= HYPER_MASK_BIT; return TRUE;
1402         default:
1403           if (event->keyval >= GDK_Shift_L && event->keyval <= GDK_Shift_Lock)
1404             return TRUE;
1405         }
1406       name = gdk_keyval_name (event->keyval);
1407       if (! name)
1408         return TRUE;
1409       nbytes = strlen (name);
1410     }
1411   else
1412     {
1413       name = alloca (8);
1414       mt = mtext ();
1415       mtext_cat_char (mt, c);
1416       nbytes = mconv_encode_buffer (msymbol ("utf-8"), mt,
1417                                     (unsigned char *) name, 32);
1418       m17n_object_unref (mt);
1419     }
1420   i = 0;
1421   if (c == 0 && event->state & GDK_SHIFT_MASK)
1422     buf[i++] = 'S', buf[i++] = '-';
1423   if (event->state & GDK_CONTROL_MASK)
1424     buf[i++] = 'C', buf[i++] = '-';
1425   if (modifier_state & META_MASK_BIT)
1426     buf[i++] = 'M', buf[i++] = '-';
1427   if (modifier_state & ALT_MASK_BIT)
1428     buf[i++] = 'A', buf[i++] = '-';
1429   if (modifier_state & SUPER_MASK_BIT)
1430     buf[i++] = 's', buf[i++] = '-';
1431   if (modifier_state & HYPER_MASK_BIT)
1432     buf[i++] = 'H', buf[i++] = '-';
1433   strncpy (buf + i, name, nbytes);
1434   buf[i + nbytes] = 0;
1435   mplist_add (entry_keyseq, Msymbol, msymbol (buf));
1436   update_entry (entry);
1437   gtk_widget_set_sensitive (cmd_control->clear, TRUE);
1438   gtk_widget_set_sensitive (cmd_control->add, TRUE);
1439   return TRUE;
1440 }
1441
1442 static gboolean
1443 key_released_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
1444 {
1445   guint c;
1446
1447   c = gdk_keyval_to_unicode (event->keyval);
1448   if (c == 0)
1449     {
1450       switch (event->keyval)
1451         {
1452         case GDK_Meta_L: case GDK_Meta_R:
1453           modifier_state &= ~META_MASK_BIT; break;
1454         case GDK_Alt_L: case GDK_Alt_R:
1455           modifier_state &= ~ALT_MASK_BIT; break;
1456         case GDK_Super_L: case GDK_Super_R:
1457           modifier_state &= ~SUPER_MASK_BIT; break;
1458         case GDK_Hyper_L: case GDK_Hyper_R:
1459           modifier_state &= ~HYPER_MASK_BIT; break;
1460         }
1461     }
1462   return FALSE;
1463 }
1464
1465 static void
1466 clear_cb (GtkButton *button, gpointer data)
1467 {
1468   struct CommandControl *cmd_control = data;
1469
1470   mplist_set (entry_keyseq, Mnil, NULL);
1471   gtk_widget_grab_focus (cmd_control->entry);
1472   update_entry (GTK_ENTRY (cmd_control->entry));
1473   gtk_widget_set_sensitive (cmd_control->clear, FALSE);
1474   gtk_widget_set_sensitive (cmd_control->add, FALSE);
1475 }
1476
1477 static void
1478 add_cb (GtkButton *button, gpointer data)
1479 {
1480   MPlist *new;
1481   GtkTreeModel *model;
1482   GtkTreeIter iter;
1483   struct ConfigControl *control = data;
1484
1485   if (mplist_length (entry_keyseq) == 0)
1486     return;
1487   model = gtk_tree_view_get_model (GTK_TREE_VIEW (control->data));
1488   if (gtk_tree_model_get_iter_first (model, &iter))
1489     {
1490       gchar *keyseq = control->data_string (entry_keyseq)->str;
1491       gchar *str;
1492
1493       do {
1494         gtk_tree_model_get (model, &iter, 0, &str, -1);
1495         if (strcmp (keyseq, str) == 0)
1496           /* entry_keyseq is already registered. */
1497           return;
1498       } while (gtk_tree_model_iter_next (model, &iter));
1499     }
1500   new = mplist_copy (CURRENT_DATA);
1501   mplist_add (new, Mplist, entry_keyseq);
1502   CONFIG_DATA (new);
1503   m17n_object_unref (new);
1504   control->update_data (control);
1505   update_status (control);
1506   clear_cb (NULL, control);
1507 }
1508
1509 static GtkWidget *
1510 create_adding_section (struct ConfigControl *control)
1511 {
1512   struct CommandControl *cmd_control = COMMAND_CONTROL (control);
1513   GtkWidget *label, *hbox, *vbox;
1514
1515   label = gtk_label_new (_("New key binding:"));
1516
1517   entry_keyseq = mplist ();
1518   cmd_control->entry = gtk_entry_new ();
1519   g_signal_connect (G_OBJECT (cmd_control->entry), "key-press-event",
1520                     G_CALLBACK (key_pressed_cb), cmd_control);
1521   g_signal_connect (G_OBJECT (cmd_control->entry), "key-release-event",
1522                     G_CALLBACK (key_released_cb), cmd_control);
1523
1524   cmd_control->clear = gtk_button_new_from_stock (GTK_STOCK_CLEAR);
1525   gtk_widget_set_sensitive (cmd_control->clear, FALSE);
1526   g_signal_connect (G_OBJECT (cmd_control->clear), "clicked",
1527                     G_CALLBACK (clear_cb), cmd_control);
1528
1529   cmd_control->add = gtk_button_new_from_stock (GTK_STOCK_ADD);
1530   gtk_widget_set_sensitive (cmd_control->add, FALSE);
1531   g_signal_connect (G_OBJECT (cmd_control->add), "clicked",
1532                     G_CALLBACK (add_cb), cmd_control);
1533
1534   vbox = gtk_vbox_new (FALSE, 12);
1535   gtk_container_set_border_width (GTK_CONTAINER (vbox), 5);
1536
1537   hbox = gtk_hbox_new (FALSE, 6);
1538   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
1539   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1540
1541   gtk_container_add (GTK_CONTAINER (vbox), cmd_control->entry);
1542
1543   hbox = gtk_hbutton_box_new ();
1544   gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
1545   gtk_box_set_spacing (GTK_BOX (hbox), 6);
1546   gtk_container_add (GTK_CONTAINER (hbox), cmd_control->clear);
1547   gtk_container_add (GTK_CONTAINER (hbox), cmd_control->add);
1548   gtk_container_add (GTK_CONTAINER (vbox), hbox);
1549
1550   return vbox;
1551 }
1552
1553 static void
1554 append_key_sequence (GString *str, MPlist *keyseq)
1555 {
1556   static MSymbol space_symbol;
1557   MPlist *p;
1558
1559   if (! space_symbol)
1560     space_symbol = msymbol (" ");
1561
1562   for (p = keyseq ; mplist_key (p) != Mnil; p = mplist_next (p))
1563     {
1564       MSymbol key = (MSymbol) mplist_value (p);
1565
1566       if (p != keyseq)
1567         g_string_append_c (str, ' ');
1568       if (key == space_symbol)
1569         g_string_append (str, "Space");
1570       else
1571         g_string_append (str, msymbol_name (key));
1572     }
1573 }
1574
1575 static GString *
1576 command_data_string (MPlist *plist)
1577 {
1578   static GString *str;
1579
1580   if (! str)
1581     str = g_string_sized_new (80);  
1582   else
1583     g_string_truncate (str, 0);
1584
1585   if (mplist_key (plist) == Mplist)
1586     {
1587       MPlist *pl;
1588
1589       /* PLIST == ((KEY KEY ...) ...) */
1590       for (pl = plist; mplist_key (pl) != Mnil; pl = mplist_next (pl))
1591         {
1592           if (pl != plist)
1593             g_string_append (str, ", ");
1594           append_key_sequence (str, mplist_value (pl));
1595         }
1596     }
1597   else
1598     {
1599       /* PLIST == (KEY KEY ...) */
1600       append_key_sequence (str, plist);
1601     }
1602   return str;
1603 }
1604
1605 static void
1606 command_update_data (struct ConfigControl *control)
1607 {
1608   GtkTreeView *tree = GTK_TREE_VIEW (control->data);
1609   GtkListStore *store = GTK_LIST_STORE (gtk_tree_view_get_model (tree));
1610   GtkTreeIter iter;
1611   MPlist *pl;
1612
1613   gtk_list_store_clear (store);
1614   for (pl = CURRENT_DATA; mplist_key (pl) != Mnil; pl = mplist_next (pl))
1615     {
1616       gtk_list_store_append (store, &iter);
1617       gtk_list_store_set (store, &iter,
1618                           0, control->data_string (mplist_value (pl))->str,
1619                           -1);
1620     }
1621 }
1622
1623 static void
1624 command_setup_dialog (GtkWidget *dialog, struct ConfigControl *control)
1625 {
1626   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1627                       create_deleting_section (control), FALSE, FALSE, 0);
1628   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
1629                       create_adding_section (control), FALSE, FALSE, 0);
1630   gtk_box_set_spacing (GTK_BOX (GTK_DIALOG (dialog)->vbox), 6);
1631 }
1632
1633 \f
1634 /* Public API */
1635
1636 GtkWidget *
1637 mim_config_new (GCallback func, gpointer data)
1638 {
1639   GtkWidget *tree, *config;
1640   GtkTreeStore *store;
1641   GtkCellRenderer *renderer;
1642   GtkTreeViewColumn *column;
1643
1644   if (initialized)
1645     return NULL;
1646   M17N_INIT ();
1647   if (merror_code < 0)
1648     return NULL;
1649
1650   initialized = 1;
1651
1652 #if ENABLE_NLS
1653   bindtextdomain ("m17n-im-config", GETTEXTDIR);
1654   bind_textdomain_codeset ("m17n-im-config", "UTF-8");
1655 #endif
1656
1657   mim_status_str[MIM_STATUS_DEFAULT] = _("default");
1658   mim_status_str[MIM_STATUS_CUSTOMIZED] = _("customized");
1659   mim_status_str[MIM_STATUS_MODIFIED] = _("modified");
1660   mim_status_str[MIM_STATUS_NO] = _("uncustomizable");
1661
1662   var.control.data_type_name = _("Value");
1663   var.control.setup_dialog = variable_setup_dialog;
1664   var.control.update_data = variable_update_data;
1665   var.control.data_string = variable_data_string;
1666   var.control.get = minput_get_variable;
1667   var.control.config = minput_config_variable;
1668
1669   cmd.control.data_type_name = _("Key Bindings");
1670   cmd.control.setup_dialog = command_setup_dialog;
1671   cmd.control.update_data = command_update_data;
1672   cmd.control.data_string = command_data_string;
1673   cmd.control.get = minput_get_command;
1674   cmd.control.config = minput_config_command;
1675
1676   store = make_store_for_input_methods ();
1677   tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
1678   g_object_unref (G_OBJECT (store));
1679
1680   renderer = gtk_cell_renderer_text_new ();
1681   column = (gtk_tree_view_column_new_with_attributes
1682             (_("Input Method"), renderer, "text", COL_TAG, NULL));
1683   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
1684
1685   renderer = gtk_cell_renderer_text_new ();
1686   column = (gtk_tree_view_column_new_with_attributes
1687             (_("Status"), renderer, "text", COL_STATUS_STR, NULL));
1688   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
1689
1690   g_signal_connect (G_OBJECT (tree), "row-expanded",
1691                     G_CALLBACK (tree_expanded_cb), NULL);
1692   g_signal_connect (G_OBJECT (tree), "row-activated",
1693                     G_CALLBACK (tree_activated_cb), NULL);
1694
1695   config =gtk_scrolled_window_new (NULL, NULL);
1696   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (config),
1697                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1698   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (config), tree);
1699   g_signal_connect (G_OBJECT (config), "destroy",
1700                     G_CALLBACK (destroy_cb), NULL);
1701
1702   g_object_set_data (G_OBJECT (config), CONFIG_TREE_VIEW, tree);
1703   if (func)
1704     {
1705       MimConfigCallback *callback;
1706
1707       callback = g_new (MimConfigCallback, 1);
1708       callback->widget = config;
1709       callback->func = (MimConfigCallbackFunc) func;
1710       callback->data = data;
1711       g_object_set_data_full (G_OBJECT (tree), CONFIG_CALLBACK_DATA,
1712                               callback, g_free);
1713     }
1714
1715   return config;
1716 }
1717
1718 gboolean
1719 mim_config_modified (GtkWidget *config)
1720 {
1721   GtkTreeView *tree;
1722   GtkTreeModel *model;
1723   MimConfigStatus *config_status;
1724
1725   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1726   model = gtk_tree_view_get_model (tree);
1727   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1728
1729   return (config_status->num_modified > 0 ? TRUE : FALSE);
1730 }
1731
1732 gboolean
1733 mim_config_default (GtkWidget *config)
1734 {
1735   GtkTreeView *tree;
1736   GtkTreeModel *model;
1737   MimConfigStatus *config_status;
1738
1739   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1740   model = gtk_tree_view_get_model (tree);
1741   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1742   gtk_tree_model_foreach (model, reset_to_default, config_status);
1743   return TRUE;
1744 }
1745
1746 gboolean
1747 mim_config_revert (GtkWidget *config)
1748 {
1749   GtkTreeView *tree;
1750   GtkTreeModel *model;
1751   MimConfigStatus *config_status;
1752
1753   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1754   model = gtk_tree_view_get_model (tree);
1755   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1756
1757   if (config_status->num_modified == 0)
1758     return FALSE;
1759   gtk_tree_model_foreach (model, revert_to_saved, config_status);
1760   return TRUE;
1761 }
1762
1763 gboolean
1764 mim_config_save (GtkWidget *config)
1765 {
1766   GtkTreeView *tree;
1767   GtkTreeModel *model;
1768   MimConfigStatus *config_status;
1769
1770   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1771   model = gtk_tree_view_get_model (tree);
1772   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
1773
1774   if (config_status->num_modified == 0)
1775     return FALSE;
1776   minput_save_config ();
1777   gtk_tree_model_foreach (model, set_as_saved, config_status);
1778   return TRUE;
1779 }
1780
1781 GtkTreeView *
1782 mim_config_get_tree_view (GtkWidget *config)
1783 {
1784   GtkTreeView *tree;
1785
1786   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
1787   return tree;
1788 }