ncurses 6.2 - patch 20200404
[ncurses.git] / menu / m_driver.c
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
41 MODULE_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 +--------------------------------------------------------------------------*/
66 static bool
67 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(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 +--------------------------------------------------------------------------*/
118 NCURSES_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 +--------------------------------------------------------------------------*/
212 NCURSES_EXPORT(int)
213 menu_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 */