ref: 712fd30652d29dc9e936f11d7837d1cb079575fc
parent: 555a05018b60b28bdfd6ada0310848c68fe20e48
author: aiju <devnull@localhost>
date: Wed Jul 30 11:57:14 EDT 2014
added sprite editor spred
--- /dev/null
+++ b/sys/man/1/spred
@@ -1,0 +1,58 @@
+b.TH SPRED 1
+.SH NAME
+spred \- sprite editor
+.SH SYNOPSIS
+.B spred
+.SH DESCRIPTION
+.I Spred
+is an editor for small images using a limited palette.
+It uses a window system mimicking
+.IR samterm (1).
+There is a command window which uses a command language described below.
+There is also an arbitrary number of palette and sprite windows.
+Each open sprite file has an associated palette file.
+.PP
+A left click on a color in a palette window selects that color.
+Colors in different palettes can be selected indepedently.
+A left click on a pixel in a sprite window sets that pixel to the selected color.
+.PP
+A right click brings up the global menu to create windows etc.
+It also lists all currently open files, including those that are not open in any window.
+A middle click brings up the menu for the local window, if applicable. Available commands there are:
+.TP
+.I pal
+The \fIpal\fR command sets the palette for the current sprite window. The palette is selected with a middle click.
+.PP
+The command language is a very stripped down version of
+.IR rc (1),
+currently only supporting "simple" commands consisting of a name and an arbitrary number of arguments separated by spaces. Quoting works just like with
+.IR rc (1).
+Available commands are:
+.TP
+.B q
+Quits the program. If any files have unsaved changes, it will fail on the first attempt to quit.
+.TP
+.BI pal file
+.TP
+.BI spr file
+Open a palette (\fIpal\fR) or sprite (\fIspr\fR) file named \fIfile\fR.
+If the file does not exist it is created.
+.TP
+.BI w [file]
+Write the currently selected file to \fIfile\fR. If \fIfile\fR is not specified, the name specified to the command opening the file is used.
+.TP
+.BI size sz
+Sets the size of the current file to \fIsz\fR.
+\fISz\fR should be of the form \fIn\fR for palettes or \fIn\fB*\fIm\fR for sprites where \fIn\fR and \fIm\fR are integers.
+.TP
+.B set 0x\fIrrggbb\fR
+Sets the currently selected color to the rgb color \fI(rr,gg,bb)\fR where
+.IR rr , gg and bb
+are in hexadecimal notation.
+.TP
+.B zoom n
+Sets the current zoom factor to \fIn\fR.
+.SH SOURCE
+.B /sys/src/cmd/spred
+.SH SEE ALSO
+.IR sam (1)
--- /dev/null
+++ b/sys/src/cmd/spred/cmd.c
@@ -1,0 +1,188 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <frame.h>
+#include "dat.h"
+#include "fns.h"
+
+extern Mousectl *mc;
+
+static void
+dopal(int, char **argv)
+{
+ Pal *p;
+
+ p = findpal("", argv[1], 2);
+ if(p == nil){
+ cmdprint("?%r\n");
+ p = newpal(argv[1]);
+ }
+ if(newwinsel(PAL, mc, p) == nil){
+ if(p->ref == 0)
+ putfil(p);
+ return;
+ }
+}
+
+static void
+dosize(int, char **argv)
+{
+ char *p;
+ int n, m;
+
+ if(actf == nil)
+ goto err;
+ switch(actf->type){
+ case PAL:
+ n = strtol(argv[1], &p, 0);
+ if(*p != 0 || n < 0)
+ goto err;
+ palsize((Pal *) actf->f, n);
+ return;
+ case SPR:
+ n = strtol(argv[1], &p, 0);
+ if(*p != '*' || n < 0)
+ goto err;
+ m = strtol(++p, &p, 0);
+ if(*p != 0 || m < 0)
+ goto err;
+ sprsize((Spr *) actf->f, n, m);
+ return;
+ }
+err:
+ cmdprint("?\n");
+}
+
+static void
+doset(int, char **argv)
+{
+ int n;
+ char *p;
+ Pal *q;
+
+ if(actf == nil)
+ goto err;
+ switch(actf->type){
+ case PAL:
+ n = strtol(argv[1], &p, 0);
+ if(*p != 0 || n < 0 || n > 0xffffff)
+ goto err;
+ q = (Pal *) actf->f;
+ palset(q, q->sel, n);
+ return;
+ }
+err:
+ cmdprint("?\n");
+}
+
+static void
+dozoom(int, char **argv)
+{
+ int n;
+ char *p;
+
+ if(actf == nil)
+ goto err;
+ n = strtol(argv[1], &p, 0);
+ if(*p != 0 || n <= 0)
+ goto err;
+ actf->zoom = n;
+ actf->tab->draw(actf);
+ return;
+err:
+ cmdprint("?\n");
+}
+
+static void
+dospr(int, char **argv)
+{
+ Win *w;
+ Spr *s;
+ Biobuf *bp;
+
+ s = newspr(argv[1]);
+ bp = Bopen(argv[1], OREAD);
+ if(bp == nil)
+ cmdprint("?%r\n");
+ else{
+ if(readspr(s, bp) < 0)
+ cmdprint("?%r\n");
+ Bterm(bp);
+ }
+ w = newwinsel(SPR, mc, s);
+ if(w == nil){
+ putfil(s);
+ return;
+ }
+ if(s->palfile != nil){
+ s->pal = findpal(argv[1], s->palfile, 1);
+ if(s->pal == nil)
+ cmdprint("?palette: %r\n");
+ else{
+ incref(s->pal);
+ w->tab->draw(w);
+ }
+ }
+}
+
+static void
+dowrite(int argc, char **argv)
+{
+ char *f;
+
+ if(argc > 2)
+ cmdprint("?\n");
+ if(argc == 2)
+ f = argv[1];
+ else
+ f = nil;
+ if(actf == nil)
+ cmdprint("?\n");
+ winwrite(actf, f);
+}
+
+static void
+doquit(int, char **)
+{
+ if(quit() < 0)
+ threadexitsall(nil);
+}
+
+static struct cmd {
+ char *name;
+ int argc;
+ void (*f)(int, char **);
+} cmds[] = {
+ {"pal", 2, dopal},
+ {"size", 2, dosize},
+ {"set", 2, doset},
+ {"spr", 2, dospr},
+ {"w", 0, dowrite},
+ {"q", 1, doquit},
+ {"zoom", 2, dozoom},
+ {nil, nil}
+};
+
+void
+docmd(char *s)
+{
+ char *t[32];
+ int nt;
+ struct cmd *c;
+
+ nt = tokenize(s, t, nelem(t));
+ if(nt == 0)
+ return;
+ for(c = cmds; c->name != 0; c++)
+ if(strcmp(t[0], c->name) == 0){
+ if(c->argc != 0 && c->argc != nt)
+ cmdprint("?\n");
+ else
+ c->f(nt, t);
+ return;
+ }
+ cmdprint("?\n");
+}
--- /dev/null
+++ b/sys/src/cmd/spred/cmdw.c
@@ -1,0 +1,199 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <frame.h>
+#include "dat.h"
+#include "fns.h"
+
+static int
+cmdinit(Win *)
+{
+ return 0;
+}
+
+static void
+scrollbar(Win *w)
+{
+ int h, t0, t1;
+
+ h = Dy(w->inner);
+ draw(w->im, rectaddpt(Rect(0, 0, SCRBSIZ+1, h), w->inner.min), w->tab->cols[BORD], nil, ZP);
+ t0 = w->toprune * h;
+ t1 = (w->toprune + w->fr.nchars) * h;
+ if(w->nrunes == 0){
+ t0 = 0;
+ t1 = h;
+ }else{
+ t0 /= w->nrunes;
+ t1 /= w->nrunes;
+ }
+ draw(w->im, rectaddpt(Rect(0, t0, SCRBSIZ, t1), w->inner.min), w->tab->cols[BACK], nil, ZP);
+}
+
+static void
+cmddraw(Win *w)
+{
+ Rectangle r;
+
+ frclear(&w->fr, 0);
+ r = insetrect(w->inner, 1);
+ r.min.x += SCRTSIZ;
+ scrollbar(w);
+ frinit(&w->fr, r, display->defaultfont, w->im, w->tab->cols);
+ frinsert(&w->fr, w->runes + w->toprune, w->runes + w->nrunes, 0);
+}
+
+void
+cmdscroll(Win *w, int l)
+{
+ int r;
+
+ if(l == 0)
+ return;
+ if(l > 0){
+ for(r = w->toprune; r < w->nrunes && l != 0; r++)
+ if(w->runes[r] == '\n')
+ l--;
+ frdelete(&w->fr, 0, r - w->toprune);
+ w->toprune = r;
+ }else{
+ for(r = w->toprune; r > 0; r--)
+ if(w->runes[r] == '\n' && --l == 0)
+ break;
+ frinsert(&w->fr, w->runes + r, w->runes + w->toprune, 0);
+ w->toprune = r;
+ }
+ scrollbar(w);
+}
+
+static void
+cmdclick(Win *w, Mousectl *mc)
+{
+ if(mc->xy.x <= w->inner.min.x + SCRBSIZ){
+ cmdscroll(w, -5);
+ return;
+ }
+ frselect(&w->fr, mc);
+}
+
+static int
+cmdrmb(Win *w, Mousectl *mc)
+{
+ if(mc->xy.x > w->inner.min.x + SCRBSIZ)
+ return -1;
+ cmdscroll(w, 5);
+ return 0;
+}
+
+void
+cmdinsert(Win *w, Rune *r, int nr, int rp)
+{
+ Rune *s;
+
+ if(nr < 0)
+ for(nr = 0, s = r; *s++ != 0; nr++)
+ ;
+ if(rp < 0 || rp > w->nrunes)
+ rp = w->nrunes;
+ if(w->nrunes + nr > w->arunes){
+ w->runes = realloc(w->runes, w->arunes = w->arunes + (nr + RUNEBLK - 1) & ~(RUNEBLK - 1));
+ if(w->runes == nil)
+ sysfatal("realloc: %r");
+ }
+ if(rp != w->nrunes)
+ memmove(w->runes + rp, w->runes + rp + nr, (w->nrunes - rp) * sizeof(Rune));
+ memmove(w->runes + rp, r, nr * sizeof(Rune));
+ w->nrunes += nr;
+ if(w->toprune > rp)
+ w->toprune += nr;
+ else{
+ frinsert(&w->fr, w->runes + rp, w->runes + rp + nr, rp - w->toprune);
+ if(rp == w->nrunes - nr){
+ if(w->fr.lastlinefull)
+ cmdscroll(w, 1);
+ }
+ }
+}
+
+static void
+cmddel(Win *w, int a, int b)
+{
+ if(a >= b)
+ return;
+ memmove(w->runes + a, w->runes + b, w->nrunes - b);
+ w->nrunes -= b - a;
+ if(w->toprune >= b)
+ w->toprune -= b - a;
+ else{
+ frdelete(&w->fr, a - w->toprune, b - w->toprune);
+ if(w->toprune >= a)
+ w->toprune = a;
+ }
+}
+
+static void
+cmdkey(Win *w, Rune r)
+{
+ static char buf[4096];
+ char *p;
+ Rune *q;
+
+ if(w->fr.p0 < w->fr.p1)
+ cmddel(w, w->toprune + w->fr.p0, w->toprune + w->fr.p1);
+ switch(r){
+ case 0x00:
+ case 0x1b:
+ break;
+ case '\b':
+ if(w->fr.p0 > 0)
+ cmddel(w, w->toprune + w->fr.p0 - 1, w->toprune + w->fr.p0);
+ break;
+ case '\n':
+ cmdinsert(w, &r, 1, w->fr.p0 + w->toprune);
+ if(w->toprune + w->fr.p0 == w->nrunes){
+ q = w->runes + w->toprune + w->fr.p0 - 1;
+ p = buf;
+ while(*--q != 0xa && q > w->runes)
+ ;
+ if(*q == 0xa)
+ q++;
+ while(q < w->runes + w->nrunes && p < buf + nelem(buf) + 1 && *q != 0xa)
+ p += runetochar(p, q++);
+ *p = 0;
+ docmd(buf);
+ }
+ break;
+ default:
+ cmdinsert(w, &r, 1, w->fr.p0 + w->toprune);
+ }
+}
+
+void
+cmdprint(char *fmt, ...)
+{
+ Rune *r;
+ va_list va;
+
+ va_start(va, fmt);
+ r = runevsmprint(fmt, va);
+ va_end(va);
+ if(r != nil)
+ cmdinsert(cmdw, r, -1, -1);
+}
+
+Wintab cmdtab = {
+ .init = cmdinit,
+ .draw = cmddraw,
+ .click = cmdclick,
+ .rmb = cmdrmb,
+ .key = cmdkey,
+ .hexcols = {
+ [BORD] DPurpleblue,
+ [DISB] 0xCCCCEEFF,
+ [BACK] 0xCCFFFFFF,
+ [HIGH] DPalegreygreen
+ }
+};
--- /dev/null
+++ b/sys/src/cmd/spred/dat.h
@@ -1,0 +1,98 @@
+typedef struct Ident Ident;
+typedef struct Win Win;
+typedef struct Wintab Wintab;
+typedef struct Pal Pal;
+typedef struct Spr Spr;
+typedef struct File File;
+
+enum {
+ BORDSIZ = 5,
+ MINSIZ = 3 * BORDSIZ,
+ SELSIZ = 2,
+ SCRBSIZ = 11,
+ SCRTSIZ = 14,
+ RUNEBLK = 4096,
+};
+
+enum {
+ DISB = NCOL,
+ NCOLS
+};
+
+enum {
+ CMD,
+ PAL,
+ SPR,
+ NTYPES
+};
+
+struct Wintab {
+ int (*init)(Win *);
+ void (*die)(Win *);
+ void (*click)(Win *, Mousectl *);
+ void (*menu)(Win *, Mousectl *);
+ int (*rmb)(Win *, Mousectl *);
+ void (*key)(Win *, Rune);
+ void (*draw)(Win *);
+ void (*zerox)(Win *, Win *);
+ u32int hexcols[NCOLS];
+ Image *cols[NCOLS];
+};
+
+struct Win {
+ Rectangle entire;
+ Rectangle inner;
+ Image *im;
+ Win *next, *prev;
+ Win *wnext, *wprev;
+ int type;
+ Wintab *tab;
+
+ Frame fr;
+ Rune *runes;
+ int nrunes, arunes;
+ int toprune;
+
+ int zoom;
+ Point scr;
+ File *f;
+ Rectangle sprr;
+};
+
+struct Ident {
+ uint type, dev;
+ Qid;
+};
+
+struct File {
+ int type;
+ Ref;
+ File *next, *prev;
+ char *name;
+ int change;
+ Ident id;
+ Win wins;
+};
+
+struct Pal {
+ File;
+ int ncol;
+ u32int *cols;
+ Image **ims;
+ int sel;
+};
+
+struct Spr {
+ File;
+ Pal *pal;
+ int w, h;
+ u32int *data;
+ char *palfile;
+};
+
+extern Win wlist;
+extern File flist;
+extern Win *actw, *actf, *cmdw;
+extern Screen *scr;
+extern Image *invcol;
+extern int quitok;
--- /dev/null
+++ b/sys/src/cmd/spred/fil.c
@@ -1,0 +1,157 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <frame.h>
+#include "dat.h"
+#include "fns.h"
+
+int
+tline(Biobuf *bp, char **str, char **args, int max)
+{
+ char *s, *p;
+ int q, dq, rc;
+
+ do{
+ s = Brdstr(bp, '\n', 10);
+ if(s == nil)
+ return -1;
+ q = dq = 0;
+ for(p = s; *p != 0; p++)
+ if(*p == '\'')
+ dq = !dq;
+ else{
+ if(dq){
+ q = !q;
+ dq = 0;
+ }
+ if(*p == '#' && !q){
+ *p = 0;
+ break;
+ }
+ }
+ rc = tokenize(s, args, max);
+ }while(rc == 0 && (free(s), 1));
+ *str = s;
+ return rc;
+}
+
+Ident
+getident(int fd)
+{
+ Dir *d;
+ Ident i;
+
+ d = dirfstat(fd);
+ if(d == nil)
+ return (Ident){-1, -1, (Qid){0, 0, 0}};
+ i = (Ident){d->type, d->dev, d->qid};
+ free(d);
+ return i;
+}
+
+void
+putident(Ident)
+{
+}
+
+int
+identcmp(Ident *a, Ident *b)
+{
+ return a->type != b->type || a->dev != b->dev || a->path != b->path;
+}
+
+int
+filcmp(File *f, File *g)
+{
+ if(f->type != g->type)
+ return f->type - g->type;
+ if(f->name == nil || g->name == nil)
+ return -1;
+ return strcmp(f->name, g->name);
+}
+
+void
+filinit(File *f, char *t)
+{
+ File *g;
+
+ f->wins.wnext = f->wins.wprev = &f->wins;
+ f->name = strdup(t);
+ for(g = flist.next; g != &flist && filcmp(g, f) < 0; g = g->next)
+ ;
+ f->prev = g->prev;
+ f->next = g;
+ g->prev->next = f;
+ g->prev = f;
+}
+
+void
+putfil(File *f)
+{
+ switch(f->type){
+ case PAL: putpal((Pal *) f); break;
+ case SPR: putspr((Spr *) f); break;
+ }
+ f->prev->next = f->next;
+ f->next->prev = f->prev;
+ free(f->name);
+ free(f);
+}
+
+static char phasetitle[] = "??? phase error ???";
+
+int
+filtitlelen(File *f)
+{
+ if(f->name != nil)
+ return utflen(f->name) + 4;
+ return strlen(phasetitle);
+}
+
+char *
+filtitle(File *f, char *s, char *e)
+{
+ if(f->name == nil)
+ return strecpy(s, e, phasetitle);
+ *s++ = f->change ? '\'' : ' ';
+ if(f->wins.wnext != &f->wins)
+ if(f->wins.wnext->wnext != &f->wins)
+ *s++ = '*';
+ else
+ *s++ = '+';
+ else
+ *s++ = '-';
+ *s++ = actf != nil && f == actf->f ? '.' : ' ';
+ *s++ = ' ';
+ return strecpy(s, e, f->name);
+}
+
+void
+winwrite(Win *w, char *f)
+{
+ if(w->f == nil){
+ cmdprint("?\n");
+ return;
+ }
+ switch(w->type){
+ case PAL:
+ writepal((Pal *) w->f, f);
+ return;
+ case SPR:
+ writespr((Spr *) w->f, f);
+ return;
+ }
+ cmdprint("?\n");
+}
+
+void
+filredraw(File *f)
+{
+ Win *w;
+
+ for(w = f->wins.wnext; w != &f->wins; w = w->wnext)
+ w->tab->draw(w);
+}
--- /dev/null
+++ b/sys/src/cmd/spred/fns.h
@@ -1,0 +1,37 @@
+void cmdprint(char *, ...);
+void docmd(char *);
+void* emalloc(ulong);
+void filinit(File *, char *);
+void filredraw(File *);
+char* filtitle(File *, char *, char *);
+int filtitlelen(File *);
+Pal* findpal(char *, char *, int);
+Ident getident(int);
+int identcmp(Ident*, Ident *);
+void initwin(void);
+Pal* newpal(char *);
+Spr* newspr(char *);
+Win* newwin(int, Rectangle, File *);
+Win* newwinsel(int, Mousectl *, File *);
+void paldraw(Win *);
+void palset(Pal *, int, u32int);
+void palsize(Pal *, int);
+void putfil(File *);
+void putident(Ident);
+void putpal(Pal *);
+void putspr(Spr *);
+int quit(void);
+int readpal(Pal *, Biobuf *);
+int readspr(Spr *, Biobuf *);
+void resize(void);
+void setfocus(Win *);
+void sprsize(Spr *, int, int);
+int tline(Biobuf *, char **, char **, int);
+void winclick(Mousectl *);
+void winclose(Win *);
+void winresize(Win *, Mousectl *);
+Win* winsel(Mousectl*, int);
+void winwrite(Win *, char *);
+void winzerox(Win *, Mousectl *);
+int writepal(Pal *, char *);
+int writespr(Spr *, char *);
--- /dev/null
+++ b/sys/src/cmd/spred/mkfile
@@ -1,0 +1,16 @@
+</$objtype/mkfile
+
+TARG=spred
+BIN=/$objtype/bin
+OFILES=\
+ spred.$O\
+ win.$O\
+ cmd.$O\
+ pal.$O\
+ cmdw.$O\
+ spr.$O\
+ fil.$O\
+
+HFILES=dat.h fns.h
+
+</sys/src/cmd/mkone
--- /dev/null
+++ b/sys/src/cmd/spred/pal.c
@@ -1,0 +1,280 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "dat.h"
+#include "fns.h"
+
+Pal *
+newpal(char *f)
+{
+ Pal *p;
+
+ p = emalloc(sizeof(*p));
+ p->type = PAL;
+ p->sel = -1;
+ filinit(p, f);
+ return p;
+}
+
+void
+putpal(Pal *p)
+{
+ int i;
+
+ for(i = 0; i < p->ncol; i++)
+ freeimage(p->ims[i]);
+ free(p->cols);
+ free(p->ims);
+}
+
+int
+readpal(Pal *p, Biobuf *bp)
+{
+ char *s, *sp;
+ char *args[8];
+ int nc, i, c;
+
+ s = nil;
+ if(tline(bp, &s, args, nelem(args)) != 2)
+ goto err;
+ if(strcmp(args[0], "pal") != 0)
+ goto err;
+ nc = strtol(args[1], &sp, 0);
+ if(*sp != 0 || nc < 0)
+ goto err;
+ free(s);
+ s = nil;
+ p->ncol = nc;
+ p->cols = emalloc(nc * sizeof(*p->cols));
+ p->ims = emalloc(nc * sizeof(*p->ims));
+ for(i = 0; i < nc; i++){
+ if(tline(bp, &s, args, nelem(args)) != 1)
+ goto err;
+ c = strtol(args[0], &sp, 0);
+ if(*sp != 0 || c < 0 || c > 0xffffff)
+ goto err;
+ p->cols[i] = c;
+ free(s);
+ s = nil;
+ }
+ for(i = 0; i < nc; i++)
+ p->ims[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, p->cols[i] << 8 | 0xff);
+ p->id = getident(bp->fid);
+ return 0;
+err:
+ if(s != nil)
+ free(s);
+ werrstr("invalid format");
+ return -1;
+}
+
+int
+writepal(Pal *p, char *f)
+{
+ Biobuf *bp;
+ int i, rc, n;
+
+ if(f == nil)
+ f = p->name;
+ bp = Bopen(f, OWRITE);
+ if(bp == nil){
+ cmdprint("?%r\n");
+ return -1;
+ }
+ n = 0;
+ rc = Bprint(bp, "pal %d\n", p->ncol);
+ if(rc < 0) goto err;
+ n += rc;
+ for(i = 0; i < p->ncol; i++){
+ rc = Bprint(bp, "%#.6x\n", p->cols[i]);
+ if(rc < 0) goto err;
+ n += rc;
+ }
+ if(Bterm(bp) < 0){
+ cmdprint("?%r\n");
+ return -1;
+ }
+ p->change = 0;
+ cmdprint("%s: #%d\n", f, n);
+ return 0;
+err:
+ cmdprint("?%r\n");
+ Bterm(bp);
+ return -1;
+}
+
+Pal *
+findpal(char *sf, char *fn, int op)
+{
+ File *f;
+ char *s, *q;
+ Ident i;
+ int fd;
+ Biobuf *bp;
+ Pal *p;
+
+ if(sf == nil)
+ sf = "";
+ s = emalloc(strlen(sf) + strlen(fn) + 2);
+ strcpy(s, sf);
+ q = strrchr(s, '/');
+ if(q != nil)
+ *++q = 0;
+ else
+ *s = 0;
+ strcpy(s, fn);
+ fd = open(s, OREAD);
+ if(fd < 0){
+ free(s);
+ return nil;
+ }
+ i = getident(fd);
+ if(i.type == (uint)-1){
+ close(fd);
+ return nil;
+ }
+ for(f = flist.next; f != &flist; f = f->next)
+ if(f->type == PAL && identcmp(&f->id, &i) == 0){
+ close(fd);
+ putident(i);
+ return (Pal *) f;
+ }
+ putident(i);
+ if(op == 0){
+ close(fd);
+ return nil;
+ }
+ bp = emalloc(sizeof(*bp));
+ Binit(bp, fd, OREAD);
+ p = newpal(s);
+ if(readpal(p, bp) < 0){
+ putfil(p);
+ p = nil;
+ goto end;
+ }
+end:
+ Bterm(bp);
+ close(fd);
+ free(bp);
+ free(s);
+ return p;
+}
+
+static void
+palredraw(Pal *p)
+{
+ File *f;
+
+ filredraw(p);
+ for(f = flist.next; f != &flist; f = f->next)
+ if(f->type == SPR && ((Spr *) f)->pal == p)
+ filredraw(f);
+}
+
+void
+palsize(Pal *p, int sz)
+{
+ int i;
+
+ if(sz == p->ncol)
+ return;
+ p->cols = realloc(p->cols, sz * sizeof(*p->cols));
+ p->ims = realloc(p->ims, sz * sizeof(*p->ims));
+ if(sz > p->ncol){
+ memset(p->cols + p->ncol, 0, sz);
+ for(i = p->ncol; i < sz; i++)
+ p->ims[i] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, 0);
+ }
+ p->ncol = sz;
+ p->change = 1;
+ quitok = 0;
+ palredraw(p);
+}
+
+void
+paldraw(Win *w)
+{
+ Pal *p;
+ int n, i;
+ Rectangle r;
+
+ if(w->type != PAL || w->f == nil)
+ sysfatal("paldraw: phase error");
+ p = (Pal *) w->f;
+ n = Dx(w->inner) / w->zoom;
+ draw(w->im, w->inner, w->tab->cols[BACK], nil, ZP);
+ for(i = 0; i < p->ncol; i++){
+ r.min = addpt(w->inner.min, mulpt(Pt(i%n, i/n), w->zoom));
+ r.max.x = r.min.x + w->zoom;
+ r.max.y = r.min.y + w->zoom;
+ draw(w->im, r, p->ims[i], nil, ZP);
+ if(p->sel == i)
+ border(w->im, r, SELSIZ, display->white, ZP);
+ }
+}
+
+void
+palset(Pal *p, int s, u32int c)
+{
+ if(s < 0 || s >= p->ncol || p->cols[s] == c)
+ return;
+ p->cols[s] = c;
+ freeimage(p->ims[s]);
+ p->ims[s] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, c << 8 | 0xff);
+ p->change = 1;
+ quitok = 0;
+ palredraw(p);
+}
+
+static int
+palinit(Win *w)
+{
+ w->zoom = 32;
+ return 0;
+}
+
+static void
+palzerox(Win *w, Win *v)
+{
+ v->zoom = w->zoom;
+}
+
+static void
+palclick(Win *w, Mousectl *mc)
+{
+ int n, i;
+ Point pt;
+ Pal *p;
+
+ if(!ptinrect(mc->xy, w->inner))
+ return;
+ if(w->f == nil)
+ sysfatal("palclick: phase error");
+ p = (Pal *) w->f;
+ n = Dx(w->inner) / w->zoom;
+ pt = subpt(mc->xy, w->inner.min);
+ if(pt.x >= n * w->zoom)
+ return;
+ i = pt.x / w->zoom + pt.y / w->zoom * n;
+ if(i >= p->ncol)
+ return;
+ p->sel = i;
+ palredraw(p);
+}
+
+Wintab paltab = {
+ .init = palinit,
+ .click = palclick,
+ .draw = paldraw,
+ .zerox = palzerox,
+ .hexcols = {
+ [BORD] 0xAA0000FF,
+ [DISB] 0xCC8888FF,
+ [BACK] 0xFFCCFFFF,
+ },
+};
--- /dev/null
+++ b/sys/src/cmd/spred/spr.c
@@ -1,0 +1,371 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include "dat.h"
+#include "fns.h"
+
+Spr *
+newspr(char *f)
+{
+ Spr *s;
+
+ s = emalloc(sizeof(*s));
+ s->type = SPR;
+ filinit(s, f);
+ return s;
+}
+
+void
+putspr(Spr *s)
+{
+ if(s->pal != nil && !decref(s->pal) && s->pal->change <= 0)
+ putfil(s->pal);
+ free(s->palfile);
+ free(s->data);
+}
+
+int
+readspr(Spr *s, Biobuf *bp)
+{
+ char *args0[8], *p, *ss, **args;
+ int n, i, j;
+
+ args = nil;
+ ss = nil;
+ if(tline(bp, &ss, args0, nelem(args0)) != 4)
+ goto err;
+ if(strcmp(args0[0], "sprite") != 0)
+ goto err;
+ n = strtol(args0[1], &p, 0);
+ if(*p != 0 || n < 0)
+ goto err;
+ s->w = n;
+ n = strtol(args0[2], &p, 0);
+ if(*p != 0 || n < 0)
+ goto err;
+ s->h = n;
+ if(*args0[3] != 0)
+ s->palfile = strdup(args0[3]);
+ else
+ s->palfile = nil;
+ free(ss);
+ ss = nil;
+ s->data = emalloc(s->w * s->h * sizeof(u32int));
+ args = emalloc((s->w + 1) * sizeof(char *));
+ for(i = 0; i < s->h; i++){
+ if(tline(bp, &ss, args, s->w + 1) != s->w)
+ goto err;
+ for(j = 0; j < s->w; j++){
+ n = strtol(args[j], &p, 0);
+ if(*p != 0 || n < 0)
+ goto err;
+ s->data[i * s->w + j] = n;
+ }
+ free(ss);
+ ss = nil;
+ }
+ free(args);
+ return 0;
+err:
+ werrstr("invalid format");
+ free(s->data);
+ free(args);
+ s->w = 0;
+ s->h = 0;
+ return -1;
+}
+
+int
+writespr(Spr *s, char *file)
+{
+ Biobuf *bp;
+ int n, rc;
+ int i, j;
+
+ if(file == nil)
+ file = s->name;
+ bp = Bopen(file, OWRITE);
+ if(bp == nil){
+ cmdprint("?%r\n");
+ return -1;
+ }
+ rc = Bprint(bp, "sprite %d %d %q\n", s->w, s->h, s->palfile != nil ? s->palfile : "");
+ if(rc < 0) goto err;
+ n = rc;
+ for(i = 0; i < s->h; i++)
+ for(j = 0; j < s->w; j++){
+ rc = Bprint(bp, "%d%c", s->data[s->w * i + j], j == s->w - 1 ? '\n' : ' ');
+ if(rc < 0) goto err;
+ n += rc;
+ }
+ if(Bterm(bp) < 0){
+ cmdprint("?%r\n");
+ return -1;
+ }
+ s->change = 0;
+ quitok = 0;
+ cmdprint("%s: #%d\n", file, n);
+ return 0;
+err:
+ cmdprint("?%r\n");
+ Bterm(bp);
+ return -1;
+}
+
+int
+sprinit(Win *w)
+{
+ w->zoom = 4;
+ return 0;
+}
+
+static Rectangle
+sprrect(Win *w, Rectangle s)
+{
+ Rectangle r;
+ Point p, q;
+ Spr *t;
+
+ t = (Spr *) w->f;
+ p = Pt(t->w * w->zoom, t->h * w->zoom);
+ q = addpt(divpt(addpt(s.min, s.max), 2), w->scr);
+ r.min = subpt(q, divpt(p, 2));
+ r.max = addpt(r.min, p);
+ return r;
+}
+
+static void
+scrollbars(Win *w)
+{
+ Rectangle r, s;
+ int dx, dy;
+ int t0, t1;
+
+ if(rectinrect(w->sprr, w->inner))
+ return;
+ r = w->inner;
+ dx = Dx(r) - SCRTSIZ;
+ dy = Dy(r) - SCRTSIZ;
+ if(dx <= 0 || dy <= 0)
+ return;
+ s = r;
+ if(!rectclip(&s, w->sprr))
+ return;
+ draw(w->im, Rect(r.min.x, r.max.y - SCRBSIZ, r.max.x - SCRTSIZ, r.max.y), w->tab->cols[BORD], nil, ZP);
+ draw(w->im, Rect(r.max.x - SCRBSIZ, r.min.y, r.max.x, r.max.y - SCRTSIZ), w->tab->cols[BORD], nil, ZP);
+ t0 = (s.min.x - w->sprr.min.x) * dx / Dx(w->sprr) + r.min.x;
+ t1 = (s.max.x - w->sprr.min.x) * dx / Dx(w->sprr) + r.min.x;
+ draw(w->im, Rect(t0, r.max.y - SCRBSIZ + 1, t1, r.max.y), w->tab->cols[BACK], nil, ZP);
+ t0 = (s.min.y - w->sprr.min.y) * dy / Dy(w->sprr) + r.min.y;
+ t1 = (s.max.y - w->sprr.min.y) * dy / Dy(w->sprr) + r.min.y;
+ draw(w->im, Rect(r.max.x - SCRBSIZ, t0, r.max.x, t1), w->tab->cols[BACK], nil, ZP);
+}
+
+void
+sprdraw(Win *w)
+{
+ Rectangle r, t;
+ Spr *s;
+ Pal *p;
+ int i, j;
+ Image *im;
+ u32int *d;
+
+ if(w->type != SPR || w->f == nil)
+ sysfatal("sprdraw: phase error");
+ s = (Spr *) w->f;
+ p = s->pal;
+ draw(w->im, w->inner, w->tab->cols[BACK], nil, ZP);
+ r = sprrect(w, w->inner);
+ w->sprr = r;
+ if(!rectinrect(r, w->inner)){
+ t = w->inner;
+ t.max.x -= SCRTSIZ;
+ t.max.y -= SCRTSIZ;
+ r = sprrect(w, t);
+ w->sprr = r;
+ rectclip(&r, t);
+ scrollbars(w);
+ }
+ d = s->data;
+ for(j = 0; j < s->h; j++)
+ for(i = 0; i < s->w; i++, d++){
+ t.min = addpt(w->sprr.min, Pt(i * w->zoom, j * w->zoom));
+ t.max = addpt(t.min, Pt(w->zoom, w->zoom));
+ if(!rectclip(&t, r))
+ continue;
+ if(p != nil && *d < p->ncol)
+ im = p->ims[*d];
+ else
+ im = invcol;
+ draw(w->im, t, im, nil, ZP);
+ }
+}
+
+static int
+sprbars(Win *w, Mousectl *mc)
+{
+ int d;
+
+ if(rectinrect(w->sprr, w->inner))
+ return -1;
+ if(mc->xy.x >= w->inner.max.x - SCRBSIZ){
+ d = Dy(w->inner) / 5;
+ switch(mc->buttons){
+ case 1: w->scr.y += d; break;
+ case 4: w->scr.y -= d; break;
+ default: return 0;
+ }
+ sprdraw(w);
+ return 0;
+ }
+ if(mc->xy.y >= w->inner.max.y - SCRBSIZ){
+ d = Dx(w->inner) / 5;
+ switch(mc->buttons){
+ case 1: w->scr.x += d; break;
+ case 4: w->scr.x -= d; break;
+ default: return 0;
+ }
+ sprdraw(w);
+ return 0;
+ }
+ return -1;
+}
+
+void
+sprclick(Win *w, Mousectl *mc)
+{
+ Spr *s;
+ Pal *p;
+ Point q;
+
+ if(w->f == nil)
+ sysfatal("sprclick: phase error");
+ if(sprbars(w, mc) >= 0)
+ return;
+ s = (Spr *) w->f;
+ p = s->pal;
+ if(p == nil || p->sel < 0 || p->sel >= p->ncol)
+ return;
+ do{
+ q = divpt(subpt(mc->xy, w->sprr.min), w->zoom);
+ if(q.x < 0 || q.y < 0 || q.x >= s->w || q.y >= s->h)
+ continue;
+ if(s->data[q.y * s->w + q.x] != p->sel){
+ s->data[q.y * s->w + q.x] = p->sel;
+ s->change = 1;
+ quitok = 0;
+ sprdraw(w);
+ }
+ }while(readmouse(mc) >= 0 && (mc->buttons & 1) != 0);
+}
+
+void
+sprsize(Spr *s, int n, int m)
+{
+ u32int *v;
+ int i, j, w, h;
+
+ v = s->data;
+ if(s->w == n && s->h == m)
+ return;
+ s->data = emalloc(n * m * sizeof(u32int));
+ w = n < s->w ? n : s->w;
+ h = m < s->h ? m : s->h;
+ for(j = 0; j < h; j++)
+ for(i = 0; i < w; i++)
+ s->data[j * n + i] = v[j * w + i];
+ s->w = n;
+ s->h = m;
+ s->change = 1;
+ quitok = 0;
+ filredraw(s);
+}
+
+static char *
+palfile(char *, char *n)
+{
+ return strdup(n);
+}
+
+static void
+sprmenu(Win *w, Mousectl *mc)
+{
+ enum { MPAL };
+ static char *menus[] = {
+ "pal",
+ nil,
+ };
+ static Menu menu = {menus};
+ Win *wp;
+ Spr *s;
+
+ if(w->f == nil)
+ sysfatal("sprmenu: phase error");
+ s = (Spr *) w->f;
+ switch(menuhit(2, mc, &menu, scr)){
+ case MPAL:
+ wp = winsel(mc, 2);
+ if(wp == nil || wp->type != PAL)
+ break;
+ if(wp->f == nil)
+ sysfatal("sprmenu: pal phase error");
+ if(s->pal != (Pal *) wp->f){
+ if(s->pal != nil && decref(s->pal) == 0 && s->pal->change <= 0)
+ putfil(s->pal);
+ incref(wp->f);
+ s->pal = (Pal *) wp->f;
+ free(s->palfile);
+ s->palfile = palfile(s->name, s->pal->name);
+ s->change = 1;
+ quitok = 0;
+ filredraw(s);
+ }
+ break;
+ }
+}
+
+static void
+sprzerox(Win *w, Win *v)
+{
+ v->zoom = w->zoom;
+ v->scr = w->scr;
+}
+
+static void
+sprkey(Win *w, Rune r)
+{
+ static char keys[] = "1234567890qwertyuiop";
+ char *p;
+ Spr *s;
+
+ s = (Spr *) w->f;
+ if(s == nil)
+ sysfatal("sprkey: phase error");
+ if(r < 0x100 && (p = strchr(keys, r)) != nil){
+ if(s->pal == nil || p - keys >= s->pal->ncol)
+ return;
+ s->pal->sel = p - keys;
+ filredraw(s->pal);
+ }
+}
+
+Wintab sprtab = {
+ .init = sprinit,
+ .click = sprclick,
+ .draw = sprdraw,
+ .menu = sprmenu,
+ .rmb = sprbars,
+ .zerox = sprzerox,
+ .key = sprkey,
+ .hexcols = {
+ [BORD] 0x00AA00FF,
+ [DISB] 0x88CC88FF,
+ [BACK] 0xCCFFCCFF,
+ },
+};
--- /dev/null
+++ b/sys/src/cmd/spred/spred.c
@@ -1,0 +1,210 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <frame.h>
+#include <cursor.h>
+#include "dat.h"
+#include "fns.h"
+
+Mousectl *mc;
+Keyboardctl *kc;
+int quitok;
+
+enum {
+ ZEROX,
+ RESIZE,
+ CLOSE,
+ WRITE,
+ QUIT,
+ WIN
+};
+
+int
+quit(void)
+{
+ File *f;
+
+ if(!quitok)
+ for(f = flist.next; f != &flist; f = f->next)
+ if(f->change > 0){
+ cmdprint("?\n");
+ quitok = 1;
+ return 0;
+ }
+ return -1;
+}
+
+static char *
+menugen(int n)
+{
+ File *f;
+ static int mw;
+ static char buf[512];
+ int rc;
+ char *p;
+
+ switch(n){
+ case ZEROX: return "zerox";
+ case CLOSE: return "close";
+ case RESIZE: return "resize";
+ case WRITE: return "write";
+ case QUIT: return "quit";
+ }
+ if(n < WIN)
+ sysfatal("menugen: no string for n=%d", n);
+ n -= WIN;
+ if(n == 0){
+ mw = 0;
+ for(f = flist.next; f != &flist; f = f->next){
+ rc = filtitlelen(f);
+ if(rc > mw)
+ mw = rc;
+ }
+ return "~~spred~~";
+ }
+ for(f = flist.next; f != &flist; f = f->next)
+ if(--n == 0){
+ p = filtitle(f, buf, buf + sizeof(buf));
+ rc = mw - utflen(buf);
+ if(p + rc >= buf + sizeof(buf))
+ rc = buf + sizeof(buf) - p - 1;
+ memset(p, ' ', rc);
+ p[rc] = 0;
+ return buf;
+ }
+ return nil;
+
+}
+
+static int
+rmb(void)
+{
+ static Menu menu = {nil, menugen};
+ int n;
+ Win *w;
+ File *f;
+
+ if(actw != nil && actw->tab->rmb != nil && actw->tab->rmb(actw, mc) >= 0)
+ return 0;
+ n = menuhit(3, mc, &menu, nil);
+ if(n < 0)
+ return 0;
+ switch(n){
+ case ZEROX:
+ w = winsel(mc, 3);
+ if(w != nil)
+ winzerox(w, mc);
+ return 0;
+ case CLOSE:
+ w = winsel(mc, 3);
+ if(w != nil)
+ winclose(w);
+ return 0;
+ case RESIZE:
+ winresize(winsel(mc, 3), mc);
+ return 0;
+ case WRITE:
+ w = winsel(mc, 3);
+ if(w != nil)
+ winwrite(w, nil);
+ return 0;
+ case QUIT:
+ return quit();
+ }
+ if(n < WIN)
+ sysfatal("rmb: no action for n=%d", n);
+ if(n == 0){
+ setfocus(cmdw);
+ return 0;
+ }
+ n -= WIN;
+ for(f = flist.next; f != &flist; f = f->next)
+ if(--n == 0){
+ if(f->wins.wnext == &f->wins){
+ newwinsel(f->type, mc, f);
+ return 0;
+ }
+ for(w = f->wins.wnext; w != &f->wins && w != actw; w = w->wnext)
+ ;
+ if(w->wnext == &f->wins)
+ w = w->wnext;
+ setfocus(w->wnext);
+ return 0;
+ }
+ return 0;
+}
+
+static void
+loop(void)
+{
+ Rune r;
+ int n;
+
+ Alt a[] = {
+ {mc->c, &mc->Mouse, CHANRCV},
+ {kc->c, &r, CHANRCV},
+ {mc->resizec, &n, CHANRCV},
+ {nil, nil, CHANEND}
+ };
+
+ for(;;){
+ flushimage(display, 1);
+ switch(alt(a)){
+ case 0:
+ if((mc->buttons & 1) != 0)
+ winclick(mc);
+ if((mc->buttons & 2) != 0)
+ if(actw != nil && actw->tab->menu != nil)
+ actw->tab->menu(actw, mc);
+ if((mc->buttons & 4) != 0)
+ if(rmb() < 0)
+ return;
+ break;
+ case 1:
+ if(actw != nil && actw->tab->key != nil)
+ actw->tab->key(actw, r);
+ break;
+ case 2:
+ resize();
+ break;
+ }
+ }
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ ARGBEGIN {
+ default:
+ ;
+ } ARGEND;
+
+ quotefmtinstall();
+ if(initdraw(nil, nil, nil) < 0)
+ sysfatal("initdraw: %r");
+ initwin();
+ mc = initmouse(nil, screen);
+ if(mc == nil)
+ sysfatal("initmouse: %r");
+ kc = initkeyboard(nil);
+ if(kc == nil)
+ sysfatal("initkeyboard: %r");
+ loop();
+ threadexitsall(nil);
+}
+
+Cursor crosscursor = {
+ {-7, -7},
+ {0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0,
+ 0x03, 0xC0, 0x03, 0xC0, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0x03, 0xC0, 0x03, 0xC0,
+ 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, 0x03, 0xC0, },
+ {0x00, 0x00, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+ 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x7F, 0xFE,
+ 0x7F, 0xFE, 0x01, 0x80, 0x01, 0x80, 0x01, 0x80,
+ 0x01, 0x80, 0x01, 0x80, 0x01, 0x80, 0x00, 0x00, }
+};
--- /dev/null
+++ b/sys/src/cmd/spred/win.c
@@ -1,0 +1,281 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <cursor.h>
+#include <frame.h>
+#include "dat.h"
+#include "fns.h"
+
+Screen *scr;
+extern Wintab *tabs[];
+Win wlist;
+File flist;
+Win *actw, *actf, *cmdw;
+Image *invcol;
+
+void*
+emalloc(ulong sz)
+{
+ void *v;
+
+ v = malloc(sz);
+ if(v == nil)
+ sysfatal("malloc: %r");
+ memset(v, 0, sz);
+ setmalloctag(v, getcallerpc(&sz));
+ return v;
+}
+
+void
+initwin(void)
+{
+ Rectangle r;
+ int i, j;
+
+ scr = allocscreen(screen, display->white, 0);
+ if(scr == nil)
+ sysfatal("allocscreen: %r");
+ for(i = 0; i < NTYPES; i++)
+ for(j = 0; j < NCOLS; j++)
+ tabs[i]->cols[j] = allocimage(display, Rect(0, 0, 1, 1), screen->chan, 1, tabs[i]->hexcols[j]);
+ invcol = allocimage(display, Rect(0, 0, 2, 2), screen->chan, 1, 0);
+ draw(invcol, Rect(1, 0, 2, 1), display->white, nil, ZP);
+ draw(invcol, Rect(0, 1, 1, 2), display->white, nil, ZP);
+ wlist.next = wlist.prev = &wlist;
+ flist.next = flist.prev = &flist;
+ r = screen->r;
+ r.max.y = r.min.y + Dy(r) / 5;
+ cmdw = newwin(CMD, r, nil);
+ if(cmdw == nil)
+ sysfatal("newwin: %r");
+}
+
+Win *
+newwin(int t, Rectangle r, File *f)
+{
+ Win *w;
+
+ w = emalloc(sizeof(*w));
+ w->next = &wlist;
+ w->prev = wlist.prev;
+ w->next->prev = w;
+ w->prev->next = w;
+ w->type = t;
+ w->tab = tabs[t];
+ w->entire = r;
+ w->inner = insetrect(r, BORDSIZ);
+ w->im = allocwindow(scr, r, Refbackup, 0);
+ draw(w->im, w->inner, w->tab->cols[BACK], nil, ZP);
+ if(f != nil){
+ incref(f);
+ w->wprev = f->wins.wprev;
+ w->wnext = &f->wins;
+ f->wins.wprev->wnext = w;
+ f->wins.wprev = w;
+ w->f = f;
+ }
+ w->tab->init(w);
+ setfocus(w);
+ w->tab->draw(w);
+ return w;
+}
+
+Win *
+newwinsel(int t, Mousectl *mc, File *f)
+{
+ Rectangle u;
+
+ u = getrect(3, mc);
+ if(Dx(u) < MINSIZ || Dy(u) < MINSIZ)
+ return nil;
+ rectclip(&u, screen->r);
+ return newwin(t, u, f);
+}
+
+void
+winzerox(Win *w, Mousectl *mc)
+{
+ Win *v;
+
+ if(w->tab->zerox == nil){
+ cmdprint("?\n");
+ return;
+ }
+ v = newwinsel(w->type, mc, w->f);
+ if(v == nil)
+ return;
+ w->tab->zerox(w, v);
+ v->tab->draw(v);
+}
+
+void
+winclose(Win *w)
+{
+ if(w->f == nil){
+ cmdprint("?\n");
+ return;
+ }
+ if(!decref(w->f)){
+ if(w->f->change > 0){
+ cmdprint("?\n");
+ incref(w->f);
+ w->f->change = -1;
+ return;
+ }
+ putfil(w->f);
+ w->f = nil;
+ }
+ freeimage(w->im);
+ if(w->f != nil){
+ w->wnext->wprev = w->wprev;
+ w->wprev->wnext = w->wnext;
+ }
+ w->next->prev = w->prev;
+ w->prev->next = w->next;
+ if(w == actw)
+ actw = nil;
+ if(w == actf)
+ actf = nil;
+ free(w);
+}
+
+void
+setfocus(Win *w)
+{
+ if(actw != nil)
+ border(actw->im, actw->entire, BORDSIZ, actw->tab->cols[DISB], ZP);
+ actw = w;
+ if(w != cmdw)
+ actf = w;
+ if(w == nil)
+ return;
+ if(w->im == nil)
+ sysfatal("setfocus: phase error");
+ topwindow(w->im);
+ w->prev->next = w->next;
+ w->next->prev = w->prev;
+ w->prev = wlist.prev;
+ w->next = &wlist;
+ w->prev->next = w;
+ w->next->prev = w;
+ border(w->im, w->entire, BORDSIZ, w->tab->cols[BORD], ZP);
+}
+
+static Win *
+winpoint(Point p)
+{
+ Win *w;
+
+ for(w = wlist.prev; w != &wlist; w = w->prev)
+ if(ptinrect(p, w->entire))
+ return w;
+ return nil;
+}
+
+void
+winclick(Mousectl *mc)
+{
+ Win *w;
+
+ w = winpoint(mc->xy);
+ if(w != nil){
+ if(w != actw)
+ setfocus(w);
+ w->tab->click(w, mc);
+ }
+ while((mc->buttons & 1) != 0)
+ readmouse(mc);
+}
+
+Win *
+winsel(Mousectl *mc, int but)
+{
+ extern Cursor crosscursor;
+ int m;
+ Win *w;
+
+ m = 1 << but - 1;
+ setcursor(mc, &crosscursor);
+ for(;;){
+ readmouse(mc);
+ if((mc->buttons & ~m) != 0){
+ w = nil;
+ goto end;
+ }
+ if((mc->buttons & m) != 0)
+ break;
+ }
+ w = winpoint(mc->xy);
+end:
+ while(readmouse(mc), mc->buttons != 0)
+ ;
+ setcursor(mc, nil);
+ return w;
+}
+
+void
+winresize(Win *w, Mousectl *mc)
+{
+ Rectangle r;
+
+ if(w == nil)
+ return;
+ r = getrect(3, mc);
+ if(Dx(r) < MINSIZ || Dy(r) < MINSIZ)
+ return;
+ rectclip(&r, screen->r);
+ freeimage(w->im);
+ w->entire = r;
+ w->inner = insetrect(r, BORDSIZ);
+ w->im = allocwindow(scr, r, Refbackup, 0);
+ draw(w->im, w->inner, w->tab->cols[BACK], nil, ZP);
+ setfocus(w);
+ w->tab->draw(w);
+}
+
+void
+resize(void)
+{
+ Rectangle old, r;
+ int dxo, dyo, dxn, dyn;
+ Win *w;
+
+ old = screen->r;
+ dxo = Dx(old);
+ dyo = Dy(old);
+ if(getwindow(display, Refnone) < 0)
+ sysfatal("resize failed: %r");
+ dxn = Dx(screen->r);
+ dyn = Dy(screen->r);
+ freescreen(scr);
+ scr = allocscreen(screen, display->white, 0);
+ if(scr == nil)
+ sysfatal("allocscreen: %r");
+ for(w = wlist.next; w != &wlist; w = w->next){
+ r = rectsubpt(w->entire, old.min);
+ r.min.x = muldiv(r.min.x, dxn, dxo);
+ r.max.x = muldiv(r.max.x, dxn, dxo);
+ r.min.y = muldiv(r.min.y, dyn, dyo);
+ r.max.y = muldiv(r.max.y, dyn, dyo);
+ w->entire = rectaddpt(r, screen->r.min);
+ w->inner = insetrect(w->entire, BORDSIZ);
+ freeimage(w->im);
+ w->im = allocwindow(scr, w->entire, Refbackup, 0);
+ if(w->im == nil)
+ sysfatal("allocwindow: %r");
+ draw(w->im, w->inner, w->tab->cols[BACK], nil, ZP);
+ border(w->im, w->entire, BORDSIZ, w->tab->cols[w == actw ? BORD : DISB], ZP);
+ w->tab->draw(w);
+ }
+}
+
+extern Wintab cmdtab, paltab, sprtab;
+
+Wintab *tabs[] = {
+ [CMD] &cmdtab,
+ [PAL] &paltab,
+ [SPR] &sprtab,
+};