text_ui.cpp
Go to the documentation of this file.
00001 /*****************************************************************************
00002  * PROJECT: Iwakishi
00003  *
00004  * FILE: text_ui.cc
00005  *
00006  * ABSTRACT: an ncurses library based text interface with Iwaki interaction
00007  * manager.
00008  *
00009  * Soundboard: play sounds in response to keyboard according to Iwaki scripts
00010  * Copyright (C) 2012-2013 Maxim Makatchev.
00011  * 
00012  * This program is free software: you can redistribute it and/or modify
00013  * it under the terms of the GNU General Public License as published by
00014  * the Free Software Foundation, either version 3 of the License, or
00015  * (at your option) any later version.
00016  *
00017  * This program is distributed in the hope that it will be useful,
00018  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00019  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020  * GNU General Public License for more details.
00021  *
00022  * You should have received a copy of the GNU General Public License
00023  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00024  * 
00025 */
00026 
00027 
00028 #include <iostream>
00029 #include <sstream>
00030 #include "iwakishi.h"
00031 #include <algorithm>
00032 #include <string>
00033 
00034 #include <locale.h>
00035 #include <ncursesw/ncurses.h>
00036 
00037 #define KEY_SMALL_Q 113
00038 #define KEY_SMALL_V 118
00039 #define KEY_CONTROL_SMALL_Q 17
00040 #define KEY_CONTROL_SMALL_V 22
00041 #define MAX_NODE_WIDTH 20
00042 #define ROOT_NODE_WIDTH 4
00043 
00044 
00045 UICommand TextUI::update(InteractionManager &im, int ch) {
00046     UICommand ui_command = uiNone;
00047     int nrow, ncol, msg_height, tree_height, globals_height = 1, header_height = 2;
00048     int keyboard_buffer_height = 1;
00049 
00050 
00051         /*
00052          * decide the layout dimensions
00053          * */
00054     getmaxyx(stdscr, nrow, ncol);    /*  figure out screen geomery */
00055 
00056     if (nrow <= this->msgPaneHeight + header_height + this->minTreeHeight) {
00057         msg_height = 0;               /* no msg pane if screen is short */
00058     } else {
00059         msg_height = this->msgPaneHeight;
00060     }
00061     tree_height = nrow - header_height - msg_height - globals_height -
00062         keyboard_buffer_height;
00063 
00064 
00065         /*
00066          * do the printing
00067          */
00068     clear();
00069 
00070     move(0,0);
00071     this->printHeader();
00072 
00073     move(header_height, 0);
00074     this->printPlanTree(im.ptree, tree_height);
00075 
00076     move(header_height + tree_height, 0);
00077     this->printGlobals( *im.getGlobalBindings() );
00078 
00079     move(header_height + tree_height + globals_height, 0);
00080     this->printMessages(msg_height);
00081 
00082     move(header_height + tree_height + globals_height + msg_height, 0);
00083     this->printKeyboardBuffer();
00084 
00085         /*
00086          * read user input
00087          * (do it outside) of TextUI::update
00088          * 
00089          ch = getch();  */      /* If raw() hadn't been called
00090                                  * we have to press enter before it
00091                                  * gets to the program          */
00092     
00093     if ( ch == KEY_CONTROL_SMALL_Q ) {
00094         printw("Goodbye");
00095         ui_command = uiQuit;
00096     } else if ( ch == KEY_CONTROL_SMALL_V ) {  /* toggle verbose mode */
00097         move(1,0);
00098         if (this->verbosity == WithBodyElements) {
00099             printw("brief");
00100             this->verbosity = Brief;           
00101         } else if (this->verbosity == Brief) {
00102             printw("verbose");
00103             this->verbosity = Verbose;
00104         } else { /* second to last verbosity, Verbose */
00105             printw("with body elements");
00106             this->verbosity = WithBodyElements;
00107         }
00108     }
00109 
00110     
00111     refresh();                  /* Print it on to the real screen */
00112 
00113         /* tick_id is used as a counter for dynamic features, like random choices */
00114     if (this->tick_id >= MAX_UI_TICK_ID) {
00115         this->tick_id = 0; 
00116     } else {
00117         this->tick_id++;
00118     }
00119     
00120     return ui_command;
00121 }
00122 
00123 void TextUI::init() {
00124     setlocale(LC_CTYPE,"");       /* for Unicode support */
00125     initscr();                  /* Start curses mode */
00126     raw();                      /* Line buffering disabled      */
00127     keypad(stdscr, TRUE);       /* We get F1, F2 etc..          */
00128     noecho();                   /* Don't echo() while we do getch */
00129     timeout(0);
00130 }
00131 
00132 
00133 void TextUI::close() {
00134     endwin();                   /* End curses mode */
00135 }
00136 
00137 
00138 void TextUI::printPlanTree(PlanTree &ptree, const int &tree_height) {
00139     int y = 0, x = 0, dy = 0, depth_old = 0, depth = 0, dx = 0;
00140     
00141     getyx(stdscr, y, x);
00142     
00143     if (this->verbosity == Brief) {                           /* brief view */
00144         tree<Node>::iterator sib2=ptree.tr.begin();
00145         tree<Node>::iterator end2=ptree.tr.end();
00146         while (( sib2!=end2 ) && (dy < tree_height)) {
00147             dx = ptree.tr.depth(sib2);
00148             move(y + dy, x + dx);
00149             printw( (*sib2).recipe_name.c_str() );
00150             ++sib2;
00151             ++dy;
00152         }
00153     } else if (this->verbosity == Verbose) {         /* verbose view
00154                                                      * includes one liner of number of currently active
00155                                                      * element, total number of elements
00156                                                      * */
00157         tree<Node>::iterator sib2=ptree.tr.begin();
00158         tree<Node>::iterator end2=ptree.tr.end();
00159         while (( sib2 != end2 ) && (dy < tree_height)) {
00160             dx = ptree.tr.depth(sib2);
00161             move(y + dy, x + dx);
00162             printw( (*sib2).recipe_name.c_str() );
00163                                                     /* print recipe status string
00164                                                      * */
00165             if (sib2->recipe_name != "ROOT") {      /* don't print details for ROOT */
00166                 string recipe_status = " " + to_string(sib2->node_id) + " " +
00167                     to_string(sib2->active_element) + " " +
00168                     to_string(sib2->body.elements.size());
00169                 printw(recipe_status.c_str());
00170             }
00171             ++sib2;
00172             ++dy;
00173         }
00174     } else {                                        /* verbosity == WithBodyElements */
00175         tree<Node>::iterator sib2_old;
00176         tree<Node>::iterator sib2=ptree.tr.begin();
00177         tree<Node>::iterator end2=ptree.tr.end();
00178         while (( sib2 != end2 ) && (dy < tree_height)) {
00179             depth_old = depth;
00180             depth = ptree.tr.depth(sib2);                /* assuming ROOT's depth is 0 */
00181             dx = ROOT_NODE_WIDTH + (depth-1) * MAX_NODE_WIDTH;
00182                                                          /* compute dx and dy */
00183             if (sib2->recipe_name == "ROOT") {
00184                 dy = 0;
00185                 dx = 0;
00186             } else if (depth > depth_old) {                     /* this node is the child of the
00187                                                                  * previous one, including first ROOT's
00188                                                                  * child */
00189                 dy += 1 + ((sib2_old->recipe_name == "ROOT") ? 0 : sib2_old->active_element);
00190             } else {
00191                                                          /* the node is a new branch (must be ROOT's child,
00192                                                          * since there is only one child possible), so
00193                                                          * second or following ROOT's child */
00194                 dy += 1 + sib2_old->body.elements.size();
00195                 
00196             }
00197             
00198             move(y + dy, x + dx);
00199             
00200             if (sib2->recipe_name == "ROOT") {           /* don't print details for ROOT */ 
00201                 string root_name = "ROOT";
00202                 printw( root_name.c_str() );                                                  
00203             }  else  {                                  
00204                 string recipe_status = "-" + sib2->recipe_name + ":" + to_string(sib2->node_id) + " " +
00205                     to_string(sib2->active_element) + " " + to_string(sib2->body.elements.size());
00206                 printw(recipe_status.c_str());
00207                     /* update body element descriptions */
00208                 if (sib2->bodyElementDescriptionsHaveBeenSet) {
00209                     this->updateBodyElementDescriptions(*sib2);
00210                 } else {
00211                     this->setBodyElementDescriptions(*sib2);
00212                 }
00213                     /* print body elements */
00214                 for (int i = 0; (unsigned int)i < sib2->body.elements.size(); i++) {
00215                     string body_element_blurp = ((i == sib2->active_element) ? "*":" ") +
00216                         to_string(i) + "." + sib2->element_descriptions[i];
00217                     move(y + dy + i + 1, x + dx);
00218                     printw(body_element_blurp.c_str());
00219                 }
00220             }
00221             sib2_old = sib2;
00222             ++sib2;
00223         } 
00224     }
00225 }
00226 
00227 void TextUI::printHeader() {
00228     string header = "[ctrl-v]erbose/brief [ctrl-q]uit [ctrl-h]elp";
00229     printw( header.c_str() );
00230 }
00231 
00232 void TextUI::printMessages(const int &msg_height) {
00233     int y, x, dy = msg_height - 1, dx = 0;
00234     getyx(stdscr, y, x);
00235 
00236     std::list<string>::iterator msg_it = this->messages.begin();
00237                                                         
00238     while  (( msg_it != this->messages.end() ) && (dy >= 0)) {  /* print from the bottom
00239                                                                  * of the pane */
00240         move(y + dy, x + dx);
00241         printw( msg_it->c_str() );            
00242         ++msg_it;
00243         --dy;
00244     }
00245 }
00246 
00247 void TextUI::printKeyboardBuffer() {
00248     printw( this->keyboardBuffer.c_str() );
00249 }
00250 
00251 
00252 void TextUI::push_msg(const string &msg) {
00253     if (this->messages.size() >= MAX_UI_MSGS) {
00254         this->messages.pop_back();
00255     }
00256     this->messages.push_front(NowTime() + " " + msg); /* by default print timestamp*/
00257 }
00258 string TextUI::makeActionDescription(BodyElement &element, int active_element, int element_id) {
00259     if (element_id <= active_element) {
00260         return makePastActionDescription(element);
00261     } else {
00262         return makeFutureActionDescription(element);
00263     }
00264 
00265 }
00266 /* generate a short description of an action body element */
00267 string TextUI::makeFutureActionDescription(BodyElement &element) {
00268     string result;
00269     int rand_element;
00270     std::ostringstream output; 
00271     
00272     if (element.element_type != "action") {
00273         return "_ERROR_";
00274     }
00275 
00276     result = element.name + " ";
00277 
00278     if (element.random) {
00279         result += "rnd ";
00280 
00281         if (element.random_elements.size() == 0) {
00282             return "_ERROR_";
00283         }
00284             
00285         rand_element = (this->tick_id) %  element.random_elements.size();
00286         result = result + "p=";
00287         output << setprecision(2) << element.element_probs[rand_element];
00288         result += output.str();
00289         result += " ";
00290             /* the random element is element.random_elements[rand_element] */
00291 
00292         std::list<ArgSlot>::iterator arg_it = element.random_elements[rand_element].args.begin();
00293         while (arg_it != element.random_elements[rand_element].args.end()) {
00294             if (arg_it->name == "utterance_file") {
00295                 result += abbreviateString(arg_it->value, 10, 15); 
00296             }
00297             arg_it++;
00298         }
00299     } else {
00300     
00301         std::list<ArgSlot>::iterator arg_it = element.args.begin();
00302         while (arg_it != element.args.end()) {
00303             if (arg_it->name == "utterance_file") {
00304                 result += abbreviateString(arg_it->value, 10, 15); 
00305             }
00306             arg_it++;
00307         }
00308     }
00309 
00310     return result;
00311     
00312 }
00313 
00314 /* generate a short description of an action body element */
00315 string TextUI::makePastActionDescription(BodyElement &element) {
00316     string result;
00317     int rand_element;
00318     std::ostringstream output; 
00319     
00320     if (element.element_type != "action") {
00321         return "_ERROR_";
00322     }
00323 
00324     result = element.name + " ";
00325 
00326     if (element.random) {
00327         result += "rnd ";
00328 
00329         if (element.random_elements.size() == 0) {
00330             return "_ERROR_";
00331         }
00332             
00333         rand_element = element.chosen_outcome;  /* the only difference between past and future versions*/
00334         result = result + "p=";
00335         output << setprecision(2) << element.element_probs[rand_element];
00336         result += output.str();
00337         result += " ";
00338             /* the random element is element.random_elements[rand_element] */
00339 
00340         std::list<ArgSlot>::iterator arg_it = element.random_elements[rand_element].args.begin();
00341         while (arg_it != element.random_elements[rand_element].args.end()) {
00342             if (arg_it->name == "utterance_file") {
00343                 result += abbreviateString(arg_it->value, 10, 15); 
00344             }
00345             arg_it++;
00346         }
00347     } else {
00348     
00349         std::list<ArgSlot>::iterator arg_it = element.args.begin();
00350         while (arg_it != element.args.end()) {
00351             if (arg_it->name == "utterance_file") {
00352                 result += abbreviateString(arg_it->value, 10, 15); 
00353             }
00354             arg_it++;
00355         }
00356     }
00357 
00358     return result;
00359     
00360 }
00361 
00362 
00363 void TextUI::setBodyElementDescriptions(Node &aNode) {
00364     
00365     aNode.element_descriptions.clear();
00366     for (int i = 0; (unsigned int)i < aNode.body.elements.size(); i++) {
00367         aNode.element_descriptions.push_back("");    /* first initialize the vector element*/
00368         this->updateBodyElementDescription(aNode, i);
00369     }
00370     aNode.bodyElementDescriptionsHaveBeenSet = true;
00371 }
00372 
00373 /*
00374  * This differs from setBodyElementDescriptions in that it only updates
00375  * descriptions of those elements that need to be, like random actions, for
00376  * example (and for now).
00377  */
00378 void TextUI::updateBodyElementDescriptions(Node &aNode) {
00379 
00380     for (int i = max(0, aNode.active_element); (unsigned int)i < aNode.body.elements.size(); i++) {
00381         if (aNode.body.elements[i].random) {
00382             this->updateBodyElementDescription(aNode, i);
00383         }
00384     }
00385 }
00386 
00387 
00388 void TextUI::updateBodyElementDescription(Node &aNode, int element_id) {
00389                                                           /* does element_id make sense? */
00390     if ( ( (unsigned int)element_id >= aNode.body.elements.size() ) ||
00391          ( element_id < 0 ) ) {
00392         return;
00393     }
00394 
00395         /* do it */
00396     if (aNode.body.elements[element_id].element_type == "action") {
00397         
00398         aNode.element_descriptions[element_id] = "action " +
00399             makeActionDescription(aNode.body.elements[element_id], aNode.active_element, element_id);
00400     } else if (aNode.body.elements[element_id].element_type == "goal") {
00401         aNode.element_descriptions[element_id] = "goal";
00402             /* TODO: add summary of the goal formula */
00403     } else if (aNode.body.elements[element_id].element_type == "assignment") {
00404         aNode.element_descriptions[element_id] = "assignment";
00405             /* TODO add summary of the assignment formula */
00406     } else {                                                   /* unknown body element */
00407         aNode.element_descriptions[element_id] = "Unknown type: " +
00408             aNode.body.elements[element_id].element_type;
00409         return;
00410     }
00411 }
00412 
00413 
00414 
00415 /*
00416  * produces map of counts of atom subtypes on global bindings
00417  * */
00418 std::map<string, int> TextUI::getAtomSubtypeCounts(Conjunction &gBindings) {
00419 
00420     std::map<string, int> counts_map;
00421 
00422     for (std::vector< Atom >::iterator atom_it=gBindings.atoms.begin();
00423          atom_it != gBindings.atoms.end(); atom_it++) {
00424         if (counts_map.count(atom_it->readSlotVal("subtype")) == 0) {
00425             counts_map[atom_it->readSlotVal("subtype")] = 1;
00426         } else {
00427             counts_map[atom_it->readSlotVal("subtype")] += 1;
00428         }
00429     }
00430     
00431     return counts_map;
00432     
00433 }
00434 
00435 /*
00436  * Prints counts of atom subtypes in global bindings
00437  * */
00438 void TextUI::printGlobals(Conjunction &gBindings) {
00439     int nrow, ncol, y, x, dy = 0, dx = 0;
00440 
00441     getmaxyx(stdscr, nrow, ncol); 
00442     getyx(stdscr, y, x);
00443 
00444     std::map<string, int> atomSubtypeCounts = this->getAtomSubtypeCounts(gBindings);
00445     
00446     std::map<string, int>::iterator entry_it = atomSubtypeCounts.begin();
00447     while (( entry_it != atomSubtypeCounts.end() ) &&
00448            (dx <= ncol - (int)entry_it->first.size() - 3)) {  /* leave the space for
00449                                                           * subtype name and 1 digit count */
00450         move(y + dy, x + dx);
00451         string count_string = entry_it->first + ":" + to_string(entry_it->second) + " ";
00452         printw( count_string.c_str() );            
00453         ++entry_it;
00454         dx += count_string.size();
00455     }
00456 }


iwaki
Author(s): Maxim Makatchev, Reid Simmons
autogenerated on Thu Aug 27 2015 13:34:44