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