ncurses 5.7 - patch 20100123
[ncurses.git] / menu / m_driver.c
1 /****************************************************************************
2  * Copyright (c) 1998-2009,2010 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
40 MODULE_ID("$Id: m_driver.c,v 1.29 2010/01/23 21:20:10 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 +--------------------------------------------------------------------------*/
65 static bool
66 Is_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 +--------------------------------------------------------------------------*/
117 NCURSES_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 +--------------------------------------------------------------------------*/
211 NCURSES_EXPORT(int)
212 menu_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 (E_OK == result)
541     {
542       /* Adjust the top row if it turns out that the current item unfortunately
543          doesn't appear in the menu window */
544       if (item->y < my_top_row)
545         my_top_row = item->y;
546       else if (item->y >= (my_top_row + menu->arows))
547         my_top_row = item->y - menu->arows + 1;
548
549       _nc_New_TopRow_and_CurrentItem(menu, my_top_row, item);
550
551     }
552
553   RETURN(result);
554 }
555
556 /* m_driver.c ends here */