Im trying to do a basic menu in C. I'm supposed to do this with ncurses lib. I was working with this tutorial:
Video On YouTube
But mine version has some problems:
1)The menu will not print properly, it will reveal only while choosing menu items. Then the highlight won't go off
2)Option made on menu won't print on the top
Can you help me? Is that idea of Menu good or should i look for other tutorial (any help ?).
#include <stdio.h>
#include <ncurses.h>
#include <string.h>
#include <menu.h>
int main(int argc, char **argv)
{
int i, c;
char powitanie[]="SLOWNIK UNIWERSALNY";
int szer, dlug; //wartosci dlugosci i szerokosci terminalu
initscr(); //Inizjalizacja całości ncurses, kolory itp
raw();
noecho();
keypad(stdscr, TRUE);
start_color();
//init_pair(1, COLOR_BLUE, COLOR_BLACK); //wybór kolorów
getmaxyx(stdscr, szer, dlug); //pobranie rozmiarów terminalu
move(szer/2, (dlug-strlen(powitanie))/2); //przesuwamy kursor na środek (tak aby się ładnie wydrukowało)
//attron(COLOR_PAIR(1)); //Aktywujemy wybrane kolory
printw(powitanie); //Drukujemy powitanie
//attroff(COLOR_PAIR(1));//Dezaktywujemy kolory
refresh();//Odswiezamy (inaczej się nie wyswietli)
WINDOW * menuwin=newwin(6, dlug-12, szer-8, 6); //Definiujemy i tworzymy 'okno'
box(menuwin, 0, 0);
refresh();//ponownie odświeżamy aby okno się pojawiło
wrefresh(menuwin);//odświeżamy samo okno
keypad(menuwin, TRUE);//umozliwiamy dzialanie klawiatury w oknie
char *opcje[] = {
"Tlumacz z Polskiego na Angielski",
"Tlumacz z Angielskiego na Polski",
"Edystuj slownik",
"Wybierz slownik",
"Wyjdz",
};
int wybor;
int zaznacz=0;
while(1)//cala ta petla sluzy ciaglemu tworzeniu menu z podswietleniem wybranego elementu
{
for(i=0; i<5; i++)
{
if(i==zaznacz)
{
wattron(menuwin, A_REVERSE);
mvwprintw(menuwin, i+1, 1, opcje[i]);
wattroff(menuwin, A_REVERSE);
}
wybor = wgetch(menuwin);
switch(wybor)
{
case KEY_UP:
zaznacz--;
if(zaznacz==-1) zaznacz=0;//zabezpieczenie przed wyjsciem "poza" menu
break;
case KEY_DOWN:
zaznacz++;
if(zaznacz==5) zaznacz=4;
break;
default:
break;
}
if(wybor==10) break;
}
printw("Wybrano:%s", opcje[zaznacz]);
}
return(0);
}
PS: Code comments are not in English but i hope the won't be necessary
There are quite a few problems here. I have included a modified version of your code that works, and I will attempt to describe the changes.
There were some unused variables, namely argc, argv, and c, so I cast these to void in order to silence compiler warnings. You can remove the c and change to int main(void), if you like, removing these variables altogether.
I have added the stdlib.h header file to your #includes for the exit() function. This is used in the new error function, fail(), that I added to your code. You should always check the return values of any function that you call when programming in C. Here it is particularly important to check, first if the terminal supports color with the has_colors() function, and then if the call to start_color() is successful. If either of these fail, the fail() function is called with an error message, and the program exits with the EXIT_FAILURE value. The function has_colors() returns a bool, and the start_color() function returns an int (OK if successful, otherwise ERR).
Now that colors have been initialized, I see that the lower border of your menu selection window is being overwritten by the menu text. To fix this, I changed the size of your window, making it one line taller:
WINDOW * menuwin=newwin(7, dlug-12, szer-9, 6);
The fundamental problem of improper printing that you reported was because of a misplaced brace in the for loop controlling the printing of the menu items. I took the opportunity to reorganize the loop a bit; now there is only one call to mvwprintw(). The A_REVERSE attribute is set before printing if the current item is also the selected item, and it is again unset after printing.
I also changed the limit tests in the switch statement from equalities to inequalites. It is better practice to use , e.g., if (zaznacz < 0) instead of if (zaznacz == -1) in such cases.
I added a newline character to the beginning of the format string in the final printw(), since some of the selections are too long to fit in the window at the end of the title. You can move this output wherever you like.
Finally, I added a refresh() after the final printw() statement, and a getch() to wait for the user to hit ENTER before exiting the program. It is very important to cleanup by calling endwin() before exiting an NCurses program. This function reverses changes made to your terminal by NCurses while your program was running, and failure to do this can lead to terminal unpleasantries.
#include <stdio.h>
#include <ncurses.h>
#include <string.h>
#include <menu.h>
#include <stdlib.h> // added for exit() function
void fail(char *msg) {
endwin();
puts(msg);
exit(EXIT_FAILURE);
}
int main(int argc, char **argv)
{
/* Commandline argument currently unused */
(void) argc;
(void) argv;
int i, c;
(void) c; // c is currently unused
char powitanie[]="SLOWNIK UNIWERSALNY";
int szer, dlug; //wartosci dlugosci i szerokosci terminalu
initscr(); //Inizjalizacja całości ncurses, kolory itp
raw();
noecho();
keypad(stdscr, TRUE);
/* Test to see if terminal has colors */
if (has_colors() == false) {
fail("Colors unavailable\n");
}
if (start_color() != OK) {
fail("Unable to start colors\n");
}
//init_pair(1, COLOR_BLUE, COLOR_BLACK); //wybór kolorów
getmaxyx(stdscr, szer, dlug); //pobranie rozmiarów terminalu
move(szer/2, (dlug-strlen(powitanie))/2); //przesuwamy kursor na środek (tak aby się ładnie wydrukowało)
//attron(COLOR_PAIR(1)); //Aktywujemy wybrane kolory
printw(powitanie); //Drukujemy powitanie
//attroff(COLOR_PAIR(1));//Dezaktywujemy kolory
refresh();//Odswiezamy (inaczej się nie wyswietli)
WINDOW * menuwin=newwin(7, dlug-12, szer-9, 6); //Definiujemy i tworzymy 'okno'
box(menuwin, 0, 0);
refresh();//ponownie odświeżamy aby okno się pojawiło
wrefresh(menuwin);//odświeżamy samo okno
keypad(menuwin, TRUE);//umozliwiamy dzialanie klawiatury w oknie
char *opcje[] = {
"Tlumacz z Polskiego na Angielski",
"Tlumacz z Angielskiego na Polski",
"Edystuj slownik",
"Wybierz slownik",
"Wyjdz",
};
int wybor;
int zaznacz=0;
while(1)//cala ta petla sluzy ciaglemu tworzeniu menu z podswietleniem wybranego elementu
{
for(i = 0; i < 5; i++) {
if(i == zaznacz)
wattron(menuwin, A_REVERSE);
mvwprintw(menuwin, i+1, 1, opcje[i]);
if (i == zaznacz)
wattroff(menuwin, A_REVERSE);
}
wybor = wgetch(menuwin);
switch(wybor)
{
case KEY_UP:
zaznacz--;
if(zaznacz < 0) zaznacz = 0;//zabezpieczenie przed wyjsciem "poza" menu
break;
case KEY_DOWN:
zaznacz++;
if(zaznacz > 4) zaznacz = 4;
break;
default:
break;
}
if(wybor==10) break;
}
printw("\nWybrano:%s", opcje[zaznacz]);
refresh();
/* Wait for user to press enter to exit */
getch();
/* Need to cleanup before exit */
endwin();
return 0;
}
Related
I have a thread that catches a key pressed through getch and if the key pressed is arrowUp or arrowDown then it scrolls my terminal using ncurses functions (incrementing an integer variable used to show elements from a linked list). This works fine most of the times but sometimes (usually when i hold an arrow pressed) ncurses prints on terminal weird and unexpected characters like 9;32H (it seems like an uncatched input). Does anyone know how i can solve this?
Here an MCVE
#include <ncurses.h>
#include <stdlib.h>
#include <pthread.h>
#define MAX(a,b) ((a) > (b) ? (a) : (b))
void * listener(void* p){
int* shift = (int*) p;
int ch;
while (1)
{
ch = getch();
if(ch == KEY_UP){
*shift = MAX(0, *shift - 1);
}
else if(ch == KEY_DOWN){
*shift = *shift + 1;
}
}
}
int main(){
char* c = malloc(100);
for(int i = 0; i < 100; i++){
c[i] = 'A' + (random() % 26);
}
int shift = 0;
pthread_t th;
initscr();
raw();
noecho();
keypad(stdscr, TRUE);
start_color();
curs_set(0);
pthread_create(&th, NULL, listener, (void*) &shift);
while(1){
for(int j = shift; j < shift+stdscr->_maxy; ++j){
move(j-shift,0);
clrtoeol();
mvaddch(j-shift, 0, c[j]);
refresh();
}
}
endwin();
free(c);
return 0;
}
This snippet shows my issue if you hold arrow down
EDIT:
The issue seems to be related to getch() from different thread that modifies global variables of ncurses library. Does anyone know a thread-safe way to get a char input?
I actually solved my issue using getchar instead of getch since it seems not to be thread safe. I found out that getch modifies ncurses global variable and calls refresh() at the end, so if another thread is changing ncurses stuff (e.g. cursor position) your program may have an unexpected behavior (in my case printing escape characters representing set cursor position)
I use Xshell to connect to a remote host and write ncurse programs. But it was found that the characters could not be printed in a cycle to the right.
#include <ncurses.h>
int main() {
initscr();
for (int i = 0;i < 10; i++){
addch('o');
}
refresh();
getch();
endwin();
return 0;
}
Result:
Only the first character is printed.
In order to print it all out, I had to add the refresh code
for (int i = 0; i < 10;i++) {
addch('o');
refresh();//print all 'o'
}
I think the BIG problem makes the box function unavailable, because the two Horizontal line of a box are not completed, it only have the first '-' too.
int main() {
initscr();
curs_set(0);
WINDOW *win;
win = newwin(10, 40, 9, 20);
box(win, 0, 0);
refresh();
wrefresh(win);
getch();
endwin();
return 0;
}
Result:
Notice the two horizontal lines up and down
I can't figure out this problem.
2021.3.11
I have I have identified the problem. The problem is with Xshell. I connect my host by Alibaba Cloud Server official website, and the program have no problem. But I still don't konw how to set up my xshell.
Using the following code (save to test.c and compile with gcc -o test test.c -lncurses) I can draw a border, put text inside it, and also put text in the main window.
#include <ncurses.h>
// Print the character 'ch' in the window
// 'win', and do this 'repeat' times.
int print_chars(WINDOW *win, char ch, int repeat) {
if (win == NULL) {
// Print to main window
for (int i=0; i<repeat; i++) {
addch(ch);
refresh();
}
} else {
// Print to the named window
for (int i=0; i<repeat; i++) {
waddch(win, ch);
}
wrefresh(win);
}
return 0;
}
int main(int argc, char **argv)
{
WINDOW *border_win, *win;
initscr();
refresh(); // This seems to be needed;
// I don't know why.
// Create a window to show the border
border_win = newwin(10,40,9,20);
box(border_win,0,0);
wrefresh(border_win);
// Create a window inside that, which
// we will write in. Otherwise it
// seems we will overwrite the border.
win = newwin(8,38,10,21);
print_chars(win, 'a', 10);
// Write something in the main window
// as well, just to show it works
print_chars(NULL, 'o', 10);
getch();
endwin();
return 0;
}
Ncurses flickers when using "unix pipes" and "redirection" for input. That is, it draws fine if I input myself but doesn't when using '|' or '<'.
I thought this might be due to getch() delay modes(no delay, half delay and infinite delay). So I explicitly tried setting nodelay(stdscr, FALSE); but as obvious, it didn't solve it.
This is the minimal working code :
#include <ncurses.h>
#include <stdlib.h>
#include <string.h>
/* Default assumptions */
#define BUFSIZE 100
#define SELINDICATOR ">>> "
#define MAXITEMS LINES /* Decides how many items are shown at a time. By default, it's (number of rows - 1) */
/* Declarations */
static void draw(char **data, short index, short selected);
static void handleInput(short *selected, short index);
int main(int argc, char *argv[]) {
char buf[BUFSIZE], **data;
short index = 0, selected = 1;
size_t curSize = 0;
/* Get the entries */
while(fgets(buf, BUFSIZE, stdin)) {
if(!(data = realloc(data, (curSize += sizeof(char *))))) {
fprintf(stderr, "error reallocating memory!\n");
exit(1);
}
if(!(data[index] = malloc(BUFSIZE))) {
fprintf(stderr, "error reallocating memory!\n");
exit(1);
}
strcpy(data[index], buf);
index++;
}
/* Start nCurses */
initscr();
noecho();
nodelay(stdscr, FALSE); // just tryin' it out if it works
while(1) {
draw(data, index, selected);
handleInput(&selected, index);
}
/* Quit nCurses */
endwin();
/* Free allocated memories */
for(short i = 0; i < index; i++)
free(data[i]);
free(data);
return 0;
}
void
draw(char **data, short index, short selected) {
static short posX = strlen(SELINDICATOR), posY; /* posY doesn't need to be static but it makes no difference and looks cleaner */
/* Clear old garbage */
clear();
posY = 0;
/* Draw line echoing inputs */
mvaddch(posY, 0, '>');
posY++;
/* Draw the entries */
for(short i = 0; posY < COLS && i < index; i++) {
if(posY == selected) {
mvprintw(posY, 0, SELINDICATOR);
}
mvprintw(posY, posX, "%s", data[i]);
refresh();
posY++;
}
/* Make the output visible */
refresh();
}
void
handleInput(short *selected, short numOfEntries) {
int input = getch();
/* A whole bunch of other stuff........ */
endwin();
exit(0);
}
Much thanks for your efforts!
Ncurses is designed and built as a tool for providing an interactive user interface. To the extent that it reads input from from the standard input (as opposed to directly from the terminal), it is possible for an ncurses-based program to have its input redirected from a file or pipe, but it's unclear why it would be important to actually display the UI in that case. If doing so causes unwanted visual effects then the easiest mitigation might be to disable the UI in that case.
In the program presented in the question, it appears that displaying the UI is cleanly separated from reading and processing input, and that reading input relies only minimally on ncurses. It should be very straightforward to modify such a program to enable it to switch between UI and no-UI modes, and my recommendation is that you do so. To that end, you may find the isatty() function useful for determining whether the standard input (and / or standard output) is a terminal.
The example is missing something, since this function
void
handleInput(short *selected, short numOfEntries) {
int input = getch();
/* A whole bunch of other stuff........ */
endwin();
exit(0);
}
will simply exit after running once. That leaves a lot of possibilities, the most likely being that you're running this program a lot of times, causing it to initialize the screen (and on a lot of terminals, switching to/from the alternate screen). That'll flicker every time...
My LAME (v3.99.5) outputs progress in console by moving up x lines in the console and overwriting the previous lines. It's pretty cool.
I've read in a different post that such behavior for a single line can be achieved with a mere "\r" instead of "\n" - although the post was for Ruby, it seems to be the same for C on my system at least:
#include <stdio.h>
#include <time.h>
int main() {
time_t t;
time_t t2;
time(&t);
t2 = t;
printf("%u\r", (unsigned int)t);
fflush(stdout);
while (1) {
if (t2 - t > 0) {
time(&t);
printf("%u\r", (unsigned int)t);
fflush(stdout);
}
time(&t2);
}
return 0;
}
The post further suggests a curses library can be used to make the same behavior multi-line.
What would be a boilerplate example of such code in C?
According to http://falsinsoft.blogspot.com/2014/05/set-console-cursor-position-in-windows.html
Windows:
void SetCursorPos(int XPos, int YPos)
{
COORD Coord;
Coord.X = XPos;
Coord.Y = YPos;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), Coord);
}
Linux:
void SetCursorPos(int XPos, int YPos)
{
printf("\033[%d;%dH", YPos+1, XPos+1);
}
You can use something like this:
/!\ Warning: If you stop your curses program without call endwin() before, the terminal used to launch the program will have a very strange behavior.
include <curses.h>
int main(int argc, char *argv[])
{
// init curses
Xinitscr(argc, argv);
// init time
time_t t = 0, t2;
time(&t2);
// main loop
while (1) {
if (t2 - t > 0)
{
time(&t);
clear();
mvprintw(1,1, "%u", (unsigned int)t);
refresh();
}
time(&t2);
}
// end curses mode
// warning: if you do not call this at the end of your program,
// your terminal won't be usable.
endwin();
return 0;
}
The example shown by #purplepsycho has some issues, addressed in this revision (works with "any" X/Open Curses implementation):
#include <curses.h>
int main(int argc, char *argv[])
{
filter();
initscr();
// init time
time_t t = 0, t2;
time(&t2);
// main loop
while (1) {
if (t2 - t > 0)
{
time(&t);
erase();
mvprintw(1,1, "%u", (unsigned int)t);
refresh();
}
time(&t2);
}
endwin();
return 0;
}
That is:
use initscr for initializing curses (PDCurses provides a non-standard function Xinitscr which is not what OP had in mind: it certainly is not often used).
use erase rather than clear (to avoid screen-flicker):
The clear and wclear routines are like erase and werase,
but they also call clearok, so that the screen is cleared
completely on the next call to wrefresh for that window
and repainted from scratch.
use filter to keep the output on a single line. If your terminal switches to the alternate screen, the screen will appear to be cleared. There is a workaround available with ncurses (see filter.c in ncurses-examples).
Better than erase() would be wclrtoeol(), called after the mvprintw.
I have a the following piece of code using ncurses. I would like to know whether I can use a single move function to print a few lines.
For Example:
move(25,25);
printw("Line 1\n");
printw("Line 2\n");
Line 1 prints at (25,25) location but Line 2 prints at (26,0) if I don't use move(26,25). Can I avoid the second move and still print Line 2 at (26,25)????
You can define a new window if what you want to print has to be aligned. Shortly :
#include <ncurses.h>
int main()
{
WINDOW* mywin;
initscr();
cbreak();
keypad(stdscr, TRUE);
int height=15;
int width=30;
int starty=25;
int startx=25;
printw("F9 to exit");
refresh();
mywin = newwin(height, width, starty, startx);
mvwprintw(mywin,0,0,"First line\n");
wprintw(mywin,"Second line");
wrefresh(mywin);
while(getch() != KEY_F(9)) {}
endwin();
return 0;
}
If this approach does not fit, then you'll have to move manually to next position you want to print.