source: mainline/uspace/lib/ui/src/menuentry.c@ 6a0b2cc

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

Should not activate disabled entry even by mouse click

  • Property mode set to 100644
File size: 14.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 entry
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/accel.h>
46#include <ui/control.h>
47#include <ui/paint.h>
48#include <ui/menubar.h>
49#include <ui/menuentry.h>
50#include <ui/window.h>
51#include "../private/menubar.h"
52#include "../private/menuentry.h"
53#include "../private/menu.h"
54#include "../private/resource.h"
55
56enum {
57 menu_entry_hpad = 4,
58 menu_entry_vpad = 4,
59 menu_entry_column_pad = 8,
60 menu_entry_sep_height = 2,
61 menu_entry_hpad_text = 1,
62 menu_entry_vpad_text = 0,
63 menu_entry_column_pad_text = 2,
64 menu_entry_sep_height_text = 1
65};
66
67/** Create new menu entry.
68 *
69 * @param menu Menu
70 * @param caption Caption
71 * @param shortcut Shotcut key(s) or empty string
72 * @param rmentry Place to store pointer to new menu entry
73 * @return EOK on success, ENOMEM if out of memory
74 */
75errno_t ui_menu_entry_create(ui_menu_t *menu, const char *caption,
76 const char *shortcut, ui_menu_entry_t **rmentry)
77{
78 ui_menu_entry_t *mentry;
79 gfx_coord_t caption_w;
80 gfx_coord_t shortcut_w;
81
82 mentry = calloc(1, sizeof(ui_menu_entry_t));
83 if (mentry == NULL)
84 return ENOMEM;
85
86 mentry->caption = str_dup(caption);
87 if (mentry->caption == NULL) {
88 free(mentry);
89 return ENOMEM;
90 }
91
92 mentry->shortcut = str_dup(shortcut);
93 if (mentry->caption == NULL) {
94 free(mentry->caption);
95 free(mentry);
96 return ENOMEM;
97 }
98
99 mentry->menu = menu;
100 list_append(&mentry->lentries, &menu->entries);
101
102 /* Update accumulated menu entry dimensions */
103 ui_menu_entry_column_widths(mentry, &caption_w, &shortcut_w);
104 if (caption_w > menu->max_caption_w)
105 menu->max_caption_w = caption_w;
106 if (shortcut_w > menu->max_shortcut_w)
107 menu->max_shortcut_w = shortcut_w;
108 menu->total_h += ui_menu_entry_height(mentry);
109
110 *rmentry = mentry;
111 return EOK;
112}
113
114/** Create new separator menu entry.
115 *
116 * @param menu Menu
117 * @param rmentry Place to store pointer to new menu entry
118 * @return EOK on success, ENOMEM if out of memory
119 */
120errno_t ui_menu_entry_sep_create(ui_menu_t *menu, ui_menu_entry_t **rmentry)
121{
122 ui_menu_entry_t *mentry;
123 errno_t rc;
124
125 rc = ui_menu_entry_create(menu, "", "", &mentry);
126 if (rc != EOK)
127 return rc;
128
129 /* Need to adjust menu height when changing to separator */
130 menu->total_h -= ui_menu_entry_height(mentry);
131 mentry->separator = true;
132 menu->total_h += ui_menu_entry_height(mentry);
133
134 *rmentry = mentry;
135 return EOK;
136}
137
138/** Destroy menu entry.
139 *
140 * @param mentry Menu entry or @c NULL
141 */
142void ui_menu_entry_destroy(ui_menu_entry_t *mentry)
143{
144 if (mentry == NULL)
145 return;
146
147 list_remove(&mentry->lentries);
148 free(mentry->caption);
149 free(mentry);
150}
151
152/** Set menu entry callback.
153 *
154 * @param mentry Menu entry
155 * @param cb Menu entry callback
156 * @param arg Callback argument
157 */
158void ui_menu_entry_set_cb(ui_menu_entry_t *mentry, ui_menu_entry_cb_t cb,
159 void *arg)
160{
161 mentry->cb = cb;
162 mentry->arg = arg;
163}
164
165/** Set menu entry disabled flag.
166 *
167 * @param mentry Menu entry
168 * @param disabled @c true iff entry is to be disabled, @c false otherwise
169 */
170void ui_menu_entry_set_disabled(ui_menu_entry_t *mentry, bool disabled)
171{
172 mentry->disabled = disabled;
173}
174
175/** Get menu entry disabled flag.
176 *
177 * @param mentry Menu entry
178 * @return disabled @c true iff entry is disabled, @c false otherwise
179 */
180bool ui_menu_entry_is_disabled(ui_menu_entry_t *mentry)
181{
182 return mentry->disabled;
183}
184
185/** Get first menu entry in menu.
186 *
187 * @param menu Menu
188 * @return First menu entry or @c NULL if there is none
189 */
190ui_menu_entry_t *ui_menu_entry_first(ui_menu_t *menu)
191{
192 link_t *link;
193
194 link = list_first(&menu->entries);
195 if (link == NULL)
196 return NULL;
197
198 return list_get_instance(link, ui_menu_entry_t, lentries);
199}
200
201/** Get last menu entry in menu.
202 *
203 * @param menu Menu
204 * @return Last menu entry or @c NULL if there is none
205 */
206ui_menu_entry_t *ui_menu_entry_last(ui_menu_t *menu)
207{
208 link_t *link;
209
210 link = list_last(&menu->entries);
211 if (link == NULL)
212 return NULL;
213
214 return list_get_instance(link, ui_menu_entry_t, lentries);
215}
216
217/** Get next menu entry in menu.
218 *
219 * @param cur Current menu entry
220 * @return Next menu entry or @c NULL if @a cur is the last one
221 */
222ui_menu_entry_t *ui_menu_entry_next(ui_menu_entry_t *cur)
223{
224 link_t *link;
225
226 link = list_next(&cur->lentries, &cur->menu->entries);
227 if (link == NULL)
228 return NULL;
229
230 return list_get_instance(link, ui_menu_entry_t, lentries);
231}
232
233/** Get previous menu entry in menu.
234 *
235 * @param cur Current menu entry
236 * @return Next menu entry or @c NULL if @a cur is the last one
237 */
238ui_menu_entry_t *ui_menu_entry_prev(ui_menu_entry_t *cur)
239{
240 link_t *link;
241
242 link = list_prev(&cur->lentries, &cur->menu->entries);
243 if (link == NULL)
244 return NULL;
245
246 return list_get_instance(link, ui_menu_entry_t, lentries);
247}
248
249/** Get width of menu entry.
250 *
251 * @param mentry Menu entry
252 * @param caption_w Place to store caption width
253 * @param shortcut_w Place to store shortcut width
254 */
255void ui_menu_entry_column_widths(ui_menu_entry_t *mentry,
256 gfx_coord_t *caption_w, gfx_coord_t *shortcut_w)
257{
258 ui_resource_t *res;
259
260 /*
261 * This needs to work even if the menu is not open, so we cannot
262 * use the menu's resource, which is only created after the menu
263 * is open (and its window is created). Use the parent window's
264 * resource instead.
265 */
266 res = ui_window_get_res(mentry->menu->parent);
267
268 *caption_w = ui_text_width(res->font, mentry->caption);
269 *shortcut_w = ui_text_width(res->font, mentry->shortcut);
270}
271
272/** Compute width of menu entry.
273 *
274 * @param menu Menu
275 * @param caption_w Widht of caption text
276 * @param shortcut_w Width of shortcut text
277 * @return Width in pixels
278 */
279gfx_coord_t ui_menu_entry_calc_width(ui_menu_t *menu, gfx_coord_t caption_w,
280 gfx_coord_t shortcut_w)
281{
282 ui_resource_t *res;
283 gfx_coord_t hpad;
284 gfx_coord_t width;
285
286 /*
287 * This needs to work even if the menu is not open, so we cannot
288 * use the menu's resource, which is only created after the menu
289 * is open (and its window is created). Use the parent window's
290 * resource instead.
291 */
292 res = ui_window_get_res(menu->parent);
293
294 if (res->textmode)
295 hpad = menu_entry_hpad_text;
296 else
297 hpad = menu_entry_hpad;
298
299 width = caption_w + 2 * hpad;
300
301 if (shortcut_w != 0) {
302 if (res->textmode)
303 width += menu_entry_column_pad_text;
304 else
305 width += menu_entry_column_pad;
306
307 width += shortcut_w;
308 }
309
310 return width;
311}
312
313/** Get height of menu entry.
314 *
315 * @param mentry Menu entry
316 * @return Width in pixels
317 */
318gfx_coord_t ui_menu_entry_height(ui_menu_entry_t *mentry)
319{
320 ui_resource_t *res;
321 gfx_font_metrics_t metrics;
322 gfx_coord_t height;
323 gfx_coord_t vpad;
324
325 /*
326 * This needs to work even if the menu is not open, so we cannot
327 * use the menu's resource, which is only created after the menu
328 * is open (and its window is created). Use the parent window's
329 * resource instead.
330 */
331 res = ui_window_get_res(mentry->menu->parent);
332
333 if (res->textmode) {
334 vpad = menu_entry_vpad_text;
335 } else {
336 vpad = menu_entry_vpad;
337 }
338
339 if (mentry->separator) {
340 /* Separator menu entry */
341 if (res->textmode)
342 height = menu_entry_sep_height_text;
343 else
344 height = menu_entry_sep_height;
345 } else {
346 /* Normal menu entry */
347 gfx_font_get_metrics(res->font, &metrics);
348 height = metrics.ascent + metrics.descent + 1;
349 }
350
351 return height + 2 * vpad;
352}
353
354/** Get menu entry accelerator character.
355 *
356 * @param mentry Menu entry
357 * @return Accelerator character (lowercase) or the null character if
358 * the menu entry has no accelerator.
359 */
360char32_t ui_menu_entry_get_accel(ui_menu_entry_t *mentry)
361{
362 return ui_accel_get(mentry->caption);
363}
364
365/** Paint menu entry.
366 *
367 * @param mentry Menu entry
368 * @param pos Position where to paint entry
369 * @return EOK on success or an error code
370 */
371errno_t ui_menu_entry_paint(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
372{
373 ui_resource_t *res;
374 ui_text_fmt_t fmt;
375 gfx_color_t *bg_color;
376 ui_menu_entry_geom_t geom;
377 gfx_rect_t rect;
378 errno_t rc;
379
380 res = ui_menu_get_res(mentry->menu);
381
382 ui_menu_entry_get_geom(mentry, pos, &geom);
383
384 ui_text_fmt_init(&fmt);
385 fmt.font = res->font;
386 fmt.halign = gfx_halign_left;
387 fmt.valign = gfx_valign_top;
388
389 if ((mentry->held && mentry->inside) ||
390 mentry == mentry->menu->selected) {
391 fmt.color = res->wnd_sel_text_color;
392 fmt.hgl_color = res->wnd_sel_text_hgl_color;
393 bg_color = res->wnd_sel_text_bg_color;
394 } else if (mentry->disabled) {
395 fmt.color = res->wnd_dis_text_color;
396 fmt.hgl_color = res->wnd_dis_text_color;
397 bg_color = res->wnd_face_color;
398 } else {
399 fmt.color = res->wnd_text_color;
400 fmt.hgl_color = res->wnd_text_hgl_color;
401 bg_color = res->wnd_face_color;
402 }
403
404 rc = gfx_set_color(res->gc, bg_color);
405 if (rc != EOK)
406 goto error;
407
408 rc = gfx_fill_rect(res->gc, &geom.outer_rect);
409 if (rc != EOK)
410 goto error;
411
412 rc = ui_paint_text(&geom.caption_pos, &fmt, mentry->caption);
413 if (rc != EOK)
414 goto error;
415
416 fmt.halign = gfx_halign_right;
417
418 rc = ui_paint_text(&geom.shortcut_pos, &fmt, mentry->shortcut);
419 if (rc != EOK)
420 goto error;
421
422 if (mentry->separator) {
423 if (res->textmode) {
424 rect = geom.outer_rect;
425 rect.p0.x -= 1;
426 rect.p1.x += 1;
427
428 rc = ui_paint_text_hbrace(res, &rect, ui_box_single,
429 res->wnd_face_color);
430 if (rc != EOK)
431 goto error;
432 } else {
433 rect.p0 = geom.caption_pos;
434 rect.p1.x = geom.shortcut_pos.x;
435 rect.p1.y = rect.p0.y + 2;
436 rc = ui_paint_bevel(res->gc, &rect, res->wnd_shadow_color,
437 res->wnd_highlight_color, 1, NULL);
438 if (rc != EOK)
439 goto error;
440 }
441 }
442
443 rc = gfx_update(res->gc);
444 if (rc != EOK)
445 goto error;
446
447 return EOK;
448error:
449 return rc;
450}
451
452/** Determine if entry is selectable.
453 *
454 * @return @c true iff entry is selectable
455 */
456bool ui_menu_entry_selectable(ui_menu_entry_t *mentry)
457{
458 return !mentry->separator;
459}
460
461/** Handle button press in menu entry.
462 *
463 * @param mentry Menu entry
464 * @param pos Menu entry position
465 */
466void ui_menu_entry_press(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
467{
468 if (mentry->held)
469 return;
470
471 if (mentry->separator || mentry->disabled)
472 return;
473
474 mentry->inside = true;
475 mentry->held = true;
476 ui_menu_entry_paint(mentry, pos);
477}
478
479/** Handle button release in menu entry.
480 *
481 * @param mentry Menu entry
482 */
483void ui_menu_entry_release(ui_menu_entry_t *mentry)
484{
485 if (!mentry->held)
486 return;
487
488 mentry->held = false;
489
490 if (mentry->inside)
491 ui_menu_entry_activate(mentry);
492}
493
494/** Activate menu entry.
495 *
496 * @param mentry Menu entry
497 */
498void ui_menu_entry_activate(ui_menu_entry_t *mentry)
499{
500 /* Close menu */
501 ui_menu_close_req(mentry->menu);
502
503 /* Call back */
504 ui_menu_entry_cb(mentry);
505}
506
507/** Call menu entry callback.
508 *
509 * @param mentry Menu entry
510 */
511void ui_menu_entry_cb(ui_menu_entry_t *mentry)
512{
513 if (mentry->cb != NULL)
514 mentry->cb(mentry, mentry->arg);
515}
516
517/** Pointer entered menu entry.
518 *
519 * @param mentry Menu entry
520 * @param pos Menu entry position
521 */
522void ui_menu_entry_enter(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
523{
524 if (mentry->inside)
525 return;
526
527 mentry->inside = true;
528 if (mentry->held)
529 (void) ui_menu_entry_paint(mentry, pos);
530}
531
532/** Pointer left menu entry.
533 *
534 * @param mentry Menu entry
535 * @param pos Menu entry position
536 */
537void ui_menu_entry_leave(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
538{
539 if (!mentry->inside)
540 return;
541
542 mentry->inside = false;
543 if (mentry->held)
544 (void) ui_menu_entry_paint(mentry, pos);
545}
546
547/** Handle menu entry position event.
548 *
549 * @param mentry Menu entry
550 * @param pos Menu entry position (top-left corner)
551 * @param pos_event Position event
552 * @return @c ui_claimed iff the event is claimed
553 */
554ui_evclaim_t ui_menu_entry_pos_event(ui_menu_entry_t *mentry,
555 gfx_coord2_t *pos, pos_event_t *event)
556{
557 ui_menu_entry_geom_t geom;
558 gfx_coord2_t ppos;
559 bool inside;
560
561 ppos.x = event->hpos;
562 ppos.y = event->vpos;
563
564 ui_menu_entry_get_geom(mentry, pos, &geom);
565 inside = gfx_pix_inside_rect(&ppos, &geom.outer_rect);
566
567 switch (event->type) {
568 case POS_PRESS:
569 if (inside) {
570 ui_menu_entry_press(mentry, pos);
571 return ui_claimed;
572 }
573 break;
574 case POS_RELEASE:
575 if (mentry->held) {
576 ui_menu_entry_release(mentry);
577 return ui_claimed;
578 }
579 break;
580 case POS_UPDATE:
581 if (inside && !mentry->inside) {
582 ui_menu_entry_enter(mentry, pos);
583 return ui_claimed;
584 } else if (!inside && mentry->inside) {
585 ui_menu_entry_leave(mentry, pos);
586 }
587 break;
588 case POS_DCLICK:
589 break;
590 }
591
592 return ui_unclaimed;
593}
594
595/** Get menu entry geometry.
596 *
597 * @param mentry Menu entry
598 * @param spos Entry position
599 * @param geom Structure to fill in with computed geometry
600 */
601void ui_menu_entry_get_geom(ui_menu_entry_t *mentry, gfx_coord2_t *pos,
602 ui_menu_entry_geom_t *geom)
603{
604 ui_resource_t *res;
605 gfx_coord_t hpad;
606 gfx_coord_t vpad;
607 gfx_coord_t width;
608
609 res = ui_menu_get_res(mentry->menu);
610
611 if (res->textmode) {
612 hpad = menu_entry_hpad_text;
613 vpad = menu_entry_vpad_text;
614 } else {
615 hpad = menu_entry_hpad;
616 vpad = menu_entry_vpad;
617 }
618
619 /* Compute total width of menu entry */
620 width = ui_menu_entry_calc_width(mentry->menu,
621 mentry->menu->max_caption_w, mentry->menu->max_shortcut_w);
622
623 geom->outer_rect.p0 = *pos;
624 geom->outer_rect.p1.x = geom->outer_rect.p0.x + width;
625 geom->outer_rect.p1.y = geom->outer_rect.p0.y +
626 ui_menu_entry_height(mentry);
627
628 geom->caption_pos.x = pos->x + hpad;
629 geom->caption_pos.y = pos->y + vpad;
630
631 geom->shortcut_pos.x = geom->outer_rect.p1.x - hpad;
632 geom->shortcut_pos.y = pos->y + vpad;
633}
634
635/** @}
636 */
Note: See TracBrowser for help on using the repository browser.