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