# # Text panel. # Text is handled by the Tblks module. # Undo/redo is handled by a separate Tundo module. # Drawing is handled by Tframe, which is also in charge of maintaining the # selection and the position of the text shown in the panel. # This does more than needed and calls frame operations even when it's not # necessary to do so. implement Pimpl; include "mods.m"; mods, debug, win, tree: import dat; Menu: import menus; setcursor, Waiting, Arrow, getfont, Cpointer, cols, panelback, maxpt, cookclick, drawtag, BACK,TEXT, readsnarf, Inset, terminate, writesnarf, SET, CLEAR, SHAD, BORD, CMtriple, CMdouble : import gui; Ptag, Pline, Pedit, Pshown, Psync, Pdead, Pdirty, Ptbl, Predraw, intag, escape, unescape, nth, Panel: import wpanel; panelctl, panelkbd, panelmouse, tagmouse, Tree: import wtree; usage: import arg; include "tblks.m"; tblks: Tblks; fixpos, fixposins, fixposdel, dtxt, strstr, Maxline, Blks, strchr, Str: import tblks; include "tundo.m"; tundo: Tundo; Edit, Edits: import tundo; include "tframe.m"; tframe: Tframe; Frame: import tframe; Ptext: adt { blks: ref Tblks->Blks; # sequence of text blocks being edited. edits: ref Tundo->Edits; # edit operations for sync, undo, and redo. f: ref Tframe->Frame; # nil or initialized frame s0: int; # offset in text for 1st selection point, to extend selection. fsss: int; # f.ss as last synced to o/mero fsse: int; # idem for f.se mlast: int; # last option used in its menu tabtext: string; # for tbls, the original text, as updated. nlines: int; # nb. of lines in text (to compute maxsz) new: fn(): ref Ptext; gotopos: fn(pi: self ref Ptext, npos: int): int; wordat: fn(pi: self ref Ptext, pos: int, long: int, selok: int): (int, int); dump: fn(pi: self ref Ptext); }; panels: array of ref Ptext; textmenu: ref Menu; # To allow search from tag lines, anytime a text is selected it becomes # an argument for search, open, etc. The argument is void when they # keyboard is used or a single click is made. seltext: string; pimpl(p: ref Panel): ref Ptext { if (p.implid < 0 || p.implid > len panels || panels[p.implid] == nil) panic("draw: bug: no impl"); return panels[p.implid]; } init(d: Livedat): string { prefixes = list of {"text:", "button:", "label:", "tag:", "tbl:"}; dat = d; initmods(); dat = d; str = load String String->PATH; if (str == nil) return sprint("loading %s: %r", String->PATH); tblks = load Tblks Tblks->PATH; if (tblks == nil) return sprint("loading %s: %r", Tblks->PATH); tframe = load Tframe Tframe->PATH; if (tframe == nil) return sprint("loading %s: %r", Tframe->PATH); tundo= load Tundo Tundo->PATH; if (tundo == nil) return sprint("loading %s: %r", Tundo->PATH); tblks->init(sys, str, err, debug['T']); tframe->init(dat, tblks, debug['F']); tundo->init(sys, err, debug['T']); return nil; } nullptext: Ptext; Ptext.new(): ref Ptext { pi := ref nullptext; return pi; } Ptext.gotopos(pi: self ref Ptext, npos: int): int { if (npos == pi.f.pos) return pi.f.pos; s := pi.blks.pack(); npos = fixpos(npos, len s.s); if (npos > 0){ n := s.findr(npos, '\n', Maxline); # position just past the last \n if (n >= 0){ if (n > 0 && n == '\n') n++; npos = n; } else if (npos < len s.s) # only when scrolling within the first line. npos = 0; } return npos; } # Taken from acme isalnum(c : int) : int { # # Hard to get absolutely right. Use what we know about ASCII # and assume anything above the Latin control characters is # potentially an alphanumeric. # if(c <= ' ') return 0; if(16r7F<=c && c<=16rA0) return 0; if(strchr("!\"#$%&'()*+,-./:;<=>?@[\\]^`{|}~", c) >= 0) return 0; return 1; } iswordchar(r: int, long: int): int { if (isalnum(r) || strchr("0123456789_", r) >= 0) return 1; if (long && strchr("|&?=.-+/:", r) >= 0) return 1; return 0; } isparen(set: string, r: int): int { i := strchr(set, r); if (i >= 0) return i + 1; else return 0; } # Returns the word at pos. # If we are looking at the end of line, we pretend we look right # before it. In any case: # The word is the selection when it exists and selok. # It is the longest set of s if pos at # (if long, |&?=.-+/: are also considers as word chars) # It is the text between {} [] '' "" () if pos is at delim. # It is the current line otherwise (if pos at blank) # Ptext.wordat(pi: self ref Ptext, pos: int, long: int, selok: int): (int, int) { ss := pi.f.ss; se := pi.f.se; lparen := "{[(«<“"; rparen := "}])»>”"; paren := "\"'`"; s := pi.blks.pack(); nr := len s.s; if (pos >nr) pos = nr; if (pos == nr && pos > 0) pos--; spos := epos := pos; if (nr == 0) return (spos, epos); if (selok && pos >= ss && pos <= se && ss != se) return (ss, se); if (iswordchar(s.s[pos], long)){ while(spos > 0 && iswordchar(s.s[spos], long)) spos--; if (spos > 0) spos++; while(epos < nr && iswordchar(s.s[epos], long)) epos++; } else if (pp := isparen(paren, s.s[pos])){ spos++; for(epos = spos; epos < nr; epos++) if (isparen(paren, s.s[epos]) == pp) break; } else if (pp = isparen(lparen, s.s[pos])){ nparen := 1; spos++; for(epos = spos; epos < nr; epos++){ if (isparen(lparen, s.s[epos]) == pp) nparen++; if (isparen(rparen, s.s[epos]) == pp) nparen--; if (nparen <= 0){ break; } } } else if (pp = isparen(rparen, s.s[pos])){ nparen := 1; if (spos > 0) for(spos--; spos > 0; spos--){ if (isparen(rparen, s.s[spos]) == pp) nparen++; if (isparen(lparen, s.s[spos]) == pp) nparen--; if (nparen <= 0){ spos++; break; } } } else { # pos at blank if (s.s[spos] == '\n' && spos > 0 && s.s[spos-1] != '\n'){ # click at right part of line; step back # so that expanding leads to previous line spos--; } while(spos > 0 && s.s[spos-1] != '\n') spos--; while(epos < nr && s.s[epos] != '\n') epos++; if (epos < nr) epos++; # include \n } if (spos < 0 || epos < 0 || epos < spos || epos > nr) panic(sprint("spos/epos bug: %d %d %d\n", spos, epos, nr)); return (spos, epos); } Ptext.dump(pi: self ref Ptext) { pi.blks.dump(); if (debug['F'] > 1) pi.f.dump(); } settextsize(p: ref Panel) { pi := pimpl(p); p.maxsz.y = 0; s := pi.blks.pack(); if (p.flags&Pline){ p.minsz.y = p.font.height; # up/down margins if (!(p.flags&Pedit) && len s.s > 0) p.minsz.x = p.font.width(s.s); } else { p.minsz = Point(p.font.height*2,p.font.height*2); p.minsz.y += 1; if (pi.nlines == 0) p.maxsz.y = 0; else { n := pi.nlines + 1; if (n < 3) n = 3; p.maxsz.y = p.font.height * n; p.maxsz.y += 1; } } } Mexec, Mfind, Mput, Mapply, Mpaste, Mcut, Mclose, Mopen: con iota; pinit(p: ref Panel) { if (tree == nil) tree = dat->tree; if (textmenu == nil) textmenu = Menu.new(array[] of {"Exec", "Find", "Put", "Apply", "Paste", "Cut", "Close", "Open"}); for (i := 0; i < len panels; i++) if (panels[i] == nil) break; if (i == len panels){ npanels := array[i+16] of ref Ptext; npanels[0:] = panels; panels = npanels; } p.implid = i; pi := panels[i] = Ptext.new(); pi.edits = Edits.new(); pi.mlast = Mexec; p.flags &= ~(Pline|Pedit); tab := 4; if (len p.name > 3 && p.name[0:3] == "tbl"){ p.flags |= Ptbl|Pedit; p.wants = Point(1,1); pi.tabtext = "[empty]"; pi.mlast = Mopen; #tab = 3; } else if (len p.name > 4 && p.name[0:4] == "text"){ p.flags |= Pedit; p.wants = Point(1,1); pi.mlast = Mfind; } else if (len p.name > 3 && p.name[0:3] == "tag"){ p.flags |= Pline|Pedit; p.wants = Point(1,0); } else { p.flags |= Pline; } if (p.flags&Pline) p.font = getfont("B"); else p.font = getfont("R"); s := ""; if (p.flags&Pline){ (nil, n) := splitl(p.name, ":"); if (n != nil){ n = n[1:]; (s, nil) = splitl(n, "."); if (s == nil) s = nil; } } pi.blks = Blks.new(s); settextsize(p); # create frame w/o image. it maintains selection, mostly. # following pdraw calls will attach an image to the frame and reset its size. pi.f = Frame.new(Rect((0, 0), p.minsz), nil, p.font, gui->cols, !(p.flags&Pline)); pi.f.tabsz = tab; pi.f.showsel = p.flags&Pedit; pi.f.init(pi.blks, pi.f.pos); pi.s0 = pi.f.ss; } pterm(p: ref Panel) { if (p.implid != -1){ pi := pimpl(p); panels[p.implid] = nil; p.implid = -1; pi.f = nil; pi.edits = nil; pi.blks = nil; } } dirty(p: ref Panel, set: int) { if (set) p.flags |= Psync; else p.flags &= ~Psync; if (p.flags&Pline) # lines do not get dirty return; old := p.flags; if (set) p.flags |= Pdirty; else p.flags &= ~Pdirty; if (old != p.flags){ if (set) p.fsctl("dirty\n", 0); else p.fsctl("clean\n", 0); spawn tree.tags("/"); # we might get here called from tree; avoid deadlock. } } tab(p: ref Panel) { pi := pimpl(p); (n, toks) := tokenize(pi.tabtext, "\t\n"); if (n <= 0) return; wl := array[n] of string; wwl := array[n] of int; for (i := 0; i < n; i++){ wl[i] = hd toks; toks = tl toks; } mint := pi.f.spwid; maxt := pi.f.tabwid; colw := 0; maxw := 0; for (i = 0; i < n; i++){ wwl[i] = p.font.width(wl[i]); if (maxw < wwl[i]) maxw = wwl[i]; } spwid := p.font.width(" "); for (i = 0; i < n; i++) while(wwl[i] < maxw){ wl[i] += " "; wwl[i] += spwid; } for(i=0; i colw) colw = w; } ncol := 1; if (colw != 0) ncol = p.rect.dx()/colw; # can't use pi.f.r, because if (ncol < 1) # frame may be using an old rectangle. ncol = 1; nrow := (n+ncol-1)/ncol; ns := ""; for(i=0; i= n) break; else ns += "\t"; } ns += "\n"; } pi.blks = Blks.new(ns); pi.f.init(pi.blks, pi.f.pos); pi.s0 = pi.f.ss; } pdraw(p: ref Panel) { pi := pimpl(p); if (!(p.flags&Pshown)){ pi.f.i = nil; return; } w := dat->win; if (p.flags&Ptbl) tab(p); if (p.rect.eq(pi.f.r)){ pi.f.i = w.image; pi.f.redraw(); } else if (p.rect.dx() == pi.f.r.dx() && p.rect.dy() == pi.f.r.dy()){ pi.f.i = w.image; pi.f.move(p.rect.min); pi.f.redraw(); } else pi.f.resize(p.rect, w.image); settextsize(p); if (p.flags&Ptag) drawtag(p); } stringlines(s: string): int { nc := n := 0; for (i := 0; i < len s; i++) if (s[i] == '\n' || nc == Maxline){ n++; nc = 0; } else nc++; return n; } # could put the update as a new del+ins event, but how would we # sync out our current ongoing edit? # The safe thing to do is to discard any edits and start again. # The application knows best. pupdate(p: ref Panel, d: array of byte) { pi := pimpl(p); if (!(p.flags&Pshown)) pi.f.i = nil; # avoid drawing s := string d; pi.blks = Blks.new(s); pi.edits = Edits.new(); if (p.flags&Ptbl) pi.tabtext = s; nsel := -1; if ( (p.flags&Pline) || !(p.flags&Pedit)) nsel = len s; pi.nlines = stringlines(s); pi.f.init(pi.blks, pi.f.pos); if (nsel >= 0){ pi.f.sel(nsel, nsel); pi.s0 = pi.f.ss; } p.flags |= Predraw; } printdiff(s: string, ftext: string) { l := len s; if (l > len ftext) l = len ftext; for (i := 0; i < l - 1 && s[i] == ftext[i]; i++) ; if (i > 15) i -= 15; else i = 0; sj := len s - 1; fj := len ftext - 1; while(sj > 0 && fj > 0 && s[sj] == ftext[fj]){ sj--; fj--; } fprint(stderr, "o/live and o/mero text differs: pos %d:\n", i); fprint(stderr, "text[%s]\n\nfile[%s]\n\n", s[i:sj], ftext[i:fj]); } syncchk(p: ref Panel) { if (debug['T']){ # double check that we are really synced with the FS. pi := pimpl(p); fname := p.path + "/data"; fd := open(fname, OREAD); data := readfile(fd); if (data != nil){ ftext := string data; s := pi.blks.pack().s; if (s != ftext){ pi.dump(); printdiff(s, ftext); panic("insdel bug"); } } # double check that the text in the frame is also in sync. pi.f.chk(); } } # BUG: needs to be reworked. While we were editing, other o/lives could # update the text. Thus, we better send the version of the text that changes # are for, and make o/mero discard (and report as failed) those that do not # match the version. When we see a discarded change, we may simply reload the # text and undo our edit. syncedits(p: ref Panel, edits: list of ref Edit) { pi := pimpl(p); ctlstr := ""; for(; edits != nil; edits = tl edits){ ev : string; pick e := hd edits { Ins => ev = sprint("ins %d %s\n", e.pos, e.s); Del => ev = sprint("del %d %d\n", e.pos, len e.s); } ctlstr += escape(ev); } # This is a good time to update o/mero idea of our selection. if (ctlstr != "" || pi.f.ss != pi.f.se && (pi.fsss != pi.f.ss || pi.fsse != pi.f.se)){ ctlstr += escape(sprint("sel %d %d\n", pi.f.ss, pi.f.se)); pi.fsss = pi.f.ss; pi.fsse = pi.f.se; } # fsctl would scape our \n's, and we want a single write for the entire # set of updates, if feasible. if (ctlstr != ""){ # a good time to update our selection in o/mero. do so. fname := p.path + "/ctl"; fd := open(fname, OWRITE); if (fd != nil){ if (debug['E']) fprint(stderr, "fsctl: %s: [%s]\n", p.path, ctlstr); seek(fd, big 0, 2); data := array of byte ctlstr; write(fd, data, len data); } } p.flags &= ~Psync; # we're synced now. } applyedit(p: ref Panel, e: ref Edit): int { if (e != nil){ pi := pimpl(p); pick ep := e { Ins => pi.blks.ins(ep.s, ep.pos); pi.f.ins(ep.pos, len ep.s); pi.s0 = pi.f.ss; return ep.pos + len ep.s; Del => pi.blks.del(len ep.s, ep.pos); pi.f.del(ep.pos, len ep.s); pi.s0 = pi.f.ss; return ep.pos; } } return -1; } undo(p: ref Panel): int { pi := pimpl(p); return applyedit(p, pi.edits.undo()); } redo(p: ref Panel): int { pi := pimpl(p); return applyedit(p, pi.edits.redo()); } ins(p: ref Panel, s: string, pos: int) { pi := pimpl(p); pi.blks.ins(s, pos); pi.s0 = fixposins(pi.s0, pos, len s); if (pi.edits.ins(s, pos) < 0){ syncedits(p, pi.edits.sync()); if (pi.edits.ins(s, pos) < 0){ pi.dump(); panic(sprint("text: ins [%s] %d failed", s, pos)); } } } del(p: ref Panel, n: int, pos: int): string { pi := pimpl(p); ds := pi.blks.del(n, pos); pi.s0 = fixposdel(pi.s0, pos, n); if (pi.edits.del(ds, pos) < 0){ syncedits(p, pi.edits.sync()); if (pi.edits.del(ds, pos) < 0){ pi.dump(); panic(sprint("text: del [%s] %d failed", ds, pos)); } } return ds; } pctl(p: ref Panel, s: string) { pi := pimpl(p); if (!(p.flags&Pshown)) pi.f.i = nil; # avoid drawing odirty := p.flags&Pdirty; ofont := p.font; if (panelctl(tree, p, s) < 0){ (nargs, args) := tokenize(s, " \t\n"); if (nargs > 0) case hd args { "sel" => ss := fixpos(int nth(args, 1), pi.blks.blen()); se := fixpos(int nth(args, 2), pi.blks.blen()); if (se < ss) se = ss; pi.s0 = ss; if (ss != pi.f.ss || se != pi.f.se){ pi.f.sel(ss, se); p.flags |= Predraw; } pi.fsss = pi.f.ss; pi.fsse = pi.f.se; "mark" => pi.f.mark = fixpos(int nth(args, 1), pi.blks.blen()); "tab" => tab := int nth(args, 1); if (tab < 3) tab = 3; if (tab > 20) tab = 20; if (pi.f.tabsz != tab){ pi.f.tabsz = tab; pi.f.resize(pi.f.r, pi.f.i); p.flags |= Predraw; } * => ; # ignore others } } if (odirty && !(p.flags&Pdirty)){ pi.edits.cpos = pi.edits.pos; # BUG: potential race here: User puts, editor issues a clean ctl, # and the user adds an edit in the mean while. # If this happens, we could just set Pdirty again. # But let see if the race is a real one. eds := pi.edits.sync(); if (eds != nil) panic("text pctl: clean while dirty edits"); } if (ofont != p.font) pi.f.resize(pi.f.r, pi.f.i); # recompute widths and init } # Any of: # o/mero: /path/to/panel ins pos str # o/mero: /path/to/panel del pos n pevent(p: ref Panel, ev: string) { pi := pimpl(p); if (!(p.flags&Pshown)) pi.f.i = nil; # avoid drawing n := strchr(ev, ' '); ev = ev[n+1:]; n = strchr(ev, ' '); ev = ev[n+1:]; # ins|del ... if (len ev < 5){ fprint(stderr, "o/live: pevent: bad event\n"); return; } op := ev[0:3]; ev = ev[3+1:]; # pos ... n = strchr(ev, ' '); if (n < 0){ fprint(stderr, "o/live: pevent: short event\n"); return; } pos := int ev[0:n]; ev = ev[n+1:]; # n|str if (len ev == 0) return; ev = ev[0:len ev -1]; # remove \n if (op == "del"){ n = int ev; del(p, n, pos); pi.f.del(pos, n); } else { s := unescape(ev); ins(p, s, pos); pi.f.ins(pos, len s); if ((p.flags&Pline) && pos + len s> pi.f.ss) pi.f.sel(pos, pos); } pi.s0 = pi.f.ss; p.flags |= Pdirty; pdraw(p); } writepsel(p: ref Panel) { fd := open("/dev/sel", OWRITE|OTRUNC); if (fd != nil) fprint(fd, "%s\n", p.path); } movetoshow(p: ref Panel, pos: int) { pi := pimpl(p); if (pos < pi.f.pos || (pos > pi.f.pos + pi.f.nr)){ npos := pi.gotopos(pos); pi.f.init(pi.blks, npos); pi.f.scroll(-pi.f.sz.y/2); } } cut(p: ref Panel, putsnarf: int): int { pi := pimpl(p); nr := pi.f.se - pi.f.ss; if (nr == 0) return 0; pos := pi.f.ss; s := ""; if (!(p.flags&Pedit)){ t := pi.blks.pack(); s = t.s[pi.f.se:pi.f.se]; pi.f.sel(pi.f.ss, pi.f.ss); } else { s = del(p, nr, pos); pi.f.del(pos, nr); } pi.s0 = pi.f.ss; if (putsnarf){ writesnarf(s); writepsel(p); } return 1; } paste(p: ref Panel, pos: int): int { if (!(p.flags&Pedit)) return 0; pi := pimpl(p); s := readsnarf(); if (len s == 0) return 0; ins(p, s, pos); pi.f.ins(pos, len s); pi.f.sel(pos, pos + len s); pi.s0 = pi.f.ss; writepsel(p); return 1; } lastcmd: string; exec(p: ref Panel, pos: int) { pi := pimpl(p); s := pi.blks.pack(); (ws, we) := pi.wordat(pos, 1, 1); c := s.s[ws:we]; lastcmd = c; pi.f.sel(ws, we); pi.s0 = pi.f.ss; if (c == "Exit") terminate(); else p.fsctl("exec " + c + "\n", 1); } look(p: ref Panel, pos: int) { if (seltext != nil){ p.fsctl("look " + seltext + "\n", 1); return; } pi := pimpl(p); s := pi.blks.pack(); (ws, we) := pi.wordat(pos, 1, 1); c := s.s[ws:we]; pi.f.sel(ws, we); pi.s0 = pi.f.ss; p.fsctl("look " + c + "\n", 1); } apply(p: ref Panel, nil: int) { if (p.flags&Pedit) if (lastcmd != nil){ writepsel(p); p.fsctl("apply " + lastcmd + "\n", 1); } } search(p: ref Panel, pos: int) { ws, we: int; txt: string; pi := pimpl(p); s := pi.blks.pack(); if (seltext == nil){ (ws, we) = pi.wordat(pos, 0, 1); txt = s.s[ws:we]; } else { we = pos+1; txt = seltext; } i := strstr(s.s[we:], txt); if (i < 0) i = strstr(s.s, txt); else i += we; if (i < 0) return; if (debug['T']) fprint(stderr, "search: %s: pos %d\n", dtxt(txt), i); pi.f.sel(i, i + len txt); pi.s0 = pi.f.ss; movetoshow(p, pi.f.ss); pt := pi.f.pos2pt(pi.f.ss); pt = pt.add((p.font.width(txt[0:1])/2, p.font.height -2)); win.wmctl("ptr " + string pt.x + " " + string pt.y); } # Select requires us to coallesce mouse events that would do nothing # but update further our possition. getmouse(mc: chan of ref Cpointer): ref Cpointer { m := <-mc; for(;;){ mm: ref Cpointer; mm = nil; # nbrecv alt { mm = <-mc => ; * => ; } if (mm == nil || mm.buttons != m.buttons) break; m = mm; } return m; } select(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer): ref Cpointer { pi := pimpl(p); b := m.buttons; s := pi.blks.pack(); pi.f.sel(pos, pos); if (m.flags&(CMdouble|CMtriple)){ long := m.flags&CMtriple; (ws, we) := pi.wordat(pos, long, 0); if (debug['T']) fprint(stderr, "getword: pos %d long %d: %d %d %s\n", pos, long, ws, we, dtxt(s.s[ws:we])); pi.f.sel(ws, we); pi.s0 = pi.f.ss; writepsel(p); do { m = <-mc; } while (m.buttons == b); } else { pi.s0 = pos; do { if (m.xy.y < pi.f.r.min.y){ # scroll up pi.f.scroll(-1-(pi.f.r.min.y-m.xy.y)/p.font.height); pi.f.sel(pi.f.pos, pi.s0); sys->sleep(100); } else if (m.xy.y > pi.f.r.max.y){ # scroll down pi.f.scroll(1+(m.xy.y-pi.f.r.max.y)/p.font.height); pi.f.sel(pi.s0, pi.f.pos + pi.f.nr); sys->sleep(100); } else { pos = pi.f.pt2pos(m.xy); if (pos < pi.s0) pi.f.sel(pos, pi.s0); else pi.f.sel(pi.s0, pos); } m = getmouse(mc); } while (m.buttons == b); } if (pi.f.ss == pi.f.se) seltext = nil; else seltext = s.s[pi.f.ss:pi.f.se]; return m; } pmouse1(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer) { dirties := 0; pi := pimpl(p); m = select(p, pos, m, mc); case m.buttons { 0 => return; 3 => if (cut(p, 1)) dirties = 1; 5 => if (cut(p, 0)){ pos = pi.f.ss; dirties = 1; } if (paste(p, pos)) dirties = 1; } if (dirties) dirty(p, 1); while(m.buttons) m = <-mc; } pmouse2(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer) { pi := pimpl(p); if (pi.f.ss != pi.f.se && pos >= pi.f.ss && pos < pi.f.se){ m = <-mc; if (m.buttons == 0) exec(p, pos); } else { m = select(p, pos, m, mc); if (m.buttons == 0) exec(p, pos); } while(m.buttons) m = <-mc; } # To avoid blinking, we use double buffering during # scroll operations. This is because we redraw the whole frame, # and do not shift rectangles around. This makes it easier to draw # overlayed scroll bars. scrdraw(p: ref Panel, i: ref Image): ref Image { pi := pimpl(p); r := Rect((0,0), (p.rect.dx(), p.rect.dy())); if (i == nil){ i = win.display.newimage(r, win.image.chans, 0, Draw->White); pi.f.resize(r, i); } drawscrollbar(p, i); win.image.draw(p.rect, i, nil, (0,0)); return i; } scrdone(p: ref Panel) { pi := pimpl(p); pi.f.resize(p.rect, win.image); } drawscrollbar(p: ref Panel, i: ref Image) { Barwid: con 25; Barht: con 120; pi := pimpl(p); bar, r: Rect; bar.max.x = r.max.x = i.r.max.x - Inset - 3; bar.min.x = r.min.x = r.max.x - Barwid; r.min.y = i.r.min.y + Inset; ysz := Barht; r.max.y = r.min.y + ysz; if (r.max.y + 3 > i.r.max.y){ r.max.y = i.r.max.y - 3; ysz = r.dy(); } i.draw(r.addpt((2,2)), cols[TEXT], cols[SHAD], (0,0)); i.draw(r, cols[CLEAR], cols[SHAD], (0,0)); l := len pi.blks.b[0].s; y0 := dy := 0; if (l > 0){ y0 = ysz * pi.f.pos / l; dy = ysz * pi.f.nr / l; if (dy < 3) dy = 3; } else { y0 = 0; dy = r.dy(); } bar.min.y = r.min.y + y0; bar.max.y = bar.min.y + dy; if (bar.max.y > r.max.y) bar.max.y = r.max.y; if (bar.min.y > bar.max.y - 2) bar.min.y = bar.max.y - 2; i.draw(bar.addpt((3,3)), cols[TEXT], cols[SHAD], (0,0)); i.draw(bar, cols[SET], nil, (0,0)); i.border(r, 1, cols[BORD], (0,0)); } jumpscale(p: ref Panel, xy: Point): real { pi := pimpl(p); s := pi.blks.pack(); dy := xy.y - p.rect.min.y; if (dy > p.rect.max.y - xy.y) dy = p.rect.max.y - xy.y; dc := len s.s - pi.f.pos; if (dc < pi.f.pos) dc = pi.f.pos; if (dy < 1) dy = 1; return (real dc) / (real dy); } pmouse3scrl(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer) { pi := pimpl(p); xy := m.xy; jfactor := real 0; jy := xy.y; old := pi.f.pos; i := scrdraw(p, nil); b := m.buttons; do { if (jfactor <= real 0.00001){ pos = pi.f.pos; jfactor = jumpscale(p, xy); } jpos := pos + int (real (xy.y - jy) * jfactor); if (jpos != old) jpos = pi.gotopos(jpos); if (jpos == old) ; # sys->sleep(10); else { old = jpos; pi.f.init(pi.blks, jpos); scrdraw(p, i); } more := 0; m = <- mc; do { alt { m = <-mc => more=1; * => more = 0; } } while(more && m.buttons == b); xy = m.xy; } while(m.buttons); scrdone(p); } pmouse3(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer) { pi := pimpl(p); m = <- mc; case m.buttons { 0 => textmenu.last = pi.mlast; cmd := textmenu.run(m, mc); if (debug['T']) fprint(stderr, "pmouse3: cmd: %s at %d\n", cmd, pos); case cmd { "scroll" => pmouse3scrl(p, pos, m, mc); "Find" => search(p, pos); "Open" => look(p, pos); "Exec" => exec(p, pos); "Apply" => apply(p, pos); "Cut" => if (cut(p, 1)) dirty(p, 1); textmenu.last = Mpaste; "Paste" => if (paste(p, pos)) dirty(p, 1); * => p.fsctl("exec " + cmd + "\n", 1); } pi.mlast = textmenu.last; 4 => pmouse3scrl(p, pos, m, mc); * => do m = <-mc; while (m.buttons); } } pmouse(p: ref Panel, m: ref Cpointer, mc: chan of ref Cpointer) { if ((p.flags&Ptag) && intag(p, m.xy)){ tagmouse(tree, p, m, mc); return; } pi := pimpl(p); # mouse movement syncs any pending edit. eds := pi.edits.sync(); if (eds != nil){ syncedits(p, eds); syncchk(p); } pos := pi.f.pt2pos(m.xy); case m.buttons { 1 => # For tbls, this should probably allow drag&drop pmouse1(p, pos, m, mc); if (debug['T'] > 1) pi.dump(); 2 => pmouse2(p, pos, m, mc); if (debug['T'] > 1) pi.dump(); 4 => pmouse3(p, pos, m, mc); if (debug['T'] > 1) pi.dump(); } } pkbd(p: ref Panel, k: int) { Killword: con 16r17; # C-w pi := pimpl(p); pos := pi.f.ss; s := ""; s[0] = k; if (!(p.flags&Pedit)){ p.fsctl("keys " + s + "\n", 1); return; } case k { '\b' => if (!cut(p, 1) && pos > 0){ pos--; movetoshow(p, pos); del(p, 1, pos); pi.f.del(pos, 1); pi.s0= pi.f.ss; } dirty(p, 1); Killword => (ws, we) := pi.wordat(pos, 1, 1); if (ws != we){ pos = ws; n := we - ws; movetoshow(p, pos); del(p, n, pos); pi.f.del(pos, n); pi.s0 = pi.f.ss; } Keyboard->Del => p.fsctl("interrupt\n", 1); Keyboard->Left or Keyboard->Right=> if (k == Keyboard->Left) pos = undo(p); else pos = redo(p); if (pos >= 0){ dirty(p, pi.edits.pos != pi.edits.cpos); pi.f.sel(pos, pos); pi.s0 = pos; movetoshow(p, pos); } Keyboard->Up or Keyboard->Down => if (p.flags&Pline) return; n := pi.f.sz.y / 3; if (n < 2) n = 2; if (k == Keyboard->Up) n = -n; pi.f.scroll(n); Keyboard->Esc => if (pi.s0 < pos) pi.f.sel(pi.s0, pos); else pi.f.sel(pos, pi.s0); * => if (k == '\n' && (p.flags&Pline)){ if (pi.s0 < pos) pi.f.sel(pi.s0, pos); else pi.f.sel(pos, pi.s0); exec(p, pos); } else { cut(p, 1); if (!(p.flags&Pline)) movetoshow(p, pos); ins(p, s, pos); pi.f.ins(pos, len s); pt := pi.f.pos2pt(pos); if (pt.y + p.font.height >= pi.f.r.max.y && !(p.flags&Pline)) pi.f.scroll(1); dirty(p, 1); if ((p.flags&Pedit) && (k == '\n' || (p.flags&Pline))) settextsize(p); } } if (debug['T'] > 1) pi.dump(); } psync(p: ref Panel) { fprint(stderr, "owptext: sync called\n"); pi := pimpl(p); fd := open(p.path + "/data", OWRITE|OTRUNC); if (fd == nil) return; s := pi.blks.pack(); data := array of byte s.s; if (write(fd, data, len data) != len data) fprint(stderr, "o/live: sync %s: %r\n", p.path); }