*** 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 command = msymbol (name == Mnil ? "commit" : "start");
151
152       if (response == GTK_RESPONSE_NO)
153         {
154           minput_config_command (lang, name, command, NULL);
155         }
156       else if (response == 0)
157         {
158           MPlist *plist = mplist ();
159
160           minput_config_command (lang, name, command, plist);
161           m17n_object_unref (plist);
162         }
163       else
164         {
165           MPlist *cmd;
166
167           cmd = minput_get_command (lang, name, command);
168           if (cmd)
169             {
170               MPlist *plist, *key_seq_list, *key_seq;
171
172               plist = mplist_next (mplist_next (mplist_next (mplist_value (cmd))));
173               key_seq_list = mplist_copy (plist);
174               key_seq = mplist ();
175               mplist_add (key_seq, Msymbol, msymbol ("C-x"));
176               mplist_add (key_seq, Msymbol, msymbol ("t"));
177               mplist_add (key_seq_list, Mplist, key_seq);
178               m17n_object_unref (key_seq);
179               minput_config_command (lang, name, command, key_seq_list);
180               m17n_object_unref (key_seq_list);
181             }
182         }
183     }
184
185   gtk_widget_destroy (dialog);
186 }
187
188 static void
189 update_child_row (GtkTreeModel *model, GtkTreeIter *iter,
190                   enum MimStatus status, MimConfigStatus *config_status,
191                   GtkTreeView *tree)
192 {
193   GtkTreeIter parent;
194   gint inc_modified;
195
196   inc_modified = (status == MIM_STATUS_MODIFIED ? 1 : -1);
197
198   gtk_tree_store_set (GTK_TREE_STORE (model), iter,
199                       COL_STATUS_STR, mim_status_str[status],
200                       COL_STATUS, status, -1);
201   if (gtk_tree_model_iter_parent (model, &parent, iter))
202     {
203       gint num_modified;
204       gchar *status_str;
205
206       gtk_tree_model_get (model, &parent, COL_STATUS, &num_modified, -1);
207       num_modified += inc_modified;
208       gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
209                           COL_STATUS, num_modified, -1);
210       if (num_modified <= 1)
211         {
212           status_str = (status == MIM_STATUS_MODIFIED
213                         ? mim_status_str[MIM_STATUS_MODIFIED] : NULL);
214           gtk_tree_store_set (GTK_TREE_STORE (model), &parent,
215                               COL_STATUS_STR, status_str);
216         }
217     }
218       
219   if (! config_status)
220     config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
221   config_status->num_modified += inc_modified;
222   if (tree && config_status->num_modified <= 1)
223     {
224       MimConfigCallback *callback;
225
226       callback = g_object_get_data (G_OBJECT (tree), CONFIG_CALLBACK_DATA);
227       if (callback)
228         callback->func (config_status->num_modified == 0 ? FALSE : TRUE,
229                         callback->arg);
230     }
231 }
232
233 static void
234 tree_activated_cb (GtkTreeView *tree, GtkTreePath *path,
235                    GtkTreeViewColumn *column, gpointer data)
236 {
237   GtkTreeModel *model;
238   GtkTreeIter iter;
239
240   model = gtk_tree_view_get_model (tree);
241   if (gtk_tree_model_get_iter (model, &iter, path))
242     {
243       MSymbol lang, name;
244
245       gtk_tree_model_get (model, &iter, COL_LANG, &lang, COL_NAME, &name, -1);
246       if (lang != Mnil)
247         {
248           /* child row for an IM */
249           enum MimStatus old, new;
250
251           old = get_mim_status (lang, name);
252           edit_im (tree, lang, name);
253           new = get_mim_status (lang, name);
254           if (old != new)
255             update_child_row (model, &iter, new, NULL, tree);
256         }
257       else
258         {
259           /* parent row for a language */
260           if (gtk_tree_view_row_expanded (tree, path))
261             gtk_tree_view_collapse_row (tree, path);
262           else
263             gtk_tree_view_expand_row (tree, path, TRUE);
264         }
265     }
266 }
267
268 static gboolean
269 config_deleted_cb (GtkWidget *widget, GdkEvent *event, gpointer data)
270 {
271   M17N_FINI ();
272   return FALSE;
273 }
274
275 typedef struct _MimTable
276 {
277   gchar *lang;
278   gchar *name;
279   MSymbol symlang;
280   MSymbol symname;
281 } MimTable;
282
283 static int
284 sort_im (const void *p1, const void *p2)
285 {
286   const MimTable *t1 = p1;
287   const MimTable *t2 = p2;
288   int result = strcmp (t1->lang, t2->lang);
289
290   return (result ? result : strcmp (t1->name, t2->name));
291 }
292
293 static GtkTreeStore *
294 make_store_for_input_methods ()
295 {
296   GtkTreeStore *store;
297   MPlist *imlist, *p;
298   int i;
299   MimTable *imtable;
300   char *lang;
301   GtkTreeIter iter1, iter2;
302   enum MimStatus status;
303   MimConfigStatus *config_status;
304
305   store = gtk_tree_store_new (NUM_COLS,
306                               G_TYPE_STRING,  /* COL_TAG */
307                               G_TYPE_STRING,  /* COL_STATUS_STR */
308                               G_TYPE_UINT,    /* COL_STATUS */
309                               G_TYPE_POINTER, /* COL_LANG */
310                               G_TYPE_POINTER  /* COL_NAME */
311                               );
312
313   config_status = g_new0 (MimConfigStatus, 1);
314   gtk_tree_store_append (store, &iter1, NULL);
315   status = get_mim_status (Mt, Mnil);
316   gtk_tree_store_set (store, &iter1,
317                       COL_TAG, _("global"),
318                       COL_STATUS_STR, mim_status_str[status],
319                       COL_STATUS, status, 
320                       COL_LANG, Mt,
321                       COL_NAME, Mnil,
322                       -1);
323
324   imlist = mdatabase_list (msymbol ("input-method"), Mnil, Mnil, Mnil);
325   config_status->num_im = mplist_length (imlist);
326   imtable = g_newa (MimTable, config_status->num_im);
327   for (i = 0, p = imlist; mplist_key (p) != Mnil; p = mplist_next (p))
328     {
329       MDatabase *mdb = (MDatabase *) mplist_value (p);
330       MSymbol *tag = mdatabase_tag (mdb);
331
332       if (tag[1] != Mnil && tag[2] != Mnil)
333         {
334           MSymbol language = mlanguage_name (tag[1]);
335
336           if (language != Mnil)
337             imtable[i].lang = msymbol_name (language);
338           else
339             /* `~' is for putting this element at the tail by sort.  */
340             imtable[i].lang = "~other";
341           imtable[i].name = msymbol_name (tag[2]);
342           imtable[i].symlang = tag[1];
343           imtable[i].symname = tag[2];
344           i++;
345         }
346     }
347   m17n_object_unref (imlist);
348   config_status->num_im = i;
349   qsort (imtable, config_status->num_im, sizeof (MimTable), sort_im);
350
351   for (lang = NULL, i = 0; i < config_status->num_im; i++)
352     {
353       if (lang != imtable[i].lang)
354         {
355           gchar *name;
356
357           gtk_tree_store_append (store, &iter1, NULL);
358           lang = imtable[i].lang;
359           if (lang[0] != '~')
360             {
361               MText *native_text;
362               gchar *native = NULL;
363               int nbytes;
364
365               if (imtable[i].symlang != Mt
366                   && (native_text = mlanguage_text (imtable[i].symlang)))
367                 {
368                   enum MTextFormat fmt;
369
370                   native = mtext_data (native_text, &fmt, &nbytes,
371                                        NULL, NULL);
372                   if (fmt != MTEXT_FORMAT_US_ASCII
373                       && fmt != MTEXT_FORMAT_UTF_8)
374                     native = 0;
375                 }
376               if (native)
377                 {
378                   name = alloca (strlen (lang) + nbytes + 4);
379                   sprintf (name, "%s (%s)", lang, native);
380                 }
381               else
382                 name = lang;
383             }
384           else
385             name = lang + 1;
386
387           gtk_tree_store_set (store, &iter1,
388                               COL_TAG, name,
389                               COL_STATUS_STR, NULL,
390                               COL_STATUS, 0,
391                               COL_LANG, Mnil,
392                               COL_NAME, Mnil,
393                               -1);
394         }
395       gtk_tree_store_append (store, &iter2, &iter1);
396       gtk_tree_store_set (store, &iter2,
397                           COL_TAG, imtable[i].name,
398                           COL_STATUS_STR, NULL,
399                           COL_LANG, imtable[i].symlang,
400                           COL_NAME, imtable[i].symname,
401                           -1);
402     }
403   config_status->num_modified = 0;
404   g_object_set_data_full (G_OBJECT (store), CONFIG_STATUS_DATA,
405                           config_status, g_free);
406   return store;
407 }
408
409 static gboolean
410 revert_to_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
411                gpointer data)
412 {
413   enum MimStatus status;
414   MSymbol lang, name;
415   MimConfigStatus *config_status = data;
416
417   gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
418   if (lang == Mnil)
419     return FALSE;
420   gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
421   if (status != MIM_STATUS_MODIFIED)
422     return FALSE;
423   minput_config_variable (lang, name, Mnil, NULL);
424   minput_config_command (lang, name, Mnil, NULL);
425   status = get_mim_status (lang, name);
426   update_child_row (model, iter, status, config_status, NULL);
427   return FALSE;
428 }
429
430 static gboolean
431 set_as_saved (GtkTreeModel *model, GtkTreePath *path, GtkTreeIter *iter,
432            gpointer data)
433 {
434   enum MimStatus status;
435   MSymbol lang, name;
436   MimConfigStatus *config_status = data;
437
438   gtk_tree_model_get (model, iter, COL_LANG, &lang, COL_NAME, &name, -1);
439   if (lang == Mnil)
440     return FALSE;
441   gtk_tree_model_get (model, iter, COL_STATUS, &status, -1);  
442   if (status != MIM_STATUS_MODIFIED)
443     return FALSE;
444   status = get_mim_status (lang, name);
445   update_child_row (model, iter, status, config_status, NULL);
446   return FALSE;
447 }
448
449 \f
450 /* Public API */
451
452 GtkWidget *
453 mim_config_widget (MimConfigCallback *callback)
454 {
455   GtkWidget *tree, *config;
456   GtkTreeStore *store;
457   GtkCellRenderer *renderer;
458   GtkTreeViewColumn *column;
459
460   M17N_INIT ();
461
462 #if ENABLE_NLS
463   bindtextdomain ("m17n-im-config", GETTEXTDIR);
464   bind_textdomain_codeset ("m17n-im-config", "UTF-8");
465 #endif
466
467   mim_status_str[MIM_STATUS_DEFAULT] = _("default");
468   mim_status_str[MIM_STATUS_CUSTOMIZED] = _("customized");
469   mim_status_str[MIM_STATUS_MODIFIED] = _("modified");
470   mim_status_str[MIM_STATUS_NO] = _("uncustomizable");
471
472   store = make_store_for_input_methods ();
473   tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
474   g_object_unref (G_OBJECT (store));
475   if (callback)
476     g_object_set_data (G_OBJECT (tree), CONFIG_CALLBACK_DATA, callback);
477
478   renderer = gtk_cell_renderer_text_new ();
479   column = (gtk_tree_view_column_new_with_attributes
480             (_("Input Method"), renderer, "text", COL_TAG, NULL));
481   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
482
483   renderer = gtk_cell_renderer_text_new ();
484   column = (gtk_tree_view_column_new_with_attributes
485             (_("status"), renderer, "text", COL_STATUS_STR, NULL));
486   gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
487
488   g_signal_connect (G_OBJECT (tree), "row-expanded",
489                     G_CALLBACK (tree_expanded_cb), NULL);
490   g_signal_connect (G_OBJECT (tree), "row-activated",
491                     G_CALLBACK (tree_activated_cb), NULL);
492
493   config =gtk_scrolled_window_new (NULL, NULL);
494   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (config),
495                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
496   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (config), tree);
497   g_signal_connect (G_OBJECT (config), "delete_event",
498                     G_CALLBACK (config_deleted_cb), NULL);
499
500   g_object_set_data (G_OBJECT (config), CONFIG_TREE_VIEW, tree);
501
502   return config;
503 }
504
505 gboolean
506 mim_config_modified (GtkWidget *config)
507 {
508   GtkTreeView *tree;
509   GtkTreeModel *model;
510   MimConfigStatus *config_status;
511
512   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
513   model = gtk_tree_view_get_model (tree);
514   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
515
516   return (config_status->num_modified > 0 ? TRUE : FALSE);
517 }
518
519 gboolean
520 mim_config_revert (GtkWidget *config)
521 {
522   GtkTreeView *tree;
523   GtkTreeModel *model;
524   MimConfigStatus *config_status;
525
526   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
527   model = gtk_tree_view_get_model (tree);
528   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
529
530   if (config_status->num_modified == 0)
531     return FALSE;
532   gtk_tree_model_foreach (model, revert_to_saved, config_status);
533   return TRUE;
534 }
535
536 gboolean
537 mim_config_save (GtkWidget *config)
538 {
539   GtkTreeView *tree;
540   GtkTreeModel *model;
541   MimConfigStatus *config_status;
542
543   tree = g_object_get_data (G_OBJECT (config), CONFIG_TREE_VIEW);
544   model = gtk_tree_view_get_model (tree);
545   config_status = g_object_get_data (G_OBJECT (model), CONFIG_STATUS_DATA);
546
547   if (config_status->num_modified == 0)
548     return FALSE;
549   minput_save_config ();
550   gtk_tree_model_foreach (model, set_as_saved, config_status);
551   return TRUE;
552 }