home · contact · privacy
Made single World struct a global variable, fitted a lot of code to this change,...
[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, void * func)
489 {
490     struct Win * w = malloc(sizeof(struct Win));
491     if (NULL == w)
492     {
493         return 1;
494     }
495     w->prev         = 0;
496     w->next         = 0;
497     w->winmapsize.y = 0;
498     w->winmapsize.x = 0;
499     w->winmap = NULL;
500     w->title        = malloc(strlen(title) + 1);
501     if (NULL == w->title)
502     {
503         return 1;
504     }
505     sprintf(w->title, "%s", title);
506     w->draw         = func;
507     w->center.y     = 0;
508     w->center.x     = 0;
509     if      (0 < width)
510     {
511         w->framesize.x = width;
512     }
513     else if (0 >= width)
514     {
515         w->framesize.x = wmeta->padsize.x + width;
516     }
517     if      (0 < height && height <= wmeta->padsize.y - 1)
518     {
519         w->framesize.y = height;
520     }
521     else if (0 >= height && wmeta->padsize.y + (height - 1) > 0)
522     {
523         w->framesize.y = wmeta->padsize.y + (height - 1);
524     }
525     *wp = w;
526     return 0;
527 }
528
529
530
531 extern void free_winmeta(struct WinMeta * wmeta)
532 {
533     delwin(wmeta->pad);
534     free(wmeta);
535 }
536
537
538
539 extern void free_win(struct Win * win)
540 {
541     free(win->title);
542     free(win);
543 }
544
545
546
547 extern uint8_t append_win(struct WinMeta * wmeta, struct Win * w)
548 {
549     if (0 != wmeta->chain_start)
550     {
551         w->prev = wmeta->chain_end;
552         wmeta->chain_end->next = w;
553     }
554     else
555     {
556         wmeta->active = w;
557         wmeta->chain_start = w;
558     }
559     wmeta->chain_end = w;
560     return update_wins(wmeta, w);
561 }
562
563
564
565 extern uint8_t suspend_win(struct WinMeta * wmeta, struct Win * w)
566 {
567     if (wmeta->chain_start != w)
568     {
569         w->prev->next = w->next;
570     }
571     else
572     {
573         wmeta->chain_start = w->next;
574     }
575     char pad_refitted = 0;
576     if (wmeta->chain_end != w)
577     {
578         w->next->prev = w->prev;
579         if (wmeta->active == w)
580         {
581             wmeta->active = w->next;
582         }
583         uint8_t test = update_wins(wmeta, w->next); /* Positioning of         */
584         if (0 != test)                              /* successor windows may  */
585         {                                           /* be affected / need     */
586             return test;                            /* correction. Note that  */
587         }                                           /* update_wins() already  */
588         pad_refitted = 1;                           /* refits the pad, voiding*/
589     }                                               /* later need for that.   */
590     else
591     {
592         wmeta->chain_end = w->prev;
593         if (wmeta->active == w)
594         {
595             wmeta->active = w->prev;
596         }
597     }
598
599     w->prev = 0;
600     w->next = 0;
601
602     if (0 == pad_refitted)
603     {
604         return refit_pad(wmeta);
605     }
606     return 0;
607 }
608
609
610
611 extern void reset_pad_offset(struct WinMeta * wmeta, uint16_t new_offset)
612 {
613     if (new_offset >= 0
614         && (new_offset < wmeta->pad_offset
615             || new_offset + wmeta->padsize.x < getmaxx(wmeta->pad)))
616     {
617         wmeta->pad_offset = new_offset;
618     }
619 }
620
621
622
623 extern uint8_t resize_active_win(struct WinMeta * wmeta, struct yx_uint16 size)
624 {
625     if (0 != wmeta->active
626         && size.x > 0 && size.y > 0 && size.y < wmeta->padsize.y)
627     {
628         wmeta->active->framesize = size;
629         return update_wins(wmeta, wmeta->active); /* Positioning of following */
630     }                                             /* windows may be affected. */
631     return 0;
632 }
633
634
635
636 extern void cycle_active_win(struct WinMeta * wmeta, char dir)
637 {
638     if (0 != wmeta->active)
639     {
640         if ('f' == dir)
641         {
642             if (wmeta->active->next != 0)
643             {
644                 wmeta->active = wmeta->active->next;
645             }
646             else
647             {
648                 wmeta->active = wmeta->chain_start;
649             }
650         }
651         else
652         {
653             if (wmeta->active->prev != 0)
654             {
655                 wmeta->active = wmeta->active->prev;
656             }
657             else
658             {
659                 wmeta->active = wmeta->chain_end;
660             }
661         }
662     }
663 }
664
665
666
667 extern uint8_t shift_active_win(struct WinMeta * wmeta, char dir)
668 {
669     if (   0 == wmeta->active                        /* No shifting with < 2  */
670         || wmeta->chain_start == wmeta->chain_end)   /* windows visible.      */
671     {
672         return 0;
673     }
674     if ('f' == dir)
675     {
676         shift_win_forward(wmeta);
677     }
678     else
679     {
680         shift_win_backward(wmeta);
681     }
682     return update_wins(wmeta, wmeta->chain_start);
683 }
684
685
686
687 extern uint8_t draw_all_wins(struct WinMeta * wm)
688 {
689     /* Empty everything before filling it a-new. */
690     erase();
691     wnoutrefresh(wm->screen);
692     werase(wm->pad);
693     if (wm->chain_start)
694     {
695
696         /* Draw windows' borders first, then windows. */
697         draw_wins_borderlines(wm->chain_start, wm->active, wm->pad);
698         draw_wins_bordercorners(wm->chain_start, wm->pad);
699         if (1 == draw_wins(wm, wm->chain_start))
700         {
701             return 1;
702         }
703
704         /* Draw virtual screen scroll hints. */
705         if (wm->pad_offset > 0)
706         {
707             padscroll_hint(wm, '<', wm->pad_offset + 1);
708         }
709         uint16_t size_x = getmaxx(wm->pad);
710         uint16_t right_edge = wm->pad_offset + wm->padsize.x;
711         if (right_edge < size_x - 1)
712         {
713             padscroll_hint(wm, '>', size_x - right_edge);
714         }
715
716         /* Write pad segment to be shown on physical screen to screen buffer. */
717         pnoutrefresh(wm->pad, 0, wm->pad_offset, 0, 0,
718                      wm->padsize.y, wm->padsize.x - 1);
719     }
720
721     /* Only at the end write accumulated changes to the physical screen. */
722     doupdate();
723     return 0;
724 }