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