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