*** empty log message ***
[m17n/m17n-im-config.git] / src / variable.c
1 #include <stdio.h>
2 #include <string.h>
3 #include <libintl.h>
4 #include <gtk/gtk.h>
5 #include <m17n.h>
6 #include <config.h>
7
8 #define _(String) dgettext (PACKAGE, String)
9
10 #define CURRENT_VALUE           \
11   (mplist_next                  \
12    (mplist_next                 \
13     (mplist_next                \
14      (mplist_value              \
15       (minput_get_variable      \
16        (current_lang, current_name, current_variable))))))
17
18 #define CURRENT_STATUS  \
19   (mplist_value                 \
20    (mplist_next                 \
21     (mplist_next                \
22      (mplist_value              \
23       (minput_get_variable      \
24        (current_lang, current_name, current_variable))))))
25
26 #define CONFIG_VARIABLE(plist)                                          \
27   minput_config_variable (current_lang, current_name, current_variable, \
28                           (plist))
29
30 enum WidgetType
31   {
32     ENTRY_WIDGET,
33     COMBO_BOX_WIDGET,
34     SPIN_BUTTON_WIDGET
35   };
36
37 struct ControllerInfo
38 {
39   /* type of current variable: Minteger, Msymbol, or Mtext */
40   MSymbol vtype;
41
42   /* widget showing and controlling current variable */
43   GtkWidget *widget;
44
45   /* type of widget */
46   enum WidgetType wtype;
47
48   /* whether minput_config_variable () should be called on ok button */
49   gboolean need_config;
50
51   /* default button */
52   GtkWidget *default_;
53
54   /* revert button */
55   GtkWidget *revert;
56
57   /* status label */
58   GtkWidget *status;
59
60   /* ok button */
61   GtkWidget *ok;
62
63   /* dialog itself */
64   GtkWidget *dialog;
65 };
66
67 static MSymbol current_lang, current_name, current_variable;
68
69 static void
70 update_widget (struct ControllerInfo *ci)
71 {
72   MPlist *plist;
73   MSymbol key, status;
74   void *value;
75
76   plist = CURRENT_VALUE;
77   /* plist == (value [valid-value ...]) */
78   key = mplist_key (plist);
79   value = mplist_value (plist);
80
81   if (ci->wtype == ENTRY_WIDGET)
82     {
83       if (key == Msymbol)
84         gtk_entry_set_text (GTK_ENTRY (ci->widget),
85                             msymbol_name ((MSymbol) value));
86       else if (key == Mtext)            
87         /* Fixme : Assuming the return value is in UTF-8 */
88         gtk_entry_set_text (GTK_ENTRY (ci->widget),
89                             mtext_data ((MText *) value,
90                                         NULL, NULL, NULL, NULL));
91       else                      /* key == Minteger */
92         {
93           gchar buf[32];
94           g_snprintf (buf, sizeof (buf), "%d", (gint) value);
95           gtk_entry_set_text (GTK_ENTRY (ci->widget), buf);
96         }
97     }
98   else if (ci->wtype == COMBO_BOX_WIDGET)
99     {
100       gint i;
101
102       for (i = 0, plist = mplist_next (plist);
103            plist && mplist_key (plist) == key;
104            i++, plist = mplist_next (plist))
105         if (mplist_value (plist) == value)
106           break;
107       gtk_combo_box_set_active (GTK_COMBO_BOX (ci->widget), i);
108     }
109   else                          /* ci->wtype == SPIN_BUTTON_WIDGET */
110     gtk_spin_button_set_value (GTK_SPIN_BUTTON (ci->widget),
111                                (gdouble) (int) value);
112 }
113
114 static void *
115 default_cb (GtkButton *button, gpointer data)
116 {
117   MPlist *empty = mplist ();
118   struct ControllerInfo *ci = data;
119
120   CONFIG_VARIABLE (empty);
121   m17n_object_unref (empty);
122   update_widget (ci);
123   gtk_widget_set_sensitive (ci->default_, FALSE);
124   if (CURRENT_STATUS == Mconfigured)
125     {
126       gtk_widget_set_sensitive (ci->revert, TRUE);
127       gtk_label_set_text (GTK_LABEL (ci->status), _("modified"));
128     }
129   else
130     {
131       gtk_widget_set_sensitive (ci->revert, FALSE);
132       gtk_label_set_text (GTK_LABEL (ci->status), _("default"));
133     }
134   ci->need_config = FALSE;
135 }
136
137 static void *
138 revert_cb (GtkButton *button, gpointer data)
139 {
140   struct ControllerInfo *ci = data;
141
142   CONFIG_VARIABLE (NULL);
143   update_widget (ci);
144   gtk_widget_set_sensitive (ci->revert, FALSE);
145   if (CURRENT_STATUS == Mnil)
146     {
147       gtk_widget_set_sensitive (ci->default_, FALSE);
148       gtk_label_set_text (GTK_LABEL (ci->status), _("default"));
149     }
150   else
151     {
152       gtk_widget_set_sensitive (ci->default_, TRUE);
153       gtk_label_set_text (GTK_LABEL (ci->status), _("customized"));
154     }
155   ci->need_config = FALSE;
156 }
157
158 static gboolean
159 config_with_entry (struct ControllerInfo *ci)
160 {
161   const gchar *text = gtk_entry_get_text (GTK_ENTRY (ci->widget));
162   MPlist *plist = mplist ();
163   gboolean ret = TRUE;
164
165   if (ci->vtype == Msymbol)
166     {
167       mplist_add (plist, Msymbol, msymbol (text));
168       CONFIG_VARIABLE (plist);
169     }
170   else if (ci->vtype == Mtext)
171     {
172       MText *mt;
173
174       mt = mconv_decode_buffer (Mcoding_utf_8, text, strlen (text));
175       mplist_add (plist, Mtext, mt);
176       CONFIG_VARIABLE (plist);
177       m17n_object_unref (mt);
178     }
179   else                          /* ci->vtype == Minteger */
180     {
181       int i;
182       gchar buf[32];
183
184       if (sscanf (text, "%d", &i) == 1)
185         {
186           mplist_add (plist, Minteger, (void *) i);
187           CONFIG_VARIABLE (plist);
188         }
189       else
190         {
191           GtkWidget *msg;
192
193           msg = gtk_message_dialog_new (GTK_WINDOW
194                                         (gtk_widget_get_toplevel (ci->widget)),
195                                         GTK_DIALOG_DESTROY_WITH_PARENT,
196                                         GTK_MESSAGE_ERROR,
197                                         GTK_BUTTONS_CLOSE,
198                                         _("The value must be an integer."));
199           gtk_dialog_run (GTK_DIALOG (msg));
200           gtk_widget_destroy (msg);
201           ret = FALSE;
202         }
203     }
204
205   m17n_object_unref (plist);
206   return ret;
207 }
208
209 static gboolean
210 config_with_combo (struct ControllerInfo *ci)
211 {
212   gchar *text = gtk_combo_box_get_active_text (GTK_COMBO_BOX (ci->widget));
213   MPlist *plist = mplist ();
214
215   if (ci->vtype == Msymbol)
216     {
217       mplist_add (plist, Msymbol, msymbol (text));
218       CONFIG_VARIABLE (plist);
219     }
220   else if (ci->vtype == Mtext)
221     {
222       MText *mt;
223
224       mt = mconv_decode_buffer (Mcoding_utf_8, text, strlen (text));
225       mplist_add (plist, Mtext, mt);
226       CONFIG_VARIABLE (plist);
227       m17n_object_unref (mt);
228     }
229   else                          /* ci->vtype == Minteger */
230     {
231       int i;
232
233       sscanf (text, "%d", &i);
234       mplist_add (plist, Minteger, (void *) i);
235       CONFIG_VARIABLE (plist);
236     }
237   m17n_object_unref (plist);
238   return TRUE;
239 }
240
241 static gboolean
242 config_with_spin (struct ControllerInfo *ci)
243 {
244   gint i;
245
246   i = gtk_spin_button_get_value_as_int (GTK_SPIN_BUTTON (ci->widget));
247   MPlist *plist = mplist ();
248
249   mplist_add (plist, Minteger, (void *) i);
250   CONFIG_VARIABLE (plist);
251   m17n_object_unref (plist);
252   return TRUE;
253 }
254
255 static void *
256 ok_cb (GtkButton *button, gpointer data)
257 {
258   struct ControllerInfo *ci = data;
259
260   if (ci->need_config)
261     {
262       if (ci->wtype == ENTRY_WIDGET)
263         {
264           if (config_with_entry (ci))
265             gtk_dialog_response (GTK_DIALOG (ci->dialog), GTK_RESPONSE_OK);
266           else
267             revert_cb (NULL, ci);
268         }
269
270       else if (ci->wtype == COMBO_BOX_WIDGET)
271         {
272           config_with_combo (ci); /* always returns true */
273           gtk_dialog_response (GTK_DIALOG (ci->dialog), GTK_RESPONSE_OK);
274         }
275
276       else                              /* ci->wtype == SPIN_BUTTON */
277         {
278           config_with_spin (ci); /* always returns true */
279           gtk_dialog_response (GTK_DIALOG (ci->dialog), GTK_RESPONSE_OK);
280         }
281     }
282   else
283     gtk_dialog_response (GTK_DIALOG (ci->dialog), GTK_RESPONSE_OK);
284 }
285
286 static void
287 changed_cb (GtkEntry *entry, gpointer data)
288 {
289   struct ControllerInfo *ci = data;
290
291   gtk_widget_set_sensitive (ci->default_, TRUE);
292   gtk_widget_set_sensitive (ci->revert, TRUE);
293   gtk_label_set_text (GTK_LABEL (ci->status), _("modified"));
294   ci->need_config = TRUE;
295 }
296
297 enum
298   {
299     VCOL_VARIABLE,
300     VCOL_VALUE,
301     VCOL_STATUS,
302     NUM_VCOLS
303   };
304
305 static void
306 set_value_status (GtkListStore *store, GtkTreeIter *iter)
307 {
308   MPlist *plist;
309   MSymbol status;
310   gchar *value_str, *status_str, buf[32];
311
312   status = CURRENT_STATUS;
313   if (status == Mconfigured)
314     status_str = _("modified");
315   else if (status == Mcustomized)
316     status_str = _("customized");
317   else
318     status_str = _("default");
319
320   plist = CURRENT_VALUE;
321   /* plist == (value [valid-value ...]) */
322   if (mplist_key (plist) == Msymbol)
323     value_str = msymbol_name ((MSymbol) mplist_value (plist));
324   else if (mplist_key (plist) == Mtext)
325     /* Fixme : Assuming the return value is in UTF-8 */
326     value_str = mtext_data ((MText *) mplist_value (plist),
327                             NULL, NULL, NULL, NULL);
328   else
329     {
330       g_snprintf (buf, sizeof (buf), "%d", (gint) mplist_value (plist));
331       value_str = buf;
332     }
333
334   gtk_list_store_set (store, iter,
335                       VCOL_VALUE, value_str,
336                       VCOL_STATUS, status_str,
337                       -1);
338 }
339
340 static GtkWidget *
341 create_widget (struct ControllerInfo *ci)
342 {
343   MPlist *plist;
344   void *value;
345
346   plist = CURRENT_VALUE;
347   /* plist == (value [valid-value ...]) */
348   ci->vtype = mplist_key (plist);
349   value = mplist_value (plist);
350   plist = mplist_next (plist);
351
352   if (ci->vtype == Msymbol)
353     {
354       if (plist && mplist_key (plist) == Msymbol)
355         {
356           gint i, nth;
357
358           ci->widget = gtk_combo_box_new_text ();
359           g_signal_connect (GTK_OBJECT (ci->widget), "changed",
360                             G_CALLBACK (changed_cb), ci);
361           ci->wtype = COMBO_BOX_WIDGET;
362           for (i = 0; plist && mplist_key (plist) == Msymbol;
363                plist = mplist_next (plist), i++)
364             {
365               if (mplist_value (plist) == value)
366                 nth = i;
367               gtk_combo_box_append_text
368                 (GTK_COMBO_BOX (ci->widget),
369                  msymbol_name ((MSymbol) mplist_value (plist)));
370             }
371           gtk_combo_box_set_active (GTK_COMBO_BOX (ci->widget), nth);
372         }
373       else
374         {
375           ci->widget = gtk_entry_new ();
376           g_signal_connect (GTK_OBJECT (ci->widget), "changed",
377                             G_CALLBACK (changed_cb), ci);
378           ci->wtype = ENTRY_WIDGET;
379           gtk_entry_set_text (GTK_ENTRY (ci->widget), msymbol_name (value));
380           gtk_editable_set_editable (GTK_EDITABLE (ci->widget), TRUE);
381         }
382     }
383   else if (ci->vtype == Mtext)
384     {
385       if (plist && mplist_key (plist) == Mtext)
386         {
387           gint i, nth;
388
389           ci->widget = gtk_combo_box_new_text ();
390           g_signal_connect (GTK_OBJECT (ci->widget), "changed",
391                             G_CALLBACK (changed_cb), ci);
392           ci->wtype = COMBO_BOX_WIDGET;
393           for (i = 0; plist && mplist_key (plist) == Mtext;
394                plist = mplist_next (plist), i++)
395             {
396               if (! mtext_cmp ((MText *) mplist_value (plist),
397                                (MText *) value))
398                 nth = i;
399               /* Fixme : Assuming the return value is in UTF-8 */
400               gtk_combo_box_append_text
401                 (GTK_COMBO_BOX (ci->widget),
402                  mtext_data ((MText *) mplist_value (plist),
403                              NULL, NULL, NULL, NULL));
404             }
405           gtk_combo_box_set_active (GTK_COMBO_BOX (ci->widget), nth);
406         }
407       else
408         {
409           ci->widget = gtk_entry_new ();
410           g_signal_connect (GTK_OBJECT (ci->widget), "changed",
411                             G_CALLBACK (changed_cb), ci);
412           ci->wtype = ENTRY_WIDGET;
413           /* Fixme : Assuming the return value is in UTF-8 */
414           gtk_entry_set_text (GTK_ENTRY (ci->widget),
415                               mtext_data (value, NULL, NULL, NULL, NULL));
416           gtk_editable_set_editable (GTK_EDITABLE (ci->widget), TRUE);
417         }
418     }
419   else                          /* ci->vtype == Minteger */
420     {
421       if (plist && mplist_key (plist) == Minteger)
422         {
423           gint i, nth;
424
425           ci->widget = gtk_combo_box_new_text ();
426           g_signal_connect (GTK_OBJECT (ci->widget), "changed",
427                             G_CALLBACK (changed_cb), ci);
428           ci->wtype = COMBO_BOX_WIDGET;
429           for (i = 0; plist && mplist_key (plist) == Minteger;
430                plist = mplist_next (plist), i++)
431             {
432               gchar buf[32];
433
434               if (mplist_value (plist) == value)
435                 nth = i;
436               g_snprintf (buf, sizeof (buf), "%d",
437                           (gint) mplist_value (plist));
438               gtk_combo_box_append_text (GTK_COMBO_BOX (ci->widget), buf);
439             }
440           gtk_combo_box_set_active (GTK_COMBO_BOX (ci->widget), nth);
441         }
442       else if (plist && mplist_key (plist) == Mplist)
443         {
444           GtkObject *adj;
445           gdouble lower, upper;
446
447           plist = mplist_value (plist);
448           lower = (gdouble) (int) mplist_value (plist);
449           upper = (gdouble) (int) mplist_value (mplist_next (plist));
450           adj = gtk_adjustment_new ((gdouble) (int) value, lower, upper,
451                                     1.0, 10.0, 0);
452           ci->widget = gtk_spin_button_new (GTK_ADJUSTMENT (adj), 0, 0);
453           g_signal_connect (GTK_OBJECT (ci->widget), "changed",
454                             G_CALLBACK (changed_cb), ci);
455           ci->wtype = SPIN_BUTTON_WIDGET;
456           gtk_spin_button_set_numeric (GTK_SPIN_BUTTON (ci->widget), TRUE);
457           gtk_spin_button_set_update_policy (GTK_SPIN_BUTTON (ci->widget),
458                                              GTK_UPDATE_ALWAYS);
459         }
460       else
461         {
462           gchar buf[32];
463
464           ci->widget = gtk_entry_new ();
465           g_signal_connect (GTK_OBJECT (ci->widget), "changed",
466                             G_CALLBACK (changed_cb), ci);
467           ci->wtype = ENTRY_WIDGET;
468           g_snprintf (buf, sizeof (buf), "%d", (gint) value);
469           gtk_entry_set_text (GTK_ENTRY (ci->widget), buf);
470           gtk_editable_set_editable (GTK_EDITABLE (ci->widget), TRUE);
471         }
472     }
473
474   return ci->widget;
475 }
476   
477 static void
478 activated_cb (GtkTreeView *parent, GtkTreePath *path,
479               GtkTreeViewColumn *col, gpointer data)
480 {
481   GtkTreeModel *model;
482   GtkTreeIter iter;
483   GtkWidget *label, *hbox, *vbox;
484   MSymbol status;
485   struct ControllerInfo ci;
486   gchar *variable;
487
488   model = gtk_tree_view_get_model (parent);
489   if (! gtk_tree_model_get_iter (model, &iter, path))
490     return;
491   gtk_tree_model_get (model, &iter, VCOL_VARIABLE, &variable, -1);
492   current_variable = msymbol (variable);
493
494   ci.dialog = (gtk_dialog_new_with_buttons
495                (variable,
496                 GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (parent))),
497                 GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
498                 NULL));
499   
500   ci.default_ = gtk_button_new_from_stock (_("_Default"));
501   g_signal_connect (G_OBJECT (ci.default_), "clicked",
502                     G_CALLBACK (default_cb), &ci);
503
504   ci.revert = gtk_button_new_from_stock (GTK_STOCK_REVERT_TO_SAVED);
505   g_signal_connect (G_OBJECT (ci.revert), "clicked",
506                     G_CALLBACK (revert_cb), &ci);
507
508   label = gtk_label_new (_("Status : "));
509   ci.status = gtk_label_new (NULL);
510
511   ci.ok = gtk_button_new_from_stock (GTK_STOCK_OK);
512   g_signal_connect (G_OBJECT (ci.ok), "clicked",
513                     G_CALLBACK (ok_cb), &ci);
514
515   vbox = gtk_vbox_new (FALSE, 12);
516   gtk_container_set_border_width (GTK_CONTAINER (vbox), 12);
517   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (ci.dialog)->vbox), vbox);
518
519   gtk_container_add (GTK_CONTAINER (vbox), create_widget (&ci));
520
521   hbox = gtk_hbutton_box_new ();
522   gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
523   gtk_box_set_spacing (GTK_BOX (hbox), 6);
524   gtk_container_add (GTK_CONTAINER (hbox), ci.default_);
525   gtk_container_add (GTK_CONTAINER (hbox), ci.revert);
526   gtk_container_add (GTK_CONTAINER (vbox), hbox);
527
528   hbox = gtk_hbox_new (FALSE, 6);
529   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
530   gtk_box_pack_start (GTK_BOX (hbox), ci.status, FALSE, FALSE, 0);
531   gtk_container_add (GTK_CONTAINER (vbox), hbox);
532
533   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (ci.dialog)->action_area),
534                      ci.ok);
535
536   status = CURRENT_STATUS;
537   if (status == Mconfigured)
538     {
539       gtk_widget_set_sensitive (ci.default_, TRUE);
540       gtk_widget_set_sensitive (ci.revert, TRUE);
541       gtk_label_set_text (GTK_LABEL (ci.status), _("modified"));
542     }
543   else if (status == Mcustomized)
544     {
545       gtk_widget_set_sensitive (ci.default_, TRUE);
546       gtk_widget_set_sensitive (ci.revert, FALSE);
547       gtk_label_set_text (GTK_LABEL (ci.status), _("customized"));
548     }
549   else
550     {
551       gtk_widget_set_sensitive (ci.default_, FALSE);
552       gtk_widget_set_sensitive (ci.revert, FALSE);
553       gtk_label_set_text (GTK_LABEL (ci.status), _("default"));
554     }
555
556   ci.need_config = FALSE;
557
558   gtk_widget_show_all (ci.dialog);
559   gtk_dialog_run (GTK_DIALOG (ci.dialog));
560   gtk_tree_model_get_iter (model, &iter, path);
561   set_value_status (GTK_LIST_STORE (model), &iter);
562   gtk_widget_destroy (ci.dialog);
563 }
564
565 GtkWidget *
566 create_variable_list (MSymbol lang, MSymbol name)
567 {
568   GtkListStore *store;
569   GtkWidget *view;
570   GtkCellRenderer *renderer;
571   MPlist *plist;
572
573   current_lang = lang;
574   current_name = name;
575
576   plist = minput_get_variable (lang, name, Mnil);
577   if (! plist)
578     return gtk_label_new (_("No customizable variables."));
579
580   /*
581    * plist == ((variable description status value [valid-value ...])
582    *           (variable description status value [valid-value ...])
583    *           ...)
584    */
585
586   store = gtk_list_store_new (NUM_VCOLS,
587                               G_TYPE_STRING,
588                               G_TYPE_STRING,
589                               G_TYPE_STRING);
590   for (; plist && mplist_key (plist) == Mplist; plist = mplist_next (plist))
591     {
592       GtkTreeIter iter;
593       MPlist *pl;
594       MSymbol variable, value, status;
595       gchar *desc;
596       gchar *status_str;
597
598       pl = mplist_value (plist);
599       /* pl == (variable description status value [valid-value ...]) */
600       current_variable = mplist_value (pl);
601
602       pl = mplist_next (pl); 
603       /* pl == (description status value [valid-value ...]) */
604       if (mplist_key (pl) == Mtext)
605         /* Fixme : Assuming the return value is in UTF-8 */
606         desc = mtext_data (mplist_value (pl), NULL, NULL, NULL, NULL);
607       else
608         desc = NULL;
609
610       pl = mplist_next (pl);
611       /* pl == (status value [valid-value ...]) */
612       status = mplist_value (pl);
613       gtk_list_store_append (store, &iter);
614       gtk_list_store_set (store, &iter,
615                           VCOL_VARIABLE, msymbol_name (current_variable),
616                           -1);
617       set_value_status (store, &iter);
618     }
619   view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
620   g_object_unref (G_OBJECT (store));
621   renderer = gtk_cell_renderer_text_new ();
622   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
623                                                -1,      
624                                                _("Name"),
625                                                renderer,
626                                                "text",
627                                                VCOL_VARIABLE,
628                                                NULL);
629   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
630                                                -1,      
631                                                _("Value"),
632                                                renderer,
633                                                "text",
634                                                VCOL_VALUE,
635                                                NULL);
636   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
637                                                -1,      
638                                                _("Status"),
639                                                renderer,
640                                                "text",
641                                                VCOL_STATUS,
642                                                NULL);
643   g_signal_connect (G_OBJECT (view), "row-activated",
644                     G_CALLBACK (activated_cb), NULL);
645   return view;
646 }