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