#include "osmenu_gtk.inl"
#include "osmenuitem_gtk.inl"
#include "oscontrol_gtk.inl"
#include "osgui_gtk.inl"
#include "../osmenuitem.h"
#include "../osgui.inl"
#include <draw2d/image.h>
#include <core/event.h>
#include <core/heap.h>
#include <sewer/cassert.h>
#if !defined(__GTK3__)
#error This file is only for GTK Toolkit
#endif
static const guint i_VIRTUAL_KEY[] =
{
UINT32_MAX,
GDK_KEY_a,
GDK_KEY_s,
GDK_KEY_d,
GDK_KEY_f,
GDK_KEY_h,
GDK_KEY_g,
GDK_KEY_z,
GDK_KEY_x,
GDK_KEY_c,
GDK_KEY_v,
GDK_KEY_backslash,
GDK_KEY_b,
GDK_KEY_q,
GDK_KEY_w,
GDK_KEY_e,
GDK_KEY_r,
GDK_KEY_y,
GDK_KEY_t,
GDK_KEY_1,
GDK_KEY_2,
GDK_KEY_3,
GDK_KEY_4,
GDK_KEY_6,
GDK_KEY_5,
GDK_KEY_9,
GDK_KEY_7,
GDK_KEY_8,
GDK_KEY_0,
GDK_KEY_bracketright,
GDK_KEY_o,
GDK_KEY_u,
GDK_KEY_bracketleft,
GDK_KEY_i,
GDK_KEY_p,
GDK_KEY_Return,
GDK_KEY_l,
GDK_KEY_j,
GDK_KEY_semicolon,
GDK_KEY_k,
GDK_KEY_apostrophe,
GDK_KEY_comma,
GDK_KEY_minus,
GDK_KEY_n,
GDK_KEY_m,
GDK_KEY_period,
GDK_KEY_Tab,
GDK_KEY_space,
GDK_KEY_less,
GDK_KEY_BackSpace,
GDK_KEY_Escape,
GDK_KEY_F17,
GDK_KEY_KP_Decimal,
GDK_KEY_KP_Multiply,
GDK_KEY_KP_Add,
GDK_KEY_Num_Lock,
GDK_KEY_KP_Divide,
GDK_KEY_KP_Enter,
GDK_KEY_KP_Subtract,
GDK_KEY_F18,
GDK_KEY_F19,
GDK_KEY_KP_Equal,
GDK_KEY_KP_0,
GDK_KEY_KP_1,
GDK_KEY_KP_2,
GDK_KEY_KP_3,
GDK_KEY_KP_4,
GDK_KEY_KP_5,
GDK_KEY_KP_6,
GDK_KEY_KP_7,
GDK_KEY_KP_8,
GDK_KEY_KP_9,
GDK_KEY_F5,
GDK_KEY_F6,
GDK_KEY_F7,
GDK_KEY_F3,
GDK_KEY_F8,
GDK_KEY_F9,
GDK_KEY_F11,
GDK_KEY_F13,
GDK_KEY_F16,
GDK_KEY_F14,
GDK_KEY_F10,
GDK_KEY_F12,
GDK_KEY_F15,
GDK_KEY_Page_Up,
GDK_KEY_Begin,
GDK_KEY_KP_Delete,
GDK_KEY_F4,
GDK_KEY_Page_Down,
GDK_KEY_F2,
GDK_KEY_End,
GDK_KEY_F1,
GDK_KEY_Left,
GDK_KEY_Right,
GDK_KEY_Down,
GDK_KEY_Up
};
struct _osmenuitem_t
{
GtkWidget *widget;
GtkWidget *check;
GtkWidget *icon;
GtkWidget *label;
vkey_t key;
uint32_t modifiers;
GtkAccelGroup *accel;
OSMenu *menu;
OSMenu *sub_menu;
Listener *OnClick;
bool_t visible;
bool_t show_icon;
bool_t show_check;
bool_t launch_event;
#if defined(__ASSERTS__)
bool_t is_alive;
#endif
};
#if defined(__ASSERTS__)
static void i_OnDestroy(GtkWidget *widget, OSMenuItem *item)
{
cassert_no_null(item);
cassert(item->is_alive == TRUE);
unref(widget);
item->is_alive = FALSE;
}
#endif
static void i_OnClick(GtkMenuItem *widget, OSMenuItem *item)
{
cassert_no_null(item);
unref(widget);
if (item->OnClick != NULL && item->launch_event == TRUE)
{
EvMenu params;
params.index = UINT32_MAX;
params.state = ekGUI_ON;
params.text = NULL;
listener_event(item->OnClick, ekGUI_EVENT_MENU, item, ¶ms, NULL, OSMenuItem, EvMenu, void);
}
}
OSMenuItem *osmenuitem_create(const uint32_t flags)
{
OSMenuItem *item = heap_new0(OSMenuItem);
if ((menu_flag_t)flags == ekMENU_ITEM)
{
GtkWidget *box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 4);
item->widget = gtk_menu_item_new();
item->label = gtk_accel_label_new("");
item->check = gtk_label_new("✓");
item->icon = gtk_image_new_from_icon_name("document-save", GTK_ICON_SIZE_MENU);
item->key = ENUM_MAX(vkey_t);
item->visible = TRUE;
item->launch_event = TRUE;
g_object_ref_sink(item->check);
g_object_ref_sink(item->icon);
gtk_label_set_use_underline(GTK_LABEL(item->label), TRUE);
gtk_accel_label_set_accel_widget(GTK_ACCEL_LABEL(item->label), item->widget);
#if GTK_CHECK_VERSION(3, 16, 0)
gtk_label_set_xalign(GTK_LABEL(item->label), 0.0);
#endif
gtk_box_pack_end(GTK_BOX(box), item->label, TRUE, TRUE, 0);
gtk_container_add(GTK_CONTAINER(item->widget), box);
g_signal_connect(G_OBJECT(item->widget), "activate", G_CALLBACK(i_OnClick), (gpointer)item);
}
else
{
cassert((menu_flag_t)flags == ekMENU_SEPARATOR);
item->widget = gtk_separator_menu_item_new();
item->key = ENUM_MAX(vkey_t);
item->visible = TRUE;
}
g_object_ref_sink(item->widget);
return item;
}
static void i_destroy_widget(OSMenuItem *item, GtkWidget *widget)
{
cassert_no_null(item);
cassert_no_null(widget);
#if defined(__ASSERTS__)
item->is_alive = TRUE;
g_signal_connect(widget, "destroy", G_CALLBACK(i_OnDestroy), (gpointer)item);
#else
unref(item);
#endif
g_object_unref(widget);
cassert(item->is_alive == FALSE);
}
void osmenuitem_destroy(OSMenuItem **item)
{
GtkWidget *box = NULL;
cassert_no_null(item);
cassert_no_null(*item);
cassert((*item)->menu == NULL);
cassert((*item)->sub_menu == NULL);
cassert(gtk_menu_item_get_submenu(GTK_MENU_ITEM((*item)->widget)) == NULL);
cassert((*item)->accel == NULL);
listener_destroy(&(*item)->OnClick);
box = gtk_bin_get_child(GTK_BIN((*item)->widget));
if (box != NULL)
{
if (_oscontrol_find_child(box, (*item)->check) != UINT32_MAX)
gtk_container_remove(GTK_CONTAINER(box), (*item)->check);
if (_oscontrol_find_child(box, (*item)->icon) != UINT32_MAX)
gtk_container_remove(GTK_CONTAINER(box), (*item)->icon);
i_destroy_widget(*item, (*item)->check);
i_destroy_widget(*item, (*item)->icon);
}
else
{
cassert((*item)->check == NULL);
cassert((*item)->icon == NULL);
}
i_destroy_widget(*item, (*item)->widget);
heap_delete(item, OSMenuItem);
}
void osmenuitem_OnClick(OSMenuItem *item, Listener *listener)
{
cassert_no_null(item);
cassert(item->label != NULL);
listener_update(&item->OnClick, listener);
}
void osmenuitem_enabled(OSMenuItem *item, const bool_t enabled)
{
cassert_no_null(item);
gtk_widget_set_sensitive(item->widget, (gboolean)enabled);
}
void osmenuitem_visible(OSMenuItem *item, const bool_t visible)
{
cassert_no_null(item);
if (item->visible != visible)
{
item->visible = visible;
if (item->menu != NULL)
_osmenu_widget_recompute(item->menu);
}
}
void osmenuitem_text(OSMenuItem *item, const char_t *text)
{
cassert_no_null(item);
gtk_label_set_text(GTK_LABEL(item->label), cast_const(text, gchar));
if (item->menu != NULL)
_osmenu_widget_recompute(item->menu);
}
static void i_update_icons(OSMenuItem *item)
{
GtkWidget *box = NULL;
cassert_no_null(item);
box = gtk_bin_get_child(GTK_BIN(item->widget));
if (_oscontrol_find_child(box, item->icon) != UINT32_MAX)
{
gtk_container_remove(GTK_CONTAINER(box), item->icon);
cassert(_oscontrol_find_child(box, item->icon) == UINT32_MAX);
}
if (_oscontrol_find_child(box, item->check) != UINT32_MAX)
{
gtk_container_remove(GTK_CONTAINER(box), item->check);
cassert(_oscontrol_find_child(box, item->check) == UINT32_MAX);
}
if (item->show_icon == TRUE)
gtk_box_pack_start(GTK_BOX(box), item->icon, FALSE, FALSE, 0);
if (item->show_check == TRUE)
gtk_box_pack_start(GTK_BOX(box), item->check, FALSE, FALSE, 0);
if (item->menu != NULL)
_osmenu_widget_recompute(item->menu);
}
void osmenuitem_image(OSMenuItem *item, const Image *image)
{
cassert_no_null(item);
if (image != NULL)
{
const char_t *icon_name = _osgui_register_icon(image);
gtk_image_set_from_icon_name(GTK_IMAGE(item->icon), icon_name, GTK_ICON_SIZE_MENU);
if (item->show_icon == FALSE)
{
item->show_icon = TRUE;
i_update_icons(item);
}
}
else
{
if (item->show_icon == TRUE)
{
item->show_icon = FALSE;
i_update_icons(item);
}
}
}
static ___INLINE GdkModifierType i_kmod(const uint32_t modifiers)
{
GdkModifierType mod = 0;
if (modifiers & ekMKEY_CONTROL)
mod |= GDK_CONTROL_MASK;
if (modifiers & ekMKEY_ALT)
mod |= GDK_MOD1_MASK;
if (modifiers & ekMKEY_SHIFT)
mod |= GDK_SHIFT_MASK;
return mod;
}
void osmenuitem_key(OSMenuItem *item, const vkey_t key, const uint32_t modifiers)
{
cassert_no_null(item);
if (item->accel != NULL)
{
if (item->key != ENUM_MAX(vkey_t))
{
gboolean ok = gtk_widget_remove_accelerator(item->widget, item->accel, i_VIRTUAL_KEY[item->key], i_kmod(item->modifiers));
cassert_unref(ok == TRUE, ok);
}
if (key != ENUM_MAX(vkey_t))
gtk_widget_add_accelerator(item->widget, "activate", item->accel, i_VIRTUAL_KEY[key], i_kmod(modifiers), GTK_ACCEL_VISIBLE);
}
item->key = key;
item->modifiers = modifiers;
}
void osmenuitem_state(OSMenuItem *item, const gui_state_t state)
{
cassert_no_null(item);
switch (state)
{
case ekGUI_OFF:
if (item->show_check == TRUE)
{
item->show_check = FALSE;
i_update_icons(item);
}
break;
case ekGUI_ON:
case ekGUI_MIXED:
if (item->show_check == FALSE)
{
item->show_check = TRUE;
i_update_icons(item);
}
break;
cassert_default();
}
}
void osmenuitem_submenu(OSMenuItem *item, OSMenu *menu)
{
cassert_no_null(item);
cassert(item->sub_menu == NULL);
item->sub_menu = menu;
_osmenu_attach_to_item(menu, item);
}
void osmenuitem_unset_submenu(OSMenuItem *item, OSMenu *menu)
{
cassert_no_null(item);
cassert_unref(item->sub_menu == menu, menu);
_osmenu_detach_from_item(item->sub_menu, item);
item->sub_menu = NULL;
}
GtkWidget *_osmenuitem_widget(OSMenuItem *item)
{
cassert_no_null(item);
return item->widget;
}
void _osmenuitem_append_to_menu(OSMenuItem *item, OSMenu *menu, GtkWidget *widget)
{
cassert_no_null(item);
cassert(item->menu == NULL);
item->menu = menu;
if (item->visible == TRUE && widget != NULL)
{
cassert(GTK_IS_MENU(widget));
gtk_menu_shell_append(GTK_MENU_SHELL(widget), item->widget);
}
}
static void i_current_menubar(GtkWidget *widget, gpointer current)
{
cassert(GTK_IS_MENU_BAR(widget));
if (_oscontrol_num_children(widget) == 0)
{
if (*dcast(current, GtkWidget) == NULL)
*dcast(current, GtkWidget) = widget;
}
else
{
*dcast(current, GtkWidget) = widget;
}
}
static GtkWidget *i_get_current_menubar(GtkWidget *widget)
{
GtkWidget *last = NULL;
cassert(GTK_IS_BOX(widget));
gtk_container_foreach(GTK_CONTAINER(widget), i_current_menubar, (gpointer)&last);
return last;
}
static void i_next_menubar(GtkWidget *widget, gpointer data)
{
cassert(GTK_IS_MENU_BAR(widget));
if (dcast(data, GtkWidget)[0] == widget)
{
cassert(dcast(data, GtkWidget)[1] == NULL);
cassert(dcast(data, GtkWidget)[2] == NULL);
dcast(data, GtkWidget)[2] = widget;
}
else if (dcast(data, GtkWidget)[2] != NULL)
{
cassert(dcast(data, GtkWidget)[1] == NULL);
dcast(data, GtkWidget)[1] = widget;
dcast(data, GtkWidget)[2] = NULL;
}
}
static GtkWidget *i_get_next_menubar(GtkWidget *widget, GtkWidget *current)
{
GtkWidget *data[3];
data[0] = current;
data[1] = NULL;
data[2] = NULL;
cassert(GTK_IS_BOX(widget));
gtk_container_foreach(GTK_CONTAINER(widget), i_next_menubar, (gpointer)data);
if (data[1] != NULL)
{
cassert(_oscontrol_num_children(data[1]) == 0);
}
return data[1];
}
static void i_add_item_to_new_bar(OSMenuItem *item, GtkWidget *widget)
{
GtkWidget *menubar = gtk_menu_bar_new();
cassert_no_null(item);
cassert_no_null(item->widget);
cassert(GTK_IS_BOX(widget));
gtk_box_pack_start(GTK_BOX(widget), menubar, FALSE, FALSE, 0);
gtk_menu_shell_append(GTK_MENU_SHELL(menubar), item->widget);
}
void _osmenuitem_append_to_menubar(OSMenuItem *item, OSMenu *menu, GtkWidget *widget, const uint32_t max_width)
{
cassert_no_null(item);
cassert_no_null(item->widget);
cassert(item->menu == NULL);
item->menu = menu;
if (item->visible == TRUE && widget != NULL)
{
GtkWidget *current = NULL;
cassert(GTK_IS_BOX(widget));
current = i_get_current_menubar(widget);
if (current != NULL)
{
GtkRequisition bsize;
cassert(GTK_IS_MENU_BAR(current));
gtk_menu_shell_append(GTK_MENU_SHELL(current), item->widget);
gtk_widget_show_all(widget);
gtk_widget_get_preferred_size(widget, &bsize, NULL);
cassert((bsize.width > 0 && bsize.height > 0) || (bsize.width == 0 && bsize.height == 0));
if ((uint32_t)bsize.width > max_width)
{
GtkWidget *next = NULL;
gtk_container_remove(GTK_CONTAINER(current), item->widget);
#if defined(__ASSERTS__)
{
GtkRequisition nbsize;
gtk_widget_get_preferred_size(current, &nbsize, NULL);
cassert((uint32_t)nbsize.width <= max_width);
}
#endif
next = i_get_next_menubar(widget, current);
if (next != NULL)
{
gtk_menu_shell_append(GTK_MENU_SHELL(next), item->widget);
gtk_widget_show_all(widget);
}
else
{
i_add_item_to_new_bar(item, widget);
}
}
}
else
{
i_add_item_to_new_bar(item, widget);
}
}
}
void _osmenuitem_unset_parent(OSMenuItem *item, OSMenu *menu)
{
cassert_no_null(item);
if (item->menu != NULL)
{
cassert_unref(item->menu == menu, menu);
item->menu = NULL;
}
}
void _osmenuitem_set_accel(OSMenuItem *item, GtkAccelGroup *accel)
{
cassert_no_null(item);
cassert_no_null(accel);
cassert(item->accel == NULL);
item->accel = accel;
if (item->key != ENUM_MAX(vkey_t))
gtk_widget_add_accelerator(item->widget, "activate", item->accel, i_VIRTUAL_KEY[item->key], i_kmod(item->modifiers), GTK_ACCEL_VISIBLE);
if (item->sub_menu != NULL)
_osmenu_set_accel(item->sub_menu, accel);
}
void _osmenuitem_unset_accel(OSMenuItem *item, GtkAccelGroup *accel)
{
cassert_no_null(item);
if (item->accel != NULL)
{
cassert(item->accel == accel);
if (item->key != ENUM_MAX(vkey_t))
{
gboolean ok = gtk_widget_remove_accelerator(item->widget, item->accel, i_VIRTUAL_KEY[item->key], i_kmod(item->modifiers));
cassert_unref(ok == TRUE, ok);
}
item->accel = NULL;
}
if (item->sub_menu != NULL)
_osmenu_unset_accel(item->sub_menu, accel);
}