*** empty log message ***
[m17n/m17n-im-config.git] / src / mim-config.c
1 #include <stdlib.h>
2 #include <string.h>
3 #include <libintl.h>
4 #include <m17n.h>
5 #include <gtk/gtk.h>
6 #include <config.h>
7 #include "m17n-im-config.h"
8
9 #define _(String) dgettext (PACKAGE, String)
10
11 #define CONFIG_CALLBACK_DATA " config-callback-data"
12 #define CONFIG_STATUS_DATA " config-status-data"
13 #define CONFIG_TREE_VIEW "  config-tree-view"
14
15 typedef struct _MimConfigStatus
16 {
17   /* Number of available input methods.  */
18   gint num_im;
19   /* Number of modified input methods.  */
20   gint num_modified;
21 } MimConfigStatus;
22
23 /* Status of variables and commands of an input method.  */
24
25 enum MimStatus
26   {
27     MIM_STATUS_DEFAULT,
28     MIM_STATUS_CUSTOMIZED,
29     MIM_STATUS_MODIFIED,
30     MIM_STATUS_NO,
31     MIM_STATUS_MAX
32   };
33
34 static char *mim_status_str[MIM_STATUS_MAX];
35
36 enum MimStatus
37 get_mim_status (MSymbol lang, MSymbol name)
38 {
39   MPlist *plist;
40   enum MimStatus status = MIM_STATUS_NO;
41
42   for (plist = minput_get_variable (lang, name, Mnil);
43        plist && mplist_key (plist) != Mnil; plist = mplist_next (plist))
44     {
45       MPlist *p = mplist_value (plist);
46       MSymbol status_symbol;
47
48       status = MIM_STATUS_DEFAULT;
49       p = mplist_next (mplist_next (p));
50       status_symbol = mplist_value (p);
51       if (status_symbol != Mnil && status_symbol != Minherited)
52         return (status_symbol == Mcustomized
53                 ? MIM_STATUS_CUSTOMIZED : MIM_STATUS_MODIFIED);
54     }
55   for (plist = minput_get_command (lang, name, Mnil);
56        plist && mplist_key (plist) != Mnil; plist = mplist_next (plist))
57     {
58       MPlist *p = mplist_value (plist);
59       MSymbol status_symbol;
60
61       status = MIM_STATUS_DEFAULT;
62       p = mplist_next (mplist_next (p));
63       status_symbol = mplist_value (p);
64       if (status_symbol != Mnil && status_symbol != Minherited)
65         return (status_symbol == Mcustomized
66                 ? MIM_STATUS_CUSTOMIZED : MIM_STATUS_MODIFIED);
67     }
68   return status;
69 }
70
71 /* Columns of each row.  */
72 enum
73   {
74     /* parent: language name
75         child: IM name  */
76     COL_TAG = 0,
77     /* parent: NULL or "modified"
78         child: "default", "customized", or "modified"  */
79     COL_STATUS_STR,
80     /* parent: num of modified children
81         child: enum MimStatus */
82     COL_STATUS,
83     /* parent: Mnil
84         child: symbolic language name.  */
85     COL_LANG,
86     /* parent: Mnil
87         child: symbolic IM name.  */
88     COL_NAME,
89     /* number of columns  */
90     NUM_COLS
91   };
92
93 /* Called when a row is expanded.  We may have to initialize
94    children.  */
95 static void
96 tree_expanded_cb (GtkTreeView *tree, GtkTreeIter *parent,
97                   GtkTreePath *path, gpointer data)
98 {
99   GtkTreeModel *model;
100   GtkTreeIter iter;
101   MSymbol lang, name;
102
103   model = gtk_tree_view_get_model (tree);
104   if (gtk_tree_model_iter_children (model, &iter, parent))
105     {
106       gchar *status_str;
107
108       gtk_tree_model_get (model, &iter, COL_STATUS_STR, &status_str, -1);
109       if (! status_str)
110         {
111           /* The first child is not yet initialized, and that means
112              the remaining children are not initialized either.  */
113           gtk_tree_model_get (model, &iter, COL_LANG, &lang, -1);
114           do {
115             enum MimStatus im_status;
116
117             gtk_tree_model_get (model, &iter, COL_NAME, &name, -1);
118             im_status = get_mim_status (lang, name);
119             gtk_tree_store_set (GTK_TREE_STORE (model), &iter,
120                                 COL_STATUS_STR, mim_status_str[im_status],
121                                 COL_STATUS, im_status,
122                                 -1);
123           } while (gtk_tree_model_iter_next (model, &iter));
124         }
125     }
126 }
127
128 static void
129 edit_im (GtkTreeView *tree, MSymbol lang, MSymbol name)
130 {
131   GtkWidget *dialog, *label;
132   gint response;
133
134   dialog = (gtk_dialog_new_with_buttons
135             (_("Edit"),
136              GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (tree))),
137              GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
138              _("Default"), 0,
139              GTK_STOCK_REVERT_TO_SAVED, GTK_RESPONSE_NO,
140              GTK_STOCK_EDIT, GTK_RESPONSE_YES,
141              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
142              NULL));
143   label = gtk_label_new (msymbol_name (name));
144   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), label);
145   gtk_widget_show_all (dialog);
146   response = gtk_dialog_run (GTK_DIALOG (dialog));
147   if (response != GTK_RESPONSE_CANCEL
148       && lang == Mt && (name == Mnil || name == msymbol ("unicode")))
149     {
150       MSymbol variable = msymbol (name == Mnil ? "candidates-group-size"
151                                   : "prompt");
152
153       if (response == GTK_RESPONSE_NO)
154         {
155           minput_config_variable (lang, name, variable, NULL);
156         }
157       else if (response == 0)
158         {
159           MPlist *plist = mplist ();
160
161           minput_config_variable (lang, name, variable, plist);
162           m17n_object_unref (plist);
163         }
164       else
165         {
166           MPlist *plist = mplist ();
167
168           if (name == Mnil)
169             {
170               mplist_add (plist, Minteger, (void *) 3);
171             }
172           else
173             {
174               MText *mt = mtext_from_data (">>", 2, MTEXT_FORMAT_US_ASCII);
175               mplist_add (plist, Mtext, mt);
176               m17n_object_unref (mt);
177             }
178           minput_config_variable (lang, name, variable, plist);
179           m17n_object_unref (plist);
180         }
181     }
182
183   gtk_widget_destroy (dialog);
184 }
185
186 static void
187 update_child_row (GtkTreeModel *model, GtkTreeIter *iter,
188                   enum MimStatus status, MimConfigStatus *config_status,
189                   GtkTreeView *tree)
190 {
191   GtkTreeIter parent;
192   gint inc_modified;
193
194   inc_modified = (status == MIM_STATUS_MODIFIED ? 1 : -1);
195
196   gtk_tree_store_set (GTK_TREE_STORE (model), iter,
197                       COL_STATUS_STR, mim_status_str[status],
198                       COL_STATUS, status, -1);
199   if (gtk_tree_model_iter_parent (model, &parent, iter))
200     {
201       gint num_modified;
202       gchar *status_str;
203
204       gtk_tree_model_get (model, &parent, COL_STATUS, &num_modified, -1);
205       num_modified += inc_modified;
206       gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
207                           COL_STATUS, num_modified, -1);
208       if (num_modified <= 1)
209         {
210           status_str = (status == MIM_STATUS_MODIFIED
211                         ? mim_status_str[MIM_STATUS_MODIFIED] : NULL);
212           gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
213                               COL_STATUS_STR, status_str);
214         }
215     }
216       
217   if (! config_status)
218     config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
219   config_status->num_modified += inc_modified;
220   if (tree && config_status->num_modified <= 1)
221     {
222       MimConfigCallback *callback;
223
224       callback = g_object_get_data (G_OBJECT (tree), CONFIG_CALLBACK_DATA);
225       if (callback)
226         callback->func (config_status->num_modified == 0 ? FALSE : TRUE,
227                         callback->arg);
228     }
229 }
230
231 static void
232 tree_activated_cb (GtkTreeView *tree, GtkTreePath *path,
233                    GtkTreeViewColumn *column, gpointer data)
234 {
235   GtkTreeModel *model;
236   GtkTreeIter iter;
237
238   model = gtk_tree_view_get_model (tree);
239   if (gtk_tree_model_get_iter (model, &iter, path))
240     {
241       MSymbol lang, name;
242
243       gtk_tree_model_get (model, &iter, COL_LANG, &lang, COL_NAME, &name, -1);
244       if (lang != Mnil)
245         {
246           /* child row for an IM */
247           enum MimStatus old, new;
248
249           old = get_mim_status (lang, name);
250           edit_im (tree, lang, name);
251           new = get_mim_status (lang, name);
252           if (old != new)
253             update_child_row (model, &iter, new, NULL, tree);
254         }
255       else
256         {
257           /* parent row for a language */
258           if (gtk_tree_view_row_expanded (tree, path))
259             gtk_tree_view_collapse_row (tree, path);
260           else
261             gtk_tree_view_expand_row (tree, path, TRUE);
262         }
263     }
264 }
265
266 static gboolean
267 config_deleted_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
268 {
269   M17N_FINI ();
270   return FALSE;
271 }
272
273 typedef struct _MimTable
274 {
275   gchar *lang;
276   gchar *name;
277   MSymbol symlang;
278   MSymbol symname;
279 } MimTable;
280
281 static int
282 sort_im (const void *p1, const void *p2)
283 {
284   const MimTable *t1 = p1;
285   const MimTable *t2 = p2;
286   int result = strcmp (t1->lang, t2->lang);
287
288   return (result ? result : strcmp (t1->name, t2->name));
289 }
290
291 static GtkTreeStore *
292 make_store_for_input_methods ()
293 {
294   GtkTreeStore *store;
295   MPlist *imlist, *p;
296   int i;
297   MimTable *imtable;
298   char *lang;
299   GtkTreeIter iter1, iter2;
300   enum MimStatus status;
301   MimConfigStatus *config_status;
302
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   for (i = 0, p = imlist; mplist_key (p) != Mnil; p = mplist_next (p))
326     {
327       MDatabase *mdb = (MDatabase *) mplist_value (p);
328       MSymbol *tag = mdatabase_tag (mdb);
329
330       if (tag[1] != Mnil && tag[2] != Mnil)
331         {
332           MSymbol language = mlanguage_name (tag[1]);
333
334           if (language != Mnil)
335             imtable[i].lang = msymbol_name (language);
336           else
337             /* `~' is for putting this element at the tail by sort.  */
338             imtable[i].lang = "~other";
339           imtable[i].name = msymbol_name (tag[2]);
340           imtable[i].symlang = tag[1];
341           imtable[i].symname = tag[2];
342           i++;
343         }
344     }
345   m17n_object_unref (imlist);
346   config_status->num_im = i;
347   qsort (imtable, config_status->num_im, sizeof (MimTable), sort_im);
348
349   for (lang = NULL, i = 0; i < config_status->num_im; i++)
350     {
351       if (lang != imtable[i].lang)
352         {
353           gchar *name;
354
355           gtk_tree_store_append (store, &iter1, NULL);
356           lang = imtable[i].lang;
357           if (lang[0] != '~')
358             {
359               MText *native_text;
360               gchar *native = NULL;
361               int nbytes;
362
363               if (imtable[i].symlang != Mt
364                   && (native_text = mlanguage_text (imtable[i].symlang)))
365                 {
366                   enum MTextFormat fmt;
367
368                   native = mtext_data (native_text, &fmt, &nbytes,
369                                        NULL, NULL);
370                   if (fmt != MTEXT_FORMAT_US_ASCII
371                       && fmt != MTEXT_FORMAT_UTF_8)
372                     native = 0;
373                 }
374               if (native)
375                 {
376                   name = alloca (strlen (lang) + nbytes + 4);
377                   sprintf (name, "%s (%s)", lang, native);
378                 }
379               else
380                 name = lang;
381             }
382           else
383             name = lang + 1;
384
385           gtk_tree_store_set (store, &iter1,
386                               COL_TAG, name,
387                               COL_STATUS_STR, NULL,
388                               COL_STATUS, 0,
389                               COL_LANG, Mnil,
390                               COL_NAME, Mnil,
391                               -1);
392         }
393       gtk_tree_store_append (store, &iter2, &iter1);
394       gtk_tree_store_set (store, &iter2,
395                           COL_TAG, imtable[i].name,
396                           COL_STATUS_STR, NULL,
397                           COL_LANG, imtable[i].symlang,
398                           COL_NAME, imtable[i].symname,
399                           -1);
400     }
401   config_status->num_modified = 0;
402   g_object_set_data_full (G_OBJECT (store), CONFIG_STATUS_DATA,
403                           config_status, g_free);
404   return store;
405 }
406
407 static gboolean
408 revert_to_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
409                gpointer data)
410 {
411   enum MimStatus status;
412   MSymbol lang, name;
413   MimConfigStatus *config_status = data;
414
415   gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
416   if (lang == Mnil)
417     return FALSE;
418   gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
419   if (status != MIM_STATUS_MODIFIED)
420     return FALSE;
421   minput_config_variable (lang, name, Mnil, NULL);
422   minput_config_command (lang, name, Mnil, NULL);
423   status = get_mim_status (lang, name);
424   update_child_row (model, iter, status, config_status, NULL);
425   return FALSE;
426 }
427
428 static gboolean
429 set_as_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
430            gpointer data)
431 {
432   enum MimStatus status;
433   MSymbol lang, name;
434   MimConfigStatus *config_status = data;
435
436   gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
437   if (lang == Mnil)
438     return FALSE;
439   gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
440   if (status != MIM_STATUS_MODIFIED)
441     return FALSE;
442   status = get_mim_status (lang, name);
443   update_child_row (model, iter, status, config_status, NULL);
444   return FALSE;
445 }
446
447 \f
448 /* Public API */
449
450 GtkWidget *
451 mim_config_widget (MimConfigCallback *callback)
452 {
453   GtkWidget *tree, *config;
454   GtkTreeStore *store;
455   GtkCellRenderer *renderer;
456   GtkTreeViewColumn *column;
457
458   M17N_INIT ();
459
460 #if ENABLE_NLS
461   bindtextdomain ("m17n-im-config", GETTEXTDIR);
462   bind_textdomain_codeset ("m17n-im-config", "UTF-8");
463 #endif
464
465   mim_status_str[MIM_STATUS_DEFAULT] = _("default");
466   mim_status_str[MIM_STATUS_CUSTOMIZED] = _("customized");
467   mim_status_str[MIM_STATUS_MODIFIED] = _("modified");
468   mim_status_str[MIM_STATUS_NO] = _("uncustomizable");
469
470   store = make_store_for_input_methods ();
471   tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
472   g_object_unref (G_OBJECT (store));
473   if (callback)
474     g_object_set_data (G_OBJECT (tree), CONFIG_CALLBACK_DATA, callback);
475
476   renderer = gtk_cell_renderer_text_new ();
477   column = (gtk_tree_view_column_new_with_attributes
478             (_("Input Method"), renderer, "text", COL_TAG, NULL));
479   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
480
481   renderer = gtk_cell_renderer_text_new ();
482   column = (gtk_tree_view_column_new_with_attributes
483             (_("status"), renderer, "text", COL_STATUS_STR, NULL));
484   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
485
486   g_signal_connect (G_OBJECT (tree), "row-expanded",
487                     G_CALLBACK (tree_expanded_cb), NULL);
488   g_signal_connect (G_OBJECT (tree), "row-activated",
489                     G_CALLBACK (tree_activated_cb), NULL);
490
491   config =gtk_scrolled_window_new (NULL, NULL);
492   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (config),
493                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
494   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (config), tree);
495   g_signal_connect (G_OBJECT (config), "destroy",
496                     G_CALLBACK (config_deleted_cb), NULL);
497
498   g_object_set_data (G_OBJECT (config), CONFIG_TREE_VIEW, tree);
499
500   return config;
501 }
502
503 gboolean
504 mim_config_modified (GtkWidget *config)
505 {
506   GtkTreeView *tree;
507   GtkTreeModel *model;
508   MimConfigStatus *config_status;
509
510   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
511   model = gtk_tree_view_get_model (tree);
512   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
513
514   return (config_status->num_modified > 0 ? TRUE : FALSE);
515 }
516
517 gboolean
518 mim_config_revert (GtkWidget *config)
519 {
520   GtkTreeView *tree;
521   GtkTreeModel *model;
522   MimConfigStatus *config_status;
523
524   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
525   model = gtk_tree_view_get_model (tree);
526   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
527
528   if (config_status->num_modified == 0)
529     return FALSE;
530   gtk_tree_model_foreach (model, revert_to_saved, config_status);
531   return TRUE;
532 }
533
534 gboolean
535 mim_config_save (GtkWidget *config)
536 {
537   GtkTreeView *tree;
538   GtkTreeModel *model;
539   MimConfigStatus *config_status;
540
541   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
542   model = gtk_tree_view_get_model (tree);
543   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
544
545   if (config_status->num_modified == 0)
546     return FALSE;
547   minput_save_config ();
548   gtk_tree_model_foreach (model, set_as_saved, config_status);
549   return TRUE;
550 }