1 /*        $NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos Exp $        */
2 
3 /*-
4  * Copyright (c) 1998-1999 Brett Lymn (blymn@baea.com.au, brett_lymn@yahoo.com.au)
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. The name of the author may not be used to endorse or promote products
13  *    derived from this software without specific prior written permission
14  *
15  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  *
26  *
27  */
28 
29 #include <sys/cdefs.h>
30 __RCSID("$NetBSD: internals.c,v 1.17 2013/10/18 19:53:59 christos Exp $");
31 
32 #include <menu.h>
33 #include <ctype.h>
34 #include <stdlib.h>
35 #include <string.h>
36 #include "internals.h"
37 
38 /* internal function prototypes */
39 static void
40 _menui_calc_neighbours(MENU *menu, int item_no);
41 static void _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item);
42 
43   /*
44    * Link all the menu items together to speed up navigation.  We need
45    * to calculate the widest item entry, then work out how many columns
46    * of items the window will accommodate and then how many rows there will
47    * be.  Once the layout is determined the neighbours of each item is
48    * calculated and the item structures updated.
49    */
50 int
_menui_stitch_items(MENU * menu)51 _menui_stitch_items(MENU *menu)
52 {
53           int i, row_major;
54 
55           row_major = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
56 
57           if (menu->posted == 1)
58                     return E_POSTED;
59           if (menu->items == NULL)
60                     return E_BAD_ARGUMENT;
61 
62           menu->item_rows = menu->item_count / menu->cols;
63           menu->item_cols = menu->cols;
64           if (menu->item_count > (menu->item_rows * menu->item_cols))
65                     menu->item_rows += 1;
66 
67           _menui_max_item_size(menu);
68 
69           for (i = 0; i < menu->item_count; i++) {
70                       /* fill in the row and column value of the item */
71                     if (row_major) {
72                               menu->items[i]->row = i / menu->item_cols;
73                               menu->items[i]->col = i % menu->item_cols;
74                     } else {
75                               menu->items[i]->row = i % menu->item_rows;
76                               menu->items[i]->col = i / menu->item_rows;
77                     }
78 
79                     _menui_calc_neighbours(menu, i);
80           }
81 
82           return E_OK;
83 }
84 
85   /*
86    * Calculate the neighbours for an item in menu.
87    */
88 static void
_menui_calc_neighbours(MENU * menu,int item_no)89 _menui_calc_neighbours(MENU *menu, int item_no)
90 {
91           int neighbour, cycle, row_major, edge;
92           ITEM *item;
93 
94           row_major = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
95           cycle = ((menu->opts & O_NONCYCLIC) != O_NONCYCLIC);
96           item = menu->items[item_no];
97 
98           if (menu->item_rows < 2) {
99                     if (cycle) {
100                               item->up = item;
101                               item->down = item;
102                     } else {
103                               item->up = NULL;
104                               item->down = NULL;
105                     }
106           } else {
107 
108                     /* up */
109                     if (menu->item_cols < 2) {
110                               if (item_no == 0) {
111                                         if (cycle)
112                                                   item->up =
113                                                       menu->items[menu->item_count - 1];
114                                         else
115                                                   item->up = NULL;
116                               } else
117                                         item->up = menu->items[item_no - 1];
118                     } else {
119                               edge = 0;
120                               if (row_major) {
121                                         if (item->row == 0) {
122                                                   neighbour =
123                                                   (menu->item_rows - 1) * menu->item_cols
124                                                             + item->col;
125                                                   if (neighbour >= menu->item_count)
126                                                             neighbour -= menu->item_cols;
127                                                   edge = 1;
128                                         } else
129                                                   neighbour = item_no - menu->item_cols;
130                               } else {
131                                         if (item->row == 0) {
132                                                   neighbour = menu->item_rows * item->col
133                                                             + menu->item_rows - 1;
134                                                   if (neighbour >= menu->item_count)
135                                                             neighbour = menu->item_count - 1;
136                                                   edge = 1;
137                                         } else
138                                                   neighbour = item_no - 1;
139                               }
140 
141 
142                               item->up = menu->items[neighbour];
143                               if ((!cycle) && (edge == 1))
144                                         item->up = NULL;
145                     }
146 
147                     /* Down */
148                     if (menu->item_cols < 2) {
149                               if (item_no == (menu->item_count - 1)) {
150                                         if (cycle)
151                                                   item->down = menu->items[0];
152                                         else
153                                                   item->down = NULL;
154                               } else
155                                         item->down = menu->items[item_no + 1];
156                     } else {
157                               edge = 0;
158                               if (row_major) {
159                                         if (item->row == menu->item_rows - 1) {
160                                                   neighbour = item->col;
161                                                   edge = 1;
162                                         } else {
163                                                   neighbour = item_no + menu->item_cols;
164                                                   if (neighbour >= menu->item_count) {
165                                                             neighbour = item->col;
166                                                             edge = 1;
167                                                   }
168                                         }
169                               } else {
170                                         if (item->row == menu->item_rows - 1) {
171                                                   neighbour = item->col * menu->item_rows;
172                                                   edge = 1;
173                                         } else {
174                                                   neighbour = item_no + 1;
175                                                   if (neighbour >= menu->item_count) {
176                                                             neighbour = item->col
177                                                                 * menu->item_rows;
178                                                             edge = 1;
179                                                   }
180                                         }
181                               }
182 
183                               item->down = menu->items[neighbour];
184                               if ((!cycle) && (edge == 1))
185                                         item->down = NULL;
186                     }
187           }
188 
189           if (menu->item_cols < 2) {
190                     if (cycle) {
191                               item->left = item;
192                               item->right = item;
193                     } else {
194                               item->left = NULL;
195                               item->right = NULL;
196                     }
197           } else {
198                     /* left */
199                     if (menu->item_rows < 2) {
200                               if (item_no == 0) {
201                                         if (cycle)
202                                                   item->left =
203                                                       menu->items[menu->item_count - 1];
204                                         else
205                                                   item->left = NULL;
206                               } else
207                                         item->left = menu->items[item_no - 1];
208                     } else {
209                               edge = 0;
210                               if (row_major) {
211                                         if (item->col == 0) {
212                                                   neighbour = item_no + menu->cols - 1;
213                                                   if (neighbour >= menu->item_count)
214                                                             neighbour = menu->item_count - 1;
215                                                   edge = 1;
216                                         } else
217                                                   neighbour = item_no - 1;
218                               } else {
219                                         if (item->col == 0) {
220                                                   neighbour = menu->item_rows
221                                                       * (menu->item_cols - 1) + item->row;
222                                                   if (neighbour >= menu->item_count)
223                                                             neighbour -= menu->item_rows;
224                                                   edge = 1;
225                                         } else
226                                                   neighbour = item_no - menu->item_rows;
227                               }
228 
229                               item->left = menu->items[neighbour];
230                               if ((!cycle) && (edge == 1))
231                                         item->left = NULL;
232                     }
233 
234                     /* right */
235                     if (menu->item_rows < 2) {
236                               if (item_no == menu->item_count - 1) {
237                                         if (cycle)
238                                                   item->right = menu->items[0];
239                                         else
240                                                   item->right = NULL;
241                               } else
242                                         item->right = menu->items[item_no + 1];
243                     } else {
244                               edge = 0;
245                               if (row_major) {
246                                         if (item->col == menu->item_cols - 1) {
247                                                   neighbour = item_no - menu->item_cols
248                                                       + 1;
249                                                   edge = 1;
250                                         } else if (item_no == menu->item_count - 1) {
251                                                   neighbour = item->row * menu->item_cols;
252                                                   edge = 1;
253                                         } else
254                                                   neighbour = item_no + 1;
255                               } else {
256                                         if (item->col == menu->item_cols - 1) {
257                                                   neighbour = item->row;
258                                                   edge = 1;
259                                         } else {
260                                                   neighbour = item_no + menu->item_rows;
261                                                   if (neighbour >= menu->item_count) {
262                                                             neighbour = item->row;
263                                                             edge = 1;
264                                                   }
265                                         }
266                               }
267 
268                               item->right = menu->items[neighbour];
269                               if ((!cycle) && (edge == 1))
270                                         item->right = NULL;
271                     }
272           }
273 }
274 
275 /*
276  * Goto the item pointed to by item and adjust the menu structure
277  * accordingly.  Call the term and init functions if required.
278  */
279 int
_menui_goto_item(MENU * menu,ITEM * item,int new_top_row)280 _menui_goto_item(MENU *menu, ITEM *item, int new_top_row)
281 {
282           int old_top_row = menu->top_row, old_cur_item = menu->cur_item;
283 
284             /* If we get a null then the menu is not cyclic so deny request */
285           if (item == NULL)
286                     return E_REQUEST_DENIED;
287 
288           menu->in_init = 1;
289           if (menu->top_row != new_top_row) {
290                     if ((menu->posted == 1) && (menu->menu_term != NULL))
291                               menu->menu_term(menu);
292                     menu->top_row = new_top_row;
293 
294                     if ((menu->posted == 1) && (menu->menu_init != NULL))
295                               menu->menu_init(menu);
296           }
297 
298             /* this looks like wasted effort but it can happen.... */
299           if (menu->cur_item != item->index) {
300 
301                     if ((menu->posted == 1) && (menu->item_term != NULL))
302                               menu->item_term(menu);
303 
304                     menu->cur_item = item->index;
305                     menu->cur_row = item->row;
306                     menu->cur_col = item->col;
307 
308                     if (menu->posted == 1)
309                               _menui_redraw_menu(menu, old_top_row, old_cur_item);
310 
311                     if ((menu->posted == 1) && (menu->item_init != NULL))
312                               menu->item_init(menu);
313 
314           }
315 
316           menu->in_init = 0;
317           return E_OK;
318 }
319 
320 /*
321  * Attempt to match items with the pattern buffer in the direction given
322  * by iterating over the menu items.  If a match is found return E_OK
323  * otherwise return E_NO_MATCH
324  */
325 int
_menui_match_items(MENU * menu,int direction,int * item_matched)326 _menui_match_items(MENU *menu, int direction, int *item_matched)
327 {
328           int i, caseless;
329 
330           caseless = ((menu->opts & O_IGNORECASE) == O_IGNORECASE);
331 
332           i = menu->cur_item;
333           if (direction == MATCH_NEXT_FORWARD) {
334                     if (++i >= menu->item_count) i = 0;
335           } else if (direction == MATCH_NEXT_REVERSE) {
336                     if (--i < 0) i = menu->item_count - 1;
337           }
338 
339 
340           do {
341                     if (menu->items[i]->name.length >= menu->plen) {
342                                 /* no chance if pattern is longer */
343                               if (caseless) {
344                                         if (strncasecmp(menu->items[i]->name.string,
345                                                             menu->pattern,
346                                                             (size_t) menu->plen) == 0) {
347                                                   *item_matched = i;
348                                                   menu->match_len = menu->plen;
349                                                   return E_OK;
350                                         }
351                               } else {
352                                         if (strncmp(menu->items[i]->name.string,
353                                                       menu->pattern,
354                                                       (size_t) menu->plen) == 0) {
355                                                   *item_matched = i;
356                                                   menu->match_len = menu->plen;
357                                                   return E_OK;
358                                         }
359                               }
360                     }
361 
362                     if ((direction == MATCH_FORWARD) ||
363                         (direction == MATCH_NEXT_FORWARD)) {
364                               if (++i >= menu->item_count) i = 0;
365                     } else {
366                               if (--i <= 0) i = menu->item_count - 1;
367                     }
368           } while (i != menu->cur_item);
369 
370           menu->match_len = 0; /* match did not succeed - kill the match len. */
371           return E_NO_MATCH;
372 }
373 
374 /*
375  * Attempt to match the pattern buffer against the items.  If c is a
376  * printable character then add it to the pattern buffer prior to
377  * performing the match.  Direction determines the direction of matching.
378  * If the match is successful update the item_matched variable with the
379  * index of the item that matched the pattern.
380  */
381 int
_menui_match_pattern(MENU * menu,int c,int direction,int * item_matched)382 _menui_match_pattern(MENU *menu, int c, int direction, int *item_matched)
383 {
384           if (menu == NULL)
385                     return E_BAD_ARGUMENT;
386           if (menu->items == NULL)
387                     return E_BAD_ARGUMENT;
388           if (*menu->items == NULL)
389                     return E_BAD_ARGUMENT;
390 
391           if (isprint(c)) {
392                       /* add char to buffer - first allocate room for it */
393                     if ((menu->pattern = (char *)
394                          realloc(menu->pattern,
395                                    menu->plen + sizeof(char) +
396                                    ((menu->plen > 0)? 0 : 1)))
397                         == NULL)
398                               return E_SYSTEM_ERROR;
399                     menu->pattern[menu->plen] = c;
400                     menu->pattern[++menu->plen] = '\0';
401 
402                       /* there is no chance of a match if pattern is longer
403                          than all the items */
404                     if (menu->plen >= menu->max_item_width) {
405                               menu->pattern[--menu->plen] = '\0';
406                               return E_NO_MATCH;
407                     }
408 
409                     if (_menui_match_items(menu, direction,
410                                                   item_matched) == E_NO_MATCH) {
411                               menu->pattern[--menu->plen] = '\0';
412                               return E_NO_MATCH;
413                     } else
414                               return E_OK;
415           } else {
416                     if (_menui_match_items(menu, direction,
417                                                   item_matched) == E_OK) {
418                               return E_OK;
419                     } else {
420                               return E_NO_MATCH;
421                     }
422           }
423 }
424 
425 /*
426  * Draw an item in the subwindow complete with appropriate highlighting.
427  */
428 void
_menui_draw_item(MENU * menu,int item)429 _menui_draw_item(MENU *menu, int item)
430 {
431           int j, pad_len, mark_len;
432 
433           mark_len = max(menu->mark.length, menu->unmark.length);
434 
435           wmove(menu->scrwin,
436                 menu->items[item]->row - menu->top_row,
437                 menu->items[item]->col * (menu->col_width + 1));
438 
439           if (menu->cur_item == item)
440                     wattrset(menu->scrwin, menu->fore);
441           if ((menu->items[item]->opts & O_SELECTABLE) != O_SELECTABLE)
442                     wattron(menu->scrwin, menu->grey);
443 
444             /* deal with the menu mark, if  one is set.
445              * We mark the selected items and write blanks for
446              * all others unless the menu unmark string is set in which
447              * case the unmark string is written.
448              */
449           if ((menu->items[item]->selected == 1) ||
450               (((menu->opts & O_ONEVALUE) == O_ONEVALUE) &&
451                     (menu->cur_item == item))) {
452                     if (menu->mark.string != NULL) {
453                               for (j = 0; j < menu->mark.length; j++) {
454                                         waddch(menu->scrwin,
455                                                menu->mark.string[j]);
456                               }
457                     }
458                       /* blank any length difference between mark & unmark */
459                     for (j = menu->mark.length; j < mark_len; j++)
460                               waddch(menu->scrwin, ' ');
461           } else {
462                     if (menu->unmark.string != NULL) {
463                               for (j = 0; j < menu->unmark.length; j++) {
464                                         waddch(menu->scrwin,
465                                                menu->unmark.string[j]);
466                               }
467                     }
468                       /* blank any length difference between mark & unmark */
469                     for (j = menu->unmark.length; j < mark_len; j++)
470                               waddch(menu->scrwin, ' ');
471           }
472 
473             /* add the menu name */
474           for (j=0; j < menu->items[item]->name.length; j++)
475                     waddch(menu->scrwin,
476                            menu->items[item]->name.string[j]);
477 
478           pad_len = menu->col_width - menu->items[item]->name.length
479                     - mark_len - 1;
480           if ((menu->opts & O_SHOWDESC) == O_SHOWDESC) {
481                     pad_len -= menu->items[item]->description.length - 1;
482                     for (j = 0; j < pad_len; j++)
483                               waddch(menu->scrwin, menu->pad);
484                     for (j = 0; j < menu->items[item]->description.length; j++) {
485                               waddch(menu->scrwin,
486                                      menu->items[item]->description.string[j]);
487                     }
488           } else {
489                     for (j = 0; j < pad_len; j++)
490                               waddch(menu->scrwin, ' ');
491           }
492           menu->items[item]->visible = 1;
493 
494             /* kill any special attributes... */
495           wattrset(menu->scrwin, menu->back);
496 
497             /*
498              * Fill in the spacing between items, annoying but it looks
499              * odd if the menu items are inverse because the spacings do not
500              * have the same attributes as the items.
501              */
502           if ((menu->items[item]->col > 0) &&
503               (menu->items[item]->col < (menu->item_cols - 1))) {
504                     wmove(menu->scrwin,
505                           menu->items[item]->row - menu->top_row,
506                           menu->items[item]->col * (menu->col_width + 1) - 1);
507                     waddch(menu->scrwin, ' ');
508           }
509 
510             /* and position the cursor nicely */
511           pos_menu_cursor(menu);
512 }
513 
514 /*
515  * Draw the menu in the subwindow provided.
516  */
517 int
_menui_draw_menu(MENU * menu)518 _menui_draw_menu(MENU *menu)
519 {
520           int rowmajor, i, j, k, row = -1, stride;
521           int incr, cur_row, offset, row_count;
522 
523           rowmajor = ((menu->opts & O_ROWMAJOR) == O_ROWMAJOR);
524 
525           if (rowmajor) {
526                     stride = 1;
527                     incr = menu->item_cols;
528           } else {
529                     stride = menu->item_rows;
530                     incr = 1;
531           }
532           row_count = 0;
533 
534           for (i = 0;  i < menu->item_count; i += incr) {
535                     if (menu->items[i]->row == menu->top_row)
536                               break;
537                     row_count++;
538                     for (j = 0; j < menu->item_cols; j++) {
539                               offset = j * stride + i;
540                               if (offset >= menu->item_count)
541                                         break; /* done */
542                               menu->items[offset]->visible = 0;
543                     }
544           }
545 
546           wmove(menu->scrwin, 0, 0);
547 
548           menu->col_width = getmaxx(menu->scrwin) / menu->cols;
549 
550           for (cur_row = 0; cur_row < menu->rows; cur_row++) {
551                     for (j = 0; j < menu->cols; j++) {
552                               offset = j * stride + i;
553                               if (offset >= menu->item_count) {
554                                  /* no more items to draw, write background blanks */
555                                         wattrset(menu->scrwin, menu->back);
556                                         if (row < 0) {
557                                                   row = menu->items[menu->item_count - 1]->row;
558                                         }
559 
560                                         wmove(menu->scrwin, cur_row,
561                                               j * (menu->col_width + 1));
562                                         for (k = 0; k < menu->col_width; k++)
563                                                   waddch(menu->scrwin, ' ');
564                               } else {
565                                         _menui_draw_item(menu, offset);
566                               }
567                     }
568 
569                     i += incr;
570                     row_count++;
571           }
572 
573           if (row_count < menu->item_rows) {
574                     for (cur_row = row_count;  cur_row < menu->item_rows; cur_row++) {
575                               for (j = 0; j < menu->item_cols; j++) {
576                                         offset = j * stride + i;
577                                         if (offset >= menu->item_count)
578                                                   break; /* done */
579                                         menu->items[offset]->visible = 0;
580                               }
581                               i += incr;
582                     }
583           }
584 
585           return E_OK;
586 }
587 
588 
589 /*
590  * Calculate the widest menu item and stash it in the menu struct.
591  *
592  */
593 void
_menui_max_item_size(MENU * menu)594 _menui_max_item_size(MENU *menu)
595 {
596           int i, with_desc, width;
597 
598           with_desc = ((menu->opts & O_SHOWDESC) == O_SHOWDESC);
599 
600           for (i = 0; i < menu->item_count; i++) {
601                     width = menu->items[i]->name.length
602                               + max(menu->mark.length, menu->unmark.length);
603                     if (with_desc)
604                               width += menu->items[i]->description.length + 1;
605 
606                     menu->max_item_width = max(menu->max_item_width, width);
607           }
608 }
609 
610 
611 /*
612  * Redraw the menu on the screen.  If the current item has changed then
613  * unhighlight the old item and highlight the new one.
614  */
615 static void
_menui_redraw_menu(MENU * menu,int old_top_row,int old_cur_item)616 _menui_redraw_menu(MENU *menu, int old_top_row, int old_cur_item)
617 {
618 
619           if (menu->top_row != old_top_row) {
620                       /* top row changed - redo the whole menu
621                        * XXXX this could be improved if we had wscrl implemented.
622 
623                        * XXXX we could scroll the window and just fill in the
624                        * XXXX changed lines.
625                        */
626                     wclear(menu->scrwin);
627                     _menui_draw_menu(menu);
628           } else {
629                     if (menu->cur_item != old_cur_item) {
630                                 /* redo the old item as a normal one. */
631                               _menui_draw_item(menu, old_cur_item);
632                     }
633                       /* and then redraw the current item */
634                     _menui_draw_item(menu, menu->cur_item);
635           }
636 }
637