El hacer un programa en modo texto para GNU/Linux en C++ que muestre un menú con una barra que se controle con las flechas del teclado implica más trabajo del que se requiere para lograr lo mismo si nuestro programa fuera a ejecutarse en un sistema operativo basado en DOS (ya sea MS-DOS, Windows, etc.)
A continuación muestro una posible solución de como lograrlo sin utilizar ncurses, únicamente códigos de escape, esto tiene la pequeña desventaja de que no funciona para cualquier terminal, pero para xterm y algunas otras no debe haber problema
Crearemos una fución que espere a que el usuario pulse una tecla y regrese el valor correspondiente a esa tecla, la función debe tomar en cuenta las teclas que al ser pulsadas regresan tres valores, siendo el primer valor el que corresponde a la tecla ESC (que es el 27)
int lee_caracter()
{
struct termios config_ant;
struct termios config_nueva;
int caracter;
tcgetattr(STDIN_FILENO, &config_ant);
config_nueva = config_ant; /* Guarda los valores actuales de configuración de la terminal */
config_nueva.c_lflag &= ~(ICANON | ECHO); /* resetea las banderas canonical y echo para que no se muestre en pantalla lo que se teclea*/
tcsetattr(STDIN_FILENO, TCSANOW, &config_nueva); /* Establece los nuevos valores para la terminal */
caracter = getchar();
// si se pulsa una tecla que empieza con un código de escape
if (caracter==27)
{
caracter=getchar();
if (caracter==91)
caracter=getchar(); //caracter ahora tiene el valor de la tecla pulsada
}
tcsetattr(STDIN_FILENO, TCSANOW, &config_ant); /* Vuelve a poner los valores originales de configuración de la terminal */
return caracter;
}
Los atributos y colores que podemos manejar son los siguientes:
#define RESET 0
#define BRIGHT 1
#define DIM 2
#define UNDERLINE 3
#define BLINK 4
#define REVERSE 7
#define HIDDEN 8
#define BLACK 0
#define RED 1
#define GREEN 2
#define YELLOW 3
#define BLUE 4
#define MAGENTA 5
#define CYAN 6
#define WHITE 7
Necesitamos también una función que nos permita establecer el atributo, color de la fuente y color de fondo que vamos a utilizar antes de escribir un texto
void ColorTexto(int atributo, int texto, int fondo)
{
std::string color = "\033["+toString(atributo)+";"+toString(texto+30)+";"+toString(fondo+40)+"m";
cout<<color;
}
Este programa utiliza las dos funciones descritas anteriores para mostrar un menú vertical, se asume una terminal de 80 columnas
/*
Debido a que este programa usa el archivo de cabecera termios.h,
sólo funciona en sistemas operativos que cumplan con el estándar POSIX
Es decir, cualquier distribución de GNU/Linux o cualquier Unix.
*/
#include <iostream>
#include <sstream> /* Para poder usar istringstream */
#include <string>
#include <termios.h> /* Para poder usar las estructuras termios */
#include <stdio.h> /* Para poder usar getchar() */
#include <iomanip> /* Para poder usar setw */
#define RESET 0
#define BRIGHT 1
#define DIM 2
#define UNDERLINE 3
#define BLINK 4
#define REVERSE 7
#define HIDDEN 8
#define BLACK 0
#define RED 1
#define GREEN 2
#define YELLOW 3
#define BLUE 4
#define MAGENTA 5
#define CYAN 6
#define WHITE 7
const std::string default_console = "\033[0m";
using std::cout;
using std::endl;
using std::setw;
template <typename T>
static std::string toString (T numero)
{
std::ostringstream ss;
ss << numero;
return ss.str();
}
void ColorTexto(int atributo, int texto, int fondo);
int lee_caracter();
void ImprimeMenu(std::string menu[], int tamanio, int op);
int main()
{
std::string str_menu[4] = {"1. Registrar", "2. Consultar", "3. Modificar", "4. Salir "};
int tecla, opcion=0, seleccion=1;
do
{
ImprimeMenu(str_menu,4,seleccion);
tecla = lee_caracter();
switch (tecla)
{
case 10:
// Enter
opcion = seleccion;
break;
case 65:
// Flecha arriba
seleccion = (seleccion==1 ? 4 : seleccion-1);
opcion = 0;
break;
case 66:
// Flecha abajo
seleccion = (seleccion==4 ? 1 : seleccion+1);
opcion = 0;
break;
default:
opcion = tecla-48;
if (opcion>=1 && opcion<=4)
{
seleccion = opcion;
ImprimeMenu(str_menu,4,seleccion);
}
}
if (opcion>=1 && opcion<=4)
{
cout<<"Opcion "<<opcion<<endl;
lee_caracter();
}
}
while (opcion!=4);
return 0;
}
void ColorTexto(int atributo, int texto, int fondo)
{
std::string color = "\033["+toString(atributo)+";"+toString(texto+30)+";"+toString(fondo+40)+"m";
cout<<color;
}
int lee_caracter()
{
struct termios config_ant;
struct termios config_nueva;
int caracter;
tcgetattr(STDIN_FILENO, &config_ant);
config_nueva = config_ant; /* Guarda los valores actuales de configuración de la terminal */
config_nueva.c_lflag &= ~(ICANON | ECHO); /* resetea las banderas canonical y echo para que no se muestre en pantalla lo que se teclea*/
tcsetattr(STDIN_FILENO, TCSANOW, &config_nueva); /* Establece los nuevos valores para la terminal */
caracter = getchar();
// si se pulsa una tecla que empieza con un código de escape
if (caracter==27)
{
caracter=getchar();
if (caracter==91)
caracter=getchar(); //caracter ahora tiene el valor de la tecla pulsada
}
tcsetattr(STDIN_FILENO, TCSANOW, &config_ant); /* Vuelve a poner los valores originales de configuración de la terminal */
return caracter;
}
void ImprimeMenu(std::string menu[], int tamanio, int op)
{
int x=0, pos;
unsigned int longitud=0;
// Obtiene la longitud de la opción del menú con más caracteres
for (x=0; x<tamanio; ++x)
{
if (menu[x].length() > longitud)
longitud = menu[x].length();
}
pos = (80 - longitud) / 2;
// Limpia la pantalla
cout << "\033[2J\033[1;1H";
for (x=0; x<tamanio; ++x)
{
// Deja el número de espacios adecuado para que el menú quede centrado (se asume una pantalla de 80 caracteres)
cout<<default_console;
cout<<setw(pos)<<" ";
if (x+1==op)
ColorTexto(BRIGHT,YELLOW,MAGENTA);
else
ColorTexto(BRIGHT,WHITE,BLUE);
cout<<menu[x]<<endl;
}
cout<<default_console<<endl;
}

Ahora la versión para mostrar el menú en forma horizontal
/*
Debido a que este programa usa el archivo de cabecera termios.h,
sólo funciona en sistemas operativos que cumplan con el estándar POSIX
Es decir, cualquier distribución de GNU/Linux o cualquier Unix.
*/
#include <iostream>
#include <sstream> /* Para poder usar istringstream */
#include <string>
#include <termios.h> /* Para poder usar las estructuras termios */
#include <stdio.h> /* Para poder usar getchar() */
#include <iomanip> /* Para poder usar setw */
#define RESET 0
#define BRIGHT 1
#define DIM 2
#define UNDERLINE 3
#define BLINK 4
#define REVERSE 7
#define HIDDEN 8
#define BLACK 0
#define RED 1
#define GREEN 2
#define YELLOW 3
#define BLUE 4
#define MAGENTA 5
#define CYAN 6
#define WHITE 7
const std::string default_console = "\033[0m";
using std::cout;
using std::endl;
using std::setw;
template <typename T>
static std::string toString (T numero)
{
std::ostringstream ss;
ss << numero;
return ss.str();
}
void ColorTexto(int atributo, int texto, int fondo);
int lee_caracter();
void ImprimeMenu(std::string menu[], int tamanio, int op);
int main()
{
std::string str_menu[4] = {"1. Registrar", "2. Consultar", "3. Modificar", "4. Salir"};
int tecla, opcion=0, seleccion=1;
do
{
ImprimeMenu(str_menu,4,seleccion);
tecla = lee_caracter();
switch (tecla)
{
case 10:
// Enter
opcion = seleccion;
break;
case 67:
// Flecha derecha
seleccion = (seleccion==4 ? 1 : seleccion+1);
opcion = 0;
break;
case 68:
// Flecha izquierda
seleccion = (seleccion==1 ? 4 : seleccion-1);
opcion = 0;
break;
default:
opcion = tecla-48;
if (opcion>=1 && opcion<=4)
{
seleccion = opcion;
ImprimeMenu(str_menu,4,seleccion);
}
}
if (opcion>=1 && opcion<=4)
{
cout<<"Opcion "<<opcion<<endl;
lee_caracter();
}
}
while (opcion!=4);
return 0;
}
void ColorTexto(int atributo, int texto, int fondo)
{
std::string color = "\033["+toString(atributo)+";"+toString(texto+30)+";"+toString(fondo+40)+"m";
cout<<color;
}
int lee_caracter()
{
struct termios config_ant;
struct termios config_nueva;
int caracter;
tcgetattr(STDIN_FILENO, &config_ant);
config_nueva = config_ant; /* Guarda los valores actuales de configuración de la terminal */
config_nueva.c_lflag &= ~(ICANON | ECHO); /* resetea las banderas canonical y echo para que no se muestre en pantalla lo que se teclea*/
tcsetattr(STDIN_FILENO, TCSANOW, &config_nueva); /* Establece los nuevos valores para la terminal */
caracter = getchar();
// si se pulsa una tecla que empieza con un código de escape
if (caracter==27)
{
caracter=getchar();
if (caracter==91)
caracter=getchar(); //caracter ahora tiene el valor de la tecla pulsada
}
tcsetattr(STDIN_FILENO, TCSANOW, &config_ant); /* Vuelve a poner los valores originales de configuración de la terminal */
return caracter;
}
void ImprimeMenu(std::string menu[], int tamanio, int op)
{
int x=0, pos;
unsigned int longitud=0;
// Obtiene la longitud total del menú dejando dos espacios entre cada opción
for (x=0; x<tamanio; ++x)
{
longitud += menu[x].length();
if (x!=tamanio-1)
longitud +=2;
}
pos = (80 - longitud) / 2;
// Limpia la pantalla
cout << "\033[2J\033[1;1H";
// Deja el número de espacios adecuado para que el menú quede centrado (se asume una pantalla de 80 caracteres)
cout<<setw(pos)<<" ";
for (x=0; x<tamanio; ++x)
{
if (x+1==op)
ColorTexto(BRIGHT,YELLOW,MAGENTA);
else
ColorTexto(BRIGHT,WHITE,BLUE);
cout<<menu[x];
if (x!=tamanio-1)
{
ColorTexto(BRIGHT,WHITE,BLUE);
cout<<" ";
}
}
cout<<default_console<<endl;
}
