#include "lib9.h" #include "image.h" #include "tk.h" #define istring u.string #define iwin u.win #define imark u.mark #define iline u.line static char* tkttagadd(Tk*, char*, char**); static char* tkttagbind(Tk*, char*, char**); static char* tkttagcget(Tk*, char*, char**); static char* tkttagconfigure(Tk*, char*, char**); static char* tkttagdelete(Tk*, char*, char**); static char* tkttaglower(Tk*, char*, char**); static char* tkttagnames(Tk*, char*, char**); static char* tkttagnextrange(Tk*, char*, char**); static char* tkttagprevrange(Tk*, char*, char**); static char* tkttagraise(Tk*, char*, char**); static char* tkttagranges(Tk*, char*, char**); static char* tkttagremove(Tk*, char*, char**); #define O(t, e) ((long)(&((t*)0)->e)) #define TKTEO (O(TkTtaginfo, env)) static TkOption tagopts[] = { "borderwidth", OPTdist, O(TkTtaginfo, opts[TkTborderwidth]), nil, "justify", OPTstab, O(TkTtaginfo, opts[TkTjustify]), tkjustify, "lmargin1", OPTdist, O(TkTtaginfo, opts[TkTlmargin1]), IAUX(TKTEO), "lmargin2", OPTdist, O(TkTtaginfo, opts[TkTlmargin2]), IAUX(TKTEO), "lmargin3", OPTdist, O(TkTtaginfo, opts[TkTlmargin3]), IAUX(TKTEO), "rmargin", OPTdist, O(TkTtaginfo, opts[TkTrmargin]), IAUX(TKTEO), "spacing1", OPTdist, O(TkTtaginfo, opts[TkTspacing1]), IAUX(TKTEO), "spacing2", OPTdist, O(TkTtaginfo, opts[TkTspacing2]), IAUX(TKTEO), "spacing3", OPTdist, O(TkTtaginfo, opts[TkTspacing3]), IAUX(TKTEO), "offset", OPTdist, O(TkTtaginfo, opts[TkToffset]), IAUX(TKTEO), "underline", OPTdist, O(TkTtaginfo, opts[TkTunderline]), nil, "overstrike", OPTdist, O(TkTtaginfo, opts[TkToverstrike]), nil, "relief", OPTstab, O(TkTtaginfo, opts[TkTrelief]), tkrelief, "tabs", OPTtabs, O(TkTtaginfo, tabs), IAUX(TKTEO), "wrap", OPTstab, O(TkTtaginfo, opts[TkTwrap]), tkwrap, nil, }; static TkOption tagenvopts[] = { "foreground", OPTcolr, O(TkTtaginfo, env), IAUX(TkCforegnd), "background", OPTcolr, O(TkTtaginfo, env), IAUX(TkCbackgnd), "fg", OPTcolr, O(TkTtaginfo, env), IAUX(TkCforegnd), "bg", OPTcolr, O(TkTtaginfo, env), IAUX(TkCbackgnd), "font", OPTfont, O(TkTtaginfo, env), nil, nil }; TkCmdtab tkttagcmd[] = { "add", tkttagadd, "bind", tkttagbind, "cget", tkttagcget, "configure", tkttagconfigure, "delete", tkttagdelete, "lower", tkttaglower, "names", tkttagnames, "nextrange", tkttagnextrange, "prevrange", tkttagprevrange, "raise", tkttagraise, "ranges", tkttagranges, "remove", tkttagremove, nil }; int tktanytags(TkTitem *it) { int i; if(it->tagextra == 0) return (it->tags[0] != 0); for(i = 0; i <= it->tagextra; i++) if(it->tags[i] != 0) return 1; return 0; } int tktsametags(TkTitem *i1, TkTitem *i2) { int i, j; for(i = 0; i <= i1->tagextra && i <= i2->tagextra; i++) if(i1->tags[i] != i2->tags[i]) return 0; for(j = i; j <= i1->tagextra; j++) if(i1->tags[j] != 0) return 0; for(j = i; j <= i2->tagextra; j++) if(i2->tags[j] != 0) return 0; return 1; } int tkttagset(TkTitem *it, int id) { int i; if(it->tagextra == 0 && it->tags[0] == 0) return 0; for(i = 0; i <= it->tagextra; i++) { if(id < 32) return ((it->tags[i] & (1<tags; t != nil; t = t->next) { if(t->id == id) return t->name; } return ""; } /* return 1 if this actually changes the value */ int tkttagbit(TkTitem *it, int id, int val) { int i, changed; ulong z, b; changed = 0; for(i = 0; i <= it->tagextra; i++) { if(id < 32) { b = (1<tags[i]; if(val == 0) { if(z & b) { changed = 1; it->tags[i] = z & (~b); } } else { if((z & b) == 0) { changed = 1; it->tags[i] = z | b; } } break; } id -= 32; } return changed; } void tkttagcomb(TkTitem *i1, TkTitem *i2, int add) { int i; for(i = 0; i <= i1->tagextra && i <= i2->tagextra; i++) { if(add == 1) i1->tags[i] |= i2->tags[i]; else if(add == 0) /* intersect */ i1->tags[i] &= i2->tags[i]; else /* subtract */ i1->tags[i] &= ~i2->tags[i]; } } char* tktaddtaginfo(Tk *tk, char *name, TkTtaginfo **ret) { int i, *ntagp; TkTtaginfo *ti; TkText *tkt, *tktshare; tkt = TKobj(TkText, tk); ti = malloc(sizeof(TkTtaginfo)); if(ti == nil) return TkNomem; ntagp = &tkt->nexttag; if(tkt->tagshare != nil) { tktshare = TKobj(TkText, tkt->tagshare); ntagp = &tktshare->nexttag; } ti->id = *ntagp; ti->name = strdup(name); if(ti->name == nil) { free(ti); return TkNomem; } ti->env = tknewenv(tk->env->top); if(ti->env == nil) { free(ti->name); free(ti); return TkNomem; } ti->tabs = nil; for(i = 0; i < TkTnumopts; i++) ti->opts[i] = TkTunset; ti->next = tkt->tags; tkt->tags = ti; (*ntagp)++; if(tkt->tagshare) tkt->nexttag = *ntagp; *ret = ti; return nil; } TkTtaginfo * tktfindtag(TkTtaginfo *t, char *name) { while(t != nil) { if(strcmp(t->name, name) == 0) return t; t = t->next; } return nil; } void tktfreetags(TkTtaginfo *t) { TkTtaginfo *n; while(t != nil) { n = t->next; free(t->name); tktfreetabs(t->tabs); tkputenv(t->env); tkfreebind(t->binds); free(t); t = n; } } int tkttagind(Tk *tk, char *name, int first, TkTindex *ans) { int id; TkTtaginfo *t; TkText *tkt; tkt = TKobj(TkText, tk); if(strcmp(name, "sel") == 0) { if(tkt->selfirst == nil) return 0; if(first) tktitemind(tkt->selfirst, ans); else tktitemind(tkt->sellast, ans); return 1; } t = tktfindtag(tkt->tags, name); if(t == nil) return 0; id = t->id; if(first) { tktstartind(tkt, ans); while(!tkttagset(ans->item, id)) if(!tktadjustind(tkt, TkTbyitem, ans)) return 0; } else { tktendind(tkt, ans); while(!tkttagset(ans->item, id)) if(!tktadjustind(tkt, TkTbyitemback, ans)) return 0; tktadjustind(tkt, TkTbyitem, ans); } return 1; } /* * Fill in opts and e, based on info from tags set in it, * using tags order for priority. * If dflt != 0, options not set are filled from tk, * otherwise iInteger options not set by any tag are left 'TkTunset' * and environment values not set are left nil. */ void tkttagopts(Tk *tk, TkTitem *it, int *opts, TkEnv *e, int dflt) { int i; TkEnv *te; TkTtaginfo *tags; TkText *tkt = TKobj(TkText, tk); tags = tkt->tags; if(opts != nil) for(i = 0; i < TkTnumopts; i++) opts[i] = TkTunset; memset(e, 0, sizeof(TkEnv)); e->top = tk->env->top; while(tags != nil) { if(tkttagset(it, tags->id)) { if(opts != nil) { for(i = 0; i < TkTnumopts; i++) { if(opts[i] == TkTunset && tags->opts[i] != TkTunset) opts[i] = tags->opts[i]; } } te = tags->env; for(i = 0; i < TkNcolor; i++) if(te->set & (1<colors[i] = te->colors[i]; e->set |= 1<font == nil && te->font != nil) e->font = te->font; } tags = tags->next; } if(dflt) { if(opts != nil) { for(i = 0; i < TkTnumopts; i++) if(opts[i] == TkTunset) opts[i] = tkt->opts[i]; } te = tk->env; for(i = 0; i < TkNcolor; i++) if(!(e->set & (1<colors[i] = te->colors[i]; e->set |= 1<font == nil) e->font = te->font; } } char* tkttagparse(Tk *tk, char **parg, TkTtaginfo **ret) { char *e, *buf; TkText *tkt = TKobj(TkText, tk); buf = mallocz(Tkmaxitem, 0); if(buf == nil) return TkNomem; *parg = tkword(tk->env->top, *parg, buf, buf+Tkmaxitem); if(*buf == '\0') { free(buf); return TkOparg; } *ret = tktfindtag(tkt->tags, buf); if(*ret == nil) { e = tktaddtaginfo(tk, buf, ret); if(e != nil) { free(buf); return e; } } free(buf); return nil; } int tkttagnrange(TkText *tkt, int tid, TkTindex *i1, TkTindex *i2, TkTindex *istart, TkTindex *iend) { int found; found = 0; while(i1->line != &tkt->end) { if(i1->item == i2->item && i2->pos == 0) break; if(tkttagset(i1->item, tid)) { if(!found) { found = 1; *istart = *i1; } if(i1->item == i2->item) { /* i2->pos > 0 */ *iend = *i2; return 1; } } else if(i1->item == i2->item || (found && i1->item->kind != TkTmark && i1->item->kind != TkTcontline)) break; tktadjustind(tkt, TkTbyitem, i1); } if(found) *iend = *i1; return found; } static int tkttagprange(TkText *tkt, int tid, TkTindex *i1, TkTindex *i2, TkTindex *istart, TkTindex *iend) { int found; found = 0; while(i1->line != &tkt->start && i1->item != i2->item) { tktadjustind(tkt, TkTbyitemback, i1); if(tkttagset(i1->item, tid)) { if(!found) { found = 1; *iend = *i1; } } else if(found && i1->item->kind != TkTmark && i1->item->kind != TkTcontline) break; } if(found) { tktadjustind(tkt, TkTbyitem, i1); *istart = *i1; if(i1->item == i2->item) istart->pos = i2->pos; } return found; } /* XXX - Tad: potential memory leak on memory allocation failure */ char * tkttagchange(Tk *tk, int tid, TkTindex *i1, TkTindex *i2, int add) { char *e; int samei, nextra, j, changed; TkTline *lmin, *lmax; TkTindex ixprev; TkTitem *nit; TkText *tkt = TKobj(TkText, tk); if(!tktindbefore(i1, i2)) return nil; nextra = tid/32; lmin = nil; lmax = nil; tktadjustind(tkt, TkTbycharstart, i1); tktadjustind(tkt, TkTbycharstart, i2); samei = (i1->item == i2->item); if(i2->pos != 0) { e = tktsplititem(i2); if(e != nil) return e; if(samei) { /* split means i1 should now point to previous item */ ixprev = *i2; tktadjustind(tkt, TkTbyitemback, &ixprev); i1->item = ixprev.item; } } if(i1->pos != 0) { e = tktsplititem(i1); if(e != nil) return e; } /* now i1 and i2 both point to beginning of non-mark/contline items */ if(tid == TkTselid) { /* * Cache location of selection. * Note: there can be only one selection range in widget */ if(add) { if(tkt->selfirst != nil) return TkBadsl; tkt->selfirst = i1->item; tkt->sellast = i2->item; tksetselect(tk); } else { tkt->selfirst = nil; tkt->sellast = nil; } } while(i1->item != i2->item) { if(i1->item->kind != TkTmark && i1->item->kind != TkTcontline) { if(tid >= 32 && i1->item->tagextra < nextra) { nit = realloc(i1->item, sizeof(TkTitem) + nextra * sizeof(long)); if(nit == nil) return TkNomem; for(j = nit->tagextra+1; j <= nextra; j++) nit->tags[j] = 0; nit->tagextra = nextra; if(i1->line->items == i1->item) i1->line->items = nit; else { ixprev = *i1; tktadjustind(tkt, TkTbyitemback, &ixprev); ixprev.item->next = nit; } /* check nit against cached items */ if(tkt->selfirst == i1->item) tkt->selfirst = nit; if(tkt->sellast == i1->item) tkt->sellast = nit; i1->item = nit; } changed = tkttagbit(i1->item, tid, add); if(lmin == nil) { if(changed) { lmin = i1->line; lmax = lmin; } } else { if(changed) lmax = i1->line; } } if(!tktadjustind(tkt, TkTbyitem, i1)) break; } if(lmin != nil) tktfixgeom(tk, lmin->prev, lmax); return nil; } static char* tkttagaddrem(Tk *tk, char *arg, int add) { char *e; TkText *tkt; TkTtaginfo *ti; TkTindex ix1, ix2; tkt = TKobj(TkText, tk); e = tkttagparse(tk, &arg, &ti); if(e != nil) return e; while(*arg != '\0') { e = tktindparse(tk, &arg, &ix1); if(e != nil) return e; if(*arg != '\0') { e = tktindparse(tk, &arg, &ix2); if(e != nil) return e; } else { ix2 = ix1; tktadjustind(tkt, TkTbychar, &ix2); } if(!tktindbefore(&ix1, &ix2)) continue; e = tkttagchange(tk, ti->id, &ix1, &ix2, add); if(e != nil) return e; } return nil; } /* Text Tag Command (+ means implemented) +add +bind +cget +configure +delete +lower +names +nextrange +prevrange +raise +ranges +remove */ static char* tkttagadd(Tk *tk, char *arg, char **val) { USED(val); return tkttagaddrem(tk, arg, 1); } static char* tkttagbind(Tk *tk, char *arg, char **val) { char *e; Rune r; TkTtaginfo *ti; TkAction *a; int event, mode; char *cmd, buf[Tkmaxitem]; e = tkttagparse(tk, &arg, &ti); if(e != nil) return e; arg = tkword(tk->env->top, arg, buf, buf+sizeof(buf)); if(buf[0] == '<') { event = tkseqparse(buf+1); if(event == -1) return TkBadsq; } else { chartorune(&r, buf); event = TkKey | r; } if(event == 0) return TkBadsq; while(*arg && (*arg == ' ' || *arg == '\t')) arg++; if(*arg == '\0') { for(a = ti->binds; a; a = a->link) if(event == a->event) return tkvalue(val, "%s", a->arg); return nil; } mode = TkArepl; if(*arg == '+') { mode = TkAadd; arg++; } tkword(tk->env->top, arg, buf, buf+sizeof(buf)); cmd = strdup(buf); if(cmd == nil) return TkNomem; e = tkaction(&ti->binds, event, TkDynamic, cmd, mode); if(e != nil) free(cmd); return e; } static char* tkttagcget(Tk *tk, char *arg, char **val) { char *e; TkTtaginfo *ti; TkOptab tko[3]; e = tkttagparse(tk, &arg, &ti); if(e != nil) return e; tko[0].ptr = ti; tko[0].optab = tagopts; tko[1].ptr = ti; tko[1].optab = tagenvopts; tko[2].ptr = nil; return tkgencget(tko, arg, val); } static char* tkttagconfigure(Tk *tk, char *arg, char **val) { char *e; TkOptab tko[3]; TkTtaginfo *ti; TkTindex ix; TkText *tkt = TKobj(TkText, tk); USED(val); e = tkttagparse(tk, &arg, &ti); if(e != nil) return e; tko[0].ptr = ti; tko[0].optab = tagopts; tko[1].ptr = ti; tko[1].optab = tagenvopts; tko[2].ptr = nil; e = tkparse(tk->env->top, arg, tko, nil); if(e != nil) return e; if(tkttagind(tk, ti->name, 1, &ix)) tktfixgeom(tk, ix.line->prev, tkt->end.prev); return nil; } static void tktunlinktag(TkText *tkt, TkTtaginfo *t) { TkTtaginfo *f, **l; l = &tkt->tags; for(f = *l; f != nil; f = f->next) { if(f == t) { *l = t->next; return; } l = &f->next; } } static char* tkttagdelete(Tk *tk, char *arg, char **val) { TkText *tkt; TkTtaginfo *t; TkTindex ix; char *e; USED(val); tkt = TKobj(TkText, tk); e = tkttagparse(tk, &arg, &t); if(e != nil) return e; while(t != nil) { if(t->id == TkTselid) return TkBadvl; while(tkttagind(tk, t->name, 1, &ix)) tkttagbit(ix.item, t->id, 0); tktunlinktag(tkt, t); t->next = nil; tktfreetags(t); if(*arg != '\0') { e = tkttagparse(tk, &arg, &t); if(e != nil) return e; } else t = nil; } return nil; } static char* tkttaglower(Tk *tk, char *arg, char **val) { TkText *tkt; TkTtaginfo *t, *tbelow, *f, **l; char *e; USED(val); tkt = TKobj(TkText, tk); e = tkttagparse(tk, &arg, &t); if(e != nil) return e; if(*arg != '\0') { e = tkttagparse(tk, &arg, &tbelow); if(e != nil) return e; } else tbelow = nil; tktunlinktag(tkt, t); if(tbelow != nil) { t->next = tbelow->next; tbelow->next = t; } else { l = &tkt->tags; for(f = *l; f != nil; f = f->next) l = &f->next; *l = t; t->next = nil; } return nil; } static char* tkttagnames(Tk *tk, char *arg, char **val) { char *e, *r, *fmt; TkTtaginfo *t; TkTindex i; TkText *tkt = TKobj(TkText, tk); TkTitem *tagit; if(*arg != '\0') { e = tktindparse(tk, &arg, &i); if(e != nil) return e; tagit = i.item; } else tagit = nil; /* generate in order highest-to-lowest priority (contrary to spec) */ fmt = "%s"; for(t = tkt->tags; t != nil; t = t->next) { if(tagit == nil || tkttagset(tagit, t->id)) { r = tkvalue(val, fmt, t->name); if(r != nil) return r; fmt = " %s"; } } return nil; } static char* tkttagnextrange(Tk *tk, char *arg, char **val) { char *e; TkTtaginfo *t; TkTindex i1, i2, istart, iend; TkText *tkt = TKobj(TkText, tk); e = tkttagparse(tk, &arg, &t); if(e != nil) return e; e = tktindparse(tk, &arg, &i1); if(e != nil) return e; if(*arg != '\0') { e = tktindparse(tk, &arg, &i2); if(e != nil) return e; } else tktendind(tkt, &i2); if(tkttagnrange(tkt, t->id, &i1, &i2, &istart, &iend)) return tkvalue(val, "%d.%d %d.%d", tktlinenum(tkt, &istart), tktlinepos(tkt, &istart), tktlinenum(tkt, &iend), tktlinepos(tkt, &iend)); return nil; } static char* tkttagprevrange(Tk *tk, char *arg, char **val) { char *e; TkTtaginfo *t; TkTindex i1, i2, istart, iend; TkText *tkt = TKobj(TkText, tk); e = tkttagparse(tk, &arg, &t); if(e != nil) return e; e = tktindparse(tk, &arg, &i1); if(e != nil) return e; if(*arg != '\0') { e = tktindparse(tk, &arg, &i2); if(e != nil) return e; } else tktstartind(tkt, &i2); if(tkttagprange(tkt, t->id, &i1, &i2, &istart, &iend)) return tkvalue(val, "%d.%d %d.%d", tktlinenum(tkt, &istart), tktlinepos(tkt, &istart), tktlinenum(tkt, &iend), tktlinepos(tkt, &iend)); return nil; } static char* tkttagraise(Tk *tk, char *arg, char **val) { TkText *tkt; TkTtaginfo *t, *tabove, *f, **l; char *e; USED(val); tkt = TKobj(TkText, tk); e = tkttagparse(tk, &arg, &t); if(e != nil) return e; if(*arg != '\0') { e = tkttagparse(tk, &arg, &tabove); if(e != nil) return e; } else tabove = nil; tktunlinktag(tkt, t); if(tabove != nil) { l = &tkt->tags; for(f = *l; f != nil; f = f->next) { if(f == tabove) { *l = t; t->next = tabove; break; } l = &f->next; } } else { t->next = tkt->tags; tkt->tags = t; } return nil; } static char* tkttagranges(Tk *tk, char *arg, char **val) { char *e, *fmt; TkTtaginfo *t; TkTindex i1, i2, istart, iend; TkText *tkt = TKobj(TkText, tk); e = tkttagparse(tk, &arg, &t); if(e != nil) return e; tktstartind(tkt, &i1); tktendind(tkt, &i2); fmt = "%d.%d %d.%d"; while(tkttagnrange(tkt, t->id, &i1, &i2, &istart, &iend)) { e = tkvalue(val, fmt, tktlinenum(tkt, &istart), tktlinepos(tkt, &istart), tktlinenum(tkt, &iend), tktlinepos(tkt, &iend)); if(e != nil) return e; fmt = " %d.%d %d.%d"; i1 = iend; } return nil; } static char* tkttagremove(Tk *tk, char *arg, char **val) { USED(val); return tkttagaddrem(tk, arg, 0); }