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