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