source: mainline/uspace/lib/ui/src/menubar.c@ 4c6fd56

ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 4c6fd56 was 61643c8, checked in by Jiri Svoboda <jiri@…>, 2 years ago

Fix accelerator keys not working when Caps Lock is enabled

  • Property mode set to 100644
File size: 13.6 KB
RevLine 
[214aefb]1/*
[5d380b6]2 * Copyright (c) 2023 Jiri Svoboda
[214aefb]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>
[61643c8]37#include <ctype.h>
[214aefb]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>
[46bd63c9]49#include <ui/menudd.h>
[c68c18b9]50#include <ui/window.h>
[214aefb]51#include "../private/menubar.h"
52#include "../private/resource.h"
53
54enum {
55 menubar_hpad = 4,
56 menubar_vpad = 4,
57 menubar_hpad_text = 1,
58 menubar_vpad_text = 0
59};
60
61static void ui_menu_bar_ctl_destroy(void *);
62static errno_t ui_menu_bar_ctl_paint(void *);
[59768c7]63static ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *, kbd_event_t *);
[214aefb]64static ui_evclaim_t ui_menu_bar_ctl_pos_event(void *, pos_event_t *);
65
66/** Menu bar control ops */
67ui_control_ops_t ui_menu_bar_ops = {
68 .destroy = ui_menu_bar_ctl_destroy,
69 .paint = ui_menu_bar_ctl_paint,
[59768c7]70 .kbd_event = ui_menu_bar_ctl_kbd_event,
71 .pos_event = ui_menu_bar_ctl_pos_event
[214aefb]72};
73
74/** Create new menu bar.
75 *
[3c8c580]76 * @param ui UI
[c68c18b9]77 * @param window Window that will contain the menu bar
[214aefb]78 * @param rmbar Place to store pointer to new menu bar
79 * @return EOK on success, ENOMEM if out of memory
80 */
[c68c18b9]81errno_t ui_menu_bar_create(ui_t *ui, ui_window_t *window, ui_menu_bar_t **rmbar)
[214aefb]82{
83 ui_menu_bar_t *mbar;
84 errno_t rc;
85
86 mbar = calloc(1, sizeof(ui_menu_bar_t));
87 if (mbar == NULL)
88 return ENOMEM;
89
90 rc = ui_control_new(&ui_menu_bar_ops, (void *) mbar, &mbar->control);
91 if (rc != EOK) {
92 free(mbar);
93 return rc;
94 }
95
[3c8c580]96 mbar->ui = ui;
[c68c18b9]97 mbar->window = window;
[46bd63c9]98 list_initialize(&mbar->menudds);
[214aefb]99 *rmbar = mbar;
100 return EOK;
101}
102
103/** Destroy menu bar
104 *
105 * @param mbar Menu bar or @c NULL
106 */
107void ui_menu_bar_destroy(ui_menu_bar_t *mbar)
108{
[46bd63c9]109 ui_menu_dd_t *mdd;
[214aefb]110
111 if (mbar == NULL)
112 return;
113
[46bd63c9]114 /* Destroy menu drop-downs */
115 mdd = ui_menu_dd_first(mbar);
116 while (mdd != NULL) {
117 ui_menu_dd_destroy(mdd);
118 mdd = ui_menu_dd_first(mbar);
[214aefb]119 }
120
121 ui_control_delete(mbar->control);
122 free(mbar);
123}
124
125/** Get base control from menu bar.
126 *
127 * @param mbar Menu bar
128 * @return Control
129 */
130ui_control_t *ui_menu_bar_ctl(ui_menu_bar_t *mbar)
131{
132 return mbar->control;
133}
134
135/** Set menu bar rectangle.
136 *
137 * @param mbar Menu bar
138 * @param rect New menu bar rectangle
139 */
140void ui_menu_bar_set_rect(ui_menu_bar_t *mbar, gfx_rect_t *rect)
141{
142 mbar->rect = *rect;
143}
144
145/** Paint menu bar.
146 *
147 * @param mbar Menu bar
148 * @return EOK on success or an error code
149 */
150errno_t ui_menu_bar_paint(ui_menu_bar_t *mbar)
151{
[c68c18b9]152 ui_resource_t *res;
[ca2680d]153 ui_text_fmt_t fmt;
[214aefb]154 gfx_coord2_t pos;
155 gfx_coord2_t tpos;
156 gfx_rect_t rect;
157 gfx_color_t *bg_color;
[46bd63c9]158 ui_menu_dd_t *mdd;
[214aefb]159 const char *caption;
160 gfx_coord_t width;
161 gfx_coord_t hpad;
162 gfx_coord_t vpad;
163 errno_t rc;
164
[c68c18b9]165 res = ui_window_get_res(mbar->window);
166
[214aefb]167 /* Paint menu bar background */
168
[c68c18b9]169 rc = gfx_set_color(res->gc, res->wnd_face_color);
[214aefb]170 if (rc != EOK)
171 goto error;
172
[c68c18b9]173 rc = gfx_fill_rect(res->gc, &mbar->rect);
[214aefb]174 if (rc != EOK)
175 goto error;
176
[c68c18b9]177 if (res->textmode) {
[214aefb]178 hpad = menubar_hpad_text;
179 vpad = menubar_vpad_text;
180 } else {
181 hpad = menubar_hpad;
182 vpad = menubar_vpad;
183 }
184
185 pos = mbar->rect.p0;
186
[ca2680d]187 ui_text_fmt_init(&fmt);
[4583015]188 fmt.font = res->font;
[214aefb]189 fmt.halign = gfx_halign_left;
190 fmt.valign = gfx_valign_top;
191
[46bd63c9]192 mdd = ui_menu_dd_first(mbar);
193 while (mdd != NULL) {
194 caption = ui_menu_dd_caption(mdd);
[ca2680d]195 width = ui_text_width(res->font, caption) + 2 * hpad;
[214aefb]196 tpos.x = pos.x + hpad;
197 tpos.y = pos.y + vpad;
198
199 rect.p0 = pos;
200 rect.p1.x = rect.p0.x + width;
201 rect.p1.y = mbar->rect.p1.y;
202
[46bd63c9]203 if (mdd == mbar->selected) {
[c68c18b9]204 fmt.color = res->wnd_sel_text_color;
[ca2680d]205 fmt.hgl_color = res->wnd_sel_text_hgl_color;
[c68c18b9]206 bg_color = res->wnd_sel_text_bg_color;
[214aefb]207 } else {
[c68c18b9]208 fmt.color = res->wnd_text_color;
[ca2680d]209 fmt.hgl_color = res->wnd_text_hgl_color;
[c68c18b9]210 bg_color = res->wnd_face_color;
[214aefb]211 }
212
[c68c18b9]213 rc = gfx_set_color(res->gc, bg_color);
[214aefb]214 if (rc != EOK)
215 goto error;
216
[c68c18b9]217 rc = gfx_fill_rect(res->gc, &rect);
[214aefb]218 if (rc != EOK)
219 goto error;
220
[ca2680d]221 rc = ui_paint_text(&tpos, &fmt, caption);
[214aefb]222 if (rc != EOK)
223 goto error;
224
225 pos.x += width;
[46bd63c9]226 mdd = ui_menu_dd_next(mdd);
[214aefb]227 }
228
[c68c18b9]229 rc = gfx_update(res->gc);
[214aefb]230 if (rc != EOK)
231 goto error;
232
233 return EOK;
234error:
235 return rc;
236}
237
238/** Select or deselect menu from menu bar.
239 *
240 * Select @a menu. If @a menu is @c NULL or it is already selected,
241 * then select none.
242 *
243 * @param mbar Menu bar
[46bd63c9]244 * @param mdd Menu drop-down to select (or deselect if selected) or @c NULL
[59768c7]245 * @param openup Open menu even if not currently open
[5d380b6]246 * @param idev_id Input device ID associated with the selecting seat
[214aefb]247 */
[46bd63c9]248void ui_menu_bar_select(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd, bool openup,
[5d380b6]249 sysarg_t idev_id)
[214aefb]250{
[46bd63c9]251 ui_menu_dd_t *old_mdd;
[59768c7]252 gfx_rect_t rect;
253 bool was_open;
[214aefb]254
[46bd63c9]255 old_mdd = mbar->selected;
[214aefb]256
[46bd63c9]257 mbar->selected = mdd;
[214aefb]258
[46bd63c9]259 /* Close previously open menu drop-down */
260 if (old_mdd != NULL && ui_menu_dd_is_open(old_mdd)) {
[59768c7]261 was_open = true;
[46bd63c9]262 (void) ui_menu_dd_close(old_mdd);
[59768c7]263 } else {
264 was_open = false;
265 }
[214aefb]266
267 (void) ui_menu_bar_paint(mbar);
268
269 if (mbar->selected != NULL) {
[59768c7]270 ui_menu_bar_entry_rect(mbar, mbar->selected, &rect);
271 if (openup || was_open) {
272 /*
[46bd63c9]273 * Open the newly selected menu drop-down if either
274 * the old menu drop-down was open or @a openup was
[59768c7]275 * specified.
276 */
[46bd63c9]277 (void) ui_menu_dd_open(mbar->selected, &rect, idev_id);
[59768c7]278 }
[214aefb]279 }
280}
281
[59768c7]282/** Move one entry left.
283 *
284 * If the selected menu is open, the newly selected menu will be open
285 * as well. If we are already at the first entry, we wrap around.
286 *
287 * @param mbar Menu bar
[5d380b6]288 * @param idev_id Input device ID
[59768c7]289 */
[5d380b6]290void ui_menu_bar_left(ui_menu_bar_t *mbar, sysarg_t idev_id)
[59768c7]291{
[46bd63c9]292 ui_menu_dd_t *nmdd;
[59768c7]293
294 if (mbar->selected == NULL)
295 return;
296
[46bd63c9]297 nmdd = ui_menu_dd_prev(mbar->selected);
298 if (nmdd == NULL)
299 nmdd = ui_menu_dd_last(mbar);
[59768c7]300
[46bd63c9]301 if (nmdd != mbar->selected)
302 ui_menu_bar_select(mbar, nmdd, false, idev_id);
[59768c7]303}
304
305/** Move one entry right.
306 *
307 * If the selected menu is open, the newly selected menu will be open
308 * as well. If we are already at the last entry, we wrap around.
309 *
310 * @param mbar Menu bar
[5d380b6]311 * @param idev_id Input device ID
[59768c7]312 */
[5d380b6]313void ui_menu_bar_right(ui_menu_bar_t *mbar, sysarg_t idev_id)
[59768c7]314{
[46bd63c9]315 ui_menu_dd_t *nmdd;
[59768c7]316
317 if (mbar->selected == NULL)
318 return;
319
[46bd63c9]320 nmdd = ui_menu_dd_next(mbar->selected);
321 if (nmdd == NULL)
322 nmdd = ui_menu_dd_first(mbar);
[59768c7]323
[46bd63c9]324 if (nmdd != mbar->selected)
325 ui_menu_bar_select(mbar, nmdd, false, idev_id);
[59768c7]326}
327
328/** Handle menu bar key press without modifiers.
329 *
330 * @param mbar Menu bar
331 * @param kbd_event Keyboard event
332 * @return @c ui_claimed iff the event is claimed
333 */
334ui_evclaim_t ui_menu_bar_key_press_unmod(ui_menu_bar_t *mbar, kbd_event_t *event)
335{
336 gfx_rect_t rect;
337
338 if (event->key == KC_F10) {
339 ui_menu_bar_activate(mbar);
340 return ui_claimed;
341 }
342
343 if (!mbar->active)
344 return ui_unclaimed;
345
346 if (event->key == KC_ESCAPE) {
347 ui_menu_bar_deactivate(mbar);
348 return ui_claimed;
349 }
350
351 if (event->key == KC_LEFT)
[5d380b6]352 ui_menu_bar_left(mbar, event->kbd_id);
[59768c7]353
354 if (event->key == KC_RIGHT)
[5d380b6]355 ui_menu_bar_right(mbar, event->kbd_id);
[59768c7]356
357 if (event->key == KC_ENTER || event->key == KC_DOWN) {
[46bd63c9]358 if (mbar->selected != NULL &&
359 !ui_menu_dd_is_open(mbar->selected)) {
[59768c7]360 ui_menu_bar_entry_rect(mbar, mbar->selected,
361 &rect);
[46bd63c9]362 ui_menu_dd_open(mbar->selected, &rect, event->kbd_id);
[59768c7]363 }
364
365 return ui_claimed;
366 }
367
[46bd63c9]368 if (event->c != '\0' && !ui_menu_dd_is_open(mbar->selected)) {
[96c6a00]369 /* Check if it is an accelerator. */
[5d380b6]370 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
[96c6a00]371 }
372
[59768c7]373 return ui_claimed;
374}
375
376/** Handle menu bar keyboard event.
377 *
378 * @param mbar Menu bar
379 * @param kbd_event Keyboard event
380 * @return @c ui_claimed iff the event is claimed
381 */
382ui_evclaim_t ui_menu_bar_kbd_event(ui_menu_bar_t *mbar, kbd_event_t *event)
383{
[b3b48f4]384 if ((event->mods & KM_ALT) != 0 &&
385 (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0') {
386 /* Check if it is an accelerator. */
[5d380b6]387 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
[b3b48f4]388 }
389
[59768c7]390 if (event->type == KEY_PRESS && (event->mods &
391 (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
[5de852c]392 return ui_menu_bar_key_press_unmod(mbar, event);
[59768c7]393 }
394
[5de852c]395 if (mbar->active)
396 return ui_claimed;
397
398 return ui_unclaimed;
[59768c7]399}
400
[b3b48f4]401/** Accelerator key press.
402 *
403 * If @a c matches an accelerator key, open the respective menu.
404 *
405 * @param mbar Menu bar
406 * @param c Character
[5d380b6]407 * @param kbd_id Keyboard ID
[b3b48f4]408 */
[5d380b6]409void ui_menu_bar_press_accel(ui_menu_bar_t *mbar, char32_t c, sysarg_t kbd_id)
[b3b48f4]410{
[46bd63c9]411 ui_menu_dd_t *mdd;
[b3b48f4]412 char32_t maccel;
413
[46bd63c9]414 mdd = ui_menu_dd_first(mbar);
415 while (mdd != NULL) {
416 maccel = ui_menu_dd_get_accel(mdd);
[61643c8]417 if ((char32_t)tolower(c) == maccel) {
[46bd63c9]418 ui_menu_bar_select(mbar, mdd, true, kbd_id);
[b3b48f4]419 return;
420 }
421
[46bd63c9]422 mdd = ui_menu_dd_next(mdd);
[b3b48f4]423 }
424}
425
[214aefb]426/** Handle menu bar position event.
427 *
428 * @param mbar Menu bar
[0262f16c]429 * @param pos_event Position event
[214aefb]430 * @return @c ui_claimed iff the event is claimed
431 */
[0262f16c]432ui_evclaim_t ui_menu_bar_pos_event(ui_menu_bar_t *mbar, pos_event_t *event)
[214aefb]433{
[c68c18b9]434 ui_resource_t *res;
[214aefb]435 gfx_coord2_t pos;
436 gfx_rect_t rect;
[46bd63c9]437 ui_menu_dd_t *mdd;
[214aefb]438 const char *caption;
439 gfx_coord_t width;
440 gfx_coord_t hpad;
[0262f16c]441 gfx_coord2_t ppos;
[5d380b6]442 sysarg_t pos_id;
[0262f16c]443
[c68c18b9]444 res = ui_window_get_res(mbar->window);
445
[0262f16c]446 ppos.x = event->hpos;
447 ppos.y = event->vpos;
[214aefb]448
[c68c18b9]449 if (res->textmode) {
[214aefb]450 hpad = menubar_hpad_text;
451 } else {
452 hpad = menubar_hpad;
453 }
454
455 pos = mbar->rect.p0;
[5d380b6]456 pos_id = event->pos_id;
[214aefb]457
[46bd63c9]458 mdd = ui_menu_dd_first(mbar);
459 while (mdd != NULL) {
460 caption = ui_menu_dd_caption(mdd);
[ca2680d]461 width = ui_text_width(res->font, caption) + 2 * hpad;
[214aefb]462
463 rect.p0 = pos;
464 rect.p1.x = rect.p0.x + width;
465 rect.p1.y = mbar->rect.p1.y;
466
467 /* Check if press is inside menu bar entry */
[0262f16c]468 if (event->type == POS_PRESS &&
469 gfx_pix_inside_rect(&ppos, &rect)) {
[59768c7]470 mbar->active = true;
[96c6a00]471
472 /* Open the menu, close if already open. */
[46bd63c9]473 if (mdd == mbar->selected)
[5d380b6]474 ui_menu_bar_select(mbar, NULL, false, pos_id);
[96c6a00]475 else
[46bd63c9]476 ui_menu_bar_select(mbar, mdd, true, pos_id);
[96c6a00]477
[214aefb]478 return ui_claimed;
479 }
480
481 pos.x += width;
[46bd63c9]482 mdd = ui_menu_dd_next(mdd);
[214aefb]483 }
484
485 return ui_unclaimed;
486}
487
[59768c7]488/** Handle menu bar position event.
489 *
490 * @param mbar Menu bar
[46bd63c9]491 * @param mdd Menu drop-down whose entry's rectangle is to be returned
[59768c7]492 * @param rrect Place to store entry rectangle
493 */
[46bd63c9]494void ui_menu_bar_entry_rect(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd,
[59768c7]495 gfx_rect_t *rrect)
496{
497 ui_resource_t *res;
498 gfx_coord2_t pos;
499 gfx_rect_t rect;
[46bd63c9]500 ui_menu_dd_t *cur;
[59768c7]501 const char *caption;
502 gfx_coord_t width;
503 gfx_coord_t hpad;
504
505 res = ui_window_get_res(mbar->window);
506
507 if (res->textmode) {
508 hpad = menubar_hpad_text;
509 } else {
510 hpad = menubar_hpad;
511 }
512
513 pos = mbar->rect.p0;
514
[46bd63c9]515 cur = ui_menu_dd_first(mbar);
[59768c7]516 while (cur != NULL) {
[46bd63c9]517 caption = ui_menu_dd_caption(cur);
[ca2680d]518 width = ui_text_width(res->font, caption) + 2 * hpad;
[59768c7]519
520 rect.p0 = pos;
521 rect.p1.x = rect.p0.x + width;
522 rect.p1.y = mbar->rect.p1.y;
523
[46bd63c9]524 if (cur == mdd) {
[59768c7]525 *rrect = rect;
526 return;
527 }
528
529 pos.x += width;
[46bd63c9]530 cur = ui_menu_dd_next(cur);
[59768c7]531 }
532
533 /* We should never get here */
534 assert(false);
535}
536
537/** Activate menu bar.
538 *
539 * @param mbar Menu bar
540 */
541void ui_menu_bar_activate(ui_menu_bar_t *mbar)
542{
543 if (mbar->active)
544 return;
545
546 mbar->active = true;
547 if (mbar->selected == NULL)
[46bd63c9]548 mbar->selected = ui_menu_dd_first(mbar);
[59768c7]549
550 (void) ui_menu_bar_paint(mbar);
551}
552
553void ui_menu_bar_deactivate(ui_menu_bar_t *mbar)
554{
[5d380b6]555 ui_menu_bar_select(mbar, NULL, false, 0);
[59768c7]556 mbar->active = false;
557}
558
[214aefb]559/** Destroy menu bar control.
560 *
561 * @param arg Argument (ui_menu_bar_t *)
562 */
563void ui_menu_bar_ctl_destroy(void *arg)
564{
565 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
566
567 ui_menu_bar_destroy(mbar);
568}
569
570/** Paint menu bar control.
571 *
572 * @param arg Argument (ui_menu_bar_t *)
573 * @return EOK on success or an error code
574 */
575errno_t ui_menu_bar_ctl_paint(void *arg)
576{
577 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
578
579 return ui_menu_bar_paint(mbar);
580}
581
[59768c7]582/** Handle menu bar control keyboard event.
583 *
584 * @param arg Argument (ui_menu_bar_t *)
585 * @param pos_event Position event
586 * @return @c ui_claimed iff the event is claimed
587 */
588ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *arg, kbd_event_t *event)
589{
590 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
591
592 return ui_menu_bar_kbd_event(mbar, event);
593}
594
[214aefb]595/** Handle menu bar control position event.
596 *
597 * @param arg Argument (ui_menu_bar_t *)
598 * @param pos_event Position event
599 * @return @c ui_claimed iff the event is claimed
600 */
601ui_evclaim_t ui_menu_bar_ctl_pos_event(void *arg, pos_event_t *event)
602{
603 ui_menu_bar_t *mbar = (ui_menu_bar_t *) arg;
604
605 return ui_menu_bar_pos_event(mbar, event);
606}
607
608/** @}
609 */
Note: See TracBrowser for help on using the repository browser.