/* Functions to deal with StringBoxes... * * $Header: /usr/home/liam/src/lq-text1.17/src/menu/RCS/stringbox.c,v 1.3 2001/05/31 03:50:13 liam Exp $ * * $Log: stringbox.c,v $ * Revision 1.3 2001/05/31 03:50:13 liam * for release 1.17 * * Revision 1.2 90/10/04 16:28:22 lee * SysV compat improved. * * Revision 1.1 90/08/29 21:50:55 lee * Initial revision * * Revision 2.1 89/08/07 13:50:19 lee * First fully working (V.3.2 only) release; * this is the baseline for future development. * * */ #include "globals.h" #include "error.h" #ifdef HAVE_STDLIB_H # include #else # include #endif #ifdef ultrix # include #else # include #endif #ifndef A_STANDOUT # include "oldcurses.h" #endif #include "menu.h" #include "internal.h" void SetStringBoxSize(); /* SetStringSize() -- * Set *Height and *Width to the minimum dimensions which will * hold the given String. * String can contain embedded newlines and tabs. */ void SetStringSize(String, Height, Width) char *String; short *Height, *Width; { int ThisLineWidth = 0; register char *p; *Height = 0; *Width = 0; if (!String || !*String) return; for (p = String; *p; p++) { if (*p == '\n') { if (ThisLineWidth > *Width) { *Width = ThisLineWidth; } ThisLineWidth = 0; ++*Height; } else { ThisLineWidth++; /* Tabs always cause motion */ if (*p == '\t') { ThisLineWidth |= 7; } } } if (ThisLineWidth > 0) { /* No trailing newline, so treat the partial line at the * end as if it were complete. */ if (ThisLineWidth > *Width) { *Width = ThisLineWidth; } ++*Height; } } /* PutStringInBox is a replacement for waddstr() that treats * newlines specially in order to facilitate scrolling. * The string * "I am a\nhappy boy" * (assuming \n is actually a newline character) * gets put into the window as * +----------- * | I am a * | happy boy * (assuming that the window is large enough). * If HScrollPos were 3, and VScrollPos 1, you would get * +----------- * | py boy */ /* MakeStringBox() is called to create a StringBox. */ t_StringBox * MakeStringBox(Name, String, Flags) unsigned long Flags; char *Name; char *String; { t_StringBox *StringBox; if ((StringBox = new(t_StringBox)) == (t_StringBox *) 0) { error(ERR_FATAL|ERR_INTERNAL, "Not enough memory to create StringBox"); /*NOTREACHED*/ } StringBox->Name = Name; /** StringBox->Flags = Flags;**/ StringBox->Window = (WINDOW *) 0; StringBox->tlx = StringBox->tly = 0; StringBox->HScrollPos = StringBox->VScrollPos = 0; StringBox->String = String; SetStringBoxSize(StringBox); return StringBox; } /* Put the string box on the screen */ ShowStringBox(StringBox) t_StringBox *StringBox; { register char *pp; char *String = StringBox->String; register int x = 1; /* start at (1, 1) */ int y = 1; int AtStartOfLine = 1; if (StringBox->Window == (WINDOW *) 0) { if ((StringBox->Window = newwin(StringBox->Height, StringBox->Width, StringBox->tly, StringBox->tlx)) == (WINDOW *) 0) { error(ERR_FATAL|ERR_INTERNAL, "Not enough memory for StringBox window %dx%d", StringBox->Height, StringBox->Width); } } /* Now we have a window of the right size in the right place... */ /* If vertical scrolling is in use, check it's in range */ if (StringBox->VScrollPos < 0) { StringBox->VScrollPos = 0; } else if (StringBox->VScrollPos >= StringBox->HowManyLines) { StringBox->VScrollPos = StringBox->HowManyLines - 1; } /* Ignore the first VScrollPos lines: */ String = StringBox->String; for (y = 0; y < StringBox->VScrollPos; y++) { extern char *strchr(); String = strchr(String, '\n'); /*CANTHAPPEN*/ if (*++String == '\0') { /* Not enough lines in the buffer.... */ return; } } y = 1; /* start the text on row 1, after the box */ AtStartOfLine = 1; for (pp = String; *pp; pp++) { if (AtStartOfLine) { int i; /* skip the initial portion of each line */ for (i = 0; i < StringBox->HScrollPos; i++) { if (!*pp) { pp--; break; } else if (*pp == '\n') { break; } pp++; } } AtStartOfLine = (*pp == '\n'); if (*pp == '\n') { /* end of line.... */ (void) wmove(StringBox->Window, y, x); (void) wclrtoeol(StringBox->Window); y++; x = 1; (void) wmove(StringBox->Window, y, x); (void) wclrtoeol(StringBox->Window); } else { /* not a newline */ if (*pp == '\t') { x |= 7; x++; /* tabs always move. Also, we started * at position 1, so increment after the x |= 7. */ } else { (void) mvwaddch(StringBox->Window, y, x, (chtype) *pp); x++; } } } /* end for */ (void) wclrtoeol(StringBox->Window); (void) wattrset(StringBox->Window, 0); /* do the box second, in case the text over-ran */ (void) box(StringBox->Window, 0, 0); if (StringBox->HScrollPos > 0) { /* Put a symbol like | to show that the message can * be scrolled to --+ the right by pressing the right- * arrow or "l". | */ (void) mvwaddch(StringBox->Window, 1, 0, ACS_SBSS); } if (StringBox->StringWidth - StringBox->HScrollPos > StringBox->Width - 2) { /* Put a symbol like | to show that the message can * be scrolled to +-- the left by pressing the left- * arrow or "h". | Also, the HOME key will move the * text to the top left of the InfoWin, and put the window * near the top left of the screen. */ /* (the -1 is because numbering starts form zero) */ (void) mvwaddch(StringBox->Window, 1, StringBox->Width - 1, ACS_SSSB); } /* If you can go up... */ if (StringBox->VScrollPos > 0) { /* ACS_trbl */ (void) mvwaddch(StringBox->Window, 0, 1, ACS_SSBS); } /* And if you can go down (the -2 is for the box): */ if (StringBox->HowManyLines > StringBox->VScrollPos + StringBox->Height - 2) { (void) mvwaddch(StringBox->Window, StringBox->Height - 1, 1, ACS_BSSS); } } ResizeStringBox(StringBox, MenuWin) t_StringBox *StringBox; WINDOW *MenuWin; { int ch; int Changed = 0; do { if (Changed) { /* Recreate the window... */ (void) delwin(StringBox->Window); StringBox->Window = (WINDOW *) 0; ShowStringBox(StringBox); (void) touchwin(stdscr); (void) wnoutrefresh(stdscr); if (MenuWin) (void) touchwin(MenuWin); if (MenuWin) (void) wnoutrefresh(MenuWin); (void) touchwin(StringBox->Window); } Changed = 0; (void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'R'); (void) wmove(StringBox->Window, StringBox->Height, StringBox->Width); (void) wrefresh(StringBox->Window); ch = getch(); switch(ch) { case 'q': case 'Q': case 'f': case 'F': ch = ' '; case ' ': break; case '?': case 'x': case 'X': case 'i': case 'I': case KEY_HELP: { char *p = "Resize window: use the arrow keys\n\ to move the window about. You can\n\ use HOME, H, or the d key to set\n\ the window to the default size.\n\ Press SPACE when you are done."; ch = ShowInfo(p, StringBox->Window, StringBox->tly + 2, StringBox->tlx + 2); if (ch == ' ') ch = 'x'; else continue; } break; case KEY_LEFT: case 'h': if (StringBox->Width > 3) { StringBox->Width--; Changed = 1; } else { (void) beep(); } break; case KEY_RIGHT: case 'l': if (StringBox->Width + StringBox->tlx < COLS - 1) { StringBox->Width++; Changed = 1; } else { (void) beep(); } break; case KEY_DOWN: case 'j': if (StringBox->Height + StringBox->tly < LINES - 1) { StringBox->Height++; Changed = 1; } else { (void) beep(); } break; case KEY_UP: case 'k': if (StringBox->Height > 3) { StringBox->Height--; Changed = 1; } else { (void) beep(); } break; case KEY_HOME: case 'H': case 'd': case 'D': /* special case: restore to default size; may * also move the box, if it wouldn't fit on the screen in * the new size. */ SetStringBoxSize(StringBox); Changed = 1; break; case 's': case'S': case 'm': case 'M': /* Sideways jump to other window functions... */ return ch; default: (void) beep(); break; } } while (ch != EOF && ch != ' '); return ch; } MoveStringBox(StringBox, MenuWin) t_StringBox *StringBox; WINDOW *MenuWin; { int ch; int Changed = 0; do { if (Changed) { /* Recreate the window... */ (void) delwin(StringBox->Window); StringBox->Window = (WINDOW *) 0; (void) touchwin(stdscr); (void) wnoutrefresh(stdscr); if (MenuWin) (void) touchwin(MenuWin); if (MenuWin) (void) wnoutrefresh(MenuWin); ShowStringBox(StringBox); } Changed = 0; (void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'M'); (void) wmove(StringBox->Window, StringBox->Height, StringBox->Width); (void) wrefresh(StringBox->Window); ch = getch(); switch(ch) { case 'q': case 'Q': case 'f': case 'F': ch = ' '; case ' ': break; 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 = "Scrolling Text:\n\ Use the arrow keys (or h j k and l)\n\ to move the window around.\n\ You can use the HOME key (or H) to\n\ move it near the top left hand corner\n\ of the screen.\n\ When you finish moving the window,\n\ or if you want to resize it,\n\ press SPACE.\n\ After you have finished, you can\n\ use R to resize the window,\n\ and S to scroll the text in it.\n\ \n\ [press SPACE to continue]"; ch = ShowInfo(p, StringBox->Window, StringBox->tly + 2, StringBox->tlx + 2); if (ch == ' ') ch = 'x'; else continue; } break; case 'h': case KEY_LEFT: if (StringBox->tlx > 0) { StringBox->tlx--; (void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx); Changed = 1; } else { (void) beep(); } break; case 'l': case KEY_RIGHT: if (StringBox->tlx + StringBox->Width < COLS) { StringBox->tlx++; (void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx); Changed = 1; } else { (void) beep(); } break; case 'k': case KEY_UP: if (StringBox->tly > 2) { --StringBox->tly; (void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx); Changed = 1; } else { (void) beep(); } break; case 'j': case KEY_DOWN: if (StringBox->tly + StringBox->Height < COLS - 1) { ++StringBox->tly; (void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx); Changed = 1; } else { (void) beep(); } break; case 'H': case KEY_HOME: StringBox->tlx = 0; StringBox->tly = 2; (void) mvwin(StringBox->Window, StringBox->tly, StringBox->tlx); Changed = 1; break; case 's': case'S': case 'r': case 'R': /* Jump to other functions */ return ch; default: (void) beep(); } } while (ch != EOF && ch != ' '); return ch; } ScrollStringBox(StringBox, MenuWin) t_StringBox *StringBox; WINDOW *MenuWin; { int ch; int Changed = 0; do { if (Changed) { /* Recreate the window... */ (void) delwin(StringBox->Window); StringBox->Window = (WINDOW *) 0; (void) touchwin(stdscr); (void) wnoutrefresh(stdscr); if (MenuWin) (void) touchwin(MenuWin); if (MenuWin) (void) wnoutrefresh(MenuWin); ShowStringBox(StringBox); } Changed = 0; (void) mvwaddch(StringBox->Window, 0, 0, (chtype) 'S'); (void) wmove(StringBox->Window, StringBox->Height, StringBox->Width); (void) wrefresh(StringBox->Window); ch = getch(); switch(ch) { case 'q': case 'Q': case 'f': case 'F': ch = ' '; case ' ': break; 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 = "Scrolling Text:\n\ Use the arrow keys (or h j k and l)\n\ to move the text within the window.\n\ You can use the HOME key (or H) to\n\ move to the start of the text.\n\ When you finish scrolling the text,\n\ or if you want to move or resize the\n\ window, press SPACE.\n\ \n\ [press SPACE to continue]"; ch = ShowInfo(p, StringBox->Window, StringBox->tly + 2, StringBox->tlx + 2); if (ch == ' ') ch = 'x'; else continue; } break; case 'h': case KEY_LEFT: /* Press |space to continue. | * <-----------------------> DescLen * <-------------------> InfoWidth * <------> StringBox->HScrollPos * (Not allowed to go further left than this) */ if (StringBox->HScrollPos > 0) { StringBox->HScrollPos--; Changed = 1; } else { (void) beep(); } break; case 'l': case KEY_RIGHT: if (StringBox->StringWidth - StringBox->HScrollPos > StringBox->Width - 2) { StringBox->HScrollPos++; Changed = 1; } else { beep(); } break; case 'k': case KEY_UP: if (StringBox->VScrollPos) { StringBox->VScrollPos--; Changed = 1; } else { (void) beep(); } break; case 'j': case KEY_DOWN: /* forbid the last line of the screen so as to avoid scrolling/ * bottom-right-corner hassle, and also to avoid obscuring * soft function labels if they're there. */ if (StringBox->Height - 2 + StringBox->VScrollPos < StringBox->HowManyLines) { StringBox->VScrollPos++; Changed = 1; } else { (void) beep(); } break; case 'H': case KEY_HOME: /* Not testing for > 0 here means that if things get really * screwed up and {H,V}ScrollPos gets -ve, HOME will set * things right again. Of course, the user won't know * that, but every little helps... */ if (StringBox->HScrollPos || StringBox->VScrollPos) { StringBox->HScrollPos = 0; StringBox->VScrollPos = 0; Changed = 1; } break; case 'r': case 'R': case 'm': case 'M': /* Jump to other functions: */ return ch; default: (void) beep(); } } while (ch != EOF && ch != ' '); return ch; } void SetStringBoxSize(StringBox) t_StringBox *StringBox; { /* Measure the text */ SetStringSize(StringBox->String, &(StringBox->Height), &(StringBox->Width)); StringBox->HowManyLines = StringBox->Height; StringBox->StringWidth = StringBox->Width; /* Leave room for a box */ StringBox->Height += 2; StringBox->Width += 2; /* Check it's not too wide */ if (StringBox->Width >= COLS) { StringBox->Width = COLS - 4; } /* Check it's not too tall */ if (StringBox->Height >= LINES - 4) { StringBox->Height = LINES - 4; } /* check it's not too thin */ if (StringBox->Width < 3) { /* tiny screen, or no text at all... */ StringBox->Width = 3; } /* Check it's not too short */ if (StringBox->Height < 3) { /* tiny screen, or no text at all... */ StringBox->Height = 3; } /* Check it's on the screen */ if (StringBox->tlx + StringBox->Width >= COLS) { /* right adjust if it didn't fit */ StringBox->tlx = (COLS - StringBox->Width) - 1; /* ASSERT: StringBox->tlx > 0 because StringBox->Width < COLS */ } if (StringBox->tly + StringBox->Height >= LINES - 3) { /* avoid the bottom line of the screen */ StringBox->tly = (LINES - 3) - StringBox->Height; } } char * AskForString(Message, MaxLen, MenuWin, Itlx, Itly) char *Message; int MaxLen; WINDOW *MenuWin; int Itlx; int Itly; { t_StringBox *Question; char *Answer; char *p; char *Text; int i; int ch; /* ensure that we allow at least 1 char of input! */ if (MaxLen < 1) MaxLen = 1; if ((Text = malloc(strlen(Message) + MaxLen + 5)) == (char *) 0) { error(ERR_FATAL|ERR_MEMORY, "Not enough mem for question \"%s\"\n", Message); } (void) sprintf(Text, "%s\n : ", Message); p = Answer = &Text[strlen(Text)]; /* i.e. pointing to the \0 */ /* Make a StringBox containing the question and a space for * the answer */ Question = MakeStringBox("Question", Text, (unsigned long) 0); /* add space for the answer... */ if (Question->Width < 5) Question->Width = 5; if (Itlx < 0) Itlx = 0; if (Itly < 3) Itly = 3; /* avoid the menu bar */ Question->tlx = Itlx; Question->tly = 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. */ /* display the StringBox and wait for an answer. If necessary, * extend and then scroll the StringBox to make the answer fit */ do { for (i = 0; i - Question->HScrollPos < Question->Width - 2 && i < MaxLen; i++) { if (&Answer[i] >= p) { Answer[i] = '_'; Answer[i + 1] = '\0'; } } (void) touchwin(stdscr); (void) wnoutrefresh(stdscr); if (MenuWin) { (void) touchwin(MenuWin); (void) wnoutrefresh(MenuWin); } #if 0 /* but why commented out? */ SetStringBoxSize(Question); /* in case the text has changed */ #endif ShowStringBox(Question); (void) touchwin(Question->Window); /* now we're on top... */ /* the +7 is for the bar on the left of the window, the " : ", * and an extra one to put the cursor where the *next* character * will be, plus an unexplained fudge factor. */ (void) move(Question->tly + Question->Height - 2, Question->tlx + (p - Answer) - Question->HScrollPos + 4); (void) wrefresh(Question->Window); (void) move(Question->tly + Question->Height - 2, Question->tlx + (p - Answer) - Question->HScrollPos + 4); ch = getch(); if (ch == KEY_F(1) || ch == '\r' || ch == '\n') break; /* Note: p always points to the point at which the next character * to be typed would go, and *p is \0. */ switch (ch) { case KEY_UP: case KEY_LEFT: case KEY_DOWN: case KEY_RIGHT: (void) MoveStringBox(Question, MenuWin); break; case KEY_HOME: /* Resize */ (void) ResizeStringBox(Question, MenuWin); break; case '\b': case 127: case 255: case -1: if (p > Answer) { *p = '\0'; --p; if (Question->HScrollPos > 0) --Question->HScrollPos; } else { beep(); } break; case 'W' ^ 64: /* copntrol-W -- delete word */ *p = '_'; while (p > Answer && (*p == ' ' || *p == '/')) { p--; if (Question->HScrollPos > 0) --Question->HScrollPos; } while (p > Answer && *p != ' ' && *p != '/') { p--; if (Question->HScrollPos > 0) --Question->HScrollPos; } if (*p == '/' || *p == ' ') p++; *p = '\0'; break; case 'U' ^ 64: case 'X' ^ 64: p = Answer; *p = '\0'; Question->HScrollPos = 0; break; default: if (p - Answer >= MaxLen) { beep(); } else { *p++ = ch; /* The magic number on the next line works as follows: * | : here is me typing stuff__ | * ^^^^ ^^ * So there are 6 characters, plus one for the cursor; * (p - Answer) gives one fewer than needed, so we arrive * at 8. */ if (p - Answer + 7 - Question->HScrollPos >= Question->Width) { Question->HScrollPos += 4; } } } } while (ch != EOF && ch != KEY_F(1)); /* Clean Up */ (void) delwin(Question->Window); (void) touchwin(stdscr); if (p > Answer) { *p = '\0'; --p; } if (p == Answer) { (void) free(Text); return (char *) 0; } else { unsigned len = strlen(Answer); (void) bcopy(Answer, Text, len + 1); Text[len] = '\0'; return realloc(Text, len + 1); } /*NOTREACHED*/ }