Logo Search packages:      
Sourcecode: whysynth version File versions  Download package

gui_popmenu.c

/* WhySynth DSSI software synthesizer GUI
 *
 * Copyright (C) 2004-2008 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., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 */

#ifdef HAVE_CONFIG_H
#  include <config.h>
#endif

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <gtk/gtk.h>
#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);

    gdk_string_extents (gtk_style_get_font(pm->menu->style),
                        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_menu_item_get_active(GTK_CHECK_MENU_ITEM(gtkmenuitem)))
        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) {
        gtk_widget_set_size_request(label, pm->width, -1);
    }

    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);
}


Generated by  Doxygen 1.6.0   Back to index