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