xref: /openbsd/lib/libmenu/m_driver.c (revision 07ea8d15)
1 
2 /***************************************************************************
3 *                            COPYRIGHT NOTICE                              *
4 ****************************************************************************
5 *                ncurses is copyright (C) 1992-1995                        *
6 *                          Zeyd M. Ben-Halim                               *
7 *                          zmbenhal@netcom.com                             *
8 *                          Eric S. Raymond                                 *
9 *                          esr@snark.thyrsus.com                           *
10 *                                                                          *
11 *        Permission is hereby granted to reproduce and distribute ncurses  *
12 *        by any means and for any fee, whether alone or as part of a       *
13 *        larger distribution, in source or in binary form, PROVIDED        *
14 *        this notice is included with any such distribution, and is not    *
15 *        removed from any of its header files. Mention of ncurses in any   *
16 *        applications linked with it is highly appreciated.                *
17 *                                                                          *
18 *        ncurses comes AS IS with no warranty, implied or expressed.       *
19 *                                                                          *
20 ***************************************************************************/
21 
22 /***************************************************************************
23 * Module menu_driver and menu_pattern                                      *
24 * Central dispatching routine and pattern matching handling                *
25 ***************************************************************************/
26 
27 #include "menu.priv.h"
28 
29 /* Macros */
30 
31 /* Remove the last character from the match pattern buffer */
32 #define Remove_Character_From_Pattern(menu) \
33   (menu)->pattern[--((menu)->pindex)] = '\0'
34 
35 /* Add a new character to the match pattern buffer */
36 #define Add_Character_To_Pattern(menu,ch) \
37   { (menu)->pattern[((menu)->pindex)++] = (ch);\
38     (menu)->pattern[(menu)->pindex] = '\0'; }
39 
40 /*---------------------------------------------------------------------------
41 |   Facility      :  libnmenu
42 |   Function      :  static bool Is_Sub_String(
43 |                           bool IgnoreCaseFlag,
44 |                           const char *part,
45 |                           const char *string)
46 |
47 |   Description   :  Checks whether or not part is a substring of string.
48 |
49 |   Return Values :  TRUE   - if it is a substring
50 |                    FALSE  - if it is not a substring
51 +--------------------------------------------------------------------------*/
52 static bool Is_Sub_String(
53 			  bool  IgnoreCaseFlag,
54 			  const char *part,
55 			  const char *string
56 			 )
57 {
58   assert( part && string );
59   if ( IgnoreCaseFlag )
60     {
61       while(*string && *part)
62 	{
63 	  if (toupper(*string++)!=toupper(*part)) break;
64 	  part++;
65 	}
66     }
67   else
68     {
69       while( *string && *part )
70 	if (*part != *string++) break;
71       part++;
72     }
73   return ( (*part) ? FALSE : TRUE );
74 }
75 
76 /*---------------------------------------------------------------------------
77 |   Facility      :  libnmenu
78 |   Function      :  static int Match_Next_Character_In_Item_Name(
79 |                           MENU *menu,
80 |                           int  ch,
81 |                           ITEM **item)
82 |
83 |   Description   :  This internal routine is called for a menu positioned
84 |                    at an item with three different classes of characters:
85 |                       - a printable character; the character is added to
86 |                         the current pattern and the next item matching
87 |                         this pattern is searched.
88 |                       - NUL; the pattern stays as it is and the next item
89 |                         matching the pattern is searched
90 |                       - BS; the pattern stays as it is and the previous
91 |                         item matching the pattern is searched
92 |
93 |                       The item parameter contains on call a pointer to
94 |                       the item where the search starts. On return - if
95 |                       a match was found - it contains a pointer to the
96 |                       matching item.
97 |
98 |   Return Values :  E_OK        - an item matching the pattern was found
99 |                    E_NO_MATCH  - nothing found
100 +--------------------------------------------------------------------------*/
101 static int Match_Next_Character_In_Item_Name(MENU *menu, int ch, ITEM **item)
102 {
103   bool found = FALSE, passed = FALSE;
104   int  idx, last;
105 
106   assert( menu && item && *item);
107   idx = (*item)->index;
108 
109   if (ch && ch!=BS)
110     {
111       /* if we become to long, we need no further checking : there can't be
112 	 a match ! */
113       if ((menu->pindex+1) > menu->namelen)
114 	RETURN(E_NO_MATCH);
115 
116       Add_Character_To_Pattern(menu,ch);
117       /* we artificially position one item back, because in the do...while
118 	 loop we start with the next item. This means, that with a new
119 	 pattern search we always start the scan with the actual item. If
120 	 we do a NEXT_PATTERN oder PREV_PATTERN search, we start with the
121 	 one after or before the actual item. */
122       if (--idx < 0)
123 	idx = menu->nitems-1;
124     }
125 
126   last = idx;			/* this closes the cycle */
127 
128   do{
129     if (ch==BS)
130       {			/* we have to go backward */
131 	if (--idx < 0)
132 	  idx = menu->nitems-1;
133       }
134     else
135       {			/* otherwise we always go forward */
136 	if (++idx >= menu->nitems)
137 	  idx = 0;
138       }
139     if (Is_Sub_String((menu->opt & O_IGNORECASE) != 0,
140 		      menu->pattern,
141 		      menu->items[idx]->name.str)
142 	)
143       found = TRUE;
144     else
145       passed = TRUE;
146   } while (!found && (idx != last));
147 
148   if (found)
149     {
150       if (!((idx==(*item)->index) && passed))
151 	{
152 	  *item = menu->items[idx];
153 	  RETURN(E_OK);
154 	}
155       /* This point is reached, if we fully cycled through the item list
156 	 and the only match we found is the starting item. With a NEXT_PATTERN
157 	 or PREV_PATTERN scan this means, that there was no additional match.
158 	 If we searched with an expanded new pattern, we should never reach
159 	 this point, because if the expanded pattern matches also the actual
160 	 item we will find it in the first attempt (passed==FALSE) and we
161 	 will never cycle through the whole item array.
162 	 */
163       assert( ch==0 || ch==BS );
164     }
165   else
166     {
167       if (ch && ch!=BS && menu->pindex>0)
168 	{
169 	  /* if we had no match with a new pattern, we have to restore it */
170 	  Remove_Character_From_Pattern(menu);
171 	}
172     }
173   RETURN(E_NO_MATCH);
174 }
175 
176 /*---------------------------------------------------------------------------
177 |   Facility      :  libnmenu
178 |   Function      :  char *menu_pattern(const MENU *menu)
179 |
180 |   Description   :  Return the value of the pattern buffer.
181 |
182 |   Return Values :  NULL          - if there is no pattern buffer allocated
183 |                    EmptyString   - if there is a pattern buffer but no
184 |                                    pattern is stored
185 |                    PatternString - as expected
186 +--------------------------------------------------------------------------*/
187 char *menu_pattern(const MENU * menu)
188 {
189   return (menu ? (menu->pattern ? menu->pattern : "") : (char *)0);
190 }
191 
192 /*---------------------------------------------------------------------------
193 |   Facility      :  libnmenu
194 |   Function      :  int set_menu_pattern(MENU *menu, const char *p)
195 |
196 |   Description   :  Set the match pattern for a menu and position to the
197 |                    first item that matches.
198 |
199 |   Return Values :  E_OK              - success
200 |                    E_BAD_ARGUMENT    - invalid menu or pattern pointer
201 |                    E_NOT_CONNECTED   - no items connected to menu
202 |                    E_BAD_STATE       - menu in user hook routine
203 |                    E_NO_MATCH        - no item matches pattern
204 +--------------------------------------------------------------------------*/
205 int set_menu_pattern(MENU *menu, const char *p)
206 {
207   ITEM *matchitem;
208   int   matchpos;
209 
210   if (!menu || !p)
211     RETURN(E_BAD_ARGUMENT);
212 
213   if (!(menu->items))
214     RETURN(E_NOT_CONNECTED);
215 
216   if ( menu->status & _IN_DRIVER )
217     RETURN(E_BAD_STATE);
218 
219   Reset_Pattern(menu);
220 
221   if (!(*p))
222     {
223       pos_menu_cursor(menu);
224       RETURN(E_OK);
225     }
226 
227   if (menu->status & _LINK_NEEDED)
228     _nc_Link_Items(menu);
229 
230   matchpos  = menu->toprow;
231   matchitem = menu->curitem;
232   assert(matchitem);
233 
234   while(*p)
235     {
236       if ( !isprint(*p) ||
237 	  (Match_Next_Character_In_Item_Name(menu,*p,&matchitem) != E_OK) )
238 	{
239 	  Reset_Pattern(menu);
240 	  pos_menu_cursor(menu);
241 	  RETURN(E_NO_MATCH);
242 	}
243       p++;
244     }
245 
246   /* This is reached if there was a match. So we position to the new item */
247   Adjust_Current_Item(menu,matchpos,matchitem);
248   RETURN(E_OK);
249 }
250 
251 /*---------------------------------------------------------------------------
252 |   Facility      :  libnmenu
253 |   Function      :  int menu_driver(MENU *menu, int c)
254 |
255 |   Description   :  Central dispatcher for the menu. Translates the logical
256 |                    request 'c' into a menu action.
257 |
258 |   Return Values :  E_OK            - success
259 |                    E_BAD_ARGUMENT  - invalid menu pointer
260 |                    E_BAD_STATE     - menu is in user hook routine
261 |                    E_NOT_POSTED    - menu is not posted
262 +--------------------------------------------------------------------------*/
263 int menu_driver(MENU * menu, int   c)
264 {
265 #define NAVIGATE(dir) \
266   if (!item->dir)\
267      result = E_REQUEST_DENIED;\
268   else\
269      item = item->dir
270 
271   int result = E_OK;
272   ITEM *item;
273   int my_top_row, rdiff;
274 
275   if (!menu)
276     RETURN(E_BAD_ARGUMENT);
277 
278   if ( menu->status & _IN_DRIVER )
279     RETURN(E_BAD_STATE);
280   if ( !( menu->status & _POSTED ) )
281     RETURN(E_NOT_POSTED);
282 
283   my_top_row = menu->toprow;
284   item    = menu->curitem;
285   assert(item);
286 
287   if ((c > KEY_MAX) && (c<=MAX_MENU_COMMAND))
288     {
289       if (!((c==REQ_BACK_PATTERN)
290 	    || (c==REQ_NEXT_MATCH) || (c==REQ_PREV_MATCH)))
291 	{
292 	  assert( menu->pattern );
293 	  Reset_Pattern(menu);
294 	}
295 
296       switch(c)
297 	{
298 	case REQ_LEFT_ITEM:
299 	  /*=================*/
300 	  NAVIGATE(left);
301 	  break;
302 
303 	case REQ_RIGHT_ITEM:
304 	  /*==================*/
305 	  NAVIGATE(right);
306 	  break;
307 
308 	case REQ_UP_ITEM:
309 	  /*===============*/
310 	  NAVIGATE(up);
311 	  break;
312 
313 	case REQ_DOWN_ITEM:
314 	  /*=================*/
315 	  NAVIGATE(down);
316 	  break;
317 
318 	case REQ_SCR_ULINE:
319 	  /*=================*/
320 	  if (my_top_row == 0)
321 	    result = E_REQUEST_DENIED;
322 	  else
323 	    {
324 	      --my_top_row;
325 	      item = item->up;
326 	    }
327 	  break;
328 
329 	case REQ_SCR_DLINE:
330 	  /*=================*/
331 	  my_top_row++;
332 	  if ((menu->rows - menu->height)>0)
333 	    {
334 	      /* only if the menu has less items than rows, we can deny the
335 		 request. Otherwise the epilogue of this routine adjusts the
336 		 top row if necessary */
337 	      my_top_row--;
338 	      result = E_REQUEST_DENIED;
339 	    }
340 	  else
341 	    item = item->down;
342 	  break;
343 
344 	case REQ_SCR_DPAGE:
345 	  /*=================*/
346 	  rdiff = menu->rows - menu->height - my_top_row;
347 	  if (rdiff > menu->height)
348 	    rdiff = menu->height;
349 	  if (rdiff==0)
350 	    result = E_REQUEST_DENIED;
351 	  else
352 	    {
353 	      my_top_row += rdiff;
354 	      while(rdiff-- > 0)
355 		item = item->down;
356 	    }
357 	  break;
358 
359 	case REQ_SCR_UPAGE:
360 	  /*=================*/
361 	  rdiff = (menu->height < my_top_row) ?
362 	    menu->height : my_top_row;
363 	  if (rdiff==0)
364 	    result = E_REQUEST_DENIED;
365 	  else
366 	    {
367 	      my_top_row -= rdiff;
368 	      while(rdiff--)
369 		item = item->up;
370 	    }
371 	  break;
372 
373 	case REQ_FIRST_ITEM:
374 	  /*==================*/
375 	  item = menu->items[0];
376 	  break;
377 
378 	case REQ_LAST_ITEM:
379 	  /*=================*/
380 	  item = menu->items[menu->nitems-1];
381 	  break;
382 
383 	case REQ_NEXT_ITEM:
384 	  /*=================*/
385 	  if ((item->index+1)>=menu->nitems)
386 	    {
387 	      if (menu->opt & O_NONCYCLIC)
388 		result = E_REQUEST_DENIED;
389 	      else
390 		item = menu->items[0];
391 	    }
392 	  else
393 	    item = menu->items[item->index + 1];
394 	  break;
395 
396 	case REQ_PREV_ITEM:
397 	  /*=================*/
398 	  if (item->index<=0)
399 	    {
400 	      if (menu->opt & O_NONCYCLIC)
401 		result = E_REQUEST_DENIED;
402 	      else
403 		item = menu->items[menu->nitems-1];
404 	    }
405 	  else
406 	    item = menu->items[item->index - 1];
407 	  break;
408 
409 	case REQ_TOGGLE_ITEM:
410 	  /*===================*/
411 	  if (menu->opt & O_ONEVALUE)
412 	    {
413 	      result = E_REQUEST_DENIED;
414 	    }
415 	  else
416 	    {
417 	      if (menu->curitem->opt & O_SELECTABLE)
418 		{
419 		  menu->curitem->value = TRUE;
420 		  Move_And_Post_Item(menu,menu->curitem);
421 		  _nc_Show_Menu(menu);
422 		}
423 	      else
424 		result = E_NOT_SELECTABLE;
425 	    }
426 	  break;
427 
428 	case REQ_CLEAR_PATTERN:
429 	  /*=====================*/
430 	  /* already cleared in prologue */
431 	  break;
432 
433 	case REQ_BACK_PATTERN:
434 	  /*====================*/
435 	  if (menu->pindex>0)
436 	    {
437 	      assert(menu->pattern);
438 	      Remove_Character_From_Pattern(menu);
439 	      pos_menu_cursor( menu );
440 	    }
441 	  else
442 	    result = E_REQUEST_DENIED;
443 	  break;
444 
445 	case REQ_NEXT_MATCH:
446 	  /*==================*/
447 	  assert(menu->pattern);
448 	  if (menu->pattern[0])
449 	    result = Match_Next_Character_In_Item_Name(menu,0,&item);
450 	  else
451 	    {
452 	      if ((item->index+1)<menu->nitems)
453 		item=menu->items[item->index+1];
454 	      else
455 		{
456 		  if (menu->opt & O_NONCYCLIC)
457 		    result = E_REQUEST_DENIED;
458 		  else
459 		    item = menu->items[0];
460 		}
461 	    }
462 	  break;
463 
464 	case REQ_PREV_MATCH:
465 	  /*==================*/
466 	  assert(menu->pattern);
467 	  if (menu->pattern[0])
468 	    result = Match_Next_Character_In_Item_Name(menu,BS,&item);
469 	  else
470 	    {
471 	      if (item->index)
472 		item = menu->items[item->index-1];
473 	      else
474 		{
475 		  if (menu->opt & O_NONCYCLIC)
476 		    result = E_REQUEST_DENIED;
477 		  else
478 		    item = menu->items[menu->nitems-1];
479 		}
480 	    }
481 	  break;
482 
483 	default:
484 	  /*======*/
485 	  result = E_UNKNOWN_COMMAND;
486 	  break;
487 	}
488     }
489   else
490     {				/* not a command */
491       if ( !(c & ~((int)MAX_REGULAR_CHARACTER)) && isprint(c) )
492 	result = Match_Next_Character_In_Item_Name( menu, c, &item );
493       else
494 	result = E_UNKNOWN_COMMAND;
495     }
496 
497   /* Adjust the top row if it turns out that the current item unfortunately
498      doesn't appear in the menu window */
499   if ( item->y < my_top_row )
500     my_top_row = item->y;
501   else if ( item->y >= (my_top_row + menu->height) )
502     my_top_row = item->y - menu->height + 1;
503 
504   _nc_New_TopRow_and_CurrentItem( menu, my_top_row, item );
505 
506   RETURN(result);
507 }
508 
509 /* m_driver.c ends here */
510