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_CANTBUY 1
19 #define COLOURPAIR_SELECTED 2
22 int ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted);
23 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1, const char *Mid, char Pad2, const char *Right, ...);
25 // -------------------
26 // --- NCurses GUI ---
27 // -------------------
29 * \brief Render the NCurses UI
31 int ShowNCursesUI(void)
36 const int displayMinWidth = 50;
37 const char *titleString = "Dispense";
42 int ret = -2; // -2: Used for marking "no return yet"
44 char balance_str[5+1+2+1]; // If $9999.99 is too little, something's wrong
54 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
57 if( currentItem >= maxItemIndex ) {
60 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
69 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
72 if( currentItem < 0 ) {
73 currentItem = maxItemIndex - 1;
75 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
82 username = gsEffectiveUser;
84 pwd = getpwuid( getuid() );
85 username = pwd->pw_name;
88 snprintf(balance_str, sizeof(balance_str), "$%i.%02i", giUserBalance/100, abs(giUserBalance)%100);
93 init_pair(COLOURPAIR_CANTBUY, COLOR_BLACK, COLOR_BLACK); // Not avaliable
94 init_pair(COLOURPAIR_SELECTED, COLOR_GREEN, COLOR_BLACK); // Selected
98 maxItemIndex = ShowItemAt(0, 0, 0, -1, 0);
99 // Get item count per screen
100 // - 6: randomly chosen (Need at least 3)
101 items_in_view = LINES - 6;
102 if( items_in_view > maxItemIndex )
103 items_in_view = maxItemIndex;
106 while( ShowItemAt(0, 0, 0, currentItem, 0) == -1 )
111 height = items_in_view + 3;
112 width = displayMinWidth;
115 xBase = COLS/2 - width/2;
116 yBase = LINES/2 - height/2;
121 PrintAlign(yBase, xBase, width, "/", '-', titleString, '-', "\\");
124 for( i = 0; i < items_in_view; i ++ )
128 move( yBase + 1 + i, xBase );
133 // Check for the '...' row
134 // - Oh god, magic numbers!
135 if( (i == 0 && itemBase > 0)
136 || (i == items_in_view - 1 && itemBase < maxItemIndex - items_in_view) )
138 printw(" ..."); pos += 8;
139 times = (width - pos) - 1;
140 while(times--) addch(' ');
145 yBase + 1 + i, xBase + pos, // Position
146 (width - pos) - 3, // Width
147 itemBase + i, // Index
148 !!(currentItem == itemBase + i) // Hilighted
153 // Scrollbar (if needed)
154 if( maxItemIndex > items_in_view ) {
158 else if( i == items_in_view - 1 ) {
162 int percentage = itemBase * 100 / (maxItemIndex-items_in_view);
163 if( i-1 == percentage*(items_in_view-3)/100 ) {
177 PrintAlign(yBase+height-2, xBase, width, "\\", '-', "", '-', "/");
180 // - Username, balance, flags
181 PrintAlign(yBase+height-1, xBase+1, width-2,
182 username, ' ', balance_str, ' ', gsUserFlags);
183 PrintAlign(yBase+height, xBase+1, width-2,
184 "q: Quit", ' ', "Arrows: Select", ' ', "Enter: Buy");
197 case 'B': _ItemDown(); break;
198 case 'A': _ItemUp(); break;
201 else if( ch == ERR || ch == '\x1b' ) {
206 fprintf(stderr, "Unknown character 0x%x\n", ch);
213 ret = ShowItemAt(0, 0, 0, currentItem, 0);
216 case 'j': _ItemDown(); break;
217 case 'k': _ItemUp(); break;
220 ret = -1; // -1: Return with no dispense
224 // Check if the return value was changed
225 if( ret != -2 ) break;
228 // Scroll only if needed
229 if( items_in_view < maxItemIndex )
231 // - If the current item is above the second item shown, and we're not at the top
232 if( currentItem < itemBase + 2 && itemBase > 0 ) {
233 itemBase = currentItem - 2;
234 if(itemBase < 0) itemBase = 0;
236 // - If the current item is below the second item show, and we're not at the bottom
237 if( currentItem > itemBase + items_in_view - 2 && itemBase + items_in_view < maxItemIndex ) {
238 itemBase = currentItem - items_in_view + 2;
239 if( itemBase > maxItemIndex - items_in_view )
240 itemBase = maxItemIndex - items_in_view;
252 * \brief Show item \a Index at (\a Col, \a Row)
253 * \return Dispense index of item
254 * \note Part of the NCurses UI
256 int ShowItemAt(int Row, int Col, int Width, int Index, int bHilighted)
265 // - This assumes that
266 case UI_MODE_STANDARD:
268 // Index = -1, request limit
269 if( Index < 0 || Index >= giNumItems+2 )
275 name = "Coke Machine";
276 Index = -1; // -1 indicates a label
283 name = gaItems[Index].Desc;
284 price = gaItems[Index].Price;
285 status = gaItems[Index].Status;
293 name = "Electronic Payment System";
294 Index = -1; // -1 indicates a label
299 name = gaItems[Index].Desc;
300 price = gaItems[Index].Price;
301 status = gaItems[Index].Status;
307 // Width = 0, don't print
310 // 4 preceding, 5 price
311 int nameWidth = Width - 4 - snprintf(NULL, 0, " %4i", price);
316 // Show hilight and status
321 color_set( COLOURPAIR_SELECTED, NULL );
324 else if( price > giUserBalance ) {
326 color_set( COLOURPAIR_CANTBUY, NULL );
330 color_set( 0, NULL );
336 color_set( COLOURPAIR_CANTBUY, NULL );
342 color_set( COLOURPAIR_CANTBUY, NULL );
347 printw("%-*.*s", nameWidth, nameWidth, name);
349 printw(" %4i", price);
355 printw("-- %-*.*s ", Width-4, Width-4, name);
359 // If the item isn't availiable for sale, return -1 (so it's skipped)
360 if( status || (price > giUserBalance && gbDisallowSelectWithoutBalance) )
367 * \brief Print a three-part string at the specified position (formatted)
368 * \note NCurses UI Helper
370 * Prints \a Left on the left of the area, \a Right on the righthand side
371 * and \a Mid in the middle of the area. These are padded with \a Pad1
372 * between \a Left and \a Mid, and \a Pad2 between \a Mid and \a Right.
374 * ::printf style format codes are allowed in \a Left, \a Mid and \a Right,
375 * and the arguments to these are read in that order.
377 void PrintAlign(int Row, int Col, int Width, const char *Left, char Pad1,
378 const char *Mid, char Pad2, const char *Right, ...)
380 int lLen, mLen, rLen;
385 // Get the length of the strings
386 va_start(args, Right);
387 lLen = vsnprintf(NULL, 0, Left, args);
388 mLen = vsnprintf(NULL, 0, Mid, args);
389 rLen = vsnprintf(NULL, 0, Right, args);
393 if( lLen + mLen/2 > Width/2 || mLen/2 + rLen > Width/2 ) {
394 return ; // TODO: What to do?
400 va_start(args, Right);
404 vsnprintf(tmp, lLen+1, Left, args);
408 times = (Width - mLen)/2 - lLen;
409 while(times--) addch(Pad1);
413 vsnprintf(tmp, mLen+1, Mid, args);
417 times = (Width - mLen)/2 - rLen;
418 if( (Width - mLen) % 2 ) times ++;
419 while(times--) addch(Pad2);
423 vsnprintf(tmp, rLen+1, Right, args);