1 /*
2 * widgets/common.c - common widget functions or callbacks
3 *
4 * Copyright © 2010 Mason Larobina <mason.larobina@gmail.com>
5 *
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20
21 #include <gtk/gtk.h>
22
23 #include "luah.h"
24 #include "globalconf.h"
25 #include "common/luaobject.h"
26 #include "common/lualib.h"
27 #include "widgets/common.h"
28
29 gboolean
key_press_cb(GtkWidget * UNUSED (win),GdkEventKey * ev,widget_t * w)30 key_press_cb(GtkWidget* UNUSED(win), GdkEventKey *ev, widget_t *w)
31 {
32 lua_State *L = common.L;
33 luaH_object_push(L, w->ref);
34 luaH_modifier_table_push(L, ev->state);
35 luaH_keystr_push(L, ev->keyval);
36 lua_pushboolean(L, ev->send_event);
37 gint ret = luaH_object_emit_signal(L, -4, "key-press", 3, 1);
38 gboolean catch = ret && lua_toboolean(L, -1) ? TRUE : FALSE;
39 lua_pop(L, ret + 1);
40 return catch;
41 }
42
43 gboolean
button_cb(GtkWidget * UNUSED (win),GdkEventButton * ev,widget_t * w)44 button_cb(GtkWidget* UNUSED(win), GdkEventButton *ev, widget_t *w)
45 {
46 gint ret;
47 lua_State *L = common.L;
48 luaH_object_push(L, w->ref);
49 luaH_modifier_table_push(L, ev->state);
50 lua_pushinteger(L, ev->button);
51
52 switch (ev->type) {
53 case GDK_2BUTTON_PRESS:
54 ret = luaH_object_emit_signal(L, -3, "button-double-click", 2, 1);
55 break;
56 case GDK_BUTTON_RELEASE:
57 ret = luaH_object_emit_signal(L, -3, "button-release", 2, 1);
58 break;
59 default:
60 ret = luaH_object_emit_signal(L, -3, "button-press", 2, 1);
61 break;
62 }
63
64 gboolean catch = ret && lua_toboolean(L, -1) ? TRUE : FALSE;
65 lua_pop(L, ret + 1);
66 return catch;
67 }
68
69 gboolean
scroll_cb(GtkWidget * UNUSED (wid),GdkEventScroll * ev,widget_t * w)70 scroll_cb(GtkWidget *UNUSED(wid), GdkEventScroll *ev, widget_t *w)
71 {
72 double dx, dy;
73 switch (ev->direction) {
74 case GDK_SCROLL_UP: dx = 0; dy = -1; break;
75 case GDK_SCROLL_DOWN: dx = 0; dy = 1; break;
76 case GDK_SCROLL_LEFT: dx = -1; dy = 0; break;
77 case GDK_SCROLL_RIGHT: dx = 1; dy = 0; break;
78 case GDK_SCROLL_SMOOTH: gdk_event_get_scroll_deltas((GdkEvent*)ev, &dx, &dy); break;
79 default: g_assert_not_reached();
80 }
81
82 lua_State *L = common.L;
83 luaH_object_push(L, w->ref);
84 luaH_modifier_table_push(L, ev->state);
85 lua_pushnumber(L, dx);
86 lua_pushnumber(L, dy);
87
88 gboolean ret = luaH_object_emit_signal(L, -4, "scroll", 3, 1);
89 lua_pop(L, ret + 1);
90 return ret;
91 }
92
93 gboolean
mouse_cb(GtkWidget * UNUSED (win),GdkEventCrossing * ev,widget_t * w)94 mouse_cb(GtkWidget* UNUSED(win), GdkEventCrossing *ev, widget_t *w)
95 {
96 lua_State *L = common.L;
97 luaH_object_push(L, w->ref);
98 luaH_modifier_table_push(L, ev->state);
99
100 GdkEventType type = ev->type;
101 g_assert(type == GDK_ENTER_NOTIFY || type == GDK_LEAVE_NOTIFY);
102 gint ret = luaH_object_emit_signal(L, -2, type == GDK_ENTER_NOTIFY ? "mouse-enter" : "mouse-leave", 1, 1);
103
104 gboolean catch = ret && lua_toboolean(L, -1) ? TRUE : FALSE;
105 lua_pop(L, ret + 1);
106 return catch;
107 }
108
109 gboolean
focus_cb(GtkWidget * UNUSED (win),GdkEventFocus * ev,widget_t * w)110 focus_cb(GtkWidget* UNUSED(win), GdkEventFocus *ev, widget_t *w)
111 {
112 lua_State *L = common.L;
113 luaH_object_push(L, w->ref);
114 gint ret;
115 if (ev->in)
116 ret = luaH_object_emit_signal(L, -1, "focus", 0, 1);
117 else
118 ret = luaH_object_emit_signal(L, -1, "unfocus", 0, 1);
119
120 /* catch focus event */
121 if (ret && lua_toboolean(L, -1)) {
122 lua_pop(L, ret + 1);
123 return TRUE;
124 }
125
126 lua_pop(L, ret + 1);
127 /* propagate event further */
128 return FALSE;
129 }
130
131 /* gtk container add callback */
132 void
add_cb(GtkContainer * UNUSED (c),GtkWidget * widget,widget_t * w)133 add_cb(GtkContainer* UNUSED(c), GtkWidget *widget, widget_t *w)
134 {
135 widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget);
136 lua_State *L = common.L;
137 luaH_object_push(L, w->ref);
138 luaH_object_push(L, child->ref);
139 luaH_object_emit_signal(L, -2, "add", 1, 0);
140 lua_pop(L, 1);
141 }
142
143 void
resize_cb(GtkWidget * UNUSED (win),GdkRectangle * rect,widget_t * w)144 resize_cb(GtkWidget* UNUSED(win), GdkRectangle *rect, widget_t *w)
145 {
146 int width = rect->width, height = rect->height;
147 if (width == w->prev_width && height == w->prev_height)
148 return;
149 w->prev_width = width;
150 w->prev_height = height;
151
152 lua_State *L = common.L;
153 luaH_object_push(L, w->ref);
154 lua_pushinteger(L, width);
155 lua_pushinteger(L, height);
156 luaH_object_emit_signal(L, -3, "resize", 2, 0);
157 lua_pop(L, 1);
158 }
159
160 /* gtk container remove callback */
161 void
remove_cb(GtkContainer * UNUSED (c),GtkWidget * widget,widget_t * w)162 remove_cb(GtkContainer* UNUSED(c), GtkWidget *widget, widget_t *w)
163 {
164 widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget);
165 lua_State *L = common.L;
166 luaH_object_push(L, w->ref);
167 luaH_object_push(L, child->ref);
168 luaH_object_emit_signal(L, -2, "remove", 1, 0);
169 lua_pop(L, 1);
170 }
171
172 void
parent_set_cb(GtkWidget * widget,GtkWidget * UNUSED (p),widget_t * w)173 parent_set_cb(GtkWidget *widget, GtkWidget *UNUSED(p), widget_t *w)
174 {
175 lua_State *L = common.L;
176 widget_t *parent = NULL;
177 GtkContainer *new;
178 g_object_get(G_OBJECT(widget), "parent", &new, NULL);
179 luaH_object_push(L, w->ref);
180 if (new && (parent = GOBJECT_TO_LUAKIT_WIDGET(new)))
181 luaH_object_push(L, parent->ref);
182 else
183 lua_pushnil(L);
184 luaH_object_emit_signal(L, -2, "parent-set", 1, 0);
185 lua_pop(L, 1);
186 }
187
188 void
destroy_cb(GtkWidget * UNUSED (win),widget_t * w)189 destroy_cb(GtkWidget* UNUSED(win), widget_t *w)
190 {
191 /* 1. emit destroy signal */
192 lua_State *L = common.L;
193 luaH_object_push(L, w->ref);
194 luaH_object_emit_signal(L, -1, "destroy", 0, 0);
195 lua_pop(L, 1);
196
197 /* 2. Call widget destructor */
198 debug("destroy %p (%s)", w, w->info->name);
199 if (w->destructor)
200 w->destructor(w);
201 w->destructor = NULL;
202 w->widget = NULL;
203
204 /* 3. Allow this Lua instance to be freed */
205 luaH_object_unref(L, w->ref);
206 }
207
208 gboolean
true_cb()209 true_cb()
210 {
211 return TRUE;
212 }
213
214 /* set child method for gtk container widgets */
215 gint
luaH_widget_set_child(lua_State * L,widget_t * w)216 luaH_widget_set_child(lua_State *L, widget_t *w)
217 {
218 widget_t *child = luaH_checkwidgetornil(L, 3);
219
220 /* remove old child */
221 GtkWidget *widget = gtk_bin_get_child(GTK_BIN(w->widget));
222 if (widget) {
223 g_object_ref(G_OBJECT(widget));
224 gtk_container_remove(GTK_CONTAINER(w->widget), GTK_WIDGET(widget));
225 }
226
227 /* add new child to container */
228 if (child)
229 gtk_container_add(GTK_CONTAINER(w->widget), GTK_WIDGET(child->widget));
230 return 0;
231 }
232
233 /* get child method for gtk container widgets */
234 gint
luaH_widget_get_child(lua_State * L,widget_t * w)235 luaH_widget_get_child(lua_State *L, widget_t *w)
236 {
237 GtkWidget *widget = gtk_bin_get_child(GTK_BIN(w->widget));
238
239 if (!widget)
240 return 0;
241
242 widget_t *child = GOBJECT_TO_LUAKIT_WIDGET(widget);
243 luaH_object_push(L, child->ref);
244 return 1;
245 }
246
247 gint
luaH_widget_remove(lua_State * L)248 luaH_widget_remove(lua_State *L)
249 {
250 widget_t *w = luaH_checkwidget(L, 1);
251 widget_t *child = luaH_checkwidget(L, 2);
252 g_object_ref(G_OBJECT(child->widget));
253 gtk_container_remove(GTK_CONTAINER(w->widget), GTK_WIDGET(child->widget));
254 return 0;
255 }
256
257 gint
luaH_widget_get_children(lua_State * L,widget_t * w)258 luaH_widget_get_children(lua_State *L, widget_t *w)
259 {
260 if (!GTK_IS_CONTAINER(w->widget))
261 return 0;
262
263 GList *children = gtk_container_get_children(GTK_CONTAINER(w->widget));
264 GList *iter = children;
265
266 /* push table of the containers children onto the stack */
267 lua_newtable(L);
268 for (gint i = 1; iter; iter = iter->next) {
269 luaH_object_push(L, GOBJECT_TO_LUAKIT_WIDGET(iter->data)->ref);
270 lua_rawseti(L, -2, i++);
271 }
272 g_list_free(children);
273 return 1;
274 }
275
276 gint
luaH_widget_replace(lua_State * L)277 luaH_widget_replace(lua_State *L)
278 {
279 widget_t *och = luaH_checkwidget(L, 1);
280 widget_t *nch = luaH_checkwidget(L, 2);
281
282 GtkWidget *parent = gtk_widget_get_parent(GTK_WIDGET(och->widget));
283 if (!parent)
284 return 0;
285
286 guint num_props;
287 GParamSpec **props = gtk_container_class_list_child_properties(
288 G_OBJECT_GET_CLASS(parent), &num_props);
289
290 GValue *values = g_new0(GValue, num_props);
291 for (guint i = 0; i < num_props; i++)
292 {
293 g_value_init(&values[i], G_PARAM_SPEC_VALUE_TYPE(props[i]));
294 gtk_container_child_get_property(GTK_CONTAINER(parent),
295 GTK_WIDGET(och->widget), props[i]->name, &values[i]);
296 }
297
298 g_object_ref(G_OBJECT(och->widget));
299 gtk_container_remove(GTK_CONTAINER(parent), GTK_WIDGET(och->widget));
300
301 gtk_container_add(GTK_CONTAINER(parent), GTK_WIDGET(nch->widget));
302 for (guint i = 0; i < num_props; i++)
303 {
304 gtk_container_child_set_property(GTK_CONTAINER(parent),
305 GTK_WIDGET(nch->widget), props[i]->name, &values[i]);
306 g_value_unset(&values[i]);
307 }
308
309 g_free(props);
310 g_free(values);
311 return 0;
312 }
313
314 gint
luaH_widget_show(lua_State * L)315 luaH_widget_show(lua_State *L)
316 {
317 widget_t *w = luaH_checkwidget(L, 1);
318 gtk_widget_show(w->widget);
319 return 0;
320 }
321
322 gint
luaH_widget_hide(lua_State * L)323 luaH_widget_hide(lua_State *L)
324 {
325 widget_t *w = luaH_checkwidget(L, 1);
326 gtk_widget_hide(w->widget);
327 return 0;
328 }
329
330 gint
luaH_widget_send_key(lua_State * L)331 luaH_widget_send_key(lua_State *L)
332 {
333 widget_t *w = luaH_checkwidget(L, 1);
334 const gchar *key_name = luaL_checkstring(L, 2);
335 if (!lua_istable(L, 3))
336 {
337 lua_newtable(L);
338 lua_insert(L, 3);
339 }
340 const gboolean is_release = lua_toboolean(L, 4);
341
342 if (!g_utf8_validate(key_name, -1, NULL))
343 return luaL_error(L, "key name isn't a utf-8 string");
344
345 guint keyval;
346 if (g_utf8_strlen(key_name, -1) == 1)
347 keyval = gdk_unicode_to_keyval(g_utf8_get_char(key_name));
348 else
349 keyval = gdk_keyval_from_name(key_name);
350
351 if (!keyval || keyval == GDK_KEY_VoidSymbol)
352 return luaL_error(L, "failed to get a valid key value");
353
354 guint state = 0;
355 GString *state_string = g_string_sized_new(32);
356 lua_pushnil(L);
357 while (lua_next(L, 3)) {
358 const gchar *mod = luaL_checkstring(L, -1);
359 g_string_append_printf(state_string, "%s-", mod);
360
361 #define MODKEY(modstr, modconst) \
362 if (strcmp(modstr, mod) == 0) { \
363 state = state | GDK_##modconst##_MASK; \
364 }
365
366 MODKEY("shift", SHIFT);
367 MODKEY("control", CONTROL);
368 MODKEY("lock", LOCK);
369 MODKEY("mod1", MOD1);
370 MODKEY("mod2", MOD2);
371 MODKEY("mod3", MOD3);
372 MODKEY("mod4", MOD4);
373 MODKEY("mod5", MOD5);
374
375 #undef MODKEY
376
377 lua_pop(L, 1);
378 }
379
380 GdkKeymapKey *keys = NULL;
381 gint n_keys;
382 if (!gdk_keymap_get_entries_for_keyval(gdk_keymap_get_default(),
383 keyval, &keys, &n_keys)) {
384 g_string_free(state_string, TRUE);
385 return luaL_error(L, "cannot type '%s' on current keyboard layout",
386 key_name);
387 }
388
389 GdkEvent *event = gdk_event_new(is_release ? GDK_KEY_RELEASE : GDK_KEY_PRESS);
390 GdkEventKey *event_key = (GdkEventKey *) event;
391 event_key->window = gtk_widget_get_window(w->widget);
392 event_key->send_event = TRUE;
393 event_key->time = GDK_CURRENT_TIME;
394 event_key->state = state;
395 event_key->keyval = keyval;
396 event_key->hardware_keycode = keys[0].keycode;
397 event_key->group = keys[0].group;
398
399 GdkDevice *kbd = NULL;
400 #if GTK_CHECK_VERSION(3,20,0)
401 GdkSeat *seat = gdk_display_get_default_seat(gdk_display_get_default());
402 kbd = gdk_seat_get_keyboard(seat);
403 #else
404 GdkDeviceManager *dev_mgr = gdk_display_get_device_manager(gdk_display_get_default());
405 GList *devices = gdk_device_manager_list_devices(dev_mgr, GDK_DEVICE_TYPE_MASTER);
406 for (GList *dev = devices; dev && !kbd; dev = dev->next)
407 if (gdk_device_get_source(dev->data) == GDK_SOURCE_KEYBOARD)
408 kbd = dev->data;
409 g_list_free(devices);
410 #endif
411 if (!kbd)
412 return luaL_error(L, "failed to find a keyboard device");
413 gdk_event_set_device(event, kbd);
414
415 gboolean ret;
416 debug("sending key '%s%s' to widget %p", state_string->str, key_name, w->widget);
417 g_signal_emit_by_name(w->widget, is_release ? "key-release-event" : "key-press-event", event, &ret);
418
419 g_string_free(state_string, TRUE);
420 g_free(keys);
421 return 0;
422 }
423
424 gint
luaH_widget_set_visible(lua_State * L,widget_t * w)425 luaH_widget_set_visible(lua_State *L, widget_t *w)
426 {
427 gboolean visible = luaH_checkboolean(L, 3);
428 gtk_widget_set_visible(w->widget, visible);
429 if (visible && w->info->tok == L_TK_WINDOW)
430 gdk_window_set_events(gtk_widget_get_window(w->widget),
431 GDK_ALL_EVENTS_MASK);
432 return 0;
433 }
434
435 gint
luaH_widget_get_min_size(lua_State * L,widget_t * w)436 luaH_widget_get_min_size(lua_State *L, widget_t *w)
437 {
438 gint width, height;
439 gtk_widget_get_size_request(w->widget, &width, &height);
440
441 lua_newtable(L);
442
443 lua_pushliteral(L, "width");
444 lua_pushinteger(L, width);
445 lua_rawset(L, -3);
446
447 lua_pushliteral(L, "height");
448 lua_pushinteger(L, height);
449 lua_rawset(L, -3);
450
451 return 1;
452 }
453
454 gint
luaH_widget_set_min_size(lua_State * L,widget_t * w)455 luaH_widget_set_min_size(lua_State *L, widget_t *w)
456 {
457 luaH_checktable(L, 3);
458
459 gint width, height;
460 gtk_widget_get_size_request(w->widget, &width, &height);
461
462 gint top = lua_gettop(L);
463 if (luaH_rawfield(L, 3, "w"))
464 width = lua_tonumber(L, -1);
465 if (luaH_rawfield(L, 3, "h"))
466 height = lua_tonumber(L, -1);
467 lua_settop(L, top);
468
469 gtk_widget_set_size_request(w->widget, width, height);
470 return 1;
471 }
472
473 gint
luaH_widget_get_align(lua_State * L,widget_t * w)474 luaH_widget_get_align(lua_State *L, widget_t *w)
475 {
476 GtkAlign halign = gtk_widget_get_halign(GTK_WIDGET(w->widget)),
477 valign = gtk_widget_get_valign_with_baseline(GTK_WIDGET(w->widget));
478 lua_createtable(L, 0, 2);
479 /* set align.h */
480 lua_pushliteral(L, "h");
481 lua_pushnumber(L, halign);
482 lua_rawset(L, -3);
483 /* set align.v */
484 lua_pushliteral(L, "v");
485 lua_pushnumber(L, valign);
486 lua_rawset(L, -3);
487 return 1;
488 }
489
490 gint
luaH_widget_set_align(lua_State * L,widget_t * w)491 luaH_widget_set_align(lua_State *L, widget_t *w)
492 {
493 luaH_checktable(L, 3);
494 GtkAlign halign = gtk_widget_get_halign(GTK_WIDGET(w->widget)),
495 valign = gtk_widget_get_valign_with_baseline(GTK_WIDGET(w->widget));
496 if (luaH_rawfield(L, 3, "h"))
497 switch (l_tokenize(lua_tostring(L, -1))) {
498 case L_TK_FILL: halign = GTK_ALIGN_FILL; break;
499 case L_TK_START: halign = GTK_ALIGN_START; break;
500 case L_TK_END: halign = GTK_ALIGN_END; break;
501 case L_TK_CENTER: halign = GTK_ALIGN_CENTER; break;
502 case L_TK_BASELINE: halign = GTK_ALIGN_BASELINE; break;
503 default:
504 return luaL_error(L, "Bad alignment value (expected fill, start, end, center, or baseline)");
505 }
506 if (luaH_rawfield(L, 3, "v"))
507 switch (l_tokenize(lua_tostring(L, -1))) {
508 case L_TK_FILL: valign = GTK_ALIGN_FILL; break;
509 case L_TK_START: valign = GTK_ALIGN_START; break;
510 case L_TK_END: valign = GTK_ALIGN_END; break;
511 case L_TK_CENTER: valign = GTK_ALIGN_CENTER; break;
512 case L_TK_BASELINE: valign = GTK_ALIGN_BASELINE; break;
513 default:
514 return luaL_error(L, "Bad alignment value (expected fill, start, end, center, or baseline)");
515 }
516 gtk_widget_set_halign(GTK_WIDGET(w->widget), halign);
517 gtk_widget_set_valign(GTK_WIDGET(w->widget), valign);
518 return 0;
519 }
520
521 gint
luaH_widget_set_tooltip(lua_State * L,widget_t * w)522 luaH_widget_set_tooltip(lua_State *L, widget_t *w)
523 {
524 gtk_widget_set_tooltip_markup(w->widget, lua_tostring(L, 3) ?: "");
525 return 0;
526 }
527
528 gint
luaH_widget_get_tooltip(lua_State * L,widget_t * w)529 luaH_widget_get_tooltip(lua_State *L, widget_t *w)
530 {
531 lua_pushstring(L, gtk_widget_get_tooltip_markup(w->widget));
532 return 1;
533 }
534
535 gint
luaH_widget_get_parent(lua_State * L,widget_t * w)536 luaH_widget_get_parent(lua_State *L, widget_t *w)
537 {
538 GtkWidget *widget = gtk_widget_get_parent(GTK_WIDGET(w->widget));
539
540 if (!widget)
541 return 0;
542
543 widget_t *parent = GOBJECT_TO_LUAKIT_WIDGET(widget);
544 luaH_object_push(L, parent->ref);
545 return 1;
546 }
547
548 gint
luaH_widget_get_focused(lua_State * L,widget_t * w)549 luaH_widget_get_focused(lua_State *L, widget_t *w)
550 {
551 gboolean focused = w->info->tok == L_TK_WINDOW ?
552 gtk_window_has_toplevel_focus(GTK_WINDOW(w->widget)) :
553 gtk_widget_is_focus(w->widget);
554 lua_pushboolean(L, focused);
555 return 1;
556 }
557
558 gint
luaH_widget_get_visible(lua_State * L,widget_t * w)559 luaH_widget_get_visible(lua_State *L, widget_t *w)
560 {
561 lua_pushboolean(L, gtk_widget_get_visible(w->widget));
562 return 1;
563 }
564
565 gint
luaH_widget_get_width(lua_State * L,widget_t * w)566 luaH_widget_get_width(lua_State *L, widget_t *w)
567 {
568 lua_pushnumber(L, gtk_widget_get_allocated_width(w->widget));
569 return 1;
570 }
571
572 gint
luaH_widget_get_height(lua_State * L,widget_t * w)573 luaH_widget_get_height(lua_State *L, widget_t *w)
574 {
575 lua_pushnumber(L, gtk_widget_get_allocated_height(w->widget));
576 return 1;
577 }
578
579 gint
luaH_widget_focus(lua_State * L)580 luaH_widget_focus(lua_State *L)
581 {
582 widget_t *w = luaH_checkwidget(L, 1);
583
584 switch (w->info->tok) {
585 case L_TK_WINDOW:
586 /* win:focus() unfocuses anything within that window */
587 gtk_window_set_focus(GTK_WINDOW(w->widget), NULL);
588 break;
589 case L_TK_ENTRY:
590 gtk_entry_grab_focus_without_selecting(GTK_ENTRY(w->widget));
591 break;
592 default:
593 gtk_widget_grab_focus(w->widget);
594 break;
595 }
596
597 return 0;
598 }
599
600 gint
luaH_widget_destroy(lua_State * L)601 luaH_widget_destroy(lua_State *L)
602 {
603 widget_t *w = luaH_checkwidget(L, 1);
604 gtk_widget_destroy(GTK_WIDGET(w->widget));
605 return 0;
606 }
607
608 // vim: ft=c:et:sw=4:ts=8:sts=4:tw=80
609