C ++ текстовое меню выбора в Linux ведет себя странно?

Код немного длинный, но это только потому, что я прокомментировал все так что это очень легко читать. По сути, это простое текстовое меню выбора, над которым я работаю.
Вы должны быть в Linux и иметь компилятор C ++ 11 для правильной работы. Вот код (полностью функциональный пример, готовый к компиляции):

#include <string>
#include <vector>
#include <iostream>
#include <unistd.h>
#include <sys/ioctl.h>
#include "raw_terminal.h" // for setting the terminal to raw mode
using namespace std;

/* Simple escape sequences to control the cursor and colors on the screen */
#define CLI_HIDE_CUR            "\033[?25l"#define CLI_SHOW_CUR            "\033[?25h"#define CLI_SAVE_CUR_POS        "\033[s"#define CLI_REST_CUR_POS        "\033[u"#define CLI_CLR_LAST_LINE       "\033[A\033[2K"#define CLI_MV_CUR_UP           "\033[A"#define CLI_MV_CUR_DN           "\033[B"#define CLI_DEFAULT_COLOR       "\033[0m"#define CLI_FGROUND_BLACK       "\033[0;30m"#define CLI_FGROUND_RED         "\033[0;31m"#define CLI_FGROUND_GREEN       "\033[0;32m"#define CLI_FGROUND_BROWN       "\033[0;33m"#define CLI_FGROUND_BLUE        "\033[0;34m"#define CLI_FGROUND_MAGENTA     "\033[0;35m"#define CLI_FGROUND_CYAN        "\033[0;36m"#define CLI_FGROUND_LIGHTGREY   "\033[0;37m"#define CLI_BOLD                "\033[0;1m"#define CLI_BGROUND_BLACK       "\033[7;30m"#define CLI_BGROUND_RED         "\033[7;31m"#define CLI_BGROUND_GREEN       "\033[7;32m"#define CLI_BGROUND_BROWN       "\033[7;33m"#define CLI_BGROUND_BLUE        "\033[7;34m"#define CLI_BGROUND_MAGENTA     "\033[7;35m"#define CLI_BGROUND_CYAN        "\033[7;36m"#define CLI_BGROUND_LIGHTGREY   "\033[7;37m"

/* Centers a string in a 'width' character wide terminal
by appending spaces before and after the string */
auto centerText(string& text, int width) -> void;

/* Creates a selection menu on the screen */
auto selectionMenu(std::vector<std::string> items) -> int;

int main() {
std::vector<string> items;
items.push_back("Menu item one");
items.push_back("Menu item two");
items.push_back("Menu item three");

int selection = selectionMenu(items);
if (selection == -1) return 0;
cout << "You selected: " << items[selection] << endl;

cin.get();

return 0;
}

auto centerText(string& text, int width) -> void {
size_t len = text.length();
if (width <= len+1) return;
for (int i=0; i<(width - len)/2; i++) text.insert(text.begin(), ' ');
for (int i=0; i<(width - len)/2+len%2; i++) text.push_back(' ');
return;
}

auto selectionMenu(std::vector<std::string> items) -> int {
/* This stuff is required to get the width of the terminal
in order to center the text on the screen with centerText() */
struct winsize w;
ioctl(0, TIOCGWINSZ, &w);

/* Hide the cursor, initialize some variables */
cout << CLI_HIDE_CUR << CLI_DEFAULT_COLOR << endl;
int selection = 0, prevSelection, key;

/* Center the menu items on the screen */
for (const auto& s : items) centerText(s, w.ws_col);

/* Print out the menu items */
for (const auto& s : items) cout << s << endl;

/* Highlight the first item */
for (int i=0; i<items.size(); i++) cout << CLI_MV_CUR_UP;
cout << CLI_BGROUND_BROWN << items[selection] << endl;

/* Configure stuff so that we're able to retrieve raw keystrokes from stdin */
raw_terminal::setRawTerminal();

/* If the enter key is down, wait until it's released.
This prevents the user from accidentally selecting
an item after hitting enter in a previous menu. */
while (getchar() == '\n') { usleep(1000); }

/* Main loop */
while (1) {
key = getchar();

/* We're only interested in escape sequences (starting with '\033') */
if (key == '\033') {

/* If nothing comes after the escape character, then esc was pressed, so we quit. */
if (getchar() == -1) {
selection = -1;
goto MENU_END;
}

/* Get the next character in the received sequence */
key = getchar();

/* up arrow */
if (key == 65) {
prevSelection = selection;
selection--;
}

/* down arrow */
else if (key == 66) {
prevSelection = selection;
selection++;
}

/* If (first item - 1) or (last item + 1) is selected, loop around */
if (selection < 0) selection = items.size()+selection;
if (selection > items.size()-1) selection -= items.size();

/* Draw the previously selected line with the default colors */
cout << CLI_MV_CUR_UP << CLI_DEFAULT_COLOR << items[prevSelection] << endl;
cout << CLI_MV_CUR_UP;

/* Move the cursor to the new selection */
if (selection < prevSelection) for (int i=0; i<(prevSelection-selection); i++) cout << CLI_MV_CUR_UP;
if (selection > prevSelection) for (int i=0; i<(selection-prevSelection); i++) cout << CLI_MV_CUR_DN;

/* Draw the newly selected line with the highlighting color */
cout << CLI_BGROUND_BROWN << items[selection] << endl;

}

/* If the retrieved key is not an escape sequence, check whether it's the enter key.
If so, break the main loop and return the selected item's number. */
else if (key == '\n') break;
}

MENU_END:
cout << CLI_DEFAULT_COLOR;

/* Position the cursor below the menu to continue */
for (int i=0; i<items.size()-selection; i++) cout << CLI_MV_CUR_DN;

/* Unhide the cursor, and set the terminal back to normal mode */
cout << CLI_SHOW_CUR << endl;
raw_terminal::restoreTerminal();

/* Return selected item's number */
return selection;
}

И это raw_terminal.h:

#ifndef _RAW_TERMINAL_H_
#define _RAW_TERMINAL_H_

#include <cstring>
#include <iostream>
#include <termios.h>

class raw_terminal {
public:
static void setRawTerminal() {
/* set the terminal to raw mode */
if (isRaw) return;
tcgetattr(fileno(stdin), &orig_term_attr);
memcpy(&new_term_attr, &orig_term_attr, sizeof(struct termios));
new_term_attr.c_lflag &= ~(ECHO|ICANON);
new_term_attr.c_cc[VTIME] = 0;
new_term_attr.c_cc[VMIN] = 0;
tcsetattr(fileno(stdin), TCSANOW, &new_term_attr);
isRaw = true;
}
static void restoreTerminal() {
tcsetattr(fileno(stdin), TCSANOW, &orig_term_attr);
isRaw = false;
}
private:
static struct termios orig_term_attr;
static struct termios new_term_attr;
static bool isRaw;
};

struct termios raw_terminal::orig_term_attr;
struct termios raw_terminal::new_term_attr;
bool raw_terminal::isRaw = false;

#endif

Это работает просто отлично, если вы используете только стрелки вверх / вниз. Но если вы нажмете влево или вправо (что ничего не должно делать), это полностью испортит экран, дублируя пункты меню и прочее.
Я думаю, проблема не в коде, потому что он полностью игнорирует левую и правую клавиши. Это терминал, который делает что-то, когда те нажаты, я думаю. Так, как я мог предотвратить это?

Любая помощь будет оценена. Спасибо!

2

Решение

Вау, это меня шокировало. Я решил проблему, включив в программу стрелки со стрелками влево и вправо:

if (key == 65 || key == 68) // ...
if (key == 66 || key == 67) // ...

Таким образом, левая и правая клавиши обычно изменяют выбор.
Первоначально я хотел игнорировать эти клавиши и использовать только вверх / вниз, но это все же лучше, чем заставлять экран сходить с ума.

1

Другие решения


По вопросам рекламы [email protected]