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