Ignore:
File:
1 edited

Legend:

Unmodified
Added
Removed
  • uspace/lib/ui/src/menu.c

    rd7f82635 rd92b8e8f  
    11/*
    2  * Copyright (c) 2021 Jiri Svoboda
     2 * Copyright (c) 2024 Jiri Svoboda
    33 * All rights reserved.
    44 *
     
    3535
    3636#include <adt/list.h>
     37#include <ctype.h>
    3738#include <errno.h>
    3839#include <gfx/color.h>
     
    4344#include <stdlib.h>
    4445#include <str.h>
     46#include <uchar.h>
     47#include <ui/ui.h>
     48#include <ui/accel.h>
    4549#include <ui/control.h>
    4650#include <ui/paint.h>
     
    5054#include <ui/resource.h>
    5155#include <ui/window.h>
    52 #include "../private/menubar.h"
    5356#include "../private/menu.h"
    5457#include "../private/resource.h"
     
    5861        menu_frame_h = 4,
    5962        menu_frame_w_text = 2,
    60         menu_frame_h_text = 1
     63        menu_frame_h_text = 1,
     64        menu_frame_h_margin_text = 1
    6165};
    6266
    6367static void ui_menu_popup_close(ui_popup_t *, void *);
     68static void ui_menu_popup_kbd(ui_popup_t *, void *, kbd_event_t *);
    6469static void ui_menu_popup_pos(ui_popup_t *, void *, pos_event_t *);
     70static void ui_menu_key_press_unmod(ui_menu_t *, kbd_event_t *);
    6571
    6672static ui_popup_cb_t ui_menu_popup_cb = {
    6773        .close = ui_menu_popup_close,
     74        .kbd = ui_menu_popup_kbd,
    6875        .pos = ui_menu_popup_pos
    6976};
     
    7178/** Create new menu.
    7279 *
     80 * @param parent Parent window
    7381 * @param mbar Menu bar
    74  * @param caption Caption
    7582 * @param rmenu Place to store pointer to new menu
    7683 * @return EOK on success, ENOMEM if out of memory
    7784 */
    78 errno_t ui_menu_create(ui_menu_bar_t *mbar, const char *caption,
    79     ui_menu_t **rmenu)
     85errno_t ui_menu_create(ui_window_t *parent, ui_menu_t **rmenu)
    8086{
    8187        ui_menu_t *menu;
     
    8591                return ENOMEM;
    8692
    87         menu->caption = str_dup(caption);
    88         if (menu->caption == NULL) {
    89                 free(menu);
    90                 return ENOMEM;
    91         }
    92 
    93         menu->mbar = mbar;
    94         list_append(&menu->lmenus, &mbar->menus);
     93        menu->parent = parent;
    9594        list_initialize(&menu->entries);
    9695
     
    117116        }
    118117
    119         list_remove(&menu->lmenus);
    120118        free(menu->caption);
    121119        free(menu);
    122120}
    123121
    124 /** Get first menu in menu bar.
    125  *
    126  * @param mbar Menu bar
    127  * @return First menu or @c NULL if there is none
    128  */
    129 ui_menu_t *ui_menu_first(ui_menu_bar_t *mbar)
    130 {
    131         link_t *link;
    132 
    133         link = list_first(&mbar->menus);
    134         if (link == NULL)
    135                 return NULL;
    136 
    137         return list_get_instance(link, ui_menu_t, lmenus);
    138 }
    139 
    140 /** Get next menu in menu bar.
    141  *
    142  * @param cur Current menu
    143  * @return Next menu or @c NULL if @a cur is the last one
    144  */
    145 ui_menu_t *ui_menu_next(ui_menu_t *cur)
    146 {
    147         link_t *link;
    148 
    149         link = list_next(&cur->lmenus, &cur->mbar->menus);
    150         if (link == NULL)
    151                 return NULL;
    152 
    153         return list_get_instance(link, ui_menu_t, lmenus);
    154 }
    155 
    156 /** Get menu caption.
    157  *
    158  * @param menu Menu
    159  * @return Caption (owned by @a menu)
    160  */
    161 const char *ui_menu_caption(ui_menu_t *menu)
    162 {
    163         return menu->caption;
     122/** Set menu callbacks.
     123 *
     124 * @param menu Menu
     125 * @param cb Callbacks
     126 * @param arg Callback argument
     127 */
     128void ui_menu_set_cb(ui_menu_t *menu, ui_menu_cb_t *cb, void *arg)
     129{
     130        menu->cb = cb;
     131        menu->arg = arg;
    164132}
    165133
     
    173141    ui_menu_geom_t *geom)
    174142{
    175         ui_resource_t *res;
    176143        gfx_coord2_t edim;
    177144        gfx_coord_t frame_w;
    178145        gfx_coord_t frame_h;
    179 
    180         res = ui_window_get_res(menu->mbar->window);
     146        ui_resource_t *res;
     147
     148        res = ui_window_get_res(menu->parent);
    181149
    182150        if (res->textmode) {
     
    202170}
    203171
    204 /** Get menu rectangle.
    205  *
    206  * @param menu Menu
    207  * @param spos Starting position (top-left corner)
    208  * @param rect Place to store menu rectangle
    209  */
    210 void ui_menu_get_rect(ui_menu_t *menu, gfx_coord2_t *spos, gfx_rect_t *rect)
    211 {
    212         ui_menu_geom_t geom;
    213 
    214         ui_menu_get_geom(menu, spos, &geom);
    215         *rect = geom.outer_rect;
    216 }
    217 
    218172/** Get UI resource from menu.
    219173 *
     
    230184 * @param menu Menu
    231185 * @param prect Parent rectangle around which the menu should be placed
    232  */
    233 errno_t ui_menu_open(ui_menu_t *menu, gfx_rect_t *prect)
     186 * @param idev_id Input device associated with the menu's seat
     187 */
     188errno_t ui_menu_open(ui_menu_t *menu, gfx_rect_t *prect, sysarg_t idev_id)
    234189{
    235190        ui_popup_t *popup = NULL;
     
    239194        errno_t rc;
    240195
     196        /* Select first entry */
     197        menu->selected = ui_menu_entry_first(menu);
     198
    241199        /* Determine menu dimensions */
    242200
     
    248206        params.rect = geom.outer_rect;
    249207        params.place = *prect;
    250 
    251         rc = ui_popup_create(menu->mbar->ui, menu->mbar->window, &params,
    252             &popup);
     208        params.idev_id = idev_id;
     209
     210        rc = ui_popup_create(ui_window_get_ui(menu->parent), menu->parent,
     211            &params, &popup);
    253212        if (rc != EOK)
    254213                return rc;
     
    268227        ui_popup_destroy(menu->popup);
    269228        menu->popup = NULL;
     229}
     230
     231/** Determine if menu is open.
     232 *
     233 * @param menu Menu
     234 * @return @c true iff menu is open
     235 */
     236bool ui_menu_is_open(ui_menu_t *menu)
     237{
     238        return menu->popup != NULL;
     239}
     240
     241/** Paint menu.
     242 *
     243 * @param menu Menu
     244 * @param spos Starting position (top-left corner)
     245 * @return EOK on success or an error code
     246 */
     247errno_t ui_menu_paint_bg_gfx(ui_menu_t *menu, gfx_coord2_t *spos)
     248{
     249        ui_resource_t *res;
     250        ui_menu_geom_t geom;
     251        gfx_rect_t bg_rect;
     252        errno_t rc;
     253
     254        res = ui_menu_get_res(menu);
     255        ui_menu_get_geom(menu, spos, &geom);
     256
     257        /* Paint menu frame */
     258
     259        rc = gfx_set_color(res->gc, res->wnd_face_color);
     260        if (rc != EOK)
     261                goto error;
     262
     263        rc = ui_paint_outset_frame(res, &geom.outer_rect, &bg_rect);
     264        if (rc != EOK)
     265                goto error;
     266
     267        /* Paint menu background */
     268
     269        rc = gfx_set_color(res->gc, res->wnd_face_color);
     270        if (rc != EOK)
     271                goto error;
     272
     273        rc = gfx_fill_rect(res->gc, &bg_rect);
     274        if (rc != EOK)
     275                goto error;
     276
     277        return EOK;
     278error:
     279        return rc;
     280}
     281
     282/** Paint menu.
     283 *
     284 * @param menu Menu
     285 * @param spos Starting position (top-left corner)
     286 * @return EOK on success or an error code
     287 */
     288errno_t ui_menu_paint_bg_text(ui_menu_t *menu, gfx_coord2_t *spos)
     289{
     290        ui_resource_t *res;
     291        ui_menu_geom_t geom;
     292        gfx_rect_t rect;
     293        errno_t rc;
     294
     295        res = ui_menu_get_res(menu);
     296        ui_menu_get_geom(menu, spos, &geom);
     297
     298        /* Paint menu background */
     299
     300        rc = gfx_set_color(res->gc, res->wnd_face_color);
     301        if (rc != EOK)
     302                goto error;
     303
     304        rc = gfx_fill_rect(res->gc, &geom.outer_rect);
     305        if (rc != EOK)
     306                goto error;
     307
     308        /* Paint menu box */
     309
     310        rect = geom.outer_rect;
     311        rect.p0.x += menu_frame_h_margin_text;
     312        rect.p1.x -= menu_frame_h_margin_text;
     313
     314        rc = ui_paint_text_box(res, &rect, ui_box_single, res->wnd_face_color);
     315        if (rc != EOK)
     316                goto error;
     317
     318        return EOK;
     319error:
     320        return rc;
    270321}
    271322
     
    282333        ui_menu_entry_t *mentry;
    283334        ui_menu_geom_t geom;
    284         gfx_rect_t bg_rect;
    285335        errno_t rc;
    286336
     
    288338        ui_menu_get_geom(menu, spos, &geom);
    289339
    290         /* Paint menu frame */
    291 
    292         rc = gfx_set_color(res->gc, res->wnd_face_color);
    293         if (rc != EOK)
    294                 goto error;
    295 
    296         rc = ui_paint_outset_frame(res, &geom.outer_rect, &bg_rect);
    297         if (rc != EOK)
    298                 goto error;
    299 
    300         /* Paint menu background */
    301 
    302         rc = gfx_set_color(res->gc, res->wnd_face_color);
    303         if (rc != EOK)
    304                 goto error;
    305 
    306         rc = gfx_fill_rect(res->gc, &bg_rect);
     340        /* Paint menu frame and background */
     341        if (res->textmode)
     342                rc = ui_menu_paint_bg_text(menu, spos);
     343        else
     344                rc = ui_menu_paint_bg_gfx(menu, spos);
    307345        if (rc != EOK)
    308346                goto error;
     
    369407                /* Press outside menu - close it */
    370408                if (event->type == POS_PRESS)
    371                         ui_menu_bar_select(menu->mbar, NULL, NULL);
     409                        ui_menu_close_req(menu);
    372410        }
    373411
    374412        return ui_unclaimed;
     413}
     414
     415/** Handle keyboard event in menu.
     416 *
     417 * @param menu Menu
     418 * @param event Keyboard event
     419 * @return ui_claimed iff the event was claimed
     420 */
     421ui_evclaim_t ui_menu_kbd_event(ui_menu_t *menu, kbd_event_t *event)
     422{
     423        if (event->type == KEY_PRESS && (event->mods &
     424            (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
     425                ui_menu_key_press_unmod(menu, event);
     426        }
     427
     428        if (event->type == KEY_PRESS && (event->mods & KM_ALT) != 0 &&
     429            (event->mods & (KM_CTRL | KM_SHIFT)) == 0 && event->c != '\0')
     430                ui_menu_press_accel(menu, event->c, event->kbd_id);
     431
     432        return ui_claimed;
     433}
     434
     435/** Move one entry up.
     436 *
     437 * Non-selectable entries are skipped. If we are already at the top,
     438 * we wrap around.
     439 *
     440 * @param menu Menu
     441 */
     442void ui_menu_up(ui_menu_t *menu)
     443{
     444        gfx_coord2_t mpos;
     445        ui_menu_entry_t *nentry;
     446
     447        if (menu->selected == NULL)
     448                return;
     449
     450        nentry = ui_menu_entry_prev(menu->selected);
     451        if (nentry == NULL)
     452                nentry = ui_menu_entry_last(menu);
     453
     454        /* Need to find a selectable entry */
     455        while (!ui_menu_entry_selectable(nentry)) {
     456                nentry = ui_menu_entry_prev(nentry);
     457                if (nentry == NULL)
     458                        nentry = ui_menu_entry_last(menu);
     459
     460                /* Went completely around and found nothing? */
     461                if (nentry == menu->selected)
     462                        return;
     463        }
     464
     465        menu->selected = nentry;
     466
     467        mpos.x = 0;
     468        mpos.y = 0;
     469        (void) ui_menu_paint(menu, &mpos);
     470}
     471
     472/** Move one entry down.
     473 *
     474 * Non-selectable entries are skipped. If we are already at the bottom,
     475 * we wrap around.
     476 *
     477 * @param menu Menu
     478 */
     479void ui_menu_down(ui_menu_t *menu)
     480{
     481        gfx_coord2_t mpos;
     482        ui_menu_entry_t *nentry;
     483
     484        if (menu->selected == NULL)
     485                return;
     486
     487        nentry = ui_menu_entry_next(menu->selected);
     488        if (nentry == NULL)
     489                nentry = ui_menu_entry_first(menu);
     490
     491        /* Need to find a selectable entry */
     492        while (!ui_menu_entry_selectable(nentry)) {
     493                nentry = ui_menu_entry_next(nentry);
     494                if (nentry == NULL)
     495                        nentry = ui_menu_entry_first(menu);
     496
     497                /* Went completely around and found nothing? */
     498                if (nentry == menu->selected)
     499                        return;
     500        }
     501
     502        menu->selected = nentry;
     503
     504        mpos.x = 0;
     505        mpos.y = 0;
     506        (void) ui_menu_paint(menu, &mpos);
     507}
     508
     509/** Handle key press without modifiers in menu popup window.
     510 *
     511 * @param menu Menu
     512 * @param event Keyboard event
     513 */
     514static void ui_menu_key_press_unmod(ui_menu_t *menu, kbd_event_t *event)
     515{
     516        ui_menu_entry_t *mentry;
     517        char32_t c;
     518
     519        switch (event->key) {
     520        case KC_ESCAPE:
     521                ui_menu_close_req(menu);
     522                break;
     523        case KC_LEFT:
     524                ui_menu_left(menu, event->kbd_id);
     525                break;
     526        case KC_RIGHT:
     527                ui_menu_right(menu, event->kbd_id);
     528                break;
     529        case KC_UP:
     530                ui_menu_up(menu);
     531                break;
     532        case KC_DOWN:
     533                ui_menu_down(menu);
     534                break;
     535        case KC_ENTER:
     536                if (menu->selected != NULL &&
     537                    !ui_menu_entry_is_disabled(menu->selected))
     538                        ui_menu_entry_activate(menu->selected);
     539                break;
     540        default:
     541                if (event->c != '\0') {
     542                        mentry = ui_menu_entry_first(menu);
     543                        while (mentry != NULL) {
     544                                c = ui_menu_entry_get_accel(mentry);
     545                                if (c == (char32_t)tolower(event->c) &&
     546                                    !ui_menu_entry_is_disabled(mentry)) {
     547                                        ui_menu_entry_activate(mentry);
     548                                        break;
     549                                }
     550                                mentry = ui_menu_entry_next(mentry);
     551                        }
     552                }
     553                break;
     554        }
    375555}
    376556
     
    384564        ui_menu_t *menu = (ui_menu_t *)arg;
    385565
    386         /* Close the menu */
    387         ui_menu_bar_select(menu->mbar, NULL, NULL);
     566        /* Forward close request to caller */
     567        ui_menu_close_req(menu);
     568}
     569
     570/** Handle keyboard event in menu popup window.
     571 *
     572 * @param popup Menu popup window
     573 * @param arg Argument (ui_menu_t *)
     574 * @param event Keyboard event
     575 */
     576static void ui_menu_popup_kbd(ui_popup_t *popup, void *arg, kbd_event_t *event)
     577{
     578        ui_menu_t *menu = (ui_menu_t *)arg;
     579
     580        menu->idev_id = ui_popup_get_idev_id(menu->popup);
     581        ui_menu_kbd_event(menu, event);
    388582}
    389583
     
    399593        gfx_coord2_t spos;
    400594
     595        menu->idev_id = ui_popup_get_idev_id(menu->popup);
     596
    401597        spos.x = 0;
    402598        spos.y = 0;
     
    404600}
    405601
     602/** Send menu left event.
     603 *
     604 * @param menu Menu
     605 * @param idev_id Input device ID
     606 */
     607void ui_menu_left(ui_menu_t *menu, sysarg_t idev_id)
     608{
     609        if (menu->cb != NULL && menu->cb->left != NULL)
     610                menu->cb->left(menu, menu->arg, idev_id);
     611}
     612
     613/** Send menu right event.
     614 *
     615 * @param menu Menu
     616 * @param idev_id Input device ID
     617 */
     618void ui_menu_right(ui_menu_t *menu, sysarg_t idev_id)
     619{
     620        if (menu->cb != NULL && menu->cb->right != NULL)
     621                menu->cb->right(menu, menu->arg, idev_id);
     622}
     623
     624/** Send menu close request event.
     625 *
     626 * @param menu Menu
     627 */
     628void ui_menu_close_req(ui_menu_t *menu)
     629{
     630        if (menu->cb != NULL && menu->cb->close_req != NULL)
     631                menu->cb->close_req(menu, menu->arg);
     632}
     633
     634/** Send menu accelerator key press event.
     635 *
     636 * @param menu Menu
     637 * @param c Character
     638 * @param kbd_id Keyboard ID
     639 */
     640void ui_menu_press_accel(ui_menu_t *menu, char32_t c, sysarg_t kbd_id)
     641{
     642        if (menu->cb != NULL && menu->cb->press_accel != NULL)
     643                menu->cb->press_accel(menu, menu->arg, c, kbd_id);
     644}
     645
     646/** Get ID of last device that input event.
     647 *
     648 * @param menu Menu
     649 * @return Input device ID
     650 */
     651sysarg_t ui_menu_get_idev_id(ui_menu_t *menu)
     652{
     653        return menu->idev_id;
     654}
     655
    406656/** @}
    407657 */
Note: See TracChangeset for help on using the changeset viewer.