// -*- C++ -*- // $RCSfile: menu.C,v $ // $Revision: 1.18 $ // $Author: langer $ // $Date: 2003/08/19 15:18:43 $ /* This software was produced by NIST, an agency of the U.S. government, * and by statute is not subject to copyright in the United States. * Recipients of this software assume all responsibilities associated * with its operation, modification and maintenance. However, to * facilitate maintenance we ask that before distributing modifed * versions of this software, you first contact the authors at * oof_manager@ctcms.nist.gov. */ // Detailed instructions are in README. #include "charstring.h" #include "fatalerror.h" #ifdef NEED_FDSTREAM #include "fdstream.hpp" #endif #include "menuDef.h" #include "menucolor.h" #include "menuform.h" #include "specialchars.h" #include "truefalse.h" #include "word.h" #include #include "stdlib.h" #include "ctype.h" #ifdef HAVE_MALLOC_H #include #endif // HAVE_MALLOC_H #include #include #include static Waiter *currentwaiter = 0; // Keep track of which waiter is in control. #ifdef MENUGFX static int using_graphics = 0; // set in FormWaiter constructor, never unset. #endif // MENUGFX void Waiter::makecurrent() { currentwaiter = this; } Waiter *garcon() { Waiter *w = currentwaiter; if(!w) // make a temporary Waiter w = new TextWaiter; return w; } int Waiter::graphics() { #ifdef MENUGFX return using_graphics; #else return 0; #endif // MENUGFX } // ------------- TextWaiter Execution routines -------------------------// void Menu::execute(const CharString &input, TextWaiter& w) { int cnum = getcnum(input, option); if(cnum < 0) { w.saywhat(w.cmdlist.concat() + " " + input /*input*/); w.trashline(); return; } w.cmdlist += option[cnum]->name ; w.currentmenu = this; option[cnum]->execute(w); w.cmdlist.strip(1); return; } void SubMenu::execute(TextWaiter& w) { w.order1(menu); } void Command0::execute(TextWaiter &w) { pronto(w, w.cmdlist); } void CommandS::execute(TextWaiter &w) { pronto(w, w.cmdlist); } /* CommandM's are executed by this sequence of functions, so that * TextWaiters and ScriptWaiters behave slightly differerently. * Waiter::do_cmdm calls CommandM::koko on either the original * command (TW), or a copy (SW). koko sets arguments and then * calls pronto, which really calls the command. koko is distinct * from pronto because pronto is used elsewhere to run the command * without setting arguments. * When defining a macro, however, always use a copy of the command. */ void CommandM::execute(TextWaiter &w) { w.do_cmdm(this); } void TextWaiter::do_cmdm(CommandM *cmd) { cmd->koko(*this, macromode); } void ScriptWaiter::do_cmdm(CommandM *cmd) { cmd->koko(*this, 1); } void CommandM::koko(TextWaiter &w, int copy) { // the lord high executioner if(!copy) { // execute command itself if(w.setargs(arg)) pronto(w, w.cmdlist); else w.msout << ms_error << "=== Command \"" << name << "\" not executed." << endl << ms_normal; } else { // make a copy and execute it CommandM *dup(duplicate()); if(w.setargs(dup->arg)) dup->pronto(w, w.cmdlist); else w.msout << ms_error << "=== Command \"" << name << "\" not executed." << endl << ms_normal; delete dup; } } CommandM *CommandM::duplicate() const { CommandM *newcmd = clone(); newcmd->ismaster = 0; newcmd->clonelist = clonelist; clonelist->grow(1, newcmd); return newcmd; } // -------------- Menu assembly routines -------------------------- // const MenuAttribute MOPT_NORMAL(0); const MenuAttribute MOPT_NOLOG(1); const MenuAttribute MOPT_SECRET(2); const MenuAttribute MOPT_NOPRINT(4); Menu::Menu(const CharString &prmpt) : prompt(prmpt), option(0) {} Menu::~Menu() {} // Add an already constructed generic option to a menu void Menu::AddOption(MenuOption* newopt) { int duplicated = 0; // does this option duplicate a previous one? for(int i=0; iname == newopt->name) { delete option[i]; option[i] = newopt; duplicated = 1; } } if(!duplicated) option.grow(1, newopt); newopt->menu = this; update_forms(); } // Construct an empty menu named "nm" and add it to the menu. Menu* Menu::AddSubMenu(const CharString &nm, const CharString &help, const CharString &prmpt, const MenuAttribute &att) { // create the option in this menu SubMenu* newoption = new SubMenu(nm, help, att); // create the submenu in the option. Menu* newsubmenu = new Menu(prmpt == ""? nm : prmpt); newoption->menu = newsubmenu; AddOption(newoption); // store the option in this menu return newsubmenu; // return pointer to new submenu } // Add an existing menu to the menu as a submenu Menu* Menu::AddSubMenu(const CharString &nm, const CharString &help, Menu *submenu, const MenuAttribute &att) { SubMenu* newoption = new SubMenu(nm, help, att); newoption->menu = submenu; AddOption(newoption); return submenu; } MenuOption::MenuOption(const CharString &nm, const CharString &hs, char t, const MenuAttribute &att) : name(nm), helpstring(hs), tag(t), active(0), highlighted(0), drawactive(0), menu(0), attribute(att) {} void Menu::AddCommand(const CharString &nm, const CharString &help, CommandFn (*func)(), const MenuAttribute &att) { Command0 *newcommand = new Command0(nm, help, func, att); AddOption(newcommand); } // creates a CommandM derived command void Menu::AddCommand(Command *cmd, const MenuAttribute &att) { cmd->attribute = cmd->attribute | att; AddOption(cmd); } void Menu::RemoveOption(const CharString &nm) { for(int i=0; iname) { if(!option[i]->active) delete option[i]; // get rid of space in the option list for(int j=i+1; jmsout << ms_error << "=== Failed attempt to remove option " << nm << " from Menu " << prompt << endl << ms_normal; } void Menu::RemoveOption(const MenuOption &opt) { RemoveOption(opt.name); } bool Menu::RenameOption(const CharString &oldname, const CharString &newname) { int which = -1; for(int i=0; iname) return 0; // don't allow conflicting names if(oldname == option[i]->name) which = i; } if(which > -1) { option[which]->rename(newname); update_forms(); return 1; } return 0; } // --------------------------------------------------------------- // // Find entry in menu corresponding to input word // this isn't a Menu method anymore, so that it can be used // on any list of MenuOptions, particularly CommandM::arg. // should be templated, but template functions can't be friends int getcnum(const CharString &word, const Vec &option) { if(!word) return -1; // error int nmatches = 0; // number of items matching word int lastmatch; int i; // Look for matches up to the length of the input word. for(i=0; iname, strlen(word))) { lastmatch = i; nmatches++; } } if(nmatches == 1) return lastmatch; // Just one match if(nmatches == 0) return -1; // No match // Resolve multiple matches by looking for exact one for(i=0; iname)) return i; } return -1; // No exact match } int getcnum(const CharString &word, const Vec &option) { if(!word) return -1; // error int nmatches = 0; // number of items matching word int lastmatch; int i; // Look for matches up to the length of the input word. for(i=0; iname, strlen(word))) { lastmatch = i; nmatches++; } } if(nmatches == 1) return lastmatch; // Just one match if(nmatches == 0) return -1; // No match // Resolve multiple matches by looking for exact one for(i=0; iname)) return i; } return -1; // No exact match } // ----------------------------------------------------------------- // void Menu::highlight(const CharString &what) { int cnum = getcnum(what, option); if(cnum < 0) return; option[cnum]->highlighted = 1; update_forms(); } void Menu::highlight_only(const CharString &what) { for(int i=0; ihighlighted = 0; int cnum = getcnum(what, option); if(cnum < 0) return; option[cnum]->highlighted = 1; update_forms(); } void Menu::unhighlight() { for(int i=0; ihighlighted = 0; update_forms(); } void Menu::unhighlight(const CharString &what) { int cnum = getcnum(what, option); if(cnum < 0) return; option[cnum]->highlighted = 0; update_forms(); } // ----------------------------------------------------------------- // void Menu::update_forms() { #ifdef MENUGFX for(int j=0; jupdate(); #endif // MENUGFX } // ----------------------------------------------------------------- // // Routines to maintain stack of commands being executed /* the Waiter path variable is set by pronto() just before calling a * CommandFn. It stores the complete path to the current command. */ #ifdef OLD void TextWaiter::push_cmd(const char* name) { cmdlist.grow(1, strdup(name)); } void TextWaiter::pop_cmd() { free(cmdlist[cmdlist.capacity()-1]); cmdlist.grow(-1); } void TextWaiter::pop_allcmds() { for(int i=0; i= s) return CharString(""); else return path[s-n-1]; } int Waiter::get_depth() const { // count number of words in path return path.size(); } // ------------------- Waiter routines ---------------------------- // Waiter::Waiter(MenuStream &ms) : msout(ms), promptfmt("%s >> "), macrocount(0), macromode(0), quitcmd(0) { makecurrent(); } Waiter::Waiter(MenuStream *ms) : msout(*ms), promptfmt("%s >> "), macrocount(0), macromode(0), quitcmd(0) { makecurrent(); } Waiter::~Waiter() { if(currentwaiter == this) // avoid dangling pointer currentwaiter = 0; } void TextWaiter::twstartup() { inbuf = 0; repeatword = 0; grouplevel = 0; showprompts = TF_TRUE; echo = TF_FALSE; logging_ = TF_FALSE; inhibit_logging_ = 0; eoffound = 0; eolfn = 0; inputstrm_ = 0; } TextWaiter::TextWaiter(bool (*stp)()) : infile(stdin), inputhandler(defaultinputhandler), Waiter(new TextMenuStream), stop(stp) { twstartup(); } TextWaiter::TextWaiter(FILE* ifile, FILE *pfile, bool (*stp)()) : infile(ifile), inputhandler(defaultinputhandler), Waiter(new FileMenuStream(pfile)), stop(stp) { twstartup(); } TextWaiter::TextWaiter(char *(*ih)(const CharString&, MenuStream&, FILE*), bool (*stp)()) : infile(stdin), inputhandler(ih), Waiter(new TextMenuStream), stop(stp) { twstartup(); } TextWaiter::TextWaiter(FILE* ifile, FILE *pfile, char *(*ih)(const CharString&, MenuStream&, FILE*), bool (*stp)()) : infile(ifile), inputhandler(ih), Waiter(new FileMenuStream(pfile)), stop(stp) { twstartup(); } // this version uses an existing output message stream TextWaiter::TextWaiter(MenuStream &ms, FILE* ifile, char *(*ih)(const CharString&, MenuStream&, FILE*), bool (*stp)()) : infile(ifile), inputhandler(ih), Waiter(ms), stop(stp) { twstartup(); } TextWaiter::TextWaiter(MenuStream &ms, FILE* ifile, bool (*stp)()) : infile(ifile), inputhandler(defaultinputhandler), Waiter(ms), stop(stp) { twstartup(); } TextWaiter::~TextWaiter() { if(inputstrm_) delete inputstrm_; } istream *TextWaiter::inputstream() { if(!inputstrm_) { fflush(infile); // synchronizes FILE and istream #ifdef NEED_FDSTREAM inputstrm_ = new boost::fdistream(fileno(infile)); #else inputstrm_ = new ifstream(fileno(infile)); #endif } return inputstrm_; } ScriptWaiter::ScriptWaiter(const CharString &s, MenuStream &ms, bool (*stp)()) : script(s), TextWaiter(ms, stdin, defaultinputhandler, stp), already_read(0) { showprompts = TF_FALSE; // overrides setting in TextWaiter constructor } StreamWaiter::StreamWaiter(MenuStream &ms, istream &is, bool (*stp)()) : TextWaiter(ms, stdin, defaultinputhandler, stp), inputstream_(is) { } #ifdef MENUGFX // BadWindow errors can be generated by clicking on a window that's // about to disappear. This catches and ignores those events. static int (*old_error_handler)(Display*, XErrorEvent*); static int new_error_handler(Display *disp, XErrorEvent *err) { if(err->error_code == BadWindow) { cerr << "X Error (BadWindow) ignored" << endl; return 0; } if(err->error_code == BadDrawable) { cerr << "X Error (BadDrawable) ignored" << endl; return 0; } (*old_error_handler)(disp, err); fatalerror(); return 0; } FormWaiter::FormWaiter(int *argc, char *argv[], XrmOptionDescList options, int noptions) : Waiter(new FormMenuStream()) { echo = TF_TRUE; logging_ = TF_FALSE; inhibit_logging_ = 0; using_graphics = 1; fl_initialize(argc, argv, "XFormMenu", options, noptions); fl_set_app_nomainform(1); setmenucolors(); // SIGALRM is used to handle double clicks. A handler is always // installed so that if the alarm goes off accidentally the program // won't crash. fl_add_signal_callback(SIGALRM, MenuForm::ignorealarm, 0); //old_error_handler = XSetErrorHandler(new_error_handler); } void FormWaiter::graphics_off() { Form::haltall(); } void FormWaiter::graphics_on() { Form::resumeall(); } void FormWaiter::set_cursor(int cursor) { Form::setcursor(cursor); } #endif // MENUGFX /* construct prompt from promptfmt, replacing %s with the given string */ CharString Waiter::makeprompt(const char *str) { char *promptbuf = new char[strlen(promptfmt) + strlen(str) + 1]; const char *f = promptfmt; char *p = promptbuf; while (*f) { if(*f == '%' && *(f+1) == 's') { // check for "%s" const char *s = str; while(*s) *p++ = *s++; // replace "%s" in fmt with str f += 2; } else *p++ = *f++; // copy verbatim from fmt string } *p = '\0'; return CharString(promptbuf); } void Waiter::setprompt(const char *fmt) { promptfmt = CharString(fmt); } /* Take orders and serve commands */ void TextWaiter::order(Menu *menu) { // take orders until eof rootmenu = menu; while(!eof() && (!stop || !(*stop)())) order1(rootmenu); } void TextWaiter::order1(Menu *menu) { // take a single order int startlevel = grouplevel; int repeat = 0; do { CharString input(inputword(menu->prompt)); repeat = 0; if(eoffound || input == "") { // end of file on input eoffound = 1; return; } switch (input[0]) { case BEGINGROUP: grouplevel++; break; case ENDGROUP: grouplevel--; if(grouplevel < 0) { fputs("Too many right parentheses!\n", stderr); grouplevel = 0; } break; case COMMENTCHAR: trashline(); repeat = 1; break; case DELIMITER: repeat = 1; break; case ABORTCHAR: break; case BEGINMACRO: if(macromode) { fputs("Nested macro definitions are not allowed!\n", stderr); trashline(); } else startmacro(menu, cmdlist); break; case ENDMACRO: if(!macromode) { fputs("Too many right braces!\n", stderr); trashline(); } else endmacro(); break; case HELPCHAR: if(input[1] == HELPCHAR) // "??" --> long help help(menu); else // "?" --> short help listoptions(menu); repeat = 1; break; default: menu->execute(input, *this); } } while((repeat || grouplevel > startlevel) && !eoffound); } #ifdef MENUGFX void FormWaiter::order(Menu *menu) { WordList pth; rootmenu = menu; errhm = makeprompt(menu->prompt); rootform = new MenuForm(menu, errhm, pth, ""); rootform->makeroot(this); rootform->show(NULL); for(;;) fl_do_forms(); // doesn't return from here! } void FormWaiter::show_homeform() const { rootform->show(NULL); } #endif // MENUGFX void TextWaiter::set_eolfn(CommandFn (*f)()) { eolfn = f; } // ------------- Help routines -------------------------// // help is like order, but it just prints the help string. // it doesn't allow for parentheses or continue to the next line. // help returns 1 if it finds something to provide help on, 0 otherwise. // The actual help is provided by MenuOption::help() and its variants. int TextWaiter::help(Menu* menu) { CharString input(word()); // get a word from this input line if(input == "" || is_special(input[0])) return 0; return menu->help(input, *this); } // return 1 if help has been provided, 0 otherwise // cf Menu::execute int Menu::help(const CharString &input, TextWaiter& w) const { int cnum = getcnum(input, option); if(cnum < 0) { w.saywhat(input); w.trashline(); return 1; } return option[cnum]->help(w); } int SubMenu::help(TextWaiter& w) const { if(!w.help(menu)) w.msout << ms_info << name << ": " << helpstring << endl << ms_normal; return 1; } int Variable::help(TextWaiter &w) const { w.msout << ms_info << name << " = " << tostring() << ": " << helpstring << endl << ms_normal; return 1; } int MenuOption::help(TextWaiter& w) const { w.msout << ms_info << name << ": " << helpstring << endl << ms_normal; return 1; } int CommandS::help(TextWaiter &w) const { w.msout << ms_info << name << ": macro {" << script << "}" << endl << ms_normal; return 1; } // Print list of commands or error message void Waiter::saywhat(const char* word) { msout << endl << ms_error; msout << "=== Command \"" << word << "\" not understood!" << endl; msout << "=== Use '" << HELPCHAR << "' to list possible commands." << endl; msout << ms_normal; } void TextWaiter::listoptions(const Menu* menu) { msout << ms_info << "=== Type \"" << HELPCHAR << HELPCHAR << " xxx\" to get help on option \"xxx\"." << endl; msout << "=== Choices are (command* menu/ variable=) : " << endl; for(int i=0; ioption.capacity(); i++) { if(!menu->option[i]->attribute(MOPT_SECRET)) { if(menu->option[i]->highlighted) msout << "<<"; msout << menu->option[i]->name << menu->option[i]->tag; if(menu->option[i]->highlighted) msout << ">>"; msout << " "; } } msout << endl << ms_normal; } // -------------- Print routines ----------------------------- // void Menu::print(FILE *file, const TrueFalse &recursive) const { print(file, CharString(""), recursive); } void Menu::print(ostream &os, const TrueFalse &recursive) const { print(os, CharString(""), recursive); } void Menu::print(FILE *file, CharString path, const TrueFalse &recursive) const { for(int i=0; iprint(file, path, recursive); } void Menu::print(ostream &os, CharString path, const TrueFalse &recursive) const { for(int i=0; iprint(os, path, recursive); } void SubMenu::print(FILE *file, CharString path, const TrueFalse &recursive) const { if(!recursive || attribute(MOPT_NOPRINT)) return; menu->print(file, path.spaceappend(name), recursive); } void SubMenu::print(ostream &os, CharString path, const TrueFalse &recursive) const { if(!recursive || attribute(MOPT_NOPRINT)) return; menu->print(os, path.spaceappend(name), recursive); } void Variable::print(FILE *file, CharString path, const TrueFalse&) const { if(attribute(MOPT_NOPRINT)) return; fprintf(file, "%s %s\n", path.charstar(), printstr().charstar()); } void Variable::print(ostream &os, CharString path, const TrueFalse&) const { if(attribute(MOPT_NOPRINT)) return; os << path << " " << printstr() << endl; } // -------------- Waiter Input routines ---------------------- // // Read new non-blank line into buffer. CharString TextWaiter::getline(const CharString &menuprompt) { char *ptr; CharString copy; CharString prompt(showprompts ? makeprompt(menuprompt) : CharString("")); do { ptr = (*inputhandler)(prompt, msout, infile); if(ptr) { copy = CharString(skipspace(ptr)); free(ptr); // inputhandler allocates space } } while(!(eoffound = feof(infile)) && ptr && (copy[0] == '\0' || copy[0] == '\n')); if(eoffound) return CharString(""); return copy; } CharString ScriptWaiter::getline(const CharString &) { if(already_read) return 0; // read only once! already_read = 1; return script; } CharString StreamWaiter::getline(const CharString &menuprompt) { CharString prompt(showprompts? makeprompt(menuprompt) : CharString("")); static int bufsiz = 1024; CharString input(bufsiz); do { inputstream_.getline(input.charstar(), bufsiz, '\n'); //inputstream >> input; } while(!(eoffound = (!inputstream_ || inputstream_.eof())) && (input[0]=='\0' || input[0]=='\n')); if(eoffound) return CharString(""); return input; } char *TextWaiter::defaultinputhandler(const CharString &prompt, MenuStream &pstrm, FILE *ifile) { char line[1024]; pstrm << ms_prompt << prompt << flush << ms_normal; if(fgets(line, sizeof(line), ifile)) return strdup(line); else return 0; } // Return one word from buffer, without prompting CharString TextWaiter::word() { static Word lastword; if(repeatword) { repeatword = 0; return lastword; } if(!inbuf || !*inbuf) return CharString(""); (*inbuf) >> lastword; return lastword; } // Return one word from buffer, prompting for input CharString TextWaiter::inputword(const CharString &menuprompt) { CharString wrd = word(); if(!inbuf || !*inbuf || wrd == "") { // need more input if(inbuf && eolfn) { // Call end-of-line function, but not makecurrent(); // before the first line has been read. If (*eolfn)(); // it's been read, then inbuf != 0. } buffer = getline(menuprompt); // Get another line of input if(buffer == "") return buffer; // signals eof if(inbuf) delete inbuf; inbuf = new istrstream(buffer, strlen(buffer)); return word(); } return wrd; } // Kill rest of input line. void TextWaiter::trashline() { inbuf->clear(ios::badbit|inbuf->rdstate()); } // ----------------- Routines to define macros ---------------// class MacroList { public: Vec defn; Vec name; Vec path; void add(const CharString &n, const WordList &p, const CharString &d) { defn.grow(1, new CharString(d)); name.grow(1, new CharString(n)); path.grow(1, new WordList(p)); } void remove(const CharString &n) { for(int i=0; i namevar("name", "macro name", name); if(getvar(&namevar)) return name; else return CharString(""); } } void Waiter::recorddelim() { // not in menuDef.h just because DELIMITER isn't defined there macrostring += DELIMITER; } #ifdef MENUGFX CharString FormWaiter::getmacroname() { return getstring("Enter macro name", defaultmacroname()); } #endif // MENUGFX CharString Waiter::defaultmacroname() { char namebuf[20]; sprintf(namebuf, "macro%d", macrocount); return CharString(namebuf); } static CharString macrodefstr(const WordList &path, const CharString &defn, const CharString &name) { return path.concat() + " {" + defn + "} " + name; } void Waiter::definemacro(const CharString ¯oname) { if(macroname == "") return; if(is_special(macroname[0])) { msout << ms_error << "=== Macro names can't begin with '" << macroname[0] << "'" << endl << ms_normal; return; } msout << ms_info << "defining macro \"" << macroname << "\"" << endl << ms_normal; if(macrostring == "") { macromenu->RemoveOption(macroname); macrolist.remove(macroname); } else { macrocount++; macromenu->AddCommand(new CommandS(macroname, macrostring, rootmenu)); macrolist.add(macroname, macropath, macrostring); log(macrodefstr(macropath, macrostring, macroname)); } } void Waiter::showmacros(ostream &os) { for(int i=0; i &arglist) { int i; CharString argwd; int nset = 0; // number of args set so far Vec isset(arglist.capacity(), 0); // read arguments ignorechar(COMMA); while(nset < arglist.capacity() && (argwd = argword()) && argwd != "") { int which = getcnum(argwd, arglist); // look up argument name if(which >= 0) { // found arg name Variable *thearg = arglist[which]; argwd = argword(); // get equals sign if(argwd[0] != EQUALS) { msout << ms_error << "=== Badly formed argument list! Missing '='." << endl << ms_normal; trashline(); } else { // argwd[0] == EQUALS thearg->fromstring(*this); if(*inbuf) { // was read correctly if(!isset[which]) { isset[which] = 1; nset ++; } } else { msout << endl << ms_error << "=== Argument \"" << thearg->name << "\" is badly formed." << endl << ms_normal; return 0; } } } else { // not a recognizable argument name msout << endl << ms_error << "=== \"" << argwd << "\" is not a recognizable argument!" << endl << ms_normal; trashline(); return 0; } } noticechar(COMMA); // ask for unset arguments for(i=0; iattribute(MOPT_SECRET)) if(!getvar(arglist[i])) return 0; } return 1; } /* argword returns one command line argument in an allocated buffer * which must be freed. */ CharString TextWaiter::argword() { CharString str(word()); // gets one word or one special character if(str == "") // no input, no more args return 0; if(is_special(str[0])) { if(isignored(str[0])) return argword(); if(str[0] == BEGINGROUP) { // is this right? grouplevel++; return 0; } if(str[0] == ENDGROUP) { grouplevel--; return 0; } if(str[0] == ENDMACRO) { repeatword = 1; // closing brace is processed by order1() next time... return 0; } if(str[0] == DELIMITER) return 0; if(str[0] == COMMENTCHAR) { trashline(); return 0; } if(isterminal(str[0])) // no more args return 0; // return special character CharString spl(2); spl[0] = str[0]; spl[1] = '\0'; return spl; } return str; } // ------------------------------------------------------ // // prompt for and return a string independent of the command stream. CharString TextWaiter::getstring(const CharString &prompt, const CharString &dflt) { msout << ms_prompt << prompt << " [" << dflt << "]: " << flush << ms_normal; char str[200]; fgets(str, sizeof(str), stdin); // get rid of trailing newline char *ptr = strchr(str, '\n'); if(ptr) *ptr = '\0'; // skip leading spaces ptr = str; skipspace(&ptr); if(*ptr == '\0') return dflt; return CharString(str); } // return 1 for yes, 0 for no int TextWaiter::question(const CharString &qstr, int dflt) { CharString answer; for(;;) { msout << ms_prompt << qstr << " [" << (dflt?"yn":"ny") << "] " << flush << ms_normal; cin >> answer; if(answer[0] == 'y') return 1; if(answer[0] == 'n') return 0; if(answer[0] == '\0') return dflt; } } CommandFn Waiter::pause() { // static so it can be put in menus #ifdef MENUGFX if(garcon()->graphics()) { PauseForm::pause(); return; } #endif CharString dummy; garcon()->msout << ms_prompt << "Hit return to continue:" << flush << ms_normal; cin >> dummy; } // ------------------ Variables -----------------------------// // executing a variable means either printing its value (if no arg is // provided), or assigning a value (if an arg is provided).\ // print variable name and value CharString Variable::printstr() const { return name + " = " + tostring(); } // void Variable::printvar(ostream &os) const { // os << name << " = " << tostring() << endl; // } // print variable together with the path, used when echoing commands void TextWaiter::echovar(const Variable *var) { WordList leadingpath(path); leadingpath.strip(1); CharString stringpath(leadingpath.concat()); if(echo) msout << ms_prompt << stringpath << " " << var->printstr() << endl << ms_normal; if(!var->attribute(MOPT_NOLOG)) log(stringpath + " " + var->printstr()); } void Variable::execute(TextWaiter& w) { w.path = w.cmdlist; if(w.macromode) { // this has to be rewritten to look for only name=arg w.record(w.path.concat()); CharString value(w.argword()); // look for value if(value == EQUALS) value = w.argword(); if(value != "") w.record("=" + value); w.record(DELIMITER); } else { //noticechar(EQUALS); CharString arg(w.argword()); if(arg == "") { // "var" w.msout << printstr() << endl; } else { if(arg[0] == EQUALS) { // "var = ???" savevalue(); fromstring(w); // new value? (destroys old value) if(*w.inbuf) { // found it? w.echovar(this); // yes update_forms(); } else { restorevalue(); // restore old value if(!w.getvar(this)) { // not already typed -- request value // error reading new value if(!attribute(MOPT_NOLOG)) w.log(printstr()); return; } } if(hook) { // execute hook function w.path = w.cmdlist + " = " + tostring(); w.makecurrent(); (*hook)(name); w.path.strip(2); // remove "= value" from path } } else { // "var value" is illegal w.repeatword = 1; // the word that wasn't '=' is the next command w.msout << printstr() << endl; } } //ignorechar(EQUALS); } } // prompt for a new value of var, using current value as default. int TextWaiter::getvar(const Variable *var) { msout << ms_prompt << "*Enter " << var->name << " [" << var->tostring() << "]: " << flush << ms_normal; char line[wbufsiz]; char *ptr = fgets(line, sizeof(line), infile); if(!ptr) return 0; // eof probably, will be caught later. // get rid of trailing newline char *c = strchr(ptr, '\n'); if(c) *c = '\0'; skipspace(&ptr); if(*ptr == '\0') return 1; // keep old value return var->fromstring(ptr, *this); } void Variable::update_forms() { #ifdef MENUGFX Vec &formlist = menu->form; for(int j=0; jupdatevariable(this); #endif } // ------------------------------------------------- // // check that a string represents a number, for Variable input verification static int check_number(const char *str, int founddot, int foundexp, int beginning) { const char c = *str; if(c == '\0' || c == '\n') return 1; // ok -- got to the end if((c == '-' || c == '+') && !beginning) return 0; beginning = 0; if(c == '.') { if(founddot) return 0; founddot = 1; } if(isalpha(c)) { if(foundexp || (c != 'e' && c != 'E')) return 0; foundexp = 1; // found "e" beginning = 1; } return check_number(str+1, founddot, foundexp, beginning); } static int checknumber(const CharString &str, Waiter &w) { CharString cs(skipspace(str)); // makes a copy char *c = (char*) strchr(cs, '\n'); if(c) *c = '\0'; if(!check_number(cs, 0, 0, 1)) { w.msout << ms_error << "=== \"" << cs << "\" is not a number!" << endl << ms_normal; return 0; } return 1; } // ------------------------------------------------- // // Variable member functions for menu template classes Variable::Variable(const CharString &nm, const CharString &hs, void *x, void (*f)(const CharString&), const MenuAttribute &att) : MenuOption(nm, hs, '=', att), address(x), hook(f), oldvalue(0) #ifdef MENUGFX , varobj(0) #endif { VariableRegistry::vr().add_variable(this); } Variable::Variable() : oldvalue(0) {} Variable::~Variable() { // if(oldvalue) delete oldvalue; #ifdef MENUGFX if(varobj) delete varobj; #endif } template <> CharString Var::tostring() const { return to_charstring(*(char*) address); } // The VariableRegistry is a list of lists of Variables. Each sublist // consists of Variables in different menus that refer to the same // underlying variable (ie have the same address). There is only one // VariableRegistry. It's allocated in the following function, to // guarantee that there's only one and that it exists when needed. VariableRegistry &VariableRegistry::vr() { // this function is a static member of the VariableRegistry class. static VariableRegistry *v = 0; if(v) return *v; v = new VariableRegistry; return *v; } VariableRegistry::~VariableRegistry() { for(int i=0; iaddress == var->address) { // new variable duplicates one in the list varlist[i]->grow(1, var); return; } } // enlarge varlist with a new list of Variables. The new list just // contains var. varlist.grow(1, new Vec(1, var)); } void VariableRegistry::call_function(const Variable *var, void (Variable::*f)()) const { for(int i=0; iaddress == var->address) { Vec &vlist = *varlist[i]; for(int j=0; j*f)(); } } } } // -------------------------------------------- // /* Override the templates for tostring and fromstring and Var, * to provide a bit more error handling than the default and to handle * quotation marks and spaces in CharStrings. */ template <> int Var::fromstring(const CharString &str, Waiter& w) const { if(checknumber(str, w)) { *(double *) address = atof(str); return 1; } return 0; } template <> void Var::fromstring(TextWaiter& w) const { Word str; *w.inbuf >> str; if(checknumber(str, w)) *(double *) address = atof(str); else w.inbuf->clear(ios::badbit|w.inbuf->rdstate()); } template <> int Var::fromstring(const CharString &str, Waiter& w) const { if(checknumber(str, w)) { *(float *) address = atof(str); return 1; } return 0; } template <> void Var::fromstring(TextWaiter& w) const { Word str; *w.inbuf >> str; if(checknumber(str, w)) *(float *) address = atof(str); else w.inbuf->clear(ios::badbit|w.inbuf->rdstate()); } template <> int Var::fromstring(const CharString &str, Waiter& w) const { if(checknumber(str, w)) { *(int*) address = atoi(str); return 1; } return 0; } template <> void Var::fromstring(TextWaiter& w) const { Word str; *w.inbuf >> str; if(checknumber(str, w)) *(int*) address = atoi(str); else w.inbuf->clear(ios::badbit|w.inbuf->rdstate()); } template <> int Var::fromstring(const CharString &str, Waiter&) const { CharString temp(str); // make a temporary copy char *c; if(c = (char*) strchr(temp, '\n')) *c = '\0'; // get rid of possible \n // strip quotation marks if(temp[0] == QUOTE && (c=(char*)strchr(&temp[1], QUOTE))) { *c = '\0'; *(CharString*) address = CharString(&temp[1]); } else *(CharString*) address = temp; return 1; } template <> void Var::fromstring(TextWaiter &w) const { Word str; // read as a Word so that special chars are handled *w.inbuf >> str; *(CharString*) address = str; } template <> CharString Var::tostring() const { CharString cs(*(CharString*) address); if(cs == "") return CharString("\"\""); // quote empty strings! // if string contains spaces and doesn't begin with QUOTE, quote it. // strings with embedded quotes will be confusing. if(strchr(cs, SPACE) && cs[0] != QUOTE) return QUOTE + cs + QUOTE; else return cs; } // --------- Commands, Arguments, etc. -------------------- // /* Commands with arguments need to have separate instances for each form, * since the forms can have different values for the arguments. Only the * master copy of the command keeps a list of the forms. The cloned copies * just have a pointer to the list. * * Each command keeps a pointer to its own form. */ Command::~Command() { #ifdef MENUGFX if(form) { if(!active) { delete form; form = 0; } else form->frozen = 0; // so form will be deleted when cmd is done } #endif // MENUGFX } CommandM::~CommandM() { if(!ismaster) { clonelist->remove(this); } else { /* do this in reverse order, since each command removes itself from * the list as it is deleted. */ for(int i=clonelist->capacity()-1; i>=0; i--) { CommandM *cmd = (*clonelist)[i]; if(cmd != this && !cmd->active) // don't delete the active command delete cmd; #ifdef MENUGFX else cmd->form->frozen = 0; // make sure it's deleted when it's done // active command should be deleted by CommandMForm::dismiss() #endif // MENUGFX } } } CharString CommandM::argstring() const { CharString str; for(int i=0; iattribute(MOPT_SECRET)) str = str.spaceappend(arg[i]->name + "=" + arg[i]->tostring()); return str; } #ifdef MENUGFX CommandForm *Command0::makecmdform(const WordList &cmdpath) { return new Command0Form(this, cmdpath); } CommandForm *CommandS::makecmdform(const WordList &cmdpath) { return new CommandSForm(this, cmdpath); } CommandForm *CommandM::makecmdform(const WordList &cmdpath) { return new CommandMForm(this, cmdpath); } #endif // MENUGFX void CommandM::pronto(Waiter &w, const WordList &path) { w.makecurrent(); w.path = path; w.args = argstring(); CharString fullcmd(w.path.concat() + " " + w.args); if(w.echo) w.msout << ms_prompt << w.errhm << fullcmd << endl << ms_normal; if(!attribute(MOPT_NOLOG)) w.log(fullcmd); if(w.macromode) { w.record(fullcmd); w.recorddelim(); } else { active = 1; func(); active = 0; } } void Command0::pronto(Waiter &w, const WordList &path) { w.makecurrent(); w.path = path; w.args = ""; CharString fullcmd(path.concat()); if(w.echo) w.msout << ms_prompt << w.errhm << fullcmd << endl << ms_normal; if(!attribute(MOPT_NOLOG)) w.log(fullcmd); if(w.macromode) { w.record(fullcmd); w.recorddelim(); } else { active = 1; (*func)(); active = 0; } } void CommandS::pronto(Waiter &w, const WordList &path) { w.makecurrent(); w.path = path; CharString fullcmd(path.concat()); if(w.echo) w.msout << ms_prompt << w.errhm << fullcmd << endl << ms_normal; if(!attribute(MOPT_NOLOG)) w.log(fullcmd); if(w.macromode) { w.record(fullcmd); w.recorddelim(); } else { ScriptWaiter sw(script, w.msout); active = 1; sw.order(menu); active = 0; w.makecurrent(); } } // --------- Auxiliary routines -------------------- // void Waiter::runfile(FILE* file, Menu* menu, int verbose, bool (*stop)()) { if(!file) return; TextWaiter w(msout, file, stop); if(inhibit_logging_) w.inhibit_logging(); if(verbose) w.talkative(); else w.quiet(); w.order(menu); if(w.showprompts) msout << endl; // so that two prompts don't appear on one line makecurrent(); // original waiter regains control } void Waiter::runfile(istream &is, Menu *menu, int verbose, bool (*stop)()) { if(!is) return; StreamWaiter w(msout, is, stop); if(inhibit_logging_) w.inhibit_logging(); if(verbose) w.talkative(); else w.quiet(); w.order(menu); if(w.showprompts) msout << endl; makecurrent(); } // Command log // startlog(), stoplog() control logging to a file. // A log of the whole session is also kept automatically, and it // can be printed with printlog(); Vec Waiter::logbook(0, BlockSize(50)); TrueFalse Waiter::logdirty_(0); void Waiter::startlog(const CharString &file, const TrueFalse &append) { stoplog(); if(append) logfile = new ofstream(file, ios::app); else logfile = new ofstream(file); if(*logfile) logging_ = TF_TRUE; } void Waiter::stoplog() { if(logging_) { logging_ = TF_FALSE; logfile->close(); } } void Waiter::log(const CharString &str) { if(inhibit_logging_ > 0) return; if(logging_ && !macromode) // explicit logging *logfile << str << endl; if(!macromode) { // automatic logging logbook.grow(1, new CharString(str)); logdirty_ = TF_TRUE; } #ifdef DEBUGVERBOSE cerr << "Waiter::log: " << str << endl; #endif } void Waiter::printlog(ostream &os) const { for(int i=0; imsout); sw.order(loopmenu); } for(int i=0; imsout); sw.order(loopmenu); } if(stopnow()) garcon()->msout << ms_error << "=== Interrupted!\n" << endl << ms_normal; if(endscript != "") { ScriptWaiter sw(endscript, garcon()->msout); sw.order(loopmenu); } } CommandM *LoopCommand::clone() const { LoopCommand *lc = new LoopCommand(name, helpstring, loopmenu, script, beginscript, endscript, fixscripts); lc->iterations = iterations; return lc; } // ----------------------------------------------------- // LoadMacroCmd::LoadMacroCmd(const CharString &cname) : cmdname(cname), CommandM(cname, "read macro definitions from a file"), filename("macrofile") { AddArgument(this, "filename", filename); } CommandFn LoadMacroCmd::func() { FILE *f = fopen(filename, "r"); if(!f) garcon()->msout << ms_error << "=== Can't read macros from file " << filename << "!" << endl << ms_normal; else { garcon()->loadmacros(f); fclose(f); } } CommandM *LoadMacroCmd::clone() const { LoadMacroCmd *lm = new LoadMacroCmd(cmdname); lm->filename = filename; return lm; } SaveMacroCmd::SaveMacroCmd(const CharString &cname) : cmdname(cname), CommandM(cname, "save macro definitions to a file"), filename("macrofile") { AddArgument(this, "filename", filename); } CommandFn SaveMacroCmd::func() { FILE *f = fopen(filename, "w"); if(!f) garcon()->msout << ms_error << "=== Can't write macros to file " << filename << "!" << endl << ms_normal; else { garcon()->showmacros(f); fclose(f); } } CommandM *SaveMacroCmd::clone() const { SaveMacroCmd *sm = new SaveMacroCmd(cmdname); sm->filename = filename; return sm; }