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