1 /**************************************************************************** 2 * Copyright (c) 1998-2005,2008 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.27 2008/08/03 22:08:22 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)++] = (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)"), menu, ch, item)); 125 126 assert(menu && item && *item); 127 idx = (*item)->index; 128 129 if (ch && ch != BS) 130 { 131 /* if we become to long, we need no further checking : there can't be 132 a match ! */ 133 if ((menu->pindex + 1) > menu->namelen) 134 RETURN(E_NO_MATCH); 135 136 Add_Character_To_Pattern(menu, ch); 137 /* we artificially position one item back, because in the do...while 138 loop we start with the next item. This means, that with a new 139 pattern search we always start the scan with the actual item. If 140 we do a NEXT_PATTERN oder PREV_PATTERN search, we start with the 141 one after or before the actual item. */ 142 if (--idx < 0) 143 idx = menu->nitems - 1; 144 } 145 146 last = idx; /* this closes the cycle */ 147 148 do 149 { 150 if (ch == BS) 151 { /* we have to go backward */ 152 if (--idx < 0) 153 idx = menu->nitems - 1; 154 } 155 else 156 { /* otherwise we always go forward */ 157 if (++idx >= menu->nitems) 158 idx = 0; 159 } 160 if (Is_Sub_String((bool)((menu->opt & O_IGNORECASE) != 0), 161 menu->pattern, 162 menu->items[idx]->name.str) 163 ) 164 found = TRUE; 165 else 166 passed = TRUE; 167 } 168 while (!found && (idx != last)); 169 170 if (found) 171 { 172 if (!((idx == (*item)->index) && passed)) 173 { 174 *item = menu->items[idx]; 175 RETURN(E_OK); 176 } 177 /* This point is reached, if we fully cycled through the item list 178 and the only match we found is the starting item. With a NEXT_PATTERN 179 or PREV_PATTERN scan this means, that there was no additional match. 180 If we searched with an expanded new pattern, we should never reach 181 this point, because if the expanded pattern matches also the actual 182 item we will find it in the first attempt (passed==FALSE) and we 183 will never cycle through the whole item array. 184 */ 185 assert(ch == 0 || ch == BS); 186 } 187 else 188 { 189 if (ch && ch != BS && menu->pindex > 0) 190 { 191 /* if we had no match with a new pattern, we have to restore it */ 192 Remove_Character_From_Pattern(menu); 193 } 194 } 195 RETURN(E_NO_MATCH); 196 } 197 198 /*--------------------------------------------------------------------------- 199 | Facility : libnmenu 200 | Function : int menu_driver(MENU *menu, int c) 201 | 202 | Description : Central dispatcher for the menu. Translates the logical 203 | request 'c' into a menu action. 204 | 205 | Return Values : E_OK - success 206 | E_BAD_ARGUMENT - invalid menu pointer 207 | E_BAD_STATE - menu is in user hook routine 208 | E_NOT_POSTED - menu is not posted 209 +--------------------------------------------------------------------------*/ 210 NCURSES_EXPORT(int) 211 menu_driver(MENU * menu, int c) 212 { 213 #define NAVIGATE(dir) \ 214 if (!item->dir)\ 215 result = E_REQUEST_DENIED;\ 216 else\ 217 item = item->dir 218 219 int result = E_OK; 220 ITEM *item; 221 int my_top_row, rdiff; 222 223 T((T_CALLED("menu_driver(%p,%d)"), menu, c)); 224 225 if (!menu) 226 RETURN(E_BAD_ARGUMENT); 227 228 if (menu->status & _IN_DRIVER) 229 RETURN(E_BAD_STATE); 230 if (!(menu->status & _POSTED)) 231 RETURN(E_NOT_POSTED); 232 233 item = menu->curitem; 234 235 my_top_row = menu->toprow; 236 assert(item); 237 238 if ((c > KEY_MAX) && (c <= MAX_MENU_COMMAND)) 239 { 240 if (!((c == REQ_BACK_PATTERN) 241 || (c == REQ_NEXT_MATCH) || (c == REQ_PREV_MATCH))) 242 { 243 assert(menu->pattern); 244 Reset_Pattern(menu); 245 } 246 247 switch (c) 248 { 249 case REQ_LEFT_ITEM: 250 /*=================*/ 251 NAVIGATE(left); 252 break; 253 254 case REQ_RIGHT_ITEM: 255 /*==================*/ 256 NAVIGATE(right); 257 break; 258 259 case REQ_UP_ITEM: 260 /*===============*/ 261 NAVIGATE(up); 262 break; 263 264 case REQ_DOWN_ITEM: 265 /*=================*/ 266 NAVIGATE(down); 267 break; 268 269 case REQ_SCR_ULINE: 270 /*=================*/ 271 if (my_top_row == 0 || !(item->up)) 272 result = E_REQUEST_DENIED; 273 else 274 { 275 --my_top_row; 276 item = item->up; 277 } 278 break; 279 280 case REQ_SCR_DLINE: 281 /*=================*/ 282 if ((my_top_row + menu->arows >= menu->rows) || !(item->down)) 283 { 284 /* only if the menu has less items than rows, we can deny the 285 request. Otherwise the epilogue of this routine adjusts the 286 top row if necessary */ 287 result = E_REQUEST_DENIED; 288 } 289 else 290 { 291 my_top_row++; 292 item = item->down; 293 } 294 break; 295 296 case REQ_SCR_DPAGE: 297 /*=================*/ 298 rdiff = menu->rows - (menu->arows + my_top_row); 299 if (rdiff > menu->arows) 300 rdiff = menu->arows; 301 if (rdiff <= 0) 302 result = E_REQUEST_DENIED; 303 else 304 { 305 my_top_row += rdiff; 306 while (rdiff-- > 0 && item != 0 && item->down != 0) 307 item = item->down; 308 } 309 break; 310 311 case REQ_SCR_UPAGE: 312 /*=================*/ 313 rdiff = (menu->arows < my_top_row) ? menu->arows : my_top_row; 314 if (rdiff <= 0) 315 result = E_REQUEST_DENIED; 316 else 317 { 318 my_top_row -= rdiff; 319 while (rdiff-- > 0 && item != 0 && item->up != 0) 320 item = item->up; 321 } 322 break; 323 324 case REQ_FIRST_ITEM: 325 /*==================*/ 326 item = menu->items[0]; 327 break; 328 329 case REQ_LAST_ITEM: 330 /*=================*/ 331 item = menu->items[menu->nitems - 1]; 332 break; 333 334 case REQ_NEXT_ITEM: 335 /*=================*/ 336 if ((item->index + 1) >= menu->nitems) 337 { 338 if (menu->opt & O_NONCYCLIC) 339 result = E_REQUEST_DENIED; 340 else 341 item = menu->items[0]; 342 } 343 else 344 item = menu->items[item->index + 1]; 345 break; 346 347 case REQ_PREV_ITEM: 348 /*=================*/ 349 if (item->index <= 0) 350 { 351 if (menu->opt & O_NONCYCLIC) 352 result = E_REQUEST_DENIED; 353 else 354 item = menu->items[menu->nitems - 1]; 355 } 356 else 357 item = menu->items[item->index - 1]; 358 break; 359 360 case REQ_TOGGLE_ITEM: 361 /*===================*/ 362 if (menu->opt & O_ONEVALUE) 363 { 364 result = E_REQUEST_DENIED; 365 } 366 else 367 { 368 if (menu->curitem->opt & O_SELECTABLE) 369 { 370 menu->curitem->value = !menu->curitem->value; 371 Move_And_Post_Item(menu, menu->curitem); 372 _nc_Show_Menu(menu); 373 } 374 else 375 result = E_NOT_SELECTABLE; 376 } 377 break; 378 379 case REQ_CLEAR_PATTERN: 380 /*=====================*/ 381 /* already cleared in prologue */ 382 break; 383 384 case REQ_BACK_PATTERN: 385 /*====================*/ 386 if (menu->pindex > 0) 387 { 388 assert(menu->pattern); 389 Remove_Character_From_Pattern(menu); 390 pos_menu_cursor(menu); 391 } 392 else 393 result = E_REQUEST_DENIED; 394 break; 395 396 case REQ_NEXT_MATCH: 397 /*==================*/ 398 assert(menu->pattern); 399 if (menu->pattern[0]) 400 result = _nc_Match_Next_Character_In_Item_Name(menu, 0, &item); 401 else 402 { 403 if ((item->index + 1) < menu->nitems) 404 item = menu->items[item->index + 1]; 405 else 406 { 407 if (menu->opt & O_NONCYCLIC) 408 result = E_REQUEST_DENIED; 409 else 410 item = menu->items[0]; 411 } 412 } 413 break; 414 415 case REQ_PREV_MATCH: 416 /*==================*/ 417 assert(menu->pattern); 418 if (menu->pattern[0]) 419 result = _nc_Match_Next_Character_In_Item_Name(menu, BS, &item); 420 else 421 { 422 if (item->index) 423 item = menu->items[item->index - 1]; 424 else 425 { 426 if (menu->opt & O_NONCYCLIC) 427 result = E_REQUEST_DENIED; 428 else 429 item = menu->items[menu->nitems - 1]; 430 } 431 } 432 break; 433 434 default: 435 /*======*/ 436 result = E_UNKNOWN_COMMAND; 437 break; 438 } 439 } 440 else 441 { /* not a command */ 442 if (!(c & ~((int)MAX_REGULAR_CHARACTER)) && isprint(UChar(c))) 443 result = _nc_Match_Next_Character_In_Item_Name(menu, c, &item); 444 #ifdef NCURSES_MOUSE_VERSION 445 else if (KEY_MOUSE == c) 446 { 447 MEVENT event; 448 WINDOW *uwin = Get_Menu_UserWin(menu); 449 450 getmouse(&event); 451 if ((event.bstate & (BUTTON1_CLICKED | 452 BUTTON1_DOUBLE_CLICKED | 453 BUTTON1_TRIPLE_CLICKED)) 454 && wenclose(uwin, event.y, event.x)) 455 { /* we react only if the click was in the userwin, that means 456 * inside the menu display area or at the decoration window. 457 */ 458 WINDOW *sub = Get_Menu_Window(menu); 459 int ry = event.y, rx = event.x; /* screen coordinates */ 460 461 result = E_REQUEST_DENIED; 462 if (mouse_trafo(&ry, &rx, FALSE)) 463 { /* rx, ry are now "curses" coordinates */ 464 if (ry < sub->_begy) 465 { /* we clicked above the display region; this is 466 * interpreted as "scroll up" request 467 */ 468 if (event.bstate & BUTTON1_CLICKED) 469 result = menu_driver(menu, REQ_SCR_ULINE); 470 else if (event.bstate & BUTTON1_DOUBLE_CLICKED) 471 result = menu_driver(menu, REQ_SCR_UPAGE); 472 else if (event.bstate & BUTTON1_TRIPLE_CLICKED) 473 result = menu_driver(menu, REQ_FIRST_ITEM); 474 RETURN(result); 475 } 476 else if (ry > sub->_begy + sub->_maxy) 477 { /* we clicked below the display region; this is 478 * interpreted as "scroll down" request 479 */ 480 if (event.bstate & BUTTON1_CLICKED) 481 result = menu_driver(menu, REQ_SCR_DLINE); 482 else if (event.bstate & BUTTON1_DOUBLE_CLICKED) 483 result = menu_driver(menu, REQ_SCR_DPAGE); 484 else if (event.bstate & BUTTON1_TRIPLE_CLICKED) 485 result = menu_driver(menu, REQ_LAST_ITEM); 486 RETURN(result); 487 } 488 else if (wenclose(sub, event.y, event.x)) 489 { /* Inside the area we try to find the hit item */ 490 int i, x, y, err; 491 492 ry = event.y; 493 rx = event.x; 494 if (wmouse_trafo(sub, &ry, &rx, FALSE)) 495 { 496 for (i = 0; i < menu->nitems; i++) 497 { 498 err = _nc_menu_cursor_pos(menu, menu->items[i], 499 &y, &x); 500 if (E_OK == err) 501 { 502 if ((ry == y) && 503 (rx >= x) && 504 (rx < x + menu->itemlen)) 505 { 506 item = menu->items[i]; 507 result = E_OK; 508 break; 509 } 510 } 511 } 512 if (E_OK == result) 513 { /* We found an item, now we can handle the click. 514 * A single click just positions the menu cursor 515 * to the clicked item. A double click toggles 516 * the item. 517 */ 518 if (event.bstate & BUTTON1_DOUBLE_CLICKED) 519 { 520 _nc_New_TopRow_and_CurrentItem(menu, 521 my_top_row, 522 item); 523 menu_driver(menu, REQ_TOGGLE_ITEM); 524 result = E_UNKNOWN_COMMAND; 525 } 526 } 527 } 528 } 529 } 530 } 531 else 532 result = E_REQUEST_DENIED; 533 } 534 #endif /* NCURSES_MOUSE_VERSION */ 535 else 536 result = E_UNKNOWN_COMMAND; 537 } 538 539 if (E_OK == result) 540 { 541 /* Adjust the top row if it turns out that the current item unfortunately 542 doesn't appear in the menu window */ 543 if (item->y < my_top_row) 544 my_top_row = item->y; 545 else if (item->y >= (my_top_row + menu->arows)) 546 my_top_row = item->y - menu->arows + 1; 547 548 _nc_New_TopRow_and_CurrentItem(menu, my_top_row, item); 549 550 } 551 552 RETURN(result); 553 } 554 555 /* m_driver.c ends here */ 556