/* menu.c -- curses based windowing interface * Liam Quin, July 1989 * * $Id: menu.c,v 1.6 2001/05/31 03:50:13 liam Exp $ */ #ifndef lint static char rcsid[] = "@(#) $Id: menu.c,v 1.6 2001/05/31 03:50:13 liam Exp $"; #endif #ifdef ultrix # include #else # include #endif #include "globals.h" #ifdef HAVE_STRING_H # include #else # include #endif #ifdef HAVE_STDLIB_H # include #else # include #endif #ifndef A_STANDOUT # include "oldcurses.h" #endif #include "menu.h" #include "internal.h" #include "error.h" void SetStringBoxSize(); /* Some Shorthand */ #define BL (MENUTOP - 1) /* VSO is Vertical Scroll Offset... */ #define VSO (Menu->TopLineOnScreen) /* Appearance of menus: * Each menu is boxed with the terminal line drawing characters. It will * use '|' if you don't have VLINE; ACS_SSBS looks like the upper-case T * that will join the HLINE of the Menu Line to the menu box itself. * See /usr/include/curses.h for an explanation of these names. On pre- * System V, curses often uses + - and | to draw lines, which is ugly but * doeas at least work. It might be possible to define variables with * the appropriate names (ACS_VLINE, etc), and to put appropriate things * into termcap. Uniplex used to do this, for example. * * If not compiled with -DSAVEMENULINE, to save a line on the screen, * the first line of each menu over-writes the Menu line, so you get: * File Edit Method Position Adults Children Animals * -------------------------------------+ Daniel +----------------------- * | Simon | * | Lorella | * | Carol | * +---------+ * Otherwise, you get this: * File Edit Method Position Adults Children Animals * -------------------------------------+---------+----------------------- * | Daniel | * | Simon | * | Lorella | * | Carol | * +---------+ * which might look neater, but uses an extra line on the screen. * It saves the menu line on each redraw, hence SAVEMENULINE. * * Most of this is wired into the Show* routines -- ShowMenuBar() and * ShowMenu(). */ static int DisplayedMenu = 0; static int NextMenuId = 1; t_Menu * NewMenu(Name) char *Name; { static int MCount = 0; t_Menu *M = (t_Menu *) malloc(sizeof(t_Menu)); if (!M) { error(ERR_FATAL, "Not enough memory for new menu"); } M->MenuStyle = 0; /* default */ M->TopLineOnScreen = 0; M->Width = -1; /*i.e. not calculated yet */ M->NameLength = strlen(Name); M->Name = malloc(M->NameLength + 1); if (!M->Name) { error(ERR_FATAL, "Not enough memory for new menu name"); } (void) strcpy(M->Name, Name); M->SelectedLine = 0; M->MenuId = MCount++; return M; } ShowMenuBar(MenuBar) t_MenuBar *MenuBar; { /* draw a menu bar... */ int barpos = 0; int i; int SelectedLength; int EndOfMenu = 0; /* Clear the top two lines. */ (void) wmove(stdscr, 0, 0); (void) wclrtoeol(stdscr); (void) wmove(stdscr, 1, 0); (void) wclrtoeol(stdscr); /* draw the menu line */ for (i = 0; i < COLS - 1; i++) { (void) mvwaddch(stdscr, 1, i, (chtype) ACS_HLINE); } /* On really slow terminals it might be worth doing a refresh() * at this point, so the clear can happen at the same time as we * are working out the size of the menu bar, etc. */ #define HSO (MenuBar->ScrollOffset) /* Horiz. scroll offset */ /** Horizontal scrolling of long menus... **/ /* Check for sanity! */ if (MenuBar->ScrollOffset < 0) { MenuBar->ScrollOffset = 0; } /* Find where to start displaying... * keep adding the menu widths until the selected menu is on screen */ /* First, work out the widths. It might be worth pre-computing * these strlen()s.... */ for (i = 0; i < MenuBar->HowManyMenus; i++) { barpos = (EndOfMenu += 2); MenuBar->Menus[i]->PositionInBar = barpos; if (MenuBar->Menus[i]->NameLength <= 0) { MenuBar->Menus[i]->NameLength = strlen(MenuBar->Menus[i]->Name); } EndOfMenu += MenuBar->Menus[i]->NameLength; } /* Now, we can work out how to get SelectedMenu on screen! */ /* First, check we're in range: */ if (MenuBar->SelectedMenu == 0) { MenuBar->ScrollOffset = 0; } if (MenuBar->ScrollOffset > EndOfMenu) { MenuBar->ScrollOffset = EndOfMenu; } /* Now, check that SelectedMenu and scrolloffset both fit on the screen */ barpos = MenuBar->Menus[MenuBar->SelectedMenu]->PositionInBar; if (MenuBar->ScrollOffset > barpos) { MenuBar->ScrollOffset = barpos - 2; } /* 2 9 20 28 ........... barpos = 28ish * File ||ScrollOffset ............. MenuBar->ScrollOffset = 5ish */ /* The +6 is * 2 for the extreme left * 2 for the extreme righht * 2 spare, so you can see the menu */ SelectedLength = MenuBar->Menus[MenuBar->SelectedMenu]->NameLength; if (barpos - MenuBar->ScrollOffset >= COLS - SelectedLength) { /* it's off the right-hand edge of the screen. The +2 * is 1 for a space at the end of the name, leaving room for * a '>' if the bar continues, and one for a space to leave room * for the right edge of the menu bar if it gets pulled down. */ MenuBar->ScrollOffset = barpos - (COLS - SelectedLength - 2); } /* Horizontal scrolling of long menus... */ if (MenuBar->ScrollOffset < 0) { MenuBar->ScrollOffset = 0; } /* Now the menu bar itself */ EndOfMenu = barpos = 0; for (i = 0; i < MenuBar->HowManyMenus; i++) { register char *q; /* leave 2 spaces before each menu so the box lines up * when it's selected. */ EndOfMenu += 2; /* Draw the spaces -- EndOfMenu represents the end of the * menubar, in characters, ignoring HSO --- so, it can be * bigger than COLS. */ while (barpos < EndOfMenu) { if (barpos >= HSO && barpos < HSO + COLS) { (void) mvwaddch(stdscr, 0, barpos - HSO, (chtype) ' '); } ++barpos; } /* This is really for ShowMenu() */ MenuBar->Menus[i]->PositionInBar = barpos - 2 - HSO; if (i == MenuBar->SelectedMenu) { (void) attrset(A_STANDOUT); } for (q = MenuBar->Menus[i]->Name; *q; q++) { /* Only write characters onto the screen in the range * HSO ... HSO + COLS */ if (barpos >= HSO && barpos < HSO + COLS) { (void) mvwaddch(stdscr, 0, barpos - HSO, (chtype) *q); } barpos++; } if (i == MenuBar->SelectedMenu) { (void) attrset(0); } if (barpos > COLS + HSO) { break; /* we can give up now! */ } EndOfMenu = barpos; } if (barpos == 0) { error(ERR_FATAL, "UseMenuBar: The Menus must have names"); } if (MenuBar->ScrollOffset > 0) { (void) mvwaddch(stdscr, 0, 0, ACS_LARROW); /* left arrow */ } if (i < MenuBar->HowManyMenus) { /* not all the menuse were shown */ (void) mvwaddch(stdscr, 0, COLS - 1, ACS_RARROW); /* right arrow */ } } int UseMenuBar(MenuBar) t_MenuBar *MenuBar; { int SelectedChar; extern int MenuUsed; int InfoBoxOnScreen = 0; /* MenuBar->ScrollOffset = 0; MenuBar->SelectedMenu = 0; */ do { int mval; int InMenu; InMenu = 0; ShowMenuBar(MenuBar); (void) refresh(); SelectedChar = getch(); switch (SelectedChar) { case 'q': case 'f': case 'Q': case 'F': return -2; info_key: case KEY_HELP: case '?': case 'x': /* "eXplain" */ case 'X': /* "eXplain" */ case 'i': /* "info" */ case 'I': /* "Info" */ { char *p = MenuBar->Menus[MenuBar->SelectedMenu]->Description; int tlx = MenuBar->Menus[MenuBar->SelectedMenu]->PositionInBar; tlx += 4; /* a little to the right.. */ if (!p) { p = "No information available about this menu."; } SelectedChar = ShowInfo(p, (WINDOW *) 0, tlx, 2); (void) touchwin(stdscr); (void) wnoutrefresh(stdscr); InfoBoxOnScreen = (SelectedChar != ' '); if (SelectedChar == 0 || SelectedChar == ' ') { continue; } break; } } /* The character was changed if ShowInfo was called... * The next switch() deals with selecting a menu, and also * canonicalises the input for the subsequent switch(). */ switch (SelectedChar) { case ' ': /* ShowInfo() returns ' 'to disable the info box. */ if (InfoBoxOnScreen) { InfoBoxOnScreen = 0; break; } case '\r': case '\n': case 'j': case KEY_DOWN: fire_menu: if (InfoBoxOnScreen) { InfoBoxOnScreen = 0; } InMenu = 1; mval = UseMenu(MenuBar->Menus[MenuBar->SelectedMenu]); /* clear(); */ if (mval > MenuBar->Menus[MenuBar->SelectedMenu]->HowManyItems || mval < 0) { SelectedChar = mval; if (mval == -2) { SelectedChar = '\004'; continue; } } else { /* If we're supposed to print the final selection on * exit, we need to know which menu was selected --- * see the end of the example main(). */ MenuUsed = MenuBar->SelectedMenu; if (mval >= 0 && MenuBar->Menus[MenuUsed]->Items[mval].Function != 0) { return (* MenuBar->Menus[MenuUsed]->Items[mval].Function)(MenuBar->Menus[MenuUsed], mval); } else { /* No function to call */ return mval; } } break; case 'h': SelectedChar = KEY_LEFT; break; case 'l': /* lower case ell */ SelectedChar = KEY_RIGHT; break; } /* The submenu might have been left with KEY_LEFT (say), so * we check again here. */ switch (SelectedChar) { case KEY_HOME: MenuBar->ScrollOffset = 0; MenuBar->SelectedMenu = 0; break; case '$': MenuBar->SelectedMenu = MenuBar->HowManyMenus - 1; break; case KEY_LEFT: if (MenuBar->SelectedMenu > 0) { (MenuBar->SelectedMenu)--; ShowMenuBar(MenuBar); } else { (void) beep(); } break; case KEY_RIGHT: if (MenuBar->SelectedMenu + 1 < MenuBar->HowManyMenus) { MenuBar->SelectedMenu++; ShowMenuBar(MenuBar); } else { (void) beep(); } break; case 'R' ^ 64: /* control-R */ (void) clearok(stdscr, TRUE); (void) refresh(); break; case ' ': case '\004': /* used internally */ break; default: (void) beep(); } if (InfoBoxOnScreen) { goto info_key; /* sorry */ } if (InMenu) { /* clear(); */ ShowMenuBar(MenuBar); (void) refresh(); goto fire_menu; } } while (SelectedChar != EOF && SelectedChar != 'q'); /*NOTREACHED*/ error(ERR_FATAL|ERR_INTERNAL, "/*NOTREACHED*/ in UseMenuBar()"); /*NOTREACHED*/ return -1; } /* UseMenu() -- display a menu, allow the user to select from it, * then undisplay it and redraw the screen */ int UseMenu(Menu) t_Menu *Menu; { WINDOW *MenuWin; int tlx, bly; /* top left x/bot. right y of menu box */ static int InfoBoxOnScreen = 0; if (InfoBoxOnScreen) { InfoBoxOnScreen++; /* force the box to be redrawn */ } /* Ensure that the menu has a MenuId -- * This is the only module which knows about MenuId... */ if (Menu->MenuId <= 0) { /* a new menu */ Menu->MenuId = NextMenuId++; } /* If the menu is not currently displayed */ if (DisplayedMenu <= 0) { DisplayedMenu = -1; } if (Menu->SelectedLine < 0) { Menu->SelectedLine = 0; Menu->TopLineOnScreen = 0; } tlx = Menu->PositionInBar; if ((bly = Menu->HowManyItems + 2) > LINES - 2) { /* -2 -- one for the menu bar, one because top line is 0 */ bly = LINES - 2; } /* Ensure that the width has been calculated */ if (Menu->Width <= 0) { (void) FindMenuWidth(Menu); } /* We leave 1 space either side, + 1 for the box */ if ((MenuWin = newwin(bly, Menu->Width + 4, 1, tlx)) == (WINDOW *) 0) { (void) beep(); error(ERR_FATAL|ERR_INTERNAL, "No more memory"); } /* spl6(); */ DisplayedMenu = Menu->MenuId; /* spl0(); */ for (;;) { int ch; ShowMenu(Menu, MenuWin); (void) wrefresh(MenuWin); ch = (InfoBoxOnScreen > 1) ? KEY_HELP : getch(); if (InfoBoxOnScreen > 1) InfoBoxOnScreen = 1; switch (ch) { char *p; case 'q': case 'Q': /* return immediately */ case 'f': case 'F': /* Finish */ InfoBoxOnScreen = 0; (void) delwin(MenuWin); (void) touchwin(stdscr); return -2; case '?': /* "huhn?" */ case 'x': /* "eXplain" */ case 'X': /* "eXplain" */ case 'i': /* "info" */ case 'I': /* "info" */ case KEY_HELP: key_info: p = Menu->Items[Menu->SelectedLine].Description; if (!p) { p = "No information available for this selection."; } ch = ShowInfo(p, MenuWin, tlx + 4, Menu->SelectedLine + 3 - VSO); (void) touchwin(stdscr); (void) wnoutrefresh(stdscr); (void) touchwin(MenuWin); (void) wrefresh(MenuWin); /* ShowInfo returns SPACE if the user wanted to get * rid of the box; otherwise, it returns the appropriate * key. * The box went away anyway, but we might put another * one there. */ InfoBoxOnScreen = (ch != ' '); if (ch == 0 || ch == ' ') continue; break; } /* So either we are looking at the original character, * or the user pressed the Info key... */ switch (ch) { case ' ': case '\r': case '\n': (void) delwin(MenuWin); (void) touchwin(stdscr); return Menu->SelectedLine; case KEY_HOME: if (Menu->SelectedLine > 0) { Menu->SelectedLine = 0; if (InfoBoxOnScreen) { ShowMenu(Menu, MenuWin); (void) wrefresh(MenuWin); goto key_info; /* sorry */ } } else { (void) delwin(MenuWin); (void) touchwin(stdscr); return KEY_HOME; } case 'k': case KEY_UP: if (Menu->SelectedLine > 0) { Menu->SelectedLine--; } else { InfoBoxOnScreen = 0; (void) delwin(MenuWin); (void) touchwin(stdscr); return -2; } if (InfoBoxOnScreen) { ShowMenu(Menu, MenuWin); (void) wrefresh(MenuWin); goto key_info; /* sorry */ } break; case 'j': case KEY_DOWN: if (Menu->SelectedLine + 1 < Menu->HowManyItems) { Menu->SelectedLine++; if (Menu->SelectedLine - Menu->TopLineOnScreen >= COLS - 4) { Menu->TopLineOnScreen++; } } else { (void) beep(); } if (InfoBoxOnScreen) { ShowMenu(Menu, MenuWin); (void) wrefresh(MenuWin); goto key_info; /* sorry */ } break; case '$': /* last item */ Menu->SelectedLine = Menu->HowManyItems - 1; break; case 'h': case KEY_LEFT: (void) delwin(MenuWin); (void) touchwin(stdscr); return KEY_LEFT; case 'l': case KEY_RIGHT: (void) delwin(MenuWin); (void) touchwin(stdscr); return KEY_RIGHT; default: (void) beep(); break; } } } /* FindMenuWidth() returns the length of the longest item in the menu. */ int FindMenuWidth(Menu) t_Menu *Menu; { int line; if (Menu->Width <= 0) { /* Include the width of the name of the menu. I am not sure * whether this is right, but it looks a bit odd if the menu * box is narrower than the highlighted menu name on the Bar... */ if (Menu->NameLength <= 0) Menu->NameLength = strlen(Menu->Name); Menu->Width = Menu->NameLength; /* Now see if any of the items are wider... */ for (line = 0; line < Menu->HowManyItems; line++) { /* Individual length entries... */ if (Menu->Items[line].NameLength == 0) { Menu->Items[line].NameLength = strlen(Menu->Items[line].Name); } /* Overall widest... */ if (Menu->Items[line].NameLength > Menu->Width) { Menu->Width = Menu->Items[line].NameLength; } } } return Menu->Width; } ShowMenu(Menu, MenuWin) t_Menu *Menu; WINDOW *MenuWin; { int line; int pos = 0; register int i; /* It is possible to compile this code so that the menu bar-line is * broken by the top item in a selected menu. See the comments at * the start of this file, and in menu.h */ #ifdef SAVEMENULINE /* draw the box by hand, because the top line is a little special... * SVR3V2 has a line drawing function mvwhline(), but it is very * V.3.2 specific. Sigh. */ for (i = 1; i <= Menu->Width + 2; i++) { (void) mvwaddch(MenuWin, 0, i, ACS_HLINE); } /* NOTE: * the ACS_trbl characters are PC/vt100-style line characters, * where the t,r,b and l can either be B for Blank or S for single; * later versions of curses may also have D for double, etc.; * hence a character with single lines joining left and top would * be ACS_SBBS. with single lines. If you have double lines, it * might be good to use them for the menu bar, so as to give * Tools Pain *Where* When Whom * ============+=======+================= * | Hands | * | Feet | * | etc. | * +-------+ */ /* T-pieces to join the menuline to the verticals of the menu */ (void) mvwaddch(MenuWin, 0, 0, ACS_BSSS); /* The +3 in the next call to mvwaddch() comes from * +2 for the box and space on the left * +2 for the box and space on the right * -1 because things start at 0 */ (void) mvwaddch(MenuWin, 0, Menu->Width + 3, ACS_BSSS); #else /* top line gets overwritten... just add corners! */ (void) mvwaddch(MenuWin, 0, 0, ACS_BBSS); /* See above for the +3 */ (void) mvwaddch(MenuWin, 0, Menu->Width + 3, ACS_BSSB); #endif /*SAVEMENULINE*/ if (Menu->TopLineOnScreen > Menu->HowManyItems) { Menu->TopLineOnScreen = Menu->HowManyItems - (LINES - 3); (void) touchwin(stdscr); (void) wnoutrefresh(stdscr); } if (Menu->SelectedLine < Menu->TopLineOnScreen) { Menu->TopLineOnScreen = Menu->SelectedLine; } else if (Menu->SelectedLine - Menu->TopLineOnScreen > LINES - 4) { Menu->TopLineOnScreen = Menu->SelectedLine - (LINES - 4); } /* Final sanity check */ if (Menu->TopLineOnScreen < 0) { Menu->TopLineOnScreen = 0; } /* Now the lines of the menu, one at a time */ for (line = 0; line < LINES; line++) { char *p; int w; if (line + VSO >= Menu->HowManyItems) break; p = ((Menu->Items)[line + VSO]).Name; /* A neat horizontal line on the left */ if (line+BL > 0) { (void) mvwaddch(MenuWin, line+BL, 0, ACS_VLINE); } (void) mvwaddch(MenuWin, line+BL, 1, (chtype) ' '); /* line is {1..N}, Selected is {0..N-1}. This simplifies * its use elsewhere, at the expense of making this routine * a little more complex. */ if (line + VSO == Menu->SelectedLine) { (void) wattrset(MenuWin, A_STANDOUT); } /* On terminals without standout mode, one could use stars: * Tools Pain *Where* When Whom * =============+=======+================= * | Hands | * |*Feet**| * | etc. | * +-------+ * I wonder how I can tell if standout mode is working? */ /* Add the actual menu item */ (void) mvwaddstr(MenuWin, line+BL, 2, p); /* check how long it was */ w = (Menu->Items)[line + VSO].NameLength; /* Add spaces where to pad it out * These might be inverse video spaces. Of course, not all * terminals make stand-out spaces different from * ordinary ones. On really dumb ttys, could use '*' here * instead of ' '. */ for (i = w; i <= Menu->Width; i++) { (void) mvwaddch(MenuWin, line+BL, i + 2, (chtype)' '); } /* Turn bold/inverse off */ if (line + VSO == Menu->SelectedLine) { (void) wattrset(MenuWin, 0); } /* The extra space is really defense against magic cookies. * See terminfo/termcap manuals... */ (void) mvwaddch(MenuWin, line+BL, i + 1, (chtype) ' '); /* And a neat vertical line on the right. */ if (line+BL > 0) { (void) mvwaddch(MenuWin, line+BL, i + 2, (chtype) ACS_VLINE); } } /* draw a line along the bottom only if it all fitted */ if (Menu->HowManyItems - Menu->TopLineOnScreen <= LINES - 2) { for (pos = i + 1; pos > 0; pos--) { (void) mvwaddch(MenuWin, line+BL, pos, (chtype) ACS_HLINE); } /* Lower left and lower right corner pieces join up neatly; * Curses uses a '+' sign if it or the tty is too old... * But the vt100-style character looks much better. */ (void) mvwaddch(MenuWin, line+BL, 0, ACS_LLCORNER); (void) mvwaddch(MenuWin, line+BL, i + 2, ACS_LRCORNER); } } int ShowInfo(String, MenuWin, Itlx, Itly) char *String; WINDOW *MenuWin; int Itlx; int Itly; /* Itlx and Itly control the place at which the pop-up window * pops up... the top left corner of the new window is at * (Itlx, Itly). By default, this is as near to the selected * item as possible. * So, down a bit and to the right a little. */ { int RetVal = 0; int ch; t_StringBox *StringBox, *MakeStringBox(); /* If there is no information given, say so! */ if (String == (char *) 0) { String = "No more information"; } /* Call a function to calculate the derived parameters, create the * window and put the text on it! */ StringBox = MakeStringBox("Info", String, 0L); if (Itlx < 0) Itlx = 0; if (Itly < 0) Itly = 0; StringBox->tlx = Itlx; StringBox->tly = Itly; /* Try to ensure that as much of the box is on screen as possible; * if the user wants to move it off-screen, that's up to it, but * we can at least ensure that it starts off OK! */ if (StringBox->tlx + StringBox->Width >= COLS) { if ((StringBox->tlx = COLS - StringBox->Width) < 0) { StringBox->tlx = 0; } } if (StringBox->tly + StringBox->Height >= LINES - 3) { if ((StringBox->tly = LINES - StringBox->Height) < MENUTOP) { /* MENUTOP is defined in internal.h to be the first row * on the screen below the menu bar. */ StringBox->tly = MENUTOP; } } do { /* Add the text. UseBold is set after the user asks for more help. * As there isn't any more, perhaps he/she hasn't noticed the info * box, so we highlight it. * (this feature seems to have been deleted -- Lee) */ (void) touchwin(stdscr); (void) wnoutrefresh(stdscr); if (MenuWin) (void) touchwin(MenuWin); if (MenuWin) (void) wnoutrefresh(MenuWin); ShowStringBox(StringBox); (void) touchwin(StringBox->Window); /* now we're on top... */ (void) wrefresh(StringBox->Window); ch = getch(); case_place: /* ComeFrom lower down, resize/move/scroll */ if (ch == '\r' || ch == '\n') ch = ' '; switch (ch) { case '?': case 'x': case 'X': /* x for explain */ case 'i': case 'I': /* i for info */ case KEY_HELP: (void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'X'); (void) wnoutrefresh(StringBox->Window); { char *p = "Info window:\n\ Press SPACE or Q when you are done.\n\ You can Resize the window with R,\n\ Scroll the text inside it with S,\n\ and Move it with M.\n\ HOME will first home the text, and\n\ then put the window near the top\n\ left hand corner of the screen.\n\ \n\ [press SPACE to continue]"; ch = ShowInfo(p, StringBox->Window, StringBox->tly + 2, StringBox->tlx + 2); if (ch == ' ') ch = 'x'; else goto case_place; /* sigh */ } break; case 'm': /* Move window */ case 'M': ch = MoveStringBox(StringBox, MenuWin); if (ch == ' ') ch = 'x'; else goto case_place; break; case 'r': case 'R': /* Resize */ ch = ResizeStringBox(StringBox, MenuWin); if (ch == ' ') ch = 'x'; else goto case_place; break; case 's': case 'S': /* Scroll */ ch = ScrollStringBox(StringBox, MenuWin); if (ch == ' ') ch = 'x'; else goto case_place; break; case KEY_LEFT: case 'h': RetVal = KEY_LEFT; ch = ' '; break; case 'H': case KEY_HOME: RetVal = KEY_HOME; ch = ' '; break; case KEY_RIGHT: case 'l': RetVal = KEY_RIGHT; ch = ' '; break; case 'k': case KEY_UP: RetVal = KEY_UP; ch = ' '; break; case 'j': case KEY_DOWN: RetVal = KEY_DOWN; ch = ' '; break; default: (void) beep(); break; case 'q': case 'Q': /* quit */ case 'f': case 'F': /* finish */ ch = ' '; case ' ': RetVal = ' '; break; } } while (ch != EOF && ch != ' '); /* Clean Up */ (void) delwin(StringBox->Window); (void) free((char *) StringBox); (void) touchwin(stdscr); return RetVal; }