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