/* FL_graph.cpp: code for drawing graphs using FLTK library Copyright (C) 2003 John ffitch This file is part of Csound. The Csound Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. Csound 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Csound; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #ifdef _MSC_VER #include #endif #include "csdl.h" #include "cwindow.h" #include "winFLTK.h" #include #define NUMOFWINDOWS (30) #define XINIT 10 /* set default window location */ #define YINIT 50 #define WIDTH 450 /* start off small - user resizes */ #define HEIGHT 150 #define BDR 5 #define BORDERW 10 /* inset from L & R edge */ #define TXHGHT 14 /* baseline offset for text */ #define MAXLSEGS 4096 /* X can only deal with so many linesegs .. */ class graph_box : public Fl_Window { void draw(); public: int curr; int last; CSOUND *csound; graph_box(void *cs, int x, int y, int w, int h, const char *l = 0) : Fl_Window(x, y, w, h, l) { curr = last = -1; csound = (CSOUND*)cs; } //void add_graph(WINDAT *wdptr); }; typedef struct { Fl_Choice *choice; Fl_Button *end; Fl_Menu_Item *menu; graph_box *graph; int graph_created; Fl_Window *form; } FLGRAPH_GLOBALS; #define ST(x) ((flgraphGlobals)->x) /*void printouts(CSOUND *csound){ csound->Message(csound, "menu object: %p\n", ST(menu)); csound->Message(csound, "form object: %p\n", ST(form)); csound->Message(csound, "graph object: %p\n", ST(graph)); csound->Message(csound, "choice object: %p\n", ST(choice)); csound->Message(csound, "globals %p\n", csound->flgraphGlobals); }*/ void flgraph_init(CSOUND *csound) { FLGRAPH_GLOBALS *flgraphGlobals = (FLGRAPH_GLOBALS *) csound->QueryGlobalVariable(csound, "FLGRAPH_GLOBALS"); if (flgraphGlobals==NULL) { csound->CreateGlobalVariable(csound, "FLGRAPH_GLOBALS", sizeof(FLGRAPH_GLOBALS)); flgraphGlobals = (FLGRAPH_GLOBALS *) csound->QueryGlobalVariable(csound, "FLGRAPH_GLOBALS"); //csound->flgraphGlobals = //(FLGRAPH_GLOBALS*) csound->Malloc(csound,sizeof(FLGRAPH_GLOBALS)); } ST(form) = (Fl_Window *) 0; ST(choice) = (Fl_Choice *) 0; ST(end) = (Fl_Button *) 0; ST(graph) = (graph_box *) 0; ST(menu) = (Fl_Menu_Item *) 0; ST(graph_created) = 0; /*ST(menu) = (Fl_Menu_Item*) csound->Calloc(csound, sizeof(Fl_Menu_Item)*(1+NUMOFWINDOWS));*/ /* VL: moved menu object to be built at each new compilation */ } /* static graph_box *graph = (graph_box *) 0; */ void graph_box::draw() { FLGRAPH_GLOBALS *flgraphGlobals = (FLGRAPH_GLOBALS *) csound->QueryGlobalVariable(csound, "FLGRAPH_GLOBALS"); Fl_Window::draw(); fl_color(0, 0, 0); fl_line_style(FL_SOLID); fl_rect(0, 0, w(), h()); if (curr >= 0) { WINDAT *win = (WINDAT*) ST(menu)[curr].user_data_; if (!win) return; MYFLT *fdata = win->fdata; int32 npts = win->npts; char *msg = win->caption; short win_x, win_y, win_h; /* window rect */ short gra_x, gra_y, gra_w, gra_h; /* graph rect is inset */ short y_axis; int lsegs, pts_pls; int pol; char string[400]; pol = win->polarity; win_x = 0; win_y = 0; /* window pixels addressed relative */ win_h = h(); /* set new width and height so we leave a 20% border around the plot */ gra_w = w() - 2*BORDERW; gra_h = h(); gra_x = win_x + BORDERW; gra_y = win_y; /* figure height of Y axis - top, middle or bottom */ if (pol == (short)BIPOL) y_axis = gra_y + (gra_h/2); else if (pol == (short)NEGPOL) y_axis = gra_y; else /* POSPOL */ y_axis = gra_y + gra_h; if (npts < MAXLSEGS) { lsegs = npts; /* one lineseg per datum */ pts_pls = 1; } else { pts_pls = npts/MAXLSEGS; if (npts%MAXLSEGS) pts_pls++; lsegs = npts/pts_pls; } fl_begin_line(); { /* take scale factors out of for-loop for faster run-time */ MYFLT x_scale = gra_w / (MYFLT) (lsegs - 1); MYFLT y_scale = gra_h / win->oabsmax; /* unipolar default */ MYFLT f, ma, mi, *fdptr = fdata; int c, i = 0, j = lsegs; if (pol == (short) BIPOL) y_scale /= 2.0; /* max data scales to h/2 */ /* put x-y pairs into a point list for XDraw */ while (j--) { int x = gra_x + (short) ((MYFLT) i++ * x_scale); int y; if (pts_pls == 1) f = *fdptr++; else { ma = mi = *fdptr++; for (c = 1; c < pts_pls; ++c) if ((f = *fdptr++) > ma) ma = f; else if ( f 0) f = ma; else if (ma > -mi) f = ma; else f = mi; } y = y_axis - (short) (f * y_scale); fl_vertex(x, y); } } fl_end_line(); /* now draw axes: y-axis is always on the left edge, x-axis height is determined by the case we're in */ fl_line(gra_x, y_axis, (gra_x + gra_w), y_axis); fl_line(gra_x, y_axis, (gra_x + gra_w), y_axis); fl_line(gra_x, gra_y, gra_x, (gra_y + gra_h)); if (win->danflag) { /* flag to add dotted divider */ fl_line_style(FL_DOT); fl_line(win_x+w()/2, win_y, win_x+w()/2, win_y+win_h); } if (pol != NEGPOL) sprintf(string, "%s %" PRIi32 " points, max %5.3f", msg, npts, win->oabsmax); else sprintf(string, "%s %" PRIi32 " points, max %5.3f", msg, npts, win->max); ST(form)->label(string); } fl_line_style(FL_SOLID); } void add_graph(CSOUND *csound, WINDAT *wdptr) { FLGRAPH_GLOBALS *flgraphGlobals = (FLGRAPH_GLOBALS *) csound->QueryGlobalVariable(csound, "FLGRAPH_GLOBALS"); WINDAT *n = (WINDAT*) malloc(sizeof(WINDAT)); int m; WINDAT *old; int replacing = 0; memcpy(n, wdptr, sizeof(WINDAT)); n->fdata = (MYFLT*) malloc(n->npts * sizeof(MYFLT)); memcpy(n->fdata, wdptr->fdata, n->npts * sizeof(MYFLT)); for (m = 0; m < NUMOFWINDOWS; m++) { // If text the same use slot if (ST(menu) != NULL) { if (ST(menu)[m].text != NULL && strlen(wdptr->caption) == 0){ if(strcmp(wdptr->caption, ST(menu)[m].text) == 0) { replacing = 1; goto replace; } } } } // Use a new slot, cycling round ST(graph)->last++; m = ST(graph)->last; if (m >= NUMOFWINDOWS) m = ST(graph)->last = 0; replace: old = (WINDAT*)ST(menu)[m].user_data_; if (old) { free((void*) old->fdata); free((void*) old); } ST(menu)[m].user_data_ = n; if (replacing == 0) { if (ST(menu)[m].text != NULL) free((void*) ST(menu)[m].text); ST(menu)[m].text = (const char*) malloc(strlen(n->caption) + 1); strcpy((char*) ST(menu)[m].text, n->caption); } // ST(graph)->curr = m; // ST(choice)->value(m); ST(graph)->curr = ST(choice)->value(); /* VL: 29.04.09 fix */ ST(graph)->redraw(); } void do_redraw(Fl_Widget *, void *cs) { CSOUND *csound = (CSOUND*)cs; FLGRAPH_GLOBALS *flgraphGlobals = (FLGRAPH_GLOBALS *) csound->QueryGlobalVariable(csound, "FLGRAPH_GLOBALS"); ST(graph)->curr = ST(choice)->value(); ST(graph)->redraw(); } void makeWindow(CSOUND *csound, char *name) { FLGRAPH_GLOBALS *flgraphGlobals = (FLGRAPH_GLOBALS *) csound->QueryGlobalVariable(csound, "FLGRAPH_GLOBALS"); if (ST(form)) return; ST(form) = new Fl_Window(WIDTH, HEIGHT, name); ST(menu) = new Fl_Menu_Item[1+NUMOFWINDOWS]; memset(ST(menu), 0, sizeof(Fl_Menu_Item)*(1+NUMOFWINDOWS)); ST(choice) = new Fl_Choice(140, 0, 140, 20, "Choose Graph"); ST(choice)->menu(ST(menu)); ST(choice)->value(0); ST(choice)->callback((Fl_Callback*) do_redraw, (void*)csound); ST(graph) = new graph_box(csound, BDR, 30 + BDR, WIDTH - 2 * BDR, HEIGHT - 30 - 2 * BDR); ST(graph)->end(); ST(end) = new Fl_Button(WIDTH - 40, 0, 35, 20, "Quit"); ST(end)->hide(); ST(form)->resizable(ST(graph)); ST(form)->end(); ST(graph_created) = 1; } void graphs_reset(CSOUND * csound){ IGN(csound); //if (csound->flgraphGlobals != NULL) // csound->Free(csound, csound->flgraphGlobals); } extern "C" { void DrawGraph_FLTK(CSOUND *csound, WINDAT *wdptr) { add_graph(csound, wdptr); csound->CheckEvents(csound); } uintptr_t MakeWindow_FLTK(CSOUND *csound, char *name) { FLGRAPH_GLOBALS *flgraphGlobals = (FLGRAPH_GLOBALS *) csound->QueryGlobalVariable(csound, "FLGRAPH_GLOBALS"); if (ST(form) == NULL) { makeWindow(csound, name); ST(form)->show(); } return (uintptr_t) ST(form); } int CsoundYield_FLTK(CSOUND *csound){ #ifndef NO_FLTK_THREADS /* nothing to do, unless no widget thread is running */ if (csound->QueryGlobalVariable(csound, "_widgets_globals") != NULL) return 1; #endif Fl_wait_locked(csound, 0.0); return 1; } void kill_graph(CSOUND *csound, uintptr_t m) { FLGRAPH_GLOBALS *flgraphGlobals = (FLGRAPH_GLOBALS *) csound->QueryGlobalVariable(csound, "FLGRAPH_GLOBALS"); for (int i = 0; i < NUMOFWINDOWS; i++) { WINDAT *n = (WINDAT*) ST(menu)[i].user_data_; if (n != NULL && ((uintptr_t) n == m ||n->windid == m)) { free(n->fdata); free(n); free((void*) ST(menu)[i].text); ST(menu)[i].user_data_ = (void*) 0; ST(menu)[i].text = (char*) 0; return; } } } int ExitGraph_FLTK(CSOUND *csound) { FLGRAPH_GLOBALS *flgraphGlobals = (FLGRAPH_GLOBALS *) csound->QueryGlobalVariable(csound, "FLGRAPH_GLOBALS"); if (flgraphGlobals == 0) { return OK; } if (ST(form) && ST(graph_created) == 1) { if (ST(form)->shown() && !(getFLTKFlags(csound) & 256)) { const char *env = csound->GetEnv(csound, "CSNOSTOP"); if (env == NULL || strcmp(env, "yes") != 0) { ST(end)->show(); // print click-Exit message in most recently active window while (ST(end)->value() == 0 && ST(form)->shown()) { Fl_wait_locked(csound, 0.03); } } } delete ST(form); ST(form) = (Fl_Window *) 0; Fl_wait_locked(csound, 0.0); ST(choice) = (Fl_Choice *) 0; ST(graph) = (graph_box *) 0; ST(end) = (Fl_Button *) 0; ST(graph_created) = 0; for (int i = 0; i < NUMOFWINDOWS; i++) { WINDAT *n = (WINDAT*) ST(menu)[i].user_data_; if (n) kill_graph(csound, (uintptr_t) ((void*) n)); } if (ST(menu)){ delete[] ST(menu); ST(menu) = (Fl_Menu_Item *) 0; } } return 0; } #define GUTTERH 20 /* space for text at top & bottom */ #define BORDERW 10 /* inset from L & R edge */ void MakeXYin_FLTK(CSOUND *csound, XYINDAT *w, MYFLT x, MYFLT y) { if (w->windid == (uintptr_t) 0) { Fl_Window *xyin = new Fl_Window(WIDTH, WIDTH, "XY input"); short win_x, win_y; short gra_x, gra_y, gra_w, gra_h; win_x = 0; win_y = 0; /* window pixels addressed relative */ Fl_lock(csound); xyin->show(); Fl_wait(csound, 0.0); Fl_unlock(csound); /* set new width and height so we leave a 20% border around the plot */ gra_w = xyin->w() - 2*BORDERW; gra_h = xyin->h() - 2*GUTTERH; gra_x = win_x + BORDERW; gra_y = win_y + GUTTERH; w->m_x = gra_x + (int) (x * (MYFLT) gra_w); w->m_y = gra_y + (int) (y * (MYFLT) gra_h); w->down = 0; Fl_lock(csound); Fl_wait(csound, 0.0); xyin->make_current(); fl_color(0, 0, 0); fl_line_style(FL_DOT); fl_line(gra_x, w->m_y, (gra_x + gra_w), w->m_y); fl_line(w->m_x, gra_y, w->m_x, (gra_y + gra_h)); Fl_unlock(csound); w->windid = (uintptr_t) xyin; } } void ReadXYin_FLTK(CSOUND *csound, XYINDAT *wdptr) { short win_x, win_y; short gra_x, gra_y, gra_w, gra_h; short m_x, m_y; Fl_Window *xwin = (Fl_Window*) wdptr->windid; Fl_lock(csound); Fl_wait(csound, 0.0); m_x = Fl::event_x(); m_y = Fl::event_y(); wdptr->down = (short) (Fl::event_button1() ? 1 : 0); Fl_unlock(csound); if (!wdptr->down) return; win_x = 0; win_y = 0; /* window pixels addressed relative */ /* set new width and height so we leave a 20% border around the plot */ gra_w = xwin->w() - 2*BORDERW; gra_h = xwin->h() - 2*GUTTERH; gra_x = win_x + BORDERW; gra_y = win_y + GUTTERH; /* clip mouse position */ if (m_x < gra_x) m_x = gra_x; else if (m_x > gra_x+gra_w) m_x = gra_x+gra_w; if (m_y < gra_y) m_y = gra_y; else if (m_y > gra_y+gra_h) m_y = gra_y+gra_h; if (m_x != wdptr->m_x || m_y != wdptr->m_y) { /* only redo if changed */ Fl_lock(csound); xwin->make_current(); /* undraw old crosshairs */ fl_color(FL_BACKGROUND_COLOR); fl_line_style(FL_SOLID); fl_line(gra_x, wdptr->m_y, (gra_x + gra_w), wdptr->m_y); fl_line(wdptr->m_x, gra_y, wdptr->m_x, (gra_y + gra_h)); // Draw new fl_color(0, 0, 0); fl_line_style(FL_SOLID); fl_line(gra_x, m_y, (gra_x + gra_w), m_y); fl_line(m_x, gra_y, m_x, (gra_y + gra_h)); Fl_unlock(csound); wdptr->m_x = m_x; wdptr->m_y = m_y; wdptr->x = ((MYFLT) m_x - gra_x) / (MYFLT) gra_w; wdptr->y = ((MYFLT) m_y - gra_y) / (MYFLT) gra_h; } } void KillXYin_FLTK(CSOUND *csound, XYINDAT *wdptr) { IGN(csound); delete ((Fl_Window*) wdptr->windid); wdptr->windid = (uintptr_t) 0; } }