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