1 2 /*************************************************************************** 3 * COPYRIGHT NOTICE * 4 **************************************************************************** 5 * ncurses is copyright (C) 1992-1995 * 6 * Zeyd M. Ben-Halim * 7 * zmbenhal@netcom.com * 8 * Eric S. Raymond * 9 * esr@snark.thyrsus.com * 10 * * 11 * Permission is hereby granted to reproduce and distribute ncurses * 12 * by any means and for any fee, whether alone or as part of a * 13 * larger distribution, in source or in binary form, PROVIDED * 14 * this notice is included with any such distribution, and is not * 15 * removed from any of its header files. Mention of ncurses in any * 16 * applications linked with it is highly appreciated. * 17 * * 18 * ncurses comes AS IS with no warranty, implied or expressed. * 19 * * 20 ***************************************************************************/ 21 22 /*************************************************************************** 23 * Module menu_driver and menu_pattern * 24 * Central dispatching routine and pattern matching handling * 25 ***************************************************************************/ 26 27 #include "menu.priv.h" 28 29 /* Macros */ 30 31 /* Remove the last character from the match pattern buffer */ 32 #define Remove_Character_From_Pattern(menu) \ 33 (menu)->pattern[--((menu)->pindex)] = '\0' 34 35 /* Add a new character to the match pattern buffer */ 36 #define Add_Character_To_Pattern(menu,ch) \ 37 { (menu)->pattern[((menu)->pindex)++] = (ch);\ 38 (menu)->pattern[(menu)->pindex] = '\0'; } 39 40 /*--------------------------------------------------------------------------- 41 | Facility : libnmenu 42 | Function : static bool Is_Sub_String( 43 | bool IgnoreCaseFlag, 44 | const char *part, 45 | const char *string) 46 | 47 | Description : Checks whether or not part is a substring of string. 48 | 49 | Return Values : TRUE - if it is a substring 50 | FALSE - if it is not a substring 51 +--------------------------------------------------------------------------*/ 52 static bool Is_Sub_String( 53 bool IgnoreCaseFlag, 54 const char *part, 55 const char *string 56 ) 57 { 58 assert( part && string ); 59 if ( IgnoreCaseFlag ) 60 { 61 while(*string && *part) 62 { 63 if (toupper(*string++)!=toupper(*part)) break; 64 part++; 65 } 66 } 67 else 68 { 69 while( *string && *part ) 70 if (*part != *string++) break; 71 part++; 72 } 73 return ( (*part) ? FALSE : TRUE ); 74 } 75 76 /*--------------------------------------------------------------------------- 77 | Facility : libnmenu 78 | Function : static int Match_Next_Character_In_Item_Name( 79 | MENU *menu, 80 | int ch, 81 | ITEM **item) 82 | 83 | Description : This internal routine is called for a menu positioned 84 | at an item with three different classes of characters: 85 | - a printable character; the character is added to 86 | the current pattern and the next item matching 87 | this pattern is searched. 88 | - NUL; the pattern stays as it is and the next item 89 | matching the pattern is searched 90 | - BS; the pattern stays as it is and the previous 91 | item matching the pattern is searched 92 | 93 | The item parameter contains on call a pointer to 94 | the item where the search starts. On return - if 95 | a match was found - it contains a pointer to the 96 | matching item. 97 | 98 | Return Values : E_OK - an item matching the pattern was found 99 | E_NO_MATCH - nothing found 100 +--------------------------------------------------------------------------*/ 101 static int Match_Next_Character_In_Item_Name(MENU *menu, int ch, ITEM **item) 102 { 103 bool found = FALSE, passed = FALSE; 104 int idx, last; 105 106 assert( menu && item && *item); 107 idx = (*item)->index; 108 109 if (ch && ch!=BS) 110 { 111 /* if we become to long, we need no further checking : there can't be 112 a match ! */ 113 if ((menu->pindex+1) > menu->namelen) 114 RETURN(E_NO_MATCH); 115 116 Add_Character_To_Pattern(menu,ch); 117 /* we artificially position one item back, because in the do...while 118 loop we start with the next item. This means, that with a new 119 pattern search we always start the scan with the actual item. If 120 we do a NEXT_PATTERN oder PREV_PATTERN search, we start with the 121 one after or before the actual item. */ 122 if (--idx < 0) 123 idx = menu->nitems-1; 124 } 125 126 last = idx; /* this closes the cycle */ 127 128 do{ 129 if (ch==BS) 130 { /* we have to go backward */ 131 if (--idx < 0) 132 idx = menu->nitems-1; 133 } 134 else 135 { /* otherwise we always go forward */ 136 if (++idx >= menu->nitems) 137 idx = 0; 138 } 139 if (Is_Sub_String((menu->opt & O_IGNORECASE) != 0, 140 menu->pattern, 141 menu->items[idx]->name.str) 142 ) 143 found = TRUE; 144 else 145 passed = TRUE; 146 } while (!found && (idx != last)); 147 148 if (found) 149 { 150 if (!((idx==(*item)->index) && passed)) 151 { 152 *item = menu->items[idx]; 153 RETURN(E_OK); 154 } 155 /* This point is reached, if we fully cycled through the item list 156 and the only match we found is the starting item. With a NEXT_PATTERN 157 or PREV_PATTERN scan this means, that there was no additional match. 158 If we searched with an expanded new pattern, we should never reach 159 this point, because if the expanded pattern matches also the actual 160 item we will find it in the first attempt (passed==FALSE) and we 161 will never cycle through the whole item array. 162 */ 163 assert( ch==0 || ch==BS ); 164 } 165 else 166 { 167 if (ch && ch!=BS && menu->pindex>0) 168 { 169 /* if we had no match with a new pattern, we have to restore it */ 170 Remove_Character_From_Pattern(menu); 171 } 172 } 173 RETURN(E_NO_MATCH); 174 } 175 176 /*--------------------------------------------------------------------------- 177 | Facility : libnmenu 178 | Function : char *menu_pattern(const MENU *menu) 179 | 180 | Description : Return the value of the pattern buffer. 181 | 182 | Return Values : NULL - if there is no pattern buffer allocated 183 | EmptyString - if there is a pattern buffer but no 184 | pattern is stored 185 | PatternString - as expected 186 +--------------------------------------------------------------------------*/ 187 char *menu_pattern(const MENU * menu) 188 { 189 return (menu ? (menu->pattern ? menu->pattern : "") : (char *)0); 190 } 191 192 /*--------------------------------------------------------------------------- 193 | Facility : libnmenu 194 | Function : int set_menu_pattern(MENU *menu, const char *p) 195 | 196 | Description : Set the match pattern for a menu and position to the 197 | first item that matches. 198 | 199 | Return Values : E_OK - success 200 | E_BAD_ARGUMENT - invalid menu or pattern pointer 201 | E_NOT_CONNECTED - no items connected to menu 202 | E_BAD_STATE - menu in user hook routine 203 | E_NO_MATCH - no item matches pattern 204 +--------------------------------------------------------------------------*/ 205 int set_menu_pattern(MENU *menu, const char *p) 206 { 207 ITEM *matchitem; 208 int matchpos; 209 210 if (!menu || !p) 211 RETURN(E_BAD_ARGUMENT); 212 213 if (!(menu->items)) 214 RETURN(E_NOT_CONNECTED); 215 216 if ( menu->status & _IN_DRIVER ) 217 RETURN(E_BAD_STATE); 218 219 Reset_Pattern(menu); 220 221 if (!(*p)) 222 { 223 pos_menu_cursor(menu); 224 RETURN(E_OK); 225 } 226 227 if (menu->status & _LINK_NEEDED) 228 _nc_Link_Items(menu); 229 230 matchpos = menu->toprow; 231 matchitem = menu->curitem; 232 assert(matchitem); 233 234 while(*p) 235 { 236 if ( !isprint(*p) || 237 (Match_Next_Character_In_Item_Name(menu,*p,&matchitem) != E_OK) ) 238 { 239 Reset_Pattern(menu); 240 pos_menu_cursor(menu); 241 RETURN(E_NO_MATCH); 242 } 243 p++; 244 } 245 246 /* This is reached if there was a match. So we position to the new item */ 247 Adjust_Current_Item(menu,matchpos,matchitem); 248 RETURN(E_OK); 249 } 250 251 /*--------------------------------------------------------------------------- 252 | Facility : libnmenu 253 | Function : int menu_driver(MENU *menu, int c) 254 | 255 | Description : Central dispatcher for the menu. Translates the logical 256 | request 'c' into a menu action. 257 | 258 | Return Values : E_OK - success 259 | E_BAD_ARGUMENT - invalid menu pointer 260 | E_BAD_STATE - menu is in user hook routine 261 | E_NOT_POSTED - menu is not posted 262 +--------------------------------------------------------------------------*/ 263 int menu_driver(MENU * menu, int c) 264 { 265 #define NAVIGATE(dir) \ 266 if (!item->dir)\ 267 result = E_REQUEST_DENIED;\ 268 else\ 269 item = item->dir 270 271 int result = E_OK; 272 ITEM *item; 273 int my_top_row, rdiff; 274 275 if (!menu) 276 RETURN(E_BAD_ARGUMENT); 277 278 if ( menu->status & _IN_DRIVER ) 279 RETURN(E_BAD_STATE); 280 if ( !( menu->status & _POSTED ) ) 281 RETURN(E_NOT_POSTED); 282 283 my_top_row = menu->toprow; 284 item = menu->curitem; 285 assert(item); 286 287 if ((c > KEY_MAX) && (c<=MAX_MENU_COMMAND)) 288 { 289 if (!((c==REQ_BACK_PATTERN) 290 || (c==REQ_NEXT_MATCH) || (c==REQ_PREV_MATCH))) 291 { 292 assert( menu->pattern ); 293 Reset_Pattern(menu); 294 } 295 296 switch(c) 297 { 298 case REQ_LEFT_ITEM: 299 /*=================*/ 300 NAVIGATE(left); 301 break; 302 303 case REQ_RIGHT_ITEM: 304 /*==================*/ 305 NAVIGATE(right); 306 break; 307 308 case REQ_UP_ITEM: 309 /*===============*/ 310 NAVIGATE(up); 311 break; 312 313 case REQ_DOWN_ITEM: 314 /*=================*/ 315 NAVIGATE(down); 316 break; 317 318 case REQ_SCR_ULINE: 319 /*=================*/ 320 if (my_top_row == 0) 321 result = E_REQUEST_DENIED; 322 else 323 { 324 --my_top_row; 325 item = item->up; 326 } 327 break; 328 329 case REQ_SCR_DLINE: 330 /*=================*/ 331 my_top_row++; 332 if ((menu->rows - menu->height)>0) 333 { 334 /* only if the menu has less items than rows, we can deny the 335 request. Otherwise the epilogue of this routine adjusts the 336 top row if necessary */ 337 my_top_row--; 338 result = E_REQUEST_DENIED; 339 } 340 else 341 item = item->down; 342 break; 343 344 case REQ_SCR_DPAGE: 345 /*=================*/ 346 rdiff = menu->rows - menu->height - my_top_row; 347 if (rdiff > menu->height) 348 rdiff = menu->height; 349 if (rdiff==0) 350 result = E_REQUEST_DENIED; 351 else 352 { 353 my_top_row += rdiff; 354 while(rdiff-- > 0) 355 item = item->down; 356 } 357 break; 358 359 case REQ_SCR_UPAGE: 360 /*=================*/ 361 rdiff = (menu->height < my_top_row) ? 362 menu->height : my_top_row; 363 if (rdiff==0) 364 result = E_REQUEST_DENIED; 365 else 366 { 367 my_top_row -= rdiff; 368 while(rdiff--) 369 item = item->up; 370 } 371 break; 372 373 case REQ_FIRST_ITEM: 374 /*==================*/ 375 item = menu->items[0]; 376 break; 377 378 case REQ_LAST_ITEM: 379 /*=================*/ 380 item = menu->items[menu->nitems-1]; 381 break; 382 383 case REQ_NEXT_ITEM: 384 /*=================*/ 385 if ((item->index+1)>=menu->nitems) 386 { 387 if (menu->opt & O_NONCYCLIC) 388 result = E_REQUEST_DENIED; 389 else 390 item = menu->items[0]; 391 } 392 else 393 item = menu->items[item->index + 1]; 394 break; 395 396 case REQ_PREV_ITEM: 397 /*=================*/ 398 if (item->index<=0) 399 { 400 if (menu->opt & O_NONCYCLIC) 401 result = E_REQUEST_DENIED; 402 else 403 item = menu->items[menu->nitems-1]; 404 } 405 else 406 item = menu->items[item->index - 1]; 407 break; 408 409 case REQ_TOGGLE_ITEM: 410 /*===================*/ 411 if (menu->opt & O_ONEVALUE) 412 { 413 result = E_REQUEST_DENIED; 414 } 415 else 416 { 417 if (menu->curitem->opt & O_SELECTABLE) 418 { 419 menu->curitem->value = TRUE; 420 Move_And_Post_Item(menu,menu->curitem); 421 _nc_Show_Menu(menu); 422 } 423 else 424 result = E_NOT_SELECTABLE; 425 } 426 break; 427 428 case REQ_CLEAR_PATTERN: 429 /*=====================*/ 430 /* already cleared in prologue */ 431 break; 432 433 case REQ_BACK_PATTERN: 434 /*====================*/ 435 if (menu->pindex>0) 436 { 437 assert(menu->pattern); 438 Remove_Character_From_Pattern(menu); 439 pos_menu_cursor( menu ); 440 } 441 else 442 result = E_REQUEST_DENIED; 443 break; 444 445 case REQ_NEXT_MATCH: 446 /*==================*/ 447 assert(menu->pattern); 448 if (menu->pattern[0]) 449 result = Match_Next_Character_In_Item_Name(menu,0,&item); 450 else 451 { 452 if ((item->index+1)<menu->nitems) 453 item=menu->items[item->index+1]; 454 else 455 { 456 if (menu->opt & O_NONCYCLIC) 457 result = E_REQUEST_DENIED; 458 else 459 item = menu->items[0]; 460 } 461 } 462 break; 463 464 case REQ_PREV_MATCH: 465 /*==================*/ 466 assert(menu->pattern); 467 if (menu->pattern[0]) 468 result = Match_Next_Character_In_Item_Name(menu,BS,&item); 469 else 470 { 471 if (item->index) 472 item = menu->items[item->index-1]; 473 else 474 { 475 if (menu->opt & O_NONCYCLIC) 476 result = E_REQUEST_DENIED; 477 else 478 item = menu->items[menu->nitems-1]; 479 } 480 } 481 break; 482 483 default: 484 /*======*/ 485 result = E_UNKNOWN_COMMAND; 486 break; 487 } 488 } 489 else 490 { /* not a command */ 491 if ( !(c & ~((int)MAX_REGULAR_CHARACTER)) && isprint(c) ) 492 result = Match_Next_Character_In_Item_Name( menu, c, &item ); 493 else 494 result = E_UNKNOWN_COMMAND; 495 } 496 497 /* Adjust the top row if it turns out that the current item unfortunately 498 doesn't appear in the menu window */ 499 if ( item->y < my_top_row ) 500 my_top_row = item->y; 501 else if ( item->y >= (my_top_row + menu->height) ) 502 my_top_row = item->y - menu->height + 1; 503 504 _nc_New_TopRow_and_CurrentItem( menu, my_top_row, item ); 505 506 RETURN(result); 507 } 508 509 /* m_driver.c ends here */ 510