ui/ncurses: Improve update handling in nested screens
[petitboot] / ui / ncurses / nc-subset.c
1 /*
2  *  Copyright (C) 2013 IBM Corporation
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; version 2 of the License.
7  *
8  *  This program is distributed in the hope that it will be useful,
9  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
10  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11  *  GNU General Public License for more details.
12  *
13  *  You should have received a copy of the GNU General Public License
14  *  along with this program; if not, write to the Free Software
15  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16  */
17
18 #if defined(HAVE_CONFIG_H)
19 #include "config.h"
20 #endif
21
22 #include <errno.h>
23 #include <stdlib.h>
24 #include <string.h>
25
26 #include <talloc/talloc.h>
27 #include <types/types.h>
28 #include <i18n/i18n.h>
29 #include <log/log.h>
30
31 #include "nc-cui.h"
32 #include "nc-subset.h"
33
34 #define N_FIELDS        3
35
36 struct subset_screen {
37         struct nc_scr           scr;
38         struct cui              *cui;
39         struct nc_scr           *return_scr;
40         struct nc_widgetset     *widgetset;
41         WINDOW                  *pad;
42         struct nc_widget_subset *options;
43
44         bool                    exit;
45         void                    (*on_exit)(struct cui *);
46
47         int                     scroll_y;
48
49         int                     label_x;
50         int                     field_x;
51
52         struct {
53                 struct nc_widget_select         *options_f;
54
55                 struct nc_widget_button         *ok_b;
56                 struct nc_widget_button         *cancel_b;
57         } widgets;
58 };
59
60 struct nc_scr *subset_screen_return_scr(struct subset_screen *screen)
61 {
62         return screen->return_scr;
63 }
64
65 void subset_screen_update(struct subset_screen *screen)
66 {
67         pb_debug("Exiting subset due to update\n");
68         return screen->on_exit(screen->cui);
69 }
70
71 static struct subset_screen *subset_screen_from_scr(struct nc_scr *scr)
72 {
73         struct subset_screen *subset_screen;
74
75         assert(scr->sig == pb_subset_screen_sig);
76         subset_screen = (struct subset_screen *)
77                 ((char *)scr - (size_t)&((struct subset_screen *)0)->scr);
78         assert(subset_screen->scr.sig == pb_subset_screen_sig);
79         return subset_screen;
80 }
81
82 static void pad_refresh(struct subset_screen *screen)
83 {
84         int y, x, rows, cols;
85
86         getmaxyx(screen->scr.sub_ncw, rows, cols);
87         getbegyx(screen->scr.sub_ncw, y, x);
88
89         prefresh(screen->pad, screen->scroll_y, 0, y, x, rows, cols);
90 }
91
92 static void subset_screen_process_key(struct nc_scr *scr, int key)
93 {
94         struct subset_screen *screen = subset_screen_from_scr(scr);
95         bool handled;
96
97         handled = widgetset_process_key(screen->widgetset, key);
98
99         if (!handled) {
100                 switch (key) {
101                 case 'x':
102                 case 27: /* esc */
103                         screen->exit = true;
104                         break;
105                 }
106         }
107
108         if (screen->exit)
109                 screen->on_exit(screen->cui);
110         else if (handled)
111                 pad_refresh(screen);
112 }
113
114 static int subset_screen_post(struct nc_scr *scr)
115 {
116         struct subset_screen *screen = subset_screen_from_scr(scr);
117         widgetset_post(screen->widgetset);
118         nc_scr_frame_draw(scr);
119         redrawwin(scr->main_ncw);
120         wrefresh(scr->main_ncw);
121         pad_refresh(screen);
122         return 0;
123 }
124
125 static int subset_screen_unpost(struct nc_scr *scr)
126 {
127         struct subset_screen *screen = subset_screen_from_scr(scr);
128         widgetset_unpost(screen->widgetset);
129         return 0;
130 }
131
132 struct nc_scr *subset_screen_scr(struct subset_screen *screen)
133 {
134         return &screen->scr;
135 }
136
137 static void ok_click(void *arg)
138 {
139         struct subset_screen *screen = arg;
140         int idx = widget_select_get_value(screen->widgets.options_f);
141         widget_subset_callback(screen->return_scr, screen->options, idx);
142         screen->exit = true;
143 }
144
145 static void cancel_click(void *arg)
146 {
147         struct subset_screen *screen = arg;
148         screen->exit = true;
149 }
150
151 static void subset_screen_layout_widgets(struct subset_screen *screen)
152 {
153         int y = 1;
154
155         /* select */
156         widget_move(widget_select_base(screen->widgets.options_f),
157                 y, screen->label_x);
158         y+= widget_height(widget_select_base(screen->widgets.options_f));
159
160         /* ok, cancel */
161         y += 1;
162
163         widget_move(widget_button_base(screen->widgets.ok_b),
164                 y, screen->field_x);
165         widget_move(widget_button_base(screen->widgets.cancel_b),
166                 y, screen->field_x + 14);
167 }
168
169 static void subset_screen_option_select(void *arg, int value)
170 {
171         struct subset_screen *screen = arg;
172         widgetset_unpost(screen->widgetset);
173         subset_screen_layout_widgets(screen);
174         widgetset_post(screen->widgetset);
175         (void)value;
176 }
177
178 static void subset_screen_setup_widgets(struct subset_screen *screen)
179 {
180         struct nc_widgetset *set = screen->widgetset;
181         struct nc_widget_subset *subset = screen->options;
182
183         build_assert(sizeof(screen->widgets) / sizeof(struct widget *)
184                         == N_FIELDS);
185
186         screen->widgets.options_f = widget_new_select(set, 0, 0,
187                         COLS - (2 * screen->label_x));
188
189         widget_select_on_change(screen->widgets.options_f,
190                         subset_screen_option_select, screen);
191
192         widget_subset_show_inactive(subset, screen->widgets.options_f);
193
194         screen->widgets.ok_b = widget_new_button(set, 0, 0, 10, _("OK"),
195                         ok_click, screen);
196         screen->widgets.cancel_b = widget_new_button(set, 0, 0, 10, _("Cancel"),
197                         cancel_click, screen);
198 }
199
200 static void subset_screen_widget_focus(struct nc_widget *widget, void *arg)
201 {
202         struct subset_screen *screen = arg;
203         int w_y, s_max;
204
205         w_y = widget_y(widget) + widget_focus_y(widget);
206         s_max = getmaxy(screen->scr.sub_ncw) - 1;
207
208         if (w_y < screen->scroll_y)
209                 screen->scroll_y = w_y;
210
211         else if (w_y + screen->scroll_y + 1 > s_max)
212                 screen->scroll_y = 1 + w_y - s_max;
213
214         else
215                 return;
216
217         pad_refresh(screen);
218 }
219
220 static void subset_screen_draw(struct subset_screen *screen)
221 {
222         bool repost = false;
223         int height;
224
225         /* Size of pad = top space + number of available options */
226         height = 1 + N_FIELDS + widget_subset_n_inactive(screen->options);
227
228         if (!screen->pad || getmaxy(screen->pad) < height) {
229                 if (screen->pad)
230                         delwin(screen->pad);
231                 screen->pad = newpad(height, COLS);
232         }
233
234         if (screen->widgetset) {
235                 widgetset_unpost(screen->widgetset);
236                 talloc_free(screen->widgetset);
237                 repost = true;
238         }
239
240         screen->widgetset = widgetset_create(screen, screen->scr.main_ncw,
241                         screen->pad);
242         widgetset_set_widget_focus(screen->widgetset,
243                         subset_screen_widget_focus, screen);
244
245         subset_screen_setup_widgets(screen);
246         subset_screen_layout_widgets(screen);
247
248         if (repost)
249                 widgetset_post(screen->widgetset);
250 }
251
252 static int subset_screen_destroy(void *arg)
253 {
254         struct subset_screen *screen = arg;
255         if (screen->pad)
256                 delwin(screen->pad);
257         return 0;
258 }
259
260 struct subset_screen *subset_screen_init(struct cui *cui,
261                 struct nc_scr *current_scr,
262                 const char *title_suffix,
263                 void *subset,
264                 void (*on_exit)(struct cui *))
265 {
266         struct subset_screen *screen;
267
268         screen = talloc_zero(cui, struct subset_screen);
269         talloc_set_destructor(screen, subset_screen_destroy);
270
271         screen->cui = cui;
272         screen->on_exit = on_exit;
273         screen->options = (struct nc_widget_subset *) subset;
274         screen->label_x = 8;
275         screen->field_x = 8;
276
277         screen->return_scr = current_scr;
278
279         nc_scr_init(&screen->scr, pb_subset_screen_sig, 0,
280                 cui, subset_screen_process_key,
281                 subset_screen_post, subset_screen_unpost,
282                 NULL);
283
284         screen->scr.frame.ltitle = talloc_strdup(screen,
285                         title_suffix);
286         screen->scr.frame.rtitle = NULL;
287         screen->scr.frame.help = talloc_strdup(screen,
288                         _("tab=next, shift+tab=previous, x=exit"));
289
290         scrollok(screen->scr.sub_ncw, true);
291
292         subset_screen_draw(screen);
293
294         return screen;
295 }