#include #include #include #include #include #include extern const char *__progname; #include #include #include #include #include #include #include "xte.h" #include "builtins.h" #include "blocktype.h" #define BLOCK_APPROACH 30 #define PIN_HITBOX 5 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; typedef struct choice CHOICE; typedef struct selpin SELPIN; struct selpin { BLOCK *b; PINTYPE type; int inx; } ; struct choice { const char *str; int len; int val; } ; 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; 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); const char *keytext; 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; unsigned int flags; #define BF_DAMAGED 0x00000001 } ; 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\ *PinSelectColour: magenta\n\ *BlockSelectColour: blue\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 *pselcstr; static char *bselcstr; 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 XColor colour_sel_pin; static XColor colour_sel_block; 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; static GC gc_sel_p; static GC gc_sel_b; 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 Cursor arrowcurs; 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_tag_maxw; static int menu_text_maxw; static int menu_maxwidth; static int menu_maxheight; static int menu_tag_x; static int menu_text_x; static MENU *curmenu; static void (*block_menu_choice)(int); 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_promptheight; static int popup_cx; static int popup_cy; static char *popup_buf; static int popup_bufalloc; static int popup_buflen; static int popup_bufcurs; static int popup_curwidth; static int popup_badrange; static CHKRV (*popup_chk)(const char *); static void (*popup_done)(int); static void *popup_valp; static MENU *popup_menu; static int popup_anybad; static int popup_tabto; 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 int chk_irange_v1; static int chk_irange_v2; static int chk_nchoice; static CHOICE *chk_choices; static int chk_unique; static Pixmap pinpm; static BLOCK *allblocks; static SIGNAL *allsignals; static BLOCK *selblock; static SELPIN selpin; // forwards static MENU menu_main; static MENU menu_block; static MENU menu_cancel; #define Cisupper(x) isupper((unsigned char)(x)) #define Ctolower(x) tolower((unsigned char)(x)) static char *deconst(const char *s) { char *rv; bcopy(&s,&rv,sizeof(rv)); return(rv); } void panic(void) __attribute__((__noreturn__)); 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); setup_blocktype(&block_sum); setup_blocktype(&block_prod); setup_blocktype(&block_cmp); setup_blocktype(&block_noise); 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_delay); setup_blocktype(&block_echo); #endif setup_blocktype(&block_output); // Poisson envelope // Key } 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[7]; if (dynamic_visual) { XAllocColorCells(disp,wincmap,False,0,0,&pixels[0],6); 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); setup_dynamic_colour(&colour_sel_pin,pixels[5],pselcstr,0); setup_dynamic_colour(&colour_sel_block,pixels[6],bselcstr,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); setup_static_colour(&colour_sel_pin,pselcstr); setup_static_colour(&colour_sel_block,bselcstr); } } 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 setup_menus_X(void) { MENU *m; unsigned long int attrmask; XSetWindowAttributes attr; Pixmap pm; int inx; MENUITEM *i; char kc; for (m=allmenus;m;m=m->link) { pm = XCreatePixmap(disp,menuwin,menu_maxwidth,menu_maxheight,depth); XFillRectangle(disp,pm,gc_bg,0,0,menu_maxwidth,menu_maxheight); for (inx=m->nitems-1;inx>=0;inx--) { i = &m->items[inx]; XDrawString(disp,pm,gc_fg,menu_tag_x,i->y,i->keytext?:((kc=i->key),&kc),i->keytext?strlen(i->keytext):1); XDrawString(disp,pm,gc_fg,menu_text_x,i->y,i->text,strlen(i->text)); } attrmask = 0; attr.background_pixmap = pm; attrmask |= CWBackPixmap; attr.backing_store = NotUseful; attrmask |= CWBackingStore; m->win = XCreateWindow(disp,menuwin,0,0,menu_maxwidth,menu_maxheight,0,depth,InputOutput,visinfo.visual,attrmask,&attr); XFreePixmap(disp,pm); } } 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); 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 bgpix, unsigned long int fgpix) { unsigned long int gcvalmask; XGCValues gcval; gcvalmask = 0; if (! def_font) { gcval.font = font->fid; gcvalmask |= GCFont; } gcval.foreground = fgpix; gcvalmask |= GCForeground; gcval.background = bgpix; gcvalmask |= GCBackground; return(XCreateGC(disp,d,gcvalmask,&gcval)); } static void setup_gcs(void) { Pixmap pm; gc_fg = setup_gc(topwin,colour_bg.pixel,colour_fg.pixel); gc_bg = setup_gc(topwin,colour_bg.pixel,colour_bg.pixel); pm = XCreatePixmap(disp,topwin,1,1,1); gc_1bpp_0 = setup_gc(pm,1,0); gc_1bpp_1 = setup_gc(pm,0,1); XFreePixmap(disp,pm); if (dynamic_visual) { gc_drag = setup_gc(topwin,colour_bg.pixel,colour_bad.pixel); } else { gc_bad = setup_gc(topwin,colour_bg.pixel,colour_bad.pixel); gc_ok = setup_gc(topwin,colour_bg.pixel,colour_ok.pixel); } gc_cursor = setup_gc(topwin,colour_bg.pixel,colour_cursor.pixel); gc_sel_p = setup_gc(topwin,colour_sel_pin.pixel,colour_fg.pixel); gc_sel_b = setup_gc(topwin,colour_sel_block.pixel,colour_fg.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); arrowcurs = XCreateFontCursor(disp,XC_top_left_arrow); XRecolorCursor(disp,arrowcurs,&colour_fg,&colour_bg); XDefineCursor(disp,dragwin,nilcurs); XDefineCursor(disp,popupwin,nilcurs); XDefineCursor(disp,drawwin,arrowcurs); } static void setup_damage(void) { pinpm = XCreatePixmap(disp,topwin,(2*PIN_HITBOX)+1,(2*PIN_HITBOX)+1,1); } 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 repair_block_damage(void) { BLOCK *b; int x; int y; for (b=allblocks;b;b=b->link) { if (b->flags & BF_DAMAGED) { b->flags &= ~BF_DAMAGED; XCopyPlane(disp,b->img,drawwin,(b==selblock)?gc_sel_b:gc_fg,0,0,b->type->size.w,b->type->size.h,b->x,b->y,1); if (selpin.b == b) { switch (selpin.type) { default: panic(); break; case PIN_INPUT: x = b->x; y = b->y + (*b->type->input_y)(b->priv,selpin.inx); break; case PIN_OUTPUT: x = b->x + b->type->size.w - 1; y = b->y + (*b->type->output_y)(b->priv,selpin.inx); break; } XCopyPlane(disp,b->img,drawwin,gc_sel_p,(x-PIN_HITBOX)-b->x,(y-PIN_HITBOX)-b->y,(2*PIN_HITBOX)+1,(2*PIN_HITBOX)+1,x-PIN_HITBOX,y-PIN_HITBOX,1); } } } } static void set_selblock(BLOCK *b) { if (selblock) { if (selblock == b) return; selblock->flags |= BF_DAMAGED; } selblock = b; if (b) b->flags |= BF_DAMAGED; } static void set_selpin(BLOCK *b, PINTYPE type, int inx) { if ((selpin.b == b) && (selpin.type == type) && (selpin.inx == inx)) return; if (selpin.b) selpin.b->flags |= BF_DAMAGED; selpin.b = b; selpin.type = type; selpin.inx = inx; b->flags |= BF_DAMAGED; } static void set_noselpin(void) { if (! selpin.b) return; selpin.b->flags |= BF_DAMAGED; selpin.b = 0; } static void redraw_edit(void) { XCopyArea(disp,editpm,editwin,gc_fg,0,0,popup_curwidth,font_baselineskip,0,0); } static int rects_overlap(int ax, int ay, int aw, int ah, int bx, int by, int bw, int bh) { if (ax < bx) { aw -= bx - ax; if (aw < 1) return(0); ax = bx; } else if (bx < ax) { bw -= ax - bx; if (bw < 1) return(0); bx = ax; } if (ay < by) { ah -= by - ay; if (ah < 1) return(0); ay = by; } else if (by < ay) { bh -= ay - by; if (bh < 1) return(0); by = ay; } return(1); } static void handle_expose(XExposeEvent *e) { BLOCK *b; if (e->window == drawwin) { for (b=allblocks;b;b=b->link) { if (b->flags & BF_DAMAGED) continue; if (rects_overlap(e->x,e->y,e->width,e->height,b->x,b->y,b->type->size.w,b->type->size.h)) { b->flags |= BF_DAMAGED; } } if (e->count == 0) repair_block_damage(); return; } if (e->window == editwin) { if (e->count == 0) redraw_edit(); 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->keytext = ""; i->impl = 0; } static void create_block_menu(void) { int inx; MENUITEM *i; menu_block.nitems = n_blocktypes + 2; menu_block.items = malloc(menu_block.nitems*sizeof(*menu_block.items)); for (inx=0;inxkey = 'a' + inx; i->text = blocktypes[inx]->name; i->keytext = 0; 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 = "cancel"; i->key = '\e'; i->keytext = "ESC"; i->impl = &block_menu_impl; i->arg = -1; } static void recreate_block_menu(void) { free(menu_block.items); 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 void destroy_block(BLOCK *b) { int i; (*b->type->destroy)(b->priv); for (i=b->type->n_in-1;i>=0;i--) free(b->inputs[i]); free(b->inputs); for (i=b->type->n_out-1;i>=0;i--) free(b->outputs[i]); free(b->outputs); free(b); } static int main_n_block_drop(void *bv, int x, int y, DROPOP op) { BLOCK *b; b = bv; if (op == DROP_FAIL) { destroy_block(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; int i; 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; for (i=b->type->n_in-1;i>=0;i--) b->inputs[i] = 0; b->outputs = b->type->n_out ? malloc(b->type->n_out*sizeof(*b->outputs)) : 0; for (i=b->type->n_out-1;i>=0;i--) b->outputs[i] = 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; if ((choice < 0) || (choice >= n_blocktypes)) { main_n_err(0); return; } b = malloc(sizeof(BLOCK)); b->type = blocktypes[choice]; b->flags = 0; (*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[] = { { "Quit", 'Q', &menu_impl_main_Q }, { "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[] = { { "Cancel", '\e', &menu_impl_cancel, "ESC" } }; static MENU menu_cancel = MENU_INIT(cancel); int widthof(const char *s, int l) { XCharStruct cs; XTextExtents(font,s,l,XTE_JUNK,&cs); return(cs.width); } static void setup_menu(MENU *m) { int ix; MENUITEM *i; int y; int w; char kc; w = 0; y = font_baseline; for (ix=0;ixnitems;ix++) { i = &m->items[ix]; i->y = y; w = widthof(i->keytext?:((kc=i->key),&kc),i->keytext?strlen(i->keytext):1); if (w > menu_tag_maxw) menu_tag_maxw = w; w = widthof(i->text,strlen(i->text)); if (w > menu_text_maxw) menu_text_maxw = w; y += font_baselineskip; } w = menu_tag_maxw + menu_text_maxw + (4 * font_space); if (w > menu_maxwidth) menu_maxwidth = w; y = m->nitems * font_baselineskip; if (y > menu_maxheight) menu_maxheight = y; m->flags = 0; m->link = allmenus; allmenus = m; } static void setup_menus(void) { create_block_menu(); menu_tag_maxw = 0; menu_text_maxw = 0; menu_maxwidth = 0; menu_maxheight = 0; allmenus = 0; setup_menu(&menu_main); setup_menu(&menu_block); setup_menu(&menu_cancel); menu_tag_x = font_space; menu_text_x = menu_tag_maxw + (3 * font_space); } 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) { BLOCK *b; int i; int px; int py; for (b=allblocks;b;b=b->link) { if ( (x >= b->x-PIN_HITBOX) && (x < b->x+b->type->size.w+PIN_HITBOX) && (y >= b->y-PIN_HITBOX) && (y < b->y+b->type->size.h+PIN_HITBOX) ) { for (i=b->type->n_in-1;i>=0;i--) { px = b->x; py = b->y + (*b->type->input_y)(b->priv,i); if ( (x >= px-PIN_HITBOX) && (x <= px+PIN_HITBOX) && (y >= py-PIN_HITBOX) && (y <= py+PIN_HITBOX) ) { set_selblock(0); set_selpin(b,PIN_INPUT,i); repair_block_damage(); return; } } for (i=b->type->n_out-1;i>=0;i--) { px = b->x + b->type->size.w - 1; py = b->y + (*b->type->output_y)(b->priv,i); if ( (x >= px-PIN_HITBOX) && (x <= px+PIN_HITBOX) && (y >= py-PIN_HITBOX) && (y <= py+PIN_HITBOX) ) { set_selblock(0); set_selpin(b,PIN_OUTPUT,i); repair_block_damage(); return; } } if ( (x >= b->x) && (x <= b->x+b->type->size.w) && (y >= b->y) && (y <= b->y+b->type->size.h) ) { set_selblock(b); set_noselpin(); repair_block_damage(); return; } } } set_selblock(0); set_noselpin(); repair_block_damage(); } 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; selblock = 0; } static void init_popup(void) { popped_up = 0; popup_buf = 0; popup_bufalloc = 0; chk_choices = 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 CHKRV popup_chk_irange(const char *s) { char *ep; long int liv; int iv; liv = strtol(s,&ep,0); if (ep == s) return((CHKRV){.status=STAT_BADC,.ngood=0}); if (*ep) return((CHKRV){.status=STAT_BADC,.ngood=ep-s}); iv = liv; if ((iv != liv) || (iv < chk_irange_v1) || (iv > chk_irange_v2)) return((CHKRV){.status=STAT_RANGE,.ngood=ep-s}); *(int *)popup_valp = iv; 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); } /* * We really shouldn't be frobbing popupwin in here at all. We do this * because some of the popup_cx/popup_cy frobbing depends on the * editor window. */ static void update_editwin(int plainc, int goodc, int popup_h, int edit_y) { int badc; int w_plain; int w_good; int w_bad; int w; int w_l; int w_r; int h_t; int h_b; int strx; int drawx; badc = popup_buflen - plainc - goodc; if (badc < 0) panic(); w_plain = widthof(popup_buf,plainc); w_good = widthof(popup_buf+plainc,goodc); w_bad = widthof(popup_buf+plainc+goodc,badc); popup_anybad = (badc > 0); popup_curwidth = w_plain + w_good + w_bad + 3; w = ((popup_curwidth > popup_promptwidth) ? popup_curwidth : popup_promptwidth) + (2 * font_space); w_l = w / 2; w_r = w - w_l; h_t = popup_h / 2; h_b = popup_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 (popup_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); strx = 0; drawx = 0; if (popup_bufcurs < 1) { XDrawLine(disp,editpm,gc_cursor,drawx+1,0,drawx+1,font_baselineskip); drawx += 3; } if (plainc > 0) { if (popup_bufcurs < plainc) { XDrawString(disp,editpm,gc_fg,drawx,font_baseline,popup_buf+strx,popup_bufcurs); drawx += widthof(popup_buf+strx,popup_bufcurs); strx += popup_bufcurs; XDrawLine(disp,editpm,gc_cursor,drawx+1,0,drawx+1,font_baselineskip); drawx += 3; XDrawString(disp,editpm,gc_fg,drawx,font_baseline,popup_buf+strx,plainc-popup_bufcurs); drawx += widthof(popup_buf+strx,plainc-popup_bufcurs); strx += plainc - popup_bufcurs; } else { XDrawString(disp,editpm,gc_fg,drawx,font_baseline,popup_buf+strx,plainc); drawx += w_plain; strx += plainc; } if (popup_bufcurs == strx) { XDrawLine(disp,editpm,gc_cursor,drawx+1,0,drawx+1,font_baselineskip); drawx += 3; } } if (goodc > 0) { if ((popup_bufcurs > strx) && (popup_bufcurs < strx+goodc)) { XDrawString(disp,editpm,gc_ok,drawx,font_baseline,popup_buf+strx,popup_bufcurs-strx); drawx += widthof(popup_buf+strx,popup_bufcurs-strx); strx = popup_bufcurs; XDrawLine(disp,editpm,gc_cursor,drawx+1,0,drawx+1,font_baselineskip); drawx += 3; XDrawString(disp,editpm,gc_ok,drawx,font_baseline,popup_buf+strx,goodc-strx); drawx += widthof(popup_buf+strx,goodc-strx); strx = goodc; } else { XDrawString(disp,editpm,gc_ok,drawx,font_baseline,popup_buf+strx,goodc); drawx += w_good; strx += goodc; } if (popup_bufcurs == strx) { XDrawLine(disp,editpm,gc_cursor,drawx+1,0,drawx+1,font_baselineskip); drawx += 3; } } if (badc) { if ((popup_bufcurs > strx) && (popup_bufcurs < strx+badc)) { XDrawString(disp,editpm,gc_bad,drawx,font_baseline,popup_buf+strx,popup_bufcurs-strx); drawx += widthof(popup_buf+strx,popup_bufcurs-strx); strx = popup_bufcurs; XDrawLine(disp,editpm,gc_cursor,drawx+1,0,drawx+1,font_baselineskip); drawx += 3; XDrawString(disp,editpm,gc_bad,drawx,font_baseline,popup_buf+strx,popup_buflen-strx); drawx += widthof(popup_buf+strx,popup_buflen-strx); } else { XDrawString(disp,editpm,gc_bad,drawx,font_baseline,popup_buf+strx,popup_buflen-strx); drawx += w_bad; } strx = popup_buflen; if (popup_bufcurs == strx) { XDrawLine(disp,editpm,gc_cursor,drawx+1,0,drawx+1,font_baselineskip); drawx += 3; } } XMoveResizeWindow(disp,popupwin,popup_cx-w_l-1,popup_cy-h_t-1,w,popup_h); XMoveWindow(disp,promptwin,(w-popup_promptwidth)/2,font_baselineskip/2); XMoveResizeWindow(disp,editwin,(w-popup_curwidth)/2,edit_y,popup_curwidth,font_baselineskip); redraw_edit(); } static void changed_edit(void) { CHKRV chk; 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); } update_editwin(0,goodc,3*font_baselineskip,(font_baselineskip*3)/2); } 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; } static void popup_popdown(void) { XUnmapWindow(disp,popupwin); XFreePixmap(disp,edit_goodprompt); if (edit_badprompt != None) { XFreePixmap(disp,edit_badprompt); edit_badprompt = None; } set_curmenu(popup_menu); input = popup_input; } static void edit_del(int at, int n) { if ((at < 0) || (n < 0) || (at+n > popup_buflen)) panic(); if (popup_bufcurs > at) { if (popup_bufcurs >= at+n) { popup_bufcurs -= n; } else { popup_bufcurs = at; } } if (at+n < popup_buflen) bcopy(popup_buf+at+n,popup_buf+at,popup_buflen-(at+n)); popup_buflen -= n; changed_edit(); } static void input_edit_keystroke(INPUT_ARGS_KEYSTROKE) { switch (key) { case 1: // ^A if (popup_bufcurs == 0) return; popup_bufcurs = 0; break; case 2: // ^B if (popup_bufcurs < 1) return; popup_bufcurs --; break; case 4: // ^D if (popup_bufcurs >= popup_buflen) return; edit_del(popup_bufcurs,1); break; case 5: // ^E if (popup_bufcurs == popup_buflen) return; popup_bufcurs = popup_buflen; break; case 6: // ^F if (popup_bufcurs == popup_buflen) return; popup_bufcurs ++; break; case 7: // ^G XBell(disp,0); popup_popdown(); (*popup_done)(0); break; case 8: // ^H case 127: // DEL if (popup_bufcurs < 1) return; edit_del(popup_bufcurs-1,1); break; case 10: // ^J case 13: // ^M if (popup_anybad || popup_badrange) { XBell(disp,0); } else { popup_popdown(); (*popup_done)(1); } break; case 11: // ^K if (popup_buflen == popup_bufcurs) return; popup_buflen = popup_bufcurs; break; case 20: // ^T if (popup_bufcurs < 2) return; { char t; t = popup_buf[popup_bufcurs-1]; popup_buf[popup_bufcurs-1] = popup_buf[popup_bufcurs-2]; popup_buf[popup_bufcurs-2] = t; } break; case 21: // ^U case 24: // ^X if (popup_buflen < 1) return; popup_buflen = 0; popup_bufcurs = 0; break; case ' ' ... '~': popup_room(popup_buflen+1); if (popup_bufcurs < popup_buflen) bcopy(popup_buf+popup_bufcurs,popup_buf+popup_bufcurs+1,popup_buflen-popup_bufcurs); popup_buf[popup_bufcurs++] = key; popup_buflen ++; break; default: XBell(disp,0); break; } changed_edit(); } static void input_edit_motion(INPUT_ARGS_MOTION) { } static void input_edit_button(INPUT_ARGS_BUTTON) { } static INPUTOPS input_edit = INPUTOPS_INIT(edit); static void popup_edit(const char *prompt, const char *rangestr, CHKRV (*chk)(const char *), void (*done)(int)) { int w_prompt; int w_range; int promptlen; int rangelen; promptlen = strlen(prompt); w_prompt = widthof(prompt,promptlen); if (rangestr) { rangelen = strlen(rangestr); w_range = widthof(rangestr,rangelen); popup_promptwidth = w_prompt + font_space + w_range; if (popup_promptwidth < 1) popup_promptwidth = 1; } else { popup_promptwidth = w_prompt; 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,w_prompt+font_space,font_baseline,rangestr,rangelen); XDrawString(disp,edit_badprompt,gc_bad,w_prompt+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_bufcurs = 0; popup_curwidth = 0; popup_chk = chk; popup_done = done; begin_popup(); changed_edit(); XMapWindow(disp,popupwin); popup_menu = curmenu; popup_input = input; input = &input_edit; } static int choice_nmatch(const char *a, int al, const char *b, int bl) { int i; unsigned char ca; unsigned char cb; if (al > bl) al = bl; for (i=0;i=0;i--) { n = choice_nmatch(popup_buf,popup_bufcurs,chk_choices[i].str,chk_choices[i].len); // printf("vs %.*s, matched %d: ",chk_choices[i].len,chk_choices[i].str,n); if ((match_i < 0) || (n > match_n)) { match_i = i; match_n = n; multi = 0; ncommon = chk_choices[i].len; // printf("new best\n"); } else if (n == match_n) { ncommon = choice_nmatch(chk_choices[match_i].str,ncommon,chk_choices[i].str,chk_choices[i].len); multi = 1; // printf("tie\n"); } } chk_unique = ! multi; if (chk_unique) *(int *)popup_valp = chk_choices[match_i].val; if (popup_bufcurs > match_n) { plainc = match_n; goodc = 0; popup_buflen = popup_bufcurs; popup_tabto = popup_bufcurs; } else if (! multi) { popup_room(chk_choices[match_i].len); plainc = popup_bufcurs; goodc = chk_choices[match_i].len - popup_bufcurs; if (goodc > 0) bcopy(chk_choices[match_i].str+popup_bufcurs,popup_buf+popup_bufcurs,goodc); popup_buflen = chk_choices[match_i].len; popup_tabto = popup_buflen; } else { popup_room(ncommon); plainc = popup_bufcurs; goodc = ncommon - popup_bufcurs; if (goodc > 0) bcopy(chk_choices[match_i].str+popup_bufcurs,popup_buf+popup_bufcurs,goodc); popup_buflen = ncommon; popup_tabto = popup_buflen; } // printf("plainc %d, goodc %d, buflen %d\n",plainc,goodc,popup_buflen); // printf("buffer %.*s\n",popup_buflen,popup_buf); update_editwin(plainc,goodc,(chk_nchoice+4)*font_baselineskip,((chk_nchoice+2)*font_baselineskip)+(font_baselineskip/2)); } static void input_choice_keystroke(INPUT_ARGS_KEYSTROKE) { switch (key) { case '\a': case '\e': XBell(disp,0); popup_popdown(); (*popup_done)(0); break; case '\t': if (popup_tabto != popup_bufcurs) { popup_bufcurs = popup_tabto; changed_choice(); } break; case '\r': case '\n': if (! chk_unique) { XBell(disp,0); } else { popup_popdown(); (*popup_done)(1); } break; case '\b': case '\177': if (popup_bufcurs > 0) { popup_bufcurs --; changed_choice(); } break; case ' ' ... '~': popup_room(popup_buflen+1); if (popup_bufcurs == popup_buflen) popup_buflen ++; popup_buf[popup_bufcurs++] = key; changed_choice(); break; default: XBell(disp,0); break; } } static void input_choice_motion(INPUT_ARGS_MOTION) { } static void input_choice_button(INPUT_ARGS_BUTTON) { } static INPUTOPS input_choice = INPUTOPS_INIT(choice); static void popup_choice(const char *prompt, void (*done)(int)) { int promptlen; int w_prompt; int w; int w_all; int i; promptlen = strlen(prompt); w_prompt = widthof(prompt,promptlen); w_all = w_prompt; for (i=chk_nchoice-1;i>=0;i--) { w = widthof(chk_choices[i].str,chk_choices[i].len); if (w > w_all) w_all = w; } w_all += 2 * font_space; popup_promptwidth = w_all; popup_promptheight = (2 + chk_nchoice) * font_baselineskip; edit_goodprompt = XCreatePixmap(disp,topwin,popup_promptwidth,popup_promptheight,depth); XFillRectangle(disp,edit_goodprompt,gc_bg,0,0,popup_promptwidth,popup_promptheight); XDrawString(disp,edit_goodprompt,gc_fg,(popup_promptwidth-w_prompt)/2,font_baseline,prompt,promptlen); for (i=chk_nchoice-1;i>=0;i--) { XDrawString(disp,edit_goodprompt,gc_fg,(w_all-widthof(chk_choices[i].str,chk_choices[i].len))/2,font_baseline+((i+2)*font_baselineskip),chk_choices[i].str,chk_choices[i].len); } XResizeWindow(disp,promptwin,popup_promptwidth,popup_promptheight); XSetWindowBackgroundPixmap(disp,promptwin,edit_goodprompt); XClearWindow(disp,promptwin); popup_buflen = 0; popup_bufcurs = 0; popup_curwidth = 0; popup_done = done; begin_popup(); changed_choice(); XMapWindow(disp,popupwin); popup_menu = curmenu; set_curmenu(&menu_cancel); popup_input = input; input = &input_choice; } 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 *range_rangestr = 0; va_list ap; int i; getarg_cont = cont; getarg_contarg = contarg; popup_valp = ploc; 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(range_rangestr); asprintf(&range_rangestr,"[%g..%g]",chk_frange_v1,chk_frange_v2); rangestr = range_rangestr; break; case PAR_IRANGE: va_start(ap,ptype); chk_irange_v1 = va_arg(ap,int); chk_irange_v2 = va_arg(ap,int); va_end(ap); chk = &popup_chk_irange; free(range_rangestr); asprintf(&range_rangestr,"[%d..%d]",chk_irange_v1,chk_irange_v2); rangestr = range_rangestr; break; case PAR_CHOICE: va_start(ap,ptype); chk_nchoice = va_arg(ap,int); free(chk_choices); chk_choices = malloc(chk_nchoice*sizeof(*chk_choices)); for (i=0;i