home · contact · privacy
Moved freeing of winmeta into windows library.
[plomrogue] / src / windows.c
1 /* windows.c */
2
3 #include "windows.h"
4 #include <stdint.h>    /* for uint8_t, uint16_t, uint32_t, UINT16_MAX */
5 #include <ncurses.h>   /* for typedefs WINDOW, chtype, wresize(), getmaxx(), */
6                        /* getmaxy(), supbad(), delwin(), mvwaddch(),         */
7                        /* mvwaddstr(), newpad(), wnoutrefres(), erase(),     */
8                        /* werase(), pnoutrefresh(), doupdate()               */
9 #include <stdlib.h>    /* for malloc(), free() */
10 #include <string.h>    /* for strlen(), strnlen(), memcpy() */
11 #include "yx_uint16.h" /* for yx_uint16 coordinates */
12
13
14
15 /* Fit virtual screen's width to minimum width demanded by current windows'
16  * geometries.
17  */
18 static uint8_t refit_pad(struct WinMeta * wmeta);
19
20
21
22 /* Update geometry (sizes, positions) of window "w" and its successors in the
23  * window chain. For the positioning algorithm place_win() is used.
24  */
25 static uint8_t update_wins(struct WinMeta * wmeta, struct Win * w);
26 static void place_win(struct WinMeta * wmeta, struct Win * w);
27
28
29
30 /* Destroy window "w"'s ncurses WINDOW (and set w.Frame.curses_win to 0). */
31 static void destroy_win(struct Win * w);
32
33
34
35 /* Draw contents of all windows in window chain from window "w" onwards. */
36 static void draw_wins(struct Win * w);
37
38
39
40 /* draw_win_borderlines() draws the vertical and horizontal borders of window
41  * "w" sans corners into the virtual screen "pad", and draws the top border
42  * line as the windows' title bar (highlighted if the window is described
43  * active by "active" being == 1).
44  *
45  * draw_wins_borderlines() calls draw_win_borderlines() recursively on all
46  * windows from "w" on. "w_active" is a pointer to the one window that
47  * draw_win_borderlines() is supposed to handle as the active window.
48  *
49  * Finally, draw_wins_bordercorners() draws into "pad" the borders of window "w"
50  * and all its successors.
51  */
52 static void draw_win_borderlines(struct Win * w, char active, WINDOW * pad);
53 static void draw_wins_borderlines(struct Win * w, struct Win * w_active,
54                                   WINDOW * pad);
55 static void draw_wins_bordercorners(struct Win * w, WINDOW * pad);
56
57
58
59 /* Shift active window forwards / backwards in window chain. */
60 static void shift_win_forward(struct WinMeta * wmeta);
61 static void shift_win_backward(struct WinMeta * wmeta);
62
63
64
65 static uint8_t refit_pad(struct WinMeta * wmeta)
66 {
67     /* Determine rightmost window column. */
68     uint32_t lastwincol = 0;
69     struct Win * w_p = wmeta->_chain_start;
70     while (w_p != 0)
71     {
72         if ((uint32_t) w_p->_start.x + (uint32_t) w_p->frame.size.x
73             > lastwincol + 1)
74         {
75             lastwincol = (uint32_t) w_p->_start.x
76                          + (uint32_t) w_p->frame.size.x - 1;
77         }
78         w_p = w_p->_next;
79     }
80
81     /* Only resize the pad if the rightmost window column has changed. */
82     if (getmaxx(wmeta->padframe.curses_win) != lastwincol)
83     {
84         if (lastwincol + 2 > UINT16_MAX)
85         {
86             return 2;
87         }
88         return (ERR == wresize(wmeta->padframe.curses_win,
89                                getmaxy(wmeta->padframe.curses_win),
90                                lastwincol + 2));
91     }
92     return 0;
93 }
94
95
96
97 static uint8_t update_wins(struct WinMeta * wmeta, struct Win * w)
98 {
99     if (0 != w->frame.curses_win)
100     {
101         destroy_win(w);
102     }
103     place_win(wmeta, w);
104     uint8_t test_refit = refit_pad(wmeta);
105     if (0 != test_refit)
106     {
107         return test_refit;
108     }
109     WINDOW * subpad_test = subpad(wmeta->padframe.curses_win,
110                                   w->frame.size.y, w->frame.size.x,
111                                   w->_start.y, w->_start.x);
112     if (NULL == subpad_test)
113     {
114         return 1;
115     }
116     w->frame.curses_win = subpad_test;
117     if (0 != w->_next)
118     {
119         return update_wins(wmeta, w->_next);
120     }
121     return 0;
122 }
123
124
125
126 static void place_win(struct WinMeta * wmeta, struct Win * w)
127 {
128     /* First window goes into the upper-left corner. */
129     w->_start.x = 0;
130     w->_start.y = 1;                            /* Leave space for title bar. */
131     if (0 != w->_prev)
132     {
133
134         /* Non-first window fallbacks to: fit rightwards of rightmost border. */
135         struct Win * w_top = w->_prev;
136         while (w_top->_start.y != 1)
137         {
138             w_top = w_top->_prev;
139         }
140         w->_start.x = w_top->_start.x + w_top->frame.size.x + 1;
141
142         /* Fit window below its predecessor if that one directly thrones over
143          * empty space wide and high enough.
144          */
145         uint16_t w_prev_maxy = w->_prev->_start.y
146                                + getmaxy(w->_prev->frame.curses_win);
147         if (   w->frame.size.x <= w->_prev->frame.size.x
148             && w->frame.size.y <  wmeta->padframe.size.y - w_prev_maxy)
149         {
150             w->_start.x = w->_prev->_start.x;
151             w->_start.y = w_prev_maxy + 1;
152         }
153
154         /* Failing that, try to open a new sub column below the nearest
155          * predecessor window that thrones over enough empty space.
156          */
157         else
158         {
159             struct Win * w_up = w->_prev;
160             struct Win * w_upup = w_up;
161             uint16_t widthdiff;
162             while (w_up != w_top)
163             {
164                 w_upup = w_up->_prev;
165                 while (1)
166                 {
167                     if (w_up->_start.y != w_upup->_start.y)
168                     {
169                         break;
170                     }
171                     w_upup = w_upup->_prev;
172                 }
173                 w_prev_maxy = w_upup->_start.y
174                               + getmaxy(w_upup->frame.curses_win);
175                 widthdiff = (w_upup->_start.x + w_upup->frame.size.x)
176                             - (w_up->_start.x + w_up->frame.size.x);
177                 if (   w->frame.size.y < wmeta->padframe.size.y - w_prev_maxy
178                     && w->frame.size.x < widthdiff)
179                 {
180                     w->_start.x = w_up->_start.x + w_up->frame.size.x + 1 ;
181                     w->_start.y = w_prev_maxy + 1;
182                     break;
183                 }
184                 w_up = w_upup;
185             }
186         }
187     }
188 }
189
190
191
192 static void destroy_win(struct Win * w)
193 {
194     delwin(w->frame.curses_win);
195     w->frame.curses_win = 0;
196 }
197
198
199
200 static void draw_wins(struct Win * w)
201 {
202     w->_draw(w);
203     if (0 != w->_next)
204     {
205         draw_wins(w->_next);
206     }
207 }
208
209
210
211 static void draw_win_borderlines(struct Win * w, char active, WINDOW * pad)
212 {
213     /* Draw vertical and horizontal border lines. */
214     uint16_t y, x;
215     for (y = w->_start.y; y <= w->_start.y + w->frame.size.y; y++)
216     {
217         mvwaddch(pad, y, w->_start.x - 1,               '|');
218         mvwaddch(pad, y, w->_start.x + w->frame.size.x, '|');
219     }
220     for (x = w->_start.x; x <= w->_start.x + w->frame.size.x; x++)
221     {
222         mvwaddch(pad, w->_start.y - 1,               x, '-');
223         mvwaddch(pad, w->_start.y + w->frame.size.y, x, '-');
224     }
225
226     /* Draw as much as possible of the title into center of top border line. */
227     char min_title_length_visible = 3;  /* min. 1 char + 2 padding/decoration */
228     if (w->frame.size.x >= min_title_length_visible)
229     {
230         uint16_t title_offset = 0;
231         if (w->frame.size.x > strlen(w->_title) + 2)
232         {
233             title_offset = (w->frame.size.x - (strlen(w->_title) + 2)) / 2;
234         }                                    /* +2 is for padding/decoration */
235         uint16_t length_visible = strnlen(w->_title, w->frame.size.x - 2);
236         char title[length_visible + 3];
237         char decoration = ' ';
238         if (1 == active)
239         {
240             decoration = '$';
241         }
242         memcpy(title + 1, w->_title, length_visible);
243         title[0] = title[length_visible + 1] = decoration;
244         title[length_visible + 2] = '\0';
245         mvwaddstr(pad, w->_start.y - 1, w->_start.x + title_offset, title);
246     }
247 }
248
249
250
251 static void draw_wins_borderlines(struct Win * w, struct Win * w_active,
252                                   WINDOW * pad)
253 {
254     char active = 0;
255     if (w == w_active)
256     {
257         active = 1;
258     }
259     draw_win_borderlines(w, active, pad);
260     if (0 != w->_next)
261     {
262         draw_wins_borderlines(w->_next, w_active, pad);
263     }
264 }
265
266
267
268 static void draw_wins_bordercorners(struct Win * w, WINDOW * pad)
269 {
270     mvwaddch(pad, w->_start.y - 1, w->_start.x - 1, '+');
271     mvwaddch(pad, w->_start.y - 1, w->_start.x + w->frame.size.x, '+');
272     mvwaddch(pad, w->_start.y + w->frame.size.y, w->_start.x - 1, '+');
273     mvwaddch(pad,
274              w->_start.y + w->frame.size.y, w->_start.x + w->frame.size.x, '+');
275     if (0 != w->_next)
276     {
277         draw_wins_bordercorners(w->_next, pad);
278     }
279 }
280
281
282
283 static void shift_win_forward(struct WinMeta * wmeta)
284 {
285     if (wmeta->active == wmeta->_chain_end)
286     {
287         wmeta->_chain_end = wmeta->active->_prev;
288         wmeta->_chain_end->_next = 0;
289         wmeta->active->_next = wmeta->_chain_start;
290         wmeta->active->_next->_prev = wmeta->active;
291         wmeta->_chain_start = wmeta->active;
292         wmeta->_chain_start->_prev = 0;
293     }
294     else
295     {
296         struct Win * old_prev = wmeta->active->_prev;
297         struct Win * old_next = wmeta->active->_next;
298         if (wmeta->_chain_end == wmeta->active->_next)
299         {
300             wmeta->_chain_end = wmeta->active;
301             wmeta->active->_next = 0;
302         }
303         else
304         {
305             wmeta->active->_next = old_next->_next;
306             wmeta->active->_next->_prev = wmeta->active;
307         }
308         if (wmeta->_chain_start == wmeta->active)
309         {
310             wmeta->_chain_start = old_next;
311         }
312         else
313         {
314             old_prev->_next = old_next;
315         }
316         old_next->_prev = old_prev;
317         old_next->_next = wmeta->active;
318         wmeta->active->_prev = old_next;
319     }
320 }
321
322
323
324 static void shift_win_backward(struct WinMeta * wmeta)
325 {
326     if (wmeta->active == wmeta->_chain_start)
327     {
328         wmeta->_chain_start = wmeta->active->_next;
329         wmeta->_chain_start->_prev = 0;
330         wmeta->active->_prev = wmeta->_chain_end;
331         wmeta->active->_prev->_next = wmeta->active;
332         wmeta->_chain_end = wmeta->active;
333         wmeta->_chain_end->_next = 0;
334     }
335     else
336     {
337         struct Win * old_prev = wmeta->active->_prev;
338         struct Win * old_next = wmeta->active->_next;
339         if (wmeta->_chain_start == wmeta->active->_prev)
340         {
341             wmeta->_chain_start = wmeta->active;
342             wmeta->active->_prev = 0;
343         }
344         else
345         {
346             wmeta->active->_prev = old_prev->_prev;
347             wmeta->active->_prev->_next = wmeta->active;
348         }
349         if (wmeta->_chain_end == wmeta->active)
350         {
351             wmeta->_chain_end = old_prev;
352         }
353         else
354         {
355             old_next->_prev = old_prev;
356         }
357         old_prev->_next = old_next;
358         old_prev->_prev = wmeta->active;
359         wmeta->active->_next = old_prev;
360     }
361 }
362
363
364
365 extern uint8_t init_win_meta(WINDOW * screen, struct WinMeta ** wmp)
366 {
367     struct WinMeta * wmeta = malloc(sizeof(struct WinMeta));
368     wmeta->_screen             = screen;
369     uint32_t maxy_test         = getmaxy(screen);
370     uint32_t maxx_test         = getmaxx(screen);
371     if (maxy_test > UINT16_MAX || maxx_test > UINT16_MAX)
372     {
373         return 2;
374     }
375     wmeta->padframe.size.y     = maxy_test;
376     wmeta->padframe.size.x     = maxx_test;
377     wmeta->_chain_start        = 0;
378     wmeta->_chain_end          = 0;
379     wmeta->pad_offset          = 0;
380     WINDOW * pad_test          = newpad(wmeta->padframe.size.y, 1);
381     if (NULL == pad_test)
382     {
383         return 1;
384     }
385     wmeta->padframe.curses_win = pad_test;
386     wmeta->active              = 0;
387     *wmp = wmeta;
388     return 0;
389 }
390
391
392
393 extern uint8_t init_win(struct WinMeta * wmeta, struct Win ** wp, char * title,
394                         int16_t height, int16_t width,
395                         void * data, void * func)
396 {
397     struct Win * w = malloc(sizeof(struct Win));
398     if (NULL == w)
399     {
400         return 1;
401     }
402     w->_prev             = 0;
403     w->_next             = 0;
404     w->frame.curses_win  = 0;
405     w->_title             = malloc(strlen(title) + 1);
406     if (NULL == w->_title)
407     {
408         return 1;
409     }
410     sprintf(w->_title, "%s", title);
411     w->data              = data;
412     w->_draw             = func;
413     if      (0 < width)
414     {
415         w->frame.size.x = width;
416     }
417     else if (0 > width)
418     {
419         w->frame.size.x = wmeta->padframe.size.x + width;
420     }
421     else
422     {
423         w->frame.size.x = wmeta->padframe.size.x;
424     }
425     if      (0 < height && height <= wmeta->padframe.size.y - 1)
426     {
427         w->frame.size.y = height;
428     }
429     else if (0 > height && wmeta->padframe.size.y + (height - 1) > 0)
430     {
431         w->frame.size.y = wmeta->padframe.size.y + (height - 1);
432     }
433     else
434     {
435         w->frame.size.y = wmeta->padframe.size.y - 1;
436     }
437     *wp = w;
438     return 0;
439 }
440
441
442
443 extern void free_winmeta(struct WinMeta * wmeta)
444 {
445     delwin(wmeta->padframe.curses_win);
446     free(wmeta);
447 }
448
449
450
451 extern void free_win(struct Win * win)
452 {
453     if (0 != win->frame.curses_win)
454     {
455         delwin(win->frame.curses_win);
456     }
457     free(win->_title);
458     free(win);
459 }
460
461
462
463 extern uint8_t append_win(struct WinMeta * wmeta, struct Win * w)
464 {
465     if (0 != wmeta->_chain_start)
466     {
467         w->_prev = wmeta->_chain_end;
468         wmeta->_chain_end->_next = w;
469     }
470     else
471     {
472         wmeta->active = w;
473         wmeta->_chain_start = w;
474     }
475     wmeta->_chain_end = w;
476     return update_wins(wmeta, w);
477 }
478
479
480
481 extern uint8_t suspend_win(struct WinMeta * wmeta, struct Win * w)
482 {
483     destroy_win(w);
484
485     if (wmeta->_chain_start != w)
486     {
487         w->_prev->_next = w->_next;
488     }
489     else
490     {
491         wmeta->_chain_start = w->_next;
492     }
493     char pad_refitted = 0;
494     if (wmeta->_chain_end != w)
495     {
496         w->_next->_prev = w->_prev;
497         if (wmeta->active == w)
498         {
499             wmeta->active = w->_next;
500         }
501         uint8_t test = update_wins(wmeta, w->_next);/* Positioning of         */
502         if (0 != test)                              /* successor windows may  */
503         {                                           /* be affected / need     */
504             return test;                            /* correction. Note that  */
505         }                                           /* update_wins() already  */
506         pad_refitted = 1;                           /* refits the pad, voiding*/
507     }                                               /* later need for that.   */
508     else
509     {
510         wmeta->_chain_end = w->_prev;
511         if (wmeta->active == w)
512         {
513             wmeta->active = w->_prev;
514         }
515     }
516
517     w->_prev = 0;
518     w->_next = 0;
519
520     if (0 == pad_refitted)
521     {
522         return refit_pad(wmeta);
523     }
524     return 0;
525 }
526
527
528
529 extern void reset_pad_offset(struct WinMeta * wmeta, uint16_t new_offset)
530 {
531     if (new_offset >= 0
532         && (new_offset < wmeta->pad_offset
533             || new_offset + wmeta->padframe.size.x
534                < getmaxx(wmeta->padframe.curses_win)))
535     {
536         wmeta->pad_offset = new_offset;
537     }
538 }
539
540
541
542 extern uint8_t resize_active_win(struct WinMeta * wmeta, struct yx_uint16 size)
543 {
544     if (0 != wmeta->active
545         && size.x > 0 && size.y > 0
546         && size.y < wmeta->padframe.size.y)
547     {
548         wmeta->active->frame.size = size;
549         return update_wins(wmeta, wmeta->active); /* Positioning of following */
550     }                                             /* windows may be affected. */
551     return 0;
552 }
553
554
555
556 extern void cycle_active_win(struct WinMeta * wmeta, char dir)
557 {
558     if (0 != wmeta->active)
559     {
560         if ('f' == dir)
561         {
562             if (wmeta->active->_next != 0)
563             {
564                 wmeta->active = wmeta->active->_next;
565             }
566             else
567             {
568                 wmeta->active = wmeta->_chain_start;
569             }
570         }
571         else
572         {
573             if (wmeta->active->_prev != 0)
574             {
575                 wmeta->active = wmeta->active->_prev;
576             }
577             else
578             {
579                 wmeta->active = wmeta->_chain_end;
580             }
581         }
582     }
583 }
584
585
586
587 extern uint8_t shift_active_win(struct WinMeta * wmeta, char dir)
588 {
589     if (   0 == wmeta->active                        /* No shifting with < 2  */
590         || wmeta->_chain_start == wmeta->_chain_end) /* windows visible.      */
591     {
592         return 0;
593     }
594     if ('f' == dir)
595     {
596         shift_win_forward(wmeta);
597     }
598     else
599     {
600         shift_win_backward(wmeta);
601     }
602     return update_wins(wmeta, wmeta->_chain_start);
603 }
604
605
606
607 extern uint8_t draw_all_wins(struct WinMeta * wmeta)
608 {
609     /* Empty everything before filling it a-new. */
610     erase();
611     wnoutrefresh(wmeta->_screen);
612     werase(wmeta->padframe.curses_win);
613     if (wmeta->_chain_start)
614     {
615
616         /* Draw windows' contents first, then their borders. */
617         draw_wins(wmeta->_chain_start);
618         draw_wins_borderlines(wmeta->_chain_start, wmeta->active,
619                               wmeta->padframe.curses_win);
620         draw_wins_bordercorners(wmeta->_chain_start,wmeta->padframe.curses_win);
621
622         /* Draw virtual screen scroll hints. */
623         if (wmeta->pad_offset > 0)
624         {
625             if (draw_scroll_hint(&wmeta->padframe,
626                                  wmeta->pad_offset, wmeta->pad_offset + 1, '<'))
627             {
628                 return 1;
629             }
630         }
631         if (wmeta->pad_offset + wmeta->padframe.size.x
632             < getmaxx(wmeta->padframe.curses_win) - 1)
633         {
634             if (draw_scroll_hint(&wmeta->padframe,
635                                  wmeta->pad_offset + wmeta->padframe.size.x - 1,
636                                  getmaxx(wmeta->padframe.curses_win)
637                                  - (wmeta->pad_offset + wmeta->padframe.size.x),
638                                  '>'))
639             {
640                 return 1;
641             }
642         }
643
644         /* Write virtual screen segment to be shown on physical screen into */
645         /* ncurses screen buffer. */
646         pnoutrefresh(wmeta->padframe.curses_win, 0, wmeta->pad_offset, 0, 0,
647                      wmeta->padframe.size.y, wmeta->padframe.size.x-1);
648     }
649
650     /* Only at the end write accumulated changes to the physical screen. */
651     doupdate();
652     return 0;
653 }
654
655
656
657 extern uint8_t draw_scroll_hint(struct Frame * frame, uint16_t pos,
658                                 uint32_t dist, char dir)
659 {
660     /* Decide on alignment (vertical/horizontal?), thereby scroll hint text. */
661     char * more = "more";
662     char * unit_cols = "columns";
663     char * unit_rows = "lines";
664     uint16_t dsc_space = frame->size.x;
665     char * unit = unit_rows;
666     if ('<' == dir || '>' == dir)
667     {
668         dsc_space = frame->size.y;
669         unit = unit_cols;
670     }
671     char * scrolldsc = malloc((4 * sizeof(char)) + strlen(more) + strlen(unit)
672                               + 10);                /* 10 = uint32 max strlen */
673     if (NULL == scrolldsc)
674     {
675         return 1;
676     }
677     sprintf(scrolldsc, " %d %s %s ", dist, more, unit);
678
679     /* Decide on offset of the description text inside the scroll hint line. */
680     char offset = 1, q;
681     if (dsc_space > strlen(scrolldsc) + 1)
682     {
683         offset = (dsc_space - strlen(scrolldsc)) / 2;
684     }
685
686     /* Draw scroll hint line as dir symbols bracketing description text. */
687     chtype symbol;
688     for (q = 0; q < dsc_space; q++)
689     {
690         if (q >= offset && q < strlen(scrolldsc) + offset)
691         {
692             symbol = scrolldsc[q - offset] | A_REVERSE;
693         }
694         else
695         {
696             symbol = dir | A_REVERSE;
697         }
698         if ('<' == dir || '>' == dir)
699         {
700             mvwaddch(frame->curses_win, q, pos, symbol);
701         }
702         else
703         {
704             mvwaddch(frame->curses_win, pos, q, symbol);
705         }
706     }
707
708     free(scrolldsc);
709     return 0;
710 }