#
# Text and frame management needs a rewrite.
#
implement Pimpl;
include "mods.m";
mods, debug, win, tree: import dat;
frame: Framem;
Menu: import menus;
setcursor, Waiting, Arrow,
getfont, Cpointer, cols, panelback, maxpt, cookclick, drawtag,
BACK,TEXT, readsnarf, Inset, terminate, writesnarf, readfile,
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;
Frame: import frame;
fixpos, dtxt, strstr, Maxline, Blks, strchr, Str: import tblks;
Edit, Edits: import tundo;
Ptext: import text;
usage: import arg;
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();
frame = load Framem Framem->PATH;
if (frame == nil)
return sprint("loading %s: %r", Framem->PATH);
dat = d;
str = load String String->PATH;
if (str == nil)
return sprint("loading %s: %r", String->PATH);
text = load Text Text->PATH;
if (text == nil)
return sprint("loading %s: %r", Text->PATH);
tblks = load Tblks Tblks->PATH;
if (tblks == nil)
return sprint("loading %s: %r", Tblks->PATH);
tundo= load Tundo Tundo->PATH;
if (tundo == nil)
return sprint("loading %s: %r", Tundo->PATH);
frame->init(d);
tblks->init(sys, str, err, debug['T']);
tundo->init(sys, err, debug['T']);
text->init(sys, str, frame, err, tblks, tundo, debug['T']);
return nil;
}
settextsize(p: ref Panel)
{
pi := pimpl(p);
pi.rect = p.rect;
if (!(p.flags&Pline)){
pi.rect.min.y += 1;
}
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;
}
}
pi.nrows = pi.rect.dy() / p.font.height;
pi.ncols = pi.rect.dx() / p.font.width("M");
}
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.mlast = 0; # Set Exec by default
p.flags &= ~(Pline|Pedit);
pi.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 = 5; # Open by default
} else if (len p.name > 4 && p.name[0:4] == "text"){
p.flags |= Pedit;
p.wants = Point(1,1);
pi.mlast = 2; # Find
} 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);
}
pterm(p: ref Panel)
{
if (p.implid != -1){
pi := pimpl(p);
panels[p.implid] = nil;
p.implid = -1;
pi.term();
}
}
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");
else
p.fsctl("clean\n");
spawn tree.tags("/"); # we might get here called from tree; avoid deadlock.
}
}
filltext(p: ref Panel)
{
nl := "\n";
pi := pimpl(p);
(i, n) := pi.blks.seek(pi.froff + pi.f.nchars);
b := pi.blks.b;
for(; i < len b && b[i] != nil && !pi.f.lastlinefull; i++){
pi.f.insert(b[i].s[n:], pi.f.nchars);
n = 0;
}
# Frame may not clear the right of the last line. BUG?
# we force that by inserting a fake \n
if (!pi.f.lastlinefull){
pi.f.insert(nl, pi.f.nchars);
pi.f.delete(pi.f.nchars-1, pi.f.nchars);
}
# clear the unused part
pt := pi.rect.min;
pt.y += pi.f.nlines * p.font.height;
wr := Rect(pt, pi.rect.max);
cback := panelback(p);
if (pi.f.ticked && pi.f.p0 == pi.f.p1 && pi.f.p0 == pi.f.nchars){
# avoid clearing the tick
hidesel(p);
win.image.draw(wr, cback, nil, (0,0));
drawsel(p);
} else
win.image.draw(wr, cback, nil, (0,0));
}
hidesel(p: ref Panel)
{
pi := pimpl(p);
pi.f.drawsel(pi.f.ptofchar(pi.f.p0), pi.f.p0, pi.f.p1, 0);
}
drawsel(p: ref Panel)
{
pi := pimpl(p);
f := pi.f;
ss := fixpos(f.nchars, pi.ss - pi.froff);
se := fixpos(f.nchars, pi.se - pi.froff);
if(f.ticked && ss == f.p0 && se == f.p1) # it's already there.
return;
# This code below was taken from acme's
#
if(f.p1<=ss || se<=f.p0 || ss==se || f.p1==f.p0){
# no overlap or too easy to bother trying
if (f.p0 != f.p1 || !(p.flags&Pedit))
f.drawsel(f.ptofchar(f.p0), f.p0, f.p1, 0);
if (ss != se || (p.flags&Pedit))
f.drawsel(f.ptofchar(ss), ss, se, 1);
} else {
if(ss < f.p0){
# extend selection backwards
f.drawsel(f.ptofchar(ss), ss, f.p0, 1);
}else if(ss > f.p0){
# trim first part of selection
f.drawsel(f.ptofchar(f.p0), f.p0, ss, 0);
}
if(se > f.p1){
# extend selection forwards
f.drawsel(f.ptofchar(f.p1), f.p1, se, 1);
}else if(se < f.p1){
# trim last part of selection
f.drawsel(f.ptofchar(se), se, f.p1, 0);
}
}
f.p0 = ss;
f.p1 = se;
}
tab(p: ref Panel)
{
pi := pimpl(p);
(n, toks) := tokenize(pi.tabtext, "\t\n");
if (n <= 0)
return;
wl := array[n] of string;
for (i := 0; i < n; i++){
wl[i] = hd toks;
toks = tl toks;
}
mint := p.font.width("0");
maxtab := 3 * mint;
maxt := maxtab;
pi.f.maxtab = 3 * mint;
colw := 0;
for(i=0; i<n; i++){
w := p.font.width(wl[i]);
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 = pi.rect.dx()/colw;
if (ncol < 1)
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;
w := p.font.width(wl[j]);
if(maxt-w%maxt < mint){
ns += "\t";
w += mint;
}
do{
ns += "\t";
w += maxt-(w%maxt);
}while(w < colw);
}
ns += "\n";
}
pi.blks = Blks.new(ns);
l := len ns;
pi.s0 = fixpos(pi.s0, l);
pi.ss = fixpos(pi.ss, l);
pi.se = fixpos(pi.se, l);
pi.froff = fixpos(pi.froff, l);
pi.mark = fixpos(pi.mark, l);
}
drawmark(img: ref Image, ur: Rect, cback: ref Image)
{
lr := ur;
# lr.max.x = lr.min.x + ur.dx()/3;
# img.draw(lr, cback, nil, (0,0));
# lr.min.x = lr.max.x;
lr.max.x = lr.min.x + ur.dx()/3;
img.draw(lr, cols[TEXT], nil, (0,0));
lr.min.x = lr.max.x;
lr.max.x = ur.max.x;
img.draw(lr, cback, nil, (0,0));
}
drawmarks(img: ref Image, pi: ref Ptext, ht: int, prect: Rect, cback: ref Image)
{
ur := prect;
ur.max.y = ur.min.y+1;
if (pi.froff == 0)
drawmark(img, ur, cback);
else
img.draw(ur, cback, nil, (0,0));
if (pi.f.nlines + 1 < pi.f.maxlines){ # leave one line for typing
dr := prect;
dr.min.y += (pi.f.nlines+1)*ht + 2;
dr.max.y = dr.min.y + 1;
if (dr.max.y <= prect.max.y && dr.min.y > prect.min.y)
drawmark(img, dr, cback);
}
}
pdraw(p: ref Panel)
{
if (!(p.flags&Pshown))
return;
w := dat->win;
pi := pimpl(p);
pi.rect = p.rect;
if (!(p.flags&Pline)){
pi.rect.min.y += 1;
}
if (pi.f != nil){
hidesel(p);
pi.f.clear(0);
} else
pi.f = Frame.new();
fcols := gui->cols[0:gui->NCOL];
cback := panelback(p);
fcols[BACK] = cback;
pi.f.init(pi.rect, p.font, w.image, fcols);
fcols = nil;
if (p.flags&Ptbl)
tab(p);
else
pi.f.maxtab = pi.tab * p.font.width("0");
filltext(p);
drawsel(p);
settextsize(p);
if (p.flags&Ptag)
drawtag(p);
if (!(p.flags&Pline))
drawmarks(win.image, pi, p.font.height, p.rect, cback);
}
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 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);
s := string d;
pi.blks = Blks.new(s);
pi.edits = Edits.new();
if (p.flags&Ptbl)
pi.tabtext = s;
pi.nlines = stringlines(s);
pi.froff = fixpos(len s, pi.froff);
pi.mark = fixpos(len s, pi.mark);
pos := 0;
if ( (p.flags&Pline) || !(p.flags&Pedit))
pos= len s;
else
pos = fixpos(len s, pi.s0);
pi.ss = pi.se = pi.s0 = pos;
p.flags |= Predraw;
}
syncchk(p: ref Panel)
{
if (debug['T']){
# double check that we are really synced with the FS.
fname := p.path + "/data";
fd := open(fname, OREAD);
data := readfile(fd);
if (data != nil){
pi := pimpl(p);
ftext := string data;
s := pi.blks.pack().s;
if (s != ftext){
pi.dump();
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, "after sync text differs: pos %d:\n", i);
fprint(stderr, "text[%s]\n\nfile[%s]\n\n", s[i:sj], ftext[i:fj]);
panic("insdel bug");
}
}
}
}
syncedits(p: ref Panel, edits: list of ref Edit)
{
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);
}
# fsctl would scape our \n's, and we want a single write for the entire
# set of updates, if feasible.
if (ctlstr != ""){
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
{
pi := pimpl(p);
if (e != nil)
pick ep := e {
Ins =>
pi.ins(ep.s, ep.pos);
pi.f.insert(ep.s, ep.pos-pi.froff);
return ep.pos + len ep.s;
Del =>
pi.del(len ep.s, ep.pos);
pi.f.delete(ep.pos - pi.froff, ep.pos - pi.froff + len ep.s);
filltext(p);
return ep.pos;
}
return -1;
}
undo(p: ref Panel): int
{
pi := pimpl(p);
e := pi.edits.undo();
return applyedit(p, e);
}
redo(p: ref Panel): int
{
pi := pimpl(p);
e := pi.edits.redo();
return applyedit(p, e);
}
ins(p: ref Panel, s: string, pos: int)
{
pi := pimpl(p);
pi.ins(s, pos);
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.del(n, pos);
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);
odirty := p.flags&Pdirty;
if (panelctl(tree, p, s) < 0){
(nargs, args) := tokenize(s, " \t\n");
if (nargs > 0)
case hd args {
"sel" =>
pi.ss = int nth(args, 1);
pi.se = int nth(args, 2);
if (pi.se < pi.ss)
pi.se = pi.ss;
pi.s0 = pi.ss;
"mark" =>
pi.mark = int nth(args, 1);
"tab" =>
pi.tab = int nth(args, 1);
if (pi.tab < 3)
pi.tab = 3;
if (pi.tab > 10)
pi.tab = 10;
* =>
; # ignore others
}
}
if (odirty && !(p.flags&Pdirty)){
pi.edits.cpos = pi.edits.pos;
# 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");
}
}
# 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);
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.delete(pos, pos+n);
filltext(p);
} else {
s := unescape(ev);
ins(p, s, pos);
# pdraw should refill the frame. (?)
if (p.flags&Pline)
pos += len s;
}
pi.ss = pi.se = pi.s0 = pos; # that's the
p.flags |= Pdirty; # best we can do.
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): int
{
pi := pimpl(p);
if (pos < pi.froff || (pos >= pi.froff + pi.f.nchars && pi.f.nlines == pi.f.maxlines)){
pi.gotopos(pos);
pi.froff = pi.scroll(-pi.f.nlines/2);
return 1;
}
return 0;
}
cut(p: ref Panel, putsnarf: int): int
{
pi := pimpl(p);
if (pi.ss == pi.se)
return 0;
p0 := pi.ss - pi.froff;
p1 := pi.se - pi.froff;
s := del(p, pi.se - pi.ss, pi.ss);
pi.f.delete(p0, p1);
pi.se = pi.s0 = pi.ss;
filltext(p);
if (putsnarf){
writesnarf(s);
writepsel(p);
}
return 1;
}
paste(p: ref Panel, pos: int): int
{
pi := pimpl(p);
s := readsnarf();
if (len s > 0){
ins(p, s, pos);
pi.f.insert(s, pos - pi.froff);
pi.ss = pi.s0 = pos;
pi.se = pos + len s;
drawsel(p);
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;
if (c == "Exit")
terminate();
pi.ss = pi.s0 = ws;
pi.se = we;
drawsel(p);
p.fsctl("exec " + c + "\n");
}
look(p: ref Panel, pos: int)
{
pi := pimpl(p);
s := pi.blks.pack();
if (seltext != nil){
p.fsctl("look " + seltext + "\n");
return;
}
(ws, we) := pi.wordat(pos, 1, 1);
c := s.s[ws:we];
pi.ss = pi.s0 = ws;
pi.se = we;
drawsel(p);
p.fsctl("look " + c + "\n");
}
apply(p: ref Panel, nil: int)
{
if (lastcmd != nil){
writepsel(p);
p.fsctl("apply " + lastcmd + "\n");
}
}
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.ss = pi.s0 = i;
pi.se = pi.ss+ len txt;
if (movetoshow(p, pi.ss))
pdraw(p);
else
drawsel(p);
pt := pi.f.ptofchar(pi.f.p0);
pt.add(pi.rect.min);
pt.add((p.font.width(txt[0:1])-2, p.font.height -2));
win.wmctl("ptr " + string pt.x + " " + string pt.y);
}
frscroll(p: ref Panel, nlines: int)
{
pi := pimpl(p);
if (nlines == 0){
return;
}
noff := pi.scroll(nlines);
if (noff < pi.froff){
s := pi.blks.pack().s;
s = s[noff:pi.froff];
pi.f.insert(s, 0);
pi.froff = noff;
} else if (noff > pi.froff){
if (pi.f.nchars > 0)
if (noff < pi.froff + pi.f.nchars)
pi.f.delete(0, noff - pi.froff);
else if (pi.f.nchars > 0)
pi.f.delete(0, pi.f.nchars - 1);
pi.froff = noff;
filltext(p);
}
}
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();
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.ss = pi.s0 = ws;
pi.se = we;
writepsel(p);
drawsel(p);
do {
m = <-mc;
} while (m.buttons == b);
} else {
pi.s0 = pos;
m = pi.f.select(m, mc, frscroll, p);
if (pi.f.p0 + pi.froff < pi.s0){
pi.ss = pi.f.p0 + pi.froff;
pi.se = pi.s0;
} else {
pi.ss = pi.s0;
pi.se = pi.f.p1 + pi.froff;
}
if (!(p.flags&Pedit) && pi.ss == pi.se)
hidesel(p);
else
drawsel(p);
}
if (pi.ss == pi.se)
seltext = nil;
else
seltext = s.s[pi.ss:pi.se];
return m;
}
pmouse1(p: ref Panel, pos: int, m: ref Cpointer, mc: chan of ref Cpointer)
{
pi := pimpl(p);
m = select(p, pos, m, mc);
case m.buttons {
0 =>
return;
3 =>
if (cut(p, 1))
dirty(p, 1);
5 =>
if (cut(p, 0))
pos = pi.ss;
if (paste(p, pos))
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 (pos >= pi.ss && pos < pi.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);
fcols := gui->cols[0:gui->NCOL];
cback := panelback(p);
fcols[BACK] = cback;
i.draw(i.r, cback, nil, (0,0));
pi.f.clear(0);
fr := r;
if (!(p.flags&Pline)){
fr.min.y += 1;
}
pi.f.init(fr, p.font, i, fcols);
if (p.flags&Ptbl)
pi.f.maxtab = 3 * p.font.width("0");
else
pi.f.maxtab = pi.tab * p.font.width("0");
filltext(p);
drawsel(p);
if (!(p.flags&Pline))
drawmarks(i, pi, p.font.height, r, cback);
drawscrollbar(p, i);
win.image.draw(p.rect, i, nil, (0,0));
return i;
}
scrdone(p: ref Panel)
{
pi := pimpl(p);
pi.f.clear(0);
pi.f = nil;
pdraw(p);
}
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.froff / l;
dy = ysz * pi.f.nchars / 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 - pi.rect.min.y;
if (dy > pi.rect.max.y - xy.y)
dy = pi.rect.max.y - xy.y;
dc := len s.s - pi.froff;
if (dc < pi.froff)
dc = pi.froff;
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.froff;
i := scrdraw(p, nil);
b := m.buttons;
do {
if (jfactor <= real 0.00001){
pos = pi.froff;
jfactor = jumpscale(p, xy);
}
jpos := pos + int (real (xy.y - jy) * jfactor);
if (jpos == old || !pi.gotopos(jpos))
; # sys->sleep(10);
else {
old = 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 = 3;
"Paste" =>
if (paste(p, pos))
dirty(p, 1);
* =>
p.fsctl("exec " + cmd + "\n");
}
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.froff + pi.f.charofpt(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();
}
drawsel(p);
}
pkbd(p: ref Panel, k: int)
{
Killword: con 16r17; # C-w
pi := pimpl(p);
pos := pi.ss;
s := "";
s[0] = k;
if (!(p.flags&Pedit)){
p.fsctl("keys " + s + "\n");
return;
}
case k {
'\b' =>
if (!cut(p, 1) && pos > 0){
pos--;
if (movetoshow(p, pos))
pdraw(p);
del(p, 1, pos);
if (pos < pi.froff | pos > pi.froff + pi.f.nchars + 1)
panic("pos del bug");
pi.f.delete(pos-pi.froff, pos-pi.froff+1);
pi.ss = pi.se = pi.s0 = pos;
}
dirty(p, 1);
filltext(p);
Killword =>
(ws, we) := pi.wordat(pos, 1, 1);
if (ws != we){
pos = ws;
n := we - ws;
if (movetoshow(p, pos))
pdraw(p);
del(p, n, pos);
if (pos < pi.froff | pos > pi.froff + pi.f.nchars + 1)
panic("pos killword bug");
pi.f.delete(pos-pi.froff, pos+n-pi.froff);
pi.ss = pi.se = pi.s0 = pos;
}
Keyboard->Del =>
p.fsctl("interrupt\n");
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.ss = pi.se = pi.s0 = pos;
if (movetoshow(p, pos))
pdraw(p);
else
drawsel(p);
}
Keyboard->Up or Keyboard->Down =>
if (p.flags&Pline)
return;
n := pi.f.nlines / 3;
if (n < 2)
n = 2;
if (k == Keyboard->Up)
n = -n;
noff := pi.scroll(n);
if (noff != pi.froff){
pi.froff = noff;
pdraw(p);
}
Keyboard->Esc =>
if (pi.s0 < pos){
pi.ss = pi.s0;
pi.se = pos;
} else {
pi.ss = pos;
pi.se = pi.s0;
}
drawsel(p);
* =>
if (k == '\n' && (p.flags&Pline)){
if (pi.s0 < pos){
pi.ss = pi.s0;
pi.se = pos;
} else {
pi.ss = pos;
pi.se = pi.s0;
}
drawsel(p);
exec(p, pos);
} else {
cut(p, 1);
if (!(p.flags&Pline) && movetoshow(p, pos))
pdraw(p);
ins(p, s, pos);
pi.f.insert(s, pos - pi.froff);
pi.ss = pi.se = pos+1;
if (pos >= pi.froff + pi.f.nchars - 2 && !(p.flags&Pline)){
noff := pi.scroll(pi.f.nlines/3);
if (noff != pi.froff){
pi.froff = noff;
pdraw(p);
}
}
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);
}
|