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