3 * UCC (University [of WA] Computer Club) Electronic Accounting System
6 * menu.c - ncurses dispense menu
8 * This file is licenced under the 3-clause BSD Licence. See the file
9 * COPYING for full details.
13 #include <pwd.h> // getpwuids
14 #include <unistd.h> // getuid
18 #define COLOURPAIR_DEFAULT 0
19 #define COLOURPAIR_CANTBUY 1
20 #define COLOURPAIR_SELECTED 2
23 int ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted);
24 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
26 // -------------------
27 // --- NCurses GUI ---
28 // -------------------
30 * \brief Render the NCurses UI
32 int ShowNCursesUI(void)
37 const int displayMinWidth = 50;
38 const char *titleString = "Dispense";
43 int ret = -2; // -2: Used for marking "no return yet"
45 char balance_str[5+1+2+1]; // If $9999.99 is too little, something's wrong
55 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
58 if( currentItem >= maxItemIndex ) {
61 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
70 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
73 if( currentItem < 0 ) {
74 currentItem = maxItemIndex - 1;
76 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
83 username = gsEffectiveUser;
85 pwd = getpwuid( getuid() );
86 username = pwd->pw_name;
89 snprintf(balance_str, sizeof(balance_str), "$%i.%02i", giUserBalance/100, abs(giUserBalance)%100);
95 init_pair(COLOURPAIR_DEFAULT, -1, -1); // Not avaliable
96 init_pair(COLOURPAIR_CANTBUY, COLOR_BLACK, -1); // Not avaliable
97 init_pair(COLOURPAIR_SELECTED, COLOR_GREEN, -1); // Selected
101 maxItemIndex = ShowItemAt(0, 0, 0, -1, 0);
102 // Get item count per screen
103 // - 6: randomly chosen (Need at least 3)
104 items_in_view = LINES - 6;
105 if( items_in_view > maxItemIndex )
106 items_in_view = maxItemIndex;
109 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
114 height = items_in_view + 3;
115 width = displayMinWidth;
118 xBase = COLS/2 - width/2;
119 yBase = LINES/2 - height/2;
124 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
127 for( i = 0; i < items_in_view; i ++ )
131 move( yBase + 1 + i, xBase );
136 // Check for the '...' row
137 // - Oh god, magic numbers!
138 if( (i == 0 && itemBase > 0)
139 || (i == items_in_view - 1 && itemBase < maxItemIndex - items_in_view) )
141 printw(" ..."); pos += 8;
142 times = (width - pos) - 1;
143 while(times--) addch(' ');
148 yBase + 1 + i, xBase + pos, // Position
149 (width - pos) - 3, // Width
150 itemBase + i, // Index
151 !!(currentItem == itemBase + i) // Hilighted
156 // Scrollbar (if needed)
157 if( maxItemIndex > items_in_view ) {
161 else if( i == items_in_view - 1 ) {
165 int percentage = itemBase * 100 / (maxItemIndex-items_in_view);
166 if( i-1 == percentage*(items_in_view-3)/100 ) {
180 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
183 // - Username, balance, flags
184 PrintAlign(yBase+height-1, xBase+1, width-2,
185 username, ' ', balance_str, ' ', gsUserFlags);
186 PrintAlign(yBase+height, xBase+1, width-2,
187 "q: Quit", ' ', "Arrows: Select", ' ', "Enter: Buy");
200 case 'B': _ItemDown(); break;
201 case 'A': _ItemUp(); break;
204 else if( ch == ERR || ch == '\x1b' ) {
209 fprintf(stderr, "Unknown character 0x%x\n", ch);
216 ret = ShowItemAt(0, 0, 0, currentItem, 0);
219 case 'j': _ItemDown(); break;
220 case 'k': _ItemUp(); break;
223 ret = -1; // -1: Return with no dispense
227 // Check if the return value was changed
228 if( ret != -2 ) break;
231 // Scroll only if needed
232 if( items_in_view < maxItemIndex )
234 // - If the current item is above the second item shown, and we're not at the top
235 if( currentItem < itemBase + 2 && itemBase > 0 ) {
236 itemBase = currentItem - 2;
237 if(itemBase < 0) itemBase = 0;
239 // - If the current item is below the second item show, and we're not at the bottom
240 if( currentItem > itemBase + items_in_view - 2 && itemBase + items_in_view < maxItemIndex ) {
241 itemBase = currentItem - items_in_view + 2;
242 if( itemBase > maxItemIndex - items_in_view )
243 itemBase = maxItemIndex - items_in_view;
254 #define COKE_LABEL "Coke Machine"
255 #define NCOKESLOTS 7 // slots 0 -- 6
256 #define EPS_LABEL "Electronic Payment System"
259 * \brief Show item \a Index at (\a Col, \a Row)
260 * \return Dispense index of item
261 * \note Part of the NCurses UI
263 int ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted)
272 case UI_MODE_STANDARD:
274 // Index = -1, request limit
275 if( Index < 0 || Index >= giNumItems+2 )
282 Index = -1; // -1 indicates a label
287 if( Index < NCOKESLOTS )
289 name = gaItems[Index].Desc;
290 price = gaItems[Index].Price;
291 status = gaItems[Index].Status;
300 Index = -1; // -1 indicates a label
305 name = gaItems[Index].Desc;
306 price = gaItems[Index].Price;
307 status = gaItems[Index].Status;
310 case UI_MODE_DRINKSONLY:
316 // Width = 0, don't print
319 // 4 preceding, 5 price
320 int nameWidth = Width - 4 - snprintf(NULL, 0, " %4i", price);
325 // Show hilight and status
330 color_set( COLOURPAIR_SELECTED, NULL );
333 else if( price > giUserBalance ) {
335 color_set( COLOURPAIR_CANTBUY, NULL );
339 color_set( 0, NULL );
345 color_set( COLOURPAIR_CANTBUY, NULL );
351 color_set( COLOURPAIR_CANTBUY, NULL );
356 printw("%-*.*s", nameWidth, nameWidth, name);
358 printw(" %4i", price);
364 printw("-- %-*.*s ", Width-4, Width-4, name);
368 // If the item isn't availiable for sale, return -1 (so it's skipped)
369 if( status > 0 || (price > giUserBalance && gbDisallowSelectWithoutBalance) )
376 * \brief Print a three-part string at the specified position (formatted)
377 * \note NCurses UI Helper
379 * Prints \a Left on the left of the area, \a Right on the righthand side
380 * and \a Mid in the middle of the area. These are padded with \a Pad1
381 * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
383 * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
384 * and the arguments to these are read in that order.
386 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
387 const char *Mid, char Pad2, const char *Right, ...)
389 int lLen, mLen, rLen;
394 // Get the length of the strings
395 va_start(args, Right);
396 lLen = vsnprintf(NULL, 0, Left, args);
397 mLen = vsnprintf(NULL, 0, Mid, args);
398 rLen = vsnprintf(NULL, 0, Right, args);
402 if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
403 return ; // TODO: What to do?
409 va_start(args, Right);
413 vsnprintf(tmp, lLen+1, Left, args);
417 times = (Width - mLen)/2 - lLen;
418 while(times--) addch(Pad1);
422 vsnprintf(tmp, mLen+1, Mid, args);
426 times = (Width - mLen)/2 - rLen;
427 if( (Width - mLen) % 2 ) times ++;
428 while(times--) addch(Pad2);
432 vsnprintf(tmp, rLen+1, Right, args);