home · contact · privacy
Improved formatting and include comments in windows.c
[plomrogue] / src / windows.c
1 /* windows.c */
2
3 #include "windows.h"
4 #include <stdint.h>    /* for uint16_t, uint32_t */
5 #include <ncurses.h>   /* for typedefs WINDOW, chtype, wresize(), getmaxx(), */
6                        /* getmaxy(), supbad(), delwin(), mvwaddch(),         */
7                        /* mvwaddstr(), newpad(), wnoutrefres(), erase(),     */
8                        /* werase(), pnoutrefresh(), doupdate()               */
9 #include <stdlib.h>    /* for malloc(), free() */
10 #include <string.h>    /* for strlen(), strnlen(), memcpy() */
11 #include "yx_uint16.h" /* for yx_uint16 coordinates */
12
13
14
15 /* Fit virtual screen's width to minimum width demanded by current windows'
16  * geometries.
17  */
18 static void refit_pad(struct WinMeta * wmeta);
19
20
21
22 /* Update geometry (sizes, positions) of window "w" and its successors in the
23  * window chain. For the positioning algorithm place_win() is used.
24  */
25 static void update_wins(struct WinMeta * wmeta, struct Win * w);
26 static void place_win(struct WinMeta * wmeta, struct Win * w);
27
28
29
30 /* Destroy window "w"'s ncurses window (and set w.Frame.curses_win to 0). */
31 static void destroy_win(struct Win * w);
32
33
34
35 /* Draw contents of all windows in window chain from window "w" onwards. */
36 static void draw_wins(struct Win * w);
37
38
39
40 /* draw_win_borderlines() draws the vertical and horizontal borders of window
41  * "w" sans corners into the virtual screen "pad", and draws the top border
42  * line as the windows' title bar (highlighted if the window is described
43  * active by "active" being set). draw_wins_borderlines().
44  *
45  * draw_wins_borderlines() calls draw_win_borderlines() recursively on all
46  * windows from "w" on. "w_active" is a pointer to the one window that
47  * draw_win_borderlines() is supposed to handle as the active window.
48  *
49  * Finally, draw_wins_bordercorners draws into "pad" the borders of window "w"
50  * and all its successors.
51  */
52 static void draw_win_borderlines(struct Win * w, char active, WINDOW * pad);
53 static void draw_wins_borderlines(struct Win * w, struct Win * w_active,
54                                   WINDOW * pad);
55 static void draw_wins_bordercorners(struct Win * w, WINDOW * pad);
56
57
58
59 static void refit_pad(struct WinMeta * wmeta)
60 {
61     /* Determine rightmost window column. */
62     uint16_t lastwincol = 0;
63     struct Win * w_p = wmeta->chain_start;
64     while (w_p != 0)
65     {
66         if (w_p->start.x + w_p->frame.size.x > lastwincol + 1)
67         {
68             lastwincol = w_p->start.x + w_p->frame.size.x - 1;
69         }
70         w_p = w_p->next;
71     }
72
73     /* Only resize the pad if the rightmost window column has changed. */
74     if (getmaxx(wmeta->padframe.curses_win) != lastwincol)
75     {
76         wresize(wmeta->padframe.curses_win,
77                 getmaxy(wmeta->padframe.curses_win), lastwincol + 2);
78     }
79 }
80
81
82
83 static void update_wins(struct WinMeta * wmeta, struct Win * w)
84 {
85     if (0 != w->frame.curses_win)
86     {
87         destroy_win(w);
88     }
89     place_win(wmeta, w);
90     refit_pad(wmeta);
91     w->frame.curses_win = subpad(wmeta->padframe.curses_win,
92                                  w->frame.size.y, w->frame.size.x,
93                                  w->start.y, w->start.x);
94     if (0 != w->next)
95     {
96         update_wins(wmeta, w->next);
97     }
98 }
99
100
101
102 static void place_win(struct WinMeta * wmeta, struct Win * w)
103 {
104     /* First window goes into the upper-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         /* Non-first window fallbacks to: fit rightwards of rightmost border. */
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->frame.size.x + 1;
117
118         /* Fit window below its predecessor if that one directly thrones over
119          * empty space wide and high enough.
120          */
121         uint16_t w_prev_maxy = w->prev->start.y
122                                + getmaxy(w->prev->frame.curses_win);
123         if (   w->frame.size.x <= w->prev->frame.size.x
124             && w->frame.size.y <  wmeta->padframe.size.y - w_prev_maxy)
125         {
126             w->start.x = w->prev->start.x;
127             w->start.y = w_prev_maxy + 1;
128         }
129
130         /* Failing that, try to open a new sub column below the nearest
131          * predecessor window that thrones over enough empty space.
132          */
133         else
134         {
135             struct Win * w_up = w->prev;
136             struct Win * w_upup = w_up;
137             uint16_t widthdiff;
138             while (w_up != w_top)
139             {
140                 w_upup = w_up->prev;
141                 while (1)
142                 {
143                     if (w_up->start.y != w_upup->start.y)
144                     {
145                         break;
146                     }
147                     w_upup = w_upup->prev;
148                 }
149                 w_prev_maxy = w_upup->start.y
150                               + getmaxy(w_upup->frame.curses_win);
151                 widthdiff = (w_upup->start.x + w_upup->frame.size.x)
152                             - (w_up->start.x + w_up->frame.size.x);
153                 if (   w->frame.size.y < wmeta->padframe.size.y - w_prev_maxy
154                     && w->frame.size.x < widthdiff)
155                 {
156                     w->start.x = w_up->start.x + w_up->frame.size.x + 1 ;
157                     w->start.y = w_prev_maxy + 1;
158                     break;
159                 }
160                 w_up = w_upup;
161             }
162         }
163     }
164 }
165
166
167
168 static void destroy_win (struct Win * w)
169 {
170     delwin(w->frame.curses_win);
171     w->frame.curses_win = 0;
172 }
173
174
175
176 static void draw_wins (struct Win * w)
177 {
178     w->draw(w);
179     if (0 != w->next)
180     {
181         draw_wins(w->next);
182     }
183 }
184
185
186
187 static void draw_win_borderlines(struct Win * w, char active, WINDOW * pad)
188 {
189     /* Draw vertical and horizontal border lines. */
190     uint16_t y, x;
191     for (y = w->start.y; y <= w->start.y + w->frame.size.y; y++)
192     {
193         mvwaddch(pad, y, w->start.x - 1,               '|');
194         mvwaddch(pad, y, w->start.x + w->frame.size.x, '|');
195     }
196     for (x = w->start.x; x <= w->start.x + w->frame.size.x; x++)
197     {
198         mvwaddch(pad, w->start.y - 1,               x, '-');
199         mvwaddch(pad, w->start.y + w->frame.size.y, x, '-');
200     }
201
202     /* Draw as much as possible of the title into center of top border line. */
203     char min_title_length_visible = 3;  /* min. 1 char + 2 padding/decoration */
204     if (w->frame.size.x >= min_title_length_visible)
205     {
206         uint16_t title_offset = 0;
207         if (w->frame.size.x > strlen(w->title) + 2)
208         {
209             title_offset = (w->frame.size.x - (strlen(w->title) + 2)) / 2;
210         }                                     /* +2 is for padding/decoration */
211         uint16_t length_visible = strnlen(w->title, w->frame.size.x - 2);
212         char title[length_visible + 3];
213         char decoration = ' ';
214         if (1 == active)
215         {
216             decoration = '$';
217         }
218         memcpy(title + 1, w->title, length_visible);
219         title[0] = title[length_visible + 1] = decoration;
220         title[length_visible + 2] = '\0';
221         mvwaddstr(pad, w->start.y - 1, w->start.x + title_offset, title);
222     }
223 }
224
225
226
227 static void draw_wins_borderlines(struct Win * w, struct Win * w_active,
228                                   WINDOW * pad)
229 {
230     char active = 0;
231     if (w == w_active)
232     {
233         active = 1;
234     }
235     draw_win_borderlines(w, active, pad);
236     if (0 != w->next)
237     {
238         draw_wins_borderlines(w->next, w_active, pad);
239     }
240 }
241
242
243
244 static void draw_wins_bordercorners(struct Win * w, WINDOW * pad)
245 {
246     mvwaddch(pad, w->start.y - 1, w->start.x - 1, '+');
247     mvwaddch(pad, w->start.y - 1, w->start.x + w->frame.size.x, '+');
248     mvwaddch(pad, w->start.y + w->frame.size.y, w->start.x - 1, '+');
249     mvwaddch(pad,
250              w->start.y + w->frame.size.y, w->start.x + w->frame.size.x, '+');
251     if (0 != w->next)
252     {
253         draw_wins_bordercorners(w->next, pad);
254     }
255 }
256
257
258
259 extern struct WinMeta init_win_meta(WINDOW * screen)
260 {
261     struct WinMeta wmeta;
262     wmeta.screen              = screen;
263     wmeta.padframe.size.y     = getmaxy(screen);
264     wmeta.padframe.size.x     = getmaxx(screen);
265     wmeta.chain_start         = 0;
266     wmeta.chain_end           = 0;
267     wmeta.pad_offset          = 0;
268     wmeta.padframe.curses_win = newpad(wmeta.padframe.size.y, 1);
269     wmeta.active              = 0;
270     return wmeta;
271 }
272
273
274
275 extern struct Win init_win(struct WinMeta * wmeta, char * title,
276                            uint16_t height, uint16_t width,
277                            void * data, void * func)
278 {
279     struct Win w;
280     w.prev             = 0;
281     w.next             = 0;
282     w.frame.curses_win = 0;
283     w.title            = title;
284     w.data             = data;
285     w.draw             = func;
286     if (width > 0)
287     {
288         w.frame.size.x = width;
289     }
290     else
291     {
292         w.frame.size.x = 1;
293     }
294     if (height > 0 && height <= wmeta->padframe.size.y - 1)
295     {
296         w.frame.size.y = height;
297     }
298     else
299     {
300         w.frame.size.y = wmeta->padframe.size.y - 1;
301     }
302     return w;
303 }
304
305
306
307 extern void append_win(struct WinMeta * wmeta, struct Win * w)
308 {
309     if (0 != wmeta->chain_start)
310     {
311         w->prev = wmeta->chain_end;
312         wmeta->chain_end->next = w;
313     }
314     else
315     {
316         wmeta->active = w;
317         wmeta->chain_start = w;
318     }
319     wmeta->chain_end = w;
320     update_wins(wmeta, w);
321 }
322
323
324
325 extern void suspend_win(struct WinMeta * wmeta, struct Win * w)
326 {
327     destroy_win(w);
328
329     if (wmeta->chain_start != w)
330     {
331         w->prev->next = w->next;
332     }
333     else
334     {
335         wmeta->chain_start = w->next;
336     }
337     char pad_refitted = 0;
338     if (wmeta->chain_end != w)
339     {
340         w->next->prev = w->prev;
341         if (wmeta->active == w)
342         {
343             wmeta->active = w->next;
344         }
345         update_wins(wmeta, w->next); /* Positioning of successor windows may  */
346         pad_refitted = 1;            /* be affected / need correction. Note   */
347     }                                /* that update_wins() already refits the */
348     else                             /* pad, voiding later need for that.     */
349     {
350         wmeta->chain_end = w->prev;
351         if (wmeta->active == w)
352         {
353             wmeta->active = w->prev;
354         }
355     }
356
357     w->prev = 0;
358     w->next = 0;
359
360     if (0 == pad_refitted)
361     {
362         refit_pad(wmeta);
363     }
364 }
365
366
367
368 extern void reset_pad_offset(struct WinMeta * wmeta, uint16_t new_offset)
369 {
370     if (new_offset >= 0
371         && (new_offset < wmeta->pad_offset
372             || new_offset + wmeta->padframe.size.x
373                < getmaxx(wmeta->padframe.curses_win)))
374     {
375         wmeta->pad_offset = new_offset;
376     }
377 }
378
379
380
381 extern void resize_active_win(struct WinMeta * wmeta, struct yx_uint16 size)
382 {
383     if (0 != wmeta->active
384         && size.x > 0 && size.y > 0
385         && size.y < wmeta->padframe.size.y)
386     {
387         wmeta->active->frame.size = size;
388         update_wins(wmeta, wmeta->chain_start);   /* Positioning of successor */
389     }                                             /* windows may be affected. */
390 }
391
392
393
394 extern void cycle_active_win(struct WinMeta * wmeta, char dir)
395 {
396     if (0 != wmeta->active)
397     {
398         if ('n' == dir)
399         {
400             if (wmeta->active->next != 0)
401             {
402                 wmeta->active = wmeta->active->next;
403             }
404             else
405             {
406                 wmeta->active = wmeta->chain_start;
407             }
408         }
409         else
410         {
411             if (wmeta->active->prev != 0)
412             {
413                 wmeta->active = wmeta->active->prev;
414             }
415             else
416             {
417                 wmeta->active = wmeta->chain_end;
418             }
419         }
420     }
421 }
422
423
424
425 extern void shift_active_win(struct WinMeta * wmeta, char dir)
426 {
427     if (0 != wmeta->active                        /* No shifting with less    */
428         && wmeta->chain_start != wmeta->chain_end /* than one window visible. */
429         && (dir == 'f' || dir == 'b'))
430     {
431         struct Win * w_shift = wmeta->active, * w_p, * w_p_next;
432
433         /* Check if shifting will lead to wrapping. */
434         char wrap = 0;
435         if (   (dir == 'f' && w_shift == wmeta->chain_end)
436             || (dir == 'b' && w_shift == wmeta->chain_start))
437         {
438             wrap = 1;
439         }
440
441         /* Suspend all visible windows, remember their order in wins[]. */
442         uint16_t i, i_max;
443         for (w_p  = wmeta->chain_start, i_max = 1;
444              w_p != wmeta->chain_end;
445              w_p  = w_p->next)
446         {
447             i_max++;
448         }
449         struct Win ** wins = malloc(i_max * sizeof(struct Win *));
450         for (i = 0, w_p = wmeta->chain_start;
451              i < i_max;
452              i++)
453         {
454             w_p_next = w_p->next;
455             suspend_win(wmeta, w_p);
456             wins[i] = w_p;
457             w_p = w_p_next;
458         }
459
460         /* Re-append all previously visible windows in the new order. */
461         if (wrap)
462         {
463             if (dir == 'f')
464             {
465                 append_win(wmeta, w_shift);
466                 for (i = 0; i < i_max - 1; i++)
467                 {
468                     append_win(wmeta, wins[i]);
469                 }
470             }
471             else
472             {
473                 for (i = 1; i < i_max; i++)
474                 {
475                     append_win(wmeta, wins[i]);
476                 }
477                 append_win(wmeta, w_shift);
478             }
479         }
480         else
481         {
482             for (i = 0; i < i_max; i++)
483             {
484                 if (   (dir == 'f' && w_shift == wins[i])
485                     || (dir == 'b' && w_shift == wins[i+1]))
486                 {
487                     append_win(wmeta, wins[i+1]);
488                     append_win(wmeta, wins[i]);
489                     i++;
490                 }
491                 else
492                 {
493                     append_win(wmeta, wins[i]);
494                 }
495             }
496         }
497         free(wins);
498
499         wmeta->active = w_shift;  /* Otherwise lastly appended win is active. */
500     }
501 }
502
503
504
505 extern void draw_all_wins(struct WinMeta * wmeta)
506 {
507     /* Empty everything before filling it a-new. */
508     erase();
509     wnoutrefresh(wmeta->screen);
510     werase(wmeta->padframe.curses_win);
511     if (wmeta->chain_start)
512     {
513
514         /* Draw windows' contents first, then their borders. */
515         draw_wins(wmeta->chain_start);
516         draw_wins_borderlines(wmeta->chain_start, wmeta->active,
517                               wmeta->padframe.curses_win);
518         draw_wins_bordercorners(wmeta->chain_start, wmeta->padframe.curses_win);
519
520         /* Draw virtual screen scroll hints. */
521         if (wmeta->pad_offset > 0)
522         {
523             draw_scroll_hint(&wmeta->padframe,
524                              wmeta->pad_offset, wmeta->pad_offset + 1, '<');
525         }
526         if (wmeta->pad_offset + wmeta->padframe.size.x
527             < getmaxx(wmeta->padframe.curses_win) - 1)
528         {
529             draw_scroll_hint(&wmeta->padframe,
530                              wmeta->pad_offset + wmeta->padframe.size.x - 1,
531                              getmaxx(wmeta->padframe.curses_win)
532                              - (wmeta->pad_offset + wmeta->padframe.size.x),
533                              '>');
534         }
535
536         /* Write virtual screen segment to be shown on physical screen into */
537         /* ncurses screen buffer. */
538         pnoutrefresh(wmeta->padframe.curses_win, 0, wmeta->pad_offset, 0, 0,
539                      wmeta->padframe.size.y, wmeta->padframe.size.x-1);
540     }
541
542     /* Only at the end write accumulated changes to the physical screen. */
543     doupdate();
544 }
545
546
547
548 extern void draw_scroll_hint(struct Frame * frame, uint16_t pos, uint32_t dist,
549                              char dir)
550 {
551     /* Decide on alignment (vertical/horizontal?), thereby scroll hint text. */
552     char * more = "more";
553     char * unit_cols = "columns";
554     char * unit_rows = "lines";
555     uint16_t dsc_space = frame->size.x;
556     char * unit = unit_rows;
557     if ('<' == dir || '>' == dir)
558     {
559         dsc_space = frame->size.y;
560         unit = unit_cols;
561     }
562     char * scrolldsc = malloc((4 * sizeof(char)) + strlen(more) + strlen(unit)
563                               + 10);                /* 10 = uint32 max strlen */
564     sprintf(scrolldsc, " %d %s %s ", dist, more, unit);
565
566     /* Decide on offset of the description text inside the scroll hint line. */
567     char offset = 1, q;
568     if (dsc_space > strlen(scrolldsc) + 1)
569     {
570         offset = (dsc_space - strlen(scrolldsc)) / 2;
571     }
572
573     /* Draw scroll hint line as dir symbols bracketing description text. */
574     chtype symbol;
575     for (q = 0; q < dsc_space; q++)
576     {
577         if (q >= offset && q < strlen(scrolldsc) + offset)
578         {
579             symbol = scrolldsc[q - offset] | A_REVERSE;
580         }
581         else
582         {
583             symbol = dir | A_REVERSE;
584         }
585         if ('<' == dir || '>' == dir)
586         {
587             mvwaddch(frame->curses_win, q, pos, symbol);
588         }
589         else
590         {
591             mvwaddch(frame->curses_win, pos, q, symbol);
592         }
593     }
594
595     free(scrolldsc);
596 }