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