configure: Use AC_GNU_SOURCE
[petitboot] / ui / twin / pbt-menu.c
1 /*
2  *  Copyright Geoff Levand <geoff@infradead.org>
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 <assert.h>
23 #include <string.h>
24 #include <linux/input.h>
25
26 #include "log/log.h"
27 #include "talloc/talloc.h"
28 #include "ui/common/ui-system.h"
29
30 #include "pbt-menu.h"
31
32 static void pbt_item_draw_cb(twin_window_t *window)
33 {
34         struct pbt_item *item = pbt_item_from_window(window);
35         twin_pixmap_t *image;
36
37         assert(window == item->window);
38
39         pbt_dump_item(item);
40
41         //pbt_dump_pixmap(window->pixmap);
42
43         if (pbt_item_is_selected(item) && item->menu->has_focus)
44                 image = item->pixmap_active;
45         else if (pbt_item_is_selected(item) && !item->menu->has_focus)
46                 image = item->pixmap_selected;
47         else
48                 image = item->pixmap_idle;
49
50         pbt_image_draw(window->pixmap, image);
51 }
52
53 static twin_bool_t pbt_item_event_cb(twin_window_t *window,
54         twin_event_t *event)
55 {
56         struct pbt_item *item = pbt_item_from_window(window);
57
58         pbt_dump_event(pbt_item_name(item), window, event);
59
60         switch(event->kind) {
61         case TwinEventButtonDown:
62                 if (item->on_execute)
63                         item->on_execute(item);
64                 break;
65         case TwinEventButtonUp:
66                 break;
67         case TwinEventMotion:
68                 /* prevent window drag */
69                 return TWIN_TRUE;
70         case TwinEventEnter:
71                 pbt_item_set_as_selected(item);
72                 break;
73         case TwinEventLeave:
74                 break;
75         case TwinEventKeyDown:
76                 switch(event->u.key.key) {
77                 case (twin_keysym_t)XK_Return:
78                 case (twin_keysym_t)KEY_ENTER:
79                         if (item->on_execute)
80                                 item->on_execute(item);
81                         break;
82                 case (twin_keysym_t)'e':
83                         if (item->on_edit)
84                                 item->on_edit(item);
85                         break;
86                 default:
87                         break;
88                 }
89                 break;
90         default:
91                 break;
92         }
93         return TWIN_FALSE;
94 }
95
96 int pbt_item_editor(struct pbt_item *item)
97 {
98         (void)item;
99         return -1;
100 }
101
102 struct pbt_item *pbt_item_create(struct pbt_menu *menu, const char *name,
103         unsigned int position, const char *icon_filename, const char *title,
104         const char *text)
105 {
106         struct pbt_quad q;
107         struct pbt_item *item;
108         twin_pixmap_t *icon;
109         twin_operand_t src;
110         const struct pbt_menu_layout *layout = &menu->layout;
111         twin_path_t *path;
112         static const twin_argb32_t focus_color = 0x10404040;
113         enum {
114                 corner_rounding = 8,
115                 stroke_width = 2,
116         };
117
118         assert(menu);
119
120         DBGS("%d:%s (%s)\n", position, name, icon_filename);
121
122         item = talloc_zero(menu, struct pbt_item);
123
124         if (!item)
125                 return NULL;
126
127         item->menu = menu;
128         item->on_edit = pbt_item_editor;
129
130         pbt_menu_get_item_quad(menu, position, &q);
131
132         item->window = twin_window_create(menu->scr->tscreen,
133                 TWIN_ARGB32, TwinWindowPlain,
134                 q.x, q.y, q.width, q.height);
135
136         if (!item->window)
137                 goto fail_window_create;
138
139         twin_window_set_name(item->window, name);
140         item->window->client_data = item;
141         item->window->draw = pbt_item_draw_cb;
142         item->window->event = pbt_item_event_cb;
143
144         item->pixmap_idle = twin_pixmap_create(TWIN_ARGB32, q.width, q.height);
145         assert(item->pixmap_idle);
146
147         item->pixmap_selected = twin_pixmap_create(TWIN_ARGB32, q.width,
148                 q.height);
149         assert(item->pixmap_selected);
150
151         item->pixmap_active = twin_pixmap_create(TWIN_ARGB32, q.width,
152                 q.height);
153         assert(item->pixmap_active);
154
155         if (!item->pixmap_idle || !item->pixmap_selected || !item->pixmap_active)
156                 goto fail_pixmap_create;
157
158         twin_fill(item->pixmap_idle, 0x01000000, TWIN_SOURCE, 0, 0, q.width,
159                 q.height);
160
161         /* Add item icon */
162
163         icon = pbt_icon_load(icon_filename);
164
165         if (!icon)
166                 goto fail_icon;
167
168         src.source_kind = TWIN_PIXMAP;
169         src.u.pixmap = icon;
170
171         twin_composite(item->pixmap_idle,
172                 //0, (item->pixmap_idle->height - icon->height) / 2,
173                 0, 0,
174                 &src, 0, 0,
175                 NULL, 0, 0,
176                 TWIN_SOURCE,
177                 icon->width, icon->height);
178
179         /* Add item text */
180
181         path = twin_path_create();
182         assert(path);
183
184         if (title) {
185                 twin_path_set_font_size(path,
186                         twin_int_to_fixed(layout->title.font_size));
187                 twin_path_set_font_style(path, TWIN_TEXT_UNHINTED);
188
189                 twin_path_move(path,
190                         twin_int_to_fixed(icon->width + layout->text_space),
191                         twin_int_to_fixed(layout->title.font_size
192                                 + layout->text_space));
193                 twin_path_utf8(path, title);
194                 twin_paint_path(item->pixmap_idle, layout->title.color, path);
195                 twin_path_empty(path);
196         }
197
198         if (text) {
199                 twin_path_set_font_size(path,
200                         twin_int_to_fixed(layout->text.font_size));
201                 twin_path_move(path,
202                         twin_int_to_fixed(icon->width + layout->text_space),
203                         twin_int_to_fixed(layout->title.font_size
204                                 + layout->text.font_size
205                                 + layout->text_space));
206                 twin_path_utf8(path, text);
207                 twin_paint_path(item->pixmap_idle, layout->text.color, path);
208                 twin_path_empty(path);
209         }
210
211         pbt_image_draw(item->pixmap_selected, item->pixmap_idle);
212         pbt_image_draw(item->pixmap_active, item->pixmap_idle);
213
214 if (0) {
215         static const struct pbt_border grey_border = {
216                 .right = 1,
217                 .left = 1,
218                 .top = 1,
219                 .bottom = 1,
220                 .fill_color = 0xffe0e0e0,
221         };
222
223         //pbt_border_draw(item->pixmap_idle, &pbt_blue_debug_border);
224         pbt_border_draw(item->pixmap_selected, &grey_border);
225         pbt_border_draw(item->pixmap_active, &pbt_green_debug_border);
226 } else {
227         assert(!(stroke_width % 2));
228
229         /* pixmap_selected */
230
231         twin_path_rounded_rectangle(path,
232                 twin_int_to_fixed(stroke_width / 2),
233                 twin_int_to_fixed(stroke_width / 2),
234                 twin_int_to_fixed(item->pixmap_selected->width - stroke_width),
235                 twin_int_to_fixed(item->pixmap_selected->height - stroke_width),
236                 twin_int_to_fixed(corner_rounding),
237                 twin_int_to_fixed(corner_rounding));
238
239         twin_paint_stroke(item->pixmap_selected, focus_color, path,
240                 twin_int_to_fixed(stroke_width));
241
242         twin_path_empty(path);
243
244         /* pixmap_active */
245
246         twin_path_rounded_rectangle(path, 0, 0,
247                 twin_int_to_fixed(item->pixmap_active->width),
248                 twin_int_to_fixed(item->pixmap_active->height),
249                 twin_int_to_fixed(corner_rounding),
250                 twin_int_to_fixed(corner_rounding));
251
252         twin_paint_path(item->pixmap_active, focus_color, path);
253
254         twin_path_empty(path); // FIXME: need it???
255 }
256         twin_path_destroy(path);
257
258         list_add_tail(menu->item_list, &item->list);
259
260         pbt_item_redraw(item);
261
262         return item;
263
264 fail_window_create:
265 fail_pixmap_create:
266 fail_icon:
267         return NULL;
268 }
269
270 void _pbt_dump_item(const struct pbt_item* item, const char *func, int line)
271 {
272         DBG("%s:%d: %p: %sselected, %sfocus\n", func, line, item,
273                 (pbt_item_is_selected(item) ? "+" : "-"),
274                 (item->menu->has_focus ? "+" : "-"));
275 }
276
277 /**
278  * pbt_menu_get_item_quad - Return item coords relative to screen origin.
279  */
280
281 struct pbt_quad *pbt_menu_get_item_quad(const struct pbt_menu *menu,
282         unsigned int pos, struct pbt_quad *q)
283 {
284         const struct pbt_menu_layout *layout = &menu->layout;
285
286         q->x = menu->window->pixmap->x + layout->item_space;
287
288         q->width = menu->window->pixmap->width - 2 * layout->item_space;
289
290         q->y = menu->window->pixmap->y + layout->item_space
291                 + pos * (layout->item_height + layout->item_space);
292
293         q->height = layout->item_height;
294
295         return q;
296 }
297
298 static void pbt_menu_draw_cb(twin_window_t *window)
299 {
300         struct pbt_menu *menu = pbt_menu_from_window(window);
301         twin_path_t *path = twin_path_create();
302
303         assert(path);
304
305         pbt_dump_pixmap(window->pixmap);
306
307         twin_fill(window->pixmap, menu->background_color, TWIN_SOURCE,
308                 0, 0, window->pixmap->width, window->pixmap->height);
309
310         pbt_border_draw(window->pixmap, &menu->border);
311
312         twin_path_destroy(path);
313 }
314
315 static twin_bool_t pbt_menu_event_cb(twin_window_t *window,
316         twin_event_t *event)
317 {
318         struct pbt_menu *menu = pbt_menu_from_window(window);
319         struct pbt_item *i;
320
321         pbt_dump_event(pbt_menu_name(menu), window, event);
322
323         switch(event->kind) {
324         case TwinEventButtonDown:
325         case TwinEventButtonUp:
326         case TwinEventMotion:
327                 /* prevent window drag */
328                 return TWIN_TRUE;
329         case TwinEventEnter:
330                 pbt_menu_set_focus(menu, 1);
331                 break;
332         case TwinEventLeave:
333                 if (!pbt_window_contains(window, event))
334                         pbt_menu_set_focus(menu, 0);
335                 break;
336         case TwinEventKeyDown:
337                 switch(event->u.key.key) {
338                 case (twin_keysym_t)XK_Up:
339                 case (twin_keysym_t)KEY_UP:
340                         i = list_prev_entry(menu->item_list, menu->selected,
341                                 list);
342                         if (i)
343                                 pbt_item_set_as_selected(i);
344                         break;
345                 case (twin_keysym_t)XK_Down:
346                 case (twin_keysym_t)KEY_DOWN:
347                         i = list_next_entry(menu->item_list, menu->selected,
348                                 list);
349                         if (i)
350                                 pbt_item_set_as_selected(i);
351                         break;
352                 case (twin_keysym_t)XK_Left:
353                 case (twin_keysym_t)KEY_LEFT:
354                         if (menu->parent) {
355                                 pbt_menu_set_focus(menu, 0);
356                                 pbt_menu_set_focus(menu->parent, 1);
357                         } else
358                                 DBGS("no parent\n");
359                         break;
360                 case (twin_keysym_t)XK_Right:
361                 case (twin_keysym_t)KEY_RIGHT:
362                         if (menu->selected->sub_menu) {
363                                 pbt_menu_set_focus(menu, 0);
364                                 pbt_menu_set_focus(menu->selected->sub_menu, 1);
365                         } else
366                                 DBGS("no sub_menu\n");
367                         break;
368                 default:
369                         return pbt_item_event_cb(menu->selected->window, event);
370                 }
371                 break;
372         default:
373                 break;
374         }
375         return TWIN_FALSE;
376 }
377
378 struct pbt_menu *pbt_menu_create(void *talloc_ctx, const char *name,
379         struct pbt_scr *scr, struct pbt_menu *parent, const struct pbt_quad *q,
380         const struct pbt_menu_layout *layout)
381 {
382         struct pbt_menu *menu;
383
384         assert(scr);
385
386         DBGS("%s\n", name);
387
388         menu = talloc_zero(talloc_ctx, struct pbt_menu);
389
390         if (!menu)
391                 return NULL;
392
393         menu->scr = scr;
394         menu->parent = parent;
395         menu->layout = *layout;
396
397         menu->item_list = talloc(menu, struct list);
398         list_init(menu->item_list);
399
400         menu->window = twin_window_create(scr->tscreen, TWIN_ARGB32,
401                 TwinWindowPlain, q->x, q->y,
402                 q->width, q->height);
403
404         if (!menu->window)
405                 goto fail_window;
406
407         DBGS("window = %p\n", menu->window);
408
409         twin_window_set_name(menu->window, name);
410
411         menu->background_color = 0x01000000; //FIXME: what value???
412
413         menu->window->draw = pbt_menu_draw_cb;
414         menu->window->event = pbt_menu_event_cb;
415         menu->window->client_data = menu;
416
417         pbt_dump_pixmap(menu->window->pixmap);
418
419         pbt_menu_redraw(menu);
420
421         return menu;
422
423 fail_window:
424         assert(0);
425         talloc_free(menu);
426         return NULL;
427 }
428
429 void pbt_menu_set_focus(struct pbt_menu *menu, int focus)
430 {
431         DBGS("%s(%p): %d -> %d\n", pbt_menu_name(menu), menu, menu->has_focus,
432                 focus);
433
434         assert(menu->selected);
435
436         if (!menu->has_focus == !focus)
437                 return;
438
439         menu->has_focus = !!focus;
440
441         /* Route key events to menu with focus. */
442
443         if (menu->has_focus)
444                 menu->scr->tscreen->active = menu->window->pixmap;
445
446         pbt_item_redraw(menu->selected);
447 }
448
449 void pbt_menu_hide(struct pbt_menu *menu)
450 {
451         struct pbt_item *item;
452
453         if (!menu)
454                 return;
455
456         list_for_each_entry(menu->item_list, item, list) {
457                 if (item->sub_menu)
458                         pbt_menu_hide(item->sub_menu);
459
460                 twin_window_hide(item->window);
461                 //twin_window_queue_paint(item->window);
462         }
463
464         twin_window_hide(menu->window);
465         //twin_window_queue_paint(menu->window);
466 }
467
468 void pbt_menu_show(struct pbt_menu *menu, int hide)
469 {
470         struct pbt_item *item;
471
472         if (!menu)
473                 return;
474
475         twin_window_show(menu->window);
476         pbt_menu_redraw(menu);
477
478         list_for_each_entry(menu->item_list, item, list) {
479                 twin_window_show(item->window);
480                 pbt_item_redraw(item);
481
482                 if (item->sub_menu) {
483                         if (pbt_item_is_selected(item))
484                                 pbt_menu_show(item->sub_menu, hide);
485                         else if (hide)
486                                 pbt_menu_hide(item->sub_menu);
487                 }
488         }
489 }
490
491 void pbt_menu_set_selected(struct pbt_menu *menu, struct pbt_item *item)
492 {
493         struct pbt_item *last_selected;
494
495         assert(item);
496
497         DBGS("%s(%p): %s(%p) -> %s(%p)\n", pbt_menu_name(menu), menu,
498                 (menu->selected ? pbt_menu_name(menu) : "none"),
499                 menu->selected, pbt_item_name(item), item);
500
501         if (menu->selected == item)
502                 return;
503
504         last_selected = menu->selected;
505         menu->selected = item;
506
507         if (last_selected) {
508                 pbt_menu_hide(last_selected->sub_menu);
509                 pbt_item_redraw(last_selected);
510         }
511
512         pbt_item_redraw(item);
513         pbt_menu_show(item->sub_menu, 0);
514 }