ncurses 6.2 - patch 20211018
[ncurses.git] / test / movewindow.c
1 /****************************************************************************
2  * Copyright 2018-2019,2020 Thomas E. Dickey                                *
3  * Copyright 2006-2013,2017 Free Software Foundation, Inc.                  *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 /*
30  * $Id: movewindow.c,v 1.51 2020/02/02 23:34:34 tom Exp $
31  *
32  * Demonstrate move functions for windows and derived windows from the curses
33  * library.
34  *
35  * Author: Thomas E. Dickey
36  */
37 /*
38 derwin
39 mvderwin
40 subwin
41 mvwin
42
43 TODO:
44     add command to reset subwindow's origin to coincide with parent.
45     add command to delete subwindow (check if it has subwindows though)
46  */
47
48 #include <test.priv.h>
49
50 #if HAVE_MVDERWIN && HAVE_MVWIN
51
52 #include <popup_msg.h>
53
54 #ifdef HAVE_XCURSES
55 #undef derwin
56 #endif
57
58 #if defined(NCURSES_CONST)
59 #define CONST_FMT NCURSES_CONST
60 #elif defined(PDCURSES)
61 #define CONST_FMT const
62 #else
63 #define CONST_FMT               /* nothing */
64 #endif
65
66 #undef LINE_MAX
67
68 #define LINE_MIN        2
69 #define LINE_MAX        (LINES - 2)
70 #define COL_MIN         2
71 #define COL_MAX         (COLS - 2)
72
73 typedef struct {
74     int y, x;
75 } PAIR;
76
77 typedef struct {
78     WINDOW *parent;             /* need this since WINDOW->_parent is not portable */
79     WINDOW *child;              /* the actual value */
80 } FRAME;
81
82 static void head_line(CONST_FMT char *fmt, ...) GCC_PRINTFLIKE(1, 2);
83 static void tail_line(CONST_FMT char *fmt, ...) GCC_PRINTFLIKE(1, 2);
84
85 static unsigned num_windows;
86 static FRAME *all_windows;
87
88 static void
89 failed(const char *s)
90 {
91     perror(s);
92     endwin();
93     ExitProgram(EXIT_FAILURE);
94 }
95
96 static void
97 message(int lineno, CONST_FMT char *fmt, va_list argp)
98 {
99     int y, x;
100
101     getyx(stdscr, y, x);
102     move(lineno, 0);
103     clrtoeol();
104
105 #ifdef HAVE_XCURSES
106     {
107         char buffer[1024];
108         vsprintf(buffer, fmt, argp);
109         addstr(buffer);
110     }
111 #elif defined(HAVE_VW_PRINTW)
112     vw_printw(stdscr, fmt, argp);
113 #else
114     vwprintw(stdscr, fmt, argp);
115 #endif
116
117     move(y, x);
118     refresh();
119 }
120
121 static void
122 head_line(CONST_FMT char *fmt, ...)
123 {
124     va_list argp;
125
126     va_start(argp, fmt);
127     message(0, fmt, argp);
128     va_end(argp);
129 }
130
131 static void
132 tail_line(CONST_FMT char *fmt, ...)
133 {
134     va_list argp;
135
136     va_start(argp, fmt);
137     message(LINES - 1, fmt, argp);
138     va_end(argp);
139 }
140
141 /*
142  * Arrow keys move cursor, return location at current on non-arrow key.
143  */
144 static PAIR *
145 selectcell(WINDOW *parent,
146            WINDOW *child,
147            int uli, int ulj,
148            int lri, int lrj,
149            bool relative,
150            bool * more)
151 {
152     static PAIR res;            /* result cell */
153     int si = lri - uli + 1;     /* depth of the select area */
154     int sj = lrj - ulj + 1;     /* width of the select area */
155     int i = 0, j = 0;           /* offsets into the select area */
156
157     res.y = uli;
158     res.x = ulj;
159
160     if (child != 0) {
161         if (relative) {
162             getparyx(child, i, j);
163         } else {
164             getbegyx(child, i, j);
165             i -= uli + getbegy(parent);
166             j -= ulj + getbegx(parent);
167         }
168     }
169
170     if (more)
171         *more = FALSE;
172
173     for (;;) {
174         bool moved = FALSE;
175
176         tail_line("Upper left [%2d,%2d] Lower right [%2d,%2d] -> %d,%d -> %d,%d",
177                   uli, ulj,
178                   lri, lrj,
179                   i, j,
180                   uli + i, ulj + j);
181         wmove(parent, uli + i, ulj + j);
182
183         switch (wgetch(parent)) {
184         case KEY_UP:
185             i += si - 1;
186             moved = TRUE;
187             break;
188         case KEY_DOWN:
189             i++;
190             moved = TRUE;
191             break;
192         case KEY_LEFT:
193             j += sj - 1;
194             moved = TRUE;
195             break;
196         case KEY_RIGHT:
197             j++;
198             moved = TRUE;
199             break;
200         case QUIT:
201             /* FALLTHRU */
202         case ESCAPE:
203             return ((PAIR *) 0);
204 #ifdef NCURSES_MOUSE_VERSION
205         case KEY_MOUSE:
206             {
207                 MEVENT event;
208
209                 getmouse(&event);
210                 if (event.y > uli && event.x > ulj) {
211                     if (parent != stdscr) {
212                         i = event.y - getbegy(parent) - uli;
213                         j = event.x - getbegx(parent) - ulj;
214                     } else {
215                         i = event.y - uli;
216                         j = event.x - ulj;
217                     }
218                 } else {
219                     beep();
220                     break;
221                 }
222             }
223 #endif
224             /* FALLTHRU */
225         default:
226             res.y = uli + i;
227             res.x = ulj + j;
228             return (&res);
229         }
230
231         if (si <= 0)
232             i = 0;
233         else
234             i %= si;
235
236         if (sj <= 0)
237             j = 0;
238         else
239             j %= sj;
240
241         /*
242          * If the caller can handle continuous movement, return the result.
243          */
244         if (moved && more) {
245             *more = TRUE;
246             res.y = uli + i;
247             res.x = ulj + j;
248             return (&res);
249         }
250     }
251 }
252
253 /*
254  * Ask user for a window definition.
255  */
256 static bool
257 getwindow(WINDOW *parent, PAIR * ul, PAIR * lr)
258 {
259     int min_col = (parent == stdscr) ? COL_MIN : 0;
260     int max_col = (parent == stdscr) ? COL_MAX : getmaxx(parent);
261     int min_line = (parent == stdscr) ? LINE_MIN : 0;
262     int max_line = (parent == stdscr) ? LINE_MAX : getmaxy(parent);
263     PAIR *tmp;
264     bool result = FALSE;
265
266     head_line("Use arrows to move cursor, anything else to mark corner 1");
267     if ((tmp = selectcell(parent, 0,
268                           min_line, min_col,
269                           max_line, max_col,
270                           FALSE,
271                           (bool *) 0)) != 0) {
272         *ul = *tmp;
273         MvWAddCh(parent, ul->y, ul->x, '*');
274
275         head_line("Use arrows to move cursor, anything else to mark corner 2");
276         if ((tmp = selectcell(parent, 0,
277                               ul->y, ul->x,
278                               max_line, max_col,
279                               FALSE,
280                               (bool *) 0)) != 0) {
281             *lr = *tmp;
282             MvWAddCh(parent, lr->y, lr->x, '*');
283             wmove(parent, lr->y, lr->x);
284             wsyncdown(parent);
285             wrefresh(parent);
286             result = (lr->y != ul->y && lr->x != ul->x);
287         }
288     }
289     head_line("done");
290     return result;
291 }
292
293 /*
294  * Draw a box inside the given window.
295  */
296 static void
297 box_inside(WINDOW *win)
298 {
299     int y0, x0;
300     int y1, x1;
301
302     getyx(win, y0, x0);
303     getmaxyx(win, y1, x1);
304
305     MvWHLine(win, 0, 0, ACS_HLINE, x1);
306     MvWHLine(win, y1 - 1, 0, ACS_HLINE, x1);
307
308     MvWVLine(win, 0, 0, ACS_VLINE, y1);
309     MvWVLine(win, 0, x1 - 1, ACS_VLINE, y1);
310
311     MvWAddCh(win, 0, 0, ACS_ULCORNER);
312     MvWAddCh(win, y1 - 1, 0, ACS_LLCORNER);
313     MvWAddCh(win, 0, x1 - 1, ACS_URCORNER);
314     MvWAddCh(win, y1 - 1, x1 - 1, ACS_LRCORNER);
315
316     wsyncdown(win);
317     wmove(win, y0, x0);
318     wrefresh(win);
319 }
320
321 /*
322  * Add a window to our list.
323  */
324 static void
325 add_window(WINDOW *parent, WINDOW *child)
326 {
327     static unsigned have = 0;
328     unsigned need = ((num_windows + 1) | 31) + 1;
329
330     keypad(child, TRUE);
331     if (need > have) {
332         all_windows = typeRealloc(FRAME, need, all_windows);
333         if (!all_windows)
334             failed("add_window");
335     }
336     all_windows[num_windows].parent = parent;
337     all_windows[num_windows].child = child;
338     num_windows++;
339 }
340
341 static int
342 window2num(WINDOW *win)
343 {
344     int n;
345     int result = -1;
346     for (n = 0; n < (int) num_windows; ++n) {
347         if (win == all_windows[n].child) {
348             result = n;
349             break;
350         }
351     }
352     return result;
353 }
354
355 static WINDOW *
356 parent_of(WINDOW *win)
357 {
358     WINDOW *result = 0;
359     int n = window2num(win);
360     if (n >= 0)
361         result = all_windows[n].parent;
362     return result;
363 }
364
365 static void
366 repaint_one(WINDOW *win)
367 {
368     touchwin(win);
369     wnoutrefresh(win);
370 }
371
372 static void
373 refresh_all(WINDOW *win)
374 {
375     unsigned n;
376
377     for (n = 0; n < num_windows; ++n) {
378         if (all_windows[n].child != win) {
379             repaint_one(all_windows[n].child);
380         }
381     }
382
383     repaint_one(win);
384     doupdate();
385 }
386
387 static WINDOW *
388 next_window(WINDOW *win)
389 {
390     WINDOW *result = win;
391     int n = window2num(win);
392
393     if (n++ >= 0) {
394         result = all_windows[(unsigned) n % num_windows].child;
395         wmove(result, 0, 0);
396         wrefresh(result);
397     }
398     return result;
399 }
400
401 static WINDOW *
402 prev_window(WINDOW *win)
403 {
404     WINDOW *result = win;
405     int n = window2num(win);
406
407     if (n-- >= 0) {
408         if (n < 0)
409             n = (int) (num_windows - 1);
410         result = all_windows[(unsigned) n % num_windows].child;
411         wmove(result, 0, 0);
412         wrefresh(result);
413     }
414     return result;
415 }
416
417 static void
418 recur_move_window(WINDOW *parent, int dy, int dx)
419 {
420     unsigned n;
421
422     for (n = 0; n < num_windows; ++n) {
423         if (all_windows[n].parent == parent) {
424             mvwin(all_windows[n].child, dy, dx);
425             recur_move_window(all_windows[n].child, dy, dx);
426         }
427     }
428 }
429
430 /*
431  * test mvwin().
432  */
433 static bool
434 move_window(WINDOW *win, bool recur)
435 {
436     WINDOW *parent = parent_of(win);
437     bool result = FALSE;
438
439     if (parent != 0) {
440         bool top = (parent == stdscr);
441         int min_col = top ? COL_MIN : 0;
442         int max_col = top ? COL_MAX : getmaxx(parent);
443         int min_line = top ? LINE_MIN : 0;
444         int max_line = top ? LINE_MAX : getmaxy(parent);
445         PAIR *tmp;
446         bool more;
447
448         head_line("Select new position for %swindow", top ? "" : "sub");
449
450         while ((tmp = selectcell(parent,
451                                  win,
452                                  min_line, min_col,
453                                  max_line, max_col,
454                                  FALSE,
455                                  &more)) != 0) {
456             int y0, x0;
457             getbegyx(parent, y0, x0);
458             /*
459              * Moving a subwindow has the effect of moving a viewport around
460              * the screen.  The parent window retains the contents of the
461              * subwindow in the original location, but the viewport will show
462              * the contents (again) at the new location.  So it will look odd
463              * when testing.
464              */
465             if (mvwin(win, y0 + tmp->y, x0 + tmp->x) != ERR) {
466                 if (recur) {
467                     recur_move_window(win, tmp->y, tmp->x);
468                 }
469                 refresh_all(win);
470                 doupdate();
471                 result = TRUE;
472             } else {
473                 result = FALSE;
474             }
475             if (!more)
476                 break;
477         }
478     }
479     head_line("done");
480     return result;
481 }
482
483 static void
484 show_derwin(WINDOW *win)
485 {
486     int pary, parx, maxy, maxx;
487
488     getmaxyx(win, maxy, maxx);
489     getparyx(win, pary, parx);
490
491     head_line("Select new position for derived window at %d,%d (%d,%d)",
492               pary, parx, maxy, maxx);
493 }
494
495 /*
496  * test mvderwin().
497  */
498 static bool
499 move_derwin(WINDOW *win)
500 {
501     WINDOW *parent = parent_of(win);
502     bool result = FALSE;
503
504     if (parent != 0) {
505         bool top = (parent == stdscr);
506         int min_col = top ? COL_MIN : 0;
507         int max_col = top ? COL_MAX : getmaxx(parent);
508         int min_line = top ? LINE_MIN : 0;
509         int max_line = top ? LINE_MAX : getmaxy(parent);
510         PAIR *tmp;
511         bool more;
512
513         show_derwin(win);
514         while ((tmp = selectcell(parent,
515                                  win,
516                                  min_line, min_col,
517                                  max_line, max_col,
518                                  TRUE,
519                                  &more)) != 0) {
520             if (mvderwin(win, tmp->y, tmp->x) != ERR) {
521                 refresh_all(win);
522                 doupdate();
523                 repaint_one(win);
524                 doupdate();
525                 result = TRUE;
526                 show_derwin(win);
527             } else {
528                 flash();
529             }
530             if (!more)
531                 break;
532         }
533     }
534     head_line("done");
535     return result;
536 }
537
538 static void
539 fill_window(WINDOW *win, chtype ch)
540 {
541     int y, x;
542     int y0, x0;
543     int y1, x1;
544
545     getyx(win, y0, x0);
546     getmaxyx(win, y1, x1);
547     for (y = 0; y < y1; ++y) {
548         for (x = 0; x < x1; ++x) {
549             MvWAddCh(win, y, x, ch);
550         }
551     }
552     wsyncdown(win);
553     wmove(win, y0, x0);
554     wrefresh(win);
555 }
556
557 static void
558 fill_with_pattern(WINDOW *win)
559 {
560     int y, x;
561     int y0, x0;
562     int y1, x1;
563     int ch = 'a';
564
565     getyx(win, y0, x0);
566     getmaxyx(win, y1, x1);
567     for (y = 0; y < y1; ++y) {
568         for (x = 0; x < x1; ++x) {
569             MvWAddCh(win, y, x, (chtype) ch);
570             if (++ch > 'z')
571                 ch = 'a';
572         }
573     }
574     wsyncdown(win);
575     wmove(win, y0, x0);
576     wrefresh(win);
577 }
578
579 #define lines_of(ul,lr) (lr.y - ul.y + 1)
580 #define cols_of(ul,lr)  (lr.x - ul.x + 1)
581 #define pair_of(ul)     ul.y, ul.x
582
583 static WINDOW *
584 create_my_window(WINDOW *current)
585 {
586     PAIR ul, lr;
587     WINDOW *result = 0;
588
589     if (getwindow(stdscr, &ul, &lr)) {
590         result = newwin(lines_of(ul, lr), cols_of(ul, lr), pair_of(ul));
591         if (result != 0) {
592             fill_window(result, 'c');
593             add_window(stdscr, result);
594         }
595     }
596     if (result == 0)
597         result = current;
598     return result;
599 }
600
601 static WINDOW *
602 create_my_derwin(WINDOW *parent)
603 {
604     PAIR ul, lr;
605     WINDOW *result = 0;
606
607     if (getwindow(parent, &ul, &lr)) {
608         result = derwin(parent, lines_of(ul, lr), cols_of(ul, lr), pair_of(ul));
609         if (result != 0) {
610             fill_window(result, 'd');
611             add_window(parent, result);
612         }
613     }
614     if (result == 0)
615         result = parent;
616     return result;
617 }
618
619 static WINDOW *
620 create_my_subwin(WINDOW *parent)
621 {
622     PAIR ul, lr;
623     WINDOW *result = 0;
624
625     if (getwindow(parent, &ul, &lr)) {
626         result = subwin(parent,
627                         lines_of(ul, lr),
628                         cols_of(ul, lr),
629                         ul.y + getbegy(parent),
630                         ul.x + getbegx(parent));
631         if (result != 0) {
632             fill_window(result, 's');
633             add_window(parent, result);
634         }
635     }
636     if (result == 0)
637         result = parent;
638     return result;
639 }
640
641 static void
642 show_help(WINDOW *current)
643 {
644     /* *INDENT-OFF* */
645     static struct {
646         int     key;
647         CONST_FMT char * msg;
648     } help[] = {
649         { HELP_KEY_1,   "Show this screen" },
650         { 'b',          "Draw a box inside the current window" },
651         { 'c',          "Create a new window" },
652         { 'd',          "Create a new derived window" },
653         { 'D',          "Move derived window (moves viewport)" },
654         { 'f',          "Fill the current window with the next character" },
655         { 'F',          "Fill the current window with a pattern" },
656         { 'm',          "Move the current window" },
657         { 'M',          "Move the current window (and its children)" },
658         { 'q',          "Quit" },
659         { 's',          "Create a new subwindow" },
660         { CTRL('L'),    "Repaint all windows, doing current one last" },
661         { CTRL('N'),    "Cursor to next window" },
662         { CTRL('P'),    "Cursor to previous window" },
663     };
664     /* *INDENT-ON* */
665
666     char **msgs = typeCalloc(char *, SIZEOF(help) + 1);
667     size_t n;
668
669     for (n = 0; n < SIZEOF(help); ++n) {
670         size_t need = (21 + strlen(help[n].msg));
671         msgs[n] = typeMalloc(char, need);
672         _nc_SPRINTF(msgs[n], _nc_SLIMIT(need)
673                     "%-20s%s", keyname(help[n].key), help[n].msg);
674     }
675     popup_msg2(current, msgs);
676     for (n = 0; n < SIZEOF(help); ++n) {
677         free(msgs[n]);
678     }
679     free(msgs);
680 }
681
682 int
683 main(int argc GCC_UNUSED, char *argv[]GCC_UNUSED)
684 {
685     WINDOW *current_win;
686     int ch;
687     bool done = FALSE;
688
689     initscr();
690     cbreak();
691     noecho();
692     nonl();
693     intrflush(stdscr, FALSE);
694
695     add_window(0, current_win = stdscr);
696
697 #ifdef NCURSES_MOUSE_VERSION
698     (void) mousemask(BUTTON1_CLICKED, (mmask_t *) NULL);
699 #endif /* NCURSES_MOUSE_VERSION */
700
701     while (!done && (ch = wgetch(current_win)) != ERR) {
702         int y, x;
703
704         getyx(current_win, y, x);
705
706         switch (ch) {
707         case HELP_KEY_1:
708             show_help(current_win);
709             break;
710         case 'b':
711             box_inside(current_win);
712             break;
713         case 'c':
714             current_win = create_my_window(current_win);
715             break;
716         case 'd':
717             current_win = create_my_derwin(current_win);
718             break;
719         case 'D':
720             if (!move_derwin(current_win)) {
721                 tail_line("error");
722                 continue;
723             }
724             break;
725         case 'f':
726             fill_window(current_win, (chtype) wgetch(current_win));
727             break;
728         case 'F':
729             fill_with_pattern(current_win);
730             break;
731         case 'm':
732         case 'M':
733             if (!move_window(current_win, (ch == 'M'))) {
734                 tail_line("error");
735                 continue;
736             }
737             break;
738         case 'q':
739             done = TRUE;
740             break;
741         case 's':
742             current_win = create_my_subwin(current_win);
743             break;
744         case CTRL('L'):
745             refresh_all(current_win);
746             break;
747         case CTRL('N'):
748             current_win = next_window(current_win);
749             break;
750         case CTRL('P'):
751             current_win = prev_window(current_win);
752             break;
753 #if 0
754             /* want to allow cursor to move around the current window too */
755             /* want to test the resizing of windows and subwindows too */
756             /* want to allow deleting a window also */
757 #endif
758         default:
759             wmove(current_win, y, x);
760             tail_line("unrecognized key (use '?' for help)");
761             beep();
762             continue;
763         }
764         tail_line("size [%d,%d] begin [%d,%d] parent [%d,%d]",
765                   getmaxy(current_win),
766                   getmaxx(current_win),
767                   getbegy(current_win),
768                   getbegx(current_win),
769                   getpary(current_win),
770                   getparx(current_win));
771         wmove(current_win, 0, 0);
772     }
773     endwin();
774 #if NO_LEAKS
775     free(all_windows);
776 #endif
777     ExitProgram(EXIT_SUCCESS);
778 }
779 #else
780 int
781 main(void)
782 {
783     printf("This program requires the curses mvderwin and mvwin functions\n");
784     ExitProgram(EXIT_FAILURE);
785 }
786 #endif