1 /**************************************************************************** 2 * Copyright 2020,2021 Thomas E. Dickey * 3 * Copyright 1998-2012,2016 Free Software Foundation, Inc. * 4 * * 5 * Permission is hereby granted, free of charge, to any person obtaining a * 6 * copy of this software and associated documentation files (the * 7 * "Software"), to deal in the Software without restriction, including * 8 * without limitation the rights to use, copy, modify, merge, publish, * 9 * distribute, distribute with modifications, sublicense, and/or sell * 10 * copies of the Software, and to permit persons to whom the Software is * 11 * furnished to do so, subject to the following conditions: * 12 * * 13 * The above copyright notice and this permission notice shall be included * 14 * in all copies or substantial portions of the Software. * 15 * * 16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * 17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * 18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * 19 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * 20 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR * 21 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR * 22 * THE USE OR OTHER DEALINGS IN THE SOFTWARE. * 23 * * 24 * Except as contained in this notice, the name(s) of the above copyright * 25 * holders shall not be used in advertising or otherwise to promote the * 26 * sale, use or other dealings in this Software without prior written * 27 * authorization. * 28 ****************************************************************************/ 29 30 /**************************************************************************** 31 * Author: Juergen Pfeifer, 1995,1997 * 32 ****************************************************************************/ 33 34 /*************************************************************************** 35 * Module m_driver * 36 * Central dispatching routine * 37 ***************************************************************************/ 38 39 #include "menu.priv.h" 40 41 MODULE_ID("$Id: m_driver.c,v 1.37 2021/03/27 23:46:29 tom Exp $") 42 43 /* Macros */ 44 45 /* Remove the last character from the match pattern buffer */ 46 #define Remove_Character_From_Pattern(menu) \ 47 (menu)->pattern[--((menu)->pindex)] = '\0' 48 49 /* Add a new character to the match pattern buffer */ 50 #define Add_Character_To_Pattern(menu,ch) \ 51 { (menu)->pattern[((menu)->pindex)++] = (char) (ch);\ 52 (menu)->pattern[(menu)->pindex] = '\0'; } 53 54 /*--------------------------------------------------------------------------- 55 | Facility : libnmenu 56 | Function : static bool Is_Sub_String( 57 | bool IgnoreCaseFlag, 58 | const char *part, 59 | const char *string) 60 | 61 | Description : Checks whether or not part is a substring of string. 62 | 63 | Return Values : TRUE - if it is a substring 64 | FALSE - if it is not a substring 65 +--------------------------------------------------------------------------*/ 66 static bool 67 Is_Sub_String( 68 bool IgnoreCaseFlag, 69 const char *part, 70 const char *string 71 ) 72 { 73 assert(part && string); 74 if (IgnoreCaseFlag) 75 { 76 while (*string && *part) 77 { 78 if (toupper(UChar(*string++)) != toupper(UChar(*part))) 79 break; 80 part++; 81 } 82 } 83 else 84 { 85 while (*string && *part) 86 if (*part != *string++) 87 break; 88 part++; 89 } 90 return ((*part) ? FALSE : TRUE); 91 } 92 93 /*--------------------------------------------------------------------------- 94 | Facility : libnmenu 95 | Function : int _nc_Match_Next_Character_In_Item_Name( 96 | MENU *menu, 97 | int ch, 98 | ITEM **item) 99 | 100 | Description : This internal routine is called for a menu positioned 101 | at an item with three different classes of characters: 102 | - a printable character; the character is added to 103 | the current pattern and the next item matching 104 | this pattern is searched. 105 | - NUL; the pattern stays as it is and the next item 106 | matching the pattern is searched 107 | - BS; the pattern stays as it is and the previous 108 | item matching the pattern is searched 109 | 110 | The item parameter contains on call a pointer to 111 | the item where the search starts. On return - if 112 | a match was found - it contains a pointer to the 113 | matching item. 114 | 115 | Return Values : E_OK - an item matching the pattern was found 116 | E_NO_MATCH - nothing found 117 +--------------------------------------------------------------------------*/ 118 MENU_EXPORT(int) 119 _nc_Match_Next_Character_In_Item_Name 120 (MENU *menu, int ch, ITEM **item) 121 { 122 bool found = FALSE, passed = FALSE; 123 int idx, last; 124 125 T((T_CALLED("_nc_Match_Next_Character(%p,%d,%p)"), 126 (void *)menu, ch, (void *)item)); 127 128 assert(menu && item && *item); 129 idx = (*item)->index; 130 131 if (ch && ch != BS) 132 { 133 /* if we become to long, we need no further checking : there can't be 134 a match ! */ 135 if ((menu->pindex + 1) > menu->namelen) 136 RETURN(E_NO_MATCH); 137 138 Add_Character_To_Pattern(menu, ch); 139 /* we artificially position one item back, because in the do...while 140 loop we start with the next item. This means, that with a new 141 pattern search we always start the scan with the actual item. If 142 we do a NEXT_PATTERN or PREV_PATTERN search, we start with the 143 one after or before the actual item. */ 144 if (--idx < 0) 145 idx = menu->nitems - 1; 146 } 147 148 last = idx; /* this closes the cycle */ 149 150 do 151 { 152 if (ch == BS) 153 { /* we have to go backward */ 154 if (--idx < 0) 155 idx = menu->nitems - 1; 156 } 157 else 158 { /* otherwise we always go forward */ 159 if (++idx >= menu->nitems) 160 idx = 0; 161 } 162 if (Is_Sub_String((bool)((menu->opt & O_IGNORECASE) != 0), 163 menu->pattern, 164 menu->items[idx]->name.str) 165 ) 166 found = TRUE; 167 else 168 passed = TRUE; 169 } 170 while (!found && (idx != last)); 171 172 if (found) 173 { 174 if (!((idx == (*item)->index) && passed)) 175 { 176 *item = menu->items[idx]; 177 RETURN(E_OK); 178 } 179 /* This point is reached, if we fully cycled through the item list 180 and the only match we found is the starting item. With a NEXT_PATTERN 181 or PREV_PATTERN scan this means, that there was no additional match. 182 If we searched with an expanded new pattern, we should never reach 183 this point, because if the expanded pattern matches also the actual 184 item we will find it in the first attempt (passed==FALSE) and we 185 will never cycle through the whole item array. 186 */ 187 assert(ch == 0 || ch == BS); 188 } 189 else 190 { 191 if (ch && ch != BS && menu->pindex > 0) 192 { 193 /* if we had no match with a new pattern, we have to restore it */ 194 Remove_Character_From_Pattern(menu); 195 } 196 } 197 RETURN(E_NO_MATCH); 198 } 199 200 /*--------------------------------------------------------------------------- 201 | Facility : libnmenu 202 | Function : int menu_driver(MENU* menu, int c) 203 | 204 | Description : Central dispatcher for the menu. Translates the logical 205 | request 'c' into a menu action. 206 | 207 | Return Values : E_OK - success 208 | E_BAD_ARGUMENT - invalid menu pointer 209 | E_BAD_STATE - menu is in user hook routine 210 | E_NOT_POSTED - menu is not posted 211 +--------------------------------------------------------------------------*/ 212 MENU_EXPORT(int) 213 menu_driver(MENU *menu, int c) 214 { 215 #define NAVIGATE(dir) \ 216 if (!item->dir)\ 217 result = E_REQUEST_DENIED;\ 218 else\ 219 item = item->dir 220 221 int result = E_OK; 222 ITEM *item; 223 int my_top_row; 224 225 T((T_CALLED("menu_driver(%p,%d)"), (void *)menu, c)); 226 227 if (!menu) 228 RETURN(E_BAD_ARGUMENT); 229 230 if (menu->status & _IN_DRIVER) 231 RETURN(E_BAD_STATE); 232 if (!(menu->status & _POSTED)) 233 RETURN(E_NOT_POSTED); 234 235 item = menu->curitem; 236 237 my_top_row = menu->toprow; 238 assert(item); 239 240 if ((c > KEY_MAX) && (c <= MAX_MENU_COMMAND)) 241 { 242 int rdiff; 243 244 if (!((c == REQ_BACK_PATTERN) 245 || (c == REQ_NEXT_MATCH) || (c == REQ_PREV_MATCH))) 246 { 247 assert(menu->pattern); 248 Reset_Pattern(menu); 249 } 250 251 switch (c) 252 { 253 case REQ_LEFT_ITEM: 254 /*=================*/ 255 NAVIGATE(left); 256 break; 257 258 case REQ_RIGHT_ITEM: 259 /*==================*/ 260 NAVIGATE(right); 261 break; 262 263 case REQ_UP_ITEM: 264 /*===============*/ 265 NAVIGATE(up); 266 break; 267 268 case REQ_DOWN_ITEM: 269 /*=================*/ 270 NAVIGATE(down); 271 break; 272 273 case REQ_SCR_ULINE: 274 /*=================*/ 275 if (my_top_row == 0 || !(item->up)) 276 result = E_REQUEST_DENIED; 277 else 278 { 279 --my_top_row; 280 item = item->up; 281 } 282 break; 283 284 case REQ_SCR_DLINE: 285 /*=================*/ 286 if ((my_top_row + menu->arows >= menu->rows) || !(item->down)) 287 { 288 /* only if the menu has less items than rows, we can deny the 289 request. Otherwise the epilogue of this routine adjusts the 290 top row if necessary */ 291 result = E_REQUEST_DENIED; 292 } 293 else 294 { 295 my_top_row++; 296 item = item->down; 297 } 298 break; 299 300 case REQ_SCR_DPAGE: 301 /*=================*/ 302 rdiff = menu->rows - (menu->arows + my_top_row); 303 if (rdiff > menu->arows) 304 rdiff = menu->arows; 305 if (rdiff <= 0) 306 result = E_REQUEST_DENIED; 307 else 308 { 309 my_top_row += rdiff; 310 while (rdiff-- > 0 && item != 0 && item->down != 0) 311 item = item->down; 312 } 313 break; 314 315 case REQ_SCR_UPAGE: 316 /*=================*/ 317 rdiff = (menu->arows < my_top_row) ? menu->arows : my_top_row; 318 if (rdiff <= 0) 319 result = E_REQUEST_DENIED; 320 else 321 { 322 my_top_row -= rdiff; 323 while (rdiff-- > 0 && item != 0 && item->up != 0) 324 item = item->up; 325 } 326 break; 327 328 case REQ_FIRST_ITEM: 329 /*==================*/ 330 item = menu->items[0]; 331 break; 332 333 case REQ_LAST_ITEM: 334 /*=================*/ 335 item = menu->items[menu->nitems - 1]; 336 break; 337 338 case REQ_NEXT_ITEM: 339 /*=================*/ 340 if ((item->index + 1) >= menu->nitems) 341 { 342 if (menu->opt & O_NONCYCLIC) 343 result = E_REQUEST_DENIED; 344 else 345 item = menu->items[0]; 346 } 347 else 348 item = menu->items[item->index + 1]; 349 break; 350 351 case REQ_PREV_ITEM: 352 /*=================*/ 353 if (item->index <= 0) 354 { 355 if (menu->opt & O_NONCYCLIC) 356 result = E_REQUEST_DENIED; 357 else 358 item = menu->items[menu->nitems - 1]; 359 } 360 else 361 item = menu->items[item->index - 1]; 362 break; 363 364 case REQ_TOGGLE_ITEM: 365 /*===================*/ 366 if (menu->opt & O_ONEVALUE) 367 { 368 result = E_REQUEST_DENIED; 369 } 370 else 371 { 372 if (menu->curitem->opt & O_SELECTABLE) 373 { 374 menu->curitem->value = !menu->curitem->value; 375 Move_And_Post_Item(menu, menu->curitem); 376 _nc_Show_Menu(menu); 377 } 378 else 379 result = E_NOT_SELECTABLE; 380 } 381 break; 382 383 case REQ_CLEAR_PATTERN: 384 /*=====================*/ 385 /* already cleared in prologue */ 386 break; 387 388 case REQ_BACK_PATTERN: 389 /*====================*/ 390 if (menu->pindex > 0) 391 { 392 assert(menu->pattern); 393 Remove_Character_From_Pattern(menu); 394 pos_menu_cursor(menu); 395 } 396 else 397 result = E_REQUEST_DENIED; 398 break; 399 400 case REQ_NEXT_MATCH: 401 /*==================*/ 402 assert(menu->pattern); 403 if (menu->pattern[0]) 404 result = _nc_Match_Next_Character_In_Item_Name(menu, 0, &item); 405 else 406 { 407 if ((item->index + 1) < menu->nitems) 408 item = menu->items[item->index + 1]; 409 else 410 { 411 if (menu->opt & O_NONCYCLIC) 412 result = E_REQUEST_DENIED; 413 else 414 item = menu->items[0]; 415 } 416 } 417 break; 418 419 case REQ_PREV_MATCH: 420 /*==================*/ 421 assert(menu->pattern); 422 if (menu->pattern[0]) 423 result = _nc_Match_Next_Character_In_Item_Name(menu, BS, &item); 424 else 425 { 426 if (item->index) 427 item = menu->items[item->index - 1]; 428 else 429 { 430 if (menu->opt & O_NONCYCLIC) 431 result = E_REQUEST_DENIED; 432 else 433 item = menu->items[menu->nitems - 1]; 434 } 435 } 436 break; 437 438 default: 439 /*======*/ 440 result = E_UNKNOWN_COMMAND; 441 break; 442 } 443 } 444 else 445 { /* not a command */ 446 if (!(c & ~((int)MAX_REGULAR_CHARACTER)) && isprint(UChar(c))) 447 result = _nc_Match_Next_Character_In_Item_Name(menu, c, &item); 448 #ifdef NCURSES_MOUSE_VERSION 449 else if (KEY_MOUSE == c) 450 { 451 MEVENT event; 452 WINDOW *uwin = Get_Menu_UserWin(menu); 453 454 getmouse(&event); 455 if ((event.bstate & (BUTTON1_CLICKED | 456 BUTTON1_DOUBLE_CLICKED | 457 BUTTON1_TRIPLE_CLICKED)) 458 && wenclose(uwin, event.y, event.x)) 459 { /* we react only if the click was in the userwin, that means 460 * inside the menu display area or at the decoration window. 461 */ 462 WINDOW *sub = Get_Menu_Window(menu); 463 int ry = event.y, rx = event.x; /* screen coordinates */ 464 465 result = E_REQUEST_DENIED; 466 if (mouse_trafo(&ry, &rx, FALSE)) 467 { /* rx, ry are now "curses" coordinates */ 468 if (ry < sub->_begy) 469 { /* we clicked above the display region; this is 470 * interpreted as "scroll up" request 471 */ 472 if (event.bstate & BUTTON1_CLICKED) 473 result = menu_driver(menu, REQ_SCR_ULINE); 474 else if (event.bstate & BUTTON1_DOUBLE_CLICKED) 475 result = menu_driver(menu, REQ_SCR_UPAGE); 476 else if (event.bstate & BUTTON1_TRIPLE_CLICKED) 477 result = menu_driver(menu, REQ_FIRST_ITEM); 478 RETURN(result); 479 } 480 else if (ry > sub->_begy + sub->_maxy) 481 { /* we clicked below the display region; this is 482 * interpreted as "scroll down" request 483 */ 484 if (event.bstate & BUTTON1_CLICKED) 485 result = menu_driver(menu, REQ_SCR_DLINE); 486 else if (event.bstate & BUTTON1_DOUBLE_CLICKED) 487 result = menu_driver(menu, REQ_SCR_DPAGE); 488 else if (event.bstate & BUTTON1_TRIPLE_CLICKED) 489 result = menu_driver(menu, REQ_LAST_ITEM); 490 RETURN(result); 491 } 492 else if (wenclose(sub, event.y, event.x)) 493 { /* Inside the area we try to find the hit item */ 494 int x, y; 495 496 ry = event.y; 497 rx = event.x; 498 if (wmouse_trafo(sub, &ry, &rx, FALSE)) 499 { 500 int i; 501 502 for (i = 0; i < menu->nitems; i++) 503 { 504 int err = _nc_menu_cursor_pos(menu, 505 menu->items[i], 506 &y, &x); 507 508 if (E_OK == err) 509 { 510 if ((ry == y) && 511 (rx >= x) && 512 (rx < x + menu->itemlen)) 513 { 514 item = menu->items[i]; 515 result = E_OK; 516 break; 517 } 518 } 519 } 520 if (E_OK == result) 521 { /* We found an item, now we can handle the click. 522 * A single click just positions the menu cursor 523 * to the clicked item. A double click toggles 524 * the item. 525 */ 526 if (event.bstate & BUTTON1_DOUBLE_CLICKED) 527 { 528 _nc_New_TopRow_and_CurrentItem(menu, 529 my_top_row, 530 item); 531 menu_driver(menu, REQ_TOGGLE_ITEM); 532 result = E_UNKNOWN_COMMAND; 533 } 534 } 535 } 536 } 537 } 538 } 539 else 540 { 541 if (menu->opt & O_MOUSE_MENU) 542 ungetmouse(&event); /* let someone else handle this */ 543 result = E_REQUEST_DENIED; 544 } 545 } 546 #endif /* NCURSES_MOUSE_VERSION */ 547 else 548 result = E_UNKNOWN_COMMAND; 549 } 550 551 if (item == 0) 552 { 553 result = E_BAD_STATE; 554 } 555 else if (E_OK == result) 556 { 557 /* Adjust the top row if it turns out that the current item unfortunately 558 doesn't appear in the menu window */ 559 if (item->y < my_top_row) 560 my_top_row = item->y; 561 else if (item->y >= (my_top_row + menu->arows)) 562 my_top_row = item->y - menu->arows + 1; 563 564 _nc_New_TopRow_and_CurrentItem(menu, my_top_row, item); 565 566 } 567 568 RETURN(result); 569 } 570 571 /* m_driver.c ends here */ 572