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