1 /*
2     WDG -- list widget
3 
4     Copyright (C) ALoR
5 
6     This program is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10 
11     This program is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15 
16     You should have received a copy of the GNU General Public License
17     along with this program; if not, write to the Free Software
18     Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 
20 */
21 
22 #include <wdg.h>
23 
24 #include <ncurses.h>
25 #include <menu.h>
26 
27 #include <stdarg.h>
28 
29 /* GLOBALS */
30 
31 struct wdg_list_call {
32    int key;
33    void (*callback)(void *);
34    SLIST_ENTRY(wdg_list_call) next;
35 };
36 
37 struct wdg_list_handle {
38    MENU *menu;
39    WINDOW *mwin;
40    WINDOW *win;
41    ITEM *current;
42    ITEM **items;
43    size_t nitems;
44    void (*select_callback)(void *);
45    SLIST_HEAD(, wdg_list_call) callbacks;
46 };
47 
48 /* PROTOS */
49 
50 void wdg_create_list(struct wdg_object *wo);
51 
52 static int wdg_list_destroy(struct wdg_object *wo);
53 static int wdg_list_resize(struct wdg_object *wo);
54 static int wdg_list_redraw(struct wdg_object *wo);
55 static int wdg_list_get_focus(struct wdg_object *wo);
56 static int wdg_list_lost_focus(struct wdg_object *wo);
57 static int wdg_list_get_msg(struct wdg_object *wo, int key, struct wdg_mouse_event *mouse);
58 
59 static void wdg_list_borders(struct wdg_object *wo);
60 
61 static int wdg_list_virtualize(int key);
62 static int wdg_list_driver(struct wdg_object *wo, int key, struct wdg_mouse_event *mouse);
63 static void wdg_list_menu_create(struct wdg_object *wo);
64 static void wdg_list_menu_destroy(struct wdg_object *wo);
65 
66 static int wdg_list_callback(struct wdg_object *wo, int key);
67 
68 /*******************************************/
69 
70 /*
71  * called to create the menu
72  */
wdg_create_list(struct wdg_object * wo)73 void wdg_create_list(struct wdg_object *wo)
74 {
75    WDG_DEBUG_MSG("wdg_create_list");
76 
77    /* set the callbacks */
78    wo->destroy = wdg_list_destroy;
79    wo->resize = wdg_list_resize;
80    wo->redraw = wdg_list_redraw;
81    wo->get_focus = wdg_list_get_focus;
82    wo->lost_focus = wdg_list_lost_focus;
83    wo->get_msg = wdg_list_get_msg;
84 
85    WDG_SAFE_CALLOC(wo->extend, 1, sizeof(struct wdg_list_handle));
86 }
87 
88 /*
89  * called to destroy the menu
90  */
wdg_list_destroy(struct wdg_object * wo)91 static int wdg_list_destroy(struct wdg_object *wo)
92 {
93    WDG_WO_EXT(struct wdg_list_handle, ww);
94    struct wdg_list_call *c;
95    int i = 0;
96 
97    WDG_DEBUG_MSG("wdg_list_destroy");
98 
99    /* erase the window */
100    wdg_list_menu_destroy(wo);
101 
102    wbkgd(ww->win, COLOR_PAIR(wo->screen_color));
103    werase(ww->win);
104    wnoutrefresh(ww->win);
105 
106    /* dealloc the structures */
107    delwin(ww->win);
108 
109    /* all the items */
110    while (ww->items && ww->items[i] != NULL)
111       free_item(ww->items[i++]);
112 
113    WDG_SAFE_FREE(ww->items);
114 
115    /* free the callback list */
116    while (SLIST_FIRST(&ww->callbacks) != NULL) {
117       c = SLIST_FIRST(&ww->callbacks);
118       SLIST_REMOVE_HEAD(&ww->callbacks, next);
119       WDG_SAFE_FREE(c);
120    }
121 
122    WDG_SAFE_FREE(wo->extend);
123 
124    return WDG_E_SUCCESS;
125 }
126 
127 /*
128  * called to resize the menu
129  */
wdg_list_resize(struct wdg_object * wo)130 static int wdg_list_resize(struct wdg_object *wo)
131 {
132    wdg_list_redraw(wo);
133 
134    return WDG_E_SUCCESS;
135 }
136 
137 /*
138  * called to redraw the menu
139  */
wdg_list_redraw(struct wdg_object * wo)140 static int wdg_list_redraw(struct wdg_object *wo)
141 {
142    WDG_WO_EXT(struct wdg_list_handle, ww);
143    size_t c = wdg_get_ncols(wo);
144    size_t l = wdg_get_nlines(wo);
145    size_t x = wdg_get_begin_x(wo);
146    size_t y = wdg_get_begin_y(wo);
147 
148    WDG_DEBUG_MSG("wdg_list_redraw");
149 
150    /* the window already exist */
151    if (ww->win) {
152       /* erase the border */
153       wbkgd(ww->win, COLOR_PAIR(wo->screen_color));
154       werase(ww->win);
155       touchwin(ww->win);
156       wnoutrefresh(ww->win);
157 
158       /* delete the internal menu */
159       wdg_list_menu_destroy(wo);
160 
161       /* resize the window and draw the new border */
162       mvwin(ww->win, y, x);
163       wresize(ww->win, l, c);
164       wdg_list_borders(wo);
165 
166       /* redraw the menu */
167       wdg_list_menu_create(wo);
168 
169    /* the first time we have to allocate the window */
170    } else {
171 
172       /* create the menu window (fixed dimensions) */
173       if ((ww->win = newwin(l, c, y, x)) == NULL)
174          return -WDG_E_FATAL;
175 
176       /* draw the titles */
177       wdg_list_borders(wo);
178 
179       /* draw the menu */
180       wdg_list_menu_create(wo);
181 
182       /* no scrolling for menu */
183       scrollok(ww->win, FALSE);
184 
185    }
186 
187    /* refresh the window */
188    touchwin(ww->win);
189    wnoutrefresh(ww->win);
190    touchwin(ww->mwin);
191    wnoutrefresh(ww->mwin);
192 
193    wo->flags |= WDG_OBJ_VISIBLE;
194 
195    return WDG_E_SUCCESS;
196 }
197 
198 /*
199  * called when the menu gets the focus
200  */
wdg_list_get_focus(struct wdg_object * wo)201 static int wdg_list_get_focus(struct wdg_object *wo)
202 {
203    /* set the flag */
204    wo->flags |= WDG_OBJ_FOCUSED;
205 
206    /* redraw the window */
207    wdg_list_redraw(wo);
208 
209    return WDG_E_SUCCESS;
210 }
211 
212 /*
213  * called when the menu looses the focus
214  */
wdg_list_lost_focus(struct wdg_object * wo)215 static int wdg_list_lost_focus(struct wdg_object *wo)
216 {
217    /* set the flag */
218    wo->flags &= ~WDG_OBJ_FOCUSED;
219 
220    /* redraw the window */
221    wdg_list_redraw(wo);
222 
223    return WDG_E_SUCCESS;
224 }
225 
226 /*
227  * called by the messages dispatcher when the menu is focused
228  */
wdg_list_get_msg(struct wdg_object * wo,int key,struct wdg_mouse_event * mouse)229 static int wdg_list_get_msg(struct wdg_object *wo, int key, struct wdg_mouse_event *mouse)
230 {
231    WDG_WO_EXT(struct wdg_list_handle, ww);
232 
233    /* handle the message */
234    switch (key) {
235 
236       case KEY_MOUSE:
237          /* is the mouse event within our edges ? */
238          if (wenclose(ww->win, mouse->y, mouse->x)) {
239             wdg_set_focus(wo);
240             /* pass the event to the menu */
241             wdg_list_driver(wo, key, mouse);
242          } else
243             return -WDG_E_NOTHANDLED;
244          break;
245 
246       case KEY_DOWN:
247       case KEY_UP:
248       case KEY_NPAGE:
249       case KEY_PPAGE:
250          /* move only if focused */
251          if (wo->flags & WDG_OBJ_FOCUSED) {
252             /* pass the event to the menu */
253             wdg_list_driver(wo, key, mouse);
254          } else
255             return -WDG_E_NOTHANDLED;
256          break;
257 
258       case KEY_RETURN:
259          if (item_userptr(current_item(ww->menu)))
260             WDG_EXECUTE(ww->select_callback, item_userptr(current_item(ww->menu)));
261          break;
262 
263       /* message not handled */
264       default:
265          return wdg_list_callback(wo, key);
266          break;
267    }
268 
269    return WDG_E_SUCCESS;
270 }
271 
272 /*
273  * draw the list borders
274  */
wdg_list_borders(struct wdg_object * wo)275 static void wdg_list_borders(struct wdg_object *wo)
276 {
277    WDG_WO_EXT(struct wdg_list_handle, ww);
278    size_t c = wdg_get_ncols(wo);
279 
280    /* the object was focused */
281    if (wo->flags & WDG_OBJ_FOCUSED) {
282       wattron(ww->win, A_BOLD);
283       wbkgdset(ww->win, COLOR_PAIR(wo->focus_color));
284    } else
285       wbkgdset(ww->win, COLOR_PAIR(wo->border_color));
286 
287    /* draw the borders */
288    box(ww->win, 0, 0);
289 
290    /* set the title color */
291    wbkgdset(ww->win, COLOR_PAIR(wo->title_color));
292 
293    /* there is a title: print it */
294    if (wo->title) {
295       switch (wo->align) {
296          case WDG_ALIGN_LEFT:
297             wmove(ww->win, 0, 3);
298             break;
299          case WDG_ALIGN_CENTER:
300             wmove(ww->win, 0, (c - strlen(wo->title)) / 2);
301             break;
302          case WDG_ALIGN_RIGHT:
303             wmove(ww->win, 0, c - strlen(wo->title) - 3);
304             break;
305       }
306       wprintw(ww->win, wo->title);
307    }
308 
309    /* restore the attribute */
310    if (wo->flags & WDG_OBJ_FOCUSED)
311       wattroff(ww->win, A_BOLD);
312 
313 }
314 
315 /*
316  * set the elements for the list
317  */
wdg_list_set_elements(struct wdg_object * wo,struct wdg_list * list)318 void wdg_list_set_elements(struct wdg_object *wo, struct wdg_list *list)
319 {
320    WDG_WO_EXT(struct wdg_list_handle, ww);
321    size_t i = 0;
322 
323    wdg_list_menu_destroy(wo);
324 
325    /* forget the curren position, we are creating a new menu */
326    ww->current = NULL;
327 
328    /* free any previously alloc'd item */
329    while (ww->items && ww->items[i] != NULL)
330       free_item(ww->items[i++]);
331 
332    WDG_SAFE_FREE(ww->items);
333    i = 0;
334    ww->nitems = 0;
335 
336    /* walk thru the list and set the menu items */
337    while (list[i].desc != NULL) {
338       /* count the items added */
339       ww->nitems++;
340 
341       WDG_SAFE_REALLOC(ww->items, ww->nitems * sizeof(ITEM *));
342 
343       ww->items[i] = new_item(list[i].desc, "");
344       set_item_userptr(ww->items[i], list[i].value);
345       i++;
346    }
347 
348    /* add the null termination to the array */
349    WDG_SAFE_REALLOC(ww->items, (ww->nitems + 1) * sizeof(ITEM *));
350    ww->items[ww->nitems] = NULL;
351 
352    wdg_list_menu_create(wo);
353 }
354 
355 
356 /*
357  * stransform keys into menu commands
358  */
wdg_list_virtualize(int key)359 static int wdg_list_virtualize(int key)
360 {
361    switch (key) {
362       case KEY_NPAGE:
363          return (REQ_SCR_DPAGE);
364       case KEY_PPAGE:
365          return (REQ_SCR_UPAGE);
366       case KEY_DOWN:
367          return (REQ_NEXT_ITEM);
368       case KEY_UP:
369          return (REQ_PREV_ITEM);
370       default:
371          if (key != KEY_MOUSE)
372             beep();
373          return (key);
374    }
375 }
376 
377 /*
378  * sends command to the active menu
379  */
wdg_list_driver(struct wdg_object * wo,int key,struct wdg_mouse_event * mouse)380 static int wdg_list_driver(struct wdg_object *wo, int key, struct wdg_mouse_event *mouse)
381 {
382    WDG_WO_EXT(struct wdg_list_handle, ww);
383    int c;
384 
385    /* variable currently not used */
386    (void) mouse;
387 
388    c = menu_driver(ww->menu, wdg_list_virtualize(key) );
389 
390    /* skip non selectable items */
391    if ( !(item_opts(current_item(ww->menu)) & O_SELECTABLE) )
392       c = menu_driver(ww->menu, wdg_list_virtualize(key) );
393 
394    /* one item has been selected */
395    if (c == E_UNKNOWN_COMMAND) {
396       if (item_userptr(current_item(ww->menu)))
397          WDG_EXECUTE(ww->select_callback, item_userptr(current_item(ww->menu)));
398    }
399 
400    /* tring to go outside edges */
401    if (c == E_REQUEST_DENIED)
402       return -WDG_E_NOTHANDLED;
403 
404    wnoutrefresh(ww->mwin);
405 
406    return WDG_E_SUCCESS;
407 }
408 
409 /*
410  * create the menu
411  */
wdg_list_menu_create(struct wdg_object * wo)412 static void wdg_list_menu_create(struct wdg_object *wo)
413 {
414    WDG_WO_EXT(struct wdg_list_handle, ww);
415    size_t l = wdg_get_nlines(wo);
416    size_t x = wdg_get_begin_x(wo);
417    size_t y = wdg_get_begin_y(wo);
418    int mrows = 0, mcols = 0;
419 
420    /* skip the creation if:
421     *    - already displayed
422     *    - no items are present
423     */
424    if (ww->menu || !ww->items || ww->nitems == 0)
425       return;
426 
427    /* create the menu */
428    ww->menu = new_menu(ww->items);
429 
430    /* set the dimensions */
431    set_menu_format(ww->menu, l - 4, 1);
432    set_menu_spacing(ww->menu, 2, 0, 0);
433 
434    /* get the geometry to make a window */
435    scale_menu(ww->menu, &mrows, &mcols);
436 
437    ww->mwin = newwin(mrows + 1, mcols, y + 2, x + 2);
438    /* set the color */
439    wbkgd(ww->mwin, COLOR_PAIR(wo->window_color));
440    keypad(ww->mwin, TRUE);
441 
442    /* associate with the menu */
443    set_menu_win(ww->menu, ww->mwin);
444 
445    /* the subwin for the menu */
446    set_menu_sub(ww->menu, derwin(ww->mwin, mrows + 1, mcols, 2, 2));
447 
448    /* menu attributes */
449    set_menu_mark(ww->menu, "");
450    set_menu_grey(ww->menu, COLOR_PAIR(wo->window_color));
451    set_menu_back(ww->menu, COLOR_PAIR(wo->window_color));
452    set_menu_fore(ww->menu, COLOR_PAIR(wo->window_color) | A_REVERSE | A_BOLD);
453 
454    /* repristinate the current position */
455    if (ww->current)
456       set_current_item(ww->menu, ww->current);
457 
458    /* display the menu */
459    post_menu(ww->menu);
460 
461    wnoutrefresh(ww->mwin);
462 }
463 
464 /*
465  * destroy the menu
466  */
wdg_list_menu_destroy(struct wdg_object * wo)467 static void wdg_list_menu_destroy(struct wdg_object *wo)
468 {
469    WDG_WO_EXT(struct wdg_list_handle, ww);
470 
471    /* nothing to clear */
472    if (ww->menu == NULL)
473       return;
474 
475    /* remember the current position to be repristinated on menu create */
476    ww->current = current_item(ww->menu);
477 
478    /* hide the menu */
479    unpost_menu(ww->menu);
480 
481    /* erase the menu */
482    wbkgd(ww->mwin, COLOR_PAIR(wo->screen_color));
483    werase(ww->mwin);
484    wnoutrefresh(ww->mwin);
485 
486    /* delete the memory */
487    free_menu(ww->menu);
488 
489    ww->menu = NULL;
490 }
491 
492 /*
493  * set the select callback
494  */
wdg_list_select_callback(wdg_t * wo,void (* callback)(void *))495 void wdg_list_select_callback(wdg_t *wo, void (*callback)(void *))
496 {
497    WDG_WO_EXT(struct wdg_list_handle, ww);
498 
499    ww->select_callback = callback;
500 }
501 
502 /*
503  * add the user callback(s)
504  */
wdg_list_add_callback(wdg_t * wo,int key,void (* callback)(void *))505 void wdg_list_add_callback(wdg_t *wo, int key, void (*callback)(void *))
506 {
507    WDG_WO_EXT(struct wdg_list_handle, ww);
508    struct wdg_list_call *c;
509 
510    WDG_SAFE_CALLOC(c, 1, sizeof(struct wdg_list_call));
511 
512    c->key = key;
513    c->callback = callback;
514 
515    SLIST_INSERT_HEAD(&ww->callbacks, c, next);
516 }
517 
518 /*
519  * search for a callback and execute it
520  */
wdg_list_callback(struct wdg_object * wo,int key)521 static int wdg_list_callback(struct wdg_object *wo, int key)
522 {
523    WDG_WO_EXT(struct wdg_list_handle, ww);
524    struct wdg_list_call *c;
525 
526    SLIST_FOREACH(c, &ww->callbacks, next) {
527       if (c->key == key) {
528          void *value;
529 
530          WDG_DEBUG_MSG("wdg_list_callback");
531 
532          /* retrieve the value from the current item */
533          value = item_userptr(current_item(ww->menu));
534 
535          /* execute the callback */
536          WDG_EXECUTE(c->callback, value);
537 
538          return WDG_E_SUCCESS;
539       }
540    }
541 
542    return -WDG_E_NOTHANDLED;
543 }
544 
545 /*
546  * force the repaint of the menu
547  */
wdg_list_refresh(wdg_t * wo)548 void wdg_list_refresh(wdg_t *wo)
549 {
550    WDG_WO_EXT(struct wdg_list_handle, ww);
551 
552    WDG_DEBUG_MSG("wdg_list_refresh");
553 
554    /* remember the position */
555    ww->current = current_item(ww->menu);
556 
557    /* delete the window */
558    unpost_menu(ww->menu);
559 
560    /* set the old position */
561    set_current_item(ww->menu, ww->current);
562    /* draw the menu */
563    post_menu(ww->menu);
564 
565    wnoutrefresh(ww->mwin);
566 }
567 
568 /* EOF */
569 
570 // vim:ts=3:expandtab
571 
572