Consultas, desarrollo de programas y petición de presupuestos:

jueves, 12 de julio de 2012

listas enlazadas dobles: aplicación en Menus


Fichero de cabecera: menu.h
/*
* menu.h
*
* Created on: 13/04/2011
* Author: juanon
*/

#ifndef MENU_H_
#define MENU_H_

#define KB_ENTER "\n" // codigos que generan una serie de teclas especiales: KB_
#define KB_ESC "\e"
#define KB_UP "\e[A" // los codigos los podemos ver en un terminal Alt+Ctrl+F1, y pulsar tecla , antes de hacer login y veremos como se genera
#define KB_DOWN "\e[B"
#define KB_LEFT "\e[D"
#define KB_RIGHT "\e[C"
#define KB_REPAG "\e[5~"
#define KB_AVPAG "\e[6~"

struct menuLista { // NODO :estructuras dinamicas: inicialmente no tienen tamaño pero a medida que necesitamos se puede ampliar.
char *cad; //dato
struct menuLista *sig;//puntero siguiente
struct menuLista *ant;//puntero anterior
//la ventaja de utilizar NODO es que no necesitamos saber el nº de elementos como nos pasaria en una tabla
} info;

// Comunes
void escribirAnchoFijo(int ancho, char *texto); // dado un ancho, cortara el texto si es mayor o rellenara con espacios blancos si el texto es menor que el ancho
// menuSimple
int menuSimpleHorizontal(int col, int fil, char **opc, int colorLetra, int colorFondo, int opcInicial); // muestra :en la columna incial, fila, tabla de las opciones, color de letra, color de fondo, y una posible opcion inicial
int menuSimpleVertical(int col, int fil, char **opc, int colorLetra, int colorFondo, int opcInicial,int conMargen);// en este ademas le ponemos un margen alrededor del menu.
// menuLista
struct menuLista *menuListaLeerArchivo(char *nombreFichero, int ancho); // coge el archivo y lo lee en el ancho que me interesa y te crea la lista
int menuListaVertical(int col, int fil, int ancho, int alto, struct menuLista *inicio, int colorLetra, int colorFondo); // el que ejecuta el menu azul: lista que lee para crear el menu
void menuListaDestruir(struct menuLista *); // destruye la lista: toda memoria que reservemos cuando no la uso hay que borrarla

#endif /* MENU_H_ */



Fichero principal: menu.c

#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <string.h>
#include "menu.h"

int ej_menuSimple() {
char *opciones[]={"cero","uno","dos","tres","cuatro","cinco","seis","siete","ocho","otro ocho","diez",""}; // matriz con su valores, la opcion 11 (la ultima tiene que ser siempre "", para saber que esa es la ultima)
int resp1 = 0;
int resp2 = 0;

noCursor(); //oculta el cursor
resp1 = menuSimpleHorizontal(1,1,opciones,WHITE,GREEN,2); //ver linea 48 lista de opciones definida en la 8, y la opcion por defecto es la 2 (que es la tercera opcion)
resp2 = menuSimpleVertical(1,2,opciones,WHITE,RED,3,1); //letra blanca, fondo rojo, opcion por defecto 3 (que es la 4 en la lista), y el 1 indica un ancho de borde 1
siCursor();
textcolor(LIGHTGRAY);
gotoxy(1,1);
printf("Valor devuelto por menú horizontal: %d\n", resp1);
printf("Valor devuelto por menú vertical: %d\n", resp2);
return 0;
}

int ej_menuLista() {
int resp = 0;
struct menuLista *menu; // creo una variable llamada menu, que es un puntero a una estrucutra menulista
menu = NULL; // le doy valor NULL: menu apunte a ningun sitio, a nulo

if (!(menu = menuListaLeerArchivo("ejemplo.txt", 25))) { // ir leyendo el contenido del archivo y fabricar la lista
// y asigno al puntero menu el inicio de la lista.
printf("\n ERROR: No se pudo abrir el archivo especificado");
exit(1);
}
noCursor();
resp = menuListaVertical(10, 5, 25, 12, menu, WHITE, BLUE);
menuListaDestruir(menu);
siCursor();
textcolor(LIGHTGRAY);
gotoxy(1, 3);
printf("Valor devuelto por menuLista: %d\n", resp);
return 0;
}

int main() {
ej_menuSimple();
ej_menuLista();
return 0;
}
// comentario de documentacion: descripcion corta y terminada en ".", la descripcion larga es del punto en adelante
/**
* Muestra y ejecuta un menú simple vertical.
* @param col Coordenada X de la esquina superior izquierda del menú
* @param fil Coordenada Y de la esquina superior izquierda del menú
* @param opc Array de opciones a mostrar, debe terminar en una opción vacía
* @param colorLetra Color usado para el texto del menú y para el fondo de la opción resaltada
* @param colorFondo Color usado para el fondo del menú y para el texto de la opción resaltada
* @param opcInicial Opción resaltada inicial
* @param conMargen Usar margen adicional rodeando el menú
* @return Nº de opción seleccionada o -1 si se pulsó ESC
*/
int menuSimpleVertical(int col, int fil, char **opc, int colorLetra, int colorFondo, int opcInicial,int conMargen) {
char teclas[10]="";
char buffer[25];
int c = 0;
int ancho = 0;
int cantidadOpciones;

// Calcula ancho necesario
for(c = 0; *opc[c]; c++) { // recorre todos los valores del array opc, mientras que el contenido de opc tenga algo recorre cuando encuentra "", termina
if(strlen(opc[c]) > ancho) {
ancho = strlen(opc[c]);
}
}
// Total de opciones
cantidadOpciones = c - 1; // c lo ha ido contando en el for, y le resto 1, pues la ultima era ""
if(opcInicial > cantidadOpciones) {
opcInicial = cantidadOpciones;
}

// Recuadro de margen
if(conMargen) {
textcolor(colorLetra);
textbackground(colorFondo);
sprintf(buffer,"%%%ds", ancho + 4);// Crear una cadena de formato: buffer="%%%ds",ancho+4 (si ancho vale=12): %%: muestra %,d=nº entero,s=es una letra: buffer="%16s"
for(c = 0;c <= cantidadOpciones+2; c++) {
gotoxy(col, fil + c);
printf(buffer," "); // crea un recuadro printf("%16s"," ");
}
fil++;
col+=2;
}
// Bucle principal, termina al pulsar ESC o ENTER
while(strcmp(teclas, KB_ESC) != 0 && strcmp(teclas, KB_ENTER) != 0) { // se ha plsado alguna tecla que no es escape ni enter
// Muestra el menu
textcolor(colorLetra);
textbackground(colorFondo);
for(c = 0; *opc[c]; c++) { // for como antes hasta encontar opc=""
gotoxy(col, fil + c);
escribirAnchoFijo(ancho, opc[c]); // me interesa que ilumine todo el ancho fijo del menu
}
// Muestra la opcion resaltada
textcolor(colorFondo);
textbackground(colorLetra);
gotoxy(col,fil + opcInicial); // me voy donde esta iluminada
escribirAnchoFijo(ancho, opc[opcInicial]); //la dibujo

fflush(stdout);// Necesario ya que ninguna opción contiene '\n', de esta manera hacemos forzosamente salga a pantalla. Vacia el buffer de pantalla y lo escribe todo.

// Espera la pulsación de una tecla
IniciaTeclado(); //gestionar la pulsacion de tecla sin entrar enter
c = 1;
teclas[0] = getch(); // recupero la tecla que hay
while(kbhit()) {
teclas[c++] = getch(); // rescata el resto de caracteres por si fuese una secuencia \e[C, \e[A
}
teclas[c] = '\0'; // fin de cadena
TerminaTeclado();

// Procesa la tecla pulsada
if(strcmp(teclas, KB_UP) == 0) { //tecla arriba,
opcInicial --; // subo
}
if(strcmp(teclas, KB_DOWN) == 0) { // tecla abajo
opcInicial ++; /bajo
}
// Asegura que la opción está dentro de los límites
if(opcInicial < 0) { // me salgo por arriba
opcInicial = cantidadOpciones; //entro por debajo, no hace tope
}
if(opcInicial > cantidadOpciones) { //me salgo por abajo
opcInicial = 0; // reentro por arriba
}
} //salida de teclas ESC o ENTER
// Si se salió con ESC, se devolverá -1
if(strcmp(teclas, KB_ESC) == 0) {
opcInicial = -1;
}
return opcInicial; // DEVUELVE LA OPCION QUE ESTA ILUMINADA
}
// DOCUMENTACION DEL MENU SIMPLE HORIZONTAL
/**
* Muestra y ejecuta un menú simple horizontal.
* @param col Coordenada X de la esquina superior izquierda del menú
* @param fil Coordenada Y de la esquina superior izquierda del menú
* @param opc Array de opciones a mostrar, debe terminar en una opción vacía
* @param colorLetra Color usado para el texto del menú y para el fondo de la opción resaltada
* @param colorFondo Color usado para el fondo del menú y para el texto de la opción resaltada
* @param opcInicial Opción resaltada inicial
* @return Nº de opción seleccionada o -1 si se pulsó ESC
*/
int menuSimpleHorizontal(int col, int fil, char **opc, int colorLetra, int colorFondo, int opcInicial) {
char teclas[10]="";
int c = 0;
int ancho = 0;
int cantidadOpciones;

// Calcula ancho necesario
for(c = 0; *opc[c]; c++) {
if(strlen(opc[c]) > ancho) {
ancho = strlen(opc[c]);
}
}

// Total de opciones
cantidadOpciones = c - 1;
if(opcInicial > cantidadOpciones) {
opcInicial = cantidadOpciones;
}

// Bucle principal, termina al pulsar ESC o ENTER
while(strcmp(teclas, KB_ESC) != 0 && strcmp(teclas, KB_ENTER) != 0) {
// Muestra el menu
textcolor(colorLetra);
textbackground(colorFondo);
for(c = 0; *opc[c]; c++) {
gotoxy(col + ancho * c, fil);
escribirAnchoFijo(ancho, opc[c]);
}
// Muestra la opcion resaltada
textcolor(colorFondo);
textbackground(colorLetra);
gotoxy(col + ancho * opcInicial,fil);
escribirAnchoFijo(ancho, opc[opcInicial]);

fflush(stdout); // Necesario ya que ninguna opción contiene \n

// Espera la pulsación de una tecla
IniciaTeclado();
c = 1;
teclas[0] = getch();
while(kbhit()) {
teclas[c++] = getch();
}
teclas[c] = '\0';
TerminaTeclado();

// Procesa la tecla pulsada
if(strcmp(teclas, KB_LEFT) == 0) {
opcInicial --;
}
if(strcmp(teclas, KB_RIGHT) == 0) {
opcInicial ++;
}
// Asegura que la opción está dentro de los límites
if(opcInicial < 0) {
opcInicial = cantidadOpciones;
}
if(opcInicial > cantidadOpciones) {
opcInicial = 0;
}
}
// Si se salió con ESC, se devolverá -1
if(strcmp(teclas, KB_ESC) == 0) {
opcInicial = -1;
}
return opcInicial;
}

/**
* Escribe una cadena de caracteres de ancho fijo, rellena de bancos o corta la cadena según sea necesaario.
* @param ancho
* @param texto
*/
void escribirAnchoFijo(int ancho, char *texto) {
int c = 0;
int largoTexto = strlen(texto); //ALMACENO LA LONGITUD QUE TIENE EL TEXTO: nota strlen cuenta á como 1 byte, pero son mas
char *salida;

if(!(salida = (char *) malloc (sizeof(char) * ancho + 1))) { // RESERVO MEMORIA DE ANCHO+1 (que es el /0), NOTA: sizeof(char)
printf("Memoria insuficiente\n"); // si falla la reserva de memoria
exit(1);
}
while(c < ancho) {
if(c < largoTexto) { // imprimo el caracter
*(salida + c) = texto[c]; //
if(texto[c] == -61) // antes una letra siempre ocupaba un byte, pero actalmente al tener letras acentuadas, pueden usarse varios bytes para codificala
ancho++; // si el primer bye es 61 sabemos que ocupa 2 byte, y es una letra acentuada (áéíóuñ)
if(texto[c] < 32 && texto[c] >= 0) { // caracteres especiales ASCII: 0-31, que no son imprimibles ESC ENTER
*(salida + c) = ' '; //LO SUSTITUYE POR UN ESPACIO EN BLANCO
}
} else { //CUANDO C es igual o mayor que la longitud largoTexto
*(salida + c) = ' '; //relleno con espacios en blanco hasta llegar a ancho
}
c++;
}
*(salida + c) = '\0'; // se ha metido \0, fin de cadena
printf("%s", salida); //TODO EN UN PRINTF: VALOR + ESPACIOS
free(salida); // libero memoria: ya que salida no la vuelvo a necesitar.
}

/**
* Crea una lista doblemente enlazada con el contenido de un fichero.
* @param nombreFichero Fichero con el contenido del menú
* @param ancho Ancho de cada línea
* @return Puntero al primer nodo de la lista
*/
struct menuLista *menuListaLeerArchivo(char *nombreFichero, int ancho) {
FILE *f;
struct menuLista *aux, *inicio, *final; //creado estos 3 punteros
inicio = NULL; // primer puntero marca a NULL, nos va a servir donde empieza la secuencia de nodo
//puntero final: nos va a apuntar donde esta el ultimo nodo // puntero auxliar, creas el nodo
f = fopen(nombreFichero, "r");
if (!f)
return NULL; // no se ha podido abrir el archivo
else {
while (!feof(f)) { // miestras que no sea final de fichero
if (!(aux = (struct menuLista *) malloc(sizeof(struct menuLista)))) { // crea en un sitio de la memoria, ha reservado el espacio para que quepa una estructura tipo menulista
printf("\n Memoria insuficiente ");
exit(1);
}
if(!(aux->cad = (char *) malloc(ancho + 1))) { // reservamos la memoria para la cadena de caracteres
printf("\n Memoria insuficiente ");
exit(1);
}
fgets(aux->cad, ancho + 1, f); // lee del fichero, y almacenara en el cap de la estructura aux, la primera linea que lee del fichero
if(feof(f)) { //liberamos la cadena y el auxiliar.
free(aux->cad); // 1º libero la cadena (el orden es importante)
free(aux); // 2º libero el nodo,
break;
}

if (inicio == NULL) // si es la primera línea (no tiene nungun nodo)
inicio = aux; // engancho el nodo a la lista
else {
final->sig = aux;
aux->ant = final; //marca la estructura anterior
}
final = aux; // 1º pasada como es el unico nodo que hay, es el primero como el ultimo, apunto al bloque completo
// en la 2º pasada, final marcara a la 2º estructura
} // volvemos a leer el fichero
inicio->ant = NULL; // primer elmento punntero inicial marca a null
final->sig = NULL; // ultimo elemento puntero final marca a null
}
fclose(f);

return inicio; // devuelvo el puntero donde se inicia la estructura.
}

/**
* Muestra y ejecuta un menú vertical basado en una lista doblemente enlazada.
* @param col Coordenada X de la esquina superior izquierda del menú
* @param fil Coordenada Y de la esquina superior izquierda del menú
* @param ancho Ancho de cada opción del menú
* @param alto Altura del menu en líneas
* @param inicio Puntero al primer nodo de la lista
* @param colorLetra Color usado para el texto del menú y para el fondo de la opción resaltada
* @param colorFondo Color usado para el fondo del menú y para el texto de la opción resaltada
* @return Nº de opción seleccionada o -1 si se pulsó ESC
*/
int menuListaVertical(int col, int fil, int ancho, int alto, struct menuLista *inicio, int colorLetra, int colorFondo) {
struct menuLista *aux, *encendido, *priPanta, *ultPanta;
register int i; // contador para bucles
char op[10] = "\0"; // tecla pulsaada
int redibujar = 1;
register int linea_actual = 0; // linea actual
int c = 0;
aux = encendido = priPanta = ultPanta = inicio; /* iniciamos los valores para que apunte inicio */

// Mueve "ultPanta" "alto" posiciones más adelante que inicio
for (i = 0; i < alto; i++) {
ultPanta = ultPanta->sig;
if (ultPanta->sig == NULL)
break;
}
ultPanta = ultPanta->ant; // retrocedemos para apuntar al puntero anterior

while ((strcmp(op, KB_ESC)) != 0 && (strcmp(op,KB_ENTER) != 0)) {
if (redibujar) {
textcolor(colorLetra);
textbackground(colorFondo);
aux = priPanta;
for (i = 0; i < alto; i++) {
gotoxy(col, fil + i);
if(aux == encendido) {
textcolor(colorFondo);
textbackground(colorLetra);
} else {
textcolor(colorLetra);
textbackground(colorFondo);
}
escribirAnchoFijo(ancho, aux->cad);
aux = aux->sig;
}
fflush(stdout);
}

IniciaTeclado();
c = 1;
op[0] = getch();
while(kbhit()) {
op[c++] = getch(); // recupera todos los caracteres pendientes
}
op[c] = '\0';
TerminaTeclado();

// Arriba
if (strcmp(op, KB_UP) == 0) {
if (encendido == inicio) {
redibujar = 0;
} else {
linea_actual--;
redibujar = 1;
if(encendido == priPanta) {
priPanta = priPanta->ant;
ultPanta = ultPanta->ant;
encendido = encendido->ant;
} else {
encendido = encendido->ant;
}
}
}
// Abajo
if (strcmp(op, KB_DOWN) == 0) {
if (encendido->sig == NULL) {
redibujar = 0;
} else {
linea_actual++;
redibujar = 1;
if(encendido == ultPanta) {
priPanta = priPanta->sig;
ultPanta = ultPanta->sig;
encendido = encendido->sig;
} else {
encendido = encendido->sig;
}
}
}
//REPAG
if (strcmp(op, KB_REPAG) == 0) {
if (encendido == inicio) {
redibujar = 0;
} else {
redibujar = 1;
for (i = 0; i < alto; i++) {
if(encendido == inicio) {
break;
}
if(priPanta != inicio) {
priPanta = priPanta->ant;
ultPanta = ultPanta->ant;
}
encendido = encendido->ant;
linea_actual--;
}
}
}
//AVPAG
if (strcmp(op, KB_AVPAG) == 0) {
if (encendido->sig == NULL) {
redibujar = 0;
} else {
redibujar = 1;
for (i = 0; i < alto; i++) {
if(encendido->sig == NULL) {
break;
}
if (ultPanta->sig != NULL) {
priPanta = priPanta->sig;
ultPanta = ultPanta->sig;
}
encendido = encendido->sig;
linea_actual++;
}
}
}
aux = priPanta;
} // termina while
if(strcmp(op, KB_ESC) == 0) {
linea_actual = -1;
}
return linea_actual;
} // fin de la función

/**
* Libera la memoria asigana a la lista del menú
* @param inicio Puntero al primer nodo de la lista
*/
void menuListaDestruir(struct menuLista *inicio) {
struct menuLista *aux, *aux2;

for (aux = inicio; aux->sig != NULL; aux = aux->sig) {
aux2 = aux->sig;
free(aux->cad);
free(aux);
aux = aux2;
}
}

No hay comentarios:

Publicar un comentario en la entrada