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