*** 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 <gtk/gtk.h>
5 #include <gdk/gdk.h>
6 #include <gdk/gdkkeysyms.h>
7 #include <m17n.h>
8
9 static MSymbol current_lang, current_name, current_command;
10
11 static MPlist *
12 get_command_bindings (MSymbol lang, MSymbol name, MSymbol command)
13 {
14   return (mplist_next
15           (mplist_next
16            (mplist_next
17             (mplist_value
18              (minput_get_command (lang, name, command))))));
19 }
20
21 static int
22 keyseq_equal (MPlist *pl1, MPlist *pl2)
23 {
24   if (mplist_length (pl1) != mplist_length (pl2))
25     return 0;
26   while (pl1 && mplist_key (pl1) == Msymbol)
27     {
28       if (mplist_value (pl1) != mplist_value (pl2))
29         return 0;
30       pl1 = mplist_next (pl1);
31       pl2 = mplist_next (pl2);
32     }
33   return 1;
34 }
35
36 static void
37 keyseq_render_function (GtkTreeViewColumn *column,
38                         GtkCellRenderer *renderer,
39                         GtkTreeModel *model,
40                         GtkTreeIter *iter,
41                         gpointer data)
42 {
43   MPlist *keyseq, *pl;
44   gint n;
45   gchar buf[1024];
46
47   gtk_tree_model_get (model, iter, 0, &keyseq, -1);
48   for (pl = keyseq, n = 0;
49        pl && mplist_key (pl) == Msymbol;
50        pl = mplist_next (pl))
51     n += strlen (msymbol_name ((MSymbol) mplist_value (pl))) + 1;
52   if (n < sizeof (buf))
53     {
54       buf[0] = '\0';
55       for (pl = keyseq;
56            pl && mplist_key (pl) == Msymbol;
57            pl = mplist_next (pl))
58         {
59           strcat (buf, msymbol_name ((MSymbol) mplist_value (pl)));
60           strcat (buf, " ");
61         }
62       g_object_set (renderer, "foreground-set", FALSE, NULL);
63     }
64   else
65     {
66       g_snprintf (buf, sizeof (buf), "Too long to display");
67       g_object_set (renderer, "foreground", "Red", "foreground-set", TRUE,
68                     NULL);
69     }
70   g_object_set (renderer, "text", buf, NULL);
71 }
72
73 static void
74 delete_cb (GtkButton *button, GtkWidget *view)
75 {
76   GtkTreeSelection *selection;
77   GtkTreeModel *model;
78   GtkTreeIter iter;
79   MPlist *keyseq, *old, *new, *pl;
80
81   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
82   if (gtk_tree_selection_get_selected (selection, &model, &iter))
83     {
84       gtk_tree_model_get (model, &iter, 0, &keyseq, -1);
85
86       old = get_command_bindings (current_lang, current_name, current_command);
87       new = mplist ();
88       for (pl = old; pl && mplist_key (pl) == Mplist; pl = mplist_next (pl))
89         if (! keyseq_equal (mplist_value (pl), keyseq))
90           mplist_add (new, Mplist, mplist_value (pl));
91       minput_config_command (current_lang, current_name, current_command, new);
92       m17n_object_unref (new);
93       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
94     }
95 }
96   
97
98 static void
99 delete_keyseq (GtkWidget *view, MSymbol lang, MSymbol name, MSymbol command)
100 {
101   GtkTreeSelection *selection;
102   GtkTreeModel *model;
103   GtkTreeIter iter;
104   MPlist *keyseq, *old, *new, *pl;
105
106   selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (view));
107   if (gtk_tree_selection_get_selected (selection, &model, &iter))
108     {
109       gtk_tree_model_get (model, &iter, 0, &keyseq, -1);
110       old = get_command_bindings (lang, name, command);
111       new = mplist ();
112       for (pl = old;
113            pl && mplist_key (pl) == Mplist;
114            pl = mplist_next (pl))
115         if (! keyseq_equal (mplist_value (pl), keyseq))
116           mplist_add (new, Mplist, mplist_value (pl));
117       minput_config_command (lang, name, command, new);
118       m17n_object_unref (new);
119       gtk_list_store_remove (GTK_LIST_STORE (model), &iter);
120     }
121 }
122
123 static MPlist *entry_keyseq;
124
125 static void
126 update_entry (GtkEntry *entry)
127 {
128   if (mplist_key (entry_keyseq) == Mnil)
129     gtk_entry_set_text (entry, "");
130   else
131     {
132       MPlist *p;
133       gchar *name;
134
135       name = msymbol_name ((MSymbol) mplist_value (entry_keyseq));
136       gtk_entry_set_text (entry, name);
137       for (p = mplist_next (entry_keyseq); mplist_key (p) != Mnil;
138            p = mplist_next (p))
139         {
140           name = msymbol_name ((MSymbol) mplist_value (p));
141           gtk_entry_append_text (entry, " ");
142           gtk_entry_append_text (entry, name);
143         }
144       gtk_editable_set_position (GTK_EDITABLE (entry), -1);
145     }
146 }
147
148 enum KeyMaskBit {
149   META_MASK_BIT = 1,
150   ALT_MASK_BIT = META_MASK_BIT << 1,
151   SUPER_MASK_BIT = ALT_MASK_BIT << 1,
152   HYPER_MASK_BIT = SUPER_MASK_BIT << 1
153 };
154
155 static unsigned modifier_state = 0;
156
157 gboolean
158 key_pressed (GtkEntry *entry, GdkEventKey *event, gpointer data)
159 {
160   guint c;
161   MText *mt;
162   char buf[32];
163   char *name;
164   int nbytes, i;
165
166   c = gdk_keyval_to_unicode (event->keyval);
167   if (c == 0)
168     {
169       switch (event->keyval)
170         {
171         case GDK_Meta_L: case GDK_Meta_R:
172           modifier_state |= META_MASK_BIT; return TRUE;
173         case GDK_Alt_L: case GDK_Alt_R:
174           modifier_state |= ALT_MASK_BIT; return TRUE;
175         case GDK_Super_L: case GDK_Super_R:
176           modifier_state |= SUPER_MASK_BIT; return TRUE;
177         case GDK_Hyper_L: case GDK_Hyper_R:
178           modifier_state |= HYPER_MASK_BIT; return TRUE;
179         default:
180           if (event->keyval >= GDK_Shift_L && event->keyval <= GDK_Shift_Lock)
181             return TRUE;
182         }
183       name = gdk_keyval_name (event->keyval);
184       if (! name)
185         return TRUE;
186       nbytes = strlen (name);
187     }
188   else
189     {
190       name = alloca (8);
191       mt = mtext ();
192       mtext_cat_char (mt, c);
193       nbytes = mconv_encode_buffer (msymbol ("utf-8"), mt,
194                                     (unsigned char *) name, 32);
195       m17n_object_unref (mt);
196     }
197   i = 0;
198   if (c == 0 && event->state & GDK_SHIFT_MASK)
199     buf[i++] = 'S', buf[i++] = '-';
200   if (event->state & GDK_CONTROL_MASK)
201     buf[i++] = 'C', buf[i++] = '-';
202   if (modifier_state & META_MASK_BIT)
203     buf[i++] = 'M', buf[i++] = '-';
204   if (modifier_state & ALT_MASK_BIT)
205     buf[i++] = 'A', buf[i++] = '-';
206   if (modifier_state & SUPER_MASK_BIT)
207     buf[i++] = 's', buf[i++] = '-';
208   if (modifier_state & HYPER_MASK_BIT)
209     buf[i++] = 'H', buf[i++] = '-';
210   strncpy (buf + i, name, nbytes);
211   buf[i + nbytes] = 0;
212   mplist_add (entry_keyseq, Msymbol, msymbol (buf));
213   update_entry (entry);
214   return TRUE;
215 }
216
217 gboolean
218 key_released (GtkEntry *entry, GdkEventKey *event, gpointer data)
219 {
220   guint c;
221
222   c = gdk_keyval_to_unicode (event->keyval);
223   if (c == 0)
224     {
225       switch (event->keyval)
226         {
227         case GDK_Meta_L: case GDK_Meta_R:
228           modifier_state &= ~META_MASK_BIT; break;
229         case GDK_Alt_L: case GDK_Alt_R:
230           modifier_state &= ~ALT_MASK_BIT; break;
231         case GDK_Super_L: case GDK_Super_R:
232           modifier_state &= ~SUPER_MASK_BIT; break;
233         case GDK_Hyper_L: case GDK_Hyper_R:
234           modifier_state &= ~HYPER_MASK_BIT; break;
235         }
236     }
237   return FALSE;
238 }
239
240 static void
241 clear_cb (GtkButton *button, GtkWidget *entry)
242 {
243   mplist_set (entry_keyseq, Mnil, NULL);
244   gtk_widget_grab_focus (entry);
245   update_entry (GTK_ENTRY (entry));
246 }
247
248 static void
249 add_cb (GtkButton *button, GtkListStore *store)
250 {
251   MPlist *old, *new, *pl, *last;
252   GtkTreeIter iter;
253   GtkWidget *view;
254
255   if (mplist_length (entry_keyseq) == 0)
256     return;
257   old = get_command_bindings (current_lang, current_name, current_command);
258   new = mplist ();
259   for (pl = old; pl && mplist_key (pl) == Mplist; pl = mplist_next (pl))
260     {
261       if (! keyseq_equal (mplist_value (pl), entry_keyseq))
262         mplist_add (new, Mplist, mplist_value (pl));
263       else
264         {
265           /* entry_keyseq is already registered. */
266           m17n_object_unref (new);
267           return;
268         }
269     }
270   mplist_add (new, Mplist, entry_keyseq);
271   minput_config_command (current_lang, current_name, current_command, new);
272   m17n_object_unref (new);
273
274   /* We cannot use ENTRY_KEYSEQ for gtk_list_store_set ().  We must
275      use a pointer to the internally copied one. */
276   new = get_command_bindings (current_lang, current_name, current_command);
277   for (pl = new; pl && mplist_key (pl) == Mplist;
278        last = pl, pl = mplist_next (pl))
279   gtk_list_store_append (store, &iter);
280   gtk_list_store_set (store, &iter, 0, mplist_value (last), -1);
281   view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
282 }
283
284 static void
285 command_cb (GtkButton *button, GtkWidget *frame)
286 {
287   GtkWidget *dialog, *view, *scrolled;
288   GtkWidget *delete, *entry, *add, *clear;
289   GtkListStore *store;
290   GtkTreeIter iter;
291   GtkTreeViewColumn *column;
292   GtkCellRenderer *renderer;
293   MPlist *bindings, *pl, *cmd;
294   gint response;
295
296   current_lang = g_object_get_data (G_OBJECT (button), "mim_lang");
297   current_name = g_object_get_data (G_OBJECT (button), "mim_name");
298   current_command = g_object_get_data (G_OBJECT (button), "mim_command");
299   bindings = get_command_bindings (current_lang, current_name, current_command);
300
301   dialog = (gtk_dialog_new_with_buttons
302             (msymbol_name (current_command),
303              GTK_WINDOW (gtk_widget_get_toplevel (frame)),
304              GTK_DIALOG_DESTROY_WITH_PARENT | GTK_DIALOG_NO_SEPARATOR,
305              GTK_STOCK_OK, GTK_RESPONSE_OK,
306              GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
307              NULL));
308
309   store = gtk_list_store_new (1, G_TYPE_POINTER);
310   for (pl = bindings; pl && mplist_key (pl) == Mplist; pl = mplist_next (pl))
311     {
312       gtk_list_store_append (store, &iter);
313       gtk_list_store_set (store, &iter, 0, mplist_value (pl), -1);
314     }
315   view = gtk_tree_view_new_with_model (GTK_TREE_MODEL (store));
316   g_object_unref (G_OBJECT (store));
317   column = gtk_tree_view_column_new ();
318   gtk_tree_view_column_set_title (column, "Current bindings");
319   gtk_tree_view_append_column (GTK_TREE_VIEW (view), column);
320   renderer = gtk_cell_renderer_text_new ();
321   gtk_tree_view_column_pack_start (column, renderer, TRUE);
322   gtk_tree_view_column_set_cell_data_func
323     (column, renderer, keyseq_render_function, NULL, NULL);
324   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), view);
325
326   delete = gtk_button_new_from_stock (GTK_STOCK_DELETE);
327   g_signal_connect (G_OBJECT (delete), "clicked",
328                     G_CALLBACK (delete_cb), view);
329   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), delete);
330
331   entry_keyseq = mplist ();
332
333   entry = gtk_entry_new ();
334   g_signal_connect (G_OBJECT (entry), "key-press-event",
335                     G_CALLBACK (key_pressed), NULL);
336   g_signal_connect (G_OBJECT (entry), "key-release-event",
337                     G_CALLBACK (key_released), NULL);
338   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), entry);
339
340   clear = gtk_button_new_from_stock (GTK_STOCK_CLEAR);
341   g_signal_connect (G_OBJECT (clear), "clicked",
342                     G_CALLBACK (clear_cb), entry);
343   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), clear);
344
345   add = gtk_button_new_from_stock (GTK_STOCK_ADD);
346   g_signal_connect (G_OBJECT (add), "clicked",
347                     G_CALLBACK (add_cb), store);
348   gtk_container_add (GTK_CONTAINER (GTK_DIALOG (dialog)->vbox), add);
349
350   gtk_widget_show_all (dialog);
351   response = gtk_dialog_run (GTK_DIALOG (dialog));
352   gtk_widget_destroy (dialog);
353
354   m17n_object_unref (entry_keyseq);
355 }
356
357 void
358 create_command_entries (GtkWidget *frame, GtkTooltips *tip,
359                         MSymbol lang, MSymbol name)
360 {
361   GtkWidget *vbox;
362   MPlist *plist;
363
364   plist = minput_get_command (lang, name, Mnil);
365   if (! plist)
366     {
367       GtkWidget *message;
368
369       message = gtk_label_new ("No commands for this method");
370       gtk_container_add (GTK_CONTAINER (frame), message);
371       return;
372     }
373
374   /*
375    * plist == ((command description status keyseq keyseq ...)
376    *           (command description status keyseq keyseq ...)
377    *           ...)
378    */
379   vbox = gtk_vbox_new (FALSE, 0);
380   gtk_container_add (GTK_CONTAINER (frame), vbox);
381
382   for (; plist && mplist_key (plist) == Mplist; plist = mplist_next (plist))
383     {
384       GtkWidget *button;
385       MPlist *pl;
386       MSymbol command;
387       gchar *desc;
388
389       pl = mplist_value (plist);
390       /* pl == (command description status keyseq keyseq ...) */
391       command = mplist_value (pl);
392       button = gtk_button_new_with_label (msymbol_name (command));
393       gtk_container_add (GTK_CONTAINER (vbox), button);
394
395       pl = mplist_next (pl);
396       /* pl == (description status keyseq keyseq ...) */
397       if (mplist_key (pl) == Mtext)
398         /* Fixme : Assuming the return value is in UTF-8 */
399         desc = mtext_data (mplist_value (pl), NULL, NULL, NULL, NULL);
400       else
401         desc = NULL;
402       gtk_tooltips_set_tip (tip, button, desc, NULL);
403
404       g_object_set_data (G_OBJECT (button), "mim_lang", lang);
405       g_object_set_data (G_OBJECT (button), "mim_name", name);
406       g_object_set_data (G_OBJECT (button), "mim_command", command);
407       g_signal_connect (G_OBJECT (button), "clicked",
408                         G_CALLBACK (command_cb), frame);
409     }
410 }
411