Permalink
Switch branches/tags
Nothing to show
Find file
941 lines (902 sloc) 19.6 KB
/* $Id: MenuBackend.h 1231 2011-08-25 10:57:49Z abrevig $
||
|| @author Alexander Brevig <abrevig@wiring.org.co>
|| @url http://wiring.org.co/
|| @contribution Adrian Brzezinski
|| @contribution Bernhard Benum
|| @contribution Brett Hagman <bhagman@wiring.org.co>
|| @contribution Ryan Michael <kerinin@gmail.com>
||
|| @description
|| | Provides an easy way of making menus.
|| |
|| | Wiring Cross-platform Library
|| #
||
|| @license Please see cores/Common/License.txt.
||
*/
#ifndef MENUBACKEND_H
#define MENUBACKEND_H
class MenuBackend; //forward declaration of the menu backend
class MenuItem; //forward declaration of the menu item
struct MenuChangeEvent
{
const MenuItem &from;
const MenuItem &to;
};
struct MenuUseEvent
{
MenuItem &item;
};
struct MenuItemChangeEvent
{
const MenuItem &item;
};
struct MenuMoveEvent
{
const MenuItem &item;
};
typedef void (*cb_change)(MenuChangeEvent);
typedef void (*cb_use)(MenuUseEvent);
typedef void (*cb_item_change)(MenuItemChangeEvent);
typedef void (*cb_move)(MenuMoveEvent);
/*
A menu item will be a container for an item that is a part of a menu
Each such item has a logical position in the hierarchy as well as a text and maybe a mnemonic shortkey
*/
class MenuItem
{
public:
/*
|| @constructor
|| | Basic item with a name and an optional mnemonic [File (f)]
|| #
||
|| @example
|| | MenuItem file = MenuItem("File",'F');
|| #
||
|| @parameter itemName the name of the item
|| @parameter shortKey the mnemonic 'shortkey'
*/
MenuItem(const char* itemName, char shortKey = '\0') : name(itemName), shortkey(shortKey)
{
before = right = after = left = 0;
level = value = 0;
menuBackend = 0;
}
/*
|| @constructor
|| | A complex item that will use utem change events
|| | It has a name, an optional level indicator (which level in the hierachy, this value is handy for jumping streches in the structure), and an optional mnemonic
|| #
||
|| @example
|| | MenuItem file = MenuItem(menuBackend, "File", 1, 'F');
|| #
||
|| @parameter mb the menu backend that controls this item
|| @parameter itemName the name of the item
|| @parameter lvl the level of this item in the hierachy
|| @parameter shortKey the mnemonic 'shortkey'
*/
MenuItem(MenuBackend &mb, const char* itemName, unsigned char lvl = 0, char shortKey = '\0') : name(itemName), shortkey(shortKey)
{
registerBackend(mb);
before = right = after = left = 0;
value = 0;
level = lvl;
}
/*
|| @description
|| | Register a backend for this item to use for callbacks and such
|| #
||
|| @parameter mb the menu backend that controls this item
*/
inline void registerBackend(MenuBackend &mb)
{
menuBackend = &mb;
}
//void use(){} //update some internal data / statistics
/*
|| @description
|| | Get the name of this item
|| #
||
|| @return The name of this item
*/
inline const char* getName() const
{
return name;
}
/*
|| @description
|| | Get the shortkey of this item
|| #
||
|| @return The shortkey of this item
*/
inline const char getShortkey() const
{
return shortkey;
}
/*
|| @description
|| | Check to see if this item has a shorkey
|| #
||
|| @return true if this item has a shorkey
*/
inline const bool hasShortkey() const
{
return (shortkey != '\0');
}
/*
|| @description
|| | Get the level of this item
|| #
||
|| @return The level of this item
*/
inline const unsigned char getLevel() const
{
return level;
}
/*
|| @description
|| | Set the level of this item
|| #
||
|| @parameter lvl the level of this item
*/
inline void setLevel(unsigned char lvl)
{
level = lvl;
}
/*
|| @description
|| | Check to see if this item has children
|| #
||
|| @return true if the item has children
*/
inline const bool hasChildren() const
{
return (before || right || after || left);
}
/*
|| @description
|| | Set the item that belongs as the historical 'back' for this item
|| #
||
|| @parameter b the item that was previously visited
*/
inline void setBack(MenuItem *b)
{
back = b;
}
/*
|| @description
|| | Get the item 'back' of this item (historical back)
|| #
||
|| @return the item back from this
*/
inline MenuItem *getBack() const
{
return back;
}
/*
|| @description
|| | Get the item 'before' this item
|| #
||
|| @return the item before this
*/
inline MenuItem *getBefore() const
{
return before;
}
/*
|| @description
|| | Get the item 'right' of this item
|| #
||
|| @return the item right of this
*/
inline MenuItem *getRight() const
{
return right;
}
/*
|| @description
|| | Get the item 'after' this item
|| #
||
|| @return the item after this
*/
inline MenuItem *getAfter() const
{
return after;
}
/*
|| @description
|| | Get the item 'left' of this item
|| #
||
|| @return the item left of this
*/
inline MenuItem *getLeft() const
{
return left;
}
//default vertical menu
/*
|| @description
|| | Add an item after this item in the hierarchy
|| #
||
|| @parameter mi the item to add
|| @return the item sent as parameter for chaining
*/
MenuItem &add(MenuItem &mi)
{
return addAfter(mi);
}
/*
|| @description
|| | Add an item before this item in the hierarchy
|| #
||
|| @parameter mi the item to add
|| @return the item sent as parameter for chaining
*/
MenuItem &addBefore(MenuItem &mi)
{
mi.after = this;
before = &mi;
if (!mi.back) mi.back = back;
return mi;
}
/*
|| @description
|| | Add an item right to this item in the hierarchy
|| #
||
|| @parameter mi the item to add
|| @return the item sent as parameter for chaining
*/
MenuItem &addRight(MenuItem &mi)
{
mi.left = this;
right = &mi;
if (!mi.back) mi.back = back;
return mi;
}
/*
|| @description
|| | Add an item after this item in the hierarchy
|| #
||
|| @parameter mi the item to add
|| @return the item sent as parameter for chaining
*/
MenuItem &addAfter(MenuItem &mi)
{
mi.before = this;
after = &mi;
if (!mi.back) mi.back = back;
return mi;
}
/*
|| @description
|| | Add an item left of this item in the hierarchy
|| #
||
|| @parameter mi the item to add
|| @return the item sent as parameter for chaining
*/
MenuItem &addLeft(MenuItem &mi)
{
mi.right = this;
left = &mi;
if (!mi.back) mi.back = back;
return mi;
}
/*
|| @description
|| | Set a callback to be fired before any 'move' function is called with this item
|| | as the current MenuItem.
|| #
||
|| @paramter cb The callback to be fired
|| @return this MenuItem
*/
MenuItem &onChangeFrom(cb_change cb)
{
cb_onChangeFrom = cb;
return *this;
}
/*
|| @description
|| | Set a callback to be fired after any 'move' function is called with this item
|| | as the resulting MenuItem.
|| #
||
|| @paramter cb The callback to be fired
|| @return this MenuItem
*/
MenuItem &onChangeTo(cb_change cb)
{
cb_onChangeTo = cb;
return *this;
}
/*
|| @description
|| | Set a callback to be fired when 'moveUp' is called with this item as the current MenuItem
|| #
||
|| @paramter cb The callback to be fired
|| @return this MenuItem
*/
MenuItem &onUp(cb_move cb)
{
cb_onUp = cb;
return *this;
}
/*
|| @description
|| | Set a callback to be fired when 'moveDown' is called with this item as the current MenuItem
|| #
||
|| @paramter cb The callback to be fired
|| @return this MenuItem
*/
MenuItem &onDown(cb_move cb)
{
cb_onDown = cb;
return *this;
}
/*
|| @description
|| | Set a callback to be fired when 'moveLeft' is called with this item as the current MenuItem
|| #
||
|| @paramter cb The callback to be fired
|| @return this MenuItem
*/
MenuItem &onLeft(cb_move cb)
{
cb_onLeft = cb;
return *this;
}
/*
|| @description
|| | Set a callback to be fired when 'moveRight' is called with this item as the current MenuItem
|| #
||
|| @paramter cb The callback to be fired
|| @return this MenuItem
*/
MenuItem &onRight(cb_move cb)
{
cb_onRight = cb;
return *this;
}
/*
|| @description
|| | Set a callback to be fired when 'use' is called with this item as the current MenuItem
|| #
||
|| @paramter cb The callback to be fired
|| @return this MenuItem
*/
MenuItem &onUse(cb_use cb)
{
cb_onUse = cb;
return *this;
}
//manipulate the value
/*
|| @description
|| | Get the value for this item
|| #
||
|| @return the value
*/
inline int getValue() const
{
return value;
}
/*
|| @description
|| | Set the value for this item
|| #
||
|| @parameter v the new value
*/
void setValue(int v); //placed in cpp because of the use of the menu backend
/*
|| @description
|| | Increment the value of this item
|| #
||
|| @parameter v increment by v
|| @return the value
*/
int increment(int v = 1);
/*
|| @description
|| | Decrement the value of this item
|| #
||
|| @parameter v decrement by v
|| @return the value
*/
int decrement(int v = 1);
/*
|| @description
|| | Check to see if an item is equal to this
|| #
||
|| @parameter mi the item to check against this item
|| @return true if they have the same name
*/
bool isEqual(MenuItem &mi)
{
//TODO extend equality test
return (menuTestStrings(getName(), mi.getName()) && getValue() == mi.getValue() && getBack() == mi.getBack());
}
protected:
const char* name;
const char shortkey;
int value;
unsigned char level;
MenuItem *before;
MenuItem *right;
MenuItem *after;
MenuItem *left;
MenuItem *back;
cb_change cb_onChangeFrom;
cb_change cb_onChangeTo;
cb_move cb_onBack;
cb_move cb_onUp;
cb_move cb_onDown;
cb_move cb_onLeft;
cb_move cb_onRight;
cb_use cb_onUse;
MenuBackend *menuBackend;
private:
friend class MenuBackend;
MenuItem *moveBack()
{
return back;
}
MenuItem *moveUp()
{
if (before)
{
before->back = this;
}
return before;
}
MenuItem *moveDown()
{
if (after)
{
after->back = this;
}
return after;
}
MenuItem *moveLeft()
{
if (left)
{
left->back = this;
}
return left;
}
MenuItem *moveRight()
{
if (right)
{
right->back = this;
}
return right;
}
//no dependant inclusion of string or cstring
bool menuTestStrings(const char *a, const char *b)
{
while (*a)
{
if (*a != *b)
{
return false;
}
b++;
a++;
}
return true;
}
};
class MenuBackend
{
public:
MenuBackend(cb_use menuUse, cb_change menuChange = 0, cb_item_change itemChange = 0) : root("MenuRoot")
{
current = &root;
cb_menuChange = menuChange;
cb_menuUse = menuUse;
cb_itemChange = itemChange;
}
/*
|| @description
|| | Get the root of this menu
|| #
||
|| @return the root item
*/
MenuItem &getRoot()
{
return root;
}
/*
|| @description
|| | Get the current item of this menu
|| #
||
|| @return the current item
*/
MenuItem &getCurrent()
{
return *current;
}
/*
|| @description
|| | Move back in the menu structure, will fire move event
|| #
*/
void moveBack()
{
if (current->cb_onBack)
{
MenuMoveEvent mme = { *current };
(*current->cb_onBack)(mme);
}
setCurrent(current->getBack());
}
/*
|| @description
|| | Move up in the menu structure, will fire move event
|| #
*/
void moveUp()
{
if (current->cb_onUp)
{
MenuMoveEvent mme = { *current };
(*current->cb_onUp)(mme);
}
setCurrent(current->moveUp());
}
/*
|| @description
|| | Move down in the menu structure, will fire move event
|| #
*/
void moveDown()
{
if (current->cb_onDown)
{
MenuMoveEvent mme = { *current };
(*current->cb_onDown)(mme);
}
setCurrent(current->moveDown());
}
/*
|| @description
|| | Move left in the menu structure, will fire move event
|| #
*/
void moveLeft()
{
if (current->cb_onLeft)
{
MenuMoveEvent mme = { *current };
(*current->cb_onLeft)(mme);
}
setCurrent(current->moveLeft());
}
/*
|| @description
|| | Move right in the menu structure, will fire move event
|| #
*/
void moveRight()
{
if (current->cb_onRight)
{
MenuMoveEvent mme = { *current };
(*current->cb_onRight)(mme);
}
setCurrent(current->moveRight());
}
/*
|| @description
|| | Use an item
|| #
||
|| @parameter item is the item to use
*/
void use(MenuItem &item)
{
setCurrent(&item);
use();
}
/*
|| @description
|| | Use an item per its shortkey
|| #
||
|| @parameter shortkey the shortkey of the target item
*/
void use(char shortkey)
{
recursiveSearch(shortkey, &root);
use();
}
/*
|| @description
|| | Use an item, will fire use event
|| #
*/
void use()
{
//current->use();
if (current->cb_onUse)
{
MenuUseEvent mue = { *current };
(*current->cb_onUse)(mue);
}
if (cb_menuUse)
{
MenuUseEvent mue = { *current };
cb_menuUse(mue);
}
}
/*
|| @description
|| | Select an item, will fire change event
|| #
*/
void select(MenuItem &item)
{
setCurrent(&item);
}
/*
|| @description
|| | Move relatively in the menu hierarchy level
|| #
||
|| @parameter levels number of levels to move
*/
void moveRelativeLevels(int levels)
{
int move = levels + current->getLevel();
moveToLevel(move);
}
/*
|| @description
|| | Move to a specific menu hierarchy level
|| #
||
|| @parameter level the target level to go to
*/
void moveToLevel(unsigned char level)
{
MenuItem *cur = current;
recursiveSearchForLevel(level, current);
if (cur != current)
{
MenuChangeEvent mce = { *cur, *current };
if (cur->cb_onChangeFrom)
{
(*cur->cb_onChangeFrom)(mce);
}
if (current->cb_onChangeTo)
{
(*current->cb_onChangeTo)(mce);
}
if (cb_menuChange)
{
(*cb_menuChange)(mce);
}
}
}
/*
|| @description
|| | Fire a callback for change
|| #
||
|| @parameter mi the item that changed
*/
void fireItemChangedEvent(MenuItem *mi)
{
if (cb_itemChange)
{
MenuItemChangeEvent mice = { *mi };
(*cb_itemChange)(mice);
}
}
private:
void setCurrent(MenuItem *next)
{
if (next)
{
MenuChangeEvent mce = { *current, *next };
if (current->cb_onChangeFrom)
{
(*current->cb_onChangeFrom)(mce);
}
if (next->cb_onChangeTo)
{
(*next->cb_onChangeTo)(mce);
}
if (cb_menuChange)
{
(*cb_menuChange)(mce);
}
current = next;
}
}
void foundShortkeyItem(MenuItem *mi)
{
mi->setBack(current);
current = mi;
}
char canSearch(const char shortkey, MenuItem *m)
{
if (m == 0)
{
return 0;
}
else
{
if (m->getShortkey() == shortkey)
{
foundShortkeyItem(m);
return 1;
}
return -1;
}
}
void rSAfter(const char shortkey, MenuItem *m)
{
if (canSearch(shortkey, m) != 1)
{
rSAfter(shortkey, m->getAfter());
rSRight(shortkey, m->getRight());
rSLeft(shortkey, m->getLeft());
}
}
void rSRight(const char shortkey, MenuItem *m)
{
if (canSearch(shortkey, m) != 1)
{
rSAfter(shortkey, m->getAfter());
rSRight(shortkey, m->getRight());
rSBefore(shortkey, m->getBefore());
}
}
void rSLeft(const char shortkey, MenuItem *m)
{
if (canSearch(shortkey, m) != 1)
{
rSAfter(shortkey, m->getAfter());
rSLeft(shortkey, m->getLeft());
rSBefore(shortkey, m->getBefore());
}
}
void rSBefore(const char shortkey, MenuItem *m)
{
if (canSearch(shortkey, m) != 1)
{
rSRight(shortkey, m->getRight());
rSLeft(shortkey, m->getLeft());
rSBefore(shortkey, m->getBefore());
}
}
void recursiveSearch(const char shortkey, MenuItem *m)
{
if (canSearch(shortkey, m) != 1)
{
rSAfter(shortkey, m->getAfter());
rSRight(shortkey, m->getRight());
rSLeft(shortkey, m->getLeft());
rSBefore(shortkey, m->getBefore());
}
}
void rSLAfter(const unsigned char level, MenuItem *m)
{
if (m)
{
if (m->getLevel() == level)
{
setCurrent(m);
}
else
{
rSLAfter(level, m->getAfter());
rSLRight(level, m->getRight());
rSLLeft(level, m->getLeft());
}
}
}
void rSLRight(const unsigned char level, MenuItem *m)
{
if (m)
{
if (m->getLevel() == level)
{
setCurrent(m);
}
else
{
rSLAfter(level, m->getAfter());
rSLRight(level, m->getRight());
rSLBefore(level, m->getBefore());
}
}
}
void rSLLeft(const unsigned char level, MenuItem *m)
{
if (m)
{
if (m->getLevel() == level)
{
setCurrent(m);
}
else
{
rSLAfter(level, m->getAfter());
rSLLeft(level, m->getLeft());
rSLBefore(level, m->getBefore());
}
}
}
void rSLBefore(const unsigned char level, MenuItem *m)
{
if (m)
{
if (m->getLevel() == level)
{
setCurrent(m);
}
else
{
rSLRight(level, m->getRight());
rSLLeft(level, m->getLeft());
rSLBefore(level, m->getBefore());
}
}
}
void recursiveSearchForLevel(const unsigned char level, MenuItem *m)
{
if (m)
{
rSLAfter(level, m->getAfter());
rSLRight(level, m->getRight());
rSLLeft(level, m->getLeft());
rSLBefore(level, m->getBefore());
}
}
MenuItem root;
MenuItem *current;
cb_change cb_menuChange;
cb_use cb_menuUse;
cb_item_change cb_itemChange;
};
#endif
// MENUBACKEND_H