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}