source: mainline/uspace/lib/ui/src/menu.c@ 9bfa8c8

Last change on this file since 9bfa8c8 was d7f7a4a, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 4 years ago

Replace some license headers with SPDX identifier

Headers are replaced using tools/transorm-copyright.sh only
when it can be matched verbatim with the license header used
throughout most of the codebase.

  • Property mode set to 100644
File size: 13.6 KB
Line 
1/*
2 * SPDX-FileCopyrightText: 2022 Jiri Svoboda
3 *
4 * SPDX-License-Identifier: BSD-3-Clause
5 */
6
7/** @addtogroup libui
8 * @{
9 */
10/**
11 * @file Menu
12 */
13
14#include <adt/list.h>
15#include <errno.h>
16#include <gfx/color.h>
17#include <gfx/context.h>
18#include <gfx/render.h>
19#include <gfx/text.h>
20#include <io/pos_event.h>
21#include <stdlib.h>
22#include <str.h>
23#include <uchar.h>
24#include <ui/accel.h>
25#include <ui/control.h>
26#include <ui/paint.h>
27#include <ui/popup.h>
28#include <ui/menu.h>
29#include <ui/menubar.h>
30#include <ui/menuentry.h>
31#include <ui/resource.h>
32#include <ui/window.h>
33#include "../private/menubar.h"
34#include "../private/menu.h"
35#include "../private/resource.h"
36
37enum {
38 menu_frame_w = 4,
39 menu_frame_h = 4,
40 menu_frame_w_text = 2,
41 menu_frame_h_text = 1,
42 menu_frame_h_margin_text = 1
43};
44
45static void ui_menu_popup_close(ui_popup_t *, void *);
46static void ui_menu_popup_kbd(ui_popup_t *, void *, kbd_event_t *);
47static void ui_menu_popup_pos(ui_popup_t *, void *, pos_event_t *);
48static void ui_menu_key_press_unmod(ui_menu_t *, kbd_event_t *);
49
50static ui_popup_cb_t ui_menu_popup_cb = {
51 .close = ui_menu_popup_close,
52 .kbd = ui_menu_popup_kbd,
53 .pos = ui_menu_popup_pos
54};
55
56/** Create new menu.
57 *
58 * @param mbar Menu bar
59 * @param caption Caption
60 * @param rmenu Place to store pointer to new menu
61 * @return EOK on success, ENOMEM if out of memory
62 */
63errno_t ui_menu_create(ui_menu_bar_t *mbar, const char *caption,
64 ui_menu_t **rmenu)
65{
66 ui_menu_t *menu;
67
68 menu = calloc(1, sizeof(ui_menu_t));
69 if (menu == NULL)
70 return ENOMEM;
71
72 menu->caption = str_dup(caption);
73 if (menu->caption == NULL) {
74 free(menu);
75 return ENOMEM;
76 }
77
78 menu->mbar = mbar;
79 list_append(&menu->lmenus, &mbar->menus);
80 list_initialize(&menu->entries);
81
82 *rmenu = menu;
83 return EOK;
84}
85
86/** Destroy menu.
87 *
88 * @param menu Menu or @c NULL
89 */
90void ui_menu_destroy(ui_menu_t *menu)
91{
92 ui_menu_entry_t *mentry;
93
94 if (menu == NULL)
95 return;
96
97 /* Destroy entries */
98 mentry = ui_menu_entry_first(menu);
99 while (mentry != NULL) {
100 ui_menu_entry_destroy(mentry);
101 mentry = ui_menu_entry_first(menu);
102 }
103
104 list_remove(&menu->lmenus);
105 free(menu->caption);
106 free(menu);
107}
108
109/** Get first menu in menu bar.
110 *
111 * @param mbar Menu bar
112 * @return First menu or @c NULL if there is none
113 */
114ui_menu_t *ui_menu_first(ui_menu_bar_t *mbar)
115{
116 link_t *link;
117
118 link = list_first(&mbar->menus);
119 if (link == NULL)
120 return NULL;
121
122 return list_get_instance(link, ui_menu_t, lmenus);
123}
124
125/** Get next menu in menu bar.
126 *
127 * @param cur Current menu
128 * @return Next menu or @c NULL if @a cur is the last one
129 */
130ui_menu_t *ui_menu_next(ui_menu_t *cur)
131{
132 link_t *link;
133
134 link = list_next(&cur->lmenus, &cur->mbar->menus);
135 if (link == NULL)
136 return NULL;
137
138 return list_get_instance(link, ui_menu_t, lmenus);
139}
140
141/** Get last menu in menu bar.
142 *
143 * @param mbar Menu bar
144 * @return Last menu or @c NULL if there is none
145 */
146ui_menu_t *ui_menu_last(ui_menu_bar_t *mbar)
147{
148 link_t *link;
149
150 link = list_last(&mbar->menus);
151 if (link == NULL)
152 return NULL;
153
154 return list_get_instance(link, ui_menu_t, lmenus);
155}
156
157/** Get previous menu in menu bar.
158 *
159 * @param cur Current menu
160 * @return Previous menu or @c NULL if @a cur is the fist one
161 */
162ui_menu_t *ui_menu_prev(ui_menu_t *cur)
163{
164 link_t *link;
165
166 link = list_prev(&cur->lmenus, &cur->mbar->menus);
167 if (link == NULL)
168 return NULL;
169
170 return list_get_instance(link, ui_menu_t, lmenus);
171}
172
173/** Get menu caption.
174 *
175 * @param menu Menu
176 * @return Caption (owned by @a menu)
177 */
178const char *ui_menu_caption(ui_menu_t *menu)
179{
180 return menu->caption;
181}
182
183/** Get menu geometry.
184 *
185 * @param menu Menu
186 * @param spos Starting position
187 * @param geom Structure to fill in with computed geometry
188 */
189void ui_menu_get_geom(ui_menu_t *menu, gfx_coord2_t *spos,
190 ui_menu_geom_t *geom)
191{
192 ui_resource_t *res;
193 gfx_coord2_t edim;
194 gfx_coord_t frame_w;
195 gfx_coord_t frame_h;
196
197 res = ui_window_get_res(menu->mbar->window);
198
199 if (res->textmode) {
200 frame_w = menu_frame_w_text;
201 frame_h = menu_frame_h_text;
202 } else {
203 frame_w = menu_frame_w;
204 frame_h = menu_frame_h;
205 }
206
207 edim.x = ui_menu_entry_calc_width(menu, menu->max_caption_w,
208 menu->max_shortcut_w);
209 edim.y = menu->total_h;
210
211 geom->outer_rect.p0 = *spos;
212 geom->outer_rect.p1.x = spos->x + edim.x + 2 * frame_w;
213 geom->outer_rect.p1.y = spos->y + edim.y + 2 * frame_h;
214
215 geom->entries_rect.p0.x = spos->x + frame_w;
216 geom->entries_rect.p0.y = spos->y + frame_h;
217 geom->entries_rect.p1.x = geom->entries_rect.p0.x + edim.x;
218 geom->entries_rect.p1.y = geom->entries_rect.p0.x + edim.y;
219}
220
221/** Get menu rectangle.
222 *
223 * @param menu Menu
224 * @param spos Starting position (top-left corner)
225 * @param rect Place to store menu rectangle
226 */
227void ui_menu_get_rect(ui_menu_t *menu, gfx_coord2_t *spos, gfx_rect_t *rect)
228{
229 ui_menu_geom_t geom;
230
231 ui_menu_get_geom(menu, spos, &geom);
232 *rect = geom.outer_rect;
233}
234
235/** Get menu accelerator character.
236 *
237 * @param menu Menu
238 * @return Accelerator character (lowercase) or the null character if
239 * the menu has no accelerator.
240 */
241char32_t ui_menu_get_accel(ui_menu_t *menu)
242{
243 return ui_accel_get(menu->caption);
244}
245
246/** Get UI resource from menu.
247 *
248 * @param menu Menu
249 * @return UI resource
250 */
251ui_resource_t *ui_menu_get_res(ui_menu_t *menu)
252{
253 return ui_popup_get_res(menu->popup);
254}
255
256/** Open menu.
257 *
258 * @param menu Menu
259 * @param prect Parent rectangle around which the menu should be placed
260 */
261errno_t ui_menu_open(ui_menu_t *menu, gfx_rect_t *prect)
262{
263 ui_popup_t *popup = NULL;
264 ui_popup_params_t params;
265 ui_menu_geom_t geom;
266 gfx_coord2_t mpos;
267 errno_t rc;
268
269 /* Select first entry */
270 menu->selected = ui_menu_entry_first(menu);
271
272 /* Determine menu dimensions */
273
274 mpos.x = 0;
275 mpos.y = 0;
276 ui_menu_get_geom(menu, &mpos, &geom);
277
278 ui_popup_params_init(&params);
279 params.rect = geom.outer_rect;
280 params.place = *prect;
281
282 rc = ui_popup_create(menu->mbar->ui, menu->mbar->window, &params,
283 &popup);
284 if (rc != EOK)
285 return rc;
286
287 menu->popup = popup;
288 ui_popup_set_cb(popup, &ui_menu_popup_cb, menu);
289
290 return ui_menu_paint(menu, &mpos);
291}
292
293/** Close menu.
294 *
295 * @param menu Menu
296 */
297void ui_menu_close(ui_menu_t *menu)
298{
299 ui_popup_destroy(menu->popup);
300 menu->popup = NULL;
301}
302
303/** Determine if menu is open.
304 *
305 * @param menu Menu
306 * @return @c true iff menu is open
307 */
308bool ui_menu_is_open(ui_menu_t *menu)
309{
310 return menu->popup != NULL;
311}
312
313/** Paint menu.
314 *
315 * @param menu Menu
316 * @param spos Starting position (top-left corner)
317 * @return EOK on success or an error code
318 */
319errno_t ui_menu_paint_bg_gfx(ui_menu_t *menu, gfx_coord2_t *spos)
320{
321 ui_resource_t *res;
322 ui_menu_geom_t geom;
323 gfx_rect_t bg_rect;
324 errno_t rc;
325
326 res = ui_menu_get_res(menu);
327 ui_menu_get_geom(menu, spos, &geom);
328
329 /* Paint menu frame */
330
331 rc = gfx_set_color(res->gc, res->wnd_face_color);
332 if (rc != EOK)
333 goto error;
334
335 rc = ui_paint_outset_frame(res, &geom.outer_rect, &bg_rect);
336 if (rc != EOK)
337 goto error;
338
339 /* Paint menu background */
340
341 rc = gfx_set_color(res->gc, res->wnd_face_color);
342 if (rc != EOK)
343 goto error;
344
345 rc = gfx_fill_rect(res->gc, &bg_rect);
346 if (rc != EOK)
347 goto error;
348
349 return EOK;
350error:
351 return rc;
352}
353
354/** Paint menu.
355 *
356 * @param menu Menu
357 * @param spos Starting position (top-left corner)
358 * @return EOK on success or an error code
359 */
360errno_t ui_menu_paint_bg_text(ui_menu_t *menu, gfx_coord2_t *spos)
361{
362 ui_resource_t *res;
363 ui_menu_geom_t geom;
364 gfx_rect_t rect;
365 errno_t rc;
366
367 res = ui_menu_get_res(menu);
368 ui_menu_get_geom(menu, spos, &geom);
369
370 /* Paint menu background */
371
372 rc = gfx_set_color(res->gc, res->wnd_face_color);
373 if (rc != EOK)
374 goto error;
375
376 rc = gfx_fill_rect(res->gc, &geom.outer_rect);
377 if (rc != EOK)
378 goto error;
379
380 /* Paint menu box */
381
382 rect = geom.outer_rect;
383 rect.p0.x += menu_frame_h_margin_text;
384 rect.p1.x -= menu_frame_h_margin_text;
385
386 rc = ui_paint_text_box(res, &rect, ui_box_single, res->wnd_face_color);
387 if (rc != EOK)
388 goto error;
389
390 return EOK;
391error:
392 return rc;
393}
394
395/** Paint menu.
396 *
397 * @param menu Menu
398 * @param spos Starting position (top-left corner)
399 * @return EOK on success or an error code
400 */
401errno_t ui_menu_paint(ui_menu_t *menu, gfx_coord2_t *spos)
402{
403 ui_resource_t *res;
404 gfx_coord2_t pos;
405 ui_menu_entry_t *mentry;
406 ui_menu_geom_t geom;
407 errno_t rc;
408
409 res = ui_menu_get_res(menu);
410 ui_menu_get_geom(menu, spos, &geom);
411
412 /* Paint menu frame and background */
413 if (res->textmode)
414 rc = ui_menu_paint_bg_text(menu, spos);
415 else
416 rc = ui_menu_paint_bg_gfx(menu, spos);
417 if (rc != EOK)
418 goto error;
419
420 /* Paint entries */
421 pos = geom.entries_rect.p0;
422
423 mentry = ui_menu_entry_first(menu);
424 while (mentry != NULL) {
425 rc = ui_menu_entry_paint(mentry, &pos);
426 if (rc != EOK)
427 goto error;
428
429 pos.y += ui_menu_entry_height(mentry);
430 mentry = ui_menu_entry_next(mentry);
431 }
432
433 rc = gfx_update(res->gc);
434 if (rc != EOK)
435 goto error;
436
437 return EOK;
438error:
439 return rc;
440}
441
442/** Handle position event in menu.
443 *
444 * @param menu Menu
445 * @param spos Starting position (top-left corner)
446 * @param event Position event
447 * @return ui_claimed iff the event was claimed
448 */
449ui_evclaim_t ui_menu_pos_event(ui_menu_t *menu, gfx_coord2_t *spos,
450 pos_event_t *event)
451{
452 ui_menu_geom_t geom;
453 ui_menu_entry_t *mentry;
454 gfx_coord2_t pos;
455 gfx_coord2_t epos;
456 ui_evclaim_t claimed;
457
458 ui_menu_get_geom(menu, spos, &geom);
459 epos.x = event->hpos;
460 epos.y = event->vpos;
461
462 pos = geom.entries_rect.p0;
463
464 mentry = ui_menu_entry_first(menu);
465 while (mentry != NULL) {
466 claimed = ui_menu_entry_pos_event(mentry, &pos, event);
467 if (claimed == ui_claimed)
468 return ui_claimed;
469
470 pos.y += ui_menu_entry_height(mentry);
471 mentry = ui_menu_entry_next(mentry);
472 }
473
474 /* Event inside menu? */
475 if (gfx_pix_inside_rect(&epos, &geom.outer_rect)) {
476 /* Claim event */
477 return ui_claimed;
478 } else {
479 /* Press outside menu - close it */
480 if (event->type == POS_PRESS)
481 ui_menu_bar_deactivate(menu->mbar);
482 }
483
484 return ui_unclaimed;
485}
486
487/** Handle keyboard event in menu.
488 *
489 * @param menu Menu
490 * @param event Keyboard event
491 * @return ui_claimed iff the event was claimed
492 */
493ui_evclaim_t ui_menu_kbd_event(ui_menu_t *menu, kbd_event_t *event)
494{
495 if (event->type == KEY_PRESS && (event->mods &
496 (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
497 ui_menu_key_press_unmod(menu, event);
498 }
499
500 if (event->type == KEY_PRESS && (event->mods & KM_ALT) != 0 &&
501 (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0')
502 ui_menu_bar_press_accel(menu->mbar, event->c);
503
504 return ui_claimed;
505}
506
507/** Move one entry up.
508 *
509 * Non-selectable entries are skipped. If we are already at the top,
510 * we wrap around.
511 *
512 * @param menu Menu
513 */
514void ui_menu_up(ui_menu_t *menu)
515{
516 gfx_coord2_t mpos;
517 ui_menu_entry_t *nentry;
518
519 if (menu->selected == NULL)
520 return;
521
522 nentry = ui_menu_entry_prev(menu->selected);
523 if (nentry == NULL)
524 nentry = ui_menu_entry_last(menu);
525
526 /* Need to find a selectable entry */
527 while (!ui_menu_entry_selectable(nentry)) {
528 nentry = ui_menu_entry_prev(nentry);
529 if (nentry == NULL)
530 nentry = ui_menu_entry_last(menu);
531
532 /* Went completely around and found nothing? */
533 if (nentry == menu->selected)
534 return;
535 }
536
537 menu->selected = nentry;
538
539 mpos.x = 0;
540 mpos.y = 0;
541 (void) ui_menu_paint(menu, &mpos);
542}
543
544/** Move one entry down.
545 *
546 * Non-selectable entries are skipped. If we are already at the bottom,
547 * we wrap around.
548 *
549 * @param menu Menu
550 */
551void ui_menu_down(ui_menu_t *menu)
552{
553 gfx_coord2_t mpos;
554 ui_menu_entry_t *nentry;
555
556 if (menu->selected == NULL)
557 return;
558
559 nentry = ui_menu_entry_next(menu->selected);
560 if (nentry == NULL)
561 nentry = ui_menu_entry_first(menu);
562
563 /* Need to find a selectable entry */
564 while (!ui_menu_entry_selectable(nentry)) {
565 nentry = ui_menu_entry_next(nentry);
566 if (nentry == NULL)
567 nentry = ui_menu_entry_first(menu);
568
569 /* Went completely around and found nothing? */
570 if (nentry == menu->selected)
571 return;
572 }
573
574 menu->selected = nentry;
575
576 mpos.x = 0;
577 mpos.y = 0;
578 (void) ui_menu_paint(menu, &mpos);
579}
580
581/** Handle key press without modifiers in menu popup window.
582 *
583 * @param menu Menu
584 * @param event Keyboard event
585 */
586static void ui_menu_key_press_unmod(ui_menu_t *menu, kbd_event_t *event)
587{
588 ui_menu_entry_t *mentry;
589 char32_t c;
590
591 switch (event->key) {
592 case KC_ESCAPE:
593 ui_menu_bar_deactivate(menu->mbar);
594 break;
595 case KC_LEFT:
596 ui_menu_bar_left(menu->mbar);
597 break;
598 case KC_RIGHT:
599 ui_menu_bar_right(menu->mbar);
600 break;
601 case KC_UP:
602 ui_menu_up(menu);
603 break;
604 case KC_DOWN:
605 ui_menu_down(menu);
606 break;
607 case KC_ENTER:
608 if (menu->selected != NULL)
609 ui_menu_entry_activate(menu->selected);
610 break;
611 default:
612 if (event->c != '\0') {
613 mentry = ui_menu_entry_first(menu);
614 while (mentry != NULL) {
615 c = ui_menu_entry_get_accel(mentry);
616 if (c == event->c && menu->selected != NULL) {
617 ui_menu_entry_activate(mentry);
618 break;
619 }
620 mentry = ui_menu_entry_next(mentry);
621 }
622 }
623 break;
624 }
625}
626
627/** Handle close event in menu popup window.
628 *
629 * @param popup Menu popup window
630 * @param arg Argument (ui_menu_t *)
631 */
632static void ui_menu_popup_close(ui_popup_t *popup, void *arg)
633{
634 ui_menu_t *menu = (ui_menu_t *)arg;
635
636 /* Deactivate menu bar, close menu */
637 ui_menu_bar_deactivate(menu->mbar);
638}
639
640/** Handle keyboard event in menu popup window.
641 *
642 * @param popup Menu popup window
643 * @param arg Argument (ui_menu_t *)
644 * @param event Keyboard event
645 */
646static void ui_menu_popup_kbd(ui_popup_t *popup, void *arg, kbd_event_t *event)
647{
648 ui_menu_t *menu = (ui_menu_t *)arg;
649
650 ui_menu_kbd_event(menu, event);
651}
652
653/** Handle position event in menu popup window.
654 *
655 * @param popup Menu popup window
656 * @param arg Argument (ui_menu_t *)
657 * @param event Position event
658 */
659static void ui_menu_popup_pos(ui_popup_t *popup, void *arg, pos_event_t *event)
660{
661 ui_menu_t *menu = (ui_menu_t *)arg;
662 gfx_coord2_t spos;
663
664 spos.x = 0;
665 spos.y = 0;
666 ui_menu_pos_event(menu, &spos, event);
667}
668
669/** @}
670 */
Note: See TracBrowser for help on using the repository browser.