master cols.c
ohmu  ·  2026-09-13
  1#include <u.h>
  2#include <libc.h>
  3#include <draw.h>
  4#include <thread.h>
  5#include <cursor.h>
  6#include <mouse.h>
  7#include <keyboard.h>
  8#include <frame.h>
  9#include <fcall.h>
 10#include <plumb.h>
 11#include <libsec.h>
 12#include "parser.h"
 13#include "dat.h"
 14#include "fns.h"
 15
 16static Rune Lheader[] = {
 17	'N', 'e', 'w', ' ',
 18	'C', 'u', 't', ' ',
 19	'P', 'a', 's', 't', 'e', ' ',
 20	'S', 'n', 'a', 'r', 'f', ' ',
 21	'S', 'o', 'r', 't', ' ',
 22	'Z', 'e', 'r', 'o', 'x', ' ',
 23	'D', 'e', 'l', 'c', 'o', 'l', ' ',
 24	0
 25};
 26
 27Image *colsbg;
 28Image *bordercol;
 29
 30void
 31colinit(Column *c, Rectangle r)
 32{
 33	Rectangle r1;
 34	Text *t;
 35    if(ispastel("root"))
 36    	colsbg = allocimagemix(display, C_ROOTBG, C_MIX);
 37    else
 38        colsbg = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_ROOTBG);
 39    
 40    if(ispastel("border"))
 41    	bordercol = allocimagemix(display, C_BORDER, C_MIX);
 42    else
 43        bordercol = allocimage(display, Rect(0,0,1,1), RGBA32, 1, C_BORDER);
 44    
 45    draw(screen, r, colsbg, nil, ZP);
 46	c->r = r;
 47	c->w = nil;
 48	c->nw = 0;
 49	t = &c->tag;
 50	t->w = nil;
 51	t->col = c;
 52	r1 = r;
 53	r1.max.y = r1.min.y + font->height;
 54	textinit(t, fileaddtext(nil, t), r1, &reffont, tagcols);
 55	t->what = Columntag;
 56	r1.min.y = r1.max.y;
 57	r1.max.y += Border;
 58	draw(screen, r1, bordercol, nil, ZP);
 59	textinsert(t, 0, Lheader, 38, TRUE);
 60	textsetselect(t, t->file->b.nc, t->file->b.nc);
 61	draw(screen, t->scrollr, colbutton, nil, colbutton->r.min);
 62	c->safe = TRUE;
 63}
 64
 65Window*
 66coladd(Column *c, Window *w, Window *clone, int y)
 67{
 68	Rectangle r, r1;
 69	Window *v;
 70	int i, j, minht, ymax, buggered;
 71
 72	v = nil;
 73	r = c->r;
 74	r.min.y = c->tag.fr.r.max.y+Border;
 75	if(y<r.min.y && c->nw>0){	/* steal half of last window by default */
 76		v = c->w[c->nw-1];
 77		y = v->body.fr.r.min.y+Dy(v->body.fr.r)/2;
 78	}
 79	/* look for window we'll land on */
 80	for(i=0; i<c->nw; i++){
 81		v = c->w[i];
 82		if(y < v->r.max.y)
 83			break;
 84	}
 85	buggered = 0;
 86	if(c->nw > 0){
 87		if(i < c->nw)
 88			i++;	/* new window will go after v */
 89		/*
 90		 * if landing window (v) is too small, grow it first.
 91		 */
 92		minht = v->tag.fr.font->height+Border+1;
 93		j = 0;
 94		while(!c->safe || v->body.fr.maxlines<=3 || Dy(v->body.all) <= minht){
 95			if(++j > 10){
 96				buggered = 1;	/* too many windows in column */
 97				break;
 98			}
 99			colgrow(c, v, 1);
100		}
101
102		/*
103		 * figure out where to split v to make room for w
104		 */
105		
106		/* new window stops where next window begins */
107		if(i < c->nw)
108			ymax = c->w[i]->r.min.y-Border;
109		else
110			ymax = c->r.max.y;
111		
112		/* new window must start after v's tag ends */
113		y = max(y, v->tagtop.max.y+Border);
114		
115		/* new window must start early enough to end before ymax */
116		y = min(y, ymax - minht);
117		
118		/* if y is too small, too many windows in column */
119		if(y < v->tagtop.max.y+Border)
120			buggered = 1;
121
122		/*
123		 * resize & redraw v
124		 */
125		r = v->r;
126		r.max.y = ymax;
127		draw(screen, r, textcols[BACK], nil, ZP);
128		r1 = r;
129		y = min(y, ymax-(v->tag.fr.font->height*v->taglines+v->body.fr.font->height+Border+1));
130		r1.max.y = min(y, v->body.fr.r.min.y+v->body.fr.nlines*v->body.fr.font->height);
131		r1.min.y = winresize(v, r1, FALSE, FALSE);
132		r1.max.y = r1.min.y+Border;
133		draw(screen, r1, bordercol, nil, ZP);
134		
135		/*
136		 * leave r with w's coordinates
137		 */
138		r.min.y = r1.max.y;
139	}
140	if(w == nil){
141		w = emalloc(sizeof(Window));
142		w->col = c;
143		draw(screen, r, textcols[BACK], nil, ZP);
144		wininit(w, clone, r);
145	}else{
146		w->col = c;
147		winresize(w, r, FALSE, TRUE);
148	}
149	w->tag.col = c;
150	w->tag.row = c->row;
151	w->body.col = c;
152	w->body.row = c->row;
153	c->w = realloc(c->w, (c->nw+1)*sizeof(Window*));
154	memmove(c->w+i+1, c->w+i, (c->nw-i)*sizeof(Window*));
155	c->nw++;
156	c->w[i] = w;
157	c->safe = TRUE;
158	
159	/* if there were too many windows, redraw the whole column */
160	if(buggered)
161		colresize(c, c->r);
162
163	savemouse(w);
164	/* near the button, but in the body */
165	moveto(mousectl, addpt(w->tag.scrollr.max, Pt(3, 3)));
166	barttext = &w->body;
167	return w;
168}
169
170void
171colclose(Column *c, Window *w, int dofree)
172{
173	Rectangle r;
174	int i, didmouse, up;
175
176	/* w is locked */
177	if(!c->safe)
178		colgrow(c, w, 1);
179	for(i=0; i<c->nw; i++)
180		if(c->w[i] == w)
181			goto Found;
182	error("can't find window");
183  Found:
184	r = w->r;
185	w->tag.col = nil;
186	w->body.col = nil;
187	w->col = nil;
188	didmouse = restoremouse(w);
189	if(dofree){
190		windelete(w);
191		winclose(w);
192	}
193	c->nw--;
194	memmove(c->w+i, c->w+i+1, (c->nw-i)*sizeof(Window*));
195	c->w = realloc(c->w, c->nw*sizeof(Window*));
196	if(c->nw == 0){
197		draw(screen, r, colsbg, nil, ZP);
198		return;
199	}
200	up = 0;
201	if(i == c->nw){		/* extend last window down */
202		w = c->w[i-1];
203		r.min.y = w->r.min.y;
204		r.max.y = c->r.max.y;
205	}else{			/* extend next window up */
206		up = 1;
207		w = c->w[i];
208		r.max.y = w->r.max.y;
209	}
210	draw(screen, r, textcols[BACK], nil, ZP);
211	if(c->safe) {
212		if(!didmouse && up)
213			w->showdel = TRUE;
214		winresize(w, r, FALSE, TRUE);
215		if(!didmouse && up)
216			movetodel(w);
217	}
218}
219
220void
221colcloseall(Column *c)
222{
223	int i;
224	Window *w;
225
226	if(c == activecol)
227		activecol = nil;
228	textclose(&c->tag);
229	for(i=0; i<c->nw; i++){
230		w = c->w[i];
231		winclose(w);
232	}
233	c->nw = 0;
234	free(c->w);
235	free(c);
236	clearmouse();
237}
238
239void
240colmousebut(Column *c)
241{
242	moveto(mousectl, divpt(addpt(c->tag.scrollr.min, c->tag.scrollr.max), 2));
243}
244
245void
246colresize(Column *c, Rectangle r)
247{
248	int i;
249	Rectangle r1, r2;
250	Window *w;
251
252	clearmouse();
253	r1 = r;
254	r1.max.y = r1.min.y + c->tag.fr.font->height;
255	textresize(&c->tag, r1, TRUE);
256	draw(screen, c->tag.scrollr, colbutton, nil, colbutton->r.min);
257	r1.min.y = r1.max.y;
258	r1.max.y += Border;
259	draw(screen, r1, bordercol, nil, ZP);
260	r1.max.y = r.max.y;
261	for(i=0; i<c->nw; i++){
262		w = c->w[i];
263		w->maxlines = 0;
264		if(i == c->nw-1)
265			r1.max.y = r.max.y;
266		else
267			r1.max.y = r1.min.y+(Dy(w->r)+Border)*Dy(r)/Dy(c->r);
268		r1.max.y = max(r1.max.y, r1.min.y + Border+font->height);
269		r2 = r1;
270		r2.max.y = r2.min.y+Border;
271		draw(screen, r2, bordercol, nil, ZP);
272		r1.min.y = r2.max.y;
273		r1.min.y = winresize(w, r1, FALSE, i==c->nw-1);
274	}
275	c->r = r;
276}
277
278static
279int
280colcmp(const void *a, const void *b)
281{
282	Rune *r1, *r2;
283	int i, nr1, nr2;
284
285	r1 = (*(Window**)a)->body.file->name;
286	nr1 = (*(Window**)a)->body.file->nname;
287	r2 = (*(Window**)b)->body.file->name;
288	nr2 = (*(Window**)b)->body.file->nname;
289	for(i=0; i<nr1 && i<nr2; i++){
290		if(*r1 != *r2)
291			return *r1-*r2;
292		r1++;
293		r2++;
294	}
295	return nr1-nr2;
296}
297
298void
299colsort(Column *c)
300{
301	int i, y;
302	Rectangle r, r1, *rp;
303	Window **wp, *w;
304
305	if(c->nw == 0)
306		return;
307	clearmouse();
308	rp = emalloc(c->nw*sizeof(Rectangle));
309	wp = emalloc(c->nw*sizeof(Window*));
310	memmove(wp, c->w, c->nw*sizeof(Window*));
311	qsort(wp, c->nw, sizeof(Window*), colcmp);
312	for(i=0; i<c->nw; i++)
313		rp[i] = wp[i]->r;
314	r = c->r;
315	r.min.y = c->tag.fr.r.max.y;
316	draw(screen, r, textcols[BACK], nil, ZP);
317	y = r.min.y;
318	for(i=0; i<c->nw; i++){
319		w = wp[i];
320		r.min.y = y;
321		if(i == c->nw-1)
322			r.max.y = c->r.max.y;
323		else
324			r.max.y = r.min.y+Dy(w->r)+Border;
325		r1 = r;
326		r1.max.y = r1.min.y+Border;
327		draw(screen, r1, bordercol, nil, ZP);
328		r.min.y = r1.max.y;
329		y = winresize(w, r, FALSE, i==c->nw-1);
330	}
331	free(rp);
332	free(c->w);
333	c->w = wp;
334}
335
336void
337colgrow(Column *c, Window *w, int but)
338{
339	Rectangle r, cr;
340	int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
341	Window *v;
342
343	for(i=0; i<c->nw; i++)
344		if(c->w[i] == w)
345			goto Found;
346	error("can't find window");
347
348  Found:
349	cr = c->r;
350	if(but < 0){	/* make sure window fills its own space properly */
351		r = w->r;
352		if(i==c->nw-1 || c->safe==FALSE)
353			r.max.y = cr.max.y;
354		else
355			r.max.y = c->w[i+1]->r.min.y - Border;
356		winresize(w, r, FALSE, TRUE);
357		return;
358	}
359	cr.min.y = c->w[0]->r.min.y;
360	if(but == 3){	/* full size */
361		if(i != 0){
362			v = c->w[0];
363			c->w[0] = w;
364			c->w[i] = v;
365		}
366		draw(screen, cr, textcols[BACK], nil, ZP);
367		winresize(w, cr, FALSE, TRUE);
368		for(i=1; i<c->nw; i++)
369			c->w[i]->body.fr.maxlines = 0;
370		c->safe = FALSE;
371		return;
372	}
373	/* store old #lines for each window */
374	onl = w->body.fr.maxlines;
375	nl = emalloc(c->nw * sizeof(int));
376	ny = emalloc(c->nw * sizeof(int));
377	tot = 0;
378	for(j=0; j<c->nw; j++){
379		l = c->w[j]->taglines-1 + c->w[j]->body.fr.maxlines;
380		nl[j] = l;
381		tot += l;
382	}
383	/* approximate new #lines for this window */
384	if(but == 2){	/* as big as can be */
385		memset(nl, 0, c->nw * sizeof(int));
386		goto Pack;
387	}
388	nnl = min(onl + max(min(5, w->taglines-1+w->maxlines), onl/2), tot);
389	if(nnl < w->taglines-1+w->maxlines)
390		nnl = (w->taglines-1+w->maxlines + nnl)/2;
391	if(nnl == 0)
392		nnl = 2;
393	dnl = nnl - onl;
394	/* compute new #lines for each window */
395	for(k=1; k<c->nw; k++){
396		/* prune from later window */
397		j = i+k;
398		if(j<c->nw && nl[j]){
399			l = min(dnl, max(1, nl[j]/2));
400			nl[j] -= l;
401			nl[i] += l;
402			dnl -= l;
403		}
404		/* prune from earlier window */
405		j = i-k;
406		if(j>=0 && nl[j]){
407			l = min(dnl, max(1, nl[j]/2));
408			nl[j] -= l;
409			nl[i] += l;
410			dnl -= l;
411		}
412	}
413    Pack:
414	/* pack everyone above */
415	y1 = cr.min.y;
416	for(j=0; j<i; j++){
417		v = c->w[j];
418		r = v->r;
419		r.min.y = y1;
420		r.max.y = y1+Dy(v->tagtop);
421		if(nl[j])
422			r.max.y += 1 + nl[j]*v->body.fr.font->height;
423		r.min.y = winresize(v, r, c->safe, FALSE);
424		r.max.y += Border;
425		draw(screen, r, bordercol, nil, ZP);
426		y1 = r.max.y;
427	}
428	/* scan to see new size of everyone below */
429	y2 = c->r.max.y;
430	for(j=c->nw-1; j>i; j--){
431		v = c->w[j];
432		r = v->r;
433		r.min.y = y2-Dy(v->tagtop);
434		if(nl[j])
435			r.min.y -= 1 + nl[j]*v->body.fr.font->height;
436		r.min.y -= Border;
437		ny[j] = r.min.y;
438		y2 = r.min.y;
439	}
440	/* compute new size of window */
441	r = w->r;
442	r.min.y = y1;
443	r.max.y = y2;
444	h = w->body.fr.font->height;
445	if(Dy(r) < Dy(w->tagtop)+1+h+Border)
446		r.max.y = r.min.y + Dy(w->tagtop)+1+h+Border;
447	/* draw window */
448	r.max.y = winresize(w, r, c->safe, TRUE);
449	if(i < c->nw-1){
450		r.min.y = r.max.y;
451		r.max.y += Border;
452		draw(screen, r, bordercol, nil, ZP);
453		for(j=i+1; j<c->nw; j++)
454			ny[j] -= (y2-r.max.y);
455	}
456	/* pack everyone below */
457	y1 = r.max.y;
458	for(j=i+1; j<c->nw; j++){
459		v = c->w[j];
460		r = v->r;
461		r.min.y = y1;
462		r.max.y = y1+Dy(v->tagtop);
463		if(nl[j])
464			r.max.y += 1 + nl[j]*v->body.fr.font->height;
465		y1 = winresize(v, r, c->safe, j==c->nw-1);
466		if(j < c->nw-1){	/* no border on last window */
467			r.min.y = y1;
468			r.max.y += Border;
469			draw(screen, r, bordercol, nil, ZP);
470			y1 = r.max.y;
471		}
472	}
473	free(nl);
474	free(ny);
475	c->safe = TRUE;
476	winmousebut(w);
477}
478
479void
480coldragwin(Column *c, Window *w, int but)
481{
482	Rectangle r;
483	int i, b;
484	Point p, op;
485	Window *v;
486	Column *nc;
487
488	clearmouse();
489	setcursor(mousectl, &boxcursor);
490	b = mouse->buttons;
491	op = mouse->xy;
492	while(mouse->buttons == b)
493		readmouse(mousectl);
494	setcursor(mousectl, nil);
495	if(mouse->buttons){
496		while(mouse->buttons)
497			readmouse(mousectl);
498		return;
499	}
500
501	for(i=0; i<c->nw; i++)
502		if(c->w[i] == w)
503			goto Found;
504	error("can't find window");
505
506  Found:
507	if(w->tagexpand)	/* force recomputation of window tag size */
508		w->taglines = 1;
509	p = mouse->xy;
510	if(abs(p.x-op.x)<5 && abs(p.y-op.y)<5){
511		colgrow(c, w, but);
512		winmousebut(w);
513		return;
514	}
515	/* is it a flick to the right? */
516	if(abs(p.y-op.y)<10 && p.x>op.x+30 && rowwhichcol(c->row, p)==c)
517		p.x = op.x+Dx(w->r);	/* yes: toss to next column */
518	nc = rowwhichcol(c->row, p);
519	if(nc!=nil && nc!=c){
520		colclose(c, w, FALSE);
521		coladd(nc, w, nil, p.y);
522		winmousebut(w);
523		return;
524	}
525	if(i==0 && c->nw==1)
526		return;			/* can't do it */
527	if((i>0 && p.y<c->w[i-1]->r.min.y) || (i<c->nw-1 && p.y>w->r.max.y)
528	|| (i==0 && p.y>w->r.max.y)){
529		/* shuffle */
530		colclose(c, w, FALSE);
531		coladd(c, w, nil, p.y);
532		winmousebut(w);
533		return;
534	}
535	if(i == 0)
536		return;
537	v = c->w[i-1];
538	if(p.y < v->tagtop.max.y)
539		p.y = v->tagtop.max.y;
540	if(p.y > w->r.max.y-Dy(w->tagtop)-Border)
541		p.y = w->r.max.y-Dy(w->tagtop)-Border;
542	r = v->r;
543	r.max.y = p.y;
544	if(r.max.y > v->body.fr.r.min.y){
545		r.max.y -= (r.max.y-v->body.fr.r.min.y)%v->body.fr.font->height;
546		if(v->body.fr.r.min.y == v->body.fr.r.max.y)
547			r.max.y++;
548	}
549	r.min.y = winresize(v, r, c->safe, FALSE);
550	r.max.y = r.min.y+Border;
551	draw(screen, r, bordercol, nil, ZP);
552	r.min.y = r.max.y;
553	if(i == c->nw-1)
554		r.max.y = c->r.max.y;
555	else
556		r.max.y = c->w[i+1]->r.min.y-Border;
557	winresize(w, r, c->safe, TRUE);
558	c->safe = TRUE;
559	winmousebut(w);
560}
561
562Text*
563colwhich(Column *c, Point p)
564{
565	int i;
566	Window *w;
567
568	if(!ptinrect(p, c->r))
569		return nil;
570	if(ptinrect(p, c->tag.all))
571		return &c->tag;
572	for(i=0; i<c->nw; i++){
573		w = c->w[i];
574		if(ptinrect(p, w->r)){
575			if(ptinrect(p, w->tagtop) || ptinrect(p, w->tag.all))
576				return &w->tag;
577			/* exclude partial line at bottom */
578			if(p.x >= w->body.scrollr.max.x && p.y >= w->body.fr.r.max.y)
579				return nil;
580			return &w->body;
581		}
582	}
583	return nil;
584}
585
586int
587colclean(Column *c)
588{
589	int i, clean;
590
591	clean = TRUE;
592	for(i=0; i<c->nw; i++)
593		clean &= winclean(c->w[i], TRUE);
594	return clean;
595}