| #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; |
| } |