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