├── .gitignore ├── Makefile ├── ctest.c └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | ctest 2 | *.o -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # File : Makefile 3 | # Description : Build file for curses tutorial 4 | # Created : Thu Jan 4 08:41:48 EST 2024 5 | # By : Patrick Mcdaniel 6 | 7 | # 8 | # Project Protections 9 | 10 | ctest : ctest.o 11 | gcc -o $@ ctest.o -lncurses -lpanel -lmenu -lform -lm 12 | 13 | clean : 14 | rm -f ctest ctest.o 15 | -------------------------------------------------------------------------------- /ctest.c: -------------------------------------------------------------------------------- 1 | //////////////////////////////////////////////////////////////////////////////// 2 | // 3 | // File : ctest.cpp 4 | // Description : This is a set of test code for the curses library. 5 | // 6 | // Author : Patrick McDaniel 7 | // Last Modified : Thu Jan 18 16:13:39 EST 2024 8 | // 9 | 10 | // Include Files 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | // 20 | // Defines 21 | #define RGB_TO_CURSES(x) ((int)(round((float)x*3.90625))) // 1000/256 conversion 22 | #define CURSES_TO_RGB(x) ((int)(round((float)x/3.90625))) // 256/1000 conversion 23 | 24 | // 25 | // Functions 26 | 27 | //////////////////////////////////////////////////////////////////////////////// 28 | // 29 | // Function : ncurses_basic 30 | // Description : Test some basic functions of the ncurses library 31 | // 32 | // Inputs : none 33 | // Outputs : 0 if successful test, -1 if failure 34 | 35 | int ncurses_basic( void ) { 36 | 37 | // Local variables 38 | int ch, i, j, x, y, sy, sx, pos, done; 39 | short badger_red = COLOR_WHITE + 1; 40 | MEVENT event; 41 | WINDOW *ann, *win; 42 | 43 | // Print out some information to the screen 44 | printw("CTest: testing the curses function\n"); 45 | refresh(); 46 | 47 | // Get the screen dimensions, fill the screen with X's 48 | getmaxyx(stdscr, y, x); // Gets the size of the window 49 | for (i=0; i window coordinates 155 | mvprintw(12, 1, "The window coordinates are %d, %d and the screen coordinates are %d, %d", wy, wx, event.y, event.x); 156 | sx = wx; sy = wy; 157 | wmouse_trafo(win, &sy, &sx, TRUE); // Convert screen -> window coordinates 158 | mvprintw(13, 1, "The window coordinates are %d, %d(converted back)", sy, sx); 159 | clrtoeol(); 160 | } else { 161 | mvprintw(12, 1, "The screen coordinates are %d, %d and not in the window.", wy, wx); 162 | clrtoeol(); 163 | } 164 | wrefresh(win); 165 | refresh(); 166 | if (event.bstate & BUTTON1_PRESSED) { 167 | mvwprintw(win, 8, 1, "You pressed the left mouse button at %d, %d [in window? %d]", event.x, event.y, inwindow); 168 | if (inwindow == TRUE) { 169 | wmouse_trafo(win, &event.y, &event.x, FALSE); 170 | mvwaddch(win, event.y, event.x, ACS_DIAMOND | A_REVERSE | A_BOLD); 171 | wmove(win, event.y, event.x); 172 | wrefresh(win); 173 | 174 | } else { 175 | mvaddch(event.y, event.x, ACS_BLOCK | A_REVERSE | A_DIM); 176 | move(event.y, event.x); 177 | refresh(); 178 | } 179 | } 180 | if (event.bstate & BUTTON2_CLICKED) { 181 | mvwprintw(win, 9, 1, "You clicked the right mouse button at %d, %d", event.x, event.y); 182 | clrtoeol(); 183 | wrefresh(win); 184 | } 185 | if (event.bstate & REPORT_MOUSE_POSITION) { 186 | mvwprintw(win, 10, 1, "You moved the mouse to %d, %d", event.x, event.y); 187 | clrtoeol(); 188 | wrefresh(win); 189 | mvaddch(event.y, event.x, 'X' | A_REVERSE | A_BOLD); 190 | refresh(); 191 | } 192 | } 193 | 194 | } 195 | if (ch == 'q') { 196 | done = 1; 197 | } 198 | } 199 | 200 | // Clean up the window, clear the screen, and return 201 | delwin(win); 202 | erase(); 203 | return( 0 ); 204 | 205 | } 206 | 207 | //////////////////////////////////////////////////////////////////////////////// 208 | // 209 | // Function : panel_basic 210 | // Description : Test of the panel library 211 | // 212 | // Inputs : none 213 | // Outputs : 0 if successful test, -1 if failure 214 | 215 | int panel_basic( void ) { 216 | 217 | // Local variables 218 | PANEL *panels[10]; 219 | WINDOW *win, *swin; 220 | int i; 221 | 222 | // Loop through some random panels 223 | for (i=0; i<10; i++) { 224 | win = newwin(10, 20, 5+i*2, 5+i*2); 225 | box(win, 0, 0); 226 | mvwprintw(win, 1, 1, "This is window %d", i); 227 | wrefresh(win); 228 | panels[i] = new_panel(win); 229 | } 230 | 231 | // Move through the panels 232 | for (i=0; i<10; i++) { 233 | top_panel(panels[i]); 234 | update_panels(); 235 | doupdate(); 236 | getch(); 237 | } 238 | 239 | // Create a new window to demonstrate subwindows 240 | win = newwin(20, 60, 35, 5), 241 | swin = derwin(win, 10, 20, 5, 5); 242 | box(win, 0, 0); 243 | box(swin, 0, 0); 244 | mvwprintw(win, 1, 1, "This is the main window"); 245 | mvwprintw(swin, 1, 1, "Subwindow"); 246 | wrefresh(win); 247 | wrefresh(swin); 248 | getch(); 249 | werase(swin); 250 | wrefresh(swin); 251 | getch(); 252 | werase(win); 253 | wrefresh(win); 254 | getch(); 255 | refresh(); 256 | 257 | // Clean up the panels, return 258 | for (int i=0; i<10; i++) { 259 | del_panel(panels[i]); 260 | } 261 | 262 | // Return successfully 263 | return( 0 ); 264 | } 265 | 266 | //////////////////////////////////////////////////////////////////////////////// 267 | // 268 | // Function : menu_basic 269 | // Description : Test some basic functions of the menu library 270 | // 271 | // Inputs : none 272 | // Outputs : 0 if successful test, -1 if failure 273 | 274 | #define BASIC_ELEMENTS 5 275 | int menu_basic( int do_window ) { 276 | 277 | // Local variables 278 | int ch, done; 279 | ITEM *basic_items[BASIC_ELEMENTS + 1]; 280 | MENU *basic_menu; 281 | WINDOW *win; 282 | 283 | // Create the basic items, menu 284 | basic_items[0] = new_item("Item 1", "Description 1"); 285 | basic_items[1] = new_item("Item 2", "Description 2"); 286 | basic_items[2] = new_item("Item 3", "Description 3"); 287 | basic_items[3] = new_item("Item 4", "Description 4"); 288 | basic_items[4] = new_item("Item 5", "Description 5"); 289 | basic_items[5] = NULL; 290 | basic_menu = new_menu((ITEM **)basic_items); 291 | 292 | // Add some color to the menu 293 | start_color(); 294 | init_pair(1, COLOR_RED, COLOR_BLACK); 295 | init_pair(2, COLOR_BLUE, COLOR_BLACK); 296 | init_pair(3, COLOR_WHITE, COLOR_BLACK); 297 | set_menu_fore(basic_menu, COLOR_PAIR(1) | A_REVERSE); 298 | set_menu_back(basic_menu, COLOR_PAIR(2)); 299 | set_menu_grey(basic_menu, COLOR_PAIR(3)); 300 | 301 | // If we are doing window, setup the window 302 | if ( do_window == 1 ) { 303 | // Window version 304 | win = newwin(20, 60, 5, 5); 305 | box(win, 0, 0); 306 | set_menu_win(basic_menu, win); 307 | set_menu_sub(basic_menu, derwin(win, 6, 38, 3, 1)); 308 | wmove(win, 1, 1); 309 | keypad(win, TRUE); 310 | post_menu(basic_menu); 311 | wrefresh(win); 312 | } else { 313 | // Screen version 314 | keypad(stdscr, TRUE); 315 | post_menu(basic_menu); 316 | refresh(); 317 | } 318 | 319 | // Get input for the menu 320 | done = 0; 321 | while ( !done ) { 322 | ch = do_window ? wgetch(win) : getch(); 323 | switch (ch) { 324 | case KEY_DOWN: 325 | case 's': // s is up: 326 | mvprintw(9, 1, "You pressed the down arrow key"); 327 | menu_driver(basic_menu, REQ_DOWN_ITEM); 328 | break; 329 | case 'w': // w is up 330 | case KEY_UP: 331 | mvprintw(9, 1, "You pressed the up arrow key"); 332 | menu_driver(basic_menu, REQ_UP_ITEM); 333 | break; 334 | case 'q': // Quit 335 | done = 1; 336 | break; 337 | } 338 | 339 | // Do a refresh 340 | if ( do_window == 1 ) { 341 | wrefresh(win); 342 | } else { 343 | refresh(); 344 | } 345 | } 346 | 347 | // Cleanup the menu 348 | unpost_menu(basic_menu); 349 | for (int i=0; i 26 | #include 27 | 28 | To link, you will need to add the link directives associated with the libraries. For example. 29 | 30 | g++ -o ctest ctest.o -lncurses lpanel -lmenu 31 | 32 | ## Environment life-cycle 33 | 34 | There are three stages of the lifecycle of the curses environments: 35 | 36 | 1. **startup**, which is performed using the initscr() function, which accept no parameters and returns nothing. The function setup the library and go into the environment. This is required before you can do anything with the terminal. Note that you will also want to set several state variables as part of this setup (see next section). 37 | 38 | initscr(); // Refreshes stdscr 39 | 40 | 2. **running**, where we have some elements (see below) that contain text and other stuff are written to the physical interface when refresh() is called. Basically, refresh() re-displays all of the data on the screen and wresfresh() re-displays on the window. 41 | 42 | refresh(); // Refreshes stdscr 43 | wrefresh(win); // Refreshes the window 44 | 45 | 3. **shutting down the interface**. This is done with the endwin() function that frees all of the internal data structures and returns the interface to the normal terminal. This is super important to do, because if you don't the terminal will be in a messed up (and largely non-functional) state after exit. 46 | 47 | endwin(); 48 | 49 | ## Flags/initialization state and related functions. 50 | 51 | 1. **Input buffering state**. Normally the terminal buffers all of the input coming from the user. You can turn this off with raw() and cbreak(), which return the character back to the program immediately. This is most useful state when trying to program the UI. The difference between these is that raw() returns the special control characters CTRL-Z and CTRL-C to the program, whereas cbreak() retains their function. cbreak is what you will normally use 52 | 53 | cbreak(); 54 | 55 | 2. **Character echo state**. Implemented by the echo() and noecho() functions, This indicates where the terminal will echo the input character to the current cursor location. Most of the time you will want to turn this off. 56 | 57 | noecho(); 58 | 59 | 3. **Extended character enabling**. Implemented by the keypad function(), this enables things like function keys and the number pad (plus import characters like the arrow keys). This is also super important. The function accepts two parameters, one is the screen and the other is a boolean value indicating whether to enable them (TRUE) or to disable them (FALSE). 60 | 61 | keypad(stdscr, TRUE); 62 | 63 | ## Windows and cursor functions 64 | 65 | As mentioned above, windows are the basic unit of UI interface. You can create as many of these and they can overlap, stack, etc. as you need when you design the your interface. In fact, the key to designing your interface is to see what windows you need, when to create, where to place, move and decorate, and when to destroy them. The central functions are: 66 | 67 | 1. **newwin()** - this creates a new window at a location with specified dimensions. Again, remember that the coordinates and dimensions are always listed as row, column. 68 | 69 | WINDOW local_win = newwin(height, width, row, col); 70 | 71 | 2. **subwin()/derwin()** - these functions create subwindows. The difference between the two is that the coordinates passsed to subwin are relative to the screen, and the coordinates passed to derwin are relative to the parent window. 72 | 73 | WINDOW sub = subwin(win, rows, cols, y, x); // Relative to scr 74 | WINDOW sub2 = derwin(win, rows, cols, y, x); // Relative to win 75 | 76 | 2. **delwin()** - this destroys the window and but *does not* s its contents disappear (for this, see clear() and erase() below). This is just the free of the window process. 77 | 78 | wclear(win); 79 | wrefresh(win); 80 | delwin(win); 81 | 82 | One note is that if you want to clear the contents of the window, use clear()/erase() [see below], then refresh(), then delete. 83 | 84 | 3. **getmaxxy()**. This tells you the size of the current window. This is also useful when you want to know the size of the screen. This is done through the MACRO getmaxxy() (which is weird because you are kinda passing by reference but don't need a & in front of it). 85 | 86 | int x, y, sy, sx; 87 | getmaxyx(win, y, x); 88 | getmaxyx(stdscr, sy, sx); // Gets the size of the window 89 | 90 | Note that there two values assigned when the initscr() is called, LINES and COLS, which are given values for the size of the window. These are not reset of the window size changes are are generally more unreliable to use. 91 | 92 | 4. **border()/wborder()/box()** - this creates a border around the window using the default horizontal and vertical lines and corners (unless you change them). Basically, this gives this a nice look about the screen and creates a kind of window look you would see in a graphical user interface. The arguments paint the outside edge of the window with characters that you can customize (where 0 means use default). 93 | 94 | border(ls, rs, ts, bs, tl, tr, bl, br); 95 | border(0, 0, 0, 0, 0, 0, 0, 0); 96 | wborder(win, ls, rs, ts, bs, tl, tr, bl, br); 97 | box(win, 0, 0); 98 | 99 | Where: 100 | 101 | ls Starting-column side (default : ACS_VLINE) 102 | rs Ending-column side (default: ACS_VLINE) 103 | ts First-line side (default: ACS_HLINE) 104 | bs Last-line side (default: ACS_HLINE) 105 | tl Corner of the first line and the starting column (default: ACS_ULCORNER) 106 | tr Corner of the first line and the ending column (default: ACS_URCORNER) 107 | bl Corner of the last line and the starting column (default: ACS_LLCORNER) 108 | br Corner of the last line and the ending column (default: ACS_LRCORNE) 109 | 110 | In reality, however you wil almost never customize the edges. While not borders, note that there are additional functions for creating horizontal and vertical lines, mvhline() and mvvline(). 111 | 112 | 5. **erase()/werase()/clear()/wclear()** - these functions erase the contents of the screen or window. The difference between erase and clear is that the latter calls clearok(), which cause a complete clear and redraw of the entire screen following the next wrefresh(). In almost all respects the latter functions lead to the best behavior. 113 | 114 | wclear(win); 115 | 116 | 5. **getxy()/wgetxy()/move()/wmove()** - this gets and sets the location of the current cursor. The get functions are macros, so you don't have to pass things by reference. 117 | 118 | int x, y; 119 | wgetxy(win, y, x) 120 | wmove(win, 10, 10); 121 | 122 | You can also change how the cursor looks on the display with the function curs_set(), which has one of three values, 0=invisible, 1=normal, and 2=strong. 123 | 124 | curs_set(2); // Show the cursor as a strong visual 125 | 126 | ## Input functions 127 | 128 | There are a couple of input functions that depend on the modality of the input. The main ones are: 129 | 130 | 1. **Character input**. getch() is the main function that receives keyboard input. This will return with the character (which is actually an integer) typed into the keyboard as it happens, assuming you have disabled the buffering (see input buffering above). The functions return ERR (integer define from ncurses) and OK if it worked. 131 | 132 | int ch = getch(); 133 | ch = wgetch() 134 | ch = mvgetch(); 135 | ch = mvwgetch(); 136 | 137 | What is returned is an integer which will either be an ASCII (0-255) number or some extended character such as function and arrow keys. The basic ones are defined in the include file and can be used in code pretty easily. 138 | 139 | int ch = getch(); 140 | if ( ch == KEY_UP ) { 141 | mvwprintw(win, 7, 0, "You pressed the up arrow key"); 142 | } 143 | 144 | Note that ungetch() puts a character back onto the input queue, and the has_key() function determines if the current terminal supports a specific terminal. Also be careful to set noecho() and keypad() to make sure you don't get echoing behavior and that extended characters are supported. 145 | 146 | 2. **String input**. getstr() and its variants are the functions used to get input by string format. What happens is that it receives input until it receives a RETURN key, then saves it to the string. Note that like sscanf(), the non-space declared versions can be unsafe and lead to vulnerabilities and crashes. So, use the "n" varients. The functions return ERR (integer define from ncurses) and OK if it worked. 147 | 148 | char str[80]; 149 | getstr(str); 150 | getnstr(str, 79); 151 | wgetstr(str); 152 | wgetnstr(str, 70); 153 | mvgetstr(y, x, str); 154 | wgetnstr(win, y, x, str, 70); 155 | 156 | Note that there are a collection of functions scanw() and vscanw() which mirror the functionality of scanf() and vscanf(). These function do a getstr() of some type then run the scan over the resulting string. 157 | 158 | 3. **Mouse input**. Dealing with the mouse is a pretty straightfoward exercise. The idea is that you register for specific events, then getch() indicates that mouse events are available, and getmouse() tells you what the event it. Breaking this down, the first function to understand is mousemask(): 159 | 160 | mmask_t mask, oldmask; 161 | mask = BUTTON1_PRESSED | BUTTON_SHIFT | BUTTON2_CLICKED; 162 | mousemask(mask, &oldmask); // Assigns new mask and saves previous one 163 | mouseinterval(0); // Set minimum time between mouse events 164 | 165 | Here, this indicates to the curses library which mouse events to report. There are lot of them, but the most frequently used ones are: 166 | 167 | BUTTON1_PRESSED button 1 down 168 | BUTTON1_RELEASED button 1 up 169 | BUTTON1_CLICKED button 1 clicked 170 | BUTTON1_DOUBLE_CLICKED button 1 double clicked 171 | BUTTON1_TRIPLE_CLICKED button 1 triple clicked 172 | BUTTON2_PRESSED button 2 down 173 | BUTTON2_RELEASED button 2 up 174 | BUTTON2_CLICKED button 2 clicked 175 | BUTTON2_DOUBLE_CLICKED button 2 double clicked 176 | BUTTON2_TRIPLE_CLICKED button 2 triple clicked 177 | BUTTON_SHIFT shift was down during button state change 178 | BUTTON_CTRL control was down during button state change 179 | BUTTON_ALT alt was down during button state change 180 | ALL_MOUSE_EVENTS report all button state changes 181 | REPORT_MOUSE_POSITION report movement 182 | 183 | Note that the left button is BUTTON1 and the right is BUTTON2. There are also BUTTON3 and BUTTON4 available for people with mice that support it. For compatibility with different peoples's computers I tend to avoid using those. Note tha a click is an UP-DOWN mouse event and a press is just DOWN. 184 | 185 | One other thing to know about dealing with the mouse is that it has a event resolution setting. Basically, this is the minimum amount of time in milliseconds that mouse events can be reported--here, this acts as a damper for mouse events (to prevent your UI from constantly being overloaded with events). The function mouseinterval() is used for setting this, where a value of 0 disables the damper, and -1 returns the current setting without changing it. 186 | 187 | Another interesting event is the ALL_MOUSE_EVENTS which reports everything, and REPORT_MOUSE_POSITION which reports mouse movement. Both of those tend to generate a lot of events, so design your code accordingly if you wish to use them. Note that getting the mouse position reporting can be challenging on some termainals. 188 | 189 | Getting and processing the mouse events is a two stage process. The first part is pretty easy--you simply call getch() which returns KEY_MOUSE if a mouse event is avilable. Thereafter you you call getmouse() to obtain the event. 190 | 191 | MEVENT event; 192 | ch = getch(); 193 | if (ch == KEY_MOUSE) { 194 | if (getmouse(&event) == OK) { 195 | if (event.bstate & BUTTON1_PRESSED) { 196 | mvwprintw(win, 8, 0, "Left mouse button at %d, %d", event.x, event.y); 197 | wrefresh(win); 198 | } 199 | } 200 | } 201 | 202 | The important part is that the mouse event structure has the state of the mouse event, its location, and and ID that is specific to the mouse that generated the event (this is useful when a system has multiple input devices, such as a mouse and a touchpad). The structure looks like: 203 | 204 | typedef struct { 205 | short id; // ID of event generating device 206 | int x, y, z; // Location of the device in coordinate system 207 | // Note that the 'z' coordinate is unused 208 | mmask_t bstate; // State bits (see definitions above) 209 | } 210 | 211 | Note that when working with the mouse, you often have to figure out which window (or the main screen) you are working with. To do this, you call the wenclose(), which tests where the a point in the screen coordinate space is within a window. This returns true when it is and false when it is not. 212 | 213 | bool inwindow; 214 | inwindow = wenclose(win, event.y, event.x); 215 | 216 | One last handy set of functions converts window coordinates to screen coordinates and vice-versa. These are the mouse_trafo() functions. 217 | 218 | // Convert screen -> window coordinates (returns false if not in window) 219 | int wx = event.x, wy = event.y, sx,sy; 220 | if (wmouse_trafo(win, &wy, &wx, FALSE) == TRUE) { 221 | sx = wx; sy = wy; 222 | wmouse_trafo(win, &sy, &sx, TRUE); // Convert screen -> window coordinates 223 | } 224 | 225 | ## Output functions 226 | 227 | There is a wide array of output functions. 228 | 229 | 1. **printw()/wrprintw()/mvwprintw()** - this is the simplest of the output functions, and it outputs a string constructed using the printf formatting. The output is put at the current of the cursor. 230 | 231 | printw("This goes to the cursor location on the stdssc"); 232 | mvprintw(y, x, "This goes to specified location in stdscr"); 233 | wprintw(iwn, "This goes to the cursor location in the window"); 234 | mvwprintw(win, y, x, "This goes a specified point in the window); 235 | 236 | 2. **addch()/waddch()/mvwaddch()** - this puts a character at the current cursor location. The nice thing about this function is that the character can be decorated using attributes that do things like bolding and underlining. This function has window and window/move variants. 237 | 238 | addch('a'); 239 | addch(ch | A_BOLD | A_UNDERLINE); 240 | 241 | In addition to all of the normal characters there are a bunch of ascii supported extended characters sich as arrows, symbols, etc. Much of these are callewd ACS names. For a complete list, search curses.h for ACS_ and they are listed. A couple examples are ACS_PI, ACS_RARROW, and ACS_DIAMOND. You usese the #defines just as any normal character: 242 | 243 | waddch(ACS_DIAMOND | A_BOLD); 244 | 245 | 3. **addstr/waddstr/mvaddstr** - these place a string on the screen or window according to the normal conventions. This is just like doing a addch() once for every character in the string. 246 | 247 | addstr("string to add"); 248 | waddstr(win, "string to add to window"); 249 | mvaddstr(y, x, "string to add at specific location); 250 | mvwaddstr(win, y, x, "string to add at specific location); 251 | 252 | One function that is useful in preventing ugly text caused by overwriting one string over another is clrtoeol(), which clears the text to the end of the line. Use this when printing a variable length string over and over to prevent previous strings of longer lengths show the end of thier string. 253 | 254 | ## Attributes 255 | 256 | Attributes are decorators for characters in ncurses. The idea is that you can add decorations onto characters that are placed into the interface. There are a couple of ways to enable and use attributes, the two central ones are: 257 | 258 | 1. **attron()/attroff()/wattron()/wattroff()** - this function enables or disables attributes. Enabled attributes remain on until they are turned off. Note that the attributes can be ORed as parameters in the attribute functions. 259 | 260 | attron(A_REVERSE | A_BOLD); 261 | attroff(A_DIM | A_UNDERLINE); 262 | wattron(win, A_REVERSE | A_BOLD); 263 | wattroff(win, A_DIM | A_UNDERLINE); 264 | 265 | Note that all of these functions accept integers (often made from the #defines listed below). If you want to be more proper, you will use the attr_t type. In that case, you will want to use the attr_ varients. Note that the last option (void *opt) is unused and should always be NULL. 266 | 267 | attr_t attr = A_REVERSE | A_BOLD; 268 | attr_on(attrs, NULL); 269 | attr_off(attrs, NULL); 270 | wattr_on(win, attrs, NULL); 271 | wattr_off(win, attrs, NULL); 272 | 273 | 2. **attrset()/wattrset()** - this function resets the attributes for the screen or window. This is kind of like turning off all of the attributes and turning the new ones on. 274 | 275 | attrset(A_REVERSE | A_UNDERLINE); 276 | wattrset(win, A_NORNAL); // Just turns off all of the attributes 277 | 278 | Like the on/off functions above, there are attr_ varients that you can use. 279 | 280 | attr_t attr = A_REVERSE | A_BOLD; 281 | attr_set(attrs, NULL); 282 | wattr_set(win, attrs, NULL); 283 | 284 | 3. **chgat()/wchgat()** - changes the attributes of a set of characters from the cursor forward, where -1 means to the end of the line. 285 | 286 | chgat(6, A_REVERSE, NULL); 287 | chgat(-1, A_BOLD, NULL); // Makes everything to end of line bold 288 | 289 | 3. **attr_get()/wattr_get()** - This just gets the current attributes from the window/screen. It returns a type that is attr_t (which is just a uint32_t) containing the OR of the current attributes and color schemes. 290 | 291 | attr_t attr; // Attribute sets 292 | short pair; // Color scheme 293 | att_get(&attr, &pair, NULL); 294 | wattr_get(win, &attr, &pair, NULL); 295 | 296 | 5. **standend()/wstandend()** - These functions just turn off all of the attributes (e.g., returns the attributes to normal). 297 | 298 | standend(); 299 | wstandend(); 300 | 301 | The types of attributes that are available are: 302 | 303 | A_NORMAL Normal display (no highlight) 304 | A_STANDOUT Best highlighting mode of the terminal. 305 | A_UNDERLINE Underlining 306 | A_REVERSE Reverse video 307 | A_BLINK Blinking 308 | A_DIM Half bright 309 | A_BOLD Extra bright or bold 310 | A_PROTECT Protected mode (old mode for VT terminals that prevents erase) 311 | A_INVIS Invisible or blank mode 312 | A_ALTCHARSET Alternate character set 313 | A_CHARTEXT Bit-mask to extract a character 314 | COLOR_PAIR(n) Color-pair number 315 | 316 | ## Colors 317 | 318 | The central idea for using colors is that you define foreground/background color pairs then use them just like normal decorations discussed in the previous discussion. The way to think about this is that you turn on colors, define color pairs, then use them. The following functions are the way you get this done. 319 | 320 | 1. **has_colors()** - this function checks to see if the terminal supports colors. It returns TRUE if it does and FALSE if not. It is a good idea to check this first, so you don't call a bunch of stuff you don't need to. 321 | 322 | 2. **start_color()** - this enables the use of colors in the terminal. I am not aware of a way to disable colors once you have enabled them, but I am not clear on why anyone would want to do so (just don't use them). 323 | 324 | 3. **init_pair()** - this function allows you to create pairs of foreground/background colors. What you need to know is that the library keeps 256 different colors and can have up to COLOR_PAIRS (a variable defined in ncurses.h, 65536 color pairs. Each color is indexed by a number 0-255, where the following colors are predefined (in ncurses.h): 325 | 326 | COLOR_BLACK 0 327 | COLOR_RED 1 328 | COLOR_GREEN 2 329 | COLOR_YELLOW 3 330 | COLOR_BLUE 4 331 | COLOR_MAGENTA 5 332 | COLOR_CYAN 6 333 | COLOR_WHITE 7 334 | 335 | So, to create a pair you give it an index (< COLOR_PAIRS) and two colors. 336 | 337 | init_pair(42, COLOR_BLACK, COLOR_RED); 338 | 339 | You can also retrive the colors for a specific pair using the pair_content function. 340 | 341 | short pindex = 1, foreground, background; 342 | pair_content(pindex, &foreground, &background); 343 | 344 | 4. **init_color** - this function create a new color based on an RGB specification. Note that the color schemes are scaled 0-1000, so some translation from 0-255 RGB numbers is required. Essentially you give it an index (>7 to avoid overwriting existing colors), and an scaled RGB value to define the new color. Assuming you have the normal (0-255,0-255,0-255) color scheme, you can use the following to create a new color (in this case, the official red color of the University of Wisconsin-Madison): 345 | 346 | #define RGB_TO_CURSES(x) ((int)((float)x*3.90625)) // 1000/256 conversion 347 | short badger_red = COLOR_WHITE + 1; 348 | init_color(badger_red, RGB_TO_CURSES(197), RGB_TO_CURSES(5), RGB_TO_CURSES(12)); 349 | 350 | Now that you have this new color, you can add it to a pair and use it to decorate any text output. 351 | 352 | init_pair(43, badger_red, COLOR_WHITE); 353 | attr_on(COLOR_PAIR(43)); 354 | mvprintw(3, 0, "This is now a special UW-Madison color!", sy, sx); 355 | 356 | You can retrive the settings for a specific color using the color_content function. 357 | 358 | short cindex = 43, red, green, blue; 359 | color_content(cindex, &red, &green, &blue); 360 | 361 | ## Panels 362 | 363 | At this stage you have enough to do some basic UI. The truth is that the update process can get a little wonky and the bookkeeping necessary to manage all of this is pretty complication and very difficult to debug. While technically not part of the curses library, the Panels library is designed to address these challenges and leave you to work directly on the important stuff of your application. 364 | 365 | Panels actually work the way that you might expect windows to work (and actually, they are a code construct much closer to the windows interfaces we are used to in X11, OSX, and Windows). The idea is that they represent a set of windows that are stacked on the screen. When displayed (using update_panels(), then doupdate()), you see all of the top one, all of the next one down the stack minus whatever parts are covered by the top one, etc. You can also move panels around, show and hide them, etc. 366 | 367 | The basic functions are: 368 | 369 | 370 | 1. **new_panel()** - this creates a new panel based on a previously defined window. 371 | 372 | WINDOW *win = new_window(10, 10, 20 20); 373 | WINDOW *win2 = new_window(10, 10, 25, 25); 374 | PANEL *pan = new_panel(win); 375 | PANEL *pan2 = new_panel(win2); 376 | 377 | 2. **update_panel()** - update the stacking order, where each new panel will be "on top" of the others defined. In the case of the previous example, win2 will be on top of win. 378 | 379 | update_panels(); 380 | 381 | 3. **doupdate()** - this shows the panels on the screen. It is a bit strange to see this as being seperate from update_panels, but it separates the shuffling and reworking of the panels on the virtual screen from the actual display operation. 382 | 383 | doupdate(); 384 | 385 | 4. **top_panel()/bottom_panel** - moves the panel to the top or bottom of the stack. 386 | 387 | top_panel(pan); 388 | bottom_panel(pan2); 389 | 390 | Also, there are panel_above() and panel_below() which allows you to travese the panel stack. 391 | 392 | 5. **set_panel_userptr** - this allows an arbitrary pointer to be associated with a panel. Often this will be some other UI object or application structure. 393 | 394 | MyObject *obj = new MyObject(), *obj2; 395 | set_panel_userptr(pan, obj); 396 | ... 397 | obj2 = panel_userptr(pan); 398 | 399 | 6. **show_panel()/hide_panel()** - this turns on and off the display state of the panels. This is useful for keeping interface objects around that you only need once in a while. 400 | 401 | hide_panel(pan); 402 | show_panel(pan); 403 | int hidden = panel_hidden(pan); // Sets flag to TRUE if hidden 404 | 405 | 7. **move_pane()** - this moves the panel around the interface. 406 | 407 | move_panel(pan, 30, 30); 408 | 409 | 8. Finally, you can delete a panel using the final del_panel() function. 410 | 411 | del_panel(pan); 412 | 413 | ## Menus 414 | 415 | Menus are an interface object that you can use to allow the selection of multiple ements. This is just like what you would see in normal UI interfaces, but just in text. The lifecycle of menus is pretty straightforward, you create the menus/items, post them, then monitor for menu events. When you are done, you unpost and free the menu items and you are done. 416 | 417 | 1. **new_menu()/new_item()/post_menu()** - this allows you to create a menu, then create and attach items onto the menu. Whe you are done, post the menu so the UI knows to start creating it. 418 | 419 | int i; 420 | const char * itemtext[5][2] = { 421 | "Item 1", "Description 1", 422 | "Item 2", "Description 2", 423 | "Item 3", "Description 3", 424 | "Item 5", "Description 5" 425 | }; 426 | ITEM **testitems = (ITEM **)calloc(5, sizeof(ITEM *)); 427 | for (i=0; i<5; i++) { 428 | testitems[0] = new_item(itemtext[i][0], itemtext[i][1]); 429 | } 430 | MENU *testmenu = new_menu(testitems); 431 | post_menu(testmenu); 432 | 433 | Note that the item text and description pass is saved by reference (by pointer), so you need to be carful to NOT have that data somewhere where it will go out of scope, e.g., on the stack. Best practices seems to be to have this as some kind of global, file, or class static data. 434 | 435 | Note that the new_item() function can be used in conjunction with set_item_userptr() and later item_userptr() which allows you to set an opaque pointer for used. 436 | 437 | 2. **item_opts_off()/items_opts_on()** - you can also set options on menu items. There is only one of these, however, the ability to turn on and off selectability of a menu item, O_SELECTABLE. 438 | 439 | item_opts(testitems[2], O_SELECTABLE); 440 | item_opts_on(testitems[3], O_SELECTABLE); 441 | 442 | 2. **set_menu_fore()/set_menu_back()/set_menu_grey()** - these functions allow you to set the colors that will be displayed as part of the menu, where the colors represent foreground, background, and non-selectable. 443 | 444 | init_pair(1, COLOR_RED, COLOR_BLACK); 445 | init_pair(2, COLOR_BLUE, COLOR_BLACK); 446 | init_pair(3, COLOR_WHITE, COLOR_BLACK); 447 | set_menu_fore(testmenu, COLOR_PAIR(1) | A__REVERSE); 448 | set_menu_back(testmenu, COLOR_PAIR(2)); 449 | set_menu_grey(testmenu, COLOR_PAIR(3)); 450 | 451 | 4. **menu_driver()** - this is the thing that makes the menu do things. Specifcally, you tell the menu driver what operation to do, and it reflects it in the interface. 452 | 453 | The menu driver accepts: 454 | 455 | REQ_LEFT_ITEM // Move left 456 | REQ_RIGHT_ITEM // Move right 457 | REQ_UP_ITEM // Move up 458 | REQ_DOWN_ITEM // Move down 459 | REQ_SCR_ULINE // Scroll up 460 | REQ_SCR_DLINE // Scroll down 461 | REQ_SCR_DPAGE // Scroll down (page) 462 | REQ_SCR_UPAGE // Sroll up (page) 463 | REQ_FIRST_ITEM // Goto first item 464 | REQ_LAST_ITEM // Goto last 465 | REQ_NEXT_ITEM // Got next time 466 | REQ_PREV_ITEM // Previous item 467 | REQ_TOGGLE_ITEM // Select/de-selecct item 468 | 469 | There is also a search mechanism so that rather than move between items, we can user a search term and then move to the item(s) that it matches. This provides a nice interface to do a quick search through the list. The idea is that the menu, then walk through that match. Each menu has a pattern buffer that you can add to simply by giving it the menu_driver() function. 470 | 471 | The commands are: 472 | 473 | REQ_CLEAR_PATTERN // Clear pattrn buffer. 474 | REQ_BACK_PATTERN // Delete prev char from pattern 475 | REQ_NEXT_MATCH // Move to next match 476 | REQ_PREV_MATCH // Move to previous match 477 | 478 | Putting this all together, here is a simple menu handing code snippet. 479 | 480 | ch = getch(); 481 | switch (ch) { 482 | case KEY_DOWN: 483 | menu_driver(testmenu, REQ_DOWN_ITEM); 484 | break; 485 | case KEY_UP: 486 | menu_driver(testmenu, REQ_UP_ITEM); 487 | break; 488 | default: 489 | menu_driver(testmenu, ch); // Add to search buffer 490 | menu_driver(testmenu, REQ_NEXT_MATCH); 491 | break; 492 | } 493 | 494 | 5. **set_menu_win()/set_menu_sub()** - this associates the menu with a window. You must set both of these because the window is the thing that has the menu (like a large window), and the subwindow is a window containing the menu iteself. 495 | 496 | set_menu_win(basic_menu, win); 497 | set_menu_sub(basic_menu, derwin(win, 6, 38, 3, 1)); 498 | 499 | 500 | 501 | 5. **unpost_menu()/free_menu()** - this allows you to clear the menu from the interface (unpost_menu(), and then cleanup/free the menu associated from its use: 502 | 503 | unpost_meny(testmenu); 504 | free_menu(testmenu); 505 | 506 | 6. **set_menu_format()/set_menu_options()** - There are also lots of other things like multi-column format via set_menu_format(), as well as menu options, set_menu_options(), allow you to do a lot of cool menu tricks like multiple columns and multi-section, etc. 507 | 508 | ## Forms 509 | 510 | The forms library allows you to create interactive interfaces structured as fields that can are allocated on single canvas such as a window. In many respects the creation of a form is a lot like that of a menu. You begin by creating fields, annotating them, posting, calling the form driver, and then shutting it down. 511 | 512 | 1. **new_field()/new_form()** - These create the base objects. For new_field(), you pass the number of rows, number of columns, row location, column location, number of offscreen rows and number of buffers. new_form() accepts an array of fields whose last element is NULL. 513 | 514 | FORM *test_form; 515 | FIELD *test_fields[3]; 516 | test_fields[0] = new_field(1, 10, 0, 0, 0, 0); 517 | test_fields[1] = new_field(1, 10, 1, 0, 0, 0); 518 | test_fields[2] = NULL; 519 | test_form = new_form(test_fields); 520 | 521 | You can also get the parameters of a field using the field_info() function. One other key thing to note is that labels are simply fields that are not editable (see O_ACTIVE, O_EDIT below). 522 | 523 | 2. **set_form_win()/set_form_sub()/sclae_form()** - These functions setup the form and subwindow for a particular form. The idea is the window is where the whole display goes (with framing, etc.), and the subwindow is where the fields that are selectable/visible go. The scale_form() function tells you what the minimum rows and columns you need in the subwindow to fir the interface. 524 | 525 | int rows, cols; 526 | scale_form(basic_form, &rows, &cols); 527 | set_form_win(basic_form, win); 528 | set_form_sub(basic_form, derwin(win, rows+1, cols+1, 1, 1)); 529 | 530 | 2. **set_field_fore()/set_field_back()/set_field_just** - You can set lots of display attributes for a field using a set of attribute functions. Some of the more interesting ones are: 531 | 532 | set_field_back(test_fields[0], COLOR_PAIR(1)); // Only sets back 533 | set_field_fore(test_fields[0], COLOR_PAIR(2)); // Only sets fore 534 | set_field_just(test_fields[0], JUSTIFY_CENTER); 535 | 536 | The super wierd thing to remember with colors and fields is that the set_field_fore() only sets foreground portion, and the set field back sets only the background portion. All other attributes work same as above, however. 537 | 538 | 3. **set_field_opts()/field_opts_on()/field_opts_off()** - These functions are used to set options for the fields, where set assigns the options, and off turns them on and off. 539 | 540 | set_fields_opts(test_field[0], O_ACTIVE); 541 | fields_opts_on(test_fields[1], O_BLANK); 542 | field_opts_off(test_fields[0], O_EDIT|O_ACTIVE); // Creates a label 543 | 544 | Turning on and off fields the opts_on() is safer, so it is recommended. Where the field options include 545 | 546 | O_VISIBLE // Show the field or not on screen 547 | O_ACTIVE // Controls whether the fields is visited (lables=0) 548 | O_PUBLIC // Show the data while editing or typing 549 | O_EDIT // Turns editing on and off 550 | O_WRAP // Enables wrapping 551 | O_BLANK // Erase the contents when first character typed 552 | O_AUTOSKIP // Move automatically to next field when field is full 553 | O_NULLOK // Call validation (see below) 554 | O_PASSOK // Only call validation when edited 555 | O_STATIC // Controlls whether field will stretch to data size 556 | 557 | 2. **post_form()/unpost_form()** - the posts the form to the screen and removes it. Note that unpost removes it from the form management system, which is different than making it invisible. 558 | 559 | post_form(test_form); 560 | ... 561 | unpost_form(test_form); 562 | 563 | 4. **form_driver()** - this perfortms the operation of the forms, specifically adding characters, moving between fields, etc. 564 | 565 | form_driver(test_form, REQ_NEXT_FIELD); 566 | form_driver(test_form, ch); // Add character to the buffer 567 | 568 | You can also just assign a specific field as having the focus using the set_current_field() function. 569 | 570 | 5. **set_field_buffer()/set_field_status()/get_field_status()** - These functions allow you to set and retrieve the contents of a buffer and determine the edit status of a field. Note that the forms library supports fields with multiple buffers, but I generally have not found a good use for them. 571 | 572 | 6. **set_current_form()/current_field()/field_index()** - These functions set or get the focus of the field on the interface. A return code of -1 (or NULL in the case of returned pointers) indicates an error. 573 | 574 | FIELD *cur = current_field(form); // Get the current field and index 575 | int fld = field_index(cur); 576 | 577 | set_current_field(form, field); // Set the current field --------------------------------------------------------------------------------