#
# 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 <wordchar>s if pos at <wordchar>
# (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<n; i++){
w := p.font.width(wl[i]);
wwl[i] = w;
if(maxt-w%maxt < mint)
w += mint;
if(w % maxt)
w += maxt-(w%maxt);
if(w > 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<nrow; i++){
for(j:=i; j<n; j+=nrow){
ns += wl[j];
if(j+nrow >= 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);
}
|