plan9port/src/cmd/acme/cols.c
Russ Cox 37f8ed2410 acme: mouse movement for Del
If the mouse was in the tag of the old window,
it was most likely pointing at Del. If bringing up a
new window from below and not moving the mouse
somewhere else, adjust it so that it ends up pointing
at Del in the replacement window's tag too.
This makes it easy to Del a sequence of windows in
a column, from top to bottom.

http://www.youtube.com/watch?v=ET8w6RT6u5M

R=r
http://codereview.appspot.com/6558047
2012-09-23 22:01:56 -04:00

582 lines
12 KiB
C

#include <u.h>
#include <libc.h>
#include <draw.h>
#include <thread.h>
#include <cursor.h>
#include <mouse.h>
#include <keyboard.h>
#include <frame.h>
#include <fcall.h>
#include <plumb.h>
#include "dat.h"
#include "fns.h"
static Rune Lheader[] = {
'N', 'e', 'w', ' ',
'C', 'u', 't', ' ',
'P', 'a', 's', 't', 'e', ' ',
'S', 'n', 'a', 'r', 'f', ' ',
'S', 'o', 'r', 't', ' ',
'Z', 'e', 'r', 'o', 'x', ' ',
'D', 'e', 'l', 'c', 'o', 'l', ' ',
0
};
void
colinit(Column *c, Rectangle r)
{
Rectangle r1;
Text *t;
draw(screen, r, display->white, nil, ZP);
c->r = r;
c->w = nil;
c->nw = 0;
t = &c->tag;
t->w = nil;
t->col = c;
r1 = r;
r1.max.y = r1.min.y + font->height;
textinit(t, fileaddtext(nil, t), r1, &reffont, tagcols);
t->what = Columntag;
r1.min.y = r1.max.y;
r1.max.y += Border;
draw(screen, r1, display->black, nil, ZP);
textinsert(t, 0, Lheader, 38, TRUE);
textsetselect(t, t->file->b.nc, t->file->b.nc);
draw(screen, t->scrollr, colbutton, nil, colbutton->r.min);
c->safe = TRUE;
}
Window*
coladd(Column *c, Window *w, Window *clone, int y)
{
Rectangle r, r1;
Window *v;
int i, j, minht, ymax, buggered;
v = nil;
r = c->r;
r.min.y = c->tag.fr.r.max.y+Border;
if(y<r.min.y && c->nw>0){ /* steal half of last window by default */
v = c->w[c->nw-1];
y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2;
}
/* look for window we'll land on */
for(i=0; i<c->nw; i++){
v = c->w[i];
if(y < v->r.max.y)
break;
}
buggered = 0;
if(c->nw > 0){
if(i < c->nw)
i++; /* new window will go after v */
/*
* if landing window (v) is too small, grow it first.
*/
minht = v->tag.fr.font->height+Border+1;
j = 0;
while(!c->safe || v->body.fr.maxlines<=3 || Dy(v->body.all) <= minht){
if(++j > 10){
buggered = 1; /* too many windows in column */
break;
}
colgrow(c, v, 1);
}
/*
* figure out where to split v to make room for w
*/
/* new window stops where next window begins */
if(i < c->nw)
ymax = c->w[i]->r.min.y-Border;
else
ymax = c->r.max.y;
/* new window must start after v's tag ends */
y = max(y, v->tagtop.max.y+Border);
/* new window must start early enough to end before ymax */
y = min(y, ymax - minht);
/* if y is too small, too many windows in column */
if(y < v->tagtop.max.y+Border)
buggered = 1;
/*
* resize & redraw v
*/
r = v->r;
r.max.y = ymax;
draw(screen, r, textcols[BACK], nil, ZP);
r1 = r;
y = min(y, ymax-(v->tag.fr.font->height*v->taglines+v->body.fr.font->height+Border+1));
r1.max.y = min(y, v->body.fr.r.min.y+v->body.fr.nlines*v->body.fr.font->height);
r1.min.y = winresize(v, r1, FALSE, FALSE);
r1.max.y = r1.min.y+Border;
draw(screen, r1, display->black, nil, ZP);
/*
* leave r with w's coordinates
*/
r.min.y = r1.max.y;
}
if(w == nil){
w = emalloc(sizeof(Window));
w->col = c;
draw(screen, r, textcols[BACK], nil, ZP);
wininit(w, clone, r);
}else{
w->col = c;
winresize(w, r, FALSE, TRUE);
}
w->tag.col = c;
w->tag.row = c->row;
w->body.col = c;
w->body.row = c->row;
c->w = realloc(c->w, (c->nw+1)*sizeof(Window*));
memmove(c->w+i+1, c->w+i, (c->nw-i)*sizeof(Window*));
c->nw++;
c->w[i] = w;
c->safe = TRUE;
/* if there were too many windows, redraw the whole column */
if(buggered)
colresize(c, c->r);
savemouse(w);
/* near the button, but in the body */
moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3)));
barttext = &w->body;
return w;
}
void
colclose(Column *c, Window *w, int dofree)
{
Rectangle r;
int i, didmouse, up;
/* w is locked */
if(!c->safe)
colgrow(c, w, 1);
for(i=0; i<c->nw; i++)
if(c->w[i] == w)
goto Found;
error("can't find window");
Found:
r = w->r;
w->tag.col = nil;
w->body.col = nil;
w->col = nil;
didmouse = restoremouse(w);
if(dofree){
windelete(w);
winclose(w);
}
c->nw--;
memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*));
c->w = realloc(c->w, c->nw*sizeof(Window*));
if(c->nw == 0){
draw(screen, r, display->white, nil, ZP);
return;
}
up = 0;
if(i == c->nw){ /* extend last window down */
w = c->w[i-1];
r.min.y = w->r.min.y;
r.max.y = c->r.max.y;
}else{ /* extend next window up */
up = 1;
w = c->w[i];
r.max.y = w->r.max.y;
}
draw(screen, r, textcols[BACK], nil, ZP);
if(c->safe) {
if(!didmouse && up)
w->showdel = TRUE;
winresize(w, r, FALSE, TRUE);
if(!didmouse && up)
movetodel(w);
}
}
void
colcloseall(Column *c)
{
int i;
Window *w;
if(c == activecol)
activecol = nil;
textclose(&c->tag);
for(i=0; i<c->nw; i++){
w = c->w[i];
winclose(w);
}
c->nw = 0;
free(c->w);
free(c);
clearmouse();
}
void
colmousebut(Column *c)
{
moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2));
}
void
colresize(Column *c, Rectangle r)
{
int i;
Rectangle r1, r2;
Window *w;
clearmouse();
r1 = r;
r1.max.y = r1.min.y + c->tag.fr.font->height;
textresize(&c->tag, r1, TRUE);
draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min);
r1.min.y = r1.max.y;
r1.max.y += Border;
draw(screen, r1, display->black, nil, ZP);
r1.max.y = r.max.y;
for(i=0; i<c->nw; i++){
w = c->w[i];
w->maxlines = 0;
if(i == c->nw-1)
r1.max.y = r.max.y;
else
r1.max.y = r1.min.y+(Dy(w->r)+Border)*Dy(r)/Dy(c->r);
r1.max.y = max(r1.max.y, r1.min.y + Border+font->height);
r2 = r1;
r2.max.y = r2.min.y+Border;
draw(screen, r2, display->black, nil, ZP);
r1.min.y = r2.max.y;
r1.min.y = winresize(w, r1, FALSE, i==c->nw-1);
}
c->r = r;
}
static
int
colcmp(const void *a, const void *b)
{
Rune *r1, *r2;
int i, nr1, nr2;
r1 = (*(Window**)a)->body.file->name;
nr1 = (*(Window**)a)->body.file->nname;
r2 = (*(Window**)b)->body.file->name;
nr2 = (*(Window**)b)->body.file->nname;
for(i=0; i<nr1 && i<nr2; i++){
if(*r1 != *r2)
return *r1-*r2;
r1++;
r2++;
}
return nr1-nr2;
}
void
colsort(Column *c)
{
int i, y;
Rectangle r, r1, *rp;
Window **wp, *w;
if(c->nw == 0)
return;
clearmouse();
rp = emalloc(c->nw*sizeof(Rectangle));
wp = emalloc(c->nw*sizeof(Window*));
memmove(wp, c->w, c->nw*sizeof(Window*));
qsort(wp, c->nw, sizeof(Window*), colcmp);
for(i=0; i<c->nw; i++)
rp[i] = wp[i]->r;
r = c->r;
r.min.y = c->tag.fr.r.max.y;
draw(screen, r, textcols[BACK], nil, ZP);
y = r.min.y;
for(i=0; i<c->nw; i++){
w = wp[i];
r.min.y = y;
if(i == c->nw-1)
r.max.y = c->r.max.y;
else
r.max.y = r.min.y+Dy(w->r)+Border;
r1 = r;
r1.max.y = r1.min.y+Border;
draw(screen, r1, display->black, nil, ZP);
r.min.y = r1.max.y;
y = winresize(w, r, FALSE, i==c->nw-1);
}
free(rp);
free(c->w);
c->w = wp;
}
void
colgrow(Column *c, Window *w, int but)
{
Rectangle r, cr;
int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
Window *v;
for(i=0; i<c->nw; i++)
if(c->w[i] == w)
goto Found;
error("can't find window");
Found:
cr = c->r;
if(but < 0){ /* make sure window fills its own space properly */
r = w->r;
if(i==c->nw-1 || c->safe==FALSE)
r.max.y = cr.max.y;
else
r.max.y = c->w[i+1]->r.min.y - Border;
winresize(w, r, FALSE, TRUE);
return;
}
cr.min.y = c->w[0]->r.min.y;
if(but == 3){ /* full size */
if(i != 0){
v = c->w[0];
c->w[0] = w;
c->w[i] = v;
}
draw(screen, cr, textcols[BACK], nil, ZP);
winresize(w, cr, FALSE, TRUE);
for(i=1; i<c->nw; i++)
c->w[i]->body.fr.maxlines = 0;
c->safe = FALSE;
return;
}
/* store old #lines for each window */
onl = w->body.fr.maxlines;
nl = emalloc(c->nw * sizeof(int));
ny = emalloc(c->nw * sizeof(int));
tot = 0;
for(j=0; j<c->nw; j++){
l = c->w[j]->taglines-1 + c->w[j]->body.fr.maxlines;
nl[j] = l;
tot += l;
}
/* approximate new #lines for this window */
if(but == 2){ /* as big as can be */
memset(nl, 0, c->nw * sizeof(int));
goto Pack;
}
nnl = min(onl + max(min(5, w->taglines-1+w->maxlines), onl/2), tot);
if(nnl < w->taglines-1+w->maxlines)
nnl = (w->taglines-1+w->maxlines + nnl)/2;
if(nnl == 0)
nnl = 2;
dnl = nnl - onl;
/* compute new #lines for each window */
for(k=1; k<c->nw; k++){
/* prune from later window */
j = i+k;
if(j<c->nw && nl[j]){
l = min(dnl, max(1, nl[j]/2));
nl[j] -= l;
nl[i] += l;
dnl -= l;
}
/* prune from earlier window */
j = i-k;
if(j>=0 && nl[j]){
l = min(dnl, max(1, nl[j]/2));
nl[j] -= l;
nl[i] += l;
dnl -= l;
}
}
Pack:
/* pack everyone above */
y1 = cr.min.y;
for(j=0; j<i; j++){
v = c->w[j];
r = v->r;
r.min.y = y1;
r.max.y = y1+Dy(v->tagtop);
if(nl[j])
r.max.y += 1 + nl[j]*v->body.fr.font->height;
r.min.y = winresize(v, r, c->safe, FALSE);
r.max.y += Border;
draw(screen, r, display->black, nil, ZP);
y1 = r.max.y;
}
/* scan to see new size of everyone below */
y2 = c->r.max.y;
for(j=c->nw-1; j>i; j--){
v = c->w[j];
r = v->r;
r.min.y = y2-Dy(v->tagtop);
if(nl[j])
r.min.y -= 1 + nl[j]*v->body.fr.font->height;
r.min.y -= Border;
ny[j] = r.min.y;
y2 = r.min.y;
}
/* compute new size of window */
r = w->r;
r.min.y = y1;
r.max.y = y2;
h = w->body.fr.font->height;
if(Dy(r) < Dy(w->tagtop)+1+h+Border)
r.max.y = r.min.y + Dy(w->tagtop)+1+h+Border;
/* draw window */
r.max.y = winresize(w, r, c->safe, TRUE);
if(i < c->nw-1){
r.min.y = r.max.y;
r.max.y += Border;
draw(screen, r, display->black, nil, ZP);
for(j=i+1; j<c->nw; j++)
ny[j] -= (y2-r.max.y);
}
/* pack everyone below */
y1 = r.max.y;
for(j=i+1; j<c->nw; j++){
v = c->w[j];
r = v->r;
r.min.y = y1;
r.max.y = y1+Dy(v->tagtop);
if(nl[j])
r.max.y += 1 + nl[j]*v->body.fr.font->height;
y1 = winresize(v, r, c->safe, j==c->nw-1);
if(j < c->nw-1){ /* no border on last window */
r.min.y = y1;
r.max.y += Border;
draw(screen, r, display->black, nil, ZP);
y1 = r.max.y;
}
}
free(nl);
free(ny);
c->safe = TRUE;
winmousebut(w);
}
void
coldragwin(Column *c, Window *w, int but)
{
Rectangle r;
int i, b;
Point p, op;
Window *v;
Column *nc;
clearmouse();
setcursor(mousectl, &boxcursor);
b = mouse->buttons;
op = mouse->xy;
while(mouse->buttons == b)
readmouse(mousectl);
setcursor(mousectl, nil);
if(mouse->buttons){
while(mouse->buttons)
readmouse(mousectl);
return;
}
for(i=0; i<c->nw; i++)
if(c->w[i] == w)
goto Found;
error("can't find window");
Found:
if(w->tagexpand) /* force recomputation of window tag size */
w->taglines = 1;
p = mouse->xy;
if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){
colgrow(c, w, but);
winmousebut(w);
return;
}
/* is it a flick to the right? */
if(abs(p.y-op.y)<10 && p.x>op.x+30 && rowwhichcol(c->row, p)==c)
p.x = op.x+Dx(w->r); /* yes: toss to next column */
nc = rowwhichcol(c->row, p);
if(nc!=nil && nc!=c){
colclose(c, w, FALSE);
coladd(nc, w, nil, p.y);
winmousebut(w);
return;
}
if(i==0 && c->nw==1)
return; /* can't do it */
if((i>0 && p.y<c->w[i-1]->r.min.y) || (i<c->nw-1 && p.y>w->r.max.y)
|| (i==0 && p.y>w->r.max.y)){
/* shuffle */
colclose(c, w, FALSE);
coladd(c, w, nil, p.y);
winmousebut(w);
return;
}
if(i == 0)
return;
v = c->w[i-1];
if(p.y < v->tagtop.max.y)
p.y = v->tagtop.max.y;
if(p.y > w->r.max.y-Dy(w->tagtop)-Border)
p.y = w->r.max.y-Dy(w->tagtop)-Border;
r = v->r;
r.max.y = p.y;
if(r.max.y > v->body.fr.r.min.y){
r.max.y -= (r.max.y-v->body.fr.r.min.y)%v->body.fr.font->height;
if(v->body.fr.r.min.y == v->body.fr.r.max.y)
r.max.y++;
}
r.min.y = winresize(v, r, c->safe, FALSE);
r.max.y = r.min.y+Border;
draw(screen, r, display->black, nil, ZP);
r.min.y = r.max.y;
if(i == c->nw-1)
r.max.y = c->r.max.y;
else
r.max.y = c->w[i+1]->r.min.y-Border;
winresize(w, r, c->safe, TRUE);
c->safe = TRUE;
winmousebut(w);
}
Text*
colwhich(Column *c, Point p)
{
int i;
Window *w;
if(!ptinrect(p, c->r))
return nil;
if(ptinrect(p, c->tag.all))
return &c->tag;
for(i=0; i<c->nw; i++){
w = c->w[i];
if(ptinrect(p, w->r)){
if(ptinrect(p, w->tagtop) || ptinrect(p, w->tag.all))
return &w->tag;
/* exclude partial line at bottom */
if(p.x >= w->body.scrollr.max.x && p.y >= w->body.fr.r.max.y)
return nil;
return &w->body;
}
}
return nil;
}
int
colclean(Column *c)
{
int i, clean;
clean = TRUE;
for(i=0; i<c->nw; i++)
clean &= winclean(c->w[i], TRUE);
return clean;
}