1 /* Copyright ©2004-2006 Anselm R. Garbe <garbeam at gmail dot com>
2  * Copyright ©2006-2010 Kris Maglione <maglione.k at Gmail>
3  * See LICENSE file for license details.
4  */
5 #include "dat.h"
6 #include <math.h>
7 #include <strings.h>
8 #include "fns.h"
9 
10 static void	column_resizeframe_h(Frame*, Rectangle);
11 
12 char *modes[] = {
13 	[Coldefault] =	"default",
14 	[Colstack] =	"stack",
15 	[Colmax] =	"max",
16 };
17 
18 bool
column_setmode(Area * a,const char * mode)19 column_setmode(Area *a, const char *mode) {
20 	char *str, *tok, *orig;
21 	char add, old;
22 
23 	/*
24 	 * The mapping between the current internal
25 	 * representation and the external interface
26 	 * is currently a bit complex. That will probably
27 	 * change.
28 	 */
29 
30 	orig = strdup(mode);
31 	str = orig;
32 	old = '\0';
33 	while(*(tok = str)) {
34 		add = old;
35 		while((old=*str) && !strchr("+-^", old))
36 			str++;
37 		*str = '\0';
38 		if(str > tok) {
39 			print("'%s' %c\n", tok, add);
40 			if(!strcmp(tok, "max")) {
41 				if(add == '\0' || add == '+')
42 					a->max = true;
43 				else if(add == '-')
44 					a->max = false;
45 				else
46 					a->max = !a->max;
47 			}else
48 			if(!strcmp(tok, "stack")) {
49 				if(add == '\0' || add == '+')
50 					a->mode = Colstack;
51 				else if(add == '-')
52 					a->mode = Coldefault;
53 				else
54 					a->mode = a->mode == Colstack ? Coldefault : Colstack;
55 			}else
56 			if(!strcmp(tok, "default")) {
57 				if(add == '\0' || add == '+') {
58 					a->mode = Coldefault;
59 					column_arrange(a, true);
60 				}else if(add == '-')
61 					a->mode = Colstack;
62 				else
63 					a->mode = a->mode == Coldefault ? Colstack : Coldefault;
64 			}else {
65 				free(orig);
66 				return false;
67 			}
68 		}
69 		if(old)
70 			str++;
71 
72 	}
73 	free(orig);
74 	return true;
75 }
76 
77 char*
column_getmode(Area * a)78 column_getmode(Area *a) {
79 	return sxprint("%s%cmax", a->mode == Colstack ? "stack" : "default",
80 				  a->max ? '+' : '-');
81 }
82 
83 int
column_minwidth(void)84 column_minwidth(void)
85 {
86 	return 4 * labelh(def.font);
87 }
88 
89 Area*
column_new(View * v,Area * pos,int scrn,uint w)90 column_new(View *v, Area *pos, int scrn, uint w) {
91 	Area *a;
92 
93 	assert(!pos || !pos->floating && pos->screen == scrn);
94 	a = area_create(v, pos, scrn, w);
95 	return a;
96 #if 0
97 	if(!a)
98 		return nil;
99 
100 	view_arrange(v);
101 	view_update(v);
102 #endif
103 }
104 
105 void
column_insert(Area * a,Frame * f,Frame * pos)106 column_insert(Area *a, Frame *f, Frame *pos) {
107 
108 	f->area = a;
109 	f->client->floating = false;
110 	f->screen = a->screen;
111 	f->column = area_idx(a);
112 	frame_insert(f, pos);
113 	if(a->sel == nil)
114 		area_setsel(a, f);
115 }
116 
117 /* Temporary. */
118 static void
stack_scale(Frame * first,int height)119 stack_scale(Frame *first, int height) {
120 	Frame *f;
121 	Area *a;
122 	uint dy;
123 	int surplus;
124 
125 	a = first->area;
126 
127 	/*
128 	 * Will need something like this.
129 	column_fit(a, &ncol, &nuncol);
130 	*/
131 
132 	dy = 0;
133 	for(f=first; f && !f->collapsed; f=f->anext)
134 		dy += Dy(f->colr);
135 
136 	/* Distribute the surplus.
137 	 */
138 	surplus = height - dy;
139 	for(f=first; f && !f->collapsed; f=f->anext)
140 		f->colr.max.y += ((float)Dy(f->r) / dy) * surplus;
141 }
142 
143 static void
stack_info(Frame * f,Frame ** firstp,Frame ** lastp,int * dyp,int * nframep)144 stack_info(Frame *f, Frame **firstp, Frame **lastp, int *dyp, int *nframep) {
145 	Frame *ft, *first, *last;
146 	int dy, nframe;
147 
148 	nframe = 0;
149 	dy = 0;
150 	first = f;
151 	last = f;
152 
153 	for(ft=f; ft && ft->collapsed; ft=ft->anext)
154 		;
155 	if(ft && ft != f) {
156 		f = ft;
157 		dy += Dy(f->colr);
158 	}
159 	for(ft=f; ft && !ft->collapsed; ft=ft->aprev) {
160 		first = ft;
161 		nframe++;
162 		dy += Dy(ft->colr);
163 	}
164 	for(ft=f->anext; ft && !ft->collapsed; ft=ft->anext) {
165 		if(first == nil)
166 			first = ft;
167 		last = ft;
168 		nframe++;
169 		dy += Dy(ft->colr);
170 	}
171 	if(nframep) *nframep = nframe;
172 	if(firstp) *firstp = first;
173 	if(lastp) *lastp = last;
174 	if(dyp) *dyp = dy;
175 }
176 
177 int
stack_count(Frame * f,int * mp)178 stack_count(Frame *f, int *mp) {
179 	Frame *fp;
180 	int n, m;
181 
182 	n = 0;
183 	for(fp=f->aprev; fp && fp->collapsed; fp=fp->aprev)
184 		n++;
185 	m = ++n;
186 	for(fp=f->anext; fp && fp->collapsed; fp=fp->anext)
187 		n++;
188 	if(mp) *mp = m;
189 	return n;
190 }
191 
192 Frame*
stack_find(Area * a,Frame * f,int dir,bool stack)193 stack_find(Area *a, Frame *f, int dir, bool stack) {
194 	Frame *fp;
195 
196 	switch (dir) {
197 	default:
198 		die("not reached");
199 	case North:
200 		if(f)
201 			for(f=f->aprev; f && f->collapsed && stack; f=f->aprev)
202 				;
203 		else {
204 			f = nil;
205 			for(fp=a->frame; fp; fp=fp->anext)
206 				if(!fp->collapsed || !stack)
207 					f = fp;
208 		}
209 		break;
210 	case South:
211 		if(f)
212 			for(f=f->anext; f && f->collapsed && stack; f=f->anext)
213 				;
214 		else
215 			for(f=a->frame; f && f->collapsed && stack; f=f->anext)
216 				;
217 		break;
218 	}
219 	return f;
220 }
221 
222 /* TODO: Move elsewhere. */
223 bool
find(Area ** ap,Frame ** fp,int dir,bool wrap,bool stack)224 find(Area **ap, Frame **fp, int dir, bool wrap, bool stack) {
225 	Rectangle r;
226 	Frame *f;
227 	Area *a;
228 
229 	f = *fp;
230 	a = *ap;
231 	r = f ? f->r : a->r;
232 
233 	if(dir == North || dir == South) {
234 		*fp = stack_find(a, f, dir, stack);
235 		if(*fp)
236 			return true;
237 		if (!a->floating)
238 			*ap = area_find(a->view, r, dir, wrap);
239 		if(!*ap)
240 			return false;
241 		*fp = stack_find(*ap, *fp, dir, stack);
242 		return true;
243 	}
244 	if(dir != East && dir != West)
245 		die("not reached");
246 	*ap = area_find(a->view, r, dir, wrap);
247 	if(!*ap)
248 		return false;
249 	*fp = ap[0]->sel;
250 	return true;
251 }
252 
253 void
column_attach(Area * a,Frame * f)254 column_attach(Area *a, Frame *f) {
255 	Frame *first;
256 	int nframe, dy, h;
257 
258 	f->colr = a->r;
259 
260 	if(a->sel) {
261 		stack_info(a->sel, &first, nil, &dy, &nframe);
262 		h = dy / (nframe+1);
263 		f->colr.max.y = f->colr.min.y + h;
264 		stack_scale(first, dy - h);
265 	}
266 
267 	column_insert(a, f, a->sel);
268 	column_arrange(a, false);
269 }
270 
271 void
column_detach(Frame * f)272 column_detach(Frame *f) {
273 	Frame *first;
274 	Area *a;
275 	int dy;
276 
277 	a = f->area;
278 	stack_info(f, &first, nil, &dy, nil);
279 	if(first && first == f)
280 		first = f->anext;
281 	column_remove(f);
282 	if(a->frame) {
283 		if(first)
284 			stack_scale(first, dy);
285 		column_arrange(a, false);
286 	}else if(a->view->areas[a->screen]->next)
287 		area_destroy(a);
288 }
289 
290 static void column_scale(Area*);
291 
292 void
column_attachrect(Area * a,Frame * f,Rectangle r)293 column_attachrect(Area *a, Frame *f, Rectangle r) {
294 	Frame *fp, *pos;
295 	int before, after;
296 
297 	pos = nil;
298 	for(fp=a->frame; fp; pos=fp, fp=fp->anext) {
299 		if(r.max.y < fp->r.min.y || r.min.y > fp->r.max.y)
300 			continue;
301 		before = fp->r.min.y - r.min.y;
302 		after = -fp->r.max.y + r.max.y;
303 	}
304 	column_insert(a, f, pos);
305 	column_resizeframe_h(f, r);
306 	column_scale(a);
307 }
308 
309 void
column_remove(Frame * f)310 column_remove(Frame *f) {
311 	Frame *pr;
312 	Area *a;
313 
314 	a = f->area;
315 	pr = f->aprev;
316 
317 	frame_remove(f);
318 
319 	f->area = nil;
320 	if(a->sel == f) {
321 		if(pr == nil)
322 			pr = a->frame;
323 		if(pr && pr->collapsed)
324 			if(pr->anext && !pr->anext->collapsed)
325 				pr = pr->anext;
326 			else
327 				pr->collapsed = false;
328 		a->sel = nil;
329 		area_setsel(a, pr);
330 	}
331 }
332 
333 static int
column_surplus(Area * a)334 column_surplus(Area *a) {
335 	Frame *f;
336 	int surplus;
337 
338 	surplus = Dy(a->r);
339 	for(f=a->frame; f; f=f->anext)
340 		surplus -= Dy(f->r);
341 	return surplus;
342 }
343 
344 static void
column_fit(Area * a,uint * n_colp,uint * n_uncolp)345 column_fit(Area *a, uint *n_colp, uint *n_uncolp) {
346 	Frame *f, **fp;
347 	uint minh, dy;
348 	uint n_col, n_uncol;
349 	uint col_h, uncol_h;
350 	int surplus, i, j;
351 
352 	/* The minimum heights of collapsed and uncollpsed frames.
353 	 */
354 	minh = labelh(def.font);
355 	col_h = labelh(def.font);
356 	uncol_h = minh + col_h + 1;
357 	if(a->max && !resizing)
358 		col_h = 0;
359 
360 	/* Count collapsed and uncollapsed frames. */
361 	n_col = 0;
362 	n_uncol = 0;
363 	for(f=a->frame; f; f=f->anext) {
364 		frame_resize(f, f->colr);
365 		if(f->collapsed)
366 			n_col++;
367 		else
368 			n_uncol++;
369 	}
370 
371 	if(n_uncol == 0) {
372 		n_uncol++;
373 		n_col--;
374 		(a->sel ? a->sel : a->frame)->collapsed = false;
375 	}
376 
377 	/* FIXME: Kludge. See frame_attachrect. */
378 	dy = Dy(a->view->r[a->screen]) - Dy(a->r);
379 	minh = col_h * (n_col + n_uncol - 1) + uncol_h;
380 	if(dy && Dy(a->r) < minh)
381 		a->r.max.y += min(dy, minh - Dy(a->r));
382 
383 	surplus = Dy(a->r)
384 		- (n_col * col_h)
385 		- (n_uncol * uncol_h);
386 
387 	/* Collapse until there is room */
388 	if(surplus < 0) {
389 		i = ceil(-1.F * surplus / (uncol_h - col_h));
390 		if(i >= n_uncol)
391 			i = n_uncol - 1;
392 		n_uncol -= i;
393 		n_col += i;
394 		surplus += i * (uncol_h - col_h);
395 	}
396 	/* Push to the floating layer until there is room */
397 	if(surplus < 0) {
398 		i = ceil(-1.F * surplus / col_h);
399 		if(i > n_col)
400 			i = n_col;
401 		n_col -= i;
402 		surplus += i * col_h;
403 	}
404 
405 	/* Decide which to collapse and which to float. */
406 	j = n_uncol - 1;
407 	i = n_col - 1;
408 	for(fp=&a->frame; *fp;) {
409 		f = *fp;
410 		if(f != a->sel) {
411 			if(!f->collapsed) {
412 				if(j < 0)
413 					f->collapsed = true;
414 				j--;
415 			}
416 			if(f->collapsed) {
417 				if(i < 0) {
418 					f->collapsed = false;
419 					area_moveto(f->view->floating, f);
420 					continue;
421 				}
422 				i--;
423 			}
424 		}
425 		/* Doesn't change if we 'continue' */
426 		fp = &f->anext;
427 	}
428 
429 	if(n_colp) *n_colp = n_col;
430 	if(n_uncolp) *n_uncolp = n_uncol;
431 }
432 
433 void
column_settle(Area * a)434 column_settle(Area *a) {
435 	Frame *f;
436 	uint yoff, yoffcr;
437 	int surplus, n_uncol, n;
438 
439 	n_uncol = 0;
440 	surplus = column_surplus(a);
441 	for(f=a->frame; f; f=f->anext)
442 		if(!f->collapsed) n_uncol++;
443 
444 	if(n_uncol == 0) {
445 		fprint(2, "%s: Badness: No uncollapsed frames, column %d, view %q\n",
446 				argv0, area_idx(a), a->view->name);
447 		return;
448 	}
449 	if(surplus < 0)
450 		fprint(2, "%s: Badness: surplus = %d in column_settle, column %d, view %q\n",
451 				argv0, surplus, area_idx(a), a->view->name);
452 
453 	yoff = a->r.min.y;
454 	yoffcr = yoff;
455 	n = surplus % n_uncol;
456 	surplus /= n_uncol;
457 	for(f=a->frame; f; f=f->anext) {
458 		f->r = rectsetorigin(f->r, Pt(a->r.min.x, yoff));
459 		f->colr = rectsetorigin(f->colr, Pt(a->r.min.x, yoffcr));
460 		f->r.min.x = a->r.min.x;
461 		f->r.max.x = a->r.max.x;
462 		if(def.incmode == ISqueeze && !resizing)
463 		if(!f->collapsed) {
464 			f->r.max.y += surplus;
465 			if(n-- > 0)
466 				f->r.max.y++;
467 		}
468 		yoff = f->r.max.y;
469 		yoffcr = f->colr.max.y;
470 	}
471 }
472 
473 /*
474  * Returns how much a frame "wants" to grow.
475  */
476 static int
foo(Frame * f)477 foo(Frame *f) {
478 	WinHints h;
479 	int maxh;
480 
481 	h = frame_gethints(f);
482 	maxh = 0;
483 	if(h.aspect.max.x)
484 		maxh = h.baspect.y +
485 		       (Dx(f->r) - h.baspect.x) *
486 		       h.aspect.max.y / h.aspect.max.x;
487 	maxh = max(maxh, h.max.y);
488 
489 	if(Dy(f->r) >= maxh)
490 		return 0;
491 	return h.inc.y - (Dy(f->r) - h.base.y) % h.inc.y;
492 }
493 
494 static int
comp_frame(const void * a,const void * b)495 comp_frame(const void *a, const void *b) {
496 	int ia, ib;
497 
498 	ia = foo(*(Frame**)a);
499 	ib = foo(*(Frame**)b);
500 	/*
501 	 * I'd like to favor the selected client, but
502 	 * it causes windows to jump as focus changes.
503 	 */
504 	return ia < ib ? -1 :
505 	       ia > ib ?  1 :
506 	                  0;
507 }
508 
509 static void
column_squeeze(Area * a)510 column_squeeze(Area *a) {
511 	static Vector_ptr fvec;
512 	Frame *f;
513 	int surplus, osurplus, dy, i;
514 
515 	fvec.n = 0;
516 	for(f=a->frame; f; f=f->anext)
517 		if(!f->collapsed) {
518 			f->r = frame_hints(f, f->r, 0);
519 			vector_ppush(&fvec, f);
520 		}
521 
522 	surplus = column_surplus(a);
523 	for(osurplus=0; surplus != osurplus;) {
524 		osurplus = surplus;
525 		qsort(fvec.ary, fvec.n, sizeof *fvec.ary, comp_frame);
526 		for(i=0; i < fvec.n; i++) {
527 			f=fvec.ary[i];
528 			dy = foo(f);
529 			if(dy > surplus)
530 				break;
531 			surplus -= dy;
532 			f->r.max.y += dy;
533 		}
534 	}
535 }
536 
537 /*
538  * Frobs a column. Which is to say, *temporary* kludge.
539  * Essentially seddles the column and resizes its clients.
540  */
541 void
column_frob(Area * a)542 column_frob(Area *a) {
543 	Frame *f;
544 
545 	for(f=a->frame; f; f=f->anext)
546 		f->r = f->colr;
547 	column_settle(a);
548 	if(a->view == selview)
549 	for(f=a->frame; f; f=f->anext)
550 		client_resize(f->client, f->r);
551 }
552 
553 static void
column_scale(Area * a)554 column_scale(Area *a) {
555 	Frame *f;
556 	uint dy;
557 	uint ncol, nuncol;
558 	uint colh;
559 	int surplus;
560 
561 	if(!a->frame)
562 		return;
563 
564 	column_fit(a, &ncol, &nuncol);
565 
566 	colh = labelh(def.font);
567 	if(a->max && !resizing)
568 		colh = 0;
569 
570 	dy = 0;
571 	surplus = Dy(a->r);
572 	for(f=a->frame; f; f=f->anext) {
573 		if(f->collapsed)
574 			f->colr.max.y = f->colr.min.y + colh;
575 		else if(Dy(f->colr) == 0)
576 			f->colr.max.y++;
577 		surplus -= Dy(f->colr);
578 		if(!f->collapsed)
579 			dy += Dy(f->colr);
580 	}
581 	for(f=a->frame; f; f=f->anext) {
582 		f->dy = Dy(f->colr);
583 		f->colr.min.x = a->r.min.x;
584 		f->colr.max.x = a->r.max.x;
585 		if(!f->collapsed)
586 			f->colr.max.y += ((float)f->dy / dy) * surplus;
587 		if(btassert("6 full", !(f->collapsed ? Dy(f->r) >= 0 : dy > 0)))
588 			warning("Something's fucked: %s:%d:%s()",
589 				__FILE__, __LINE__, __func__);
590 		frame_resize(f, f->colr);
591 	}
592 
593 	if(def.incmode == ISqueeze && !resizing)
594 		column_squeeze(a);
595 	column_settle(a);
596 }
597 
598 void
column_arrange(Area * a,bool dirty)599 column_arrange(Area *a, bool dirty) {
600 	Frame *f;
601 	View *v;
602 
603 	if(a->floating)
604 		float_arrange(a);
605 	if(a->floating || !a->frame)
606 		return;
607 
608 	v = a->view;
609 
610 	switch(a->mode) {
611 	case Coldefault:
612 		if(dirty)
613 			for(f=a->frame; f; f=f->anext)
614 				f->colr = Rect(0, 0, 100, 100);
615 		break;
616 	case Colstack:
617 		/* XXX */
618 		for(f=a->frame; f; f=f->anext)
619 			f->collapsed = (f != a->sel);
620 		break;
621 	default:
622 		fprint(2, "Dieing: %s: screen: %d a: %p mode: %x floating: %d\n",
623 		       v->name, a->screen, a, a->mode, a->floating);
624 		die("not reached");
625 		break;
626 	}
627 	column_scale(a);
628 	/* XXX */
629 	if(a->sel->collapsed)
630 		area_setsel(a, a->sel);
631 	if(v == selview) {
632 		//view_restack(v);
633 		client_resize(a->sel->client, a->sel->r);
634 		for(f=a->frame; f; f=f->anext)
635 			client_resize(f->client, f->r);
636 	}
637 }
638 
639 void
column_resize(Area * a,int w)640 column_resize(Area *a, int w) {
641 	Area *an;
642 	int dw;
643 
644 	an = a->next;
645 	assert(an != nil);
646 
647 	dw = w - Dx(a->r);
648 	a->r.max.x += dw;
649 	an->r.min.x += dw;
650 
651 	/* view_arrange(a->view); */
652 	view_update(a->view);
653 }
654 
655 static void
column_resizeframe_h(Frame * f,Rectangle r)656 column_resizeframe_h(Frame *f, Rectangle r) {
657 	Area *a;
658 	Frame *fn, *fp;
659 	uint minh;
660 
661 	minh = labelh(def.font);
662 
663 	a = f->area;
664 	fn = f->anext;
665 	fp = f->aprev;
666 
667 	if(fp)
668 		r.min.y = max(r.min.y, fp->colr.min.y + minh);
669 	else
670 		r.min.y = max(r.min.y, a->r.min.y);
671 
672 	if(fn)
673 		r.max.y = min(r.max.y, fn->colr.max.y - minh);
674 	else
675 		r.max.y = min(r.max.y, a->r.max.y);
676 
677 	if(fp) {
678 		fp->colr.max.y = r.min.y;
679 		frame_resize(fp, fp->colr);
680 	}
681 	else
682 		r.min.y = min(r.min.y, r.max.y - minh);
683 
684 	if(fn) {
685 		fn->colr.min.y = r.max.y;
686 		frame_resize(fn, fn->colr);
687 	}
688 	else
689 		r.max.y = max(r.max.y, r.min.y + minh);
690 
691 	f->colr = r;
692 	frame_resize(f, r);
693 }
694 
695 void
column_resizeframe(Frame * f,Rectangle r)696 column_resizeframe(Frame *f, Rectangle r) {
697 	Area *a, *al, *ar;
698 	View *v;
699 	uint minw;
700 
701 	a = f->area;
702 	v = a->view;
703 
704 	minw = column_minwidth();
705 
706 	al = a->prev;
707 	ar = a->next;
708 
709 	if(al)
710 		r.min.x = max(r.min.x, al->r.min.x + minw);
711 	else { /* Hm... */
712 		r.min.x = max(r.min.x, v->r[a->screen].min.x);
713 		r.max.x = max(r.max.x, r.min.x + minw);
714 	}
715 
716 	if(ar)
717 		r.max.x = min(r.max.x, ar->r.max.x - minw);
718 	else {
719 		r.max.x = min(r.max.x, v->r[a->screen].max.x);
720 		r.min.x = min(r.min.x, r.max.x - minw);
721 	}
722 
723 	a->r.min.x = r.min.x;
724 	a->r.max.x = r.max.x;
725 	if(al) {
726 		al->r.max.x = a->r.min.x;
727 		column_arrange(al, false);
728 	}
729 	if(ar) {
730 		ar->r.min.x = a->r.max.x;
731 		column_arrange(ar, false);
732 	}
733 
734 	column_resizeframe_h(f, r);
735 
736 	view_update(v);
737 }
738 
739