/* $NetBSD: menu.c,v 1.4 2026/11/02 08:44:55 nia Exp $ */ /*- * Copyright (c) 1998-1999 Brett Lymn * (blymn@baea.com.au, brett_lymn@yahoo.com.au) * Copyright (c) 2026 Nia Alarie * All rights reserved. * * This code has been donated to The NetBSD Foundation by the Author. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. The name of the author may not be used to endorse or promote products * derived from this software withough specific prior written permission * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "header.h" #include int menu_mode = 0; static int menu_ready; static int col_width; static WINDOW *top_win; static MENU *top_menu; static ITEM *top_items[8]; static MENU *file_menu; static ITEM *file_items[8]; static MENU *edit_menu; static ITEM *edit_items[8]; static MENU *view_menu; static ITEM *view_items[8]; static WINDOW *create_menu_popup(MENU *, MENU *, int, int, int, int); static void handle_menu_popup(MENU *, WINDOW *, const char *); static int file_item_init(void); static int edit_item_init(void); static int view_item_init(void); static void quit_item_callback(MENU *); static void file_item_callback(MENU *); static void edit_item_callback(MENU *); static void view_item_callback(MENU *); static void open_item_callback(MENU *parent) { (void)parent; readfile(); } static void save_item_callback(MENU *parent) { (void)parent; savebuffer(); } static void saveas_item_callback(MENU *parent) { (void)parent; writefile(); } static void find_item_callback(MENU *parent) { (void)parent; search(); } static void replace_item_callback(MENU *parent) { (void)parent; query_replace(); } static void paste_item_callback(MENU *parent) { (void)parent; paste(); } static void cut_item_callback(MENU *parent) { (void)parent; killtoeol(); } static void goto_item_callback(MENU *parent) { (void)parent; gotoline(); } static void quit_item_callback(MENU *parent) { (void)parent; quit_ask(); } static void next_item_callback(MENU *parent) { (void)parent; next_buffer(); } static void status_item_callback(MENU *parent) { (void)parent; showpos(); } /* * Create and return an appropriately sized and positioned window for * the given menu. If x is less than 0 then the function uses the * parent menu position to calculate the correct location of the * popup. The parameter y must always be a valid location. If either * drows or dcols are less than 0 then the child menu dimensions are * used to set the window size. The parent parameter may be NULL iff x * is greater than or equal to 0. Returns NULL on error. */ static WINDOW * create_menu_popup(MENU *parent, MENU *child, int y, int x, int drows, int dcols) { int lhs, index, rows, cols; WINDOW *popup; assert(child != NULL); scale_menu(child, &rows, &cols); if (drows > 0) rows = drows; if (dcols > 0) cols = dcols; if (x >= 0) { lhs = x; } else { if (parent == NULL) { msg("Bad parent in create_menu_popup"); return NULL; } index = item_index(current_item(parent)); lhs = index * col_width; if (lhs > COLS) lhs = COLS - cols; } if ((popup = newwin(rows, cols, y, lhs)) == NULL) return NULL; keypad(popup, TRUE); nodelay(popup, FALSE); set_menu_sub(child, popup); return popup; } /* * Do a generic menu handling for the given menu in the given popup window. * This includes posting the menu, handling the key event loop and then * unposting the menu when done. The name parameter is used when reporting * errors. */ static void handle_menu_popup(MENU *menu, WINDOW *popup, const char *name) { int c, isdone = 0, status; Menu_Hook callback; ITEM *current; (void)name; if ((status = post_menu(menu)) < 0) { msg("Post of popup menu failed"); return; } do { c = wgetch(popup); switch (c) { case '\e': isdone = 1; break; case '\t': case KEY_DOWN: menu_driver(menu, REQ_DOWN_ITEM); break; case KEY_UP: menu_driver(menu, REQ_UP_ITEM); break; case '\n': case '\r': if ((current = current_item(menu)) != NULL) { callback = (Menu_Hook) item_userptr(current); if (callback != NULL) { callback(menu); menu_mode = 0; isdone = 1; } } break; case KEY_LEFT: menu_driver(top_menu, REQ_LEFT_ITEM); isdone = 1; break; case KEY_RIGHT: menu_driver(top_menu, REQ_RIGHT_ITEM); isdone = 1; break; default: menu_driver(menu, c); break; } touchwin(popup); wrefresh(popup); } while (!isdone); werase(popup); wrefresh(popup); unpost_menu(menu); } /* * Handle the menu mode keystrokes. */ void handle_menus(int c) { Menu_Hook cb; ITEM *current; if (!menu_ready) return; post_menu(top_menu); touchwin(top_win); wrefresh(top_win); switch (c) { case '\e': menu_mode = 0; break; case KEY_UP: beep(); break; case '\r': case '\n': case KEY_DOWN: if ((current = current_item(top_menu)) != NULL) { cb = (Menu_Hook) item_userptr(current); if (cb != NULL) { cb(top_menu); } } break; case KEY_LEFT: menu_driver(top_menu, REQ_LEFT_ITEM); break; case '\t': case KEY_RIGHT: menu_driver(top_menu, REQ_RIGHT_ITEM); break; default: menu_driver(top_menu, c); /* this makes item search work */ break; } wrefresh(top_win); } static int file_item_init(void) { size_t i; if ((file_items[0] = new_item(__UNCONST("Open"), __UNCONST("C-x c-f"))) == NULL) { return -1; } if ((file_items[1] = new_item(__UNCONST("Save"), __UNCONST("C-x c-s"))) == NULL) { goto fail; } if ((file_items[2] = new_item(__UNCONST("Save As..."), __UNCONST("C-x c-w"))) == NULL) { goto fail; } if ((file_items[3] = new_item(__UNCONST("Quit"), __UNCONST("C-x c-c"))) == NULL) { goto fail; } file_items[4] = NULL; set_item_userptr(file_items[0], (char *) &open_item_callback); set_item_userptr(file_items[1], (char *) &save_item_callback); set_item_userptr(file_items[2], (char *) &saveas_item_callback); set_item_userptr(file_items[3], (char *) &quit_item_callback); if ((file_menu = new_menu(file_items)) == NULL) goto fail; set_menu_format(file_menu, 4, 1); menu_opts_on(file_menu, O_SHOWDESC); return 0; fail: for (i = 0; i < __arraycount(file_items); ++i) { free_item(file_items[i]); file_items[i] = NULL; } return -1; } static int edit_item_init(void) { size_t i; if ((edit_items[0] = new_item(__UNCONST("Cut line"), __UNCONST("Ctrl-K"))) == NULL) { return -1; } if ((edit_items[1] = new_item(__UNCONST("Paste"), __UNCONST("Ctrl-Y"))) == NULL) { goto fail; } if ((edit_items[2] = new_item(__UNCONST("Find..."), __UNCONST("Ctrl-S"))) == NULL) { goto fail; } if ((edit_items[3] = new_item(__UNCONST("Find & Replace"), __UNCONST("Ctrl-R"))) == NULL) { goto fail; } if ((edit_items[4] = new_item(__UNCONST("Go To..."), __UNCONST("Esc-G"))) == NULL) { goto fail; } set_item_userptr(edit_items[0], (char *) &cut_item_callback); set_item_userptr(edit_items[1], (char *) &paste_item_callback); set_item_userptr(edit_items[2], (char *) &find_item_callback); set_item_userptr(edit_items[3], (char *) &replace_item_callback); set_item_userptr(edit_items[4], (char *) &goto_item_callback); edit_items[5] = NULL; if ((edit_menu = new_menu(edit_items)) == NULL) goto fail; set_menu_format(edit_menu, 5, 1); menu_opts_on(edit_menu, O_SHOWDESC); return 0; fail: for (i = 0; i < __arraycount(edit_items); ++i) { free_item(edit_items[i]); edit_items[i] = NULL; } return -1; } static int view_item_init(void) { size_t i; if ((view_items[0] = new_item(__UNCONST("Next buffer"), __UNCONST("C-x n"))) == NULL) { return -1; } if ((view_items[1] = new_item(__UNCONST("Status line"), __UNCONST("C-x ="))) == NULL) { goto fail; } set_item_userptr(view_items[0], (char *) &next_item_callback); set_item_userptr(view_items[1], (char *) &status_item_callback); view_items[2] = NULL; if ((view_menu = new_menu(view_items)) == NULL) goto fail; set_menu_format(view_menu, 2, 1); menu_opts_on(view_menu, O_SHOWDESC); return 0; fail: for (i = 0; i < __arraycount(view_items); ++i) { free_item(view_items[i]); view_items[i] = NULL; } return -1; } static void file_item_callback(MENU *parent) { WINDOW *file_popup; file_popup = create_menu_popup(parent, file_menu, 1, -1, -1, -1); if (file_popup != NULL) { handle_menu_popup(file_menu, file_popup, "file"); } else { msg("File menu popup failed."); } } static void edit_item_callback(MENU *parent) { WINDOW *edit_popup; edit_popup = create_menu_popup(parent, edit_menu, 1, -1, -1, -1); if (edit_popup != NULL) { handle_menu_popup(edit_menu, edit_popup, "edit"); } else { msg("Edit menu popup failed."); } } static void view_item_callback(MENU *parent) { WINDOW *view_popup = NULL; view_popup = create_menu_popup(parent, view_menu, 1, -1, -1, -1); if (view_popup != NULL) { handle_menu_popup(view_menu, view_popup, "view"); } else { msg("View menu popup failed."); } } int create_menu(void) { int top_rows, top_cols; int status; /* set default menu attributes - all menus will inherit these */ set_menu_back(NULL, A_REVERSE); set_menu_fore(NULL, A_NORMAL); /* ignore case and show matches */ menu_opts_on(NULL, O_IGNORECASE | O_SHOWMATCH); if ((top_items[0] = new_item(__UNCONST("File"), __UNCONST(""))) == NULL) { return -1; } if ((top_items[1] = new_item(__UNCONST("Edit"), __UNCONST(""))) == NULL) { return -1; } if ((top_items[2] = new_item(__UNCONST("View"), __UNCONST(""))) == NULL) { return -1; } top_items[3] = NULL; set_item_userptr(top_items[0], (char *) file_item_callback); set_item_userptr(top_items[1], (char *) edit_item_callback); set_item_userptr(top_items[2], (char *) view_item_callback); if (file_item_init() != 0) return -1; if (edit_item_init() != 0) return -1; if (view_item_init() != 0) return -1; top_menu = new_menu(top_items); if (top_menu == NULL) { return -1; } menu_opts_off(top_menu, O_ROWMAJOR); set_menu_format(top_menu, 1, 4); scale_menu(top_menu, &top_rows, &top_cols); if ((top_win = newwin(1, top_cols, 0,0)) == NULL) { return -1; } keypad(top_win, TRUE); nodelay(top_win, FALSE); set_menu_sub(top_menu, top_win); col_width = top_cols / 4; if ((status = post_menu(top_menu)) < 0) { return -1; } wrefresh(top_win); menu_ready = 1; return 0; } void menu_escape(void) { if (!menu_ready) return; menu_mode = !menu_mode; touchwin(top_win); wrefresh(top_win); }