5e47beac1e08b519e11de7a6501378e46a26976a
[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@T-Online.de> 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.10 1998/02/11 12:13:49 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 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   my_top_row = menu->toprow;
222   item    = menu->curitem;
223   assert(item);
224   
225   if ((c > KEY_MAX) && (c<=MAX_MENU_COMMAND))
226     {  
227       if (!((c==REQ_BACK_PATTERN)
228             || (c==REQ_NEXT_MATCH) || (c==REQ_PREV_MATCH)))
229         {
230           assert( menu->pattern );
231           Reset_Pattern(menu);
232         }
233       
234       switch(c)
235         {
236         case REQ_LEFT_ITEM:
237           /*=================*/  
238           NAVIGATE(left);
239           break;
240           
241         case REQ_RIGHT_ITEM:
242           /*==================*/  
243           NAVIGATE(right);
244           break;
245           
246         case REQ_UP_ITEM:
247           /*===============*/  
248           NAVIGATE(up);
249           break;
250           
251         case REQ_DOWN_ITEM:
252           /*=================*/  
253           NAVIGATE(down);
254           break;
255           
256         case REQ_SCR_ULINE:
257           /*=================*/  
258           if (my_top_row == 0)
259             result = E_REQUEST_DENIED;
260           else
261             {
262               --my_top_row;
263               item = item->up;
264             }  
265           break;
266           
267         case REQ_SCR_DLINE:
268           /*=================*/  
269           my_top_row++;
270           if ((menu->rows - menu->arows)>0)
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               my_top_row--;
276               result = E_REQUEST_DENIED;
277             }
278           else
279             item = item->down;
280           break;
281           
282         case REQ_SCR_DPAGE:
283           /*=================*/  
284           rdiff = menu->rows - menu->arows - my_top_row;
285           if (rdiff > menu->arows) 
286             rdiff = menu->arows;
287           if (rdiff==0)
288             result = E_REQUEST_DENIED;
289           else
290             {
291               my_top_row += rdiff;
292               while(rdiff-- > 0)
293                 item = item->down;
294             }
295           break;
296           
297         case REQ_SCR_UPAGE:
298           /*=================*/  
299           rdiff = (menu->arows < my_top_row) ?
300             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--)
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       else
432         result = E_UNKNOWN_COMMAND;
433     }
434   
435   /* Adjust the top row if it turns out that the current item unfortunately
436      doesn't appear in the menu window */
437   if ( item->y < my_top_row )
438     my_top_row = item->y;
439   else if ( item->y >= (my_top_row + menu->arows) )
440     my_top_row = item->y - menu->arows + 1;
441   
442   _nc_New_TopRow_and_CurrentItem( menu, my_top_row, item );
443   
444   RETURN(result);
445 }
446
447 /* m_driver.c ends here */