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