#include #include #include #include #include #include #include #include #include extern const char *__progname; #include #include #include #include #include #include #include #define XK_MISCELLANY #include #include "xte.h" #include "random.h" #include "builtins.h" #include "blocktype.h" #include "block-output.h" #define BLOCK_APPROACH_BLOCK 30 #define WIRE_APPROACH 6 #define PIN_HITBOX 5 #define MAXSELPIN 2 #define ERRMSG_MINTIME 500000 // microseconds #define CELL_COST 10 #define TURN_COST 400 #define CROSS_COST 100 // per step 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 enum { LINE_V = 1, LINE_H, } LINETYPE; typedef unsigned long long int TIME; typedef unsigned int DIST; #define MAXDIST 0xffffffff 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 linelist LINELIST; typedef struct box BOX; typedef struct xyddh XYDDH; typedef struct xyddhe XYDDHE; typedef struct linegen LINEGEN; typedef struct floodcell FLOODCELL; #define DIR_L 0 #define DIR_R 1 #define DIR_U 2 #define DIR_D 3 #define DIR__N 4 static const unsigned char dir_turn_180[] = { [DIR_L] = DIR_R, [DIR_R] = DIR_L, [DIR_U] = DIR_D, [DIR_D] = DIR_U }; #define DIR_TURN_180(d) dir_turn_180[(d)] static const unsigned char dir_turn_r[] = { [DIR_L] = DIR_U, [DIR_R] = DIR_D, [DIR_U] = DIR_R, [DIR_D] = DIR_L }; #define DIR_TURN_R(d) dir_turn_r[(d)] static const unsigned char dir_turn_l[] = { [DIR_L] = DIR_D, [DIR_R] = DIR_U, [DIR_U] = DIR_L, [DIR_D] = DIR_R }; #define DIR_TURN_L(d) dir_turn_l[(d)] static const signed char dir_dx[] = { [DIR_L] = -1, [DIR_R] = 1, [DIR_U] = 0, [DIR_D] = 0 }; #define DIR_DX(d) dir_dx[(d)] static const signed char dir_dy[] = { [DIR_L] = 0, [DIR_R] = 0, [DIR_U] = -1, [DIR_D] = 1 }; #define DIR_DY(d) dir_dy[(d)] static const signed char dir_xoff[] = { [DIR_L] = 0, [DIR_R] = 4, [DIR_U] = 2, [DIR_D] = 2 }; #define DIR_XOFF(d) dir_xoff[(d)] static const signed char dir_yoff[] = { [DIR_L] = 2, [DIR_R] = 2, [DIR_U] = 0, [DIR_D] = 4 }; #define DIR_YOFF(d) dir_yoff[(d)] struct floodcell { DIST dist[DIR__N]; DIST remove; unsigned char srcdir[DIR__N]; unsigned char flags; #define FCF_START 0x01 #define FCF_WIRE_H 0x02 #define FCF_WIRE_V 0x04 #define FCF_BLOCK 0x08 } ; struct linegen { int x0; int y0; int linelen; LINETYPE type; int inc; LINELIST **listp; int xo; int yo; } ; struct xyddhe { unsigned int x; unsigned int y; unsigned int dir; unsigned int dist; } ; struct xyddh { int n; int nb; XYDDHE **v; } ; struct box { int x1; int x2; int y1; int y2; } ; struct linelist { LINELIST *link; LINETYPE type; int c; int c1; int c2; } ; struct iopin { 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; } ; #define MENUITEM_BLANK() { "", -1, 0, "" } struct block { BLOCK *flink; BLOCK *blink; BLOCKTYPE *type; SIGNAL **inputs; SIGNAL **outputs; void *priv; int x; int y; Pixmap img; unsigned int flags; #define BF_DAMAGED 0x00000001 } ; struct signal { SIGNAL *flink; SIGNAL *blink; SIGNAL *dflink; SIGNAL *dblink; IOPIN *drive; int n_driven; int a_driven; IOPIN **driven; LINELIST *shape; Window win; unsigned int flags; #define SF_DAMAGED 0x00000001 #define SF_WINMAPPED 0x00000002 double lastv; double nextv; } ; 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 XColor colour_dbg[8]; static int margin; static int borderwidth; static Window topwin; static Window drawwin; static Window menuwin; static Window dragwin; static Window popupwin; static Window errorwin; static Window promptwin; static Window confirmwin; 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; static GC gc_dbg[8][8]; // indexed [fg][bg] #define COL_K 0 #define COL_R 1 #define COL_G 2 #define COL_Y 3 #define COL_B 4 #define COL_M 5 #define COL_C 6 #define COL_W 7 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_pm_w; static int drag_pm_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 int errmsglen; static char *errmsg = 0; static int errmsgwidth; static Pixmap errmsgpm; static INPUTOPS *errmsg_input; static TIME errmsg_start; 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 int move_oldx; static int move_oldy; static void (*on_confirm)(int); static MENU *confirm_menu; static INPUTOPS *confirm_input; static double W_time; static char *W_file; static BLOCK *allblocks; static SIGNAL *allsignals; static SIGNAL *dsignals; static BLOCK *selblock; static IOPIN selpin[MAXSELPIN]; // forwards static MENU menu_main; static MENU menu_block; static MENU menu_cancel; static MENU menu_confirm; #define Cisupper(x) isupper((unsigned char)(x)) #define Ctolower(x) tolower((unsigned char)(x)) // forwards static void popup_edit(const char *, const char *, const char *, int, CHKRV (*)(const char *), void (*)(int)); static char *deconst(const char *s) { char *rv; bcopy(&s,&rv,sizeof(rv)); return(rv); } void panic(void) __attribute__((__noreturn__)); void panic(void) { fflush(0); (void)*(volatile char *)0; abort(); } static TIME time_now(void) { struct timeval tv; gettimeofday(&tv,0); return((tv.tv_sec*(TIME)1000000)+(TIME)tv.tv_usec); } #if 0 static const char *linetype_str(LINETYPE t) { static char badbuf[64]; switch (t) { case LINE_V: return("V"); break; case LINE_H: return("H"); break; } snprintf(&badbuf[0],sizeof(badbuf),"?%d",(int)t); return(&badbuf[0]); } #endif #if 0 static const char *pintype_str(PINTYPE pt) { static char badbuf[64]; switch (pt) { case PIN_INPUT: return("INPUT"); break; case PIN_OUTPUT: return("OUTPUT"); break; } snprintf(&badbuf[0],sizeof(badbuf),"?%d",(int)pt); return(&badbuf[0]); } #endif #if 0 static const char *dir_str(int dir) { static char badbuf[64]; switch (dir) { case DIR_L: return("L"); break; case DIR_R: return("R"); break; case DIR_U: return("U"); break; case DIR_D: return("D"); break; } snprintf(&badbuf[0],sizeof(badbuf),"?%d",dir); return(&badbuf[0]); } #endif 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_min); setup_blocktype(&block_max); setup_blocktype(&block_cmp); setup_blocktype(&block_noise); setup_blocktype(&block_osc); #if 0 setup_blocktype(&block_vco); setup_blocktype(&block_ddcosc); setup_blocktype(&block_genosc); #endif setup_blocktype(&block_delay); setup_blocktype(&block_window); setup_blocktype(&block_envelope); setup_blocktype(&block_output); setup_blocktype(&block_display); // 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, const 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, const 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[15]; 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); setup_dynamic_colour(&colour_dbg[0],pixels[7],"#000",0); setup_dynamic_colour(&colour_dbg[1],pixels[8],"#f00",0); setup_dynamic_colour(&colour_dbg[2],pixels[9],"#0f0",0); setup_dynamic_colour(&colour_dbg[3],pixels[10],"#ff0",0); setup_dynamic_colour(&colour_dbg[4],pixels[11],"#00f",0); setup_dynamic_colour(&colour_dbg[5],pixels[12],"#f0f",0); setup_dynamic_colour(&colour_dbg[6],pixels[13],"#0ff",0); setup_dynamic_colour(&colour_dbg[7],pixels[14],"#fff",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); setup_static_colour(&colour_dbg[0],"#000"); setup_static_colour(&colour_dbg[1],"#f00"); setup_static_colour(&colour_dbg[2],"#0f0"); setup_static_colour(&colour_dbg[3],"#ff0"); setup_static_colour(&colour_dbg[4],"#00f"); setup_static_colour(&colour_dbg[5],"#f0f"); setup_static_colour(&colour_dbg[6],"#0ff"); setup_static_colour(&colour_dbg[7],"#fff"); } } 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.backing_store = NotUseful; attrmask |= CWBackingStore; attr.bit_gravity = StaticGravity; attrmask |= CWBitGravity; attr.win_gravity = StaticGravity; attrmask |= CWWinGravity; confirmwin = XCreateWindow(disp,topwin,0,0,1,1,1,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.border_pixel = colour_border.pixel; attrmask |= CWBorderPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; errorwin = XCreateWindow(disp,topwin,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; int i; int j; 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); for (i=0;i<8;i++) for (j=0;j<8;j++) gc_dbg[i][j] = setup_gc(topwin,colour_dbg[j].pixel,colour_dbg[i].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,errorwin,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; int i; for (b=allblocks;b;b=b->flink) { 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); for (i=MAXSELPIN-1;i>=0;i--) { if (selpin[i].b == b) { switch (selpin[i].type) { default: panic(); break; case PIN_INPUT: x = b->x; y = b->y + (*b->type->input_y)(b->priv,selpin[i].inx); break; case PIN_OUTPUT: x = b->x + b->type->size.w - 1; y = b->y + (*b->type->output_y)(b->priv,selpin[i].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) { int i; int j; for (i=MAXSELPIN-1;i>=0;i--) { if ((selpin[i].b == b) && (selpin[i].type == type) && (selpin[i].inx == inx)) { b->flags |= BF_DAMAGED; for (i++;iflags |= BF_DAMAGED; bcopy(&selpin[1],&selpin[0],(MAXSELPIN-1)*sizeof(selpin[0])); selpin[MAXSELPIN-1].b = 0; } j = -1; for (i=MAXSELPIN-1;i>=0;i--) if (! selpin[i].b) j = i; if (j < 0) panic(); selpin[j].b = b; selpin[j].type = type; selpin[j].inx = inx; b->flags |= BF_DAMAGED; } static void set_noselpins(void) { int i; for (i=MAXSELPIN-1;i>=0;i--) { if (selpin[i].b) { selpin[i].b->flags |= BF_DAMAGED; selpin[i].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->flink) { 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; KeySym ks; if (! downp) return; sl = XLookupString(e,&str[0],sizeof(str),&ks,0); switch (ks) { case XK_KP_1: (*input->button)(e->x,e->y,Button1); break; case XK_KP_2: (*input->button)(e->x,e->y,Button2); break; case XK_KP_3: (*input->button)(e->x,e->y,Button3); break; default: if (sl < 1) return; (*input->keystroke)(str[0]); break; } } 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 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 popdown_errmsg(void) { XUnmapWindow(disp,errorwin); input = errmsg_input; } static void input_errmsg_keystroke(INPUT_ARGS_KEYSTROKE) { TIME t; t = time_now(); if (t-errmsg_start < ERRMSG_MINTIME) return; popdown_errmsg(); } static void input_errmsg_motion(INPUT_ARGS_MOTION) { } static void input_errmsg_button(INPUT_ARGS_BUTTON) { TIME t; t = time_now(); if (t-errmsg_start < ERRMSG_MINTIME) return; popdown_errmsg(); } static INPUTOPS input_errmsg = INPUTOPS_INIT(errmsg); static void pop_error(const char *, ...) __attribute__((__format__(__printf__,1,2))); static void pop_error(const char *fmt, ...) { va_list ap; int w; int w_l; int w_r; int h; int h_t; int h_b; free(errmsg); va_start(ap,fmt); errmsglen = vasprintf(&errmsg,fmt,ap); va_end(ap); errmsgwidth = widthof(errmsg,errmsglen); w = (font_space * 2) + errmsgwidth; h = font_baselineskip * 2; errmsgpm = XCreatePixmap(disp,topwin,w,h,depth); XFillRectangle(disp,errmsgpm,gc_bg,0,0,w,h); XDrawString(disp,errmsgpm,gc_fg,font_space,(font_baselineskip/2)+font_baseline,errmsg,errmsglen); XResizeWindow(disp,errorwin,w,h); XSetWindowBackgroundPixmap(disp,errorwin,errmsgpm); XClearWindow(disp,errorwin); begin_popup(); 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; } XMoveResizeWindow(disp,errorwin,popup_cx-w_l-1,popup_cy-h_t-1,w,h); XMapWindow(disp,errorwin); XBell(disp,0); errmsg_start = time_now(); errmsg_input = input; input = &input_errmsg; } 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; } pop_error("Can't drop here"); } 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_pm_w) || (h > drag_pm_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; if (w > drag_pm_w) drag_pm_w = w; if (h > drag_pm_h) drag_pm_h = h; } if ((w != drag_w) || (h != drag_h)) XResizeWindow(disp,dragwin,w,h); drag_w = w; drag_h = h; if (drag_pm == None) drag_pm = XCreatePixmap(disp,rootwin,drag_pm_w,drag_pm_h,1); if (drag_pm_r == None) drag_pm_r = XCreatePixmap(disp,rootwin,drag_pm_w,drag_pm_h,depth); if (!dynamic_visual && (drag_pm_g == None)) drag_pm_g = XCreatePixmap(disp,rootwin,drag_pm_w,drag_pm_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,drag_w,drag_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,drag_w,drag_h,0,0,1); XCopyPlane(disp,drag_pm,drag_pm_g,gc_ok,0,0,drag_w,drag_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->flink) { if ( (x > b->x+b->type->size.w+BLOCK_APPROACH_BLOCK) || (x+w+BLOCK_APPROACH_BLOCK < b->x) || (y > b->y+b->type->size.h+BLOCK_APPROACH_BLOCK) || (y+h+BLOCK_APPROACH_BLOCK < 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->flink = allblocks; b->blink = 0; if (allblocks) allblocks->blink = b; 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 SIGNAL **signalp_for_pin(IOPIN *sp) { switch (sp->type) { case PIN_INPUT: if (sp->inx >= sp->b->type->n_in) panic(); return(&sp->b->inputs[sp->inx]); break; case PIN_OUTPUT: if (sp->inx >= sp->b->type->n_out) panic(); return(&sp->b->outputs[sp->inx]); break; } panic(); } static SIGNAL *signal_new(void) { SIGNAL *s; s = malloc(sizeof(SIGNAL)); s->drive = 0; s->n_driven = 0; s->a_driven = 0; s->driven = 0; s->shape = 0; s->win = None; s->flags = 0; s->flink = allsignals; s->blink = 0; if (allsignals) allsignals->blink = s; allsignals = s; return(s); } static void signal_damage(SIGNAL *s) { if (! s) return; if (s->flags & SF_DAMAGED) return; s->flags |= SF_DAMAGED; s->dflink = dsignals; s->dblink = 0; if (dsignals) dsignals->dblink = s; dsignals = s; } static void signal_add_pin(SIGNAL *s, IOPIN p) { IOPIN *pc; switch (p.type) { default: panic(); break; case PIN_INPUT: pc = malloc(sizeof(IOPIN)); *pc = p; s->n_driven ++; if (s->n_driven > s->a_driven) s->driven = realloc(s->driven,(s->a_driven=s->n_driven)*sizeof(*s->driven)); s->driven[s->n_driven-1] = pc; if (p.inx >= p.b->type->n_in) panic(); signal_damage(p.b->inputs[p.inx]); p.b->inputs[p.inx] = s; break; case PIN_OUTPUT: if (s->drive) panic(); pc = malloc(sizeof(IOPIN)); *pc = p; s->drive = pc; if (p.inx >= p.b->type->n_out) panic(); signal_damage(p.b->outputs[p.inx]); p.b->outputs[p.inx] = s; break; } signal_damage(s); } static void linelist_free_all(LINELIST *list) { LINELIST *l; while ((l = list)) { list = l->link; free(l); } } static int linelist_map(LINELIST *list, int (*fn)(LINELIST *, LINETYPE, int, int, int)) { LINELIST *l; int s; int v; s = 0; for (l=list;l;l=l->link) { switch (l->type) { default: panic(); break; case LINE_V: case LINE_H: v = (*fn)(l,l->type,l->c,l->c1,l->c2); break; } if (v < 0) return(v); s += v; } return(s); } static LINELIST *linelist_push(LINELIST *list, LINETYPE type, int c, int c1, int c2) { LINELIST *l; switch (type) { default: panic(); break; case LINE_V: case LINE_H: l = malloc(sizeof(LINELIST)); l->type = type; l->c = c; l->c1 = c1; l->c2 = c2; break; } l->link = list; return(l); } static void signal_free(SIGNAL *s) { if (s->n_driven || s->drive) panic(); free(s->driven); linelist_free_all(s->shape); if (s->win != None) XDestroyWindow(disp,s->win); if (s->flink) s->flink->blink = s->blink; if (s->blink) s->blink->flink = s->flink; else allsignals = s->flink; if (s->flags & SF_DAMAGED) { if (s->dflink) s->dflink->dblink = s->dblink; if (s->dblink) s->dblink->dflink = s->dflink; else dsignals = s->dflink; } free(s); } static void signal_maybe_free(SIGNAL *s) { if (!s->n_driven && !s->drive) signal_free(s); } static void signal_remove_pin(SIGNAL *s, IOPIN p) { int i; switch (p.type) { default: panic(); break; case PIN_INPUT: if (p.inx >= p.b->type->n_in) panic(); if (p.b->inputs[p.inx] != s) panic(); signal_damage(s); for (i=s->n_driven-1;i>=0;i--) { if ((s->driven[i]->b == p.b) && (s->driven[i]->type == PIN_INPUT) && (s->driven[i]->inx == p.inx)) { p.b->inputs[p.inx] = 0; free(s->driven[i]); if (i != s->n_driven-1) s->driven[i] = s->driven[s->n_driven-1]; s->n_driven --; signal_maybe_free(s); return; } } panic(); break; case PIN_OUTPUT: if (! s->drive) panic(); if (p.inx >= p.b->type->n_out) panic(); if (p.b->outputs[p.inx] != s) panic(); if ((s->drive->b != p.b) || (s->drive->type != PIN_OUTPUT) || (s->drive->inx != p.inx)) panic(); signal_damage(s); p.b->outputs[p.inx] = 0; free(s->drive); s->drive = 0; signal_maybe_free(s); break; } } static void signal_merge(SIGNAL *s1, SIGNAL *s2) { IOPIN p; int i; if (s1->drive && s2->drive) panic(); if (s2->drive) { SIGNAL *t; t = s1; s1 = s2; s2 = t; } for (i=s2->n_driven-1;i>=0;i--) { p = *s2->driven[i]; signal_remove_pin(s2,p); signal_add_pin(s1,p); } } static void linelist_to_rectlist(LINELIST *s, int *nrp, XRectangle **rpp) { LINELIST *l; int i; int j; i = 0; for (l=s;l;l=l->link) i ++; *nrp = i; *rpp = malloc(i*sizeof(XRectangle)); j = 0; for (l=s;l;l=l->link) { if (j >= i) panic(); switch (l->type) { default: panic(); break; case LINE_V: rpp[0][j++] = (XRectangle){.x=l->c,.y=l->c1,.width=1,.height=l->c2-l->c1}; break; case LINE_H: rpp[0][j++] = (XRectangle){.x=l->c1,.y=l->c,.width=l->c2-l->c1,.height=1}; break; } } if (j != i) panic(); } static void signal_map(SIGNAL *s) { unsigned long int attrmask; XSetWindowAttributes attr; XRectangle *rects; int nrects; int mustmap; if (s->win == None) { attrmask = 0; attr.background_pixel = colour_fg.pixel; attrmask |= CWBackPixel; attr.backing_store = NotUseful; attrmask |= CWBackingStore; s->win = XCreateWindow(disp,drawwin,0,0,32767,32767,0,depth,InputOutput,CopyFromParent,attrmask,&attr); mustmap = 1; } else { mustmap = 0; } linelist_to_rectlist(s->shape,&nrects,&rects); XShapeCombineRectangles(disp,s->win,ShapeBounding,0,0,rects,nrects,ShapeSet,Unsorted); free(rects); if (mustmap) XMapWindow(disp,s->win); s->flags |= SF_WINMAPPED; } static void bbox_add_block(BOX *box, BLOCK *b) { int v; if (b->x < box->x1) box->x1 = b->x; if (b->y < box->y1) box->y1 = b->y; v = b->x + b->type->size.w; if (v > box->x2) box->x2 = v; v = b->y + b->type->size.h; if (v > box->y2) box->y2 = v; } static void bbox_add_signal(BOX *box, SIGNAL *s) { LINELIST *l; for (l=s->shape;l;l=l->link) { switch (l->type) { default: panic(); break; case LINE_V: if (l->c < box->x1) box->x1 = l->c; if (l->c >= box->x2) box->x2 = l->c+1; if (l->c1 < box->y1) box->y1 = l->c1; if (l->c2 > box->y2) box->y2 = l->c2; break; case LINE_H: if (l->c1 < box->x1) box->x1 = l->c1; if (l->c < box->y1) box->y1 = l->c; if (l->c2 > box->x2) box->x2 = l->c2; if (l->c >= box->y2) box->y2 = l->c; break; } } } static void xyddh_init(XYDDH *h) { h->n = 0; h->nb = 0; h->v = 0; } static void xyddh_room(XYDDH *h, int n) { int nb; nb = (n + 65535) >> 16; if (nb > h->nb) { h->v = realloc(h->v,nb*sizeof(*h->v)); while (h->nb < nb) h->v[h->nb++] = malloc(65536*sizeof(XYDDHE)); } } static XYDDHE *xyddh_index_(XYDDH *h, int inx) { if ((inx < 0) || (inx >= h->n)) panic(); return(&h->v[inx>>16][inx&0xffff]); } #define xyddh_index(h,inx) (*xyddh_index_((h),(inx))) static void xyddh_put(XYDDH *h, unsigned int x, unsigned int y, int dir, unsigned int dist) { int inx; int up; XYDDHE e; XYDDHE *ep; XYDDHE *eup; //printf("xyddh_put %p (%hu,%hu) dir=%s dist=%hu n=%d\n",(void *)h,x,y,dir_str(dir),dist,h->n); inx = h->n++; xyddh_room(h,h->n); e = (XYDDHE){.x=x,.y=y,.dir=dir,.dist=dist}; ep = &xyddh_index(h,inx); while (inx > 0) { up = (inx - 1) >> 1; eup = &xyddh_index(h,up); if (eup->dist <= dist) break; *ep = *eup; ep = eup; inx = up; } *ep = e; } static int xyddh_empty(XYDDH *h) { return(!h->n); } static XYDDHE xyddh_get(XYDDH *h) { XYDDHE rv; XYDDHE e; int x; int l; int r; XYDDHE *ep; XYDDHE *elp; XYDDHE *erp; if (h->n < 1) panic(); //printf("xyddh_get %p ->",(void *)h); ep = &xyddh_index(h,0); rv = *ep; if (h->n < 2) { h->n = 0; } else { e = xyddh_index(h,h->n-1); h->n --; x = 0; while (1) { l = x + x + 1; if (l >= h->n) break; r = l + 1; elp = &xyddh_index(h,l); erp = (r < h->n) ? &xyddh_index(h,r) : 0; if (erp && (erp->dist < e.dist) && ((elp->dist >= e.dist) || (erp->dist <= elp->dist))) { *ep = *erp; ep = erp; x = r; } else if (elp->dist < e.dist) { *ep = *elp; ep = elp; x = l; } else { break; } } *ep = e; } //printf(" (%hu,%hu) dir %s dist %hu\n",rv.x,rv.y,dir_str(rv.dir),rv.dist); return(rv); } static void mem_replicate(void *base, int unit, int units) { char *bp; int i; bp = base; if (unit < 1) panic(); if (units < 2) return; i = 1; while ((i<<1) < units) { bcopy(bp,bp+(i*unit),i*unit); i <<= 1; } if (i < units) bcopy(bp,bp+(i*unit),(units-i)*unit); } static LINELIST *sort_linelist(LINELIST *list) { LINELIST *a; LINELIST *b; LINELIST *t; LINELIST **lp; if (!list || !list->link) return(list); a = 0; b = 0; while ((t = list)) { list = t->link; t->link = a; a = b; b = t; } a = sort_linelist(a); b = sort_linelist(b); lp = &list; while (1) { if (a && (!b || ((a->c < b->c) || ((a->c == b->c) && (a->c1 < b->c1))))) { t = a; a = a->link; } else if (b) { t = b; b = b->link; } else { break; } *lp = t; lp = &t->link; } *lp = 0; return(list); } static LINELIST *collapse_lines(LINELIST *list) { LINELIST *l; LINELIST *l2; if (! list) return(0); l = list; while (1) { l2 = l->link; if (! l2) return(list); if ((l->c == l2->c) && (l->c2 >= l2->c1)) { if (l2->c2 > l->c2) l->c2 = l2->c2; l->link = l2->link; free(l2); } else { l = l2; } } } static LINELIST *collapse_linelist(LINELIST *shape) { LINELIST *h; LINELIST *v; LINELIST *l; LINELIST **lp; h = 0; v = 0; while ((l = shape)) { shape = l->link; switch (l->type) { default: panic(); break; case LINE_H: l->link = h; h = l; break; case LINE_V: l->link = v; v = l; break; } } h = collapse_lines(sort_linelist(h)); v = collapse_lines(sort_linelist(v)); lp = &h; while ((l = *lp)) lp = &l->link; *lp = v; return(h); } #if 0 static int print_line_with_tab(LINELIST *l, LINETYPE t, int c, int c1, int c2) { printf("\t%p %s %d %d %d\n",(void *)l,linetype_str(t),c,c1,c2); return(0); } #endif static void linegen_init(LINEGEN *lg, LINELIST **listp, int xo, int yo) { lg->linelen = 0; lg->listp = listp; lg->xo = xo; lg->yo = yo; } static void linegen_gen_line(LINEGEN *lg, LINETYPE t, int c, int c1, int c2) { switch (t) { default: panic(); break; case LINE_V: case LINE_H: *lg->listp = linelist_push(*lg->listp,t,c,c1,c2); break; } } static void linegen_flush_line(LINEGEN *lg) { if (lg->linelen < 2) panic(); switch (lg->type) { default: panic(); break; case LINE_V: if (lg->inc < 0) { linegen_gen_line(lg,LINE_V,lg->xo+lg->x0,lg->yo+lg->y0-lg->linelen+1,lg->yo+lg->y0+1); } else { linegen_gen_line(lg,LINE_V,lg->xo+lg->x0,lg->yo+lg->y0,lg->yo+lg->y0+lg->linelen); } break; case LINE_H: if (lg->inc < 0) { linegen_gen_line(lg,LINE_H,lg->yo+lg->y0,lg->xo+lg->x0-lg->linelen+1,lg->xo+lg->x0+1); } else { linegen_gen_line(lg,LINE_H,lg->yo+lg->y0,lg->xo+lg->x0,lg->xo+lg->x0+lg->linelen); } break; } } static void linegen_point(LINEGEN *lg, int x, int y) { //printf("linegen_point %d,%d\n",x,y); switch (lg->linelen) { case 0: lg->x0 = x; lg->y0 = y; lg->linelen = 1; break; case 1: if (lg->x0 == x) { if (y == lg->y0-1) { lg->type = LINE_V; lg->inc = -1; } else if (y == lg->y0+1) { lg->type = LINE_V; lg->inc = 1; } else if (y == lg->y0) { return; } else { panic(); } } else if (y == lg->y0) { if (x == lg->x0-1) { lg->type = LINE_H; lg->inc = -1; } else if (x == lg->x0+1) { lg->type = LINE_H; lg->inc = 1; } else if (x == lg->x0) { return; } else { panic(); } } else { panic(); } lg->linelen = 2; break; default: switch (lg->type) { default: panic(); break; case LINE_V: if ((x == lg->x0) && (y == lg->y0+(lg->inc*lg->linelen))) { lg->linelen ++; } else if (y != lg->y0+(lg->inc*(lg->linelen-1))) { panic(); } else { linegen_flush_line(lg); if (x == lg->x0+1) { lg->inc = 1; } else if (x == lg->x0-1) { lg->inc = -1; } else { panic(); } lg->y0 = y; lg->type = LINE_H; lg->linelen = 2; } break; case LINE_H: if ((y == lg->y0) && (x == lg->x0+(lg->inc*lg->linelen))) { lg->linelen ++; } else if (x != lg->x0+(lg->inc*(lg->linelen-1))) { panic(); } else { linegen_flush_line(lg); if (y == lg->y0+1) { lg->inc = 1; } else if (y == lg->y0-1) { lg->inc = -1; } else { panic(); } lg->x0 = x; lg->type = LINE_V; lg->linelen = 2; } break; } break; } } static void linegen_done(LINEGEN *lg) { switch (lg->linelen) { case 0: break; case 1: panic(); break; default: linegen_flush_line(lg); break; } } static void signal_repair(SIGNAL *sr) { BOX limits; BLOCK *b; SIGNAL *s; static FLOODCELL *arena; #define ARENA(x,y) (*arenaptr((x),(y))) int maxremove; static int arena_a = 0; int arena_x; int arena_y; int arena_n; int i; XYDDH h; int pincount; int pin0x; int pin0y; int pin0dir; LINELIST *pinconn; FLOODCELL *arenaptr(int x, int y) { if ((x < 0) || (y < 0) || (x >= arena_x) || (y >= arena_y)) panic(); return(&arena[x+(y*arena_x)]); } static void init_arena(void) { FLOODCELL *c0; int i; c0 = &ARENA(0,0); for (i=DIR__N-1;i>=0;i--) c0->dist[i] = MAXDIST; c0->remove = MAXDIST; c0->flags = 0; mem_replicate(c0,sizeof(FLOODCELL),arena_n); } void arena_block_rect(int x1, int y1, int x2, int y2) { int x; int y; FLOODCELL *c; int i; x1 -= limits.x1; x2 -= limits.x1; y1 -= limits.y1; y2 -= limits.y1; for (y=y1;y=0;i--) c->dist[i] = 0; c->remove = 0; c->flags |= FCF_BLOCK; xyddh_put(&h,x,y,DIR_L,0); } } } void wire_box(int x1, int y1, int x2, int y2, unsigned int setval) { int x; int y; FLOODCELL *c; x1 -= limits.x1; x2 -= limits.x1; y1 -= limits.y1; y2 -= limits.y1; for (y=y1;y<=y2;y++) { for (x=x1;x<=x2;x++) { c = &ARENA(x,y); c->flags |= setval; xyddh_put(&h,x,y,DIR_L,0); } } } int arena_add_wire(LINELIST *l __attribute__((__unused__)), LINETYPE t, int c, int c1, int c2) { switch (t) { default: panic(); break; case LINE_V: wire_box(c-WIRE_APPROACH,c1-WIRE_APPROACH,c+1+WIRE_APPROACH,c2+WIRE_APPROACH,FCF_WIRE_V); break; case LINE_H: wire_box(c1-WIRE_APPROACH,c-WIRE_APPROACH,c2+WIRE_APPROACH,c+1+WIRE_APPROACH,FCF_WIRE_H); break; } return(0); } void flood_remove(void) { XYDDHE e; void try(int x, int y, int dist) { FLOODCELL *c; if ((x < 0) || (y < 0) || (x >= arena_x) || (y >= arena_y)) return; c = &ARENA(x,y); if (c->remove <= dist) return; c->remove = dist; if (dist > maxremove) maxremove = dist; xyddh_put(&h,x,y,DIR_L,dist); } maxremove = 0; while (! xyddh_empty(&h)) { e = xyddh_get(&h); try(e.x-1,e.y,e.dist+1); try(e.x,e.y-1,e.dist+1); try(e.x+1,e.y,e.dist+1); try(e.x,e.y+1,e.dist+1); } } void clear_nonblocked(void) { int x; int y; FLOODCELL *c; for (y=arena_y-1;y>=0;y--) { for (x=arena_x-1;x>=0;x--) { c = &ARENA(x,y); if (c->flags & FCF_BLOCK) continue; c->dist[0] = MAXDIST; mem_replicate(&c->dist[0],sizeof(c->dist[0]),DIR__N); } } } void add_pin(IOPIN *pp) { int goal_x; int goal_y; XYDDHE he; FLOODCELL *c; LINEGEN lg; int setstart(LINELIST *l __attribute__((__unused__)), LINETYPE t, int c, int c1, int c2) { int k; FLOODCELL *fc; switch (t) { default: panic(); break; case LINE_V: c -= limits.x1; c1 -= limits.y1; c2 -= limits.y1; for (k=c1;kdist[0] = 0; mem_replicate(&fc->dist[0],sizeof(fc->dist[0]),DIR__N); fc->flags |= FCF_START; xyddh_put(&h,c,k,DIR_L,0); xyddh_put(&h,c,k,DIR_R,0); } break; case LINE_H: c -= limits.y1; c1 -= limits.x1; c2 -= limits.x1; for (k=c1;kdist[0] = 0; mem_replicate(&fc->dist[0],sizeof(fc->dist[0]),DIR__N); fc->flags |= FCF_START; xyddh_put(&h,k,c,DIR_U,0); xyddh_put(&h,k,c,DIR_D,0); } break; } return(0); } void try(int tx, int ty, int srcdir, int dir, int dist) { FLOODCELL *c; tx += DIR_DX(dir); ty += DIR_DY(dir); if ((tx < 0) || (ty < 0) || (tx >= arena_x) || (ty >= arena_y)) return; c = &ARENA(tx,ty); /* * Three interwoven switches, all in the name of having only one * copy of the interesting code, (ab)using somewhat the same * language feature as Duff's Device, but made more complex by * the control structure labels involved. * * This forms some kind of argument on the "should labeled * control structure exist?" question, but I'm not sure whether * it's on the "for" side or the "against" side. It's cool * that it's possible to do this, but it's arguably difficult * enough to read that duplicating the code would be the lower * price. */ switch <"wire"> (c->flags & (FCF_WIRE_H | FCF_WIRE_V)) { default <"wire">: return; break; case <"wire"> FCF_WIRE_H: switch <"dirh"> (dir) { case <"wire"> FCF_WIRE_V: switch <"dirv"> (dir) { case <"dirv"> DIR_L: case <"dirv"> DIR_R: case <"dirh"> DIR_U: case <"dirh"> DIR_D: dist += CROSS_COST; /* fall through */ case <"wire"> 0: dist += ((maxremove - c->remove) * (maxremove - c->remove)) / maxremove; if (dist < c->dist[dir]) { c->dist[dir] = dist; c->srcdir[dir] = srcdir; xyddh_put(&h,tx,ty,dir,dist); } break; } break; } break; } } switch (pp->type) { default: panic(); break; case PIN_INPUT: goal_x = pp->b->x - WIRE_APPROACH; goal_y = pp->b->y + (*pp->b->type->input_y)(pp->b->priv,pp->inx); pinconn = linelist_push(pinconn,LINE_H,goal_y,goal_x,goal_x+WIRE_APPROACH); break; case PIN_OUTPUT: goal_x = pp->b->x + pp->b->type->size.w + WIRE_APPROACH; goal_y = pp->b->y + (*pp->b->type->output_y)(pp->b->priv,pp->inx); pinconn = linelist_push(pinconn,LINE_H,goal_y,goal_x-WIRE_APPROACH,goal_x); break; } goal_x -= limits.x1; goal_y -= limits.y1; clear_nonblocked(); xyddh_init(&h); switch (pincount++) { case 0: pin0x = goal_x; pin0y = goal_y; pin0dir = (pp->type == PIN_INPUT) ? DIR_L : DIR_R; return; break; case 1: c = &ARENA(pin0x,pin0y); c->dist[pin0dir] = 0; c->flags |= FCF_START; xyddh_put(&h,pin0x,pin0y,pin0dir,0); break; default: linelist_map(sr->shape,&setstart); break; } c = &ARENA(goal_x,goal_y); c->dist[0] = MAXDIST; mem_replicate(&c->dist[0],sizeof(c->dist[0]),DIR__N); do <"found"> { while (! xyddh_empty(&h)) { he = xyddh_get(&h); if ((he.x == goal_x) && (he.y == goal_y)) break <"found">; c = &ARENA(he.x,he.y); try(he.x,he.y,he.dir,he.dir,he.dist+CELL_COST); try(he.x,he.y,he.dir,DIR_TURN_L(he.dir),he.dist+CELL_COST+TURN_COST); try(he.x,he.y,he.dir,DIR_TURN_R(he.dir),he.dist+CELL_COST+TURN_COST); } printf("Can't add pin to signal\n"); return; } while (0); linegen_init(&lg,&sr->shape,limits.x1,limits.y1); while (1) { linegen_point(&lg,he.x,he.y); c = &ARENA(he.x,he.y); if (c->flags & FCF_START) break; he.x -= DIR_DX(he.dir); he.y -= DIR_DY(he.dir); he.dir = c->srcdir[he.dir]; } linegen_done(&lg); } linelist_free_all(sr->shape); sr->shape = 0; limits.x1 = (~0U) >> 1; limits.x2 = - limits.x1; limits.y1 = limits.x1; limits.y2 = limits.x2; for (b=allblocks;b;b=b->flink) bbox_add_block(&limits,b); for (s=allsignals;s;s=s->flink) bbox_add_signal(&limits,s); limits.x1 -= WIRE_APPROACH + 1; limits.y1 -= WIRE_APPROACH + 1; limits.x2 += WIRE_APPROACH + 1; limits.y2 += WIRE_APPROACH + 1; arena_x = limits.x2 - limits.x1; arena_y = limits.y2 - limits.y1; arena_n = arena_x * arena_y; if (arena_n > arena_a) { free(arena); arena_a = arena_n; arena = malloc(arena_a*sizeof(*arena)); } init_arena(); xyddh_init(&h); for (b=allblocks;b;b=b->flink) arena_block_rect(b->x-WIRE_APPROACH,b->y-WIRE_APPROACH,b->x+b->type->size.w+WIRE_APPROACH,b->y+b->type->size.h+WIRE_APPROACH); for (s=allsignals;s;s=s->flink) linelist_map(s->shape,&arena_add_wire); flood_remove(); pincount = 0; pinconn = 0; if (sr->drive) add_pin(sr->drive); for (i=sr->n_driven-1;i>=0;i--) add_pin(sr->driven[i]); { LINELIST *l; while ((l = pinconn)) { pinconn = l->link; l->link = sr->shape; sr->shape = l; } } sr->shape = collapse_linelist(sr->shape); signal_map(sr); } static void maybe_remove_last_pin(SIGNAL *s) { if (s->drive && !s->driven) { signal_remove_pin(s,*s->drive); } else if ((s->n_driven == 1) && !s->drive) { signal_remove_pin(s,*s->driven[0]); } } static void signals_repair(void) { SIGNAL *s; while ((s = dsignals)) { dsignals = s->dflink; s->flags &= ~SF_DAMAGED; signal_repair(s); } } static void input_confirm_keystroke(INPUT_ARGS_KEYSTROKE) { switch (key) { case '\e': (*on_confirm)(-1); break; case 'n': (*on_confirm)(0); break; case 'y': (*on_confirm)(1); break; default: return; break; } XUnmapWindow(disp,confirmwin); input = confirm_input; set_curmenu(&menu_main); } static void input_confirm_motion(INPUT_ARGS_MOTION) { } static void input_confirm_button(INPUT_ARGS_BUTTON) { } static INPUTOPS input_confirm = INPUTOPS_INIT(confirm); static void confirm_popup(const char *line0, ...) { va_list ap; const char *line; int nlines; int i; int maxw; int h; const char **cl; int *ll; int *lw; int w_l; int w_r; int h_t; int h_b; Pixmap pm; if (! line0) panic(); nlines = 1; va_start(ap,line0); while (1) { line = va_arg(ap,const char *); if (! line) break; nlines ++; } va_end(ap); cl = malloc(nlines*sizeof(*cl)); ll = malloc(nlines*sizeof(*ll)); lw = malloc(nlines*sizeof(*lw)); cl[0] = line0; i = 1; va_start(ap,line0); while (1) { line = va_arg(ap,const char *); if (! line) break; if (i >= nlines) panic(); cl[i++] = line; } va_end(ap); if (i != nlines) panic(); maxw = 0; for (i=nlines-1;i>=0;i--) { ll[i] = strlen(cl[i]); lw[i] = widthof(cl[i],ll[i]); if (lw[i] > maxw) maxw = lw[i]; } maxw += font_space * 2; h = (nlines + 1) * font_baselineskip; pm = XCreatePixmap(disp,topwin,maxw,h,depth); XFillRectangle(disp,pm,gc_bg,0,0,maxw,h); for (i=nlines-1;i>=0;i--) { XDrawString(disp,pm,gc_fg,(maxw-lw[i])/2,(font_baselineskip/2)+font_baseline+(i*font_baselineskip),cl[i],ll[i]); } XSetWindowBackgroundPixmap(disp,confirmwin,pm); XClearWindow(disp,confirmwin); XFreePixmap(disp,pm); begin_popup(); w_l = maxw / 2; w_r = maxw - w_l; h_t = h / 2; h_b = h - h_t; if (maxw >= 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; } XMoveResizeWindow(disp,confirmwin,popup_cx-w_l-1,popup_cy-h_t-1,maxw,h); XMapWindow(disp,confirmwin); confirm_input = input; input = &input_confirm; } static void menu_impl_main_c(int arg __attribute__((__unused__))) { SIGNAL **sp0; SIGNAL **sp1; SIGNAL *s; IOPIN p; if (! selpin[1].b) { pop_error("Must select two pins"); return; } sp0 = signalp_for_pin(&selpin[0]); sp1 = signalp_for_pin(&selpin[1]); if (*sp0 && *sp1) { if (sp0[0]->drive && sp1[0]->drive) { pop_error("Both signals already driven"); } else { signal_merge(*sp0,*sp1); } } else if (!*sp0 && !*sp1) { if ((selpin[0].type == PIN_OUTPUT) && (selpin[1].type == PIN_OUTPUT)) { pop_error("Both pins are outputs"); return; } s = signal_new(); signal_add_pin(s,selpin[0]); signal_add_pin(s,selpin[1]); } else { if (*sp0) { s = *sp0; p = selpin[1]; } else { s = *sp1; p = selpin[0]; } if (s->drive && (p.type == PIN_OUTPUT)) { pop_error("Signal is already driven"); return; } signal_add_pin(s,p); } selpin[0].b->flags |= BF_DAMAGED; selpin[1].b->flags |= BF_DAMAGED; set_noselpins(); signals_repair(); } static void menu_impl_main_d(int arg __attribute__((__unused__))) { SIGNAL *s; if (! selpin[0].b) { pop_error("Must select a pin"); return; } if (selpin[1].b) { pop_error("Must select only one pin"); return; } s = *signalp_for_pin(&selpin[0]); if (! s) { pop_error("Pin not connected"); return; } selpin[0].b->flags |= BF_DAMAGED; signal_remove_pin(s,selpin[0]); maybe_remove_last_pin(s); set_noselpins(); signals_repair(); } static void main_m_block_drag_cancel(void) { (*drag_drop)(drag_arg,0,0,DROP_FAIL); stop_dragging(); } static int main_m_block_drop(void *bv, int x, int y, DROPOP op) { BLOCK *b; int i; b = bv; if (op == DROP_FAIL) { b->x = move_oldx; b->y = move_oldy; XCopyPlane(disp,drag_pm,drawwin,gc_fg,0,0,b->type->size.w,b->type->size.h,b->x,b->y,1); 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->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); for (i=b->type->n_in-1;i>=0;i--) signal_damage(b->inputs[i]); for (i=b->type->n_out-1;i>=0;i--) signal_damage(b->outputs[i]); signals_repair(); set_curmenu(&menu_main); } return(1); } static void menu_impl_main_m(int arg __attribute__((__unused__))) { if (! selblock) { pop_error("Must select a block"); return; } XClearArea(disp,drawwin,selblock->x,selblock->y,selblock->type->size.w,selblock->type->size.h,False); move_oldx = selblock->x; move_oldy = selblock->y; selblock->x = - selblock->type->size.w - (BLOCK_APPROACH_BLOCK * 2); selblock->y = - selblock->type->size.h - (BLOCK_APPROACH_BLOCK * 2); set_curmenu(&menu_cancel); cancel_it = &main_m_block_drag_cancel; start_dragging(selblock->type->size.w,selblock->type->size.h,&render_block,&main_m_block_drop,selblock); } static void menu_x_doit(int really) { int any; SIGNAL *s; int i; BLOCK *b; if (really <= 0) return; b = selblock; set_selblock(0); XClearArea(disp,drawwin,b->x,b->y,b->type->size.w,b->type->size.h,False); any = 0; for (i=b->type->n_in-1;i>=0;i--) { s = b->inputs[i]; if (s) { signal_remove_pin(s,(IOPIN){.b=b,.type=PIN_INPUT,.inx=i}); maybe_remove_last_pin(s); b->inputs[i] = 0; any = 1; } } for (i=b->type->n_out-1;i>=0;i--) { s = b->outputs[i]; if (s) { signal_remove_pin(s,(IOPIN){.b=b,.type=PIN_OUTPUT,.inx=i}); maybe_remove_last_pin(s); b->outputs[i] = 0; any = 1; } } if (b->flink) b->flink->blink = b->blink; if (b->blink) b->blink->flink = b->flink; else allblocks = b->flink; destroy_block(b); if (any) signals_repair(); } static void menu_impl_main_x(int arg __attribute__((__unused__))) { int i; if (! selblock) { pop_error("Must select a block"); return; } set_curmenu(&menu_confirm); on_confirm = &menu_x_doit; do <"found"> { for (i=selblock->type->n_in-1;i>=0;i--) if (selblock->inputs[i]) break <"found">; for (i=selblock->type->n_out-1;i>=0;i--) if (selblock->outputs[i]) break <"found">; confirm_popup("Destroy this block?",(const char *)0); return; } while (0); confirm_popup("NOTE: This block still has connections!","","Destroy this block?",(const char *)0); } static void menu_impl_main_r(int arg __attribute__((__unused__))) { SIGNAL *s1; SIGNAL *s2; if (! selpin[1].b) { pop_error("Must select two pins"); return; } s1 = *signalp_for_pin(&selpin[0]); s2 = *signalp_for_pin(&selpin[1]); if (!s1 && !s2) { pop_error("Both pins disconnected"); return; } if (s1 == s2) { pop_error("Pins are connected together"); return; } if ( ((selpin[0].type == PIN_OUTPUT) && (selpin[1].type != PIN_OUTPUT) && s2 && s2->drive) || ((selpin[1].type == PIN_OUTPUT) && (selpin[0].type != PIN_OUTPUT) && s1 && s1->drive) ) { pop_error("Would connect two outputs together"); return; } selpin[0].b->flags |= BF_DAMAGED; selpin[1].b->flags |= BF_DAMAGED; if (s1) signal_remove_pin(s1,selpin[0]); if (s2) signal_remove_pin(s2,selpin[1]); if (s1) signal_add_pin(s1,selpin[1]); if (s2) signal_add_pin(s2,selpin[0]); set_noselpins(); signals_repair(); } static void gen_setup(void) { BLOCK *b; SIGNAL *s; for (b=allblocks;b;b=b->flink) (*b->type->run_init)(b->priv); for (s=allsignals;s;s=s->flink) s->nextv = 0; } static void gen_step(void) { BLOCK *b; SIGNAL *s; int i; static int va = 0; static double *vv = 0; double v; for (s=allsignals;s;s=s->flink) s->lastv = s->nextv; for (b=allblocks;b;b=b->flink) { if (b->type->n_in > va) { free(vv); va = b->type->n_in; vv = malloc(va*sizeof(*vv)); } for (i=b->type->n_in-1;i>=0;i--) vv[i] = b->inputs[i]->lastv; (*b->type->run_step)(b->priv,vv); } for (s=allsignals;s;s=s->flink) { b = s->drive->b; for (i=b->type->n_in-1;i>=0;i--) vv[i] = b->inputs[i]->lastv; v = (*b->type->run_out)(b->priv,vv,s->drive->inx); if (v < -1) v = -1; if (v > 1) v = 1; s->nextv = v; } } static void gen_done(void) { } static void main_W_got_file(int ok) { int fd; FILE *f; int nsamp; if (! ok) { XBell(disp,0); return; } fd = open(W_file,O_WRONLY|O_CREAT|O_TRUNC,0666); if (fd < 0) { pop_error("Can't open %s: %s",W_file,strerror(errno)); return; } f = fdopen(fd,"w"); if (! f) { pop_error("Can't fdopen %s: %s",W_file,strerror(errno)); close(fd); return; } out_set_file(f); gen_setup(); for (nsamp=W_time*44100;nsamp>0;nsamp--) gen_step(); gen_done(); out_set_file(0); fclose(f); } // XXX Maybe try to check paths as they're entered? static CHKRV chk_main_W_file(const char *s) { free(W_file); W_file = strdup(s); return((CHKRV){.status=STAT_GOOD,.ngood=strlen(s)}); } static void main_W_got_time(int ok) { if (! ok) { XBell(disp,0); return; } popup_edit("To file",0,0,0,&chk_main_W_file,&main_W_got_file); } static CHKRV chk_main_W_time(const char *s) { char *ep; double d; d = strtod(s,&ep); if ((ep == s) || *ep) return((CHKRV){.status=STAT_BADC,.ngood=ep-s}); if ((d <= 0) || (d > 43200)) return((CHKRV){.status=STAT_RANGE,.ngood=ep-s}); W_time = d; return((CHKRV){.status=STAT_GOOD,.ngood=ep-s}); } // Must have exactly one out block which isn't undefined static int cannot_write(void) { BLOCK *b; int i; BLOCK *o; SIGNAL *s; set_selblock(0); set_noselpins(); o = 0; for (b=allblocks;b;b=b->flink) { for (i=b->type->n_in-1;i>=0;i--) { if (! b->inputs[i]) { set_selblock(b); set_selpin(b,PIN_INPUT,i); pop_error("This block has a disconnected input"); return(1); } if (! b->inputs[i]->drive) { set_selblock(b); set_selpin(b,PIN_INPUT,i); pop_error("This block has an undriven input"); return(1); } } if (b->type == &block_output) { if (o) { pop_error("Multiple output blocks"); return(1); } o = b; } } if (! o) { pop_error("No output blocks"); return(1); } for (s=allsignals;s;s=s->flink) { if (!s->drive || (s->drive->type != PIN_OUTPUT)) panic(); } return(0); } static void menu_impl_main_W(int arg __attribute__((__unused__))) { if (cannot_write()) return; popup_edit("Duration","(seconds)",0,0,&chk_main_W_time,&main_W_got_time); } static void main_p_resetup_done(void *bv, int ok) { BLOCK *b; static Pixmap pm = None; static int pm_w = -1; static int pm_h = -1; if (! ok) return; b = bv; if ((b->type->size.w > pm_w) || (b->type->size.h > pm_h)) { if (pm != None) XFreePixmap(disp,pm); if (b->type->size.w > pm_w) pm_w = b->type->size.w; if (b->type->size.h > pm_h) pm_h = b->type->size.h; pm = XCreatePixmap(disp,rootwin,pm_w,pm_h,1); } (*b->type->render)(b->priv,(Drawable)pm,gc_1bpp_0,gc_1bpp_1); XCopyArea(disp,pm,b->img,gc_1bpp_1,0,0,b->type->size.w,b->type->size.h,0,0); XCopyPlane(disp,pm,drawwin,gc_fg,0,0,b->type->size.w,b->type->size.h,b->x,b->y,1); } static void menu_impl_main_p(int arg __attribute__((__unused__))) { BLOCK *b; if (! selblock) { pop_error("Must select a block"); return; } b = selblock; selblock = 0; XClearArea(disp,drawwin,b->x,b->y,b->type->size.w,b->type->size.h,False); (*b->type->resetup)(b->priv,main_p_resetup_done,b); } static MENUITEM menu_main_items[] = { { "Quit", 'Q', &menu_impl_main_Q }, { "Write", 'W', &menu_impl_main_W }, MENUITEM_BLANK(), { "New block", 'n', &menu_impl_main_n }, { "Destroy", 'x', &menu_impl_main_x }, { "Param change", 'p', &menu_impl_main_p }, MENUITEM_BLANK(), { "Connect", 'c', &menu_impl_main_c }, { "Disconnect", 'd', &menu_impl_main_d }, { "Reconnect", 'r', &menu_impl_main_r }, { "Move", 'm', &menu_impl_main_m } }; 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); static void menu_impl_confirm(int arg) { set_curmenu(confirm_menu); (*on_confirm)(arg); } static MENUITEM menu_confirm_items[] = { { "Cancel", '\e', &menu_impl_confirm, "ESC", -1 }, { "", -1, 0, "" }, { "Yes", 'y', &menu_impl_confirm, "Y", 1 }, { "No", 'n', &menu_impl_confirm, "N", 0 } }; static MENU menu_confirm = MENU_INIT(confirm); 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); setup_menu(&menu_confirm); 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->flink) { 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_noselpins(); repair_block_damage(); return; } } } set_selblock(0); set_noselpins(); 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_pm_w = -1; drag_pm_h = -1; drag_pm = None; drag_pm_r = None; drag_pm_g = None; } static void init_objects(void) { allblocks = 0; allsignals = 0; dsignals = 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 CHKRV popup_chk_time(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; if (d < 0) return((CHKRV){.status=STAT_RANGE,.ngood=0}); 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 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, const char *initstr, int initlen, 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; if (initlen) { popup_room(initlen); bcopy(initstr,popup_buf,initlen); } popup_buflen = initlen; popup_bufcurs = initlen; 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 def) { 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); if (def >= 0) { popup_room(chk_choices[def].len); popup_buflen = chk_choices[def].len; bcopy(chk_choices[def].str,popup_buf,chk_choices[def].len); } else { popup_buflen = 0; } popup_bufcurs = popup_buflen; 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; char *s; int l; getarg_cont = cont; getarg_contarg = contarg; popup_valp = ploc; l = 0; s = 0; switch (ptype.kind) { case PAR_SIGNAL: chk = &popup_chk_signal; rangestr = "[-1..1]"; if (ptype.flags & PTF_DEFVAL) l = asprintf(&s,"%g",*(double *)ploc); break; case PAR_AMP: chk = &popup_chk_amp; rangestr = 0; if (ptype.flags & PTF_DEFVAL) l = asprintf(&s,"%g",*(double *)ploc); break; case PAR_FREQ: chk = &popup_chk_freq; rangestr = "[1pHz..1GHz]"; if (ptype.flags & PTF_DEFVAL) l = asprintf(&s,"%g",*(double *)ploc); 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; if (ptype.flags & PTF_DEFVAL) l = asprintf(&s,"%g",*(double *)ploc); 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; if (ptype.flags & PTF_DEFVAL) l = asprintf(&s,"%d",*(int *)ploc); 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