Re: Dynamic menu
Here is the new version of my code, after having refined it following
the suggestions of ?? and Victor, and now I have an question about the
protected dtor.
Before of that question, here are the changes I made to the code:
- added the common data and functions to the MenuItem base class, which
now can't be created nor destroyed by the client code.
- protected all the classes derived from MenuItem - neither those
classes can be created nor destroyed by the client.
- changed the order of public / protected / private sections.
- moved out from the Menu class the implementation of the add_item() and
interact() functions.
- added some checks to avoid duplicate commands when adding new items.
- added the const specifier wherever I thought I could.
Now the code is more readable even for myself :-)
Since nobody questioned the overall structure, I suppose there is no way
to create something like this avoiding inheritance - and with "something
like this" I mean "a menu that accepts a pointer to function regardless
of the type of that function argument".
There is one thing that stopped me for a moment, and still stops me
about its understanding: if I omit to declare MenuItemContainer as a
friend of MenuItem, the compiler tells me I cannot delete a MenuItem*
from the MenuItemContainer dtor, because the dtor is protected.
Now, shouldn't a derived class be able to access the protected members
of its base class? What I'm missing?
Thanks again for your attention.
//-------
#include <iostream>
#include <typeinfo>
#include <vector>
#include <list>
/* ==========================================================
MenuItem base class - public interface
*/
class MenuItem {
public:
virtual void execute() const {}
std::string caption() const {
return _caption;
}
std::string command() const {
return _command;
}
MenuItem* parent() const {
return _parent;
}
protected:
friend class MenuItemContainer;
MenuItem(std::string caption,
std::string command,
MenuItem* parent) :
_caption(caption),
_command(command),
_parent(parent) {}
virtual ~MenuItem() {}
private:
std::string _caption;
std::string _command;
MenuItem* _parent;
};
/* ==========================================================
Protected classes to be used by Menu class
*/
class MenuItemContainer : protected MenuItem {
protected:
friend class Menu;
MenuItemContainer(std::string caption,
std::string command,
MenuItem* parent) :
MenuItem(caption, command, parent) {}
~MenuItemContainer() {
for (size_t i = 0; i < _items.size(); ++i) {
delete _items[i];
}
}
std::vector<MenuItem*> _items;
};
class MenuItemWithoutParam : protected MenuItem {
protected:
friend class Menu;
MenuItemWithoutParam(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)()) :
MenuItem(caption, command, parent),
_callback(callback) {}
void execute() const {
if (_callback) {
_callback();
}
}
private:
void (*_callback)();
};
template<class T> class MenuItemWithParam : protected MenuItem {
protected:
friend class Menu;
MenuItemWithParam(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param) :
MenuItem(caption, command, parent),
_callback(callback),
_param(param) {}
void execute() const {
if (_callback && _param) {
_callback(_param);
}
}
private:
void (*_callback)(T*);
T* _param;
};
/* ==========================================================
Menu class definition
*/
class Menu {
public:
struct DuplicateCommand {};
Menu(std::istream& input,
std::ostream& output) :
_basecontainer("","",0),
_input(input),
_output(output) {}
template<class T>
MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param);
MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)());
MenuItem* add_item(std::string caption,
std::string command,
MenuItem* parent);
void interact() const;
private:
MenuItemContainer _basecontainer;
std::istream& _input;
std::ostream& _output;
};
/* ==========================================================
Menu class members implementation
*/
template<class T>
MenuItem* Menu::add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)(T*),
T* param) {
if (!parent || typeid(*parent) != typeid(MenuItemContainer)) {
parent = &_basecontainer;
}
MenuItemContainer* cont = static_cast<MenuItemContainer*>(parent);
if (command == "up" || command == "exit") throw DuplicateCommand();
for (size_t i = 0; i < cont->_items.size(); ++i) {
if (command == cont->_items[i]->command()) {
throw DuplicateCommand();
}
}
MenuItem* item = new MenuItemWithParam<T>(
caption,
command,
parent,
callback,
param);
cont->_items.push_back(item);
return item;
}
MenuItem* Menu::add_item(std::string caption,
std::string command,
MenuItem* parent,
void (*callback)()) {
if (!parent || typeid(*parent) != typeid(MenuItemContainer)) {
parent = &_basecontainer;
}
MenuItemContainer* cont = static_cast<MenuItemContainer*>(parent);
if (command == "up" || command == "exit") throw DuplicateCommand();
for (size_t i = 0; i < cont->_items.size(); ++i) {
if (command == cont->_items[i]->command()) {
throw DuplicateCommand();
}
}
MenuItem* item = new MenuItemWithoutParam(
caption,
command,
parent,
callback);
cont->_items.push_back(item);
return item;
}
MenuItem* Menu::add_item(std::string caption,
std::string command,
MenuItem* parent) {
if (!parent || typeid(*parent) != typeid(MenuItemContainer)) {
parent = &_basecontainer;
}
MenuItemContainer* cont = static_cast<MenuItemContainer*>(parent);
if (command == "up" || command == "exit") throw DuplicateCommand();
for (size_t i = 0; i < cont->_items.size(); ++i) {
if (command == cont->_items[i]->command()) {
throw DuplicateCommand();
}
}
MenuItem* item = new MenuItemContainer(
caption,
command,
parent);
cont->_items.push_back(item);
return item;
}
void Menu::interact() const {
std::string line;
const MenuItemContainer* current = &_basecontainer;
const MenuItem* selected;
for (;;) {
selected = 0;
_output << "\n-------" << std::endl;
for (size_t i = 0; i < current->_items.size(); ++i) {
_output << "[" << current->_items[i]->command();
_output << "] " << current->_items[i]->caption();
_output << std::endl;
}
_output << "-" << std::endl;
if (current->parent()) {
_output << "[up] Upper menu" << std::endl;
}
_output << "[exit] Exit program" << std::endl;
_output << "-" << std::endl;
_output << "Insert command:" << std::endl << ">";
getline(_input, line);
if (line == "up" && current->parent()) {
selected = current->parent();
} else if (line == "exit") {
return;
} else {
for (size_t i = 0; i < current->_items.size(); ++i) {
if (current->_items[i]->command() == line) {
selected = current->_items[i];
break;
}
}
}
_output << std::endl;
if (selected) {
if (typeid(*selected) == typeid(MenuItemContainer)) {
current = static_cast<const MenuItemContainer*>
(selected);
} else {
selected->execute();
}
} else {
_output << "Unrecognized command" << std::endl;
}
}
}
/* ==========================================================
Client section
*/
using namespace std;
Menu menu(cin, cout);
void increment(int* i) {
if (i)
++*i;
}
void print(int* i) {
if (i)
cout << *i << endl;
}
void cite(string* str) {
if (str)
cout << *str << endl;
}
void addquote(MenuItem* parent) {
static list<string> quotes;
string caption;
string command;
string quote;
cout << "Insert caption: " << endl;
cout << ">";
getline(cin, caption);
cout << "Insert command: " << endl;
cout << ">";
getline(cin, command);
cout << "Insert quote: " << endl;
cout << ">";
getline(cin, quote);
if (caption != "" && command != "" && quote != "") {
quotes.push_back(quote);
try {
menu.add_item(caption,
command,
parent,
cite,
"es.back());
} catch (Menu::DuplicateCommand) {
cout << "!!! Unable to create quote: ";
cout << "duplicated command" << endl;
}
} else {
cout << "!!! Unable to create quote, ";
cout << "please retry filling in all fields" << endl;
}
}
int main() {
int i = 42;
MenuItem* program = menu.add_item("Program", "p", 0);
MenuItem* variable = menu.add_item("Variable", "v", program);
MenuItem* quotes = menu.add_item("Quotes", "q", program);
menu.add_item("Increment", "i", variable, increment, &i);
menu.add_item("Print", "p", variable, print, &i);
string hello = "Hello world!";
string panic = "Don't panic!";
menu.add_item("Add quote", "a", quotes, addquote, quotes);
menu.add_item("Hello", "h", quotes, cite, &hello);
menu.add_item("H2G2", "42", quotes, cite, &panic);
menu.interact();
return 0;
}
//-------
--
FSC - http://userscripts.org/scripts/show/59948
http://fscode.altervista.org - http://sardinias.com