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