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