/* WhySynth DSSI software synthesizer GUI * * Copyright (C) 2004-2007 Sean Bolton * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License, or (at your option) any later version. * * This program is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR * PURPOSE. See the GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307, USA. */ #ifdef HAVE_CONFIG_H # include #endif #include #include #include #include #include #include "gui_popmenu.h" /* this features entirely too much brute-force walking of the menu tree, but * it works, so I'm not going to clean it up now.... */ /* define this to allow use of the mouse scroll wheel to change a * popmenubutton's value: */ /* #define POPMENU_USE_SCROLL_WHEEL */ static void * pm_zalloc(size_t size) { void *p; for (p = calloc(1, size); p == NULL; sleep(1)); return p; } popmenu * popmenu_new(void) { return (popmenu *)pm_zalloc(sizeof(popmenu)); } static popmenuitem * pm_add_to_tree(popmenuitem **listhead, const char *path, int sort, int id) { const char *cp; int len; popmenuitem *pmi, *pmi_to_insert_after; /* find length of top element of path */ cp = strchr(path, '|'); if (cp == NULL) len = strlen(path); else len = cp - path; if (len == 0) { fprintf(stderr, "popmenu_add: menu element has zero length (path = '%s'), aborting!\n", path); exit(1); } /* find top element of path in list */ pmi = *listhead; pmi_to_insert_after = NULL; while (pmi) { if (!strncmp(path, pmi->label, len) && pmi->label[len] == '\0') { break; } else { if (pmi->sort <= sort) pmi_to_insert_after = pmi; } pmi = pmi->next; } if (pmi) { /* found it */ if (cp != NULL) { /* expecting a branch node */ if (pmi->isbranch) { return pm_add_to_tree(&pmi->nodelist, cp + 1, sort, id); /* recurse to next lower level */ } else { fprintf(stderr, "popmenu_add: found leaf where branch should have been (path='%s', node='%s'), aborting!\n", path, pmi->label); exit(1); } } else { /* expecting a leaf node */ if (pmi->isbranch) { fprintf(stderr, "popmenu_add: found matching branch while trying to add leaf (path='%s', node='%s'), aborting!\n", path, pmi->label); } else { fprintf(stderr, "popmenu_add: leaf already exists (path='%s', node='%s'), aborting!\n", path, pmi->label); } exit(1); } } else { /* didn't find it */ pmi = (popmenuitem *)pm_zalloc(sizeof(popmenuitem)); pmi->sort = sort; pmi->id = id; if (cp == NULL) { /* adding a leaf */ pmi->label = path; pmi->isbranch = 0; } else { /* adding a branch */ if (cp[1] != '\0') { fprintf(stderr, "popmenu_add: trying to add to non-existent branch '%s', aborting!\n", path); exit(1); } char *p = (char *)pm_zalloc(len + 1); memcpy(p, path, len); p[len] = '\0'; pmi->label = (const char *)p; pmi->isbranch = 1; pmi->nodelist = NULL; } /* insert into list */ if (pmi_to_insert_after) { pmi->next = pmi_to_insert_after->next; pmi_to_insert_after->next = pmi; } else { pmi->next = *listhead; *listhead = pmi; } return pmi; } } void popmenu_add(popmenu *pm, const char *path, int sort, int id) { pm_add_to_tree(&pm->nodelist, path, sort, id); } static void pm_on_popmenuitem_activate(GtkMenuItem *menuitem, gpointer user_data); /* forward */ static void pm_build_tree(popmenu *pm, GtkWidget *parentmenu, popmenuitem *firstitem, popmenuitem **longest_item) { popmenuitem *pmi = firstitem; while (pmi) { if (pmi->isbranch) { GtkWidget *mi, *sm; mi = gtk_menu_item_new_with_label (pmi->label); gtk_widget_show (mi); gtk_container_add (GTK_CONTAINER (parentmenu), mi); sm = gtk_menu_new (); gtk_menu_item_set_submenu (GTK_MENU_ITEM (mi), sm); pm_build_tree(pm, sm, pmi->nodelist, longest_item); } else { /* leaf */ GtkWidget *mi; mi = gtk_radio_menu_item_new_with_label (pm->radiogroup, pmi->label); pmi->menuitem = mi; pm->radiogroup = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (mi)); gtk_widget_show (mi); gtk_container_add (GTK_CONTAINER (parentmenu), mi); gtk_check_menu_item_set_show_toggle (GTK_CHECK_MENU_ITEM (mi), TRUE); gtk_signal_connect (GTK_OBJECT (mi), "activate", GTK_SIGNAL_FUNC (pm_on_popmenuitem_activate), (gpointer)pm); if (*longest_item == NULL || strlen(pmi->label) > strlen((*longest_item)->label)) *longest_item = pmi; } pmi = pmi->next; } } void popmenu_build(popmenu *pm, popmenu_activation_callback func) { popmenuitem *longest_item = NULL; gint width, l, r, a, d; pm->menu = gtk_menu_new (); gtk_object_ref (GTK_OBJECT (pm->menu)); gtk_object_sink (GTK_OBJECT (pm->menu)); /* see gtk docs FAQ for how to free this */ pm->activation_callback = func; pm_build_tree(pm, pm->menu, pm->nodelist, &longest_item); #if GTK_CHECK_VERSION(2, 0, 0) gdk_string_extents (gtk_style_get_font(pm->menu->style), #else gdk_string_extents (pm->menu->style->font, #endif longest_item->label, &l, &r, &width, &a, &d); pm->width = width; } /* find popmenuitem given id */ static popmenuitem * pm_find_id(popmenuitem *firstitem, int id) { popmenuitem *pmi = firstitem; while (pmi) { if (pmi->isbranch) { popmenuitem *subpmi = pm_find_id(pmi->nodelist, id); if (subpmi) { pmi = subpmi; break; } } else { if (pmi->id == id) break; } pmi = pmi->next; } return pmi; } int popmenu_set_active(popmenu *pm, int id) { popmenuitem *pmi = pm_find_id(pm->nodelist, id); if (pmi) { gtk_signal_handler_block_by_func(GTK_OBJECT(pmi->menuitem), GTK_SIGNAL_FUNC (pm_on_popmenuitem_activate), (gpointer)pm); gtk_check_menu_item_set_active (GTK_CHECK_MENU_ITEM (pmi->menuitem), TRUE); /* causes emission of 'activate' signal */ gtk_signal_handler_unblock_by_func(GTK_OBJECT(pmi->menuitem), GTK_SIGNAL_FUNC (pm_on_popmenuitem_activate), (gpointer)pm); return 1; } return 0; } void popmenu_popup(popmenu *pm, GdkEventButton *event) { gtk_menu_popup (GTK_MENU(pm->menu), NULL, NULL, NULL, NULL, event->button, event->time); } const char * popmenu_get_label_from_id(popmenu *pm, int id) { popmenuitem *pmi = pm_find_id(pm->nodelist, id); return (pmi ? pmi->label : NULL); } /* find popmenuitem given GtkMenuItem */ static popmenuitem * pm_find_gtkmenuitem(popmenuitem *firstitem, GtkMenuItem *gtkmenuitem) { popmenuitem *pmi = firstitem; while (pmi) { if (pmi->isbranch) { popmenuitem *subpmi = pm_find_gtkmenuitem(pmi->nodelist, gtkmenuitem); if (subpmi) { pmi = subpmi; break; } } else { if (pmi->menuitem == (GtkWidget *)gtkmenuitem) break; } pmi = pmi->next; } return pmi; } static void pm_on_popmenuitem_activate(GtkMenuItem *gtkmenuitem, gpointer user_data) { popmenu *pm = (popmenu *)user_data; popmenuitem *pmi; #if GTK_CHECK_VERSION(2, 0, 0) if (!gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtkmenuitem))) #else if (!(GTK_CHECK_MENU_ITEM(gtkmenuitem))->active) #endif return; /* ignore deactivation */ pmi = pm_find_gtkmenuitem(pm->nodelist, gtkmenuitem); if (pmi == NULL) { fprintf(stderr, "popmenu on_popmenuitem_activate: couldn't find popmenuitem!\n"); return; } (*pm->activation_callback)(pm, pmi); } static gboolean pmb_on_button_event(GtkWidget *widget, GdkEventButton *event, gpointer data); /* forward */ #ifdef POPMENU_USE_SCROLL_WHEEL static gboolean pmb_on_scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data); /* forward */ #endif /* POPMENU_USE_SCROLL_WHEEL */ popmenubutton * popmenubutton_new(popmenu *pm, int initial_id, popmenubutton_activation_callback func) { popmenubutton *pmb = (popmenubutton *)pm_zalloc(sizeof(popmenubutton)); GtkWidget *label, *button; label = gtk_label_new ("-"); pmb->label = label; gtk_widget_show (label); // gtk_label_set_line_wrap (GTK_LABEL (label), TRUE); // gtk_label_set_justify (GTK_LABEL (label), GTK_JUSTIFY_CENTER); // gtk_misc_set_padding (GTK_MISC (label), 5, 5); // gtk_misc_set_alignment (GTK_MISC (label), 0.1, 0.5); if (pm) { #if GTK_CHECK_VERSION(2, 0, 0) gtk_widget_set_size_request(label, pm->width, -1); #else gtk_widget_set_usize(label, pm->width, -1); #endif } button = gtk_button_new(); pmb->button = button; gtk_object_ref (GTK_OBJECT (button)); gtk_object_sink (GTK_OBJECT (button)); /* see gtk docs FAQ for how to free this */ gtk_container_add (GTK_CONTAINER(button), label); gtk_widget_show (button); // gtk_container_set_border_width (GTK_CONTAINER (button), 8); gtk_signal_connect (GTK_OBJECT (button), "button_press_event", GTK_SIGNAL_FUNC (pmb_on_button_event), (gpointer)pmb); #ifdef POPMENU_USE_SCROLL_WHEEL gtk_signal_connect (GTK_OBJECT (button), "scroll_event", GTK_SIGNAL_FUNC (pmb_on_scroll_event), (gpointer)pmb); #endif /* POPMENU_USE_SCROLL_WHEEL */ popmenubutton_set_popmenu_and_id(pmb, pm, initial_id); pmb->activation_callback = func; return pmb; } int popmenubutton_set_popmenu_and_id(popmenubutton *pmb, popmenu *pm, int id) { popmenuitem *pmi; pmb->menu = pm; pmb->id = id; if (pm && (pmi = pm_find_id(pm->nodelist, id))) { gtk_label_set_text(GTK_LABEL(pmb->label), pmi->label); return 1; } gtk_label_set_text(GTK_LABEL(pmb->label), "-"); return 0; } static popmenuitem * pmb_find_previous_popmenuitem(popmenuitem **previous, popmenuitem *firstitem, int id) { popmenuitem *pmi = firstitem; while (pmi) { if (pmi->isbranch) { popmenuitem *subpmi = pmb_find_previous_popmenuitem(previous, pmi->nodelist, id); if (subpmi) { pmi = subpmi; break; } } else { if (pmi->id == id) break; *previous = pmi; } pmi = pmi->next; } return pmi; } static popmenuitem * pmb_find_next_popmenuitem(int *found, popmenuitem *firstitem, int id) { popmenuitem *pmi = firstitem; while (pmi) { if (pmi->isbranch) { popmenuitem *subpmi = pmb_find_next_popmenuitem(found, pmi->nodelist, id); if (subpmi) return subpmi; } else { if (*found) return pmi; if (pmi->id == id) *found = 1; } pmi = pmi->next; } return NULL; } static gboolean pmb_on_button_event(GtkWidget *widget, GdkEventButton *event, gpointer data) { popmenubutton *pmb = (popmenubutton *)data; popmenu *pm = (popmenu *)pmb->menu; if (pm == NULL) { return TRUE; } switch (event->button) { case 1: { popmenuitem *pmi; int found = 0; if ((pmi = pmb_find_next_popmenuitem(&found, pm->nodelist, pmb->id))) { popmenubutton_set_popmenu_and_id(pmb, pmb->menu, pmi->id); (*pmb->activation_callback)(pmb, pmi); } } break; case 2: { popmenuitem *pmi = NULL; if (pmb_find_previous_popmenuitem(&pmi, pm->nodelist, pmb->id) && pmi != NULL) { popmenubutton_set_popmenu_and_id(pmb, pmb->menu, pmi->id); (*pmb->activation_callback)(pmb, pmi); } } break; default: case 3: popmenu_set_active(pm, pmb->id); pm->user_data = pmb; popmenu_popup (pm, event); break; } return TRUE; } #ifdef POPMENU_USE_SCROLL_WHEEL static gboolean pmb_on_scroll_event(GtkWidget *widget, GdkEventScroll *event, gpointer data) { popmenubutton *pmb = (popmenubutton *)data; popmenu *pm = (popmenu *)pmb->menu; if (pm == NULL) { return TRUE; } switch (event->direction) { case GDK_SCROLL_UP: { popmenuitem *pmi = NULL; if (pmb_find_previous_popmenuitem(&pmi, pm->nodelist, pmb->id) && pmi != NULL) { popmenubutton_set_popmenu_and_id(pmb, pmb->menu, pmi->id); (*pmb->activation_callback)(pmb, pmi); } } break; case GDK_SCROLL_DOWN: { popmenuitem *pmi; int found = 0; if ((pmi = pmb_find_next_popmenuitem(&found, pm->nodelist, pmb->id))) { popmenubutton_set_popmenu_and_id(pmb, pmb->menu, pmi->id); (*pmb->activation_callback)(pmb, pmi); } } break; default: break; } return TRUE; } #endif /* POPMENU_USE_SCROLL_WHEEL */ void popmenubutton_on_menuitem_activate(popmenu *pm, popmenuitem *pmi) { popmenubutton *pmb = (popmenubutton *)pm->user_data; pmb->id = pmi->id; gtk_label_set_text(GTK_LABEL(pmb->label), pmi->label); (*pmb->activation_callback)(pmb, pmi); }