1/****************************************************************************
2 * Copyright 2020 Thomas E. Dickey                                          *
3 * Copyright 1998-2012,2014 Free Software Foundation, Inc.                  *
4 *                                                                          *
5 * Permission is hereby granted, free of charge, to any person obtaining a  *
6 * copy of this software and associated documentation files (the            *
7 * "Software"), to deal in the Software without restriction, including      *
8 * without limitation the rights to use, copy, modify, merge, publish,      *
9 * distribute, distribute with modifications, sublicense, and/or sell       *
10 * copies of the Software, and to permit persons to whom the Software is    *
11 * furnished to do so, subject to the following conditions:                 *
12 *                                                                          *
13 * The above copyright notice and this permission notice shall be included  *
14 * in all copies or substantial portions of the Software.                   *
15 *                                                                          *
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19 * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23 *                                                                          *
24 * Except as contained in this notice, the name(s) of the above copyright   *
25 * holders shall not be used in advertising or otherwise to promote the     *
26 * sale, use or other dealings in this Software without prior written       *
27 * authorization.                                                           *
28 ****************************************************************************/
29
30/****************************************************************************
31 *   Author:  Juergen Pfeifer, 1995,1997                                    *
32 ****************************************************************************/
33
34/***************************************************************************
35* Module m_global                                                          *
36* Globally used internal routines and the default menu and item structures *
37***************************************************************************/
38
39#include "menu.priv.h"
40
41MODULE_ID("$Id: m_global.c,v 1.30 2020/02/02 23:34:34 tom Exp $")
42
43static char mark[] = "-";
44/* *INDENT-OFF* */
45NCURSES_EXPORT_VAR(MENU) _nc_Default_Menu = {
46  16,				  /* Nr. of chars high */
47  1,				  /* Nr. of chars wide */
48  16,				  /* Nr. of items high */
49  1,			          /* Nr. of items wide */
50  16,				  /* Nr. of formatted items high */
51  1,				  /* Nr. of formatted items wide */
52  16,				  /* Nr. of items high (actual) */
53  0,				  /* length of widest name */
54  0,				  /* length of widest description */
55  1,				  /* length of mark */
56  1,				  /* length of one item */
57  1,                              /* Spacing for descriptor */
58  1,                              /* Spacing for columns */
59  1,                              /* Spacing for rows */
60  (char *)0,			  /* buffer used to store match chars */
61  0,				  /* Index into pattern buffer */
62  (WINDOW *)0,			  /* Window containing entire menu */
63  (WINDOW *)0,			  /* Portion of menu displayed */
64  (WINDOW *)0,			  /* User's window */
65  (WINDOW *)0,			  /* User's subwindow */
66  (ITEM **)0,			  /* List of items */
67  0,				  /* Total Nr. of items in menu */
68  (ITEM *)0,			  /* Current item */
69  0,				  /* Top row of menu */
70  (chtype)A_REVERSE,		  /* Attribute for selection */
71  (chtype)A_NORMAL,		  /* Attribute for nonselection */
72  (chtype)A_UNDERLINE,		  /* Attribute for inactive */
73  ' ',  			  /* Pad character */
74  (Menu_Hook)0,			  /* Menu init */
75  (Menu_Hook)0,			  /* Menu term */
76  (Menu_Hook)0,			  /* Item init */
77  (Menu_Hook)0,			  /* Item term */
78  (void *)0,			  /* userptr */
79  mark,				  /* mark */
80  ALL_MENU_OPTS,                  /* options */
81  0			          /* status */
82};
83
84NCURSES_EXPORT_VAR(ITEM) _nc_Default_Item = {
85  { (char *)0, 0 },		  /* name */
86  { (char *)0, 0 },		  /* description */
87  (MENU *)0,		          /* Pointer to parent menu */
88  (char *)0,			  /* Userpointer */
89  ALL_ITEM_OPTS,		  /* options */
90  0,				  /* Item Nr. */
91  0,				  /* y */
92  0,				  /* x */
93  FALSE,			  /* value */
94  (ITEM *)0,		          /* left */
95  (ITEM *)0,		          /* right */
96  (ITEM *)0,		          /* up */
97  (ITEM *)0		          /* down */
98  };
99/* *INDENT-ON* */
100
101/*---------------------------------------------------------------------------
102|   Facility      :  libnmenu
103|   Function      :  static void ComputeMaximum_NameDesc_Lenths(MENU *menu)
104|
105|   Description   :  Calculates the maximum name and description lengths
106|                    of the items connected to the menu
107|
108|   Return Values :  -
109+--------------------------------------------------------------------------*/
110NCURSES_INLINE static void
111ComputeMaximum_NameDesc_Lengths(MENU * menu)
112{
113  unsigned MaximumNameLength = 0;
114  unsigned MaximumDescriptionLength = 0;
115  ITEM **items;
116  unsigned check;
117
118  assert(menu && menu->items);
119  for (items = menu->items; *items; items++)
120    {
121      check = (unsigned)_nc_Calculate_Text_Width(&((*items)->name));
122      if (check > MaximumNameLength)
123	MaximumNameLength = check;
124
125      check = (unsigned)_nc_Calculate_Text_Width(&((*items)->description));
126      if (check > MaximumDescriptionLength)
127	MaximumDescriptionLength = check;
128    }
129
130  menu->namelen = (short)MaximumNameLength;
131  menu->desclen = (short)MaximumDescriptionLength;
132  T(("ComputeMaximum_NameDesc_Lengths %d,%d", menu->namelen, menu->desclen));
133}
134
135/*---------------------------------------------------------------------------
136|   Facility      :  libnmenu
137|   Function      :  static void ResetConnectionInfo(MENU *, ITEM **)
138|
139|   Description   :  Reset all information in the menu and the items in
140|                    the item array that indicates a connection
141|
142|   Return Values :  -
143+--------------------------------------------------------------------------*/
144NCURSES_INLINE static void
145ResetConnectionInfo(MENU * menu, ITEM ** items)
146{
147  ITEM **item;
148
149  assert(menu && items);
150  for (item = items; *item; item++)
151    {
152      (*item)->index = 0;
153      (*item)->imenu = (MENU *) 0;
154    }
155  if (menu->pattern)
156    free(menu->pattern);
157  menu->pattern = (char *)0;
158  menu->pindex = 0;
159  menu->items = (ITEM **) 0;
160  menu->nitems = 0;
161}
162
163/*---------------------------------------------------------------------------
164|   Facility      :  libnmenu
165|   Function      :  bool _nc_Connect_Items(MENU *menu, ITEM **items)
166|
167|   Description   :  Connect the items in the item array to the menu.
168|                    Decorate all the items with a number and a backward
169|                    pointer to the menu.
170|
171|   Return Values :  TRUE       - successful connection
172|                    FALSE      - connection failed
173+--------------------------------------------------------------------------*/
174NCURSES_EXPORT(bool)
175_nc_Connect_Items(MENU * menu, ITEM ** items)
176{
177  ITEM **item;
178  unsigned int ItemCount = 0;
179
180  if (menu && items)
181    {
182      for (item = items; *item; item++)
183	{
184	  if ((*item)->imenu)
185	    {
186	      /* if a item is already connected, reject connection */
187	      break;
188	    }
189	}
190      if (!(*item))
191	/* we reached the end, so there was no connected item */
192	{
193	  for (item = items; *item; item++)
194	    {
195	      if (menu->opt & O_ONEVALUE)
196		{
197		  (*item)->value = FALSE;
198		}
199	      (*item)->index = (short)ItemCount++;
200	      (*item)->imenu = menu;
201	    }
202	}
203    }
204  else
205    return (FALSE);
206
207  if (ItemCount != 0)
208    {
209      menu->items = items;
210      menu->nitems = (short)ItemCount;
211      ComputeMaximum_NameDesc_Lengths(menu);
212      if ((menu->pattern = typeMalloc(char, (unsigned)(1 + menu->namelen))))
213	{
214	  Reset_Pattern(menu);
215	  set_menu_format(menu, menu->frows, menu->fcols);
216	  menu->curitem = *items;
217	  menu->toprow = 0;
218	  return (TRUE);
219	}
220    }
221
222  /* If we fall through to this point, we have to reset all items connection
223     and inform about a reject connection */
224  ResetConnectionInfo(menu, items);
225  return (FALSE);
226}
227
228/*---------------------------------------------------------------------------
229|   Facility      :  libnmenu
230|   Function      :  void _nc_Disconnect_Items(MENU *menu)
231|
232|   Description   :  Disconnect the menus item array from the menu
233|
234|   Return Values :  -
235+--------------------------------------------------------------------------*/
236NCURSES_EXPORT(void)
237_nc_Disconnect_Items(MENU * menu)
238{
239  if (menu && menu->items)
240    ResetConnectionInfo(menu, menu->items);
241}
242
243/*---------------------------------------------------------------------------
244|   Facility      :  libnmenu
245|   Function      :  int _nc_Calculate_Text_Width(const TEXT * item)
246|
247|   Description   :  Calculate the number of columns for a TEXT.
248|
249|   Return Values :  the width
250+--------------------------------------------------------------------------*/
251NCURSES_EXPORT(int)
252_nc_Calculate_Text_Width(const TEXT * item /*FIXME: limit length */ )
253{
254#if USE_WIDEC_SUPPORT
255  int result = item->length;
256
257  T((T_CALLED("_nc_menu_text_width(%p)"), (const void *)item));
258  if (result != 0 && item->str != 0)
259    {
260      int count = (int)mbstowcs(0, item->str, 0);
261      wchar_t *temp = 0;
262
263      if (count > 0
264	  && (temp = typeMalloc(wchar_t, 2 + count)) != 0)
265	{
266	  int n;
267
268	  result = 0;
269	  mbstowcs(temp, item->str, (unsigned)count);
270	  for (n = 0; n < count; ++n)
271	    {
272	      int test = wcwidth(temp[n]);
273
274	      if (test <= 0)
275		test = 1;
276	      result += test;
277	    }
278	  free(temp);
279	}
280    }
281  returnCode(result);
282#else
283  return item->length;
284#endif
285}
286
287/*
288 * Calculate the actual width of a menu entry for wide-characters.
289 */
290#if USE_WIDEC_SUPPORT
291static int
292calculate_actual_width(MENU * menu, bool name)
293{
294  int width = 0;
295  int check = 0;
296  ITEM **items;
297
298  assert(menu && menu->items);
299
300  if (menu->items != 0)
301    {
302      for (items = menu->items; *items; items++)
303	{
304	  if (name)
305	    {
306	      check = _nc_Calculate_Text_Width(&((*items)->name));
307	    }
308	  else
309	    {
310	      check = _nc_Calculate_Text_Width(&((*items)->description));
311	    }
312	  if (check > width)
313	    width = check;
314	}
315    }
316  else
317    {
318      width = (name ? menu->namelen : menu->desclen);
319    }
320
321  T(("calculate_actual_width %s = %d/%d",
322     name ? "name" : "desc",
323     width,
324     name ? menu->namelen : menu->desclen));
325  return width;
326}
327#else
328#define calculate_actual_width(menu, name) (name ? menu->namelen : menu->desclen)
329#endif
330
331/*---------------------------------------------------------------------------
332|   Facility      :  libnmenu
333|   Function      :  void _nc_Calculate_Item_Length_and_Width(MENU *menu)
334|
335|   Description   :  Calculate the length of an item and the width of the
336|                    whole menu.
337|
338|   Return Values :  -
339+--------------------------------------------------------------------------*/
340NCURSES_EXPORT(void)
341_nc_Calculate_Item_Length_and_Width(MENU * menu)
342{
343  int l;
344
345  assert(menu);
346
347  menu->height = (short)(1 + menu->spc_rows * (menu->arows - 1));
348
349  l = calculate_actual_width(menu, TRUE);
350  l += menu->marklen;
351
352  if ((menu->opt & O_SHOWDESC) && (menu->desclen > 0))
353    {
354      l += calculate_actual_width(menu, FALSE);
355      l += menu->spc_desc;
356    }
357
358  menu->itemlen = (short)l;
359  l *= menu->cols;
360  l += (menu->cols - 1) * menu->spc_cols;	/* for the padding between the columns */
361  menu->width = (short)l;
362
363  T(("_nc_CalculateItem_Length_and_Width columns %d, item %d, width %d",
364     menu->cols,
365     menu->itemlen,
366     menu->width));
367}
368
369/*---------------------------------------------------------------------------
370|   Facility      :  libnmenu
371|   Function      :  void _nc_Link_Item(MENU *menu)
372|
373|   Description   :  Statically calculate for every item its four neighbors.
374|                    This depends on the orientation of the menu. This
375|                    static approach simplifies navigation in the menu a lot.
376|
377|   Return Values :  -
378+--------------------------------------------------------------------------*/
379NCURSES_EXPORT(void)
380_nc_Link_Items(MENU * menu)
381{
382  if (menu && menu->items && *(menu->items))
383    {
384      int i, j;
385      ITEM *item;
386      int Number_Of_Items = menu->nitems;
387      int col = 0, row = 0;
388      int Last_in_Row;
389      int Last_in_Column;
390      bool cycle = (menu->opt & O_NONCYCLIC) ? FALSE : TRUE;
391
392      ClrStatus(menu, _LINK_NEEDED);
393
394      if (menu->opt & O_ROWMAJOR)
395	{
396	  int Number_Of_Columns = menu->cols;
397
398	  for (i = 0; i < Number_Of_Items; i++)
399	    {
400	      item = menu->items[i];
401
402	      Last_in_Row = row * Number_Of_Columns + (Number_Of_Columns - 1);
403
404	      item->left = (col) ?
405	      /* if we are not in the leftmost column, we can use the
406	         predecessor in the items array */
407		menu->items[i - 1] :
408		(cycle ? menu->items[(Last_in_Row >= Number_Of_Items) ?
409				     Number_Of_Items - 1 :
410				     Last_in_Row] :
411		 (ITEM *) 0);
412
413	      item->right = ((col < (Number_Of_Columns - 1)) &&
414			     ((i + 1) < Number_Of_Items)
415		)?
416		menu->items[i + 1] :
417		(cycle ? menu->items[row * Number_Of_Columns] :
418		 (ITEM *) 0
419		);
420
421	      Last_in_Column = (menu->rows - 1) * Number_Of_Columns + col;
422
423	      item->up = (row) ? menu->items[i - Number_Of_Columns] :
424		(cycle ? menu->items[(Last_in_Column >= Number_Of_Items) ?
425				     Number_Of_Items - 1 :
426				     Last_in_Column] :
427		 (ITEM *) 0);
428
429	      item->down = ((i + Number_Of_Columns) < Number_Of_Items)
430		?
431		menu->items[i + Number_Of_Columns] :
432		(cycle ? menu->items[(row + 1) < menu->rows ?
433				     Number_Of_Items - 1 : col] :
434		 (ITEM *) 0);
435	      item->x = (short)col;
436	      item->y = (short)row;
437	      if (++col == Number_Of_Columns)
438		{
439		  row++;
440		  col = 0;
441		}
442	    }
443	}
444      else
445	{
446	  int Number_Of_Rows = menu->rows;
447
448	  for (j = 0; j < Number_Of_Items; j++)
449	    {
450	      item = menu->items[i = (col * Number_Of_Rows + row)];
451
452	      Last_in_Column = (menu->cols - 1) * Number_Of_Rows + row;
453
454	      item->left = (col) ?
455		menu->items[i - Number_Of_Rows] :
456		(cycle ? (Last_in_Column >= Number_Of_Items) ?
457		 menu->items[Last_in_Column - Number_Of_Rows] :
458		 menu->items[Last_in_Column] :
459		 (ITEM *) 0);
460
461	      item->right = ((i + Number_Of_Rows) < Number_Of_Items)
462		?
463		menu->items[i + Number_Of_Rows] :
464		(cycle ? menu->items[row] : (ITEM *) 0);
465
466	      Last_in_Row = col * Number_Of_Rows + (Number_Of_Rows - 1);
467
468	      item->up = (row) ?
469		menu->items[i - 1] :
470		(cycle ?
471		 menu->items[(Last_in_Row >= Number_Of_Items) ?
472			     Number_Of_Items - 1 :
473			     Last_in_Row] :
474		 (ITEM *) 0);
475
476	      item->down = (row < (Number_Of_Rows - 1))
477		?
478		(menu->items[((i + 1) < Number_Of_Items) ?
479			     i + 1 :
480			     (col - 1) * Number_Of_Rows + row + 1]) :
481		(cycle ?
482		 menu->items[col * Number_Of_Rows] :
483		 (ITEM *) 0
484		);
485
486	      item->x = (short)col;
487	      item->y = (short)row;
488	      if ((++row) == Number_Of_Rows)
489		{
490		  col++;
491		  row = 0;
492		}
493	    }
494	}
495    }
496}
497
498/*---------------------------------------------------------------------------
499|   Facility      :  libnmenu
500|   Function      :  void _nc_Show_Menu(const MENU* menu)
501|
502|   Description   :  Update the window that is associated with the menu
503|
504|   Return Values :  -
505+--------------------------------------------------------------------------*/
506NCURSES_EXPORT(void)
507_nc_Show_Menu(const MENU * menu)
508{
509  WINDOW *win;
510  int maxy, maxx;
511
512  assert(menu);
513  if ((menu->status & _POSTED) && !(menu->status & _IN_DRIVER))
514    {
515      /* adjust the internal subwindow to start on the current top */
516      assert(menu->sub);
517      mvderwin(menu->sub, menu->spc_rows * menu->toprow, 0);
518      win = Get_Menu_Window(menu);
519
520      maxy = getmaxy(win);
521      maxx = getmaxx(win);
522
523      if (menu->height < maxy)
524	maxy = menu->height;
525      if (menu->width < maxx)
526	maxx = menu->width;
527
528      copywin(menu->sub, win, 0, 0, 0, 0, maxy - 1, maxx - 1, 0);
529      pos_menu_cursor(menu);
530    }
531}
532
533/*---------------------------------------------------------------------------
534|   Facility      :  libnmenu
535|   Function      :  void _nc_New_TopRow_and_CurrentItem(
536|                            MENU *menu,
537|                            int new_toprow,
538|                            ITEM *new_current_item)
539|
540|   Description   :  Redisplay the menu so that the given row becomes the
541|                    top row and the given item becomes the new current
542|                    item.
543|
544|   Return Values :  -
545+--------------------------------------------------------------------------*/
546NCURSES_EXPORT(void)
547_nc_New_TopRow_and_CurrentItem(
548				MENU * menu,
549				int new_toprow,
550				ITEM * new_current_item)
551{
552  ITEM *cur_item;
553  bool mterm_called = FALSE;
554  bool iterm_called = FALSE;
555
556  assert(menu);
557  if (menu->status & _POSTED)
558    {
559      if (new_current_item != menu->curitem)
560	{
561	  Call_Hook(menu, itemterm);
562	  iterm_called = TRUE;
563	}
564      if (new_toprow != menu->toprow)
565	{
566	  Call_Hook(menu, menuterm);
567	  mterm_called = TRUE;
568	}
569
570      cur_item = menu->curitem;
571      assert(cur_item);
572      menu->toprow = (short)(((menu->rows - menu->frows) >= 0)
573			     ? min(menu->rows - menu->frows, new_toprow)
574			     : 0);
575      menu->curitem = new_current_item;
576
577      if (mterm_called)
578	{
579	  Call_Hook(menu, menuinit);
580	}
581      if (iterm_called)
582	{
583	  /* this means, move from the old current_item to the new one... */
584	  Move_To_Current_Item(menu, cur_item);
585	  Call_Hook(menu, iteminit);
586	}
587      if (mterm_called || iterm_called)
588	{
589	  _nc_Show_Menu(menu);
590	}
591      else
592	pos_menu_cursor(menu);
593    }
594  else
595    {				/* if we are not posted, this is quite simple */
596      menu->toprow = (short)(((menu->rows - menu->frows) >= 0)
597			     ? min(menu->rows - menu->frows, new_toprow)
598			     : 0);
599      menu->curitem = new_current_item;
600    }
601}
602
603/* m_global.c ends here */
604