source: mainline/uspace/lib/ui/src/menubar.c

Last change on this file was ec50d65e, checked in by Jiri Svoboda <jiri@…>, 12 months ago

Editor needs to hide cursor when menu bar is activated

  • Property mode set to 100644
File size: 16.6 KB
Line 
1/*
2 * Copyright (c) 2024 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 *);
68static void ui_menu_bar_activate_ev(ui_menu_bar_t *);
69static void ui_menu_bar_deactivate_ev(ui_menu_bar_t *);
70
71/** Menu bar control ops */
72ui_control_ops_t ui_menu_bar_ops = {
73 .destroy = ui_menu_bar_ctl_destroy,
74 .paint = ui_menu_bar_ctl_paint,
75 .kbd_event = ui_menu_bar_ctl_kbd_event,
76 .pos_event = ui_menu_bar_ctl_pos_event
77};
78
79/** Create new menu bar.
80 *
81 * @param ui UI
82 * @param window Window that will contain the menu bar
83 * @param rmbar Place to store pointer to new menu bar
84 * @return EOK on success, ENOMEM if out of memory
85 */
86errno_t ui_menu_bar_create(ui_t *ui, ui_window_t *window, ui_menu_bar_t **rmbar)
87{
88 ui_menu_bar_t *mbar;
89 errno_t rc;
90
91 mbar = calloc(1, sizeof(ui_menu_bar_t));
92 if (mbar == NULL)
93 return ENOMEM;
94
95 rc = ui_control_new(&ui_menu_bar_ops, (void *) mbar, &mbar->control);
96 if (rc != EOK) {
97 free(mbar);
98 return rc;
99 }
100
101 mbar->ui = ui;
102 mbar->window = window;
103 list_initialize(&mbar->menudds);
104
105 if (window->mbar == NULL)
106 window->mbar = mbar;
107
108 *rmbar = mbar;
109 return EOK;
110}
111
112/** Destroy menu bar
113 *
114 * @param mbar Menu bar or @c NULL
115 */
116void ui_menu_bar_destroy(ui_menu_bar_t *mbar)
117{
118 ui_menu_dd_t *mdd;
119
120 if (mbar == NULL)
121 return;
122
123 if (mbar->window->mbar == mbar)
124 mbar->window->mbar = NULL;
125
126 /* Destroy menu drop-downs */
127 mdd = ui_menu_dd_first(mbar);
128 while (mdd != NULL) {
129 ui_menu_dd_destroy(mdd);
130 mdd = ui_menu_dd_first(mbar);
131 }
132
133 ui_control_delete(mbar->control);
134 free(mbar);
135}
136
137/** Set menu bar callbacks.
138 *
139 * @param mbar Menu bar
140 * @param cb Callbacks
141 * @param arg Callback argument
142 */
143void ui_menu_bar_set_cb(ui_menu_bar_t *mbar, ui_menu_bar_cb_t *cb, void *arg)
144{
145 mbar->cb = cb;
146 mbar->arg = arg;
147}
148
149/** Get base control from menu bar.
150 *
151 * @param mbar Menu bar
152 * @return Control
153 */
154ui_control_t *ui_menu_bar_ctl(ui_menu_bar_t *mbar)
155{
156 return mbar->control;
157}
158
159/** Set menu bar rectangle.
160 *
161 * @param mbar Menu bar
162 * @param rect New menu bar rectangle
163 */
164void ui_menu_bar_set_rect(ui_menu_bar_t *mbar, gfx_rect_t *rect)
165{
166 mbar->rect = *rect;
167}
168
169/** Paint menu bar.
170 *
171 * @param mbar Menu bar
172 * @return EOK on success or an error code
173 */
174errno_t ui_menu_bar_paint(ui_menu_bar_t *mbar)
175{
176 ui_resource_t *res;
177 ui_text_fmt_t fmt;
178 gfx_coord2_t pos;
179 gfx_coord2_t tpos;
180 gfx_rect_t rect;
181 gfx_color_t *bg_color;
182 ui_menu_dd_t *mdd;
183 const char *caption;
184 gfx_coord_t width;
185 gfx_coord_t hpad;
186 gfx_coord_t vpad;
187 errno_t rc;
188
189 res = ui_window_get_res(mbar->window);
190
191 /* Paint menu bar background */
192
193 rc = gfx_set_color(res->gc, res->wnd_face_color);
194 if (rc != EOK)
195 goto error;
196
197 rc = gfx_fill_rect(res->gc, &mbar->rect);
198 if (rc != EOK)
199 goto error;
200
201 if (res->textmode) {
202 hpad = menubar_hpad_text;
203 vpad = menubar_vpad_text;
204 } else {
205 hpad = menubar_hpad;
206 vpad = menubar_vpad;
207 }
208
209 pos = mbar->rect.p0;
210
211 ui_text_fmt_init(&fmt);
212 fmt.font = res->font;
213 fmt.halign = gfx_halign_left;
214 fmt.valign = gfx_valign_top;
215
216 mdd = ui_menu_dd_first(mbar);
217 while (mdd != NULL) {
218 caption = ui_menu_dd_caption(mdd);
219 width = ui_text_width(res->font, caption) + 2 * hpad;
220 tpos.x = pos.x + hpad;
221 tpos.y = pos.y + vpad;
222
223 rect.p0 = pos;
224 rect.p1.x = rect.p0.x + width;
225 rect.p1.y = mbar->rect.p1.y;
226
227 if (mdd == mbar->selected) {
228 fmt.color = res->wnd_sel_text_color;
229 fmt.hgl_color = res->wnd_sel_text_hgl_color;
230 bg_color = res->wnd_sel_text_bg_color;
231 } else {
232 fmt.color = res->wnd_text_color;
233 fmt.hgl_color = res->wnd_text_hgl_color;
234 bg_color = res->wnd_face_color;
235 }
236
237 rc = gfx_set_color(res->gc, bg_color);
238 if (rc != EOK)
239 goto error;
240
241 rc = gfx_fill_rect(res->gc, &rect);
242 if (rc != EOK)
243 goto error;
244
245 rc = ui_paint_text(&tpos, &fmt, caption);
246 if (rc != EOK)
247 goto error;
248
249 pos.x += width;
250 mdd = ui_menu_dd_next(mdd);
251 }
252
253 rc = gfx_update(res->gc);
254 if (rc != EOK)
255 goto error;
256
257 return EOK;
258error:
259 return rc;
260}
261
262/** Select or deselect menu from menu bar.
263 *
264 * Select @a menu. If @a menu is @c NULL or it is already selected,
265 * then select none.
266 *
267 * @param mbar Menu bar
268 * @param mdd Menu drop-down to select (or deselect if selected) or @c NULL
269 * @param openup Open menu even if not currently open
270 * @param idev_id Input device ID associated with the selecting seat
271 */
272void ui_menu_bar_select(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd, bool openup,
273 sysarg_t idev_id)
274{
275 ui_menu_dd_t *old_mdd;
276 gfx_rect_t rect;
277 bool was_open;
278
279 old_mdd = mbar->selected;
280
281 mbar->selected = mdd;
282
283 /* Close previously open menu drop-down */
284 if (old_mdd != NULL && ui_menu_dd_is_open(old_mdd)) {
285 was_open = true;
286 (void) ui_menu_dd_close(old_mdd);
287 } else {
288 was_open = false;
289 }
290
291 (void) ui_menu_bar_paint(mbar);
292
293 if (mbar->selected != NULL) {
294 ui_menu_bar_entry_rect(mbar, mbar->selected, &rect);
295 if (openup || was_open) {
296 /*
297 * Open the newly selected menu drop-down if either
298 * the old menu drop-down was open or @a openup was
299 * specified.
300 */
301 (void) ui_menu_dd_open(mbar->selected, &rect, idev_id);
302 }
303
304 if (!mbar->active)
305 ui_menu_bar_activate_ev(mbar);
306 mbar->active = true;
307 } else {
308 if (mbar->active)
309 ui_menu_bar_deactivate_ev(mbar);
310 mbar->active = false;
311 }
312}
313
314/** Select first drop-down.
315 *
316 * @param mbar Menu bar
317 * @param openup @c true to open drop-down if it was not open
318 * @param idev_id Input device ID
319 */
320void ui_menu_bar_select_first(ui_menu_bar_t *mbar, bool openup,
321 sysarg_t idev_id)
322{
323 ui_menu_dd_t *mdd;
324
325 mdd = ui_menu_dd_first(mbar);
326 ui_menu_bar_select(mbar, mdd, openup, idev_id);
327}
328
329/** Select last drop-down.
330 *
331 * @param mbar Menu bar
332 * @param openup @c true to open drop-down if it was not open
333 * @param idev_id Input device ID
334 */
335void ui_menu_bar_select_last(ui_menu_bar_t *mbar, bool openup,
336 sysarg_t idev_id)
337{
338 ui_menu_dd_t *mdd;
339
340 mdd = ui_menu_dd_last(mbar);
341 ui_menu_bar_select(mbar, mdd, openup, idev_id);
342}
343
344/** Select system menu.
345 *
346 * @param mbar Menu bar
347 * @param openup @c true to open drop-down if it was not open
348 * @param idev_id Input device ID
349 */
350void ui_menu_bar_select_sysmenu(ui_menu_bar_t *mbar, bool openup,
351 sysarg_t idev_id)
352{
353 ui_wdecor_sysmenu_hdl_set_active(mbar->window->wdecor, true);
354
355 if (openup)
356 ui_window_send_sysmenu(mbar->window, idev_id);
357}
358
359/** Move one entry left.
360 *
361 * If the selected menu is open, the newly selected menu will be open
362 * as well. If we are already at the first entry, we wrap around.
363 *
364 * @param mbar Menu bar
365 * @param idev_id Input device ID
366 */
367void ui_menu_bar_left(ui_menu_bar_t *mbar, sysarg_t idev_id)
368{
369 ui_menu_dd_t *nmdd;
370 bool sel_sysmenu = false;
371 bool was_open;
372
373 if (mbar->selected == NULL)
374 return;
375
376 nmdd = ui_menu_dd_prev(mbar->selected);
377 if (nmdd == NULL) {
378 if ((mbar->window->wdecor->style & ui_wds_sysmenu_hdl) != 0) {
379 sel_sysmenu = true;
380 } else {
381 nmdd = ui_menu_dd_last(mbar);
382 }
383 }
384
385 was_open = mbar->selected != NULL &&
386 ui_menu_dd_is_open(mbar->selected);
387
388 if (nmdd != mbar->selected)
389 ui_menu_bar_select(mbar, nmdd, false, idev_id);
390
391 /*
392 * Only open sysmenu *after* closing the previous menu, avoid
393 * having multiple popup windows at the same time.
394 */
395 if (sel_sysmenu)
396 ui_menu_bar_select_sysmenu(mbar, was_open, idev_id);
397}
398
399/** Move one entry right.
400 *
401 * If the selected menu is open, the newly selected menu will be open
402 * as well. If we are already at the last entry, we wrap around.
403 *
404 * @param mbar Menu bar
405 * @param idev_id Input device ID
406 */
407void ui_menu_bar_right(ui_menu_bar_t *mbar, sysarg_t idev_id)
408{
409 ui_menu_dd_t *nmdd;
410 bool sel_sysmenu = false;
411 bool was_open;
412
413 if (mbar->selected == NULL)
414 return;
415
416 nmdd = ui_menu_dd_next(mbar->selected);
417 if (nmdd == NULL) {
418 if ((mbar->window->wdecor->style & ui_wds_sysmenu_hdl) != 0) {
419 sel_sysmenu = true;
420 } else {
421 nmdd = ui_menu_dd_first(mbar);
422 }
423 }
424
425 was_open = mbar->selected != NULL &&
426 ui_menu_dd_is_open(mbar->selected);
427
428 if (nmdd != mbar->selected)
429 ui_menu_bar_select(mbar, nmdd, false, idev_id);
430
431 /*
432 * Only open sysmenu *after* closing the previous menu, avoid
433 * having multiple popup windows at the same time.
434 */
435 if (sel_sysmenu)
436 ui_menu_bar_select_sysmenu(mbar, was_open, idev_id);
437}
438
439/** Handle menu bar key press without modifiers.
440 *
441 * @param mbar Menu bar
442 * @param kbd_event Keyboard event
443 * @return @c ui_claimed iff the event is claimed
444 */
445ui_evclaim_t ui_menu_bar_key_press_unmod(ui_menu_bar_t *mbar, kbd_event_t *event)
446{
447 gfx_rect_t rect;
448
449 if (event->key == KC_F10) {
450 ui_menu_bar_activate(mbar);
451 return ui_claimed;
452 }
453
454 if (!mbar->active)
455 return ui_unclaimed;
456
457 if (event->key == KC_ESCAPE) {
458 ui_menu_bar_deactivate(mbar);
459 return ui_claimed;
460 }
461
462 if (event->key == KC_LEFT)
463 ui_menu_bar_left(mbar, event->kbd_id);
464
465 if (event->key == KC_RIGHT)
466 ui_menu_bar_right(mbar, event->kbd_id);
467
468 if (event->key == KC_ENTER || event->key == KC_DOWN) {
469 if (mbar->selected != NULL &&
470 !ui_menu_dd_is_open(mbar->selected)) {
471 ui_menu_bar_entry_rect(mbar, mbar->selected,
472 &rect);
473 ui_menu_dd_open(mbar->selected, &rect, event->kbd_id);
474 }
475
476 return ui_claimed;
477 }
478
479 if (event->c != '\0' && !ui_menu_dd_is_open(mbar->selected)) {
480 /* Check if it is an accelerator. */
481 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
482 }
483
484 return ui_claimed;
485}
486
487/** Handle menu bar keyboard event.
488 *
489 * @param mbar Menu bar
490 * @param kbd_event Keyboard event
491 * @return @c ui_claimed iff the event is claimed
492 */
493ui_evclaim_t ui_menu_bar_kbd_event(ui_menu_bar_t *mbar, kbd_event_t *event)
494{
495 if ((event->mods & KM_ALT) != 0 &&
496 (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0') {
497 /* Check if it is an accelerator. */
498 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
499 }
500
501 if (event->type == KEY_PRESS && (event->mods &
502 (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
503 return ui_menu_bar_key_press_unmod(mbar, event);
504 }
505
506 if (mbar->active)
507 return ui_claimed;
508
509 return ui_unclaimed;
510}
511
512/** Accelerator key press.
513 *
514 * If @a c matches an accelerator key, open the respective menu.
515 *
516 * @param mbar Menu bar
517 * @param c Character
518 * @param kbd_id Keyboard ID
519 */
520void ui_menu_bar_press_accel(ui_menu_bar_t *mbar, char32_t c, sysarg_t kbd_id)
521{
522 ui_menu_dd_t *mdd;
523 char32_t maccel;
524
525 mdd = ui_menu_dd_first(mbar);
526 while (mdd != NULL) {
527 maccel = ui_menu_dd_get_accel(mdd);
528 if ((char32_t)tolower(c) == maccel) {
529 ui_menu_bar_select(mbar, mdd, true, kbd_id);
530 return;
531 }
532
533 mdd = ui_menu_dd_next(mdd);
534 }
535}
536
537/** Handle menu bar position event.
538 *
539 * @param mbar Menu bar
540 * @param pos_event Position event
541 * @return @c ui_claimed iff the event is claimed
542 */
543ui_evclaim_t ui_menu_bar_pos_event(ui_menu_bar_t *mbar, pos_event_t *event)
544{
545 ui_resource_t *res;
546 gfx_coord2_t pos;
547 gfx_rect_t rect;
548 ui_menu_dd_t *mdd;
549 const char *caption;
550 gfx_coord_t width;
551 gfx_coord_t hpad;
552 gfx_coord2_t ppos;
553 sysarg_t pos_id;
554
555 res = ui_window_get_res(mbar->window);
556
557 ppos.x = event->hpos;
558 ppos.y = event->vpos;
559
560 if (res->textmode) {
561 hpad = menubar_hpad_text;
562 } else {
563 hpad = menubar_hpad;
564 }
565
566 pos = mbar->rect.p0;
567 pos_id = event->pos_id;
568
569 mdd = ui_menu_dd_first(mbar);
570 while (mdd != NULL) {
571 caption = ui_menu_dd_caption(mdd);
572 width = ui_text_width(res->font, caption) + 2 * hpad;
573
574 rect.p0 = pos;
575 rect.p1.x = rect.p0.x + width;
576 rect.p1.y = mbar->rect.p1.y;
577
578 /* Check if press is inside menu bar entry */
579 if (event->type == POS_PRESS &&
580 gfx_pix_inside_rect(&ppos, &rect)) {
581 mbar->active = true;
582
583 /* Open the menu, if not already open. */
584 if (mdd != mbar->selected)
585 ui_menu_bar_select(mbar, mdd, true, pos_id);
586
587 return ui_claimed;
588 }
589
590 pos.x += width;
591 mdd = ui_menu_dd_next(mdd);
592 }
593
594 return ui_unclaimed;
595}
596
597/** Handle menu bar position event.
598 *
599 * @param mbar Menu bar
600 * @param mdd Menu drop-down whose entry's rectangle is to be returned
601 * @param rrect Place to store entry rectangle
602 */
603void ui_menu_bar_entry_rect(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd,
604 gfx_rect_t *rrect)
605{
606 ui_resource_t *res;
607 gfx_coord2_t pos;
608 gfx_rect_t rect;
609 ui_menu_dd_t *cur;
610 const char *caption;
611 gfx_coord_t width;
612 gfx_coord_t hpad;
613
614 res = ui_window_get_res(mbar->window);
615
616 if (res->textmode) {
617 hpad = menubar_hpad_text;
618 } else {
619 hpad = menubar_hpad;
620 }
621
622 pos = mbar->rect.p0;
623
624 cur = ui_menu_dd_first(mbar);
625 while (cur != NULL) {
626 caption = ui_menu_dd_caption(cur);
627 width = ui_text_width(res->font, caption) + 2 * hpad;
628
629 rect.p0 = pos;
630 rect.p1.x = rect.p0.x + width;
631 rect.p1.y = mbar->rect.p1.y;
632
633 if (cur == mdd) {
634 *rrect = rect;
635 return;
636 }
637
638 pos.x += width;
639 cur = ui_menu_dd_next(cur);
640 }
641
642 /* We should never get here */
643 assert(false);
644}
645
646/** Activate menu bar.
647 *
648 * @param mbar Menu bar
649 */
650void ui_menu_bar_activate(ui_menu_bar_t *mbar)
651{
652 if (mbar->active)
653 return;
654
655 mbar->active = true;
656 if (mbar->selected == NULL)
657 mbar->selected = ui_menu_dd_first(mbar);
658
659 (void) ui_menu_bar_paint(mbar);
660 ui_menu_bar_activate_ev(mbar);
661}
662
663/** Deactivate menu bar.
664 *
665 * @param mbar Menu bar
666 */
667void ui_menu_bar_deactivate(ui_menu_bar_t *mbar)
668{
669 ui_menu_bar_select(mbar, NULL, false, 0);
670 ui_menu_bar_deactivate_ev(mbar);
671}
672
673/** Destroy menu bar control.
674 *
675 * @param arg Argument (ui_menu_bar_t *)
676 */
677void ui_menu_bar_ctl_destroy(void *arg)
678{
679 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
680
681 ui_menu_bar_destroy(mbar);
682}
683
684/** Paint menu bar control.
685 *
686 * @param arg Argument (ui_menu_bar_t *)
687 * @return EOK on success or an error code
688 */
689errno_t ui_menu_bar_ctl_paint(void *arg)
690{
691 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
692
693 return ui_menu_bar_paint(mbar);
694}
695
696/** Handle menu bar control keyboard event.
697 *
698 * @param arg Argument (ui_menu_bar_t *)
699 * @param pos_event Position event
700 * @return @c ui_claimed iff the event is claimed
701 */
702ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *arg, kbd_event_t *event)
703{
704 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
705
706 return ui_menu_bar_kbd_event(mbar, event);
707}
708
709/** Handle menu bar control position event.
710 *
711 * @param arg Argument (ui_menu_bar_t *)
712 * @param pos_event Position event
713 * @return @c ui_claimed iff the event is claimed
714 */
715ui_evclaim_t ui_menu_bar_ctl_pos_event(void *arg, pos_event_t *event)
716{
717 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
718
719 return ui_menu_bar_pos_event(mbar, event);
720}
721
722/** Send menu bar activate event.
723 *
724 * @param mbar Menu bar
725 */
726static void ui_menu_bar_activate_ev(ui_menu_bar_t *mbar)
727{
728 if (mbar->cb != NULL && mbar->cb->activate != NULL)
729 mbar->cb->activate(mbar, mbar->arg);
730}
731
732/** Send menu bar deactivate event.
733 *
734 * @param mbar Menu bar
735 */
736static void ui_menu_bar_deactivate_ev(ui_menu_bar_t *mbar)
737{
738 if (mbar->cb != NULL && mbar->cb->deactivate != NULL)
739 mbar->cb->deactivate(mbar, mbar->arg);
740}
741
742/** @}
743 */
Note: See TracBrowser for help on using the repository browser.