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

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

Use Alt-key accelerators to open menus

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