/*************************************************************************** * __________ __ ___. * 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" /* Declare back- and foreground colors. */ #define BGCOLOR (LCD_BLACK) #define FGCOLOR (LCD_WHITE) /* 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; /* 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; /* Set back- and foreground color. */ rb->lcd_set_background(BGCOLOR); rb->lcd_set_foreground(FGCOLOR); /* Clear background. */ rb->lcd_clear_display(); /* 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; } } } }