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