Forum: Projekte & Code Kleine Menüerweiterung für MCURSES


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Niels J. (niels)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
N'Abend,

dann will ich hier mal meine Menüerweiterung für MCURSES (Dank an 
der Stelle an Frank M. (ukw)) publik machen:

Im Anhang, seht ihr einmal einen Screenshot in Arbeit. Nicht sonderlich 
spektakulär, aber wie ich finde doch sehr praktisch um z.B. ein 
umfangreicheres Interface zu realisieren oder Funktionen gezielt 
aufzurufen, wenn man sich gerade in ein neues Hardware Modul 
einarbeitet.

Zum Code:
In das Projekt müssen nur die beiden Dateien menu.h und menu.c 
eingebunden werden, diese sollten komplett unabhängig von der Hardware 
laufen. Natürlich muss auch MCurses eingebunden werden ;)
Um ein Menü zu erstellen muss dann ein Array vom Typ menu_entries 
erstellt werden. menu_entries setzt sich aus folgenden Elementen 
zusammen:

char  text[20]
  Der Name des Menü-Elements. Kann bei Bedarf natürlich auch in
char*  text
 geändert werden. Da ich Menüeinträge teilweise aber auch dynamisch 
erstelle war mir das Hantieren mit malloc irgendwann zu blöd.

int  entry_number_of_entries
  Dieser Wert hat mehrere Funktionen: Wenn der Eintrag im Menü auf ein 
Untermenü verweist, müssen hier die Anzahl der Eiträge im Untermenü 
angegeben werden. Verweist der Eintrag direkt auf eine Funktion muss 
eine 1 angegeben werden. Ist der Wert 0 passiert nichts bei der Auswahl, 
war ganz praktisch um Dummy Einträge zu erstellen, als ich die 
Scrollfunktion eingebaut habe.
MenuFnct  menu_function
  Hier muss der Name der aufzurufenden Funktion angegeben werden. Die 
Funktion muss vom Typ void Funktion(void) sein, Parameter werden also 
keine Übergeben. Verweist der Menüeintrag auf ein Untermenü muss die 
Funktion show_menu angeben werden. Wenn entry_number_of_entries 0 ist, 
muss hier nichts eingetragen werden.

struct  menu_entries *new_menu_entries
  Wenn der Eintrag auf ein Untermenü verweist, muss hier auf das 
aufzurufende menu_entries Array verwiesen werden.

Hier einmal ein Beispiel für ein solches Array:
 menu_entries main_menu[] =
{
    {"Main Menu"},
    {"LEDs",        LEDS_MENU_ENTRIES,show_menu, leds_menu},
    {"WLAN",        init_wlan},
    {"Speicher",    SPEICHER_MENU_ENTRIES, show_menu, speicher_menu},
    {"Mikrofon",    inti_mic}
};
Achja: Im ersten Eintrag des Arrays braucht nur der text angegeben zu 
werden, dies ist dann der Titel des Menüs.


Folgende vier Funktionen werden verwendet:
void menu_up(void)
  Aufzurufen, um im Menü einen Eintrag nach oben zu gehen. Ist man schon 
im obersten Menüpunkt, wird zum letzten gesprungen.
void menu_down(void)
  Wie menu_up() nur eben nach unten. Ganz unten wird auch wieder nach 
oben gesprungen.
void menu_select(void)
  Der aktuell ausgewählte Menüpunkt wird ausgeführt. Es kann also 
entweder ein Untermenü geöffnet werden, eine Funktion aufgerufen werden, 
oder es passiert gar nichts.
void show_menu(void)
  Diese Funktion ist ganz zu Anfang aufzurufen und gibt das Menü zum 
ersten mal aus.


Das #define LAST_ROW ist dafür verantwortlich, bis zu welcher Zeile eine 
Menü angezeigt wird. Da MCurses für gewöhnlich 24 Zeilen hat, habe ich 
erstmal 23 angegeben, um die unterste Zeile noch für Anzeigen o.Ä. frei 
zu lassen.


Die Einbindung im Hauptprogramm kann dann beispielsweise so aussehen:
int main(void)
{
    if (mcurses_is_up){
        ch = getch ();

        switch (ch)
        {
            case 'm':       show_main_menu();       break;
            case KEY_UP:    menu_up();            break;
            case KEY_DOWN:  menu_down();          break;
            case KEY_CR: case KEY_RIGHT: menu_select();        break;
        }
    }
}

void show_main_menu(void)
{
    current_number_of_entries = MAIN_MENU_ENTRIES;
    current_menu_entries = main_menu;
    show_menu();
}
Sollte soweit selbsterklärend sein, denke ich. Mit einem Druck auf "m" 
kommt man erstmals und auch später wieder zurück zum Hauptmenü.


Ich habe das ganze im Augenblick auf einem STM32F407Discovery über USB 
und Putty am laufen, und konnte auch nach längerem Testen keine Fehler 
mehr finden. Selbstredend ist konstruktive Kritik erwünscht, denn ich 
bin mir sicher, dass da noch einiges zu verbessern ist. Etwaige fehlende 
Optimierungen bitte ich zu entschuldigen, ich war bis vor kurzen noch 
Matlab und nahezu endlose Rechenkapazität gewohnt, da verliert man 
irgendwann das Auge dazu ;) Aber unter Anderem auch deswegen 
veröffentliche ich es ja auch hier^^

Also, freut mich, falls ich damit jemandem behilflich sein kann, bis 
dahin

beste Grüße

Niels

von Frank M. (ukw) (Moderator) Benutzerseite


Bewertung
0 lesenswert
nicht lesenswert
Hi Niels,

schöne Anwendung!

Ich habe den Source mal überflogen und hätte da noch einen 
Erweiterungsvorschlag:

Es gibt offenbar noch keine Menu-Back-Funktion, also zurück zum 
vorherigen Menu? Es könnte ja sein, dass ich mal versehentlich in ein 
Untermenü springe. Wie komme ich da wieder raus? Okay, ich könnte immer 
mit Taste 'm' zum Hauptmenü, aber das halte ich bei Schachtelungstiefen 
größer 2 nicht gerade sehr intuitiv.

Wäre doch eine kleinere Erweiterung wert, z.B. könnte man diese 
Funktionalität auf KEY_LEFT oder KEY_BACKSPACE (oder beides) legen. 
Abbruch aus dem Hauptmenü ist dann halt Abbruch des kompletten Menüs.

Oder habe ich da etwas übersehen? Ok, man müsste das zuvor besuchte Menü 
auf einem Stack-Speicher ablegen. Oder das Menü macht selbst auch den 
Tasten-Input und ruft sich selbst rekursiv auf, wenn man ein Untermenü 
betritt....

Naja, könnte beliebig kompliziert werden. Vielleicht lassen wir das doch 
;-)

Gruß,

Frank

: Bearbeitet durch Moderator
von Niels J. (niels)


Bewertung
0 lesenswert
nicht lesenswert
N'Abend,

das mit der Menü zurück Funktion macht mir auch immernoch Gedanken... 
Augenblicklich habe ich es beispielsweise folgendermaßen gelöst:
 menu_entries speicher_menu[] =
{
    {"Speicher"},
    {"..", MAIN_MENU_ENTRIES,   show_menu},
    {"Mount", 1, mount_fat},
    {"Open Storage", 1, open_root_in_menu},
    {"Check storage",1, check_storage},
    {"Unmount",1, unmount_fat}
};

...

int main(void)
{
speicher_menu[1].new_menu_entries = main_menu;
...
case KEY_CR: case KEY_RIGHT: menu_select();        break;
...
}

Den Zurück Eintrag also per Hand eingefügt. Leider kann ich die 
Menü-Eintrags-Arrays nicht von vorne herein rekursiv definieren, 
deswegen dieser m.M. unschöne Weg über main.
Tatsächlich hatte ich einmal testweise eine automatische Zurück Funktion 
mit dabei, allerdings hat die sich dann in einer "zurück-Schleife" 
gefangen. Wobei... Wenn ich bei einem show_menu() dem dann neuen 
current_menu Array automatisch an zweite Stelle den Zeiger zum 
aufrufenden menu_entries Array speichere, dies aber nur mache, wenn 
menu_position größer als 1 ist, sollte ich das abfangen können. Ich 
glaube, das könnte klappen, da setze ich mich doch gleich morgen mal ran 
:D

Beste Grüße

Niels

von Niels J. (niels)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
und die menu.h Datei dazu...

: Bearbeitet durch User
von Niels J. (niels)


Angehängte Dateien:

Bewertung
0 lesenswert
nicht lesenswert
Ach Mist, da ist ja gerade was schief gelaufen...

Also nochmal. Folgendes hat sich geändert/ist neu:
void menu_back(void)
Mit dieser Funktion kommt ihr wieder in das übergeordnete Menü
void menu_first(void)
Mit dieser Funktion springt ihr wieder zum Anfang des aktuellen Menüs.

Damit die menu_back() Funktion richtig arbeiten kann, müssen zwei Dinge 
beachtet werden:

1. Ganz zu Anfang muss das aller erste Menü, also für gewöhnlich das 
Main-Menü folgendermaßen fest gelegt werden:
first_menu = main_menu;
first_menu ist in der menu.h so definiert: "extern menu_entries 
*first_menu;", main_menu ist mein oberstes Menü.

2. Jedes weitere Untermenü davon muss als ersten Eintrag nach dem Namen, 
also effektiv dem zweiten, als text etwas wie ".." oder "zurück" haben, 
und als entry_number_of_entries die Anzahl der Einträge des ihm 
übergeordneten Menüs.
Klingt komisch, wird aber hoffentlich im Beispiel im Anhang etwas 
deutlicher.

Auch die Steuerung kann dadurch ein wenig erweitert werden:
    if (mcurses_is_up){
        ch = getch ();

        switch (ch)
        {
            case 'm':       show_main_menu();       break;
            case KEY_UP:    menu_up();            break;
            case KEY_DOWN:  menu_down();          break;
            case KEY_CR: case KEY_RIGHT: menu_select();        break;
            case KEY_LEFT: case KEY_BACKSPACE: menu_back(); break;
            case KEY_HOME: menu_first(); break;
        }
    }

Beste Grüße

Niels

PS.:

Ignoriert die main.c, die habe ich aus Versehen hier hochgeladen...

: Bearbeitet durch User

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.