/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2009 Wincent Balin
 *
 * 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 software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "plugin.h"
#include "pdbox.h"

#include "lib/xlcd.h"

/* Button colors. */
#define BTNCOLOR_DARK (LCD_DARKGRAY)
#define BTNCOLOR_LIGHT (LCD_LIGHTGRAY)

/* Variables in the main code. */
extern char* filename;
extern bool quit;

/* Screen multiplier. */
static float screen_multiplier;

/* Back- and foreground colors. */
static unsigned bgcolor;
static unsigned fgcolor;

/* Displacement of the slanted corner in the contour of the number widget. */
static int number_corner;

/* Button flags. */
static bool play_on;
static bool previous_on;
static bool next_on;
static bool menu_on;
static bool action_on;

/* Pause flag. */
static bool paused;


/* Draw circle using midpoint circle algorithm.
   Adapted from http://en.wikipedia.org/wiki/Midpoint_circle_algorithm. */
void drawcircle(int x, int y, int r)
{
    int f = 1 - r;
    int ddfx = 1;
    int ddfy = -2 * r;
    int xp = 0;
    int yp = r;

    /* Draw outer points. */
    rb->lcd_drawpixel(x, y + r);
    rb->lcd_drawpixel(x, y + r);
    rb->lcd_drawpixel(x + r, y);
    rb->lcd_drawpixel(x - r, y);

    /* Calculate coordinates of points in one octant. */
    while(xp < yp)
    {
        /* ddfx == 2 * xp + 1;
           ddfy == -2 * yp;
           f == xp*xp + yp*yp - r*r + 2*xp - yp + 1; */
        if(f >= 0)
        {
            yp--;
            ddfy += 2;
            f += ddfy;
        }

        xp++;
        ddfx += 2;
        f += ddfx;

        /* Draw pixels in all octants. */
        rb->lcd_drawpixel(x + xp, y + yp);
        rb->lcd_drawpixel(x + xp, y - yp);
        rb->lcd_drawpixel(x - xp, y + yp);
        rb->lcd_drawpixel(x - xp, y - yp);
        rb->lcd_drawpixel(x + yp, y + xp);
        rb->lcd_drawpixel(x + yp, y - xp);
        rb->lcd_drawpixel(x - yp, y + xp);
        rb->lcd_drawpixel(x - yp, y - xp);
    }
}

/* Fill circle. */
void fillcircle(int x, int y, int r)
{
    int f = 1 - r;
    int ddfx = 1;
    int ddfy = -2 * r;
    int xp = 0;
    int yp = r;

    /* Draw outer points. */
    rb->lcd_drawpixel(x, y + r);
    rb->lcd_drawpixel(x, y + r);
    rb->lcd_drawpixel(x + r, y);
    rb->lcd_drawpixel(x - r, y);

    /* Calculate coordinates of points in one octant. */
    while(xp < yp)
    {
        /* ddfx == 2 * xp + 1;
           ddfy == -2 * yp;
           f == xp*xp + yp*yp - r*r + 2*xp - yp + 1; */
        if(f >= 0)
        {
            yp--;
            ddfy += 2;
            f += ddfy;
        }

        xp++;
        ddfx += 2;
        f += ddfx;

        /* Fill circle with horizontal lines. */
        rb->lcd_hline(x - xp, x + xp, y - yp);
        rb->lcd_hline(x - xp, x + xp, y + yp);
        rb->lcd_hline(x - yp, x + yp, y - xp);
        rb->lcd_hline(x - yp, x + yp, y + xp);
    }

    /* Draw last horizontal line (central one). */
    rb->lcd_hline(x - r, x + r, y);
}

/* Initialize GUI. */
void pd_gui_init(void)
{
    /* Reset button flags. */
    play_on = false;
    previous_on = false;
    next_on = false;
    menu_on = false;
    action_on = false;

    /* Unpause Pure Data. */
    paused = false;

    /* Calculate dimension factors. */
    screen_multiplier = ((float) LCD_WIDTH) / 160.0f;
    number_corner = 5 * screen_multiplier;

    /* Get back- and foreground colors. */
    bgcolor = rb->lcd_get_background();
    fgcolor = rb->lcd_get_foreground();

    /* Clear background. */
    rb->lcd_clear_display();

    /* Draw background of appropriate color. */
    rb->lcd_set_foreground(bgcolor);
    rb->lcd_fillrect(0, 0, LCD_WIDTH, LCD_HEIGHT);
    rb->lcd_set_foreground(fgcolor);

    /* Update display. */
    rb->lcd_update();
}

/* Load PD patch. */
unsigned int pd_gui_load_patch(struct pd_widget* wg, unsigned int max_widgets)
{
    int fd;
    char line[100];
    char* saveptr;
    unsigned int widgets = 0;

    /* Open PD patch. */
    fd = open(filename, O_RDONLY);

    /* Check for I/O error. */
    if(!fd)
    {
        /* Show error message and make plug-in quit. */
        rb->splash(HZ, "Error opening .pd file!");
        quit = true;
        return 0;
    }

    /* Read lines from PD file. */
    while(rb->read_line(fd, line, sizeof(line)) > 0)
    {
        /* Check whether we got too many widgets. */
        if(widgets >= max_widgets)
        {
            rb->splash(HZ, "Too many widgets!");
            quit = true;
            return 0;
        }

        /* Search for key strings in the line. */
        if((strstr(line, "floatatom") != NULL) &&
           (strstr(line, "pod_") != NULL))
        {
            wg->id = PD_NUMBER;

            strtok_r(line, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->x = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->y = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->w = 7 * atoi(strtok_r(NULL, " ", &saveptr)) *
                                                          screen_multiplier;
            wg->h = 16 * screen_multiplier;
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strncpy(wg->name, strtok_r(NULL, " ", &saveptr), sizeof(wg->name));

            /* We got one more widget. */
            wg++;
            widgets++;
        }
        else if((strstr(line, "symbolatom") != NULL) &&
                (strstr(line, "pod_") != NULL))
        {
            wg->id = PD_SYMBOL;

            strtok_r(line, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->x = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->y = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->w = 7 * atoi(strtok_r(NULL, " ", &saveptr)) *
                                                          screen_multiplier;
            wg->h = 16 * screen_multiplier;
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strncpy(wg->name, strtok_r(NULL, " ", &saveptr), sizeof(wg->name));

            /* We got one more widget. */
            wg++;
            widgets++;
        }
        else if((strstr(line, "vsl") != NULL) &&
                (strstr(line, "pod_") != NULL))
        {
            wg->id = PD_VSLIDER;

            strtok_r(line, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->x = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->y = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            strtok_r(NULL, " ", &saveptr);
            wg->w = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->h = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->min = atoi(strtok_r(NULL, " ", &saveptr));
            wg->max = atoi(strtok_r(NULL, " ", &saveptr));
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strncpy(wg->name, strtok_r(NULL, " ", &saveptr), sizeof(wg->name));

            /* We got one more widget. */
            wg++;
            widgets++;
        }
        else if((strstr(line, "hsl") != NULL) &&
                (strstr(line, "pod_") != NULL))
        {
            wg->id = PD_HSLIDER;

            strtok_r(line, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->x = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->y = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            strtok_r(NULL, " ", &saveptr);
            wg->w = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->h = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->min = atoi(strtok_r(NULL, " ", &saveptr));
            wg->max = atoi(strtok_r(NULL, " ", &saveptr));
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strncpy(wg->name, strtok_r(NULL, " ", &saveptr), sizeof(wg->name));

            /* We got one more widget. */
            wg++;
            widgets++;
        }
        else if((strstr(line, "vradio") != NULL) &&
                (strstr(line, "pod_") != NULL))
        {
            wg->id = PD_VRADIO;

            strtok_r(line, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->x = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->y = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            strtok_r(NULL, " ", &saveptr);
            wg->w = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->min = 0;
            wg->max = atoi(strtok_r(NULL, " ", &saveptr));
            wg->h = wg->w * wg->max;
            strtok_r(NULL, " ", &saveptr);
            strncpy(wg->name, strtok_r(NULL, " ", &saveptr), sizeof(wg->name));
            wg->max--;

            /* We got one more widget. */
            wg++;
            widgets++;
        }
        else if((strstr(line, "hradio") != NULL) &&
                (strstr(line, "pod_") != NULL))
        {
            wg->id = PD_HRADIO;

            strtok_r(line, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->x = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->y = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            strtok_r(NULL, " ", &saveptr);
            wg->h = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->min = 0;
            wg->max = atoi(strtok_r(NULL, " ", &saveptr));
            wg->w = wg->h * wg->max;
            strtok_r(NULL, " ", &saveptr);
            strncpy(wg->name, strtok_r(NULL, " ", &saveptr), sizeof(wg->name));
            wg->max--;

            /* We got one more widget. */
            wg++;
            widgets++;
        }
        else if((strstr(line, "bng") != NULL) &&
                (strstr(line, "pod_") != NULL))
        {
            wg->id = PD_BANG;

            strtok_r(line, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->x = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->y = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            strtok_r(NULL, " ", &saveptr);
            wg->w = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->h = wg->w;
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            strncpy(wg->name, strtok_r(NULL, " ", &saveptr), sizeof(wg->name));
            wg->min = 0;
            wg->max = 1;

            /* Clear timeout flag. */
            wg->timeout = 0;

            /* We got one more widget. */
            wg++;
            widgets++;
        }
        else if(strstr(line, "text") != NULL)
        {
            wg->id = PD_TEXT;

            strtok_r(line, " ", &saveptr);
            strtok_r(NULL, " ", &saveptr);
            wg->x = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            wg->y = atoi(strtok_r(NULL, " ", &saveptr)) * screen_multiplier;
            strncpy(wg->name, strtok_r(NULL, " ", &saveptr), sizeof(wg->name));
            char* w = strtok_r(NULL, " ", &saveptr);
            while(w != NULL)
            {
                strcat(wg->name, w);
                strcat(wg->name, " ");
                w = strtok_r(NULL, " ", &saveptr);
            }
            /* Cut off unneeded characters (';' and '\n'). */
            int namelen = strlen(wg->name);
            if(namelen > 1)
            {
                /* Cut off '\n'. */
                wg->name[namelen-1] = '\0';
                namelen--;
                /* Cut the last semi-colon, if there is one. */
                if(wg->name[namelen-1] == ';')
                    wg->name[namelen-1] = '\0';
            }


            /* We got one more widget. */
            wg++;
            widgets++;
        }
    }

    /* Close PD patch. */
    close(fd);

    /* Return amount of loaded widgets. */
    return widgets;
}

/* Draw standard user interface. */
void pd_gui_draw_standard(void)
{
    /* Draw main circle. */
    rb->lcd_set_foreground(fgcolor);
    fillcircle(LCD_WIDTH / 2,
               LCD_HEIGHT / 2,
               2 * MIN(LCD_WIDTH, LCD_HEIGHT) / 5);

    /* Draw center circle. */
    rb->lcd_set_foreground(action_on ? BTNCOLOR_DARK : BTNCOLOR_LIGHT);
    fillcircle(LCD_WIDTH / 2,
               LCD_HEIGHT / 2,
               MIN(LCD_WIDTH, LCD_HEIGHT) / 8);

    /* Draw pressed buttons. */
    if(play_on)
        fillcircle(LCD_WIDTH / 2,
                   3 * LCD_HEIGHT / 4,
                   MIN(LCD_WIDTH, LCD_HEIGHT) / 8);

    if(previous_on)
        fillcircle(LCD_WIDTH / 3,
                   LCD_HEIGHT / 2,
                   MIN(LCD_WIDTH, LCD_HEIGHT) / 8);

    if(next_on)
        fillcircle(2 * LCD_WIDTH / 3 + 1,
                   LCD_HEIGHT / 2,
                   MIN(LCD_WIDTH, LCD_HEIGHT) / 8);

    if(menu_on)
        fillcircle(LCD_WIDTH / 2,
                   LCD_HEIGHT / 4,
                   MIN(LCD_WIDTH, LCD_HEIGHT) / 8);

    /* Restore foreground color. */
    rb->lcd_set_foreground(fgcolor);
}

/* Draw custom user interface. */
void pd_gui_draw_custom(struct pd_widget* wg, unsigned int widgets)
{
    unsigned int i;
    int j;
    int v;

    for(i = 0; i < widgets; wg++, i++)
    {
        switch(wg->id)
        {
            case PD_BANG:
                /* Clear area to (re-)draw. */
                rb->lcd_set_foreground(bgcolor);
                rb->lcd_fillrect(wg->x, wg->y, wg->w, wg->h);
                rb->lcd_set_foreground(fgcolor);
                /* Draw border (rectangle). */
                rb->lcd_drawrect(wg->x, wg->y, wg->w, wg->h);
                /* Draw button (circle), being filled depending on value. */
                if(((int) wg->value) == 0) /* Button not pressed. */
                    drawcircle(wg->x + wg->w/2,
                               wg->y + wg->w/2,
                               wg->w/2 - 1);
                else /* Button pressed. */
                    fillcircle(wg->x + wg->w/2,
                               wg->y + wg->w/2,
                               wg->w/2 - 1);
                break;

            case PD_VSLIDER:
                /* Clear area to (re-)draw. */
                rb->lcd_set_foreground(bgcolor);
                rb->lcd_fillrect(wg->x, wg->y, wg->w, wg->h);
                rb->lcd_set_foreground(fgcolor);
                /* Draw border. */
                rb->lcd_drawrect(wg->x, wg->y, wg->w, wg->h);
                /* Draw slider. */
                v = ((float) wg->h / (wg->max - wg->min)) *
                    (wg->max - wg->value);
                rb->lcd_fillrect(wg->x, wg->y + v, wg->w, 2);
                break;

            case PD_HSLIDER:
                /* Clear area to (re-)draw. */
                rb->lcd_set_foreground(bgcolor);
                rb->lcd_fillrect(wg->x, wg->y, wg->w, wg->h);
                rb->lcd_set_foreground(fgcolor);
                /* Draw border. */
                rb->lcd_drawrect(wg->x, wg->y, wg->w, wg->h);
                /* Draw slider. */
                v = ((float) wg->w / (wg->max - wg->min)) *
                    (wg->max - wg->value);
                rb->lcd_fillrect(wg->x + wg->w - v, wg->y, 2, wg->h);
                break;

            case PD_HRADIO:
                /* Clear area to (re-)draw. */
                rb->lcd_set_foreground(bgcolor);
                rb->lcd_fillrect(wg->x, wg->y, wg->w, wg->h);
                rb->lcd_set_foreground(fgcolor);
                for(j = 0; j < wg->w / wg->h; j++)
                {
                    /* Draw border. */
                    rb->lcd_drawrect(wg->x + wg->h * j, wg->y, wg->h, wg->h);
                    /* If marked, draw button. */
                    if(((int) wg->value) == j)
                        rb->lcd_fillrect(wg->x + wg->h * j + 2, wg->y + 2,
                                         wg->h - 4, wg->h - 4);
                }
                break;

            case PD_VRADIO:
                /* Clear area to (re-)draw. */
                rb->lcd_set_foreground(bgcolor);
                rb->lcd_fillrect(wg->x, wg->y, wg->w, wg->h);
                rb->lcd_set_foreground(fgcolor);
                for(j = 0; j < wg->h / wg->w; j++)
                {
                    /* Draw border. */
                    rb->lcd_drawrect(wg->x, wg->y + wg->w * j, wg->w, wg->w);
                    /* If marked, draw button. */
                    if(((int) wg->value) == j)
                        rb->lcd_fillrect(wg->x + 2, wg->y + wg->w * j + 2,
                                         wg->w - 4, wg->w - 4);
                }
                break;

            case PD_NUMBER:
                rb->lcd_hline(wg->x,
                              wg->x + wg->w - number_corner,
                              wg->y);
                rb->lcd_drawline(wg->x + wg->w - number_corner,
                                 wg->y,
                                 wg->x + wg->w,
                                 wg->y + number_corner);
                rb->lcd_vline(wg->x + wg->w,
                              wg->y + number_corner,
                              wg->y + wg->h);
                rb->lcd_hline(wg->x,
                              wg->x + wg->w,
                              wg->y + wg->h);
                rb->lcd_vline(wg->x,
                              wg->y,
                              wg->y + wg->h);
                char sbuf[12];
                ftoan(wg->value, sbuf, 12);
                rb->lcd_putsxy(wg->x + 2, wg->y + 2, sbuf);
                break;

            case PD_TEXT:
                rb->lcd_putsxy(wg->x + 2, wg->y, wg->name);
                break;

            case PD_SYMBOL:
                break;
        }
    }
}

/* Draw the GUI. */
void pd_gui_draw(struct pd_widget* wg, unsigned int widgets)
{
    /* Draw GUI. */
    if(widgets == 0)
        pd_gui_draw_standard();
    else
        pd_gui_draw_custom(wg, widgets);

    /* Update display. */
    rb->lcd_update();
}

/* Parse buttons, if needed, send messages to the Pure Data code. */
bool pd_gui_parse_buttons(unsigned int widgets)
{
    static long last_bv = 0;
    long bv = rb->button_get(false);

    /* Extract differences between current and previous button values. */
    long diff_bv = bv ^ last_bv;

    /* If no difference since the last button value, return here. */
    if(diff_bv == 0)
        return false;

    /* Query whether we have to quit. */
    if(bv == PDPOD_QUIT)
    {
        /* No need to send the quitting message to Pure Data core,
           as setting the quit flag ends threads and jumps
           to the cleanup code. */
        quit = true;
        return false;
    }

    /* Check the action (shift, mode) button. */
    if(diff_bv & PDPOD_ACTION)
    {
        if(bv & PDPOD_ACTION)
        {
            SEND_TO_CORE("b;\n");

            if(widgets == 0)
                action_on = true;
        }
        else
        {
            if(widgets == 0)
                action_on = false;
        }
    }

    /* Check play button. */
    if(diff_bv & PDPOD_PLAY)
    {
        if(bv & PDPOD_PLAY)
        {
            /* Action + play = pause. */
            if(action_on)
            {
                /* Switch paused state. */
                paused = !paused;
                SEND_TO_CORE(paused ? "p 1;\n" : "p 0;\n");
            }

            if(!action_on && !paused)
                SEND_TO_CORE("d 1;\n");

            if(widgets == 0)
                play_on = true;
        }
        else
        {
            if(!action_on && !paused)
                SEND_TO_CORE("d 0;\n");

            if(widgets == 0)
                play_on = false;
        }
    }

    /* Check rewind (previous) button. */
    if(diff_bv & PDPOD_PREVIOUS)
    {
        if(bv & PDPOD_PREVIOUS)
        {
            if(!paused)
                SEND_TO_CORE("w 1;\n");

            if(widgets == 0)
                previous_on = true;
        }
        else
        {
            if(!paused)
                SEND_TO_CORE("w 0;\n");

            if(widgets == 0)
                previous_on = false;
        }
    }

    /* Check forward (next) button. */
    if(diff_bv & PDPOD_NEXT)
    {
        if(bv & PDPOD_NEXT)
        {
            if(!paused)
                SEND_TO_CORE("f 1;\n");

            if(widgets == 0)
                next_on = true;
        }
        else
        {
            if(!paused)
                SEND_TO_CORE("f 0;\n");

            if(widgets == 0)
                next_on = false;
        }
    }

    /* Check menu (select) button. */
    if(diff_bv & PDPOD_MENU)
    {
        if(bv & PDPOD_MENU)
        {
            if(!action_on && !paused)
                SEND_TO_CORE("m 1;\n");

            if(widgets == 0)
                menu_on = true;
        }
        else
        {
            if(!action_on && !paused)
                SEND_TO_CORE("m 0;\n");

            if(widgets == 0)
                menu_on = false;
        }
    }

    /* Check scroll right (or up) button. */
    if(diff_bv & PDPOD_WHEELRIGHT)
    {
        if(bv & PDPOD_WHEELRIGHT)
        {
            SEND_TO_CORE(action_on ? "r 10;\n" : "r 1;\n");
        }
    }

    /* Check scroll left (or down) button. */
    if(diff_bv & PDPOD_WHEELLEFT)
    {
        if(bv & PDPOD_WHEELLEFT)
        {
            SEND_TO_CORE(action_on ? "l 10;\n" : "l 1;\n");
        }
    }

    /* Backup button value. */
    last_bv = bv;

    /* GUI has to be updated. */
    return true;
}

/* Emulate timer for widgets which use time-out. */
bool pd_gui_apply_timeouts(struct pd_widget* wg, unsigned int widgets)
{
    unsigned int i;
    bool result = false;

    for(i = 0; i < widgets; wg++, i++)
    {
        if(wg->id == PD_BANG)
        {
            if(wg->timeout > 0)
            {
                /* Decrement timeout value. */
                wg->timeout--;

                /* If zero reached, clear value. */
                if(wg->timeout == 0)
                {
                    wg->value = 0;
                    result = true;
                }
            }
        }
    }

    return result;
}

/* Parse and apply message from the Pure Data core. */
void pd_gui_parse_message(struct datagram* dg,
                          struct pd_widget* wg,
                          unsigned int widgets)
{
    unsigned int i;
    char* saveptr;
    char* object = NULL;
    char* argument = NULL;
    float argvalue = 0;

    object = strtok_r(dg->data, " ", &saveptr);
    argument = strtok_r(NULL, " ;\n", &saveptr);

    if(argument != NULL)
        argvalue = atof(argument);

    for(i = 0; i < widgets; wg++, i++)
    {
        if(strncmp(object, wg->name, strlen(wg->name)) == 0)
        {
            /* If object not a number, set boundaries. */
            if(wg->id != PD_NUMBER)
            {
                if(argvalue > wg->max)
                    argvalue = wg->max;
                else if(argvalue < wg->min)
                    argvalue = wg->min;
            }

            /* Set value. */
            if(wg->id == PD_BANG)
            {
                wg->value = 1;
                wg->timeout = HZ / 10;
            }
            else
            {
                wg->value = argvalue;
            }
        }
    }
}
