ncurses 6.2 - patch 20200411
[ncurses.git] / c++ / cursesm.cc
1 // * this is for making emacs happy: -*-Mode: C++;-*-
2 /****************************************************************************
3  * Copyright 2019,2020 Thomas E. Dickey                                     *
4  * Copyright 1998-2011,2017 Free Software Foundation, Inc.                  *
5  *                                                                          *
6  * Permission is hereby granted, free of charge, to any person obtaining a  *
7  * copy of this software and associated documentation files (the            *
8  * "Software"), to deal in the Software without restriction, including      *
9  * without limitation the rights to use, copy, modify, merge, publish,      *
10  * distribute, distribute with modifications, sublicense, and/or sell       *
11  * copies of the Software, and to permit persons to whom the Software is    *
12  * furnished to do so, subject to the following conditions:                 *
13  *                                                                          *
14  * The above copyright notice and this permission notice shall be included  *
15  * in all copies or substantial portions of the Software.                   *
16  *                                                                          *
17  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
18  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
19  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
20  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
21  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
22  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
23  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
24  *                                                                          *
25  * Except as contained in this notice, the name(s) of the above copyright   *
26  * holders shall not be used in advertising or otherwise to promote the     *
27  * sale, use or other dealings in this Software without prior written       *
28  * authorization.                                                           *
29  ****************************************************************************/
30
31 /****************************************************************************
32  *   Author: Juergen Pfeifer, 1997                                          *
33  ****************************************************************************/
34
35 #include "internal.h"
36 #include "cursesm.h"
37 #include "cursesapp.h"
38
39 MODULE_ID("$Id: cursesm.cc,v 1.26 2020/02/02 23:34:34 tom Exp $")
40
41 NCursesMenuItem::~NCursesMenuItem() THROWS(NCursesException)
42 {
43   if (item)
44     OnError(::free_item(item));
45 }
46
47 bool
48 NCursesMenuItem::action()
49 {
50   return FALSE;
51 }
52
53 NCursesMenuCallbackItem::~NCursesMenuCallbackItem() THROWS(NCursesException)
54 {
55 }
56
57 bool
58 NCursesMenuCallbackItem::action()
59 {
60   if (p_fct)
61     return p_fct (*this);
62   else
63     return FALSE;
64 }
65
66 /* Internal hook functions. They will route the hook
67  * calls to virtual methods of the NCursesMenu class,
68  * so in C++ providing a hook is done simply by
69  * implementing a virtual method in a derived class
70  */
71 void
72 _nc_xx_mnu_init(MENU *m)
73 {
74   NCursesMenu::getHook(m)->On_Menu_Init();
75 }
76
77 void
78 _nc_xx_mnu_term(MENU *m)
79 {
80   NCursesMenu::getHook(m)->On_Menu_Termination();
81 }
82
83 void
84 _nc_xx_itm_init(MENU *m)
85 {
86   NCursesMenu* M = NCursesMenu::getHook(m);
87   M->On_Item_Init (*(M->current_item ()));
88 }
89
90 void
91 _nc_xx_itm_term(MENU *m)
92 {
93   NCursesMenu* M = NCursesMenu::getHook(m);
94   M->On_Item_Termination (*(M->current_item ()));
95 }
96
97 /* Construct an ITEM* array from an array of NCursesMenuItem
98  * objects.
99  */
100 ITEM**
101 NCursesMenu::mapItems(NCursesMenuItem* nitems[])
102 {
103   int itemCount = 0,lcv;
104
105   for (lcv=0; nitems[lcv]->item; ++lcv)
106     ++itemCount;
107
108   ITEM** itemArray = new ITEM*[itemCount + 1];
109
110   for (lcv=0;nitems[lcv]->item;++lcv) {
111     itemArray[lcv] = nitems[lcv]->item;
112   }
113   itemArray[lcv] = NULL;
114
115   my_items = nitems;
116
117   if (menu)
118     delete[] ::menu_items(menu);
119   return itemArray;
120 }
121
122 void
123 NCursesMenu::InitMenu(NCursesMenuItem* nitems[],
124                       bool with_frame,
125                       bool autoDelete_Items)
126 {
127   int mrows, mcols;
128
129   keypad(TRUE);
130   meta(TRUE);
131
132   b_framed = with_frame;
133   b_autoDelete = autoDelete_Items;
134
135   menu = static_cast<MENU*>(0);
136   menu = ::new_menu(mapItems(nitems));
137   if (!menu)
138     OnError (E_SYSTEM_ERROR);
139
140   UserHook* hook = new UserHook;
141   hook->m_user   = NULL;
142   hook->m_back   = this;
143   hook->m_owner  = menu;
144   ::set_menu_userptr(menu, static_cast<void*>(hook));
145
146   ::set_menu_init (menu, _nc_xx_mnu_init);
147   ::set_menu_term (menu, _nc_xx_mnu_term);
148   ::set_item_init (menu, _nc_xx_itm_init);
149   ::set_item_term (menu, _nc_xx_itm_term);
150
151   scale(mrows, mcols);
152   ::set_menu_win(menu, w);
153
154   if (with_frame) {
155     if ((mrows > height()-2) || (mcols > width()-2))
156       OnError(E_NO_ROOM);
157     sub = new NCursesWindow(*this,mrows,mcols,1,1,'r');
158     ::set_menu_sub(menu, sub->w);
159     b_sub_owner = TRUE;
160   }
161   else {
162     sub = static_cast<NCursesWindow*>(0);
163     b_sub_owner = FALSE;
164   }
165   setDefaultAttributes();
166 }
167
168 void
169 NCursesMenu::setDefaultAttributes()
170 {
171   NCursesApplication* S = NCursesApplication::getApplication();
172   if (S) {
173     ::set_menu_fore(menu, S->foregrounds());
174     ::set_menu_back(menu, S->backgrounds());
175     ::set_menu_grey(menu, S->inactives());
176   }
177 }
178
179 NCursesMenu::~NCursesMenu() THROWS(NCursesException)
180 {
181   UserHook* hook = reinterpret_cast<UserHook*>(::menu_userptr(menu));
182   delete hook;
183   if (b_sub_owner) {
184     ::set_menu_sub(menu, static_cast<WINDOW *>(0));
185     delete sub;
186   }
187   if (menu) {
188     ITEM** itms = ::menu_items(menu);
189     int cnt = count();
190
191     OnError(::set_menu_items(menu, static_cast<ITEM**>(0)));
192
193     if (b_autoDelete) {
194       if (cnt>0) {
195         for (int i=0; i <= cnt; i++)
196           delete my_items[i];
197       }
198       delete[] my_items;
199     }
200
201     ::free_menu(menu);
202     // It's essential to do this after free_menu()
203     delete[] itms;
204   }
205 }
206
207 void
208 NCursesMenu::setSubWindow(NCursesWindow& nsub)
209 {
210   if (!isDescendant(nsub))
211     OnError(E_SYSTEM_ERROR);
212   else {
213     if (b_sub_owner)
214       delete sub;
215     sub = &nsub;
216     ::set_menu_sub(menu,sub->w);
217   }
218 }
219
220 bool
221 NCursesMenu::set_pattern (const char *pat)
222 {
223   int res = ::set_menu_pattern (menu, pat);
224   switch(res) {
225   case E_OK:
226     break;
227   case E_NO_MATCH:
228     return FALSE;
229   default:
230     OnError (res);
231   }
232   return TRUE;
233 }
234
235 // call the menu driver and do basic error checking.
236 int
237 NCursesMenu::driver (int c)
238 {
239   int res = ::menu_driver (menu, c);
240   switch (res) {
241   case E_OK:
242   case E_REQUEST_DENIED:
243   case E_NOT_SELECTABLE:
244   case E_UNKNOWN_COMMAND:
245   case E_NO_MATCH:
246     break;
247   default:
248     OnError (res);
249   }
250   return (res);
251 }
252
253 static const int CMD_QUIT   = MAX_COMMAND + 1;
254 static const int CMD_ACTION = MAX_COMMAND + 2;
255 //
256 // -------------------------------------------------------------------------
257 // Provide a default key virtualization. Translate the keyboard
258 // code c into a menu request code.
259 // The default implementation provides a hopefully straightforward
260 // mapping for the most common keystrokes and menu requests.
261 // -------------------------------------------------------------------------
262 int
263 NCursesMenu::virtualize(int c)
264 {
265   switch(c) {
266   case CTRL('X')     : return(CMD_QUIT);              // eXit
267
268   case KEY_DOWN      : return(REQ_DOWN_ITEM);
269   case CTRL('N')     : return(REQ_NEXT_ITEM);         // Next
270   case KEY_UP        : return(REQ_UP_ITEM);
271   case CTRL('P')     : return(REQ_PREV_ITEM);         // Previous
272
273   case CTRL('U')     : return(REQ_SCR_ULINE);         // Up
274   case CTRL('D')     : return(REQ_SCR_DLINE);         // Down
275   case CTRL('F')     : return(REQ_SCR_DPAGE);         // Forward
276   case CTRL('B')     : return(REQ_SCR_UPAGE);         // Backward
277
278   case CTRL('Y')     : return(REQ_CLEAR_PATTERN);
279   case CTRL('H')     : return(REQ_BACK_PATTERN);
280   case CTRL('A')     : return(REQ_NEXT_MATCH);
281   case CTRL('E')     : return(REQ_PREV_MATCH);
282   case CTRL('T')     : return(REQ_TOGGLE_ITEM);
283
284   case CTRL('J')     :
285   case CTRL('M')     : return(CMD_ACTION);
286
287   case KEY_HOME      : return(REQ_FIRST_ITEM);
288   case KEY_LEFT      : return(REQ_LEFT_ITEM);
289   case KEY_RIGHT     : return(REQ_RIGHT_ITEM);
290   case KEY_END       : return(REQ_LAST_ITEM);
291   case KEY_BACKSPACE : return(REQ_BACK_PATTERN);
292   case KEY_NPAGE     : return(REQ_SCR_DPAGE);
293   case KEY_PPAGE     : return(REQ_SCR_UPAGE);
294
295   default:
296     return(c);
297   }
298 }
299
300 NCursesMenuItem*
301 NCursesMenu::operator()(void)
302 {
303   int drvCmnd;
304   int err;
305   int c;
306   bool b_action = FALSE;
307
308   post();
309   show();
310   refresh();
311
312   while (!b_action && ((drvCmnd = virtualize((c=getKey()))) != CMD_QUIT)) {
313
314     switch((err=driver(drvCmnd))) {
315     case E_REQUEST_DENIED:
316       On_Request_Denied(c);
317       break;
318     case E_NOT_SELECTABLE:
319       On_Not_Selectable(c);
320       break;
321     case E_UNKNOWN_COMMAND:
322       if (drvCmnd == CMD_ACTION) {
323         if (options() & O_ONEVALUE) {
324           NCursesMenuItem* itm = current_item();
325           assert(itm != 0);
326           if (itm->options() & O_SELECTABLE)
327             {
328               b_action = itm->action();
329               refresh();
330             }
331           else
332             On_Not_Selectable(c);
333         }
334         else {
335           int n = count();
336           for(int i=0; i<n; i++) {
337             NCursesMenuItem* itm = my_items[i];
338             if (itm->value()) {
339               b_action |= itm->action();
340               refresh();
341             }
342           }
343         }
344       } else
345         On_Unknown_Command(c);
346       break;
347     case E_NO_MATCH:
348       On_No_Match(c);
349       break;
350     case E_OK:
351       break;
352     default:
353       OnError(err);
354     }
355   }
356
357   unpost();
358   hide();
359   refresh();
360   if (options() & O_ONEVALUE)
361     return my_items[::item_index (::current_item (menu))];
362   else
363     return NULL;
364 }
365
366 void
367 NCursesMenu::On_Menu_Init()
368 {
369 }
370
371 void
372 NCursesMenu::On_Menu_Termination()
373 {
374 }
375
376 void
377 NCursesMenu::On_Item_Init(NCursesMenuItem& item)
378 {
379   (void) item;
380 }
381
382 void
383 NCursesMenu::On_Item_Termination(NCursesMenuItem& item)
384 {
385   (void) item;
386 }
387
388 void
389 NCursesMenu::On_Request_Denied(int c) const
390 {
391   (void) c;
392   ::beep();
393 }
394
395 void
396 NCursesMenu::On_Not_Selectable(int c) const
397 {
398   (void) c;
399   ::beep();
400 }
401
402 void
403 NCursesMenu::On_No_Match(int c) const
404 {
405   (void) c;
406   ::beep();
407 }
408
409 void
410 NCursesMenu::On_Unknown_Command(int c) const
411 {
412   (void) c;
413   ::beep();
414 }