2015-04-23 12:11:36 -05:00
|
|
|
// 23 april 2015
|
|
|
|
#include "uipriv_unix.h"
|
|
|
|
|
2015-04-24 14:59:38 -05:00
|
|
|
// TODO get rid of the base item and store the GType, the disabled flag, and the checked flag like we do on Windows?
|
2015-04-23 22:37:27 -05:00
|
|
|
|
2015-04-23 12:11:36 -05:00
|
|
|
static GArray *menus = NULL;
|
|
|
|
static gboolean menusFinalized = FALSE;
|
2015-04-30 12:02:30 -05:00
|
|
|
static gboolean hasQuit = FALSE;
|
|
|
|
static gboolean hasPreferences = FALSE;
|
|
|
|
static gboolean hasAbout = FALSE;
|
2015-04-23 12:11:36 -05:00
|
|
|
|
|
|
|
struct menu {
|
|
|
|
uiMenu m;
|
|
|
|
char *name;
|
2015-04-23 19:15:21 -05:00
|
|
|
GArray *items; // []*struct menuItem
|
2015-04-23 12:11:36 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
struct menuItem {
|
|
|
|
uiMenuItem mi;
|
|
|
|
char *name;
|
|
|
|
int type;
|
|
|
|
void (*onClicked)(uiMenuItem *, uiWindow *, void *);
|
|
|
|
void *onClickedData;
|
2015-04-23 14:30:37 -05:00
|
|
|
GtkWidget *baseItem; // template for new instances; kept in sync with everything else
|
2015-04-23 12:11:36 -05:00
|
|
|
GHashTable *uiWindows; // map[GtkMenuItem]uiWindow
|
2015-04-23 19:07:42 -05:00
|
|
|
// TODO this assumes that a gulong can fit in a gpointer
|
|
|
|
GHashTable *signals; // map[GtkMenuItem]gulong
|
2015-04-23 12:11:36 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
enum {
|
|
|
|
typeRegular,
|
|
|
|
typeCheckbox,
|
|
|
|
typeQuit,
|
|
|
|
typePreferences,
|
|
|
|
typeAbout,
|
|
|
|
typeSeparator,
|
|
|
|
};
|
|
|
|
|
|
|
|
#define NEWHASH() g_hash_table_new(g_direct_hash, g_direct_equal)
|
|
|
|
|
2015-04-23 19:07:42 -05:00
|
|
|
// we do NOT want programmatic updates to raise an ::activated signal
|
|
|
|
static void singleSetChecked(GtkCheckMenuItem *menuitem, gboolean checked, gulong signal)
|
|
|
|
{
|
|
|
|
g_signal_handler_block(menuitem, signal);
|
|
|
|
gtk_check_menu_item_set_active(menuitem, checked);
|
|
|
|
g_signal_handler_unblock(menuitem, signal);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void setChecked(struct menuItem *item, gboolean checked)
|
|
|
|
{
|
|
|
|
GHashTableIter iter;
|
|
|
|
gpointer widget;
|
|
|
|
|
|
|
|
gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(item->baseItem), checked);
|
|
|
|
g_hash_table_iter_init(&iter, item->uiWindows);
|
|
|
|
while (g_hash_table_iter_next(&iter, &widget, NULL))
|
|
|
|
singleSetChecked(GTK_CHECK_MENU_ITEM(widget), checked, (gulong) g_hash_table_lookup(item->signals, widget));
|
|
|
|
}
|
|
|
|
|
2015-04-23 14:30:37 -05:00
|
|
|
static void onClicked(GtkMenuItem *menuitem, gpointer data)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) data;
|
|
|
|
uiWindow *w;
|
|
|
|
|
2015-04-23 19:07:42 -05:00
|
|
|
// we need to manually update the checked states of all menu items if one changes
|
|
|
|
// notice that this is getting the checked state of the menu item that this signal is sent from
|
|
|
|
if (item->type == typeCheckbox)
|
|
|
|
setChecked(item, gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)));
|
|
|
|
|
2015-04-23 14:30:37 -05:00
|
|
|
w = uiWindow(g_hash_table_lookup(item->uiWindows, menuitem));
|
|
|
|
(*(item->onClicked))(uiMenuItem(item), w, item->onClickedData);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void defaultOnClicked(uiMenuItem *item, uiWindow *w, void *data)
|
|
|
|
{
|
|
|
|
// do nothing
|
|
|
|
}
|
|
|
|
|
|
|
|
static void menuItemEnableDisable(struct menuItem *item, gboolean enabled)
|
|
|
|
{
|
|
|
|
GHashTableIter iter;
|
|
|
|
gpointer widget;
|
|
|
|
|
|
|
|
gtk_widget_set_sensitive(item->baseItem, enabled);
|
|
|
|
g_hash_table_iter_init(&iter, item->uiWindows);
|
|
|
|
while (g_hash_table_iter_next(&iter, &widget, NULL))
|
|
|
|
gtk_widget_set_sensitive(GTK_WIDGET(widget), enabled);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void menuItemEnable(uiMenuItem *ii)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
|
|
|
|
menuItemEnableDisable(item, TRUE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void menuItemDisable(uiMenuItem *ii)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
|
|
|
|
menuItemEnableDisable(item, FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void menuItemOnClicked(uiMenuItem *ii, void (*f)(uiMenuItem *, uiWindow *, void *), void *data)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
|
|
|
|
item->onClicked = f;
|
|
|
|
item->onClickedData = data;
|
|
|
|
}
|
|
|
|
|
2015-04-23 15:00:34 -05:00
|
|
|
static int menuItemChecked(uiMenuItem *ii)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
|
|
|
|
return gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item->baseItem)) != FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void menuItemSetChecked(uiMenuItem *ii, int checked)
|
|
|
|
{
|
|
|
|
struct menuItem *item = (struct menuItem *) ii;
|
|
|
|
gboolean c;
|
|
|
|
|
|
|
|
// use explicit values
|
|
|
|
c = FALSE;
|
|
|
|
if (checked)
|
|
|
|
c = TRUE;
|
2015-04-23 19:07:42 -05:00
|
|
|
setChecked(item, c);
|
2015-04-23 15:00:34 -05:00
|
|
|
}
|
|
|
|
|
2015-04-23 12:11:36 -05:00
|
|
|
static uiMenuItem *newItem(struct menu *m, int type, const char *name)
|
|
|
|
{
|
|
|
|
struct menuItem *item;
|
|
|
|
|
|
|
|
if (menusFinalized)
|
|
|
|
complain("attempt to create a new menu item after menus have been finalized");
|
|
|
|
|
2015-04-23 19:15:21 -05:00
|
|
|
item = uiNew(struct menuItem);
|
|
|
|
g_array_append_val(m->items, item);
|
2015-04-23 12:11:36 -05:00
|
|
|
|
|
|
|
item->type = type;
|
|
|
|
switch (item->type) {
|
|
|
|
case typeQuit:
|
|
|
|
item->name = g_strdup("Quit");
|
|
|
|
break;
|
|
|
|
case typePreferences:
|
|
|
|
item->name = g_strdup("Preferences...");
|
|
|
|
break;
|
|
|
|
case typeAbout:
|
|
|
|
item->name = g_strdup("About");
|
|
|
|
break;
|
|
|
|
case typeSeparator:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
item->name = g_strdup(name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-04-23 14:30:37 -05:00
|
|
|
item->onClicked = defaultOnClicked;
|
|
|
|
|
2015-04-23 12:11:36 -05:00
|
|
|
switch (item->type) {
|
|
|
|
case typeCheckbox:
|
|
|
|
item->baseItem = gtk_check_menu_item_new_with_label(item->name);
|
|
|
|
break;
|
|
|
|
case typeSeparator:
|
|
|
|
item->baseItem = gtk_separator_menu_item_new();
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
item->baseItem = gtk_menu_item_new_with_label(item->name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
item->uiWindows = NEWHASH();
|
2015-04-23 19:07:42 -05:00
|
|
|
item->signals = NEWHASH();
|
2015-04-23 12:11:36 -05:00
|
|
|
|
2015-04-23 14:30:37 -05:00
|
|
|
uiMenuItem(item)->Enable = menuItemEnable;
|
|
|
|
uiMenuItem(item)->Disable = menuItemDisable;
|
|
|
|
uiMenuItem(item)->OnClicked = menuItemOnClicked;
|
2015-04-23 15:00:34 -05:00
|
|
|
uiMenuItem(item)->Checked = menuItemChecked;
|
|
|
|
uiMenuItem(item)->SetChecked = menuItemSetChecked;
|
2015-04-23 12:11:36 -05:00
|
|
|
|
|
|
|
return uiMenuItem(item);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendItem(uiMenu *mm, const char *name)
|
|
|
|
{
|
|
|
|
return newItem((struct menu *) mm, typeRegular, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendCheckItem(uiMenu *mm, const char *name)
|
|
|
|
{
|
|
|
|
return newItem((struct menu *) mm, typeCheckbox, name);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendQuitItem(uiMenu *mm)
|
|
|
|
{
|
2015-04-30 12:02:30 -05:00
|
|
|
if (hasQuit)
|
|
|
|
complain("attempt to add multiple Quit menu items");
|
|
|
|
hasQuit = TRUE;
|
2015-04-23 12:11:36 -05:00
|
|
|
// TODO conditionally add separator
|
|
|
|
newItem((struct menu *) mm, typeSeparator, NULL);
|
|
|
|
return newItem((struct menu *) mm, typeQuit, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendPreferencesItem(uiMenu *mm)
|
|
|
|
{
|
2015-04-30 12:02:30 -05:00
|
|
|
if (hasPreferences)
|
|
|
|
complain("attempt to add multiple Preferences menu items");
|
|
|
|
hasPreferences = TRUE;
|
2015-04-23 12:11:36 -05:00
|
|
|
// TODO conditionally add separator
|
|
|
|
newItem((struct menu *) mm, typeSeparator, NULL);
|
|
|
|
return newItem((struct menu *) mm, typePreferences, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenuItem *menuAppendAboutItem(uiMenu *mm)
|
|
|
|
{
|
2015-04-30 12:02:30 -05:00
|
|
|
if (hasAbout)
|
|
|
|
complain("attempt to add multiple About menu items");
|
|
|
|
hasAbout = TRUE;
|
2015-04-23 12:11:36 -05:00
|
|
|
// TODO conditionally add separator
|
|
|
|
newItem((struct menu *) mm, typeSeparator, NULL);
|
|
|
|
return newItem((struct menu *) mm, typeAbout, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
void menuAppendSeparator(uiMenu *mm)
|
|
|
|
{
|
|
|
|
newItem((struct menu *) mm, typeSeparator, NULL);
|
|
|
|
}
|
|
|
|
|
|
|
|
uiMenu *uiNewMenu(const char *name)
|
|
|
|
{
|
|
|
|
struct menu *m;
|
|
|
|
|
|
|
|
if (menusFinalized)
|
|
|
|
complain("attempt to create a new menu after menus have been finalized");
|
|
|
|
if (menus == NULL)
|
2015-04-23 19:15:21 -05:00
|
|
|
menus = g_array_new(FALSE, TRUE, sizeof (struct menu *));
|
2015-04-23 12:11:36 -05:00
|
|
|
|
2015-04-23 19:15:21 -05:00
|
|
|
m = uiNew(struct menu);
|
|
|
|
g_array_append_val(menus, m);
|
2015-04-23 12:11:36 -05:00
|
|
|
|
|
|
|
m->name = g_strdup(name);
|
2015-04-23 19:15:21 -05:00
|
|
|
m->items = g_array_new(FALSE, TRUE, sizeof (struct menuItem *));
|
2015-04-23 12:11:36 -05:00
|
|
|
|
|
|
|
uiMenu(m)->AppendItem = menuAppendItem;
|
|
|
|
uiMenu(m)->AppendCheckItem = menuAppendCheckItem;
|
|
|
|
uiMenu(m)->AppendQuitItem = menuAppendQuitItem;
|
|
|
|
uiMenu(m)->AppendPreferencesItem = menuAppendPreferencesItem;
|
|
|
|
uiMenu(m)->AppendAboutItem = menuAppendAboutItem;
|
|
|
|
uiMenu(m)->AppendSeparator = menuAppendSeparator;
|
|
|
|
|
|
|
|
return uiMenu(m);
|
|
|
|
}
|
|
|
|
|
2015-04-23 17:48:01 -05:00
|
|
|
static void appendMenuItem(GtkMenuShell *submenu, struct menuItem *item, uiWindow *w)
|
|
|
|
{
|
|
|
|
GtkWidget *menuitem;
|
2015-04-23 19:07:42 -05:00
|
|
|
gulong signal;
|
2015-04-23 17:48:01 -05:00
|
|
|
|
|
|
|
menuitem = g_object_new(G_OBJECT_TYPE(item->baseItem), NULL);
|
|
|
|
if (item->name != NULL)
|
|
|
|
gtk_menu_item_set_label(GTK_MENU_ITEM(menuitem), item->name);
|
|
|
|
if (item->type != typeSeparator) {
|
2015-04-23 19:07:42 -05:00
|
|
|
signal = g_signal_connect(menuitem, "activate", G_CALLBACK(onClicked), item);
|
|
|
|
gtk_widget_set_sensitive(menuitem, gtk_widget_get_sensitive(item->baseItem));
|
2015-04-23 17:48:01 -05:00
|
|
|
if (item->type == typeCheckbox)
|
2015-04-23 19:07:42 -05:00
|
|
|
singleSetChecked(GTK_CHECK_MENU_ITEM(menuitem), gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(item->baseItem)), signal);
|
2015-04-23 17:48:01 -05:00
|
|
|
}
|
|
|
|
gtk_menu_shell_append(submenu, menuitem);
|
|
|
|
g_hash_table_insert(item->uiWindows, menuitem, w);
|
2015-04-23 19:07:42 -05:00
|
|
|
g_hash_table_insert(item->signals, menuitem, (gpointer) signal);
|
2015-04-23 17:48:01 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO should this return a zero-height widget (or NULL) if there are no menus defined?
|
|
|
|
GtkWidget *makeMenubar(uiWindow *w)
|
|
|
|
{
|
|
|
|
GtkWidget *menubar;
|
|
|
|
guint i, j;
|
|
|
|
struct menu *m;
|
|
|
|
GtkWidget *menuitem;
|
|
|
|
GtkWidget *submenu;
|
|
|
|
|
|
|
|
menusFinalized = TRUE;
|
|
|
|
|
|
|
|
menubar = gtk_menu_bar_new();
|
|
|
|
|
|
|
|
for (i = 0; i < menus->len; i++) {
|
2015-04-23 19:15:21 -05:00
|
|
|
m = g_array_index(menus, struct menu *, i);
|
2015-04-23 17:48:01 -05:00
|
|
|
menuitem = gtk_menu_item_new_with_label(m->name);
|
|
|
|
submenu = gtk_menu_new();
|
|
|
|
gtk_menu_item_set_submenu(GTK_MENU_ITEM(menuitem), submenu);
|
|
|
|
for (j = 0; j < m->items->len; j++)
|
2015-04-23 19:15:21 -05:00
|
|
|
appendMenuItem(GTK_MENU_SHELL(submenu), g_array_index(m->items, struct menuItem *, j), w);
|
2015-04-23 17:48:01 -05:00
|
|
|
gtk_menu_shell_append(GTK_MENU_SHELL(menubar), menuitem);
|
|
|
|
}
|
|
|
|
|
|
|
|
gtk_widget_set_hexpand(menubar, TRUE);
|
|
|
|
gtk_widget_set_halign(menubar, GTK_ALIGN_FILL);
|
|
|
|
return menubar;
|
|
|
|
}
|
2015-04-30 11:05:18 -05:00
|
|
|
|
2015-04-30 11:36:50 -05:00
|
|
|
struct freeMenuItemData {
|
|
|
|
GArray *items;
|
|
|
|
guint i;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void freeMenuItem(GtkWidget *widget, gpointer data)
|
|
|
|
{
|
|
|
|
struct freeMenuItemData *fmi = (struct freeMenuItemData *) data;
|
|
|
|
struct menuItem *item;
|
|
|
|
|
|
|
|
item = g_array_index(fmi->items, struct menuItem *, fmi->i);
|
|
|
|
if (g_hash_table_remove(item->uiWindows, widget) == FALSE)
|
|
|
|
complain("GtkMenuItem %p not in menu items uiWindows map", widget);
|
|
|
|
if (g_hash_table_remove(item->signals, widget) == FALSE)
|
|
|
|
complain("GtkMenuItem %p not in menu items signals map", widget);
|
|
|
|
fmi->i++;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void freeMenu(GtkWidget *widget, gpointer data)
|
|
|
|
{
|
|
|
|
guint *i = (guint *) data;
|
|
|
|
struct menu *m;
|
|
|
|
GtkMenuItem *item;
|
|
|
|
GtkWidget *submenu;
|
|
|
|
struct freeMenuItemData fmi;
|
|
|
|
|
|
|
|
m = g_array_index(menus, struct menu *, *i);
|
|
|
|
item = GTK_MENU_ITEM(widget);
|
|
|
|
submenu = gtk_menu_item_get_submenu(item);
|
|
|
|
fmi.items = m->items;
|
|
|
|
fmi.i = 0;
|
|
|
|
gtk_container_foreach(GTK_CONTAINER(submenu), freeMenuItem, &fmi);
|
|
|
|
(*i)++;
|
|
|
|
}
|
|
|
|
|
2015-04-30 11:05:18 -05:00
|
|
|
void freeMenubar(GtkWidget *mb)
|
|
|
|
{
|
2015-04-30 11:36:50 -05:00
|
|
|
guint i;
|
|
|
|
|
|
|
|
i = 0;
|
|
|
|
gtk_container_foreach(GTK_CONTAINER(mb), freeMenu, &i);
|
2015-04-30 11:05:18 -05:00
|
|
|
}
|