// -*- 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 <string.h>
#include "stdlib.h"
#include "ctype.h"
#ifdef HAVE_MALLOC_H
#include <malloc.h>
#endif // HAVE_MALLOC_H
#include <signal.h>
#include <stdio.h>
#include <iostream.h>
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; i<option.capacity() && !duplicated; i++) {
if(option[i]->name == 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; i<option.capacity(); i++) {
if(nm == option[i]->name) {
if(!option[i]->active)
delete option[i];
// get rid of space in the option list
for(int j=i+1; j<option.capacity(); j++)
option[j-1] = option[j];
option.grow(-1);
update_forms(); // update menu forms
return;
}
}
garcon()->msout << 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; i<option.capacity(); i++) {
if(newname == option[i]->name)
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<MenuOption*> &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; i<option.capacity(); i++) {
if(!strncmp(word, option[i]->name, 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; i<option.capacity(); i++) {
if(!strcmp(word, option[i]->name)) return i;
}
return -1; // No exact match
}
int getcnum(const CharString &word, const Vec<Variable*> &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; i<option.capacity(); i++) {
if(!strncmp(word, option[i]->name, 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; i<option.capacity(); i++) {
if(!strcmp(word, option[i]->name)) 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; i<option.capacity(); i++)
option[i]->highlighted = 0;
int cnum = getcnum(what, option);
if(cnum < 0) return;
option[cnum]->highlighted = 1;
update_forms();
}
void Menu::unhighlight() {
for(int i=0; i<option.capacity(); i++)
option[i]->highlighted = 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; j<form.capacity(); j++)
form[j]->update();
#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<cmdlist.capacity(); i++)
free(cmdlist[i]);
cmdlist.resize(0);
}
CharString TextWaiter::getpath(int d) const {
// d is how far short of the end of the list to stop. default=0
CharString thepath;
for(int i=0; i<cmdlist.capacity()-d; i++)
thepath = thepath.spaceappend(cmdlist[i]);
return thepath;
}
#endif
// return command n-deep in heirarchy
CharString Waiter::get_command(int n) const {
// return nth word from the end of the path
int s = path.size();
if(n >= 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; i<menu->option.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; i<option.capacity(); i++)
option[i]->print(file, path, recursive);
}
void Menu::print(ostream &os, CharString path, const TrueFalse &recursive) const
{
for(int i=0; i<option.capacity(); i++)
option[i]->print(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<CharString*> defn;
Vec<CharString*> name;
Vec<WordList*> 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<name.capacity(); i++)
if(n == *name[i]) {
delete name[i];
delete path[i];
delete defn[i];
name.remove(name[i]); // inefficient
defn.remove(defn[i]);
path.remove(path[i]);
break;
}
}
~MacroList() {
for(int i=0; i<name.capacity(); i++) {
delete name[i];
delete path[i];
delete defn[i];
}
}
};
static MacroList macrolist;
void Waiter::endmacro() {
macromode = 0;
definemacro(getmacroname());
}
CharString TextWaiter::getmacroname() {
CharString arg(argword());
if(arg != "") {
return arg;
}
else { // nothing provided, create a dummy string Variable and ask for name
// construct a default name
CharString name(defaultmacroname());
Var<CharString> 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<macrolist.name.capacity(); i++)
os << macrodefstr(*macrolist.path[i],
*macrolist.defn[i],
*macrolist.name[i])
<< endl;
}
void Waiter::showmacros(FILE *f) {
for(int i=0; i<macrolist.name.capacity(); i++)
fprintf(f, "%s\n", macrodefstr(*macrolist.path[i],
*macrolist.defn[i],
*macrolist.name[i]).charstar());
}
void Waiter::loadmacros(FILE *f) {
runfile(f, rootmenu, 0);
}
// ----------------- Routines to get arguments ---------------//
int TextWaiter::setargs(Vec<Variable*> &arglist) {
int i;
CharString argwd;
int nset = 0; // number of args set so far
Vec<int> 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; i<arglist.capacity(); i++) {
if(!isset[i] && !arglist[i]->attribute(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<MenuForm*> &formlist = menu->form;
for(int j=0; j<formlist.capacity(); j++)
formlist[j]->updatevariable(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<char>::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; i<varlist.capacity(); i++)
delete varlist[i];
}
void VariableRegistry::add_variable(Variable *var) {
for(int i=0; i<varlist.capacity(); i++) {
if((*varlist[i])[0]->address == 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<Variable*>(1, var));
}
void VariableRegistry::call_function(const Variable *var, void (Variable::*f)())
const
{
for(int i=0; i<varlist.capacity(); i++) {
if((*varlist[i])[0]->address == var->address) {
Vec<Variable*> &vlist = *varlist[i];
for(int j=0; j<vlist.capacity(); j++) {
if(vlist[j] != var) // don't call for var itself
(vlist[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<double>::fromstring(const CharString &str, Waiter& w) const {
if(checknumber(str, w)) {
*(double *) address = atof(str);
return 1;
}
return 0;
}
template <>
void Var<double>::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<float>::fromstring(const CharString &str, Waiter& w) const {
if(checknumber(str, w)) {
*(float *) address = atof(str);
return 1;
}
return 0;
}
template <>
void Var<float>::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<int>::fromstring(const CharString &str, Waiter& w) const {
if(checknumber(str, w)) {
*(int*) address = atoi(str);
return 1;
}
return 0;
}
template <>
void Var<int>::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<CharString>::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<CharString>::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<CharString>::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; i<arg.capacity(); i++)
if(!arg[i]->attribute(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<CharString*> 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; i<logbook.capacity(); i++)
os << *logbook[i] << endl;
logdirty_ = TF_FALSE;
}
void Waiter::readlog(istream &is) {
for(int i=0; i<logbook.capacity(); i++)
delete logbook[i];
logbook.resize(0);
char line[1024];
while(is.getline(line, 1024)) {
logbook.grow(1, new CharString(line));
}
}
// ------------------------------------------------------------- //
// Specific command classes
LoopCommand::LoopCommand(const CharString &nm, const CharString &hs, Menu *mnu,
const CharString &s, const CharString &bs,
const CharString &es, int f)
: iterations(0), script(s), beginscript(bs), endscript(es),
CommandM(nm, hs), loopmenu(mnu), fixscripts(f)
{
if(!fixscripts) {
AddArgument(this, "initialize", beginscript);
AddArgument(this, "body", script);
AddArgument(this, "finish", endscript);
}
AddArgument(this, "iterations", iterations);
}
CommandFn LoopCommand::func() {
Interrupter stopnow;
if(beginscript != "") {
ScriptWaiter sw(beginscript, garcon()->msout);
sw.order(loopmenu);
}
for(int i=0; i<iterations && !stopnow(); i++) {
ScriptWaiter sw(script, garcon()->msout);
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;
}
syntax highlighted by Code2HTML, v. 0.9.1