/* * This is a curses menu wrapper as part of ncurses-ruby. * It borrows heavily from form_wrap.c. * Contributed by Earle Clubb * Valcom Inc. * Copyright 2007 * * Modifications * (C) 2009 Tobias Herzke * * This module is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This module is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this module; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Changes * (C) 2011 Tobias Herzke */ #if defined(HAVE_MENU_H) || defined(HAVE_NCURSESW_MENU_H) #include "menu_wrap.h" #include "ncurses_wrap.h" #include "compat.h" VALUE mMenu; VALUE cITEM; VALUE cMENU; void init_menu_err_codes(void) { /* The routine succeeded. */ MENU_DEF_CONST(E_OK); /* System error occurred (see errno). */ MENU_DEF_CONST(E_SYSTEM_ERROR); /* Routine detected an incorrect or out-of-range argument. */ MENU_DEF_CONST(E_BAD_ARGUMENT); /* The menu is already posted. */ MENU_DEF_CONST(E_POSTED); /* Routine was called from an initialization or termination function. */ MENU_DEF_CONST(E_BAD_STATE); /* Menu is too large for its window. */ MENU_DEF_CONST(E_NO_ROOM); /* The menu has not been posted. */ MENU_DEF_CONST(E_NOT_POSTED); /* The menu driver code saw an unknown request code. */ MENU_DEF_CONST(E_UNKNOWN_COMMAND); /* Character failed to match. */ MENU_DEF_CONST(E_NO_MATCH); /* The designated item cannot be selected. */ MENU_DEF_CONST(E_NOT_SELECTABLE); /* No items are connected to the menu. */ MENU_DEF_CONST(E_NOT_CONNECTED); /* The menu driver could not process the request.} */ MENU_DEF_CONST(E_REQUEST_DENIED); } /* * Menu driver request characters - menu_driver(3x) man page */ void init_menu_req_constants(void) { /* Move left to an item. */ MENU_DEF_CONST(REQ_LEFT_ITEM); /* Move right to an item. */ MENU_DEF_CONST(REQ_RIGHT_ITEM); /* Move up to an item. */ MENU_DEF_CONST(REQ_UP_ITEM); /* Move down to an item. */ MENU_DEF_CONST(REQ_DOWN_ITEM); /* Scroll up a line. */ MENU_DEF_CONST(REQ_SCR_ULINE); /* Scroll down a line. */ MENU_DEF_CONST(REQ_SCR_DLINE); /* Scroll up a page. */ MENU_DEF_CONST(REQ_SCR_UPAGE); /* Scroll down a page. */ MENU_DEF_CONST(REQ_SCR_DPAGE); /* Move to the first item. */ MENU_DEF_CONST(REQ_FIRST_ITEM); /* Move to the last item. */ MENU_DEF_CONST(REQ_LAST_ITEM); /* Move to the next item. */ MENU_DEF_CONST(REQ_NEXT_ITEM); /* Move to the previous item. */ MENU_DEF_CONST(REQ_PREV_ITEM); /* Select/deselect an item. */ MENU_DEF_CONST(REQ_TOGGLE_ITEM); /* Clear the menu pattern buffer. */ MENU_DEF_CONST(REQ_CLEAR_PATTERN); /* Delete the previous character from the pattern buffer. */ MENU_DEF_CONST(REQ_BACK_PATTERN); /* Move to the next item matching the pattern match. */ MENU_DEF_CONST(REQ_NEXT_MATCH); /* Move to the previous item matching the pattern match. */ MENU_DEF_CONST(REQ_PREV_MATCH); } /* * Item options - mitem_opts(3x) man page */ void init_item_opts_constants(void) { /* Item may be selected during menu processing. */ MENU_DEF_CONST(O_SELECTABLE); } /* * Menu options - menu_opts(3x) man page */ void init_menu_opts_constants(void) { /* Only one item can be selected for this menu. */ MENU_DEF_CONST(O_ONEVALUE); /* Display the item descriptions when the menu is posted. */ MENU_DEF_CONST(O_SHOWDESC); /* Display the menu in row-major order. */ MENU_DEF_CONST(O_ROWMAJOR); /* Ignore the case when pattern-matching. */ MENU_DEF_CONST(O_IGNORECASE); /* Move the cursor to within the item name while pattern-matching. */ MENU_DEF_CONST(O_SHOWMATCH); /* Don’t wrap around next-item and previous-item, requests to the other end of the menu. */ MENU_DEF_CONST(O_NONCYCLIC); } /* * ITEM wrapper */ static VALUE wrap_item(ITEM *item) { if (item == 0) return Qnil; { VALUE items_hash = rb_iv_get(mMenu, "@items_hash"); VALUE item_address = INT2NUM((long)(item)); VALUE rb_item = rb_hash_aref(items_hash, item_address); if (rb_item == Qnil) { rb_item = Data_Wrap_Struct(cITEM, 0, 0, item); rb_iv_set(rb_item, "@destroyed", Qfalse); rb_hash_aset(items_hash, item_address, rb_item); } return rb_item; } } static ITEM *get_item(VALUE rb_item) { ITEM *item; if (rb_item == Qnil) return 0; if (rb_iv_get(rb_item, "@destroyed") == Qtrue) { rb_raise(rb_eRuntimeError, "Attempt to access a destroyed item"); return 0; } Data_Get_Struct(rb_item, ITEM, item); return item; } /* * MENU wrapper */ static VALUE wrap_menu(MENU *menu) { if (menu == 0) return Qnil; { VALUE menus_hash = rb_iv_get(mMenu, "@menus_hash"); VALUE menu_address = INT2NUM((long)(menu)); VALUE rb_menu = rb_hash_aref(menus_hash, menu_address); if (rb_menu == Qnil) { rb_menu = Data_Wrap_Struct(cMENU, 0, 0, menu); rb_iv_set(rb_menu, "@destroyed", Qfalse); rb_hash_aset(menus_hash, menu_address, rb_menu); } return rb_menu; } } static MENU *get_menu(VALUE rb_menu) { MENU *menu; if (rb_menu == Qnil) return 0; if (rb_iv_get(rb_menu, "@destroyed") == Qtrue) { rb_raise(rb_eRuntimeError, "Attempt to access a destroyed menu"); return 0; } Data_Get_Struct(rb_menu, MENU, menu); return menu; } /* * Proc objects are registered using hashes (one for each type of hook) * The key in the hash is the address of the ncurses "object" and the value is * the Proc object. */ #define ITEM_INIT_HOOK 0 #define ITEM_TERM_HOOK 1 #define MENU_INIT_HOOK 2 #define MENU_TERM_HOOK 3 #define PROC_HASHES_COUNT 4 static VALUE get_proc_hash(int hook) { VALUE arr = rb_iv_get(mMenu, "@proc_hashes"); VALUE hash = rb_ary_entry(arr, (long)hook); if (hash == Qnil) rb_raise(rb_eRuntimeError, "Invalid proc hash."); return hash; } /* * Returns an existing Ruby Proc for a given owning "object" and hook type. * Qnil will be returned if no Proc was associated with the owner */ static VALUE get_proc(void *owner, int hook) { if (owner == 0) return Qnil; { VALUE owner_address = INT2NUM((long)(owner)); VALUE proc_hash = get_proc_hash(hook); VALUE proc = rb_hash_aref(proc_hash, owner_address); return proc; } } /* * Registers the Proc object with a given owner "object" and hook type. * If proc is Qnil, the hook is unregistered instead. */ static void reg_proc(void *owner, int hook, VALUE proc) { if (owner == NULL) return; { VALUE proc_hash = get_proc_hash(hook); VALUE owner_address = INT2NUM((long)(owner)); if (proc == Qnil) rb_hash_delete(proc_hash, owner_address); else rb_hash_aset(proc_hash, owner_address, proc); } } /* * Menu creation/destruction functions - menu_new(3X) man page */ static VALUE rbncurs_m_new_menu(VALUE dummy, VALUE rb_item_array) { long n = rbncurs_array_length(rb_item_array); /* Will ncurses free this array? If not, must do it after calling free_menu(). */ ITEM **items = ALLOC_N(ITEM*, (n+1)); long i; for (i=0; i