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
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/window.h>
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 *);
63static ui_evclaim_t ui_menu_bar_ctl_kbd_event(void *, kbd_event_t *);
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,
70 .kbd_event = ui_menu_bar_ctl_kbd_event,
71 .pos_event = ui_menu_bar_ctl_pos_event
72};
73
74/** Create new menu bar.
75 *
76 * @param ui UI
77 * @param window Window that will contain the menu bar
78 * @param rmbar Place to store pointer to new menu bar
79 * @return EOK on success, ENOMEM if out of memory
80 */
81errno_t ui_menu_bar_create(ui_t *ui, ui_window_t *window, ui_menu_bar_t **rmbar)
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
96 mbar->ui = ui;
97 mbar->window = window;
98 list_initialize(&mbar->menudds);
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{
109 ui_menu_dd_t *mdd;
110
111 if (mbar == NULL)
112 return;
113
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);
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{
152 ui_resource_t *res;
153 ui_text_fmt_t fmt;
154 gfx_coord2_t pos;
155 gfx_coord2_t tpos;
156 gfx_rect_t rect;
157 gfx_color_t *bg_color;
158 ui_menu_dd_t *mdd;
159 const char *caption;
160 gfx_coord_t width;
161 gfx_coord_t hpad;
162 gfx_coord_t vpad;
163 errno_t rc;
164
165 res = ui_window_get_res(mbar->window);
166
167 /* Paint menu bar background */
168
169 rc = gfx_set_color(res->gc, res->wnd_face_color);
170 if (rc != EOK)
171 goto error;
172
173 rc = gfx_fill_rect(res->gc, &mbar->rect);
174 if (rc != EOK)
175 goto error;
176
177 if (res->textmode) {
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
187 ui_text_fmt_init(&fmt);
188 fmt.font = res->font;
189 fmt.halign = gfx_halign_left;
190 fmt.valign = gfx_valign_top;
191
192 mdd = ui_menu_dd_first(mbar);
193 while (mdd != NULL) {
194 caption = ui_menu_dd_caption(mdd);
195 width = ui_text_width(res->font, caption) + 2 * hpad;
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
203 if (mdd == mbar->selected) {
204 fmt.color = res->wnd_sel_text_color;
205 fmt.hgl_color = res->wnd_sel_text_hgl_color;
206 bg_color = res->wnd_sel_text_bg_color;
207 } else {
208 fmt.color = res->wnd_text_color;
209 fmt.hgl_color = res->wnd_text_hgl_color;
210 bg_color = res->wnd_face_color;
211 }
212
213 rc = gfx_set_color(res->gc, bg_color);
214 if (rc != EOK)
215 goto error;
216
217 rc = gfx_fill_rect(res->gc, &rect);
218 if (rc != EOK)
219 goto error;
220
221 rc = ui_paint_text(&tpos, &fmt, caption);
222 if (rc != EOK)
223 goto error;
224
225 pos.x += width;
226 mdd = ui_menu_dd_next(mdd);
227 }
228
229 rc = gfx_update(res->gc);
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
244 * @param mdd Menu drop-down to select (or deselect if selected) or @c NULL
245 * @param openup Open menu even if not currently open
246 * @param idev_id Input device ID associated with the selecting seat
247 */
248void ui_menu_bar_select(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd, bool openup,
249 sysarg_t idev_id)
250{
251 ui_menu_dd_t *old_mdd;
252 gfx_rect_t rect;
253 bool was_open;
254
255 old_mdd = mbar->selected;
256
257 mbar->selected = mdd;
258
259 /* Close previously open menu drop-down */
260 if (old_mdd != NULL && ui_menu_dd_is_open(old_mdd)) {
261 was_open = true;
262 (void) ui_menu_dd_close(old_mdd);
263 } else {
264 was_open = false;
265 }
266
267 (void) ui_menu_bar_paint(mbar);
268
269 if (mbar->selected != NULL) {
270 ui_menu_bar_entry_rect(mbar, mbar->selected, &rect);
271 if (openup || was_open) {
272 /*
273 * Open the newly selected menu drop-down if either
274 * the old menu drop-down was open or @a openup was
275 * specified.
276 */
277 (void) ui_menu_dd_open(mbar->selected, &rect, idev_id);
278 }
279 }
280}
281
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
288 * @param idev_id Input device ID
289 */
290void ui_menu_bar_left(ui_menu_bar_t *mbar, sysarg_t idev_id)
291{
292 ui_menu_dd_t *nmdd;
293
294 if (mbar->selected == NULL)
295 return;
296
297 nmdd = ui_menu_dd_prev(mbar->selected);
298 if (nmdd == NULL)
299 nmdd = ui_menu_dd_last(mbar);
300
301 if (nmdd != mbar->selected)
302 ui_menu_bar_select(mbar, nmdd, false, idev_id);
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
311 * @param idev_id Input device ID
312 */
313void ui_menu_bar_right(ui_menu_bar_t *mbar, sysarg_t idev_id)
314{
315 ui_menu_dd_t *nmdd;
316
317 if (mbar->selected == NULL)
318 return;
319
320 nmdd = ui_menu_dd_next(mbar->selected);
321 if (nmdd == NULL)
322 nmdd = ui_menu_dd_first(mbar);
323
324 if (nmdd != mbar->selected)
325 ui_menu_bar_select(mbar, nmdd, false, idev_id);
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)
352 ui_menu_bar_left(mbar, event->kbd_id);
353
354 if (event->key == KC_RIGHT)
355 ui_menu_bar_right(mbar, event->kbd_id);
356
357 if (event->key == KC_ENTER || event->key == KC_DOWN) {
358 if (mbar->selected != NULL &&
359 !ui_menu_dd_is_open(mbar->selected)) {
360 ui_menu_bar_entry_rect(mbar, mbar->selected,
361 &rect);
362 ui_menu_dd_open(mbar->selected, &rect, event->kbd_id);
363 }
364
365 return ui_claimed;
366 }
367
368 if (event->c != '\0' && !ui_menu_dd_is_open(mbar->selected)) {
369 /* Check if it is an accelerator. */
370 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
371 }
372
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{
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. */
387 ui_menu_bar_press_accel(mbar, event->c, event->kbd_id);
388 }
389
390 if (event->type == KEY_PRESS && (event->mods &
391 (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
392 return ui_menu_bar_key_press_unmod(mbar, event);
393 }
394
395 if (mbar->active)
396 return ui_claimed;
397
398 return ui_unclaimed;
399}
400
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
407 * @param kbd_id Keyboard ID
408 */
409void ui_menu_bar_press_accel(ui_menu_bar_t *mbar, char32_t c, sysarg_t kbd_id)
410{
411 ui_menu_dd_t *mdd;
412 char32_t maccel;
413
414 mdd = ui_menu_dd_first(mbar);
415 while (mdd != NULL) {
416 maccel = ui_menu_dd_get_accel(mdd);
417 if ((char32_t)tolower(c) == maccel) {
418 ui_menu_bar_select(mbar, mdd, true, kbd_id);
419 return;
420 }
421
422 mdd = ui_menu_dd_next(mdd);
423 }
424}
425
426/** Handle menu bar position event.
427 *
428 * @param mbar Menu bar
429 * @param pos_event Position event
430 * @return @c ui_claimed iff the event is claimed
431 */
432ui_evclaim_t ui_menu_bar_pos_event(ui_menu_bar_t *mbar, pos_event_t *event)
433{
434 ui_resource_t *res;
435 gfx_coord2_t pos;
436 gfx_rect_t rect;
437 ui_menu_dd_t *mdd;
438 const char *caption;
439 gfx_coord_t width;
440 gfx_coord_t hpad;
441 gfx_coord2_t ppos;
442 sysarg_t pos_id;
443
444 res = ui_window_get_res(mbar->window);
445
446 ppos.x = event->hpos;
447 ppos.y = event->vpos;
448
449 if (res->textmode) {
450 hpad = menubar_hpad_text;
451 } else {
452 hpad = menubar_hpad;
453 }
454
455 pos = mbar->rect.p0;
456 pos_id = event->pos_id;
457
458 mdd = ui_menu_dd_first(mbar);
459 while (mdd != NULL) {
460 caption = ui_menu_dd_caption(mdd);
461 width = ui_text_width(res->font, caption) + 2 * hpad;
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 */
468 if (event->type == POS_PRESS &&
469 gfx_pix_inside_rect(&ppos, &rect)) {
470 mbar->active = true;
471
472 /* Open the menu, close if already open. */
473 if (mdd == mbar->selected)
474 ui_menu_bar_select(mbar, NULL, false, pos_id);
475 else
476 ui_menu_bar_select(mbar, mdd, true, pos_id);
477
478 return ui_claimed;
479 }
480
481 pos.x += width;
482 mdd = ui_menu_dd_next(mdd);
483 }
484
485 return ui_unclaimed;
486}
487
488/** Handle menu bar position event.
489 *
490 * @param mbar Menu bar
491 * @param mdd Menu drop-down whose entry's rectangle is to be returned
492 * @param rrect Place to store entry rectangle
493 */
494void ui_menu_bar_entry_rect(ui_menu_bar_t *mbar, ui_menu_dd_t *mdd,
495 gfx_rect_t *rrect)
496{
497 ui_resource_t *res;
498 gfx_coord2_t pos;
499 gfx_rect_t rect;
500 ui_menu_dd_t *cur;
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
515 cur = ui_menu_dd_first(mbar);
516 while (cur != NULL) {
517 caption = ui_menu_dd_caption(cur);
518 width = ui_text_width(res->font, caption) + 2 * hpad;
519
520 rect.p0 = pos;
521 rect.p1.x = rect.p0.x + width;
522 rect.p1.y = mbar->rect.p1.y;
523
524 if (cur == mdd) {
525 *rrect = rect;
526 return;
527 }
528
529 pos.x += width;
530 cur = ui_menu_dd_next(cur);
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)
548 mbar->selected = ui_menu_dd_first(mbar);
549
550 (void) ui_menu_bar_paint(mbar);
551}
552
553void ui_menu_bar_deactivate(ui_menu_bar_t *mbar)
554{
555 ui_menu_bar_select(mbar, NULL, false, 0);
556 mbar->active = false;
557}
558
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
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
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.