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