home · contact · privacy
Some shortening / re-styling of place_win()'s code and comments.
[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     /* If w is first window, it goes into the top 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         /* If not, fit w's top left to top right of last top predecessor. */
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 w's top left to bottom left of its ->prev if enough space. */
125         uint16_t w_prev_maxy = w->prev->start.y + w->prev->framesize.y;
126         if (   w->framesize.x <= w->prev->framesize.x
127             && w->framesize.y <  world.wmeta->padsize.y - w_prev_maxy)
128         {
129             w->start.x = w->prev->start.x;
130             w->start.y = w_prev_maxy + 1;
131         }
132
133         /* Failing that, try to fit w' top left to the top right of the last
134          * predecessor w_test 1) not followed by windows with a left corner
135          * further rightwards than its own 2) with enough space rightwards for w
136          * until the bottom right of w_thr directly throning over it 3) and with
137          * this same space extending far enough to the bottom for fitting in w.
138          */
139         else
140         {
141             struct Win * w_test = w->prev;
142             struct Win * w_thr;
143             while (w_test != w_top)
144             {
145                 w_thr = w_test->prev;
146                 for (; w_test->start.y <= w_thr->start.y; w_thr = w_thr->prev);
147                 uint16_t w_thr_bottom = w_thr->start.y + w_thr->framesize.y;
148                 uint16_t free_width = (w_thr->start.x + w_thr->framesize.x)
149                                       - (w_test->start.x + w_test->framesize.x);
150                 if (   w->framesize.y < world.wmeta->padsize.y - w_thr_bottom
151                     && w->framesize.x < free_width)
152                 {
153                     w->start.x = w_test->start.x + w_test->framesize.x + 1;
154                     w->start.y = w_thr_bottom + 1;
155                     break;
156                 }
157                 w_test = w_thr;
158             }
159         }
160     }
161 }
162
163
164
165 static void scroll_hint(struct yx_uint16 fsize, char dir, uint16_t dist,
166                         char * unit, struct yx_uint16 start)
167 {
168     /* Decide on alignment (vertical/horizontal?), thereby hint text space. */
169     char * more = "more";
170     uint16_t dsc_space = fsize.x;
171     if ('<' == dir || '>' == dir)
172     {
173         dsc_space = fsize.y;
174     }                                  /* vv-- 10 = max strlen for uint16_t */
175     char scrolldsc[1 + strlen(more) + 1 + 10 + 1 + strlen(unit) + 1 + 1];
176     sprintf(scrolldsc, " %d %s %s ", dist, more, unit);
177
178     /* Decide on offset of the description text inside the scroll hint line. */
179     uint16_t dsc_offset = 1;
180     if (dsc_space > strlen(scrolldsc) + 1)
181     {
182         dsc_offset = (dsc_space - strlen(scrolldsc)) / 2;
183     }
184
185     /* Draw scroll hint line as dir symbols bracketing description text. */
186     uint16_t draw_offset = 0;
187     if      ('>' == dir)
188     {
189         draw_offset = fsize.x - 1;
190     }
191     else if ('v' == dir)
192     {
193         draw_offset = fsize.y - 1;
194     }
195     uint16_t q = 0;
196     for (; q < dsc_space; q++)
197     {
198         chtype c = dir | A_REVERSE;
199         if (q >= dsc_offset && q < strlen(scrolldsc) + dsc_offset)
200         {
201             c = scrolldsc[q - dsc_offset] | A_REVERSE;
202         }
203         if ('<' == dir || '>' == dir)
204         {
205             mvwaddch(world.wmeta->pad, start.y + q, start.x + draw_offset, c);
206         }
207         else
208         {
209             mvwaddch(world.wmeta->pad, start.y + draw_offset, start.x + q, c);
210         }
211     }
212 }
213
214
215 static void padscroll_hint(char dir, uint16_t dist)
216 {
217     struct yx_uint16 start;
218     start.y = 0;
219     start.x = world.wmeta->pad_offset;
220     scroll_hint(world.wmeta->padsize, dir, dist, "columns", start);
221 }
222
223
224
225 static void winscroll_hint(struct Win * w, char dir, uint16_t dist)
226 {
227     char * unit = "lines";
228     if ('<' == dir || '>' == dir)
229     {
230         unit = "columns";
231     }
232     struct yx_uint16 start = w->start;
233     scroll_hint(w->framesize, dir, dist, unit, start);
234 }
235
236
237
238 static void draw_wins(struct Win * w)
239 {
240     w->draw(w);
241     uint16_t y, x, size_y, size_x;
242     size_y = w->winmapsize.y;
243     size_x = w->winmapsize.x;
244     uint16_t offset_y = center_offset(w->center.y, size_y, w->framesize.y);
245     uint16_t offset_x = center_offset(w->center.x, size_x, w->framesize.x);
246     for (y = offset_y; y < w->framesize.y + offset_y && y < size_y; y++)
247     {
248         for (x = offset_x; x < w->framesize.x + offset_x && x < size_x; x++)
249         {
250             chtype ch = w->winmap[(y * w->winmapsize.x) + x];
251             mvwaddch(world.wmeta->pad, w->start.y + (y - offset_y),
252                                        w->start.x + (x - offset_x), ch);
253         }
254     }
255     free(w->winmap);
256     w->winmap = NULL;
257     w->winmapsize.y = 0;
258     w->winmapsize.x = 0;
259     if (offset_y > 0)
260     {
261         winscroll_hint(w, '^', offset_y + 1);
262     }
263     if (size_y > offset_y + w->framesize.y)
264     {
265         winscroll_hint(w, 'v', size_y - ((offset_y + w->framesize.y) - 1));
266     }
267     if (offset_x > 0)
268     {
269         winscroll_hint(w, '<', offset_x + 1);
270     }
271     if (size_x > offset_x + w->framesize.x)
272     {
273         winscroll_hint(w, '>', size_x - ((offset_x + w->framesize.x) - 1));
274     }
275     if (0 != w->next)
276     {
277         return draw_wins(w->next);
278     }
279 }
280
281
282
283 static void draw_win_borderlines(struct Win * w, char active, WINDOW * pad)
284 {
285     /* Draw vertical and horizontal border lines. */
286     uint16_t y, x;
287     for (y = w->start.y; y <= w->start.y + w->framesize.y; y++)
288     {
289         mvwaddch(pad, y, w->start.x - 1,              '|');
290         mvwaddch(pad, y, w->start.x + w->framesize.x, '|');
291     }
292     for (x = w->start.x; x <= w->start.x + w->framesize.x; x++)
293     {
294         mvwaddch(pad, w->start.y - 1,              x, '-');
295         mvwaddch(pad, w->start.y + w->framesize.y, x, '-');
296     }
297
298     /* Draw as much as possible of the title into center of top border line. */
299     char min_title_length_visible = 3;  /* min. 1 char + 2 padding/decoration */
300     if (w->framesize.x >= min_title_length_visible)
301     {
302         uint16_t title_offset = 0;
303         if (w->framesize.x > strlen(w->title) + 2)
304         {
305             title_offset = (w->framesize.x - (strlen(w->title) + 2)) / 2;
306         }                                    /* +2 is for padding/decoration */
307         uint16_t length_visible = strnlen(w->title, w->framesize.x - 2);
308         char title[length_visible + 3];
309         char decoration = ' ';
310         if (1 == active)
311         {
312             decoration = '$';
313         }
314         memcpy(title + 1, w->title, length_visible);
315         title[0] = title[length_visible + 1] = decoration;
316         title[length_visible + 2] = '\0';
317         mvwaddstr(pad, w->start.y - 1, w->start.x + title_offset, title);
318     }
319 }
320
321
322
323 static void draw_wins_borderlines(struct Win * w, struct Win * w_active,
324                                   WINDOW * pad)
325 {
326     char active = 0;
327     if (w == w_active)
328     {
329         active = 1;
330     }
331     draw_win_borderlines(w, active, pad);
332     if (0 != w->next)
333     {
334         draw_wins_borderlines(w->next, w_active, pad);
335     }
336 }
337
338
339
340 static void draw_wins_bordercorners(struct Win * w, WINDOW * pad)
341 {
342     mvwaddch(pad, w->start.y - 1, w->start.x - 1, '+');
343     mvwaddch(pad, w->start.y - 1, w->start.x + w->framesize.x, '+');
344     mvwaddch(pad, w->start.y + w->framesize.y, w->start.x - 1, '+');
345     mvwaddch(pad, w->start.y + w->framesize.y, w->start.x + w->framesize.x,'+');
346     if (0 != w->next)
347     {
348         draw_wins_bordercorners(w->next, pad);
349     }
350 }
351
352
353
354 static void shift_win_forward()
355 {
356     if (world.wmeta->active == world.wmeta->chain_end)
357     {
358         world.wmeta->chain_end = world.wmeta->active->prev;
359         world.wmeta->chain_end->next = 0;
360         world.wmeta->active->next = world.wmeta->chain_start;
361         world.wmeta->active->next->prev = world.wmeta->active;
362         world.wmeta->chain_start = world.wmeta->active;
363         world.wmeta->chain_start->prev = 0;
364     }
365     else
366     {
367         struct Win * old_prev = world.wmeta->active->prev;
368         struct Win * old_next = world.wmeta->active->next;
369         if (world.wmeta->chain_end == world.wmeta->active->next)
370         {
371             world.wmeta->chain_end = world.wmeta->active;
372             world.wmeta->active->next = 0;
373         }
374         else
375         {
376             world.wmeta->active->next = old_next->next;
377             world.wmeta->active->next->prev = world.wmeta->active;
378         }
379         if (world.wmeta->chain_start == world.wmeta->active)
380         {
381             world.wmeta->chain_start = old_next;
382         }
383         else
384         {
385             old_prev->next = old_next;
386         }
387         old_next->prev = old_prev;
388         old_next->next = world.wmeta->active;
389         world.wmeta->active->prev = old_next;
390     }
391 }
392
393
394
395 static void shift_win_backward()
396 {
397     if (world.wmeta->active == world.wmeta->chain_start)
398     {
399         world.wmeta->chain_start = world.wmeta->active->next;
400         world.wmeta->chain_start->prev = 0;
401         world.wmeta->active->prev = world.wmeta->chain_end;
402         world.wmeta->active->prev->next = world.wmeta->active;
403         world.wmeta->chain_end = world.wmeta->active;
404         world.wmeta->chain_end->next = 0;
405     }
406     else
407     {
408         struct Win * old_prev = world.wmeta->active->prev;
409         struct Win * old_next = world.wmeta->active->next;
410         if (world.wmeta->chain_start == world.wmeta->active->prev)
411         {
412             world.wmeta->chain_start = world.wmeta->active;
413             world.wmeta->active->prev = 0;
414         }
415         else
416         {
417             world.wmeta->active->prev = old_prev->prev;
418             world.wmeta->active->prev->next = world.wmeta->active;
419         }
420         if (world.wmeta->chain_end == world.wmeta->active)
421         {
422             world.wmeta->chain_end = old_prev;
423         }
424         else
425         {
426             old_next->prev = old_prev;
427         }
428         old_prev->next = old_next;
429         old_prev->prev = world.wmeta->active;
430         world.wmeta->active->next = old_prev;
431     }
432 }
433
434
435
436 extern void init_win_meta(WINDOW * screen)
437 {
438     char * f_name = "init_win_meta()";
439     char * err_s = "init_win_meta() creates virtual screen beyond legal size.";
440     char * err_m = "init_win_meta() triggers memory alloc error via newpad().";
441     world.wmeta         = try_malloc(sizeof(struct WinMeta), f_name);
442     world.wmeta->screen = screen;
443     uint32_t maxy_test  = getmaxy(screen);
444     uint32_t maxx_test  = getmaxx(screen);
445     uint8_t test = (maxy_test > UINT16_MAX || maxx_test > UINT16_MAX);
446     exit_err(test, err_s);
447     world.wmeta->padsize.y   = maxy_test;
448     world.wmeta->padsize.x   = maxx_test;
449     world.wmeta->chain_start = 0;
450     world.wmeta->chain_end   = 0;
451     world.wmeta->pad_offset  = 0;
452     world.wmeta->pad         = newpad(world.wmeta->padsize.y, 1);
453     exit_err(NULL == world.wmeta->pad, err_m);
454     world.wmeta->active      = 0;
455 }
456
457
458
459 extern void init_win(struct Win ** wp, char * title, int16_t height,
460                      int16_t width, void * func)
461 {
462     char * f_name = "init_win()";
463     struct Win * w  = try_malloc(sizeof(struct Win), f_name);
464     w->prev         = 0;
465     w->next         = 0;
466     w->winmapsize.y = 0;
467     w->winmapsize.x = 0;
468     w->winmap = NULL;
469     w->title        = try_malloc(strlen(title) + 1, f_name);
470     sprintf(w->title, "%s", title);
471     w->draw         = func;
472     w->center.y     = 0;
473     w->center.x     = 0;
474     if      (0 < width)
475     {
476         w->framesize.x = width;
477     }
478     else if (0 >= width)
479     {
480         w->framesize.x = world.wmeta->padsize.x + width;
481     }
482     if      (0 < height && height <= world.wmeta->padsize.y - 1)
483     {
484         w->framesize.y = height;
485     }
486     else if (0 >= height && world.wmeta->padsize.y + (height - 1) > 0)
487     {
488         w->framesize.y = world.wmeta->padsize.y + (height - 1);
489     }
490     *wp = w;
491 }
492
493
494
495 extern void free_winmeta()
496 {
497     delwin(world.wmeta->pad);
498     free(world.wmeta);
499 }
500
501
502
503 extern void free_win(struct Win * win)
504 {
505     free(win->title);
506     free(win);
507 }
508
509
510
511 extern void append_win(struct Win * w)
512 {
513     if (0 != world.wmeta->chain_start)
514     {
515         w->prev = world.wmeta->chain_end;
516         world.wmeta->chain_end->next = w;
517     }
518     else
519     {
520         world.wmeta->active = w;
521         world.wmeta->chain_start = w;
522     }
523     world.wmeta->chain_end = w;
524     update_wins(w);
525 }
526
527
528
529 extern void suspend_win(struct Win * w)
530 {
531     if (world.wmeta->chain_start != w)
532     {
533         w->prev->next = w->next;
534     }
535     else
536     {
537         world.wmeta->chain_start = w->next;
538     }
539     char pad_refitted = 0;
540     if (world.wmeta->chain_end != w)
541     {
542         w->next->prev = w->prev;
543         if (world.wmeta->active == w)
544         {
545             world.wmeta->active = w->next;
546         }
547         update_wins(w->next);      /* Positioning of successor windows may be */
548         pad_refitted = 1;          /* affected / need correction. Note that   */
549     }                              /* update_wins() already refits the pad,   */
550     else                           /* voiding later need for that.            */
551     {
552         world.wmeta->chain_end = w->prev;
553         if (world.wmeta->active == w)
554         {
555             world.wmeta->active = w->prev;
556         }
557     }
558
559     w->prev = 0;
560     w->next = 0;
561
562     if (0 == pad_refitted)
563     {
564         refit_pad();
565     }
566 }
567
568
569
570 extern void reset_pad_offset(uint16_t new_offset)
571 {
572     if (new_offset >= 0
573         && (new_offset < world.wmeta->pad_offset
574             || new_offset + world.wmeta->padsize.x < getmaxx(world.wmeta->pad)))
575     {
576         world.wmeta->pad_offset = new_offset;
577     }
578 }
579
580
581
582 extern void resize_active_win(struct yx_uint16 size)
583 {
584     if (0 != world.wmeta->active
585         && size.x > 0 && size.y > 0 && size.y < world.wmeta->padsize.y)
586     {
587         world.wmeta->active->framesize = size;
588         update_wins(world.wmeta->active);         /* Positioning of following */
589     }                                             /* windows may be affected. */
590 }
591
592
593
594 extern void cycle_active_win(char dir)
595 {
596     if (0 != world.wmeta->active)
597     {
598         if ('f' == dir)
599         {
600             if (world.wmeta->active->next != 0)
601             {
602                 world.wmeta->active = world.wmeta->active->next;
603             }
604             else
605             {
606                 world.wmeta->active = world.wmeta->chain_start;
607             }
608         }
609         else
610         {
611             if (world.wmeta->active->prev != 0)
612             {
613                 world.wmeta->active = world.wmeta->active->prev;
614             }
615             else
616             {
617                 world.wmeta->active = world.wmeta->chain_end;
618             }
619         }
620     }
621 }
622
623
624
625 extern void shift_active_win(char dir)
626 {
627     if (   0 == world.wmeta->active  /* No shifting with < 2 windows visible. */
628         || world.wmeta->chain_start == world.wmeta->chain_end)
629     {
630         return;
631     }
632     if ('f' == dir)
633     {
634         shift_win_forward();
635     }
636     else
637     {
638         shift_win_backward();
639     }
640     update_wins(world.wmeta->chain_start);
641 }
642
643
644
645 extern void draw_all_wins()
646 {
647     /* Empty everything before filling it a-new. */
648     erase();
649     wnoutrefresh(world.wmeta->screen);
650     werase(world.wmeta->pad);
651     if (world.wmeta->chain_start)
652     {
653
654         /* Draw windows' borders first, then windows. */
655         draw_wins_borderlines(world.wmeta->chain_start, world.wmeta->active,
656                               world.wmeta->pad);
657         draw_wins_bordercorners(world.wmeta->chain_start, world.wmeta->pad);
658         draw_wins(world.wmeta->chain_start);
659
660         /* Draw virtual screen scroll hints. */
661         if (world.wmeta->pad_offset > 0)
662         {
663             padscroll_hint('<', world.wmeta->pad_offset + 1);
664         }
665         uint16_t size_x = getmaxx(world.wmeta->pad);
666         uint16_t right_edge = world.wmeta->pad_offset + world.wmeta->padsize.x;
667         if (right_edge < size_x - 1)
668         {
669             padscroll_hint('>', size_x - right_edge);
670         }
671
672         /* Write pad segment to be shown on physical screen to screen buffer. */
673         pnoutrefresh(world.wmeta->pad, 0, world.wmeta->pad_offset, 0, 0,
674                      world.wmeta->padsize.y, world.wmeta->padsize.x - 1);
675     }
676
677     /* Only at the end write accumulated changes to the physical screen. */
678     doupdate();
679 }