5e86843cc262523ab9c8ff601b56d2f5dc82644a
[ncurses.git] / menu / m_driver.c
1 /*-----------------------------------------------------------------------------+
2 |           The ncurses menu library is  Copyright (C) 1995-1997               |
3 |             by Juergen Pfeifer <Juergen.Pfeifer@T-Online.de>                 |
4 |                          All Rights Reserved.                                |
5 |                                                                              |
6 | Permission to use, copy, modify, and distribute this software and its        |
7 | documentation for any purpose and without fee is hereby granted, provided    |
8 | that the above copyright notice appear in all copies and that both that      |
9 | copyright notice and this permission notice appear in supporting             |
10 | documentation, and that the name of the above listed copyright holder(s) not |
11 | be used in advertising or publicity pertaining to distribution of the        |
12 | software without specific, written prior permission.                         | 
13 |                                                                              |
14 | THE ABOVE LISTED COPYRIGHT HOLDER(S) DISCLAIM ALL WARRANTIES WITH REGARD TO  |
15 | THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FIT-  |
16 | NESS, IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR   |
17 | ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RE- |
18 | SULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, |
19 | NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH    |
20 | THE USE OR PERFORMANCE OF THIS SOFTWARE.                                     |
21 +-----------------------------------------------------------------------------*/
22
23 /***************************************************************************
24 * Module menu_driver and menu_pattern                                      *
25 * Central dispatching routine and pattern matching handling                *
26 ***************************************************************************/
27
28 #include "menu.priv.h"
29
30 MODULE_ID("$Id: m_driver.c,v 1.8 1997/05/01 16:47:26 juergen Exp $")
31
32 /* Macros */
33
34 /* Remove the last character from the match pattern buffer */
35 #define Remove_Character_From_Pattern(menu) \
36   (menu)->pattern[--((menu)->pindex)] = '\0'
37
38 /* Add a new character to the match pattern buffer */
39 #define Add_Character_To_Pattern(menu,ch) \
40   { (menu)->pattern[((menu)->pindex)++] = (ch);\
41     (menu)->pattern[(menu)->pindex] = '\0'; }
42
43 /*---------------------------------------------------------------------------
44 |   Facility      :  libnmenu  
45 |   Function      :  static bool Is_Sub_String( 
46 |                           bool IgnoreCaseFlag,
47 |                           const char *part,
48 |                           const char *string)
49 |   
50 |   Description   :  Checks whether or not part is a substring of string.
51 |
52 |   Return Values :  TRUE   - if it is a substring
53 |                    FALSE  - if it is not a substring
54 +--------------------------------------------------------------------------*/
55 static bool Is_Sub_String(
56                           bool  IgnoreCaseFlag,
57                           const char *part,
58                           const char *string
59                          )
60 {
61   assert( part && string );
62   if ( IgnoreCaseFlag )
63     {
64       while(*string && *part)
65         {
66           if (toupper(*string++)!=toupper(*part)) break;
67           part++;
68         }
69     }
70   else
71     {
72       while( *string && *part )
73         if (*part != *string++) break;
74       part++;
75     }
76   return ( (*part) ? FALSE : TRUE );
77 }
78
79 /*---------------------------------------------------------------------------
80 |   Facility      :  libnmenu  
81 |   Function      :  static int Match_Next_Character_In_Item_Name(
82 |                           MENU *menu,
83 |                           int  ch,
84 |                           ITEM **item)
85 |   
86 |   Description   :  This internal routine is called for a menu positioned
87 |                    at an item with three different classes of characters:
88 |                       - a printable character; the character is added to
89 |                         the current pattern and the next item matching
90 |                         this pattern is searched.
91 |                       - NUL; the pattern stays as it is and the next item
92 |                         matching the pattern is searched
93 |                       - BS; the pattern stays as it is and the previous
94 |                         item matching the pattern is searched
95 |
96 |                       The item parameter contains on call a pointer to
97 |                       the item where the search starts. On return - if
98 |                       a match was found - it contains a pointer to the
99 |                       matching item.
100 |  
101 |   Return Values :  E_OK        - an item matching the pattern was found
102 |                    E_NO_MATCH  - nothing found
103 +--------------------------------------------------------------------------*/
104 static int Match_Next_Character_In_Item_Name(MENU *menu, int ch, ITEM **item)
105 {
106   bool found = FALSE, passed = FALSE;
107   int  idx, last;
108   
109   assert( menu && item && *item);
110   idx = (*item)->index;
111   
112   if (ch && ch!=BS)
113     {
114       /* if we become to long, we need no further checking : there can't be
115          a match ! */
116       if ((menu->pindex+1) > menu->namelen) 
117         RETURN(E_NO_MATCH);
118       
119       Add_Character_To_Pattern(menu,ch);
120       /* we artificially position one item back, because in the do...while
121          loop we start with the next item. This means, that with a new
122          pattern search we always start the scan with the actual item. If
123          we do a NEXT_PATTERN oder PREV_PATTERN search, we start with the
124          one after or before the actual item. */
125       if (--idx < 0) 
126         idx = menu->nitems-1;
127     }
128   
129   last = idx;                   /* this closes the cycle */
130   
131   do{
132     if (ch==BS)
133       {                 /* we have to go backward */
134         if (--idx < 0) 
135           idx = menu->nitems-1;
136       }
137     else
138       {                 /* otherwise we always go forward */
139         if (++idx >= menu->nitems) 
140           idx = 0;
141       }
142     if (Is_Sub_String((menu->opt & O_IGNORECASE) != 0,
143                       menu->pattern,
144                       menu->items[idx]->name.str)
145         )
146       found = TRUE;
147     else
148       passed = TRUE;    
149   } while (!found && (idx != last));
150   
151   if (found)
152     {
153       if (!((idx==(*item)->index) && passed))
154         {
155           *item = menu->items[idx];
156           RETURN(E_OK);
157         }
158       /* This point is reached, if we fully cycled through the item list
159          and the only match we found is the starting item. With a NEXT_PATTERN
160          or PREV_PATTERN scan this means, that there was no additional match.
161          If we searched with an expanded new pattern, we should never reach
162          this point, because if the expanded pattern matches also the actual
163          item we will find it in the first attempt (passed==FALSE) and we
164          will never cycle through the whole item array.   
165          */
166       assert( ch==0 || ch==BS );
167     }
168   else
169     {
170       if (ch && ch!=BS && menu->pindex>0)
171         {
172           /* if we had no match with a new pattern, we have to restore it */
173           Remove_Character_From_Pattern(menu);
174         }
175     }           
176   RETURN(E_NO_MATCH);
177 }       
178
179 /*---------------------------------------------------------------------------
180 |   Facility      :  libnmenu  
181 |   Function      :  char *menu_pattern(const MENU *menu)
182 |   
183 |   Description   :  Return the value of the pattern buffer.
184 |
185 |   Return Values :  NULL          - if there is no pattern buffer allocated
186 |                    EmptyString   - if there is a pattern buffer but no
187 |                                    pattern is stored
188 |                    PatternString - as expected
189 +--------------------------------------------------------------------------*/
190 char *menu_pattern(const MENU * menu)
191 {
192   return (menu ? (menu->pattern ? menu->pattern : "") : (char *)0);
193 }
194
195 /*---------------------------------------------------------------------------
196 |   Facility      :  libnmenu  
197 |   Function      :  int set_menu_pattern(MENU *menu, const char *p)
198 |   
199 |   Description   :  Set the match pattern for a menu and position to the
200 |                    first item that matches.
201 |
202 |   Return Values :  E_OK              - success
203 |                    E_BAD_ARGUMENT    - invalid menu or pattern pointer
204 |                    E_NOT_CONNECTED   - no items connected to menu
205 |                    E_BAD_STATE       - menu in user hook routine
206 |                    E_NO_MATCH        - no item matches pattern
207 +--------------------------------------------------------------------------*/
208 int set_menu_pattern(MENU *menu, const char *p)
209 {
210   ITEM *matchitem;
211   int   matchpos;
212   
213   if (!menu || !p)      
214     RETURN(E_BAD_ARGUMENT);
215   
216   if (!(menu->items))
217     RETURN(E_NOT_CONNECTED);
218   
219   if ( menu->status & _IN_DRIVER )
220     RETURN(E_BAD_STATE);
221   
222   Reset_Pattern(menu);
223   
224   if (!(*p))
225     {
226       pos_menu_cursor(menu);
227       RETURN(E_OK);
228     }
229   
230   if (menu->status & _LINK_NEEDED) 
231     _nc_Link_Items(menu);
232   
233   matchpos  = menu->toprow;
234   matchitem = menu->curitem;
235   assert(matchitem);
236   
237   while(*p)
238     {
239       if ( !isprint(*p) || 
240           (Match_Next_Character_In_Item_Name(menu,*p,&matchitem) != E_OK) )
241         {
242           Reset_Pattern(menu);
243           pos_menu_cursor(menu);
244           RETURN(E_NO_MATCH);
245         }
246       p++;
247     }                   
248   
249   /* This is reached if there was a match. So we position to the new item */
250   Adjust_Current_Item(menu,matchpos,matchitem);
251   RETURN(E_OK);
252 }
253
254 /*---------------------------------------------------------------------------
255 |   Facility      :  libnmenu  
256 |   Function      :  int menu_driver(MENU *menu, int c)
257 |   
258 |   Description   :  Central dispatcher for the menu. Translates the logical
259 |                    request 'c' into a menu action.
260 |
261 |   Return Values :  E_OK            - success
262 |                    E_BAD_ARGUMENT  - invalid menu pointer
263 |                    E_BAD_STATE     - menu is in user hook routine
264 |                    E_NOT_POSTED    - menu is not posted
265 +--------------------------------------------------------------------------*/
266 int menu_driver(MENU * menu, int   c)
267 {
268 #define NAVIGATE(dir) \
269   if (!item->dir)\
270      result = E_REQUEST_DENIED;\
271   else\
272      item = item->dir
273
274   int result = E_OK;
275   ITEM *item;
276   int my_top_row, rdiff;
277   
278   if (!menu)
279     RETURN(E_BAD_ARGUMENT);
280   
281   if ( menu->status & _IN_DRIVER )
282     RETURN(E_BAD_STATE);
283   if ( !( menu->status & _POSTED ) )
284     RETURN(E_NOT_POSTED);
285   
286   my_top_row = menu->toprow;
287   item    = menu->curitem;
288   assert(item);
289   
290   if ((c > KEY_MAX) && (c<=MAX_MENU_COMMAND))
291     {  
292       if (!((c==REQ_BACK_PATTERN)
293             || (c==REQ_NEXT_MATCH) || (c==REQ_PREV_MATCH)))
294         {
295           assert( menu->pattern );
296           Reset_Pattern(menu);
297         }
298       
299       switch(c)
300         {
301         case REQ_LEFT_ITEM:
302           /*=================*/  
303           NAVIGATE(left);
304           break;
305           
306         case REQ_RIGHT_ITEM:
307           /*==================*/  
308           NAVIGATE(right);
309           break;
310           
311         case REQ_UP_ITEM:
312           /*===============*/  
313           NAVIGATE(up);
314           break;
315           
316         case REQ_DOWN_ITEM:
317           /*=================*/  
318           NAVIGATE(down);
319           break;
320           
321         case REQ_SCR_ULINE:
322           /*=================*/  
323           if (my_top_row == 0)
324             result = E_REQUEST_DENIED;
325           else
326             {
327               --my_top_row;
328               item = item->up;
329             }  
330           break;
331           
332         case REQ_SCR_DLINE:
333           /*=================*/  
334           my_top_row++;
335           if ((menu->rows - menu->arows)>0)
336             {
337               /* only if the menu has less items than rows, we can deny the
338                  request. Otherwise the epilogue of this routine adjusts the
339                  top row if necessary */
340               my_top_row--;
341               result = E_REQUEST_DENIED;
342             }
343           else
344             item = item->down;
345           break;
346           
347         case REQ_SCR_DPAGE:
348           /*=================*/  
349           rdiff = menu->rows - menu->arows - my_top_row;
350           if (rdiff > menu->arows) 
351             rdiff = menu->arows;
352           if (rdiff==0)
353             result = E_REQUEST_DENIED;
354           else
355             {
356               my_top_row += rdiff;
357               while(rdiff-- > 0)
358                 item = item->down;
359             }
360           break;
361           
362         case REQ_SCR_UPAGE:
363           /*=================*/  
364           rdiff = (menu->arows < my_top_row) ?
365             menu->arows : my_top_row;
366           if (rdiff==0)
367             result = E_REQUEST_DENIED;
368           else
369             {
370               my_top_row -= rdiff;
371               while(rdiff--)
372                 item = item->up;
373             }
374           break;
375           
376         case REQ_FIRST_ITEM:
377           /*==================*/  
378           item = menu->items[0];
379           break;
380           
381         case REQ_LAST_ITEM:
382           /*=================*/  
383           item = menu->items[menu->nitems-1];
384           break;
385
386         case REQ_NEXT_ITEM:
387           /*=================*/  
388           if ((item->index+1)>=menu->nitems)
389             {
390               if (menu->opt & O_NONCYCLIC)
391                 result = E_REQUEST_DENIED;
392               else
393                 item = menu->items[0];
394             }
395           else
396             item = menu->items[item->index + 1];
397           break;
398           
399         case REQ_PREV_ITEM:
400           /*=================*/  
401           if (item->index<=0)
402             {
403               if (menu->opt & O_NONCYCLIC)
404                 result = E_REQUEST_DENIED;
405               else
406                 item = menu->items[menu->nitems-1];
407             }
408           else
409             item = menu->items[item->index - 1];
410           break;
411           
412         case REQ_TOGGLE_ITEM:
413           /*===================*/  
414           if (menu->opt & O_ONEVALUE)
415             {
416               result = E_REQUEST_DENIED;
417             }
418           else
419             {
420               if (menu->curitem->opt & O_SELECTABLE)
421                 {
422                   menu->curitem->value = !menu->curitem->value;
423                   Move_And_Post_Item(menu,menu->curitem);
424                   _nc_Show_Menu(menu);
425                 }
426               else
427                 result = E_NOT_SELECTABLE;
428             }
429           break;
430           
431         case REQ_CLEAR_PATTERN:
432           /*=====================*/  
433           /* already cleared in prologue */
434           break;
435           
436         case REQ_BACK_PATTERN:
437           /*====================*/  
438           if (menu->pindex>0)
439             {
440               assert(menu->pattern);
441               Remove_Character_From_Pattern(menu);
442               pos_menu_cursor( menu );
443             }
444           else
445             result = E_REQUEST_DENIED;
446           break;
447           
448         case REQ_NEXT_MATCH:
449           /*==================*/  
450           assert(menu->pattern);
451           if (menu->pattern[0])
452             result = Match_Next_Character_In_Item_Name(menu,0,&item);
453           else
454             {
455               if ((item->index+1)<menu->nitems)
456                 item=menu->items[item->index+1];
457               else
458                 {
459                   if (menu->opt & O_NONCYCLIC)
460                     result = E_REQUEST_DENIED;
461                   else
462                     item = menu->items[0];
463                 }
464             }
465           break;        
466           
467         case REQ_PREV_MATCH:
468           /*==================*/  
469           assert(menu->pattern);
470           if (menu->pattern[0])
471             result = Match_Next_Character_In_Item_Name(menu,BS,&item);
472           else
473             {
474               if (item->index)
475                 item = menu->items[item->index-1];
476               else
477                 {
478                   if (menu->opt & O_NONCYCLIC)
479                     result = E_REQUEST_DENIED;
480                   else
481                     item = menu->items[menu->nitems-1];
482                 }
483             }
484           break;
485           
486         default:
487           /*======*/  
488           result = E_UNKNOWN_COMMAND;
489           break;
490         }
491     }
492   else
493     {                           /* not a command */
494       if ( !(c & ~((int)MAX_REGULAR_CHARACTER)) && isprint(c) )
495         result = Match_Next_Character_In_Item_Name( menu, c, &item );
496       else
497         result = E_UNKNOWN_COMMAND;
498     }
499   
500   /* Adjust the top row if it turns out that the current item unfortunately
501      doesn't appear in the menu window */
502   if ( item->y < my_top_row )
503     my_top_row = item->y;
504   else if ( item->y >= (my_top_row + menu->arows) )
505     my_top_row = item->y - menu->arows + 1;
506   
507   _nc_New_TopRow_and_CurrentItem( menu, my_top_row, item );
508   
509   RETURN(result);
510 }
511
512 /* m_driver.c ends here */