xref: /openbsd-src/lib/libmenu/m_driver.c (revision a28daedfc357b214be5c701aa8ba8adb29a7f1c2)
1 /*	$OpenBSD: m_driver.c,v 1.7 2001/01/22 18:02:03 millert Exp $	*/
2 
3 /****************************************************************************
4  * Copyright (c) 1998,2000 Free Software Foundation, Inc.                   *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30 
31 /****************************************************************************
32  *   Author: Juergen Pfeifer <juergen.pfeifer@gmx.net> 1995,1997            *
33  ****************************************************************************/
34 
35 /***************************************************************************
36 * Module m_driver                                                          *
37 * Central dispatching routine                                              *
38 ***************************************************************************/
39 
40 #include "menu.priv.h"
41 
42 MODULE_ID("$From: m_driver.c,v 1.18 2000/12/10 02:16:48 tom Exp $")
43 
44 /* Macros */
45 
46 /* Remove the last character from the match pattern buffer */
47 #define Remove_Character_From_Pattern(menu) \
48   (menu)->pattern[--((menu)->pindex)] = '\0'
49 
50 /* Add a new character to the match pattern buffer */
51 #define Add_Character_To_Pattern(menu,ch) \
52   { (menu)->pattern[((menu)->pindex)++] = (ch);\
53     (menu)->pattern[(menu)->pindex] = '\0'; }
54 
55 /*---------------------------------------------------------------------------
56 |   Facility      :  libnmenu
57 |   Function      :  static bool Is_Sub_String(
58 |                           bool IgnoreCaseFlag,
59 |                           const char *part,
60 |                           const char *string)
61 |
62 |   Description   :  Checks whether or not part is a substring of string.
63 |
64 |   Return Values :  TRUE   - if it is a substring
65 |                    FALSE  - if it is not a substring
66 +--------------------------------------------------------------------------*/
67 static bool Is_Sub_String(
68 			  bool  IgnoreCaseFlag,
69 			  const char *part,
70 			  const char *string
71 			 )
72 {
73   assert( part && string );
74   if ( IgnoreCaseFlag )
75     {
76       while(*string && *part)
77 	{
78 	  if (toupper(*string++)!=toupper(*part)) break;
79 	  part++;
80 	}
81     }
82   else
83     {
84       while( *string && *part )
85 	if (*part != *string++) break;
86       part++;
87     }
88   return ( (*part) ? FALSE : TRUE );
89 }
90 
91 /*---------------------------------------------------------------------------
92 |   Facility      :  libnmenu
93 |   Function      :  int _nc_Match_Next_Character_In_Item_Name(
94 |                           MENU *menu,
95 |                           int  ch,
96 |                           ITEM **item)
97 |
98 |   Description   :  This internal routine is called for a menu positioned
99 |                    at an item with three different classes of characters:
100 |                       - a printable character; the character is added to
101 |                         the current pattern and the next item matching
102 |                         this pattern is searched.
103 |                       - NUL; the pattern stays as it is and the next item
104 |                         matching the pattern is searched
105 |                       - BS; the pattern stays as it is and the previous
106 |                         item matching the pattern is searched
107 |
108 |                       The item parameter contains on call a pointer to
109 |                       the item where the search starts. On return - if
110 |                       a match was found - it contains a pointer to the
111 |                       matching item.
112 |
113 |   Return Values :  E_OK        - an item matching the pattern was found
114 |                    E_NO_MATCH  - nothing found
115 +--------------------------------------------------------------------------*/
116 NCURSES_EXPORT(int)
117 _nc_Match_Next_Character_In_Item_Name
118 (MENU *menu, int ch, ITEM **item)
119 {
120   bool found = FALSE, passed = FALSE;
121   int  idx, last;
122 
123   assert( menu && item && *item);
124   idx = (*item)->index;
125 
126   if (ch && ch!=BS)
127     {
128       /* if we become to long, we need no further checking : there can't be
129 	 a match ! */
130       if ((menu->pindex+1) > menu->namelen)
131 	RETURN(E_NO_MATCH);
132 
133       Add_Character_To_Pattern(menu,ch);
134       /* we artificially position one item back, because in the do...while
135 	 loop we start with the next item. This means, that with a new
136 	 pattern search we always start the scan with the actual item. If
137 	 we do a NEXT_PATTERN oder PREV_PATTERN search, we start with the
138 	 one after or before the actual item. */
139       if (--idx < 0)
140 	idx = menu->nitems-1;
141     }
142 
143   last = idx;			/* this closes the cycle */
144 
145   do{
146     if (ch==BS)
147       {			/* we have to go backward */
148 	if (--idx < 0)
149 	  idx = menu->nitems-1;
150       }
151     else
152       {			/* otherwise we always go forward */
153 	if (++idx >= menu->nitems)
154 	  idx = 0;
155       }
156     if (Is_Sub_String((menu->opt & O_IGNORECASE) != 0,
157 		      menu->pattern,
158 		      menu->items[idx]->name.str)
159 	)
160       found = TRUE;
161     else
162       passed = TRUE;
163   } while (!found && (idx != last));
164 
165   if (found)
166     {
167       if (!((idx==(*item)->index) && passed))
168 	{
169 	  *item = menu->items[idx];
170 	  RETURN(E_OK);
171 	}
172       /* This point is reached, if we fully cycled through the item list
173 	 and the only match we found is the starting item. With a NEXT_PATTERN
174 	 or PREV_PATTERN scan this means, that there was no additional match.
175 	 If we searched with an expanded new pattern, we should never reach
176 	 this point, because if the expanded pattern matches also the actual
177 	 item we will find it in the first attempt (passed==FALSE) and we
178 	 will never cycle through the whole item array.
179 	 */
180       assert( ch==0 || ch==BS );
181     }
182   else
183     {
184       if (ch && ch!=BS && menu->pindex>0)
185 	{
186 	  /* if we had no match with a new pattern, we have to restore it */
187 	  Remove_Character_From_Pattern(menu);
188 	}
189     }
190   RETURN(E_NO_MATCH);
191 }
192 
193 /*---------------------------------------------------------------------------
194 |   Facility      :  libnmenu
195 |   Function      :  int menu_driver(MENU *menu, int c)
196 |
197 |   Description   :  Central dispatcher for the menu. Translates the logical
198 |                    request 'c' into a menu action.
199 |
200 |   Return Values :  E_OK            - success
201 |                    E_BAD_ARGUMENT  - invalid menu pointer
202 |                    E_BAD_STATE     - menu is in user hook routine
203 |                    E_NOT_POSTED    - menu is not posted
204 +--------------------------------------------------------------------------*/
205 NCURSES_EXPORT(int)
206 menu_driver (MENU * menu, int   c)
207 {
208 #define NAVIGATE(dir) \
209   if (!item->dir)\
210      result = E_REQUEST_DENIED;\
211   else\
212      item = item->dir
213 
214   int result = E_OK;
215   ITEM *item;
216   int my_top_row, rdiff;
217 
218   if (!menu)
219     RETURN(E_BAD_ARGUMENT);
220 
221   if ( menu->status & _IN_DRIVER )
222     RETURN(E_BAD_STATE);
223   if ( !( menu->status & _POSTED ) )
224     RETURN(E_NOT_POSTED);
225 
226   item = menu->curitem;
227 
228     my_top_row = menu->toprow;
229     assert(item);
230 
231     if ((c > KEY_MAX) && (c<=MAX_MENU_COMMAND))
232       {
233 	if (!((c==REQ_BACK_PATTERN)
234 	      || (c==REQ_NEXT_MATCH) || (c==REQ_PREV_MATCH)))
235 	  {
236 	    assert( menu->pattern );
237 	    Reset_Pattern(menu);
238 	  }
239 
240 	switch(c)
241 	  {
242 	  case REQ_LEFT_ITEM:
243 	    /*=================*/
244 	    NAVIGATE(left);
245 	    break;
246 
247 	  case REQ_RIGHT_ITEM:
248 	    /*==================*/
249 	    NAVIGATE(right);
250 	    break;
251 
252 	  case REQ_UP_ITEM:
253 	    /*===============*/
254 	    NAVIGATE(up);
255 	    break;
256 
257 	  case REQ_DOWN_ITEM:
258 	    /*=================*/
259 	    NAVIGATE(down);
260 	    break;
261 
262 	  case REQ_SCR_ULINE:
263 	    /*=================*/
264 	  if (my_top_row == 0 || !(item->up))
265 	      result = E_REQUEST_DENIED;
266 	    else
267 	      {
268 		--my_top_row;
269 		item = item->up;
270 	      }
271 	    break;
272 
273 	  case REQ_SCR_DLINE:
274 	    /*=================*/
275 	  if ((my_top_row + menu->arows >= menu->rows) || !(item->down))
276 	      {
277 		/* only if the menu has less items than rows, we can deny the
278 		   request. Otherwise the epilogue of this routine adjusts the
279 		   top row if necessary */
280 		result = E_REQUEST_DENIED;
281 	      }
282 	  else {
283 	    my_top_row++;
284 	      item = item->down;
285 	  }
286 	    break;
287 
288 	  case REQ_SCR_DPAGE:
289 	    /*=================*/
290 	  rdiff = menu->rows - (menu->arows + my_top_row);
291 	    if (rdiff > menu->arows)
292 	      rdiff = menu->arows;
293 	  if (rdiff<=0)
294 	      result = E_REQUEST_DENIED;
295 	    else
296 	      {
297 		my_top_row += rdiff;
298 	      while(rdiff-- > 0 && item!=(ITEM*)0)
299 		  item = item->down;
300 	      }
301 	    break;
302 
303 	  case REQ_SCR_UPAGE:
304 	    /*=================*/
305 	  rdiff = (menu->arows < my_top_row) ? menu->arows : my_top_row;
306 	  if (rdiff<=0)
307 	      result = E_REQUEST_DENIED;
308 	    else
309 	      {
310 		my_top_row -= rdiff;
311 	      while(rdiff-- && item!=(ITEM*)0)
312 		  item = item->up;
313 	      }
314 	    break;
315 
316 	  case REQ_FIRST_ITEM:
317 	    /*==================*/
318 	    item = menu->items[0];
319 	    break;
320 
321 	  case REQ_LAST_ITEM:
322 	    /*=================*/
323 	    item = menu->items[menu->nitems-1];
324 	    break;
325 
326 	  case REQ_NEXT_ITEM:
327 	    /*=================*/
328 	    if ((item->index+1)>=menu->nitems)
329 	      {
330 		if (menu->opt & O_NONCYCLIC)
331 		  result = E_REQUEST_DENIED;
332 		else
333 		  item = menu->items[0];
334 	      }
335 	    else
336 	      item = menu->items[item->index + 1];
337 	    break;
338 
339 	  case REQ_PREV_ITEM:
340 	    /*=================*/
341 	    if (item->index<=0)
342 	      {
343 		if (menu->opt & O_NONCYCLIC)
344 		  result = E_REQUEST_DENIED;
345 		else
346 		  item = menu->items[menu->nitems-1];
347 	      }
348 	    else
349 	      item = menu->items[item->index - 1];
350 	    break;
351 
352 	  case REQ_TOGGLE_ITEM:
353 	    /*===================*/
354 	    if (menu->opt & O_ONEVALUE)
355 	      {
356 		result = E_REQUEST_DENIED;
357 	      }
358 	    else
359 	      {
360 		if (menu->curitem->opt & O_SELECTABLE)
361 		  {
362 		    menu->curitem->value = !menu->curitem->value;
363 		    Move_And_Post_Item(menu,menu->curitem);
364 		    _nc_Show_Menu(menu);
365 		  }
366 		else
367 		  result = E_NOT_SELECTABLE;
368 	      }
369 	    break;
370 
371 	  case REQ_CLEAR_PATTERN:
372 	    /*=====================*/
373 	    /* already cleared in prologue */
374 	    break;
375 
376 	  case REQ_BACK_PATTERN:
377 	    /*====================*/
378 	    if (menu->pindex>0)
379 	      {
380 		assert(menu->pattern);
381 		Remove_Character_From_Pattern(menu);
382 		pos_menu_cursor( menu );
383 	      }
384 	    else
385 	      result = E_REQUEST_DENIED;
386 	    break;
387 
388 	  case REQ_NEXT_MATCH:
389 	    /*==================*/
390 	    assert(menu->pattern);
391 	    if (menu->pattern[0])
392 	      result = _nc_Match_Next_Character_In_Item_Name(menu,0,&item);
393 	    else
394 	      {
395 		if ((item->index+1)<menu->nitems)
396 		  item=menu->items[item->index+1];
397 		else
398 		  {
399 		    if (menu->opt & O_NONCYCLIC)
400 		      result = E_REQUEST_DENIED;
401 		    else
402 		      item = menu->items[0];
403 		  }
404 	      }
405 	    break;
406 
407 	  case REQ_PREV_MATCH:
408 	    /*==================*/
409 	    assert(menu->pattern);
410 	    if (menu->pattern[0])
411 	      result = _nc_Match_Next_Character_In_Item_Name(menu,BS,&item);
412 	    else
413 	      {
414 		if (item->index)
415 		  item = menu->items[item->index-1];
416 		else
417 		  {
418 		    if (menu->opt & O_NONCYCLIC)
419 		      result = E_REQUEST_DENIED;
420 		    else
421 		      item = menu->items[menu->nitems-1];
422 		  }
423 	      }
424 	    break;
425 
426 	  default:
427 	    /*======*/
428 	    result = E_UNKNOWN_COMMAND;
429 	    break;
430 	  }
431       }
432     else
433       {				/* not a command */
434 	if ( !(c & ~((int)MAX_REGULAR_CHARACTER)) && isprint(c) )
435 	  result = _nc_Match_Next_Character_In_Item_Name( menu, c, &item );
436 #ifdef NCURSES_MOUSE_VERSION
437         else if (KEY_MOUSE == c)
438 	  {
439 	    MEVENT	event;
440 	    WINDOW* uwin = Get_Menu_UserWin(menu);
441 
442 	    getmouse(&event);
443 	    if ((event.bstate & (BUTTON1_CLICKED         |
444 				 BUTTON1_DOUBLE_CLICKED  |
445 				 BUTTON1_TRIPLE_CLICKED   ))
446 	     && wenclose(uwin,event.y, event.x))
447 	      { /* we react only if the click was in the userwin, that means
448 		 * inside the menu display area or at the decoration window.
449 		 */
450 		WINDOW* sub = Get_Menu_Window(menu);
451 		int ry = event.y, rx = event.x; /* screen coordinates */
452 
453 		result = E_REQUEST_DENIED;
454 		if (mouse_trafo(&ry,&rx,FALSE))
455 		  { /* rx, ry are now "curses" coordinates */
456 		    if (ry < sub->_begy)
457 		      { /* we clicked above the display region; this is
458 			 * interpreted as "scroll up" request
459 			 */
460 			if (event.bstate & BUTTON1_CLICKED)
461 			  result = menu_driver(menu,REQ_SCR_ULINE);
462 			else if (event.bstate & BUTTON1_DOUBLE_CLICKED)
463 			  result = menu_driver(menu,REQ_SCR_UPAGE);
464 			else if (event.bstate & BUTTON1_TRIPLE_CLICKED)
465 			  result = menu_driver(menu,REQ_FIRST_ITEM);
466 			RETURN(result);
467 		      }
468 		    else if (ry >= sub->_begy + sub->_maxy)
469 		      { /* we clicked below the display region; this is
470 			 * interpreted as "scroll down" request
471 			 */
472 			if (event.bstate & BUTTON1_CLICKED)
473 			  result = menu_driver(menu,REQ_SCR_DLINE);
474 			else if (event.bstate & BUTTON1_DOUBLE_CLICKED)
475 			  result = menu_driver(menu,REQ_SCR_DPAGE);
476 			else if (event.bstate & BUTTON1_TRIPLE_CLICKED)
477 			  result = menu_driver(menu,REQ_LAST_ITEM);
478 			RETURN(result);
479 		      }
480 		    else if (wenclose(sub,event.y,event.x))
481 		      { /* Inside the area we try to find the hit item */
482 			int i,x,y,err;
483 			ry = event.y; rx = event.x;
484 			if (wmouse_trafo(sub,&ry,&rx,FALSE))
485 			  {
486 			    for(i=0;i<menu->nitems;i++)
487 			      {
488 				err = _nc_menu_cursor_pos(menu,menu->items[i],
489 							  &y, &x);
490 				if (E_OK==err)
491 				  {
492 				    if ((ry==y)       &&
493 					(rx>=x)       &&
494 					(rx < x + menu->itemlen))
495 				      {
496 					item = menu->items[i];
497 					result = E_OK;
498 					break;
499 				      }
500 				  }
501 			      }
502 			    if (E_OK==result)
503 			      { /* We found an item, now we can handle the click.
504 				 * A single click just positions the menu cursor
505 				 * to the clicked item. A double click toggles
506 				 * the item.
507 				 */
508 				if (event.bstate & BUTTON1_DOUBLE_CLICKED)
509 				  {
510 				    _nc_New_TopRow_and_CurrentItem(menu,
511 								   my_top_row,
512 								   item);
513 				    menu_driver(menu,REQ_TOGGLE_ITEM);
514 				    result = E_UNKNOWN_COMMAND;
515 				  }
516 			      }
517 			  }
518 		      }
519 		  }
520 	      }
521 	    else
522 	        result = E_REQUEST_DENIED;
523 	  }
524 #endif /* NCURSES_MOUSE_VERSION */
525 	else
526 	  result = E_UNKNOWN_COMMAND;
527       }
528 
529   if (E_OK==result)
530     {
531     /* Adjust the top row if it turns out that the current item unfortunately
532        doesn't appear in the menu window */
533     if ( item->y < my_top_row )
534       my_top_row = item->y;
535     else if ( item->y >= (my_top_row + menu->arows) )
536       my_top_row = item->y - menu->arows + 1;
537 
538     _nc_New_TopRow_and_CurrentItem( menu, my_top_row, item );
539 
540     }
541 
542   RETURN(result);
543 }
544 
545 /* m_driver.c ends here */
546