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