Uses a bit-per-pixel LCD (ST7920) or console output to demonstrate use of the menu system.
#include <boost/property_tree/info_parser.hpp>
#include <iostream>
#include <chrono>
#include <thread>
#include <assert.h>
#include <boost/exception/diagnostic_information.hpp>
#include <boost/exception/errinfo_errno.hpp>
#include <boost/program_options.hpp>
#include <csignal>
#include <assert.h>
std::sig_atomic_t quit = 0;
void signalHandler(int) {
quit = 1;
}
class MenuViews;
void toggleVisibility(
) {
}
void toggleState(
) {
}
class Menus {
void makeRootMenuItem(menu::MenuAccess &ma);
void makeSubMenuItem(
menu::MenuAccess &ma,
int x,
int y
);
void makeBackMenuItem(menu::MenuAccess &ma);
public:
for (int l0 = 0; l0 < 16; ++l0) {
for (int l1 = 0; l1 < 16; ++l1) {
std::ostringstream oss;
oss << "Sub " << l0 << '-' << l1;
subs[l0][l1] = menu::Menu::make(oss.str());
}
}
{
for (int i = 0; i < 8; ++i) {
makeSubMenuItem(ma, iconFont, i, 0);
}
{
"Initially invisible",
menu::MenuItem::Invisible
);
"Disabled",
menu::MenuItem::Invisible | menu::MenuItem::Disabled
);
"Another toggle",
menu::MenuItem::Invisible | menu::MenuItem::Toggle |
menu::MenuItem::ToggledOn
);
"Show invisible", menu::MenuItem::Toggle
);
tog->choseConnect(std::bind(
&menu::MenuItem::toggle, std::placeholders::_3
));
tog->choseConnect(std::bind(
&toggleVisibility, std::placeholders::_3, invis
));
tog->choseConnect(std::bind(
&toggleVisibility, std::placeholders::_3, indis
));
tog->choseConnect(std::bind(
&toggleVisibility, std::placeholders::_3, intog
));
intog->choseConnect(std::bind(
&menu::MenuItem::toggle, std::placeholders::_3
));
}
ma.
append(menu::GenericMenuItem::make(
"Does nothing - 0123456789 - really long"
));
}
for (int l0 = 0; l0 < 16; ++l0) {
for (int l1 = 0; l1 < 16; ++l1) {
menu::MenuAccess ma(subs[l0][l1]);
std::ostringstream oss;
if (l1 < 15) {
makeSubMenuItem(ma, iconFont, l0, l1 + 1);
}
if (l1 > 0) {
makeSubMenuItem(ma, iconFont, l0, l1 - 1);
}
if (l0 < 15) {
makeSubMenuItem(ma, iconFont, l0 + 1, l1);
}
if (l0 > 0) {
makeSubMenuItem(ma, iconFont, l0 - 1, l1);
}
makeBackMenuItem(ma);
makeRootMenuItem(ma);
}
}
}
return root;
}
return subs[x][y];
}
};
class MenuViews {
public:
private:
void setMenu() {
);
if (view) {
curr = view;
}
}
public:
MenuViews(
const Menus &menus,
) :
renderer(strcache, menu::renderers::BppMenuRenderer::InvertSelected),
strcache(sc),
psgen("/", ">")
{
renderer.
iconDimensions(strcache->font()->estimatedMaxCharacterSize());
}
root = curr = menu::MenuView::make(menus.rootMenu());
root->context() = this;
for (int l0 = 0; l0 < 16; ++l0) {
for (int l1 = 0; l1 < 16; ++l1) {
subs[l0][l1]->context() = this;
}
}
if (input) {
inputHandlers = input->makeConnectedHandlers();
inputHandlers->connect(
std::bind(&MenuViews::inputUp, this, std::placeholders::_2)
);
inputHandlers->connect(
os::EventTypeCode(EV_KEY, KEY_PAGEUP),
std::bind(&MenuViews::inputPageUp, this, std::placeholders::_2)
);
inputHandlers->connect(
os::EventTypeCode(EV_KEY, KEY_DOWN),
std::bind(&MenuViews::inputDown, this, std::placeholders::_2)
);
inputHandlers->connect(
os::EventTypeCode(EV_KEY, KEY_PAGEDOWN),
std::bind(&MenuViews::inputPageDown, this, std::placeholders::_2)
);
inputHandlers->connect(
os::EventTypeCode(EV_KEY, KEY_HOME),
std::bind(&MenuViews::inputHome, this, std::placeholders::_2)
);
inputHandlers->connect(
os::EventTypeCode(EV_KEY, KEY_END),
std::bind(&MenuViews::inputEnd, this, std::placeholders::_2)
);
inputHandlers->connect(
os::EventTypeCode(EV_KEY, KEY_ENTER),
std::bind(&MenuViews::inputChose, this, std::placeholders::_2)
);
inputHandlers->connect(
os::EventTypeCode(EV_KEY, KEY_LEFT),
std::bind(&MenuViews::inputBack, this, std::placeholders::_2)
);
inputHandlers->connect(
os::EventTypeCode(EV_KEY, KEY_RIGHT),
std::bind(&MenuViews::inputForward, this, std::placeholders::_2)
);
inputHandlers->connect(
os::EventTypeCode(EV_KEY, KEY_ESC),
std::bind(&signalHandler, 0)
);
}
}
void inputUp(int val) {
if (val > 0) {
curr->forward(val);
}
}
void inputPageUp(int val) {
if (val > 0) {
}
}
void inputDown(int val) {
if (val > 0) {
curr->backward(val);
}
}
void inputPageDown(int val) {
if (val > 0) {
}
}
void inputHome(int val) {
if (val > 0) {
curr->jumpToFirst();
}
}
void inputEnd(int val) {
if (val > 0) {
curr->jumpToLast();
}
}
void inputChose(int val) {
if (val > 0) {
curr->chose();
}
}
void inputBack(int val) {
if (val > 0) {
back();
}
}
void inputForward(int val) {
if (val > 0) {
forward();
}
}
void back() {
setMenu();
}
void forward() {
setMenu();
}
setMenu();
}
void changePage(int x, int y) {
setMenu();
}
void changeToRoot() {
setMenu();
}
return strcache;
}
std::string pathString() const {
}
return root;
}
return subs[x][y];
}
return curr;
}
};
MenuViews *GetMenuView(menu::MenuView &view) {
return boost::any_cast<MenuViews*>(view.context());
}
void Menus::makeRootMenuItem(menu::MenuAccess &ma) {
gmi->choseConnect(std::bind(
&MenuViews::changeToRoot,
std::bind(&GetMenuView, std::placeholders::_1)
));
}
void Menus::makeSubMenuItem(
menu::MenuAccess &ma,
int x,
int y
) {
std::ostringstream oss;
oss << "Goto Sub " << x << '-' << y;
gmi->icon(iconFont->tryGet((x + y * 4) % 30 + 1));
gmi->choseConnect(std::bind(
(void(MenuViews::*)(int,int))&MenuViews::changePage,
std::bind(&GetMenuView, std::placeholders::_1),
x,
y
));
}
void Menus::makeBackMenuItem(menu::MenuAccess &ma) {
gmi->choseConnect(std::bind(
&MenuViews::back,
std::bind(&GetMenuView, std::placeholders::_1)
));
}
void runtest(
MenuViews &views,
) try {
menu::MenuOutput menuout;
int theight;
{
tcache->font()->estimatedMaxCharacterSize();
views.stringCache()->font()->estimatedMaxCharacterSize();
int lines = (disp->height() - mdest.
y) / cdim.
h + 1;
menuout.attach(views.rootView(), lines);
views.renderer.maxVisible(lines);
views.psgen.maxLength((disp->width() * 2) / tdim.
w);
frame.invertLines(tdim.
h, 1);
}
int cnt = 0;
while ((input || (++cnt < 48)) && !quit) {
{
menu::MenuOutputAccess moa(menuout);
if (moa.changed()) {
views.renderer.render(menuimg, moa);
frame.write(menuimg, mdest);
frame.clearLines(0, theight);
if (titleDim.
w > frame.width()) {
titleSrc.
x = titleDim.
w - frame.width();
titleDim.
w = frame.width();
}
frame.write(
title,
titleSrc,
titleDim
);
disp->write(&frame);
}
}
if (input) {
std::this_thread::sleep_for(std::chrono::milliseconds(32));
views.view()->update();
menuout.attach(views.view());
} else {
std::this_thread::sleep_for(std::chrono::seconds(2));
if ((cnt & 15) == 8) {
views.back();
menuout.attach(views.view());
views.view()->forward();
views.view()->update();
} else if (cnt & 1) {
views.view()->chose();
views.view()->update();
menuout.attach(views.view());
} else {
views.view()->backward(2);
views.view()->update();
}
}
}
quit = true;
} catch (...) {
std::cerr << "Test failed in runtest():\n" <<
boost::current_exception_diagnostic_information()
<< std::endl;
quit = true;
}
void doPoll(os::Poller &poller)
try {
while (!quit) {
poller.wait(std::chrono::milliseconds(64));
}
} catch (...) {
std::cerr << "Test failed in doPoll():\n" <<
boost::current_exception_diagnostic_information() << std::endl;
quit = true;
}
int main(int argc, char *argv[])
try {
std::string devpath, mfontpath, tfontpath, miconpath, confpath, lcdname;
int dispW, dispH;
bool grabinput = false, uselcd = false;
std::string imgpath(argv[0]);
{
int found = 0;
while (!imgpath.empty() && (found < 3)) {
imgpath.pop_back();
if (imgpath.back() == '/') {
++found;
}
}
imgpath += "images/";
}
{
boost::program_options::options_description optdesc(
"Options for bit-per-pixel menu test"
);
optdesc.add_options()
(
"help,h",
"Show this help message"
)
(
"width,x",
boost::program_options::value<int>(&dispW)->
default_value(144),
"Display width in pixels"
)
(
"height,y",
boost::program_options::value<int>(&dispH)->
default_value(32),
"Display height in pixels"
)
(
"input,i",
boost::program_options::value<std::string>(&devpath),
"Input device path, typically /dev/input/event[0-9]+. If "
"unspecified, pre-programmed input will be used."
)
(
"grab,g",
"Request exclusive access to the input device. Intended to "
"prevent input from showing up on the same console that has this "
"program's output."
)
(
"tfont",
boost::program_options::value<std::string>(&tfontpath)->
default_value(imgpath + "font_Vx7.bppia"),
"Title font file"
)
(
"mfont",
boost::program_options::value<std::string>(&mfontpath)->
default_value(imgpath + "font_Vx8B.bppia"),
"Menu font file"
)
(
"icons",
boost::program_options::value<std::string>(&miconpath)->
default_value(imgpath + "menuicons.bppia"),
"Menu icon image file"
)
(
"st7920",
"Use a graphic ST7920 LCD"
)
(
"conf,c",
boost::program_options::value<std::string>(&confpath)->
default_value("samples/pins.conf"),
"Pin configuration file; required if LCD used"
)
(
"lcdname",
boost::program_options::value<std::string>(&lcdname)->
default_value("lcdGraphic"),
"Name of LCD inside pin configuration"
)
;
boost::program_options::variables_map vm;
boost::program_options::store(
boost::program_options::parse_command_line(argc, argv, optdesc),
vm
);
boost::program_options::notify(vm);
if (vm.count("help")) {
std::cout << "Test of bit-per-pixel menu\n" <<
argv[0] << " [options]\n" << optdesc << std::endl;
return 0;
}
if (vm.count("grab")) {
grabinput = true;
}
if (vm.count("st7920")) {
uselcd = true;
}
}
std::signal(SIGINT, &signalHandler);
std::signal(SIGTERM, &signalHandler);
std::thread inputPolling;
if (!devpath.empty()) {
try {
einput = os::EvdevInput::make(devpath);
std::cerr << "Failed to open device file " << devpath << std::endl;
return 2;
std::cerr << "Failed to initalize libevdev, error code " <<
*boost::get_error_info<boost::errinfo_errno>(eie) << std::endl;
return 3;
}
einput->usePoller(poller);
inputPolling = std::thread(&doPoll, std::ref(poller));
if (grabinput) {
while (einput->value(os::EventTypeCode(EV_KEY, KEY_ENTER)) != 0) {
std::this_thread::sleep_for(std::chrono::milliseconds(32));
}
if (!einput->grab()) {
std::cerr << "Failed to grab input device." << std::endl;
}
}
}
));
tfontpath
));
menuicons.
load(miconpath);
{
std::shared_ptr<duds::hardware::interface::DigitalPort> port;
if (uselcd) {
boost::property_tree::ptree tree;
boost::property_tree::read_info(confpath, tree);
pc.
parse(tree.get_child(
"pins"));
std::shared_ptr<duds::hardware::devices::displays::ST7920> lcd =
std::make_shared<duds::hardware::devices::displays::ST7920>(
);
lcd->initialize();
} else {
disp = std::make_shared<duds::hardware::devices::displays::SimulatedBppDisplay>(dispW, dispH);
}
Menus menus(mfontCache->font());
MenuViews views(menus, mfontCache, einput);
runtest(disp, std::ref(tfontCache), std::ref(views), std::ref(einput));
}
std::cout << "Title font string cache image size: " << tfontCache->bytes() <<
" bytes in " << tfontCache->strings() << " strings.\n"
"Menu font string cache image size: " << mfontCache->bytes() << " bytes in " <<
mfontCache->strings() << " strings." << std::endl;
if (einput) {
inputPolling.join();
}
} catch (...) {
quit = true;
std::cerr << "Test failed in main():\n" <<
boost::current_exception_diagnostic_information() << std::endl;
return 1;
}