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
RevLine 
[214aefb]1/*
[46bd63c9]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 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>
[c88d7f99]45#include <ui/accel.h>
[214aefb]46#include <ui/control.h>
47#include <ui/paint.h>
[59768c7]48#include <ui/menubar.h>
[214aefb]49#include <ui/menuentry.h>
[c68c18b9]50#include <ui/window.h>
[214aefb]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,
[b8b64a8]59 menu_entry_column_pad = 8,
[6186f9f]60 menu_entry_sep_height = 2,
[214aefb]61 menu_entry_hpad_text = 1,
[b8b64a8]62 menu_entry_vpad_text = 0,
[6186f9f]63 menu_entry_column_pad_text = 2,
64 menu_entry_sep_height_text = 1
[214aefb]65};
66
67/** Create new menu entry.
68 *
69 * @param menu Menu
70 * @param caption Caption
[b8b64a8]71 * @param shortcut Shotcut key(s) or empty string
[214aefb]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,
[b8b64a8]76 const char *shortcut, ui_menu_entry_t **rmentry)
[214aefb]77{
78 ui_menu_entry_t *mentry;
[b8b64a8]79 gfx_coord_t caption_w;
80 gfx_coord_t shortcut_w;
[214aefb]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
[b8b64a8]92 mentry->shortcut = str_dup(shortcut);
93 if (mentry->caption == NULL) {
94 free(mentry->caption);
95 free(mentry);
96 return ENOMEM;
97 }
98
[214aefb]99 mentry->menu = menu;
100 list_append(&mentry->lentries, &menu->entries);
101
[f251883]102 /* Update accumulated menu entry dimensions */
[b8b64a8]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;
[f251883]108 menu->total_h += ui_menu_entry_height(mentry);
109
[214aefb]110 *rmentry = mentry;
111 return EOK;
112}
113
[6186f9f]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
[214aefb]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
[112f70a]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
[214aefb]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
[59768c7]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
[214aefb]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
[59768c7]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
[214aefb]249/** Get width of menu entry.
250 *
251 * @param mentry Menu entry
[b8b64a8]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
[3c8c580]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
[46bd63c9]263 * is open (and its window is created). Use the parent window's
[3c8c580]264 * resource instead.
265 */
[46bd63c9]266 res = ui_window_get_res(mentry->menu->parent);
[b8b64a8]267
[c88d7f99]268 *caption_w = ui_text_width(res->font, mentry->caption);
269 *shortcut_w = ui_text_width(res->font, mentry->shortcut);
[b8b64a8]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
[214aefb]277 * @return Width in pixels
278 */
[b8b64a8]279gfx_coord_t ui_menu_entry_calc_width(ui_menu_t *menu, gfx_coord_t caption_w,
280 gfx_coord_t shortcut_w)
[214aefb]281{
282 ui_resource_t *res;
283 gfx_coord_t hpad;
[b8b64a8]284 gfx_coord_t width;
[214aefb]285
[3c8c580]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
[46bd63c9]289 * is open (and its window is created). Use the parent window's
[3c8c580]290 * resource instead.
291 */
[46bd63c9]292 res = ui_window_get_res(menu->parent);
[214aefb]293
[b8b64a8]294 if (res->textmode)
[214aefb]295 hpad = menu_entry_hpad_text;
[b8b64a8]296 else
[214aefb]297 hpad = menu_entry_hpad;
[b8b64a8]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;
[214aefb]308 }
309
[b8b64a8]310 return width;
[214aefb]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
[3c8c580]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
[46bd63c9]328 * is open (and its window is created). Use the parent window's
[3c8c580]329 * resource instead.
330 */
[46bd63c9]331 res = ui_window_get_res(mentry->menu->parent);
[214aefb]332
333 if (res->textmode) {
334 vpad = menu_entry_vpad_text;
335 } else {
336 vpad = menu_entry_vpad;
337 }
338
[6186f9f]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
[214aefb]351 return height + 2 * vpad;
352}
353
[c88d7f99]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
[214aefb]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;
[c88d7f99]374 ui_text_fmt_t fmt;
[214aefb]375 gfx_color_t *bg_color;
376 ui_menu_entry_geom_t geom;
[6186f9f]377 gfx_rect_t rect;
[214aefb]378 errno_t rc;
379
[3c8c580]380 res = ui_menu_get_res(mentry->menu);
[214aefb]381
382 ui_menu_entry_get_geom(mentry, pos, &geom);
383
[c88d7f99]384 ui_text_fmt_init(&fmt);
[4583015]385 fmt.font = res->font;
[214aefb]386 fmt.halign = gfx_halign_left;
387 fmt.valign = gfx_valign_top;
388
[0262f16c]389 if ((mentry->held && mentry->inside) ||
390 mentry == mentry->menu->selected) {
[214aefb]391 fmt.color = res->wnd_sel_text_color;
[c88d7f99]392 fmt.hgl_color = res->wnd_sel_text_hgl_color;
[214aefb]393 bg_color = res->wnd_sel_text_bg_color;
[112f70a]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;
[214aefb]398 } else {
399 fmt.color = res->wnd_text_color;
[c88d7f99]400 fmt.hgl_color = res->wnd_text_hgl_color;
[214aefb]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
[c88d7f99]412 rc = ui_paint_text(&geom.caption_pos, &fmt, mentry->caption);
[b8b64a8]413 if (rc != EOK)
414 goto error;
415
416 fmt.halign = gfx_halign_right;
417
[c88d7f99]418 rc = ui_paint_text(&geom.shortcut_pos, &fmt, mentry->shortcut);
[214aefb]419 if (rc != EOK)
420 goto error;
421
[6186f9f]422 if (mentry->separator) {
[81ec7e1]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 }
[6186f9f]441 }
442
[214aefb]443 rc = gfx_update(res->gc);
444 if (rc != EOK)
445 goto error;
446
447 return EOK;
448error:
449 return rc;
450}
451
[59768c7]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
[214aefb]461/** Handle button press in menu entry.
462 *
463 * @param mentry Menu entry
[0262f16c]464 * @param pos Menu entry position
[214aefb]465 */
[0262f16c]466void ui_menu_entry_press(ui_menu_entry_t *mentry, gfx_coord2_t *pos)
[214aefb]467{
[0262f16c]468 if (mentry->held)
469 return;
[214aefb]470
[6a0b2cc]471 if (mentry->separator || mentry->disabled)
[6186f9f]472 return;
473
[0262f16c]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;
[214aefb]487
[0262f16c]488 mentry->held = false;
[214aefb]489
[71edd430]490 if (mentry->inside)
491 ui_menu_entry_activate(mentry);
[59768c7]492}
[214aefb]493
[59768c7]494/** Activate menu entry.
495 *
496 * @param mentry Menu entry
497 */
498void ui_menu_entry_activate(ui_menu_entry_t *mentry)
499{
[46bd63c9]500 /* Close menu */
501 ui_menu_close_req(mentry->menu);
[59768c7]502
503 /* Call back */
504 ui_menu_entry_cb(mentry);
[214aefb]505}
506
[95a9cbc]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
[0262f16c]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;
[8edec53]588 case POS_DCLICK:
589 break;
[0262f16c]590 }
591
592 return ui_unclaimed;
593}
594
[214aefb]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
[3c8c580]609 res = ui_menu_get_res(mentry->menu);
[214aefb]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
[b8b64a8]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);
[214aefb]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);
[b8b64a8]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;
[214aefb]633}
634
635/** @}
636 */
Note: See TracBrowser for help on using the repository browser.