*** empty log message ***
[m17n/m17n-im-config.git] / src / command.c
1 #include <stdlib.h>
2 #include <string.h>
3 #include <libintl.h>
4 #include <m17n.h>
5 #include <gtk/gtk.h>
6 #include <gdk/gdk.h>
7 #include <gdk/gdkkeysyms.h>
8 #include <config.h>
9
10 #define _(String) dgettext (PACKAGE, String)
11
12 #define CURRENT_BINDINGS        \
13   (mplist_next                  \
14    (mplist_next                 \
15     (mplist_next                \
16      (mplist_value              \
17       (minput_get_command       \
18        (current_lang, current_name, current_command))))))
19
20 #define CURRENT_STATUS          \
21   (mplist_value                 \
22    (mplist_next                 \
23     (mplist_next                \
24      (mplist_value              \
25       (minput_get_command       \
26        (current_lang, current_name, current_command))))))
27
28 #define CONFIG_COMMAND(plist)                                           \
29   minput_config_command (current_lang, current_name, current_command,   \
30                          (plist))
31
32 static void
33 mtext__cat_str (MText *mt, char *str)
34 {
35   while (*str)
36     {
37       mtext_cat_char (mt, (int) *str);
38       str++;
39     }
40 }
41
42 static unsigned modifier_state = 0;
43 static MPlist *entry_keyseq;
44 static MSymbol current_lang, current_name, current_command;
45
46 struct BindingWidgets
47 {
48   GtkWidget *entry;
49   GtkWidget *clear;
50   GtkWidget *add;
51   GtkWidget *view;
52   GtkWidget *default_;
53   GtkWidget *revert;
54   GtkWidget *delete;
55   GtkWidget *status;
56 };
57
58 enum KeyMaskBit {
59   META_MASK_BIT = 1,
60   ALT_MASK_BIT = META_MASK_BIT << 1,
61   SUPER_MASK_BIT = ALT_MASK_BIT << 1,
62   HYPER_MASK_BIT = SUPER_MASK_BIT << 1
63 };
64
65 static int
66 keyseq_equal (MPlist *pl1, MPlist *pl2)
67 {
68   if (mplist_length (pl1) != mplist_length (pl2))
69     return 0;
70   while (pl1 && mplist_key (pl1) == Msymbol)
71     {
72       if (mplist_value (pl1) != mplist_value (pl2))
73         return 0;
74       pl1 = mplist_next (pl1);
75       pl2 = mplist_next (pl2);
76     }
77   return 1;
78 }
79
80 static void
81 update_entry (GtkEntry *entry)
82 {
83   if (mplist_key (entry_keyseq) == Mnil)
84     gtk_entry_set_text (entry, "");
85   else
86     {
87       MPlist *p;
88       gchar *name;
89
90       name = msymbol_name ((MSymbol) mplist_value (entry_keyseq));
91       gtk_entry_set_text (entry, name);
92       for (p = mplist_next (entry_keyseq); mplist_key (p) != Mnil;
93            p = mplist_next (p))
94         {
95           name = msymbol_name ((MSymbol) mplist_value (p));
96           gtk_entry_append_text (entry, " ");
97           gtk_entry_append_text (entry, name);
98         }
99       gtk_editable_set_position (GTK_EDITABLE (entry), -1);
100     }
101 }
102
103 static void
104 update_binding_store (GtkWidget *view)
105 {
106   GtkListStore *store;
107   GtkTreeIter iter;
108   MPlist *pl;
109
110   store =  GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (view)));
111   gtk_list_store_clear (store);
112
113   for (pl = CURRENT_BINDINGS;
114        pl && mplist_key (pl) == Mplist;
115        pl = mplist_next (pl))
116     {
117       gtk_list_store_append (store, &iter);
118       gtk_list_store_set (store, &iter, 0, mplist_value (pl), -1);
119     }
120 }
121
122 static gboolean
123 key_pressed_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
124 {
125   guint c;
126   MText *mt;
127   char buf[32];
128   char *name;
129   int nbytes, i;
130   struct BindingWidgets *bw = data;
131
132   c = gdk_keyval_to_unicode (event->keyval);
133   if (c == 0)
134     {
135       switch (event->keyval)
136         {
137         case GDK_Meta_L: case GDK_Meta_R:
138           modifier_state |= META_MASK_BIT; return TRUE;
139         case GDK_Alt_L: case GDK_Alt_R:
140           modifier_state |= ALT_MASK_BIT; return TRUE;
141         case GDK_Super_L: case GDK_Super_R:
142           modifier_state |= SUPER_MASK_BIT; return TRUE;
143         case GDK_Hyper_L: case GDK_Hyper_R:
144           modifier_state |= HYPER_MASK_BIT; return TRUE;
145         default:
146           if (event->keyval >= GDK_Shift_L && event->keyval <= GDK_Shift_Lock)
147             return TRUE;
148         }
149       name = gdk_keyval_name (event->keyval);
150       if (! name)
151         return TRUE;
152       nbytes = strlen (name);
153     }
154   else
155     {
156       name = alloca (8);
157       mt = mtext ();
158       mtext_cat_char (mt, c);
159       nbytes = mconv_encode_buffer (msymbol ("utf-8"), mt,
160                                     (unsigned char *) name, 32);
161       m17n_object_unref (mt);
162     }
163   i = 0;
164   if (c == 0 && event->state & GDK_SHIFT_MASK)
165     buf[i++] = 'S', buf[i++] = '-';
166   if (event->state & GDK_CONTROL_MASK)
167     buf[i++] = 'C', buf[i++] = '-';
168   if (modifier_state & META_MASK_BIT)
169     buf[i++] = 'M', buf[i++] = '-';
170   if (modifier_state & ALT_MASK_BIT)
171     buf[i++] = 'A', buf[i++] = '-';
172   if (modifier_state & SUPER_MASK_BIT)
173     buf[i++] = 's', buf[i++] = '-';
174   if (modifier_state & HYPER_MASK_BIT)
175     buf[i++] = 'H', buf[i++] = '-';
176   strncpy (buf + i, name, nbytes);
177   buf[i + nbytes] = 0;
178   mplist_add (entry_keyseq, Msymbol, msymbol (buf));
179   update_entry (entry);
180   gtk_widget_set_sensitive (bw->clear, TRUE);
181   gtk_widget_set_sensitive (bw->add, TRUE);
182   return TRUE;
183 }
184
185 static gboolean
186 key_released_cb (GtkEntry *entry, GdkEventKey *event, gpointer data)
187 {
188   guint c;
189
190   c = gdk_keyval_to_unicode (event->keyval);
191   if (c == 0)
192     {
193       switch (event->keyval)
194         {
195         case GDK_Meta_L: case GDK_Meta_R:
196           modifier_state &= ~META_MASK_BIT; break;
197         case GDK_Alt_L: case GDK_Alt_R:
198           modifier_state &= ~ALT_MASK_BIT; break;
199         case GDK_Super_L: case GDK_Super_R:
200           modifier_state &= ~SUPER_MASK_BIT; break;
201         case GDK_Hyper_L: case GDK_Hyper_R:
202           modifier_state &= ~HYPER_MASK_BIT; break;
203         }
204     }
205   return FALSE;
206 }
207
208 static void
209 update_widgets (struct BindingWidgets *bw)
210 {
211   MSymbol status = CURRENT_STATUS;
212
213   if (status == Mconfigured)
214     {
215       gtk_widget_set_sensitive (bw->default_, TRUE);
216       gtk_widget_set_sensitive (bw->revert, TRUE);
217       gtk_label_set_text (GTK_LABEL (bw->status), _("Status : modified"));
218     }
219   else if (status == Mcustomized)
220     {
221       gtk_widget_set_sensitive (bw->default_, TRUE);
222       gtk_widget_set_sensitive (bw->revert, FALSE);
223       gtk_label_set_text (GTK_LABEL (bw->status), _("Status : customized"));
224     }
225   else
226     {
227       gtk_widget_set_sensitive (bw->default_, FALSE);
228       gtk_widget_set_sensitive (bw->revert, FALSE);
229       gtk_label_set_text (GTK_LABEL (bw->status), _("Status : default"));
230     }
231 }
232
233 static void
234 clear_cb (GtkButton *button, gpointer data)
235 {
236   struct BindingWidgets *bw = data;
237
238   mplist_set (entry_keyseq, Mnil, NULL);
239   gtk_widget_grab_focus (bw->entry);
240   update_entry (GTK_ENTRY (bw->entry));
241   gtk_widget_set_sensitive (bw->clear, FALSE);
242   gtk_widget_set_sensitive (bw->add, FALSE);
243 }
244
245 static void
246 add_cb (GtkButton *button, gpointer data)
247 {
248   MPlist *new, *pl, *last;
249   GtkListStore *store;
250   GtkTreeIter iter;
251   struct BindingWidgets *bw = data;
252
253   if (mplist_length (entry_keyseq) == 0)
254     return;
255   new = mplist ();
256   for (pl = CURRENT_BINDINGS;
257        pl && mplist_key (pl) == Mplist;
258        pl = mplist_next (pl))
259     {
260       if (! keyseq_equal (mplist_value (pl), entry_keyseq))
261         mplist_add (new, Mplist, mplist_value (pl));
262       else
263         {
264           /* entry_keyseq is already registered. */
265           m17n_object_unref (new);
266           return;
267         }
268     }
269   mplist_add (new, Mplist, entry_keyseq);
270   CONFIG_COMMAND (new);
271   m17n_object_unref (new);
272
273   /* We cannot use ENTRY_KEYSEQ for gtk_list_store_set ().  We must
274      use a pointer to the internally copied one. */
275   new = CURRENT_BINDINGS;
276   for (pl = new;
277        pl && mplist_key (pl) == Mplist;
278        last = pl, pl = mplist_next (pl));
279   store = GTK_LIST_STORE (gtk_tree_view_get_model (GTK_TREE_VIEW (bw->view)));
280   gtk_list_store_append (store, &iter);
281   gtk_list_store_set (store, &iter, 0, mplist_value (last), -1);
282   update_binding_store (bw->view);
283   clear_cb (NULL, bw);
284   update_widgets (bw);
285 }
286
287 static GtkWidget *
288 create_adding_section (struct BindingWidgets *bw)
289 {
290   GtkWidget *label, *hbox, *vbox;
291
292   label = gtk_label_new (_("New key binding:"));
293
294   entry_keyseq = mplist ();
295   bw->entry = gtk_entry_new ();
296   g_signal_connect (G_OBJECT (bw->entry), "key-press-event",
297                     G_CALLBACK (key_pressed_cb), bw);
298   g_signal_connect (G_OBJECT (bw->entry), "key-release-event",
299                     G_CALLBACK (key_released_cb), bw);
300
301   bw->clear = gtk_button_new_from_stock (GTK_STOCK_CLEAR);
302   gtk_widget_set_sensitive (bw->clear, FALSE);
303   g_signal_connect (G_OBJECT (bw->clear), "clicked",
304                     G_CALLBACK (clear_cb), bw);
305
306   bw->add = gtk_button_new_from_stock (GTK_STOCK_ADD);
307   gtk_widget_set_sensitive (bw->add, FALSE);
308   g_signal_connect (G_OBJECT (bw->add), "clicked",
309                     G_CALLBACK (add_cb), bw);
310
311   vbox = gtk_vbox_new (FALSE, 6);
312   gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
313
314   hbox = gtk_hbox_new (FALSE, 6);
315   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 6);
316   gtk_container_add (GTK_CONTAINER (vbox), hbox);
317
318   gtk_container_add (GTK_CONTAINER (vbox), bw->entry);
319
320   hbox = gtk_hbutton_box_new ();
321   gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
322   gtk_box_set_spacing (GTK_BOX (hbox), 6);
323   gtk_container_add (GTK_CONTAINER (hbox), bw->clear);
324   gtk_container_add (GTK_CONTAINER (hbox), bw->add);
325   gtk_container_add (GTK_CONTAINER (vbox), hbox);
326
327   return vbox;
328 }
329
330 static void
331 selection_cb (GtkTreeSelection *selection, gpointer data)
332 {
333   struct BindingWidgets *bw = data;
334
335   gtk_widget_set_sensitive
336     (bw->delete,
337      gtk_tree_selection_count_selected_rows (selection) ? TRUE : FALSE);
338 }
339
340 static void
341 keyseq_render_function (GtkTreeViewColumn *column,
342                         GtkCellRenderer *renderer,
343                         GtkTreeModel *model,
344                         GtkTreeIter *iter,
345                         gpointer data)
346 {
347   MPlist *keyseq, *pl;
348   gint n;
349   MText *mt = mtext ();
350
351   gtk_tree_model_get (model, iter, 0, &keyseq, -1);
352   for (pl = keyseq;
353        pl && mplist_key (pl) == Msymbol;
354        pl = mplist_next (pl))
355     {
356       mtext__cat_str (mt, msymbol_name ((MSymbol) mplist_value (pl)));
357       mtext_cat_char (mt, ' ');
358     }
359   g_object_set (renderer, "text", mtext_data (mt, NULL, NULL, NULL, NULL),
360                 NULL);
361   m17n_object_unref (mt);    
362 }
363
364 static void *
365 default_cb (GtkButton *button, gpointer data)
366 {
367   MPlist *empty = mplist ();
368   struct BindingWidgets *bw = data;
369
370   CONFIG_COMMAND (empty);
371   m17n_object_unref (empty);
372   update_binding_store (bw->view);
373   update_widgets (bw);
374 }
375
376 static void *
377 revert_cb (GtkButton *button, gpointer data)
378 {
379   struct BindingWidgets *bw = data;
380
381   CONFIG_COMMAND (NULL);
382   update_binding_store (bw->view);
383   update_widgets (bw);
384 }
385
386 static void
387 delete_cb (GtkButton *button, gpointer data)
388 {
389   GtkTreeSelection *selection;
390   GtkTreeModel *model;
391   GtkTreeIter iter;
392   MPlist *keyseq, *new, *pl;
393   struct BindingWidgets *bw = data;
394
395   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (bw->view));
396   if (! gtk_tree_selection_get_selected (selection, &model, &iter))
397     return;
398   gtk_tree_model_get (model, &iter, 0, &keyseq, -1);
399   new = mplist ();
400   for (pl = CURRENT_BINDINGS;
401        pl && mplist_key (pl) == Mplist;
402        pl = mplist_next (pl))
403     if (! keyseq_equal (mplist_value (pl), keyseq))
404       mplist_add (new, Mplist, mplist_value (pl));
405   CONFIG_COMMAND (new);
406   m17n_object_unref (new);
407   gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
408   update_binding_store (bw->view);
409   update_widgets (bw);
410 }
411
412 static GtkWidget *
413 create_deleting_section (struct BindingWidgets *bw)
414 {
415   GtkListStore *store;
416   GtkWidget *label, *scrolled, *hbox, *vbox;
417   GtkTreeViewColumn *column;
418   GtkCellRenderer *renderer;
419   GtkTreeIter iter;
420   GtkTreeSelection *selection;
421   MPlist *pl;
422
423   label = gtk_label_new (_("Current key bindings:"));
424
425   store = gtk_list_store_new (1, G_TYPE_POINTER);
426   bw->view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
427   g_object_unref (G_OBJECT (store));
428   gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (bw->view), FALSE);
429   update_binding_store (bw->view);
430   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW(bw->view));
431   gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
432   g_signal_connect (G_OBJECT (selection), "changed",
433                     G_CALLBACK (selection_cb), bw);
434
435   scrolled = gtk_scrolled_window_new (NULL, NULL);
436   gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
437                                   GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
438   gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
439                                          bw->view);
440
441   column = gtk_tree_view_column_new ();
442   gtk_tree_view_append_column (GTK_TREE_VIEW (bw->view), column);
443
444   renderer = gtk_cell_renderer_text_new ();
445   gtk_tree_view_column_pack_start (column, renderer, TRUE);
446   gtk_tree_view_column_set_cell_data_func
447     (column, renderer, keyseq_render_function, NULL, NULL);
448
449   bw->default_ = gtk_button_new_from_stock (_("_Default"));
450   g_signal_connect (G_OBJECT (bw->default_), "clicked",
451                     G_CALLBACK (default_cb), bw);
452
453   bw->revert = gtk_button_new_from_stock (GTK_STOCK_REVERT_TO_SAVED);
454   g_signal_connect (G_OBJECT (bw->revert), "clicked",
455                     G_CALLBACK (revert_cb), bw);
456
457   bw->delete = gtk_button_new_from_stock (GTK_STOCK_DELETE);
458   gtk_widget_set_sensitive (bw->delete, FALSE);
459   g_signal_connect (G_OBJECT (bw->delete), "clicked",
460                     G_CALLBACK (delete_cb), bw);
461
462   vbox = gtk_vbox_new (FALSE, 6);
463   gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
464
465   hbox = gtk_hbox_new (FALSE, 6);
466   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 6);
467   gtk_container_add (GTK_CONTAINER (vbox), hbox);
468
469   gtk_container_add (GTK_CONTAINER (vbox), scrolled);
470
471   hbox = gtk_hbutton_box_new ();
472   gtk_button_box_set_layout (GTK_BUTTON_BOX (hbox), GTK_BUTTONBOX_END);
473   gtk_box_set_spacing (GTK_BOX (hbox), 6);
474   gtk_container_add (GTK_CONTAINER (hbox), bw->default_);
475   gtk_container_add (GTK_CONTAINER (hbox), bw->revert);
476   gtk_container_add (GTK_CONTAINER (hbox), bw->delete);
477   gtk_container_add (GTK_CONTAINER (vbox), hbox);
478
479   return vbox;
480 }
481
482 static void
483 set_status (GtkListStore *store, GtkTreeIter *iter)
484 {
485   MSymbol status;
486   gchar *status_str;
487
488   status = CURRENT_STATUS;
489   if (status == Mconfigured)
490     status_str = _("modified");
491   else if (status == Mcustomized)
492     status_str = _("customized");
493   else
494     status_str = _("default");
495   gtk_list_store_set (store, iter, 1, status_str, -1);
496 }
497
498 static void
499 activated_cb (GtkTreeView *parent, GtkTreePath *path,
500               GtkTreeViewColumn *col, gpointer data)
501 {
502   GtkTreeModel *model;
503   GtkTreeIter iter;
504   GtkWidget *dialog, *vbox, *hbox;
505   struct BindingWidgets bw;
506   gchar *command;
507
508   model = gtk_tree_view_get_model (parent);
509   if (! gtk_tree_model_get_iter (model, &iter, path))
510     return;
511   gtk_tree_model_get (model, &iter, 0, &command, -1);
512   current_command = msymbol (command);
513
514   dialog = (gtk_dialog_new_with_buttons
515             (command,
516              GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (parent))),
517              GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
518              GTK_STOCK_OK, GTK_RESPONSE_OK,
519              NULL));
520
521   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
522                      create_deleting_section (&bw));
523
524   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox),
525                      create_adding_section (&bw));
526
527   vbox = gtk_vbox_new (FALSE, 6);
528   gtk_container_set_border_width (GTK_CONTAINER (vbox), 6);
529   hbox = gtk_hbox_new (FALSE, 6);
530   bw.status = gtk_label_new (NULL);
531   gtk_box_pack_start (GTK_BOX (hbox), bw.status, FALSE, FALSE, 6);
532   gtk_container_add (GTK_CONTAINER (vbox), hbox);
533   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), vbox);
534
535   update_widgets (&bw);
536   gtk_widget_show_all (dialog);
537   gtk_dialog_run (GTK_DIALOG (dialog));
538   gtk_tree_model_get_iter (model, &iter, path);
539   set_status (GTK_LIST_STORE (model), &iter);
540   gtk_widget_destroy (dialog);
541   m17n_object_unref (entry_keyseq);
542 }
543
544 GtkWidget *
545 create_command_list (GtkTooltips *tip, MSymbol lang, MSymbol name)
546 {
547   GtkListStore *store;
548   GtkWidget *view;
549   GtkCellRenderer *renderer;
550   MPlist *plist;
551
552   current_lang = lang;
553   current_name = name;
554
555   plist = minput_get_command (lang, name, Mnil);
556   /*
557    * plist == ((command description status keyseq keyseq ...)
558    *           (command description status keyseq keyseq ...)
559    *           ...)
560    */
561   if (! plist)
562     return gtk_label_new (_("No customizable commands."));
563
564   store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_STRING);
565   for (; plist && mplist_key (plist) == Mplist; plist = mplist_next (plist))
566     {
567       GtkTreeIter iter;
568       MPlist *pl;
569       MSymbol command, status;
570       gchar *desc;
571       gchar *status_str;
572
573       pl = mplist_value (plist);
574       /* pl == (command description status keyseq keyseq ...) */
575       current_command = command = mplist_value (pl);
576
577       pl = mplist_next (pl); 
578       /* pl == (description status keyseq keyseq ...) */
579       if (mplist_key (pl) == Mtext)
580         /* Fixme : Assuming the return value is in UTF-8 */
581         desc = mtext_data (mplist_value (pl), NULL, NULL, NULL, NULL);
582       else
583         desc = NULL;
584
585       pl = mplist_next (pl);
586       /* pl == (status keyseq keyseq ...) */
587       status = mplist_value (pl);
588       gtk_list_store_append (store, &iter);
589       gtk_list_store_set (store, &iter,
590                           0, msymbol_name (command),
591                           -1);
592       set_status (store, &iter);
593     }
594   view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
595   g_object_unref (G_OBJECT (store));
596   renderer = gtk_cell_renderer_text_new ();
597   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
598                                                -1,      
599                                                _("Name"),
600                                                renderer,
601                                                "text", 0,
602                                                NULL);
603   renderer = gtk_cell_renderer_text_new ();
604   gtk_tree_view_insert_column_with_attributes (GTK_TREE_VIEW (view),
605                                                -1,      
606                                                _("Status"),
607                                                renderer,
608                                                "text", 1,
609                                                NULL);
610   g_signal_connect (G_OBJECT (view), "row-activated",
611                     G_CALLBACK (activated_cb), NULL);
612   return view;
613 }