add existing code (build system, libs, initial mizucp code)
[mizunara.git] / ui / gtk / menu.c
1 /*
2  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3  *
4  * Copyright 2017 Olaf Wintermann. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions are met:
8  *
9  *   1. Redistributions of source code must retain the above copyright
10  *      notice, this list of conditions and the following disclaimer.
11  *
12  *   2. Redistributions in binary form must reproduce the above copyright
13  *      notice, this list of conditions and the following disclaimer in the
14  *      documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
20  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
21  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
22  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
23  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
24  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
25  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include <stdio.h>
30 #include <stdlib.h>
31 #include <inttypes.h>
32 #include <stdarg.h>
33
34 #include "menu.h"
35 #include "toolkit.h"
36 #include "../common/context.h"
37 #include "../ui/properties.h"
38 #include "../ui/window.h"
39 #include "container.h"
40
41 static UcxList *menus;
42 static UcxList *current;
43
44 void ui_menu(char *label) {
45     // free current menu hierarchy
46     ucx_list_free(current);
47     
48     // create menu
49     UiMenu *menu = malloc(sizeof(UiMenu));
50     menu->item.add_to = (ui_menu_add_f)add_menu_widget;
51     
52     menu->label  = label;
53     menu->items  = NULL;
54     menu->parent = NULL;    
55     
56     current = ucx_list_prepend(NULL, menu);
57     menus = ucx_list_append(menus, menu);
58     
59 }
60
61 void ui_submenu(char *label) {
62     UiMenu *menu = malloc(sizeof(UiMenu));
63     menu->item.add_to = (ui_menu_add_f)add_menu_widget;
64     
65     menu->label  = label;
66     menu->items  = NULL;
67     menu->parent = NULL;
68     
69     // add submenu to current menu
70     UiMenu *cm = current->data;
71     cm->items = ucx_list_append(cm->items, menu);
72     
73     // set the submenu to current menu
74     current = ucx_list_prepend(current, menu);
75 }
76
77 void ui_submenu_end() {
78     if(ucx_list_size(current) < 2) {
79         return;
80     }
81     current = ucx_list_remove(current, current);
82     //UcxList *c = current;
83 }
84
85 void ui_menuitem(char *label, ui_callback f, void *userdata) {
86     ui_menuitem_gr(label, f, userdata, -1);
87 }
88
89 void ui_menuitem_st(char *stockid, ui_callback f, void *userdata) {
90     ui_menuitem_stgr(stockid, f, userdata, -1);
91 }
92
93 void ui_menuitem_gr(char *label, ui_callback f, void *userdata, ...) {
94     if(!current) {
95         return;
96     }
97     
98     UiMenuItem *item = malloc(sizeof(UiMenuItem));
99     item->item.add_to = (ui_menu_add_f)add_menuitem_widget;
100     
101     item->label = label;
102     item->userdata = userdata;
103     item->callback = f;
104     item->groups = NULL;
105     
106     // add groups
107     va_list ap;
108     va_start(ap, userdata);
109     int group;
110     while((group = va_arg(ap, int)) != -1) {
111         item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
112     }
113     va_end(ap);
114     
115     UiMenu *cm = current->data;
116     cm->items = ucx_list_append(cm->items, item);
117 }
118
119 void ui_menuitem_stgr(char *stockid, ui_callback f, void *userdata, ...) {
120     if(!current) {
121         return;
122     }
123     
124     UiStMenuItem *item = malloc(sizeof(UiStMenuItem));
125     item->item.add_to = (ui_menu_add_f)add_menuitem_st_widget;
126     
127     item->stockid = stockid;
128     item->userdata = userdata;
129     item->callback = f;
130     item->groups = NULL;
131     
132     // add groups
133     va_list ap;
134     va_start(ap, userdata);
135     int group;
136     while((group = va_arg(ap, int)) != -1) {
137         item->groups = ucx_list_append(item->groups, (void*)(intptr_t)group);
138     }
139     va_end(ap);
140     
141     UiMenu *cm = current->data;
142     cm->items = ucx_list_append(cm->items, item);
143 }
144
145 void ui_menuseparator() {
146     if(!current) {
147         return;
148     }
149     
150     UiMenuItemI  *item = malloc(sizeof(UiMenuItemI));
151     item->add_to = (ui_menu_add_f)add_menuseparator_widget;
152     
153     UiMenu *cm = current->data;
154     cm->items = ucx_list_append(cm->items, item);
155 }
156
157 void ui_checkitem(char *label, ui_callback f, void *userdata) {
158     if(!current) {
159         return;
160     }
161     
162     UiCheckItem *item = malloc(sizeof(UiCheckItem));
163     item->item.add_to = (ui_menu_add_f)add_checkitem_widget;
164     item->label = label;
165     item->callback = f;
166     item->userdata = userdata;
167     
168     UiMenu *cm = current->data;
169     cm->items = ucx_list_append(cm->items, item);
170 }
171
172 void ui_checkitem_nv(char *label, char *vname) {
173     if(!current) {
174         return;
175     }
176     
177     UiCheckItemNV *item = malloc(sizeof(UiCheckItemNV));
178     item->item.add_to = (ui_menu_add_f)add_checkitemnv_widget;
179     item->varname = vname;
180     item->label = label;
181     
182     UiMenu *cm = current->data;
183     cm->items = ucx_list_append(cm->items, item);
184 }
185
186 void ui_menuitem_list(UiList *items, ui_callback f, void *userdata) {
187     if(!current) {
188         return;
189     }
190     
191     UiMenuItemList *item = malloc(sizeof(UiMenuItemList));
192     item->item.add_to = (ui_menu_add_f)add_menuitem_list_widget;
193     item->callback = f;
194     item->userdata = userdata;
195     item->list = items;
196     
197     UiMenu *cm = current->data;
198     cm->items = ucx_list_append(cm->items, item);
199 }
200
201 // private menu functions
202 GtkWidget *ui_create_menubar(UiObject *obj) {
203     if(menus == NULL) {
204         return NULL;
205     }
206     
207     GtkWidget *mb = gtk_menu_bar_new();
208     
209     UcxList *ls = menus;
210     while(ls) {
211         UiMenu *menu = ls->data;
212         menu->item.add_to(mb, 0, &menu->item, obj);
213         
214         ls = ls->next;
215     }
216     
217     return mb;
218 }
219
220 void add_menu_widget(GtkWidget *parent, int i, UiMenuItemI *item, UiObject *obj) {
221     UiMenu *menu = (UiMenu*)item;
222     
223     GtkWidget *menu_widget = gtk_menu_new();
224     GtkWidget *menu_item = gtk_menu_item_new_with_mnemonic(menu->label);
225     gtk_menu_item_set_submenu(GTK_MENU_ITEM(menu_item), menu_widget);
226     
227     UcxList *ls = menu->items;
228     int index = 0;
229     while(ls) {
230         UiMenuItemI *i = ls->data;
231         i->add_to(menu_widget, index, i, obj);
232         
233         ls = ls->next;
234         index++;
235     }
236     
237     gtk_menu_shell_append(GTK_MENU_SHELL(parent), menu_item);
238 }
239
240 void add_menuitem_widget(GtkWidget *parent, int index, UiMenuItemI *item, UiObject *obj) {
241     UiMenuItem *i = (UiMenuItem*)item;
242     
243     //GtkWidget *widget = gtk_menu_item_new_with_label(i->title);
244     GtkWidget *widget = gtk_menu_item_new_with_mnemonic(i->label);
245     
246     if(i->callback != NULL) {
247         UiEventData *event = malloc(sizeof(UiEventData));
248         event->obj = obj;
249         event->userdata = i->userdata;
250         event->callback = i->callback;
251         event->value = 0;
252
253         g_signal_connect(
254                 widget,
255                 "activate",
256                 G_CALLBACK(ui_menu_event_wrapper),
257                 event);
258         g_signal_connect(
259                 widget,
260                 "destroy",
261                 G_CALLBACK(ui_destroy_userdata),
262                 event);
263     }
264     
265     gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
266     
267     if(i->groups) {
268         uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
269     }
270 }
271
272 void add_menuitem_st_widget(
273         GtkWidget *parent,
274         int index,
275         UiMenuItemI *item,
276         UiObject *obj)
277 {
278     UiStMenuItem *i = (UiStMenuItem*)item;
279     
280     GtkWidget *widget = gtk_image_menu_item_new_from_stock(i->stockid, obj->ctx->accel_group);
281     
282     if(i->callback != NULL) {
283         UiEventData *event = malloc(sizeof(UiEventData));
284         event->obj = obj;
285         event->userdata = i->userdata;
286         event->callback = i->callback;
287         event->value = 0;
288
289         g_signal_connect(
290                 widget,
291                 "activate",
292                 G_CALLBACK(ui_menu_event_wrapper),
293                 event);
294         g_signal_connect(
295                 widget,
296                 "destroy",
297                 G_CALLBACK(ui_destroy_userdata),
298                 event);
299     }
300     
301     gtk_menu_shell_append(GTK_MENU_SHELL(parent), widget);
302     
303     if(i->groups) {
304         uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, i->groups);
305     }
306 }
307
308 void add_menuseparator_widget(
309         GtkWidget *parent,
310         int index,
311         UiMenuItemI *item,
312         UiObject *obj)
313 {
314     gtk_menu_shell_append(
315             GTK_MENU_SHELL(parent),
316             gtk_separator_menu_item_new());
317 }
318
319 void add_checkitem_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
320     UiCheckItem *ci = (UiCheckItem*)item;
321     GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
322     gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
323     
324     if(ci->callback) {
325         UiEventData *event = malloc(sizeof(UiEventData));
326         event->obj = obj;
327         event->userdata = ci->userdata;
328         event->callback = ci->callback;
329         event->value = 0;
330
331         g_signal_connect(
332                 widget,
333                 "toggled",
334                 G_CALLBACK(ui_menu_event_toggled),
335                 event);
336         g_signal_connect(
337                 widget,
338                 "destroy",
339                 G_CALLBACK(ui_destroy_userdata),
340                 event);
341     }
342 }
343
344 void add_checkitemnv_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
345     UiCheckItemNV *ci = (UiCheckItemNV*)item;
346     GtkWidget *widget = gtk_check_menu_item_new_with_mnemonic(ci->label);
347     gtk_menu_shell_append(GTK_MENU_SHELL(p), widget);
348     
349     UiVar *var = uic_create_var(obj->ctx, ci->varname, UI_VAR_INTEGER);
350     if(var) {
351         UiInteger *value = var->value;
352         value->obj = widget;
353         value->get = ui_checkitem_get;
354         value->set = ui_checkitem_set;
355         value = 0;
356     } else {
357         // TODO: error
358     }
359 }
360
361 void add_menuitem_list_widget(GtkWidget *p, int index, UiMenuItemI *item, UiObject *obj) {
362     UiMenuItemList *il = (UiMenuItemList*)item;
363     UcxMempool *mp = obj->ctx->mempool;
364     
365     UiActiveMenuItemList *ls = ucx_mempool_malloc(
366             mp,
367             sizeof(UiActiveMenuItemList));
368     
369     ls->object = obj;
370     ls->menu = GTK_MENU_SHELL(p);
371     ls->index = index;
372     ls->oldcount = 0;
373     ls->list = il->list;
374     ls->callback = il->callback;
375     ls->userdata = il->userdata;
376     
377     ls->list->observers = ui_add_observer(
378             ls->list->observers,
379             (ui_callback)ui_update_menuitem_list,
380             ls);
381     
382     ui_update_menuitem_list(NULL, ls);
383 }
384
385
386 void ui_update_menuitem_list(UiEvent *event, UiActiveMenuItemList *list) {    
387     // remove old items
388     if(list->oldcount > 0) {
389         int i = 0;
390         GList *mi = gtk_container_get_children(GTK_CONTAINER(list->menu));
391         while(mi) {
392             if(i >= list->index && i < list->index + list->oldcount) {
393                 //gtk_container_remove(GTK_CONTAINER(list->menu), mi->data);
394                 gtk_widget_destroy(mi->data);
395             }
396             mi = mi->next;
397             i++;
398         }
399     }
400     
401     char *str = ui_list_first(list->list);
402     if(str) {
403         GtkWidget *widget = gtk_separator_menu_item_new();
404         gtk_menu_shell_insert(list->menu, widget, list->index);
405         gtk_widget_show(widget);
406     }
407     int i = 1;
408     while(str) {
409         GtkWidget *widget = gtk_menu_item_new_with_label(str);
410         gtk_menu_shell_insert(list->menu, widget, list->index + i);
411         gtk_widget_show(widget);
412         
413         if(list->callback) {
414             // TODO: use mempool
415             UiEventData *event = malloc(sizeof(UiEventData));
416             event->obj = list->object;
417             event->userdata = list->userdata;
418             event->callback = list->callback;
419             event->value = i - 1;
420
421             g_signal_connect(
422                 widget,
423                 "activate",
424                 G_CALLBACK(ui_menu_event_wrapper),
425                 event);
426             g_signal_connect(
427                 widget,
428                 "destroy",
429                 G_CALLBACK(ui_destroy_userdata),
430                 event);
431         }
432         
433         str = ui_list_next(list->list);
434         i++;
435     }
436     
437     list->oldcount = i;
438 }
439
440 void ui_menu_event_wrapper(GtkMenuItem *item, UiEventData *event) {
441     UiEvent evt;
442     evt.obj = event->obj;
443     evt.window = event->obj->window;
444     evt.document = event->obj->ctx->document;
445     evt.eventdata = NULL;
446     evt.intval = event->value;
447     event->callback(&evt, event->userdata);    
448 }
449
450 void ui_menu_event_toggled(GtkCheckMenuItem *ci, UiEventData *event) {
451     UiEvent evt;
452     evt.obj = event->obj;
453     evt.window = event->obj->window;
454     evt.document = event->obj->ctx->document;
455     evt.eventdata = NULL;
456     evt.intval = gtk_check_menu_item_get_active(ci);
457     event->callback(&evt, event->userdata);    
458 }
459
460 int64_t ui_checkitem_get(UiInteger *i) {
461     int state = gtk_check_menu_item_get_active(i->obj);
462     i->value = state;
463     return state;
464 }
465
466 void ui_checkitem_set(UiInteger *i, int64_t value) {
467     i->value = value;
468     gtk_check_menu_item_set_active(i->obj, value);
469 }
470
471
472 /*
473  * widget menu functions
474  */
475
476 static gboolean ui_button_press_event(GtkWidget *widget, GdkEvent *event, GtkMenu *menu) {
477     if(event->type == GDK_BUTTON_PRESS) {
478         GdkEventButton *e = (GdkEventButton*)event;
479         if(e->button == 3) {
480             gtk_widget_show_all(GTK_WIDGET(menu));
481             ui_contextmenu_popup(menu);
482             return TRUE;
483         }
484     }
485     return FALSE;
486 }
487
488 UIMENU ui_contextmenu(UiObject *obj) {
489     UiContainer *ct = uic_get_current_container(obj);
490     return ui_contextmenu_w(obj, ct->current);
491 }
492
493 UIMENU ui_contextmenu_w(UiObject *obj, UIWIDGET widget) {
494     UiContainer *ct = uic_get_current_container(obj);
495     
496     GtkMenu *menu = GTK_MENU(gtk_menu_new());
497     g_signal_connect(widget, "button-press-event", (GCallback) ui_button_press_event, menu);
498     
499     ct->menu = menu;
500     return menu;
501 }
502
503 void ui_contextmenu_popup(UIMENU menu) {
504 #if GTK_MAJOR_VERSION >= 3 && GTK_MINOR_VERSION >= 16
505     gtk_menu_popup_at_pointer(menu, NULL);
506 #else
507     gtk_menu_popup(menu, NULL, NULL, 0, 0, 0, gtk_get_current_event_time());
508 #endif
509 }
510
511 void ui_widget_menuitem(UiObject *obj, char *label, ui_callback f, void *userdata) {
512     ui_widget_menuitem_gr(obj, label, f, userdata, -1);
513 }
514
515 void ui_widget_menuitem_gr(UiObject *obj, char *label, ui_callback f, void *userdata, ...) {
516     UiContainer *ct = uic_get_current_container(obj);
517     if(!ct->menu) {
518         return;
519     }
520     
521     // add groups
522     UcxList *groups = NULL;
523     va_list ap;
524     va_start(ap, userdata);
525     int group;
526     while((group = va_arg(ap, int)) != -1) {
527         ucx_list_append(groups, (void*)(intptr_t)group);
528     }
529     va_end(ap);
530     
531     // create menuitem
532     GtkWidget *widget = gtk_menu_item_new_with_mnemonic(label);
533     gtk_widget_show(widget);
534     
535     if(f) {
536         UiEventData *event = malloc(sizeof(UiEventData));
537         event->obj = obj;
538         event->userdata = userdata;
539         event->callback = f;
540         event->value = 0;
541
542         g_signal_connect(
543                 widget,
544                 "activate",
545                 G_CALLBACK(ui_menu_event_wrapper),
546                 event);
547         g_signal_connect(
548                 widget,
549                 "destroy",
550                 G_CALLBACK(ui_destroy_userdata),
551                 event);
552     }
553     
554     gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
555     
556     if(groups) {
557         uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
558     }
559 }
560
561 void ui_widget_menuitem_st(UiObject *obj, char *stockid, ui_callback f, void *userdata) {
562     ui_widget_menuitem_stgr(obj, stockid, f, userdata, -1);
563 }
564
565 void ui_widget_menuitem_stgr(UiObject *obj, char *stockid, ui_callback f, void *userdata, ...) {
566     UiContainer *ct = uic_get_current_container(obj);
567     if(!ct->menu) {
568         return;
569     }
570     
571     // add groups
572     UcxList *groups = NULL;
573     va_list ap;
574     va_start(ap, userdata);
575     int group;
576     while((group = va_arg(ap, int)) != -1) {
577         ucx_list_append(groups, (void*)(intptr_t)group);
578     }
579     va_end(ap);
580     
581     // create menuitem
582     GtkWidget *widget = gtk_image_menu_item_new_from_stock(stockid, obj->ctx->accel_group);
583     gtk_widget_show(widget);
584     
585     if(f) {
586         UiEventData *event = malloc(sizeof(UiEventData));
587         event->obj = obj;
588         event->userdata = userdata;
589         event->callback = f;
590         event->value = 0;
591
592         g_signal_connect(
593                 widget,
594                 "activate",
595                 G_CALLBACK(ui_menu_event_wrapper),
596                 event);
597         g_signal_connect(
598                 widget,
599                 "destroy",
600                 G_CALLBACK(ui_destroy_userdata),
601                 event);
602     }
603     
604     gtk_menu_shell_append(GTK_MENU_SHELL(ct->menu), widget);
605     
606     if(groups) {
607         uic_add_group_widget(obj->ctx, widget, (ui_enablefunc)ui_set_enabled, groups);
608     }
609 }