source: mainline/uspace/lib/ui/src/menubar.c@ 9e240c1

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 9e240c1 was 4034954c, checked in by Jiri Svoboda <jiri@…>, 21 months ago

Do not forget to close system menu. Close first, open second!

When moving out of system menu to menu bar we would forget to
close it. This was only causing noticeable problems in text mode.
Also we first close the current menu, then open the other one,
as juggling multiple popup windows in text mode could cause problems.

  • Property mode set to 100644
File size: 15.7 KB
Line 
1/*
2 * Copyright (c) 2023 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libui
30 * @{
31 */
32/**
33 * @file Menu bar
34 */
35
36#include <adt/list.h>
37#include <ctype.h>
38#include <errno.h>
39#include <gfx/color.h>
40#include <gfx/context.h>
41#include <gfx/render.h>
42#include <gfx/text.h>
43#include <io/pos_event.h>
44#include <stdlib.h>
45#include <str.h>
46#include <ui/control.h>
47#include <ui/paint.h>
48#include <ui/menubar.h>
49#include <ui/menudd.h>
50#include <ui/wdecor.h>
51#include <ui/window.h>
52#include "../private/menubar.h"
53#include "../private/resource.h"
54#include "../private/wdecor.h"
55#include "../private/window.h"
56
57enum {
58 menubar_hpad = 4,
59 menubar_vpad = 4,
60 menubar_hpad_text = 1,
61 menubar_vpad_text = 0
62};
63
64static void ui_menu_bar_ctl_destroy(void *);
65static errno_t ui_menu_bar_ctl_paint(void *);
66static ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *, kbd_event_t *);
67static ui_evclaim_t ui_menu_bar_ctl_pos_event(void *, pos_event_t *);
68
69/** Menu bar control ops */
70ui_control_ops_t ui_menu_bar_ops = {
71 .destroy = ui_menu_bar_ctl_destroy,
72 .paint = ui_menu_bar_ctl_paint,
73 .kbd_event = ui_menu_bar_ctl_kbd_event,
74 .pos_event = ui_menu_bar_ctl_pos_event
75};
76
77/** Create new menu bar.
78 *
79 * @param ui UI
80 * @param window Window that will contain the menu bar
81 * @param rmbar Place to store pointer to new menu bar
82 * @return EOK on success, ENOMEM if out of memory
83 */
84errno_t ui_menu_bar_create(ui_t *ui, ui_window_t *window, ui_menu_bar_t **rmbar)
85{
86 ui_menu_bar_t *mbar;
87 errno_t rc;
88
89 mbar = calloc(1, sizeof(ui_menu_bar_t));
90 if (mbar == NULL)
91 return ENOMEM;
92
93 rc = ui_control_new(&ui_menu_bar_ops, (void *) mbar, &mbar->control);
94 if (rc != EOK) {
95 free(mbar);
96 return rc;
97 }
98
99 mbar->ui = ui;
100 mbar->window = window;
101 list_initialize(&mbar->menudds);
102
103 if (window->mbar == NULL)
104 window->mbar = mbar;
105
106 *rmbar = mbar;
107 return EOK;
108}
109
110/** Destroy menu bar
111 *
112 * @param mbar Menu bar or @c NULL
113 */
114void ui_menu_bar_destroy(ui_menu_bar_t *mbar)
115{
116 ui_menu_dd_t *mdd;
117
118 if (mbar == NULL)
119 return;
120
121 if (mbar->window->mbar == mbar)
122 mbar->window->mbar = NULL;
123
124 /* Destroy menu drop-downs */
125 mdd = ui_menu_dd_first(mbar);
126 while (mdd != NULL) {
127 ui_menu_dd_destroy(mdd);
128 mdd = ui_menu_dd_first(mbar);
129 }
130
131 ui_control_delete(mbar->control);
132 free(mbar);
133}
134
135/** Get base control from menu bar.
136 *
137 * @param mbar Menu bar
138 * @return Control
139 */
140ui_control_t *ui_menu_bar_ctl(ui_menu_bar_t *mbar)
141{
142 return mbar->control;
143}
144
145/** Set menu bar rectangle.
146 *
147 * @param mbar Menu bar
148 * @param rect New menu bar rectangle
149 */
150void ui_menu_bar_set_rect(ui_menu_bar_t *mbar, gfx_rect_t *rect)
151{
152 mbar->rect = *rect;
153}
154
155/** Paint menu bar.
156 *
157 * @param mbar Menu bar
158 * @return EOK on success or an error code
159 */
160errno_t ui_menu_bar_paint(ui_menu_bar_t *mbar)
161{
162 ui_resource_t *res;
163 ui_text_fmt_t fmt;
164 gfx_coord2_t pos;
165 gfx_coord2_t tpos;
166 gfx_rect_t rect;
167 gfx_color_t *bg_color;
168 ui_menu_dd_t *mdd;
169 const char *caption;
170 gfx_coord_t width;
171 gfx_coord_t hpad;
172 gfx_coord_t vpad;
173 errno_t rc;
174
175 res = ui_window_get_res(mbar->window);
176
177 /* Paint menu bar background */
178
179 rc = gfx_set_color(res->gc, res->wnd_face_color);
180 if (rc != EOK)
181 goto error;
182
183 rc = gfx_fill_rect(res->gc, &mbar->rect);
184 if (rc != EOK)
185 goto error;
186
187 if (res->textmode) {
188 hpad = menubar_hpad_text;
189 vpad = menubar_vpad_text;
190 } else {
191 hpad = menubar_hpad;
192 vpad = menubar_vpad;
193 }
194
195 pos = mbar->rect.p0;
196
197 ui_text_fmt_init(&fmt);
198 fmt.font = res->font;
199 fmt.halign = gfx_halign_left;
200 fmt.valign = gfx_valign_top;
201
202 mdd = ui_menu_dd_first(mbar);
203 while (mdd != NULL) {
204 caption = ui_menu_dd_caption(mdd);
205 width = ui_text_width(res->font, caption) + 2 * hpad;
206 tpos.x = pos.x + hpad;
207 tpos.y = pos.y + vpad;
208
209 rect.p0 = pos;
210 rect.p1.x = rect.p0.x + width;
211 rect.p1.y = mbar->rect.p1.y;
212
213 if (mdd == mbar->selected) {
214 fmt.color = res->wnd_sel_text_color;
215 fmt.hgl_color = res->wnd_sel_text_hgl_color;
216 bg_color = res->wnd_sel_text_bg_color;
217 } else {
218 fmt.color = res->wnd_text_color;
219 fmt.hgl_color = res->wnd_text_hgl_color;
220 bg_color = res->wnd_face_color;
221 }
222
223 rc = gfx_set_color(res->gc, bg_color);
224 if (rc != EOK)
225 goto error;
226
227 rc = gfx_fill_rect(res->gc, &rect);
228 if (rc != EOK)
229 goto error;
230
231 rc = ui_paint_text(&tpos, &fmt, caption);
232 if (rc != EOK)
233 goto error;
234
235 pos.x += width;
236 mdd = ui_menu_dd_next(mdd);
237 }
238
239 rc = gfx_update(res->gc);
240 if (rc != EOK)
241 goto error;
242
243 return EOK;
244error:
245 return rc;
246}
247
248/** Select or deselect menu from menu bar.
249 *
250 * Select @a menu. If @a menu is @c NULL or it is already selected,
251 * then select none.
252 *
253 * @param mbar Menu bar
254 * @param mdd Menu drop-down to select (or deselect if selected) or @c NULL
255 * @param openup Open menu even if not currently open
256 * @param idev_id Input device ID associated with the selecting seat
257 */
258void ui_menu_bar_select(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd, bool openup,
259 sysarg_t idev_id)
260{
261 ui_menu_dd_t *old_mdd;
262 gfx_rect_t rect;
263 bool was_open;
264
265 old_mdd = mbar->selected;
266
267 mbar->selected = mdd;
268
269 /* Close previously open menu drop-down */
270 if (old_mdd != NULL && ui_menu_dd_is_open(old_mdd)) {
271 was_open = true;
272 (void) ui_menu_dd_close(old_mdd);
273 } else {
274 was_open = false;
275 }
276
277 (void) ui_menu_bar_paint(mbar);
278
279 if (mbar->selected != NULL) {
280 ui_menu_bar_entry_rect(mbar, mbar->selected, &rect);
281 if (openup || was_open) {
282 /*
283 * Open the newly selected menu drop-down if either
284 * the old menu drop-down was open or @a openup was
285 * specified.
286 */
287 (void) ui_menu_dd_open(mbar->selected, &rect, idev_id);
288 }
289
290 mbar->active = true;
291 } else {
292 mbar->active = false;
293 }
294}
295
296/** Select first drop-down.
297 *
298 * @param mbar Menu bar
299 * @param openup @c true to open drop-down if it was not open
300 * @param idev_id Input device ID
301 */
302void ui_menu_bar_select_first(ui_menu_bar_t *mbar, bool openup,
303 sysarg_t idev_id)
304{
305 ui_menu_dd_t *mdd;
306
307 mdd = ui_menu_dd_first(mbar);
308 ui_menu_bar_select(mbar, mdd, openup, idev_id);
309}
310
311/** Select last drop-down.
312 *
313 * @param mbar Menu bar
314 * @param openup @c true to open drop-down if it was not open
315 * @param idev_id Input device ID
316 */
317void ui_menu_bar_select_last(ui_menu_bar_t *mbar, bool openup,
318 sysarg_t idev_id)
319{
320 ui_menu_dd_t *mdd;
321
322 mdd = ui_menu_dd_last(mbar);
323 ui_menu_bar_select(mbar, mdd, openup, idev_id);
324}
325
326/** Select system menu.
327 *
328 * @param mbar Menu bar
329 * @param openup @c true to open drop-down if it was not open
330 * @param idev_id Input device ID
331 */
332void ui_menu_bar_select_sysmenu(ui_menu_bar_t *mbar, bool openup,
333 sysarg_t idev_id)
334{
335 ui_wdecor_sysmenu_hdl_set_active(mbar->window->wdecor, true);
336
337 if (openup)
338 ui_window_send_sysmenu(mbar->window, idev_id);
339}
340
341/** Move one entry left.
342 *
343 * If the selected menu is open, the newly selected menu will be open
344 * as well. If we are already at the first entry, we wrap around.
345 *
346 * @param mbar Menu bar
347 * @param idev_id Input device ID
348 */
349void ui_menu_bar_left(ui_menu_bar_t *mbar, sysarg_t idev_id)
350{
351 ui_menu_dd_t *nmdd;
352 bool sel_sysmenu = false;
353 bool was_open;
354
355 if (mbar->selected == NULL)
356 return;
357
358 nmdd = ui_menu_dd_prev(mbar->selected);
359 if (nmdd == NULL) {
360 if ((mbar->window->wdecor->style & ui_wds_sysmenu_hdl) != 0) {
361 sel_sysmenu = true;
362 } else {
363 nmdd = ui_menu_dd_last(mbar);
364 }
365 }
366
367 was_open = mbar->selected != NULL &&
368 ui_menu_dd_is_open(mbar->selected);
369
370 if (nmdd != mbar->selected)
371 ui_menu_bar_select(mbar, nmdd, false, idev_id);
372
373 /*
374 * Only open sysmenu *after* closing the previous menu, avoid
375 * having multiple popup windows at the same time.
376 */
377 if (sel_sysmenu)
378 ui_menu_bar_select_sysmenu(mbar, was_open, idev_id);
379}
380
381/** Move one entry right.
382 *
383 * If the selected menu is open, the newly selected menu will be open
384 * as well. If we are already at the last entry, we wrap around.
385 *
386 * @param mbar Menu bar
387 * @param idev_id Input device ID
388 */
389void ui_menu_bar_right(ui_menu_bar_t *mbar, sysarg_t idev_id)
390{
391 ui_menu_dd_t *nmdd;
392 bool sel_sysmenu = false;
393 bool was_open;
394
395 if (mbar->selected == NULL)
396 return;
397
398 nmdd = ui_menu_dd_next(mbar->selected);
399 if (nmdd == NULL) {
400 if ((mbar->window->wdecor->style & ui_wds_sysmenu_hdl) != 0) {
401 sel_sysmenu = true;
402 } else {
403 nmdd = ui_menu_dd_first(mbar);
404 }
405 }
406
407 was_open = mbar->selected != NULL &&
408 ui_menu_dd_is_open(mbar->selected);
409
410 if (nmdd != mbar->selected)
411 ui_menu_bar_select(mbar, nmdd, false, idev_id);
412
413 /*
414 * Only open sysmenu *after* closing the previous menu, avoid
415 * having multiple popup windows at the same time.
416 */
417 if (sel_sysmenu)
418 ui_menu_bar_select_sysmenu(mbar, was_open, idev_id);
419}
420
421/** Handle menu bar key press without modifiers.
422 *
423 * @param mbar Menu bar
424 * @param kbd_event Keyboard event
425 * @return @c ui_claimed iff the event is claimed
426 */
427ui_evclaim_t ui_menu_bar_key_press_unmod(ui_menu_bar_t *mbar, kbd_event_t *event)
428{
429 gfx_rect_t rect;
430
431 if (event->key == KC_F10) {
432 ui_menu_bar_activate(mbar);
433 return ui_claimed;
434 }
435
436 if (!mbar->active)
437 return ui_unclaimed;
438
439 if (event->key == KC_ESCAPE) {
440 ui_menu_bar_deactivate(mbar);
441 return ui_claimed;
442 }
443
444 if (event->key == KC_LEFT)
445 ui_menu_bar_left(mbar, event->kbd_id);
446
447 if (event->key == KC_RIGHT)
448 ui_menu_bar_right(mbar, event->kbd_id);
449
450 if (event->key == KC_ENTER || event->key == KC_DOWN) {
451 if (mbar->selected != NULL &&
452 !ui_menu_dd_is_open(mbar->selected)) {
453 ui_menu_bar_entry_rect(mbar, mbar->selected,
454 &rect);
455 ui_menu_dd_open(mbar->selected, &rect, event->kbd_id);
456 }
457
458 return ui_claimed;
459 }
460
461 if (event->c != '\0' && !ui_menu_dd_is_open(mbar->selected)) {
462 /* Check if it is an accelerator. */
463 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
464 }
465
466 return ui_claimed;
467}
468
469/** Handle menu bar keyboard event.
470 *
471 * @param mbar Menu bar
472 * @param kbd_event Keyboard event
473 * @return @c ui_claimed iff the event is claimed
474 */
475ui_evclaim_t ui_menu_bar_kbd_event(ui_menu_bar_t *mbar, kbd_event_t *event)
476{
477 if ((event->mods & KM_ALT) != 0 &&
478 (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0') {
479 /* Check if it is an accelerator. */
480 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
481 }
482
483 if (event->type == KEY_PRESS && (event->mods &
484 (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
485 return ui_menu_bar_key_press_unmod(mbar, event);
486 }
487
488 if (mbar->active)
489 return ui_claimed;
490
491 return ui_unclaimed;
492}
493
494/** Accelerator key press.
495 *
496 * If @a c matches an accelerator key, open the respective menu.
497 *
498 * @param mbar Menu bar
499 * @param c Character
500 * @param kbd_id Keyboard ID
501 */
502void ui_menu_bar_press_accel(ui_menu_bar_t *mbar, char32_t c, sysarg_t kbd_id)
503{
504 ui_menu_dd_t *mdd;
505 char32_t maccel;
506
507 mdd = ui_menu_dd_first(mbar);
508 while (mdd != NULL) {
509 maccel = ui_menu_dd_get_accel(mdd);
510 if ((char32_t)tolower(c) == maccel) {
511 ui_menu_bar_select(mbar, mdd, true, kbd_id);
512 return;
513 }
514
515 mdd = ui_menu_dd_next(mdd);
516 }
517}
518
519/** Handle menu bar position event.
520 *
521 * @param mbar Menu bar
522 * @param pos_event Position event
523 * @return @c ui_claimed iff the event is claimed
524 */
525ui_evclaim_t ui_menu_bar_pos_event(ui_menu_bar_t *mbar, pos_event_t *event)
526{
527 ui_resource_t *res;
528 gfx_coord2_t pos;
529 gfx_rect_t rect;
530 ui_menu_dd_t *mdd;
531 const char *caption;
532 gfx_coord_t width;
533 gfx_coord_t hpad;
534 gfx_coord2_t ppos;
535 sysarg_t pos_id;
536
537 res = ui_window_get_res(mbar->window);
538
539 ppos.x = event->hpos;
540 ppos.y = event->vpos;
541
542 if (res->textmode) {
543 hpad = menubar_hpad_text;
544 } else {
545 hpad = menubar_hpad;
546 }
547
548 pos = mbar->rect.p0;
549 pos_id = event->pos_id;
550
551 mdd = ui_menu_dd_first(mbar);
552 while (mdd != NULL) {
553 caption = ui_menu_dd_caption(mdd);
554 width = ui_text_width(res->font, caption) + 2 * hpad;
555
556 rect.p0 = pos;
557 rect.p1.x = rect.p0.x + width;
558 rect.p1.y = mbar->rect.p1.y;
559
560 /* Check if press is inside menu bar entry */
561 if (event->type == POS_PRESS &&
562 gfx_pix_inside_rect(&ppos, &rect)) {
563 mbar->active = true;
564
565 /* Open the menu, close if already open. */
566 if (mdd == mbar->selected)
567 ui_menu_bar_select(mbar, NULL, false, pos_id);
568 else
569 ui_menu_bar_select(mbar, mdd, true, pos_id);
570
571 return ui_claimed;
572 }
573
574 pos.x += width;
575 mdd = ui_menu_dd_next(mdd);
576 }
577
578 return ui_unclaimed;
579}
580
581/** Handle menu bar position event.
582 *
583 * @param mbar Menu bar
584 * @param mdd Menu drop-down whose entry's rectangle is to be returned
585 * @param rrect Place to store entry rectangle
586 */
587void ui_menu_bar_entry_rect(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd,
588 gfx_rect_t *rrect)
589{
590 ui_resource_t *res;
591 gfx_coord2_t pos;
592 gfx_rect_t rect;
593 ui_menu_dd_t *cur;
594 const char *caption;
595 gfx_coord_t width;
596 gfx_coord_t hpad;
597
598 res = ui_window_get_res(mbar->window);
599
600 if (res->textmode) {
601 hpad = menubar_hpad_text;
602 } else {
603 hpad = menubar_hpad;
604 }
605
606 pos = mbar->rect.p0;
607
608 cur = ui_menu_dd_first(mbar);
609 while (cur != NULL) {
610 caption = ui_menu_dd_caption(cur);
611 width = ui_text_width(res->font, caption) + 2 * hpad;
612
613 rect.p0 = pos;
614 rect.p1.x = rect.p0.x + width;
615 rect.p1.y = mbar->rect.p1.y;
616
617 if (cur == mdd) {
618 *rrect = rect;
619 return;
620 }
621
622 pos.x += width;
623 cur = ui_menu_dd_next(cur);
624 }
625
626 /* We should never get here */
627 assert(false);
628}
629
630/** Activate menu bar.
631 *
632 * @param mbar Menu bar
633 */
634void ui_menu_bar_activate(ui_menu_bar_t *mbar)
635{
636 if (mbar->active)
637 return;
638
639 mbar->active = true;
640 if (mbar->selected == NULL)
641 mbar->selected = ui_menu_dd_first(mbar);
642
643 (void) ui_menu_bar_paint(mbar);
644}
645
646/** Deactivate menu bar.
647 *
648 * @param mbar Menu bar
649 */
650void ui_menu_bar_deactivate(ui_menu_bar_t *mbar)
651{
652 ui_menu_bar_select(mbar, NULL, false, 0);
653}
654
655/** Destroy menu bar control.
656 *
657 * @param arg Argument (ui_menu_bar_t *)
658 */
659void ui_menu_bar_ctl_destroy(void *arg)
660{
661 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
662
663 ui_menu_bar_destroy(mbar);
664}
665
666/** Paint menu bar control.
667 *
668 * @param arg Argument (ui_menu_bar_t *)
669 * @return EOK on success or an error code
670 */
671errno_t ui_menu_bar_ctl_paint(void *arg)
672{
673 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
674
675 return ui_menu_bar_paint(mbar);
676}
677
678/** Handle menu bar control keyboard event.
679 *
680 * @param arg Argument (ui_menu_bar_t *)
681 * @param pos_event Position event
682 * @return @c ui_claimed iff the event is claimed
683 */
684ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *arg, kbd_event_t *event)
685{
686 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
687
688 return ui_menu_bar_kbd_event(mbar, event);
689}
690
691/** Handle menu bar control position event.
692 *
693 * @param arg Argument (ui_menu_bar_t *)
694 * @param pos_event Position event
695 * @return @c ui_claimed iff the event is claimed
696 */
697ui_evclaim_t ui_menu_bar_ctl_pos_event(void *arg, pos_event_t *event)
698{
699 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
700
701 return ui_menu_bar_pos_event(mbar, event);
702}
703
704/** @}
705 */
Note: See TracBrowser for help on using the repository browser.