xref: /freebsd/contrib/ncurses/menu/m_driver.c (revision b9f654b163bce26de79705e77b872427c9f2afa1)
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