#include #include #include #include #include extern const char *__progname; #include #include #include #include #include #include "xte.h" #include "builtins.h" #include "blocktype.h" #define BLOCK_APPROACH 30 typedef enum { PIN_INPUT = 1, PIN_OUTPUT, } PINTYPE; typedef enum { DROP_TEST = 1, DROP_DROP, DROP_FAIL, } DROPOP; typedef enum { STAT_GOOD = 1, STAT_BADC, STAT_RANGE, } CHKSTATUS; typedef struct iopin IOPIN; typedef struct block BLOCK; typedef struct signal SIGNAL; typedef struct menu MENU; typedef struct menuitem MENUITEM; typedef struct inputops INPUTOPS; typedef struct chkrv CHKRV; struct chkrv { CHKSTATUS status; int ngood; } ; struct inputops { void (*keystroke)(unsigned char); #define INPUT_ARGS_KEYSTROKE \ unsigned char key __attribute__((__unused__)) void (*motion)(int, int); #define INPUT_ARGS_MOTION \ int x __attribute__((__unused__)), \ int y __attribute__((__unused__)) void (*button)(int, int, int); #define INPUT_ARGS_BUTTON \ int x __attribute__((__unused__)), \ int y __attribute__((__unused__)), \ int button __attribute__((__unused__)) } ; #define INPUTOPS_INIT(name) {\ &input_##name##_keystroke, \ &input_##name##_motion, \ &input_##name##_button } struct menu { int nitems; MENUITEM *items; int width; int height; Window win; MENU *link; unsigned int flags; #define MF_MAPPED 0x00000001 } ; #define MENU_INIT(name) {\ sizeof(menu_##name##_items) / sizeof(menu_##name##_items[0]), \ &menu_##name##_items[0] } struct menuitem { const char *text; int key; void (*impl)(int); int arg; int y; } ; struct iopin { BLOCK *block; PINTYPE pintype; int inx; } ; struct block { BLOCK *link; BLOCKTYPE *type; SIGNAL **inputs; SIGNAL **outputs; void *priv; int x; int y; Pixmap img; } ; struct signal { SIGNAL *link; IOPIN *drive; int n_driven; IOPIN **driven; } ; static XrmDatabase db; static const char *defaults = "\ *Foreground: white\n\ *Background: black\n\ *BorderColour: white\n\ *OKColour: green\n\ *BadColour: red\n\ *CursorColour: cyan\n\ *BorderWidth: 1\n\ *Font: fixed\n\ "; static char *displayname; static char *geometryspec; static char *fontname; static char *foreground; static char *background; static char *bordercstr; static char *okcstr; static char *badcstr; static char *cursorcstr; static char *borderwstr; static char *bordermstr; static char *visualstr; static int synch; static int argc; static char **argv; Display *disp; static Screen *scr; static int scrwidth; static int scrheight; static int depth; static Window rootwin; static Colormap wincmap; static int defcmap; static XVisualInfo visinfo; static INPUTOPS *input; XFontStruct *font; static int def_font; static int dynamic_visual; static XColor colour_fg; static XColor colour_bg; static XColor colour_border; static XColor colour_bad; static XColor colour_ok; static XColor colour_cursor; static int margin; static int borderwidth; static Window topwin; static Window drawwin; static Window menuwin; static Window dragwin; static Window popupwin; static Window promptwin; static Window editwin; static Pixmap editpm; static Pixmap edit_goodprompt; static Pixmap edit_badprompt; static int editpm_w; static GC wingc; static GC gc_fg; static GC gc_bg; GC gc_1bpp_0; GC gc_1bpp_1; static GC gc_drag; static GC gc_bad; static GC gc_ok; static GC gc_cursor; int font_space; int font_endspace; int font_baseline; int font_baselineskip; static int winx; static int winy; static int winw; static int winh; static Cursor nilcurs; static XTextProperty wn_prop; static XTextProperty in_prop; static XSizeHints *normal_hints; static XWMHints *wm_hints; static XClassHint *class_hints; static int (*old_ioerr)(Display *); static int (*old_err)(Display *, XErrorEvent *); int xte_junk_dir; int xte_junk_asc; int xte_junk_desc; static Window xqp_root; static Window xqp_child; static int xqp_rootx; static int xqp_rooty; #define XQP_JUNK &xqp_root, &xqp_child, &xqp_rootx, &xqp_rooty static int n_blocktypes; static BLOCKTYPE **blocktypes; static MENU *allmenus; static int menu_maxwidth; static int menu_maxheight; static MENU *curmenu; static void (*block_menu_choice)(int); static char **block_menu_texts; static void (*cancel_it)(void); static int dragging; static int drag_w; static int drag_h; static int drag_xoff; static int drag_yoff; static int drag_curok; static INPUTOPS *drag_input; static Pixmap drag_pm; static Pixmap drag_pm_r; static Pixmap drag_pm_g; static int (*drag_drop)(void *, int, int, DROPOP); static void *drag_arg; static int popped_up; //static const char *popup_prompt; //static int popup_promptlen; static int popup_promptwidth; static int popup_cx; static int popup_cy; static char *popup_buf; static int popup_bufalloc; static int popup_buflen; static int popup_curwidth; static int popup_badrange; static CHKRV (*popup_chk)(const char *); static void (*popup_done)(int); static void *popup_valp; static int popup_anybad; static INPUTOPS *popup_input; static void (*getarg_cont)(int, void *); static void *getarg_contarg; static double chk_frange_v1; static double chk_frange_v2; static BLOCK *allblocks; static SIGNAL *allsignals; // forwards static MENU menu_main; static MENU menu_block; static MENU menu_cancel; static char *deconst(const char *s) { char *rv; bcopy(&s,&rv,sizeof(rv)); return(rv); } void panic(void) { (void)*(volatile char *)0; abort(); } static void saveargv(int ac, char **av) { int i; int nc; char *abuf; argc = ac; argv = (char **) malloc((ac+1)*sizeof(char *)); nc = 1; for (i=0;i 0) { skip --; continue; } if (**av != '-') { fprintf(stderr,"%s: unrecognized argument `%s'\n",__progname,*av); errs ++; continue; } if (0) { needarg:; fprintf(stderr,"%s: %s needs a following argument\n",__progname,*av); errs ++; continue; } #define WANTARG() do { if (++skip >= ac) goto needarg; } while (0) if (!strcmp(*av,"-display")) { WANTARG(); displayname = av[skip]; continue; } if (!strcmp(*av,"-geometry")) { WANTARG(); geometryspec = av[skip]; continue; } if (!strcmp(*av,"-font") || !strcmp(*av,"-fn")) { WANTARG(); fontname = av[skip]; continue; } if (!strcmp(*av,"-foreground") || !strcmp(*av,"-fg")) { WANTARG(); foreground = av[skip]; continue; } if (!strcmp(*av,"-background") || !strcmp(*av,"-bg")) { WANTARG(); background = av[skip]; continue; } if (!strcmp(*av,"-bordercolor") || !strcmp(*av,"-bd")) { WANTARG(); bordercstr = av[skip]; continue; } if (!strcmp(*av,"-borderwidth") || !strcmp(*av,"-bw")) { WANTARG(); borderwstr = av[skip]; continue; } if (!strcmp(*av,"-bordermargin") || !strcmp(*av,"-bm")) { WANTARG(); bordermstr = av[skip]; continue; } if (!strcmp(*av,"-visual")) { WANTARG(); visualstr = av[skip]; continue; } if (!strcmp(*av,"-sync")) { synch = 1; continue; } #undef WANTARG fprintf(stderr,"%s: unrecognized option `%s'\n",__progname,*av); errs ++; } if (errs) exit(1); } static void setup_blocktype(BLOCKTYPE *bt) { n_blocktypes ++; blocktypes = realloc(blocktypes,n_blocktypes*sizeof(*blocktypes)); blocktypes[n_blocktypes-1] = bt; bt->inx = n_blocktypes - 1; } static void setup_blocktypes(void) { n_blocktypes = 0; blocktypes = 0; setup_blocktype(&block_constant); setup_blocktype(&block_amp); #if 0 setup_blocktype(&block_sum); setup_blocktype(&block_prod); setup_blocktype(&block_cmp); setup_blocktype(&block_noise); #endif setup_blocktype(&block_osc); setup_blocktype(&block_vco); setup_blocktype(&block_ddcosc); setup_blocktype(&block_genosc); #if 0 setup_blocktype(&block_envelope); setup_blocktype(&block_output); // Poisson envelope // Key #endif } static int find_best_visual(XVisualInfo *vv, int n) { int best; int i; XVisualInfo *v; best = -1; for (i=n-1;i>=0;i--) { v = vv + i; switch (v->class) { case StaticGray: case GrayScale: continue; break; } if ( (best < 0) || (vv[i].depth > vv[best].depth) || ( (vv[i].depth == vv[best].depth) && (vv[i].visual == XDefaultVisual(disp,vv[i].screen)) ) ) { best = i; } } if (best < 0) return(0); visinfo = vv[best]; switch (visinfo.class) { case StaticGray: case GrayScale: panic(); break; case PseudoColor: case DirectColor: dynamic_visual = 1; break; case StaticColor: case TrueColor: dynamic_visual = 0; break; default: printf("Chosen visual has bizarre class %d\n",(int)visinfo.class); exit(1); break; } return(1); } static void setup_visual(void) { XVisualInfo *xvi; int nvi; XVisualInfo template; int class; const char *classname; if (! visualstr) { template.screen = XScreenNumberOfScreen(scr); xvi = XGetVisualInfo(disp,VisualScreenMask,&template,&nvi); if (xvi == 0) { fprintf(stderr,"%s: no visuals found?!\n",__progname); exit(1); } if (nvi == 0) { fprintf(stderr,"%s: what? XGetVisualInfo worked but found no visuals?\n",__progname); exit(1); } if (find_best_visual(xvi,nvi)) return; fprintf(stderr,"%s: no usable visual found on this screen\n",__progname); exit(1); } else { #define FOO(c) if (!strcasecmp(visualstr,#c)) { class = c; classname = #c; } else FOO(StaticGray) FOO(StaticColor) FOO(TrueColor) FOO(GrayScale) FOO(PseudoColor) FOO(DirectColor) #undef FOO { unsigned long int id; char *cp; id = strtol(visualstr,&cp,0); if (*cp) { fprintf(stderr,"%s: %s: invalid visual option\n",__progname,visualstr); exit(1); } template.visualid = (VisualID) id; xvi = XGetVisualInfo(disp,VisualIDMask,&template,&nvi); if (xvi == 0) { fprintf(stderr,"%s: no such visual found\n",__progname); exit(1); } if (nvi == 0) { fprintf(stderr,"%s: what? XGetVisualInfo returned non-nil but zero count?\n",__progname); exit(1); } if (xvi->screen != XScreenNumberOfScreen(scr)) { fprintf(stderr,"%s: visual %s is on a different screen\n",__progname,visualstr); exit(1); } if (find_best_visual(xvi,nvi)) return; fprintf(stderr,"%s: visual %s isn't usable (need colour)\n",__progname,visualstr); exit(1); } switch (class) { case StaticGray: case GrayScale: fprintf(stderr,"%s: can't work with %s visuals\n",__progname,classname); exit(1); break; } template.class = class; template.screen = XScreenNumberOfScreen(scr); xvi = XGetVisualInfo(disp,VisualClassMask|VisualScreenMask,&template,&nvi); if (xvi == 0) { fprintf(stderr,"%s: no %s visual found on this screen\n",__progname,classname); exit(1); } if (nvi == 0) { fprintf(stderr,"%s: what? XGetVisualInfo returned non-nil but zero count?\n",__progname); exit(1); } if (find_best_visual(xvi,nvi)) return; fprintf(stderr,"%s: no usable %s visual found on this screen\n",__progname,classname); exit(1); } } static void setup_db(void) { char *str; char *home; char hostname[256]; XrmDatabase db2; db = XrmGetStringDatabase(defaults); str = XResourceManagerString(disp); if (str) { db2 = XrmGetStringDatabase(str); XrmMergeDatabases(db2,&db); } home = getenv("HOME"); if (home) { str = malloc(strlen(home)+1+10+1); sprintf(str,"%s/.Xdefaults",home); db2 = XrmGetFileDatabase(str); if (db2) { XrmMergeDatabases(db2,&db); } free(str); gethostname(&hostname[0],(sizeof(hostname)/sizeof(hostname[0]))-1); hostname[(sizeof(hostname)/sizeof(hostname[0]))-1] = '\0'; str = malloc(strlen(home)+1+11+strlen(&hostname[0])+1); sprintf(str,"%s/.Xdefaults-%s",home,&hostname[0]); db2 = XrmGetFileDatabase(str); if (db2) { XrmMergeDatabases(db2,&db); } free(str); } } static void maybeset(char **strp, char *str) { if (str && !*strp) *strp = str; } static char *get_default_value(const char *name, const char *class) { char *type; XrmValue value; if (XrmGetResource(db,name,class,&type,&value) == False) return(0); return(value.addr); } static void setup_cmap(void) { if (visinfo.visual == XDefaultVisualOfScreen(scr)) { wincmap = XDefaultColormapOfScreen(scr); defcmap = 1; } else { wincmap = XCreateColormap(disp,rootwin,visinfo.visual,AllocNone); defcmap = 0; } } static void setup_dynamic_colour(XColor *col, unsigned long int pix, char *str, int load) { if (XParseColor(disp,wincmap,str,col) == 0) { fprintf(stderr,"%s: bad colour `%s'\n",__progname,str); exit(1); } col->pixel = pix; col->flags = DoRed | DoGreen | DoBlue; if (load) XStoreColor(disp,wincmap,col); } static void setup_static_colour(XColor *col, char *str) { if (XParseColor(disp,wincmap,str,col) == 0) { fprintf(stderr,"%s: bad colour `%s'\n",__progname,str); exit(1); } while (1) { if (XAllocColor(disp,wincmap,col) == 0) { if (! defcmap) { fprintf(stderr,"%s: can't allocate colormap cell for color `%s'\n",__progname,str); exit(1); } wincmap = XCopyColormapAndFree(disp,wincmap); defcmap = 0; continue; } break; } } static void setup_colours(void) { unsigned long int pixels[5]; if (dynamic_visual) { XAllocColorCells(disp,wincmap,False,0,0,&pixels[0],5); setup_dynamic_colour(&colour_fg,pixels[0],foreground,1); setup_dynamic_colour(&colour_bg,pixels[1],background,1); setup_dynamic_colour(&colour_border,pixels[2],bordercstr,1); setup_dynamic_colour(&colour_bad,pixels[3],badcstr,0); setup_dynamic_colour(&colour_ok,pixels[3],okcstr,0); setup_dynamic_colour(&colour_cursor,pixels[4],cursorcstr,0); } else { setup_static_colour(&colour_fg,foreground); setup_static_colour(&colour_bg,background); setup_static_colour(&colour_border,bordercstr); setup_static_colour(&colour_bad,badcstr); setup_static_colour(&colour_ok,okcstr); setup_static_colour(&colour_cursor,cursorcstr); } } static void setup_numbers(void) { if (bordermstr) margin = atoi(bordermstr); if (borderwstr) borderwidth = atoi(borderwstr); } static void setup_font(void) { unsigned long int fontprop; font = XLoadQueryFont(disp,fontname); if (font == 0) { font = XQueryFont(disp,XGContextFromGC(wingc)); if (font == 0) { fprintf(stderr,"%s: can't query the server default font, sorry\n",__progname); exit(1); } fprintf(stderr,"%s: can't load font %s, using server default\n",__progname,fontname); def_font = 1; } else { def_font = 0; } if (XGetFontProperty(font,XA_NORM_SPACE,&fontprop)) { font_space = fontprop; } else { font_space = XTextWidth(font," ",1); } if (XGetFontProperty(font,XA_END_SPACE,&fontprop)) { font_endspace = fontprop; } else { font_endspace = 1.5 * font_space; } font_baseline = font->ascent; font_baselineskip = font->ascent + font->descent; } static void create_menu_windows(void) { MENU *m; unsigned long int attrmask; XSetWindowAttributes attr; for (m=allmenus;m;m=m->link) { attrmask = 0; attr.background_pixel = colour_bg.pixel; attrmask |= CWBackPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = ExposureMask; attrmask |= CWEventMask; m->win = XCreateWindow(disp,menuwin,0,0,menu_maxwidth,menu_maxheight,0,depth,InputOutput,visinfo.visual,attrmask,&attr); } } static void setup_windows(void) { int x; int y; int w; int h; int bits; unsigned long int attrmask; XSetWindowAttributes attr; unsigned long int gcvalmask; XGCValues gcval; w = (scrwidth * 2) / 3; h = (scrheight * 2) / 3; x = (scrwidth - w) / 2; y = (scrheight - h) / 2; bits = XParseGeometry(geometryspec,&x,&y,&w,&h); if (bits & XNegative) x = scrwidth + x - w; if (bits & YNegative) y = scrheight + y - h; attrmask = 0; attr.background_pixel = colour_bg.pixel; attrmask |= CWBackPixel; attr.border_pixel = colour_border.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | PointerMotionHintMask | StructureNotifyMask; attrmask |= CWEventMask; attr.colormap = wincmap; attrmask |= CWColormap; winx = x; winy = y; winw = w; winh = h; w -= 2 * borderwidth; h -= 2 * borderwidth; topwin = XCreateWindow(disp,rootwin,x,y,w,h,borderwidth,depth,InputOutput,visinfo.visual,attrmask,&attr); wn_prop.value = (unsigned char *) deconst("synth"); wn_prop.encoding = XA_STRING; wn_prop.format = 8; wn_prop.nitems = strlen(wn_prop.value); in_prop.value = (unsigned char *) deconst("synth"); in_prop.encoding = XA_STRING; in_prop.format = 8; in_prop.nitems = strlen(in_prop.value); normal_hints = XAllocSizeHints(); normal_hints->flags = PMinSize | PResizeInc; normal_hints->x = x; normal_hints->y = y; normal_hints->flags |= (bits & (XValue|YValue)) ? USPosition : PPosition; normal_hints->width = w; normal_hints->height = h; normal_hints->flags |= (bits & (WidthValue|HeightValue)) ? USSize : PSize; normal_hints->min_width = w; normal_hints->min_height = h; normal_hints->width_inc = 1; normal_hints->height_inc = 1; wm_hints = XAllocWMHints(); wm_hints->flags = InputHint; wm_hints->input = False; class_hints = XAllocClassHint(); class_hints->res_name = deconst("synth"); class_hints->res_class = deconst("Sound"); XSetWMProperties(disp,topwin,&wn_prop,&in_prop,argv,argc,normal_hints,wm_hints,class_hints); gcvalmask = 0; if (! def_font) { gcval.font = font->fid; gcvalmask |= GCFont; } gcval.foreground = colour_fg.pixel; gcvalmask |= GCForeground; gcval.background = colour_bg.pixel; gcvalmask |= GCBackground; wingc = XCreateGC(disp,topwin,gcvalmask,&gcval); attrmask = 0; attr.background_pixel = colour_bg.pixel; attrmask |= CWBackPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = ExposureMask; attrmask |= CWEventMask; drawwin = XCreateWindow(disp,topwin,0,0,32767,32767,0,depth,InputOutput,visinfo.visual,attrmask,&attr); XMapWindow(disp,drawwin); attrmask = 0; attr.background_pixel = colour_bg.pixel; attrmask |= CWBackPixel; attr.border_pixel = colour_border.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = ExposureMask; attrmask |= CWEventMask; menuwin = XCreateWindow(disp,topwin,-1,-1,menu_maxwidth+2,menu_maxheight+2,1,depth,InputOutput,visinfo.visual,attrmask,&attr); XMapWindow(disp,menuwin); create_menu_windows(); attrmask = 0; attr.background_pixel = colour_bg.pixel; attrmask |= CWBackPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; dragwin = XCreateWindow(disp,drawwin,0,0,1,1,0,depth,InputOutput,visinfo.visual,attrmask,&attr); attrmask = 0; attr.background_pixel = colour_bg.pixel; attrmask |= CWBackPixel; attr.border_pixel = colour_border.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; popupwin = XCreateWindow(disp,drawwin,0,0,1,1,1,depth,InputOutput,visinfo.visual,attrmask,&attr); attrmask = 0; attr.background_pixel = colour_bg.pixel; attrmask |= CWBackPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.bit_gravity = StaticGravity; attrmask |= CWBitGravity; attr.win_gravity = StaticGravity; attrmask |= CWWinGravity; promptwin = XCreateWindow(disp,popupwin,0,0,1,1,0,depth,InputOutput,visinfo.visual,attrmask,&attr); XMapWindow(disp,promptwin); attrmask = 0; attr.background_pixel = colour_bg.pixel; attrmask |= CWBackPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; attr.event_mask = ExposureMask; attrmask |= CWEventMask; attr.bit_gravity = StaticGravity; attrmask |= CWBitGravity; attr.win_gravity = StaticGravity; attrmask |= CWWinGravity; editwin = XCreateWindow(disp,popupwin,0,0,1,1,0,depth,InputOutput,visinfo.visual,attrmask,&attr); editpm = XCreatePixmap(disp,popupwin,1,font_baselineskip,depth); editpm_w = 1; XMapWindow(disp,editwin); XMapRaised(disp,topwin); } static GC setup_gc(Drawable d, unsigned long int pix) { unsigned long int gcvalmask; XGCValues gcval; gcvalmask = 0; if (! def_font) { gcval.font = font->fid; gcvalmask |= GCFont; } gcval.foreground = pix; gcvalmask |= GCForeground; gcval.background = colour_bg.pixel; gcvalmask |= GCBackground; return(XCreateGC(disp,d,gcvalmask,&gcval)); } static void setup_gcs(void) { Pixmap pm; gc_fg = setup_gc(topwin,colour_fg.pixel); gc_bg = setup_gc(topwin,colour_bg.pixel); pm = XCreatePixmap(disp,topwin,1,1,1); gc_1bpp_0 = setup_gc(pm,0); gc_1bpp_1 = setup_gc(pm,1); XFreePixmap(disp,pm); if (dynamic_visual) { gc_drag = setup_gc(topwin,colour_bad.pixel); } else { gc_bad = setup_gc(topwin,colour_bad.pixel); gc_ok = setup_gc(topwin,colour_ok.pixel); } gc_cursor = setup_gc(topwin,colour_cursor.pixel); } static void setup_cursors(void) { Pixmap pm; pm = XCreatePixmap(disp,topwin,1,1,1); XDrawPoint(disp,pm,gc_1bpp_0,0,0); nilcurs = XCreatePixmapCursor(disp,pm,pm,&colour_fg,&colour_bg,0,0); XFreePixmap(disp,pm); XDefineCursor(disp,dragwin,nilcurs); XDefineCursor(disp,popupwin,nilcurs); } static void size_blocktypes(void) { int i; BLOCKTYPE *bt; for (i=n_blocktypes-1;i>=0;i--) { bt = blocktypes[i]; bt->size = (*bt->compute_size)(); } } static int ioerror_handler(Display *d) { return((*old_ioerr)(d)); } static int error_handler(Display *d, XErrorEvent *e) { return((*old_err)(d,e)); } static void setup_error(void) { old_err = XSetErrorHandler(error_handler); old_ioerr = XSetIOErrorHandler(ioerror_handler); } static void redraw_menu(MENU *m) { int inx; MENUITEM *i; for (inx=m->nitems-1;inx>=0;inx--) { i = &m->items[inx]; XDrawString(disp,m->win,gc_fg,font_space,i->y,i->text,strlen(i->text)); } } static void redraw_block(int ex, int ey, int ew, int eh, int bx, int by, int bw, int bh, Pixmap bimg) { int xo; int yo; xo = 0; yo = 0; if (ex < bx) { ew -= bx - ex; if (ew < 1) return; ex = bx; } else if (bx < ex) { xo = ex - bx; bw -= xo; if (bw < 1) return; bx = ex; } if (ey < by) { eh -= by - ey; if (eh < 1) return; ey = by; } else if (by < ey) { yo = ey - by; bh -= yo; if (bh < 1) return; by = ey; } if (ew > bw) ew = bw; if (eh > bh) eh = bh; XCopyPlane(disp,bimg,drawwin,gc_fg,xo,yo,ew,eh,ex,ey,1); } static void redraw_edit(void) { XCopyArea(disp,editpm,editwin,gc_fg,0,0,popup_curwidth,font_baselineskip,0,0); } static void handle_expose(XExposeEvent *e) { MENU *m; BLOCK *b; if (e->window == drawwin) { for (b=allblocks;b;b=b->link) { redraw_block(e->x,e->y,e->width,e->height,b->x,b->y,b->type->size.w,b->type->size.h,b->img); } return; } if (e->window == editwin) { if (e->count == 0) redraw_edit(); return; } for (m=allmenus;m;m=m->link) { if (e->window == m->win) { if (e->count == 0) redraw_menu(m); return; } } } static void handle_button(XButtonEvent *e) { if (e->window != topwin) return; (*input->button)(e->x,e->y,e->button); } static void handle_key(XKeyEvent *e, int downp) { char str[64]; int sl; if (! downp) return; sl = XLookupString(e,&str[0],sizeof(str),0,0); if (sl < 1) return; (*input->keystroke)(str[0]); } static void motion_query(void) { int winx; int winy; unsigned int mask; switch (XQueryPointer(disp,topwin,XQP_JUNK,&winx,&winy,&mask)) { case False: // Left the screen! Do nothing. return; break; case True: break; default: fprintf(stderr,"XQueryPointer returned neither True nor False\n"); exit(1); break; } (*input->motion)(winx,winy); } /* * There is a problem here. * * If we get a hint event, we want to ignore all non-hint events * between the hint event and the response carrying the XQueryPointer * response data. The trouble is, Xlib gives us no way to do this. * Not even the X*IfEvent functions help, because a single read() * could return (a) events before the XQueryPointer response, (b) that * response, and (c) events after it, all in the same blob, with no * API that lets us tell the difference between (a) and (c) (and, * indeed, once the response is consumed there may be no difference * left). * * Thus, this is unfixable (well, short of reimplementing Xlib). So, * we ignore the issue and assume that it's "good enough" to just call * XQueryPointer in response to a hint event and ignore the * possibility of non-hint events between the hint event and the * XQueryPointer response. */ static void handle_motion(XMotionEvent *e) { if (e->window != topwin) return; if (e->is_hint) { motion_query(); } else { (*input->motion)(e->x,e->y); } } static void handle_event(XEvent *e) { switch (e->type) { default: break; case Expose: /* XExposeEvent - xexpose */ handle_expose(&e->xexpose); break; case KeyPress: /* XKeyPressedEvent - XKeyEvent - xkey */ handle_key(&e->xkey,1); break; case KeyRelease: /* XKeyReleasedEvent - XKeyEvent - xkey */ handle_key(&e->xkey,0); break; case MotionNotify: /* XPointerMovedEvent - XMotionEvent - xmotion */ handle_motion(&e->xmotion); break; case ButtonPress: /* XButtonPressedEvent - XButtonEvent - xbutton */ handle_button(&e->xbutton); break; } } static void run(void) { XEvent e; while (1) { XNextEvent(disp,&e); handle_event(&e); } } static void set_curmenu(MENU *m) { if (! (m->flags & MF_MAPPED)) { XMapWindow(disp,m->win); m->flags |= MF_MAPPED; } XRaiseWindow(disp,m->win); curmenu = m; } static void stop_dragging(void) { dragging = 0; XUnmapWindow(disp,dragwin); input = drag_input; } static void input_drag_keystroke(INPUT_ARGS_KEYSTROKE) { if (key != '\e') return; (*drag_drop)(drag_arg,0,0,DROP_FAIL); stop_dragging(); } static void input_drag_motion(INPUT_ARGS_MOTION) { int ok; x -= drag_xoff; y -= drag_yoff; XMoveWindow(disp,dragwin,x,y); ok = (*drag_drop)(drag_arg,x,y,DROP_TEST); if (ok != drag_curok) { if (dynamic_visual) { XStoreColor(disp,wincmap,ok?&colour_ok:&colour_bad); } else { XSetWindowBackgroundPixmap(disp,dragwin,ok?drag_pm_g:drag_pm_r); XClearWindow(disp,dragwin); } drag_curok = ok; } } static void input_drag_button(INPUT_ARGS_BUTTON) { x = x - drag_xoff; y = y - drag_yoff; if ((*drag_drop)(drag_arg,x,y,DROP_DROP)) { stop_dragging(); return; } XBell(disp,0); } static INPUTOPS input_drag = INPUTOPS_INIT(drag); static void start_dragging(int w, int h, void (*render)(void *, Drawable, GC, GC), int (*drop)(void *, int, int, DROPOP), void *arg) { if (dragging) panic(); if ((w != drag_w) || (h != drag_h)) { if (drag_pm != None) XFreePixmap(disp,drag_pm); if (drag_pm_r != None) XFreePixmap(disp,drag_pm_r); if (drag_pm_g != None) XFreePixmap(disp,drag_pm_g); drag_pm = None; drag_pm_r = None; drag_pm_g = None; drag_w = w; drag_h = h; XResizeWindow(disp,dragwin,w,h); } if (drag_pm == None) drag_pm = XCreatePixmap(disp,rootwin,w,h,1); if (drag_pm_r == None) drag_pm_r = XCreatePixmap(disp,rootwin,w,h,depth); if (!dynamic_visual && (drag_pm_g == None)) drag_pm_g = XCreatePixmap(disp,rootwin,w,h,depth); (*render)(arg,(Drawable)drag_pm,gc_1bpp_0,gc_1bpp_1); if (dynamic_visual) { XCopyPlane(disp,drag_pm,drag_pm_r,gc_drag,0,0,w,h,0,0,1); XSetWindowBackgroundPixmap(disp,dragwin,drag_pm_r); XClearWindow(disp,dragwin); } else { XCopyPlane(disp,drag_pm,drag_pm_r,gc_bad,0,0,w,h,0,0,1); XCopyPlane(disp,drag_pm,drag_pm_g,gc_ok,0,0,w,h,0,0,1); } XMapWindow(disp,dragwin); drag_xoff = w / 2; drag_yoff = h / 2; drag_drop = drop; drag_arg = arg; drag_curok = -1; dragging = 1; drag_input = input; input = &input_drag; motion_query(); } static void menu_impl_main_Q(int arg __attribute__((__unused__))) { exit(0); } static void block_menu_impl(int inx) { (*block_menu_choice)(inx); } static void menu_blank_item(MENUITEM *i) { i->text = ""; i->key = -1; i->impl = 0; } static void create_block_menu(void) { int inx; MENUITEM *i; char *txt; menu_block.nitems = n_blocktypes + 2; menu_block.items = malloc(menu_block.nitems*sizeof(*menu_block.items)); block_menu_texts = malloc(n_blocktypes*sizeof(*block_menu_texts)); for (inx=0;inxkey = 'a' + inx; asprintf(&txt,"%c %s",i->key,blocktypes[inx]->name); block_menu_texts[inx] = txt; i->text = txt; i->impl = &block_menu_impl; i->arg = inx; } menu_blank_item(&menu_block.items[n_blocktypes]); i = &menu_block.items[n_blocktypes+1]; i->text = "ESC cancel"; i->key = '\e'; i->impl = &block_menu_impl; i->arg = -1; } static void recreate_block_menu(void) { int inx; for (inx=menu_block.nitems-3;inx>=0;inx--) free(block_menu_texts[inx]); free(menu_block.items); free(block_menu_texts); create_block_menu(); } static void pick_block_type(void (*chosen)(int)) { block_menu_choice = chosen; set_curmenu(&menu_block); } static void main_n_err(BLOCK *b) { free(b); XBell(disp,0); set_curmenu(&menu_main); } static void main_n_block_drag_cancel(void) { (*drag_drop)(drag_arg,0,0,DROP_FAIL); stop_dragging(); } static void render_block(void *bv, Drawable d, GC gc1, GC gc2) { BLOCK *b; b = bv; (*b->type->render)(b->priv,d,gc1,gc2); } static int block_collides(int x, int y, int w, int h) { BLOCK *b; for (b=allblocks;b;b=b->link) { if ( (x > b->x+b->type->size.w+BLOCK_APPROACH) || (x+w+BLOCK_APPROACH < b->x) || (y > b->y+b->type->size.h+BLOCK_APPROACH) || (y+h+BLOCK_APPROACH < b->y) ) continue; return(1); } return(0); } static int main_n_block_drop(void *bv, int x, int y, DROPOP op) { BLOCK *b; b = bv; if (op == DROP_FAIL) { (*b->type->destroy)(b->priv); free(b->inputs); free(b->outputs); free(b); set_curmenu(&menu_main); return(0); } if (block_collides(x,y,b->type->size.w,b->type->size.h)) return(0); if (op == DROP_DROP) { b->link = allblocks; allblocks = b; b->x = x; b->y = y; XCopyPlane(disp,drag_pm,drawwin,gc_fg,0,0,b->type->size.w,b->type->size.h,x,y,1); b->img = drag_pm; drag_pm = None; set_curmenu(&menu_main); } return(1); } static void main_n_setup_done(void *p, void *bv) { BLOCK *b; if (! p) { main_n_err(bv); return; } b = bv; b->priv = p; b->inputs = b->type->n_in ? malloc(b->type->n_in*sizeof(*b->inputs)) : 0; b->outputs = b->type->n_out ? malloc(b->type->n_out*sizeof(*b->outputs)) : 0; set_curmenu(&menu_cancel); cancel_it = &main_n_block_drag_cancel; start_dragging(b->type->size.w,b->type->size.h,&render_block,&main_n_block_drop,b); } static void main_n_got_type(int choice) { BLOCK *b; printf("n_block_type: choice = %d\n",choice); if ((choice < 0) || (choice >= n_blocktypes)) { main_n_err(0); return; } printf("creating block type %d (%s)\n",choice,blocktypes[choice]->name); b = malloc(sizeof(BLOCK)); b->type = blocktypes[choice]; (*b->type->setup)(&main_n_setup_done,b); } static void menu_impl_main_n(int arg __attribute__((__unused__))) { if (menu_block.nitems != n_blocktypes+2) recreate_block_menu(); pick_block_type(&main_n_got_type); } static MENUITEM menu_main_items[] = { { "Q quit", 'Q', &menu_impl_main_Q }, { "n new block", 'n', &menu_impl_main_n } }; static MENU menu_main = MENU_INIT(main); static MENU menu_block; static void menu_impl_cancel(int arg __attribute__((__unused__))) { (*cancel_it)(); } static MENUITEM menu_cancel_items[] = { { "ESC cancel", '\e', &menu_impl_cancel } }; static MENU menu_cancel = MENU_INIT(cancel); static void setup_menu(MENU *m) { int ix; MENUITEM *i; int y; XCharStruct cs; int w; w = 0; y = font_baseline; for (ix=0;ixnitems;ix++) { i = &m->items[ix]; i->y = y; XTextExtents(font,i->text,strlen(i->text),XTE_JUNK,&cs); if (cs.width > w) w = cs.width; y += font_baselineskip; } w += 2 * font_space; m->width = w; if (w > menu_maxwidth) menu_maxwidth = w; m->height = m->nitems * font_baselineskip; if (m->height > menu_maxheight) menu_maxheight = m->height; m->flags = 0; m->link = allmenus; allmenus = m; } static void setup_menus(void) { create_block_menu(); menu_maxwidth = 0; menu_maxheight = 0; allmenus = 0; setup_menu(&menu_main); setup_menu(&menu_block); setup_menu(&menu_cancel); } static void input_menu_keystroke(INPUT_ARGS_KEYSTROKE) { int i; for (i=curmenu->nitems-1;i>=0;i--) { if (curmenu->items[i].key == key) { (*curmenu->items[i].impl)(curmenu->items[i].arg); return; } } XBell(disp,0); } static void input_menu_motion(INPUT_ARGS_MOTION) { } static void input_menu_button(INPUT_ARGS_BUTTON) { } static INPUTOPS input_menu = INPUTOPS_INIT(menu); static void init_input(void) { input = &input_menu; } static void init_menu(void) { set_curmenu(&menu_main); } static void init_drag(void) { dragging = 0; drag_w = -1; drag_h = -1; drag_pm = None; drag_pm_r = None; drag_pm_g = None; } static void init_objects(void) { allblocks = 0; allsignals = 0; } static void init_popup(void) { popped_up = 0; popup_buf = 0; popup_bufalloc = 0; } static CHKRV popup_chk_signal(const char *s) { char *ep; double d; d = strtod(s,&ep); if (ep == s) return((CHKRV){.status=STAT_BADC,.ngood=0}); if (*ep) return((CHKRV){.status=STAT_BADC,.ngood=ep-s}); if ((d < -1) || (d > 1)) return((CHKRV){.status=STAT_RANGE,.ngood=ep-s}); *(double *)popup_valp = d; return((CHKRV){.status=STAT_GOOD,.ngood=ep-s}); } static CHKRV popup_chk_amp(const char *s) { char *ep; double d; d = strtod(s,&ep); if (ep == s) return((CHKRV){.status=STAT_BADC,.ngood=0}); if (*ep) return((CHKRV){.status=STAT_BADC,.ngood=ep-s}); *(double *)popup_valp = d; return((CHKRV){.status=STAT_GOOD,.ngood=ep-s}); } static CHKRV popup_chk_freq(const char *s) { char *ep; double d; d = strtod(s,&ep); if (ep == s) return((CHKRV){.status=STAT_BADC,.ngood=0}); if (*ep) return((CHKRV){.status=STAT_BADC,.ngood=ep-s}); if ((d < 1e-12) || (d > 1e9)) return((CHKRV){.status=STAT_RANGE,.ngood=ep-s}); *(double *)popup_valp = d; return((CHKRV){.status=STAT_GOOD,.ngood=ep-s}); } static CHKRV popup_chk_frange(const char *s) { char *ep; double d; d = strtod(s,&ep); if (ep == s) return((CHKRV){.status=STAT_BADC,.ngood=0}); if (*ep) return((CHKRV){.status=STAT_BADC,.ngood=ep-s}); if ((d < chk_frange_v1) || (d > chk_frange_v2)) return((CHKRV){.status=STAT_RANGE,.ngood=ep-s}); *(double *)popup_valp = d; return((CHKRV){.status=STAT_GOOD,.ngood=ep-s}); } static void popup_room(int n) { if (n > popup_bufalloc) popup_buf = realloc(popup_buf,popup_bufalloc=n+8); } static void changed_popup(void) { XCharStruct csgood; XCharStruct csbad; int w; int h; int w_l; int w_r; int h_t; int h_b; CHKRV chk; int x; int goodc; int badrange; popup_room(popup_buflen+1); popup_buf[popup_buflen] = '\0'; chk = (*popup_chk)(popup_buf); goodc = chk.ngood; switch (chk.status) { case STAT_BADC: case STAT_GOOD: badrange = 0; break; case STAT_RANGE: badrange = 1; break; } if (badrange != popup_badrange) { if (edit_badprompt == None) panic(); popup_badrange = badrange; XSetWindowBackgroundPixmap(disp,promptwin,badrange?edit_badprompt:edit_goodprompt); XClearWindow(disp,promptwin); } XTextExtents(font,popup_buf,goodc,XTE_JUNK,&csgood); XTextExtents(font,popup_buf+goodc,popup_buflen-goodc,XTE_JUNK,&csbad); popup_anybad = popup_buflen > goodc; popup_curwidth = csgood.width + csbad.width + 3; w = ((popup_curwidth > popup_promptwidth) ? popup_curwidth : popup_promptwidth) + (2 * font_space); h = 3 * font_baselineskip; w_l = w / 2; w_r = w - w_l; h_t = h / 2; h_b = h - h_t; if (w >= winw) { popup_cx = winw / 2; } else if (w_l > popup_cx) { popup_cx = w_l; } else if (popup_cx > winw-w_r) { popup_cx = winw - w_r; } if (h >= winh) { popup_cy = winh / 2; } else if (h_t > popup_cy) { popup_cy = h_t; } else if (popup_cy > winh-h_b) { popup_cy = winh - h_b; } if (popup_curwidth > editpm_w) { XFreePixmap(disp,editpm); editpm_w = popup_curwidth; editpm = XCreatePixmap(disp,editwin,popup_curwidth,font_baselineskip,depth); } XFillRectangle(disp,editpm,gc_bg,0,0,popup_curwidth,font_baselineskip); x = 0; if (goodc > 0) { XDrawString(disp,editpm,gc_fg,0,font_baseline,popup_buf,goodc); x += csgood.width; } if (popup_buflen > goodc) { XDrawString(disp,editpm,gc_bad,x,font_baseline,popup_buf+goodc,popup_buflen-goodc); x += csbad.width; } XDrawLine(disp,editpm,gc_cursor,x+1,0,x+1,font_baselineskip); XMoveResizeWindow(disp,popupwin,popup_cx-w_l-1,popup_cy-h_t-1,w,h); XMoveWindow(disp,promptwin,(w-popup_promptwidth)/2,font_baselineskip/2); XMoveResizeWindow(disp,editwin,(w-popup_curwidth)/2,(font_baselineskip/2)+font_baselineskip,popup_curwidth,font_baselineskip); redraw_edit(); } static void begin_popup(void) { int x; int y; unsigned int mask; switch (XQueryPointer(disp,topwin,XQP_JUNK,&x,&y,&mask)) { case False: x = winw / 2; y = winh / 2; break; case True: break; default: fprintf(stderr,"XQueryPointer returned neither True nor False\n"); exit(1); break; } popup_cx = x; popup_cy = y; changed_popup(); } static void popup_popdown(void) { XUnmapWindow(disp,popupwin); XFreePixmap(disp,edit_goodprompt); if (edit_badprompt != None) XFreePixmap(disp,edit_badprompt); input = popup_input; } static void input_popup_keystroke(INPUT_ARGS_KEYSTROKE) { switch (key) { case '\a': XBell(disp,0); popup_popdown(); (*popup_done)(0); break; case '\r': case '\n': if (popup_anybad || popup_badrange) { XBell(disp,0); } else { popup_popdown(); (*popup_done)(1); } break; case '\b': case '\177': if (popup_buflen > 0) { popup_buflen --; changed_popup(); } break; case ' ' ... '~': popup_room(popup_buflen+1); popup_buf[popup_buflen++] = key; changed_popup(); break; default: XBell(disp,0); break; } } static void input_popup_motion(INPUT_ARGS_MOTION) { } static void input_popup_button(INPUT_ARGS_BUTTON) { } static INPUTOPS input_popup = INPUTOPS_INIT(popup); static void popup_edit(const char *prompt, const char *rangestr, CHKRV (*chk)(const char *), void (*done)(int)) { XCharStruct csprompt; XCharStruct csrange; int promptlen; int rangelen; promptlen = strlen(prompt); XTextExtents(font,prompt,promptlen,XTE_JUNK,&csprompt); if (rangestr) { rangelen = strlen(rangestr); XTextExtents(font,rangestr,rangelen,XTE_JUNK,&csrange); popup_promptwidth = csprompt.width + font_space + csrange.width; if (popup_promptwidth < 1) popup_promptwidth = 1; } else { popup_promptwidth = csprompt.width; if (popup_promptwidth < 1) popup_promptwidth = 1; } edit_goodprompt = XCreatePixmap(disp,topwin,popup_promptwidth,font_baselineskip,depth); XFillRectangle(disp,edit_goodprompt,gc_bg,0,0,popup_promptwidth,font_baselineskip); XDrawString(disp,edit_goodprompt,gc_fg,0,font_baseline,prompt,promptlen); if (rangestr) { edit_badprompt = XCreatePixmap(disp,topwin,popup_promptwidth,font_baselineskip,depth); XCopyArea(disp,edit_goodprompt,edit_badprompt,gc_fg,0,0,popup_promptwidth,font_baselineskip,0,0); XDrawString(disp,edit_goodprompt,gc_ok,csprompt.width+font_space,font_baseline,rangestr,rangelen); XDrawString(disp,edit_badprompt,gc_bad,csprompt.width+font_space,font_baseline,rangestr,rangelen); } else { edit_badprompt = None; } XResizeWindow(disp,promptwin,popup_promptwidth,font_baselineskip); XSetWindowBackgroundPixmap(disp,promptwin,edit_goodprompt); XClearWindow(disp,promptwin); popup_badrange = 0; popup_buflen = 0; popup_curwidth = 0; popup_chk = chk; popup_done = done; begin_popup(); XMapWindow(disp,popupwin); popup_input = input; input = &input_popup; } static void setup_getarg_cont(int ok) { (*getarg_cont)(ok,getarg_contarg); } void setup_getarg(const char *prompt, void (*cont)(int, void *), void *contarg, void *ploc, PARAMTYPE ptype, ...) { CHKRV (*chk)(const char *); const char *rangestr; static char *frange_rangestr = 0; va_list ap; switch (ptype) { case PAR_SIGNAL: chk = &popup_chk_signal; rangestr = "[-1..1]"; break; case PAR_AMP: chk = &popup_chk_amp; rangestr = 0; break; case PAR_FREQ: chk = &popup_chk_freq; rangestr = "[1pHz..1GHz]"; break; case PAR_FRANGE: va_start(ap,ptype); chk_frange_v1 = va_arg(ap,double); chk_frange_v2 = va_arg(ap,double); va_end(ap); chk = &popup_chk_frange; free(frange_rangestr); asprintf(&frange_rangestr,"[%g..%g]",chk_frange_v1,chk_frange_v2); rangestr = frange_rangestr; break; default: panic(); break; } getarg_cont = cont; getarg_contarg = contarg; popup_valp = ploc; popup_edit(prompt,rangestr,chk,&setup_getarg_cont); } int main(int, char **); int main(int ac, char **av) { saveargv(ac,av); handleargs(ac,av); setup_blocktypes(); setup_error(); disp = XOpenDisplay(displayname); if (disp == 0) { fprintf(stderr,"%s: can't open display\n",__progname); exit(1); } if (synch) XSynchronize(disp,True); scr = XDefaultScreenOfDisplay(disp); setup_visual(); scrwidth = XWidthOfScreen(scr); scrheight = XHeightOfScreen(scr); depth = visinfo.depth; rootwin = XRootWindowOfScreen(scr); setup_db(); maybeset(&geometryspec,get_default_value("synth.geometry","Sound.Geometry")); maybeset(&fontname,get_default_value("synth.font","Sound.Font")); maybeset(&foreground,get_default_value("synth.foreground","Sound.Foreground")); maybeset(&background,get_default_value("synth.background","Sound.Background")); maybeset(&bordercstr,get_default_value("synth.borderColour","Sound.BorderColour")); maybeset(&okcstr,get_default_value("synth.okColour","Sound.OKColour")); maybeset(&badcstr,get_default_value("synth.badColour","Sound.BadColour")); maybeset(&cursorcstr,get_default_value("synth.cursorColour","Sound.CursorColour")); maybeset(&borderwstr,get_default_value("synth.borderWidth","Sound.BorderWidth")); maybeset(&bordermstr,get_default_value("synth.borderMargin","Sound.BorderMargin")); setup_cmap(); setup_colours(); setup_numbers(); setup_font(); setup_menus(); setup_windows(); setup_gcs(); setup_cursors(); size_blocktypes(); init_menu(); init_drag(); init_objects(); init_popup(); init_input(); run(); return(0); }