@ -2,7 +2,8 @@
# include "oso.h"
# include "oso.h"
# include <ctype.h>
# include <ctype.h>
# include <form.h>
# include <form.h>
# include <menu.h>
ORCA_NOINLINE static void qmenu_reprint ( Qmenu * qm ) ;
void term_util_init_colors ( ) {
void term_util_init_colors ( ) {
if ( has_colors ( ) ) {
if ( has_colors ( ) ) {
@ -38,20 +39,18 @@ struct Qmsg {
Qmsg_dismiss_mode dismiss_mode ;
Qmsg_dismiss_mode dismiss_mode ;
} ;
} ;
struct Qmenu_item_extra {
typedef struct Qmenu_item {
int user_id ;
char const * text ;
int id ;
U8 owns_string : 1 , is_spacer : 1 ;
U8 owns_string : 1 , is_spacer : 1 ;
} ;
} Qmenu_item ;
struct Qmenu {
struct Qmenu {
Qblock qblock ;
Qblock qblock ;
MENU * ncurses_menu ;
Qmenu_item * items ;
ITEM * * ncurses_items ;
Usz items_count , items_cap ;
Usz items_count , items_cap ;
ITEM * initial_item ;
int current_item , id ;
int id ;
U8 needs_reprint : 1 , is_frontmost : 1 ;
// Flag for right-padding hack. Temp until we do our own menus
U8 has_submenu_item : 1 ;
} ;
} ;
struct Qform {
struct Qform {
@ -175,18 +174,26 @@ bool qnav_draw(void) {
getmaxyx ( stdscr , term_h , term_w ) ;
getmaxyx ( stdscr , term_h , term_w ) ;
for ( Usz i = 0 ; i < qnav_stack . count ; + + i ) {
for ( Usz i = 0 ; i < qnav_stack . count ; + + i ) {
Qblock * qb = qnav_stack . blocks [ i ] ;
Qblock * qb = qnav_stack . blocks [ i ] ;
if ( qnav_stack . occlusion_dirty ) {
bool is_frontmost = i = = qnav_stack . count - 1 ;
bool is_frontmost = i = = qnav_stack . count - 1 ;
if ( qnav_stack . occlusion_dirty )
qblock_print_frame ( qb , is_frontmost ) ;
qblock_print_frame ( qb , is_frontmost ) ;
switch ( qb - > tag ) {
switch ( qb - > tag ) {
case Qblock_type_qmsg :
case Qblock_type_qmsg :
break ;
break ;
case Qblock_type_qmenu :
case Qblock_type_qmenu : {
qmenu_set_displayed_active ( qmenu_of ( qb ) , is_frontmost ) ;
Qmenu * qm = qmenu_of ( qb ) ;
break ;
if ( qm - > is_frontmost ! = is_frontmost ) {
case Qblock_type_qform :
qm - > is_frontmost = is_frontmost ;
break ;
qm - > needs_reprint = 1 ;
}
if ( qm - > needs_reprint ) {
qmenu_reprint ( qm ) ;
qm - > needs_reprint = 0 ;
}
}
break ;
}
case Qblock_type_qform :
break ;
}
}
touchwin ( qb - > outer_window ) ; // here? or after continue?
touchwin ( qb - > outer_window ) ; // here? or after continue?
if ( term_h < 1 | | term_w < 1 )
if ( term_h < 1 | | term_w < 1 )
@ -222,7 +229,7 @@ void qblock_print_title(Qblock *qb, char const *title, int attr) {
wattr_get ( qb - > outer_window , & attrs , & pair , NULL ) ;
wattr_get ( qb - > outer_window , & attrs , & pair , NULL ) ;
wattrset ( qb - > outer_window , attr ) ;
wattrset ( qb - > outer_window , attr ) ;
waddch ( qb - > outer_window , ' ' ) ;
waddch ( qb - > outer_window , ' ' ) ;
wprintw ( qb - > outer_window , title ) ;
waddstr ( qb - > outer_window , title ) ;
waddch ( qb - > outer_window , ' ' ) ;
waddch ( qb - > outer_window , ' ' ) ;
wattr_set ( qb - > outer_window , attrs , pair , NULL ) ;
wattr_set ( qb - > outer_window , attrs , pair , NULL ) ;
}
}
@ -343,91 +350,65 @@ Qmsg *qmsg_of(Qblock *qb) { return ORCA_CONTAINER_OF(qb, Qmsg, qblock); }
Qmenu * qmenu_create ( int id ) {
Qmenu * qmenu_create ( int id ) {
Qmenu * qm = ( Qmenu * ) malloc ( sizeof ( Qmenu ) ) ;
Qmenu * qm = ( Qmenu * ) malloc ( sizeof ( Qmenu ) ) ;
qblock_init ( & qm - > qblock , Qblock_type_qmenu ) ;
qblock_init ( & qm - > qblock , Qblock_type_qmenu ) ;
qm - > ncurses_menu = NULL ;
qm - > items = NULL ;
qm - > ncurses_items = NULL ;
qm - > items_count = 0 ;
qm - > items_count = 0 ;
qm - > items_cap = 0 ;
qm - > items_cap = 0 ;
qm - > initial_item = NULL ;
qm - > current_item = 0 ;
qm - > id = id ;
qm - > id = id ;
qm - > has_submenu_item = 0 ;
qm - > needs_reprint = 1 ;
qm - > is_frontmost = 0 ;
return qm ;
return qm ;
}
}
void qmenu_destroy ( Qmenu * qm ) { qmenu_free ( qm ) ; }
void qmenu_destroy ( Qmenu * qm ) { qmenu_free ( qm ) ; }
int qmenu_id ( Qmenu const * qm ) { return qm - > id ; }
int qmenu_id ( Qmenu const * qm ) { return qm - > id ; }
static ORCA_NOINLINE void
static ORCA_NOINLINE Qmenu_item * qmenu_allocitems ( Qmenu * qm , Usz count ) {
qmenu_allocitems ( Qmenu * qm , Usz count , Usz * out_idx , ITEM * * * out_items ,
struct Qmenu_item_extra * * out_extras ) {
Usz old_count = qm - > items_count ;
Usz old_count = qm - > items_count ;
// Add 1 for the extra null terminator guy
if ( old_count > SIZE_MAX - count ) // overflow
Usz new_count = old_count + count + 1 ;
exit ( 1 ) ;
Usz new_count = old_count + count ;
Usz items_cap = qm - > items_cap ;
Usz items_cap = qm - > items_cap ;
ITEM * * items = qm - > ncurses_ items;
Qmenu_item * items = qm - > items ;
if ( new_count > items_cap ) {
if ( new_count > items_cap ) {
// todo overflow check, realloc fail check
// todo overflow check, realloc fail check
Usz old_cap = items_cap ;
Usz new_cap = new_count < 32 ? 32 : orca_round_up_power2 ( new_count ) ;
Usz new_cap = new_count < 32 ? 32 : orca_round_up_power2 ( new_count ) ;
Usz new_size = new_cap * ( sizeof ( ITEM * ) + sizeof ( struct Qmenu_item_extra ) ) ;
Usz new_size = new_cap * sizeof ( Qmenu_item ) ;
ITEM * * new_items = ( ITEM * * ) realloc ( items , new_size ) ;
Qmenu_item * new_items = ( Qmenu_item * ) realloc ( items , new_size ) ;
if ( ! new_items )
if ( ! new_items )
exit ( 1 ) ;
exit ( 1 ) ;
items = new_items ;
items = new_items ;
items_cap = new_cap ;
items_cap = new_cap ;
// Move old extras data to new position
qm - > items = new_items ;
Usz old_extras_offset = sizeof ( ITEM * ) * old_cap ;
Usz new_extras_offset = sizeof ( ITEM * ) * new_cap ;
Usz old_extras_size = sizeof ( struct Qmenu_item_extra ) * old_count ;
memmove ( ( char * ) items + new_extras_offset ,
( char * ) items + old_extras_offset , old_extras_size ) ;
qm - > ncurses_items = new_items ;
qm - > items_cap = new_cap ;
qm - > items_cap = new_cap ;
}
}
// Not using new_count here in order to leave an extra 1 for the null
qm - > items_count = new_count ;
// terminator as required by ncurses.
return items + old_count ;
qm - > items_count = old_count + count ;
}
Usz extras_offset = sizeof ( ITEM * ) * items_cap ;
ORCA_NOINLINE static void qmenu_reprint ( Qmenu * qm ) {
* out_idx = old_count ;
WINDOW * win = qm - > qblock . content_window ;
* out_items = items + old_count ;
Qmenu_item * items = qm - > items ;
* out_extras =
bool isfront = qm - > is_frontmost ;
( struct Qmenu_item_extra * ) ( ( char * ) items + extras_offset ) + old_count ;
werase ( win ) ;
}
for ( Usz i = 0 , n = qm - > items_count ; i < n ; + + i ) {
ORCA_FORCEINLINE static struct Qmenu_item_extra *
bool iscur = items [ i ] . id = = qm - > current_item ;
qmenu_item_extras_ptr ( Qmenu * qm ) {
wattrset ( win , isfront ? iscur ? A_BOLD : A_NORMAL : A_DIM ) ;
Usz offset = sizeof ( ITEM * ) * qm - > items_cap ;
wmove ( win , ( int ) i , iscur ? 1 : 3 ) ;
return ( struct Qmenu_item_extra * ) ( ( char * ) qm - > ncurses_items + offset ) ;
if ( iscur )
}
waddstr ( win , " > " ) ;
// Get the curses menu item user pointer out, turn it to an int, and use it as
waddstr ( win , items [ i ] . text ) ;
// an index into the 'extras' arrays.
}
ORCA_FORCEINLINE static struct Qmenu_item_extra *
qmenu_itemextra ( struct Qmenu_item_extra * extras , ITEM * item ) {
return extras + ( int ) ( intptr_t ) ( item_userptr ( item ) ) ;
}
}
void qmenu_set_title ( Qmenu * qm , char const * title ) {
void qmenu_set_title ( Qmenu * qm , char const * title ) {
qblock_set_title ( & qm - > qblock , title ) ;
qblock_set_title ( & qm - > qblock , title ) ;
}
}
void qmenu_add_choice ( Qmenu * qm , int id , char const * text ) {
void qmenu_add_choice ( Qmenu * qm , int id , char const * text ) {
assert ( id ! = 0 ) ;
assert ( id ! = 0 ) ;
Usz idx ;
Qmenu_item * item = qmenu_allocitems ( qm , 1 ) ;
ITEM * * items ;
item - > text = text ;
struct Qmenu_item_extra * extras ;
item - > id = id ;
qmenu_allocitems ( qm , 1 , & idx , & items , & extras ) ;
item - > owns_string = false ;
items [ 0 ] = new_item ( text , NULL ) ;
item - > is_spacer = false ;
set_item_userptr ( items [ 0 ] , ( void * ) ( uintptr_t ) idx ) ;
if ( ! qm - > current_item )
extras [ 0 ] . user_id = id ;
qm - > current_item = id ;
extras [ 0 ] . owns_string = false ;
extras [ 0 ] . is_spacer = false ;
}
void qmenu_add_submenu ( Qmenu * qm , int id , char const * text ) {
assert ( id ! = 0 ) ;
qm - > has_submenu_item = true ; // don't add +1 right padding to subwindow
Usz idx ;
ITEM * * items ;
struct Qmenu_item_extra * extras ;
qmenu_allocitems ( qm , 1 , & idx , & items , & extras ) ;
items [ 0 ] = new_item ( text , " > " ) ;
set_item_userptr ( items [ 0 ] , ( void * ) ( uintptr_t ) idx ) ;
extras [ 0 ] . user_id = id ;
extras [ 0 ] . owns_string = false ;
extras [ 0 ] . is_spacer = false ;
}
}
void qmenu_add_printf ( Qmenu * qm , int id , char const * fmt , . . . ) {
void qmenu_add_printf ( Qmenu * qm , int id , char const * fmt , . . . ) {
va_list ap ;
va_list ap ;
@ -442,85 +423,42 @@ void qmenu_add_printf(Qmenu *qm, int id, char const *fmt, ...) {
va_end ( ap ) ;
va_end ( ap ) ;
if ( printedsize ! = textsize )
if ( printedsize ! = textsize )
exit ( 1 ) ; // todo better handling?
exit ( 1 ) ; // todo better handling?
Usz idx ;
Qmenu_item * item = qmenu_allocitems ( qm , 1 ) ;
ITEM * * items ;
item - > text = buffer ;
struct Qmenu_item_extra * extras ;
item - > id = id ;
qmenu_allocitems ( qm , 1 , & idx , & items , & extras ) ;
item - > owns_string = true ;
items [ 0 ] = new_item ( buffer , NULL ) ;
item - > is_spacer = false ;
set_item_userptr ( items [ 0 ] , ( void * ) ( uintptr_t ) idx ) ;
if ( ! qm - > current_item )
extras [ 0 ] . user_id = id ;
qm - > current_item = id ;
extras [ 0 ] . owns_string = true ;
extras [ 0 ] . is_spacer = false ;
}
}
void qmenu_add_spacer ( Qmenu * qm ) {
void qmenu_add_spacer ( Qmenu * qm ) {
Usz idx ;
Qmenu_item * item = qmenu_allocitems ( qm , 1 ) ;
ITEM * * items ;
item - > text = " " ;
struct Qmenu_item_extra * extras ;
item - > id = 0 ;
qmenu_allocitems ( qm , 1 , & idx , & items , & extras ) ;
item - > owns_string = false ;
items [ 0 ] = new_item ( " " , NULL ) ;
item - > is_spacer = true ;
item_opts_off ( items [ 0 ] , O_SELECTABLE ) ;
set_item_userptr ( items [ 0 ] , ( void * ) ( uintptr_t ) idx ) ;
extras [ 0 ] . user_id = 0 ;
extras [ 0 ] . owns_string = false ;
extras [ 0 ] . is_spacer = true ;
}
}
void qmenu_set_current_item ( Qmenu * qm , int id ) {
void qmenu_set_current_item ( Qmenu * qm , int id ) {
ITEM * * items = qm - > ncurses_items ;
if ( qm - > current_item = = id )
struct Qmenu_item_extra * extras = qmenu_item_extras_ptr ( qm ) ;
ITEM * found = NULL ;
for ( Usz i = 0 , n = qm - > items_count ; i < n ; i + + ) {
ITEM * item = items [ i ] ;
if ( qmenu_itemextra ( extras , item ) - > user_id = = id ) {
found = item ;
break ;
}
}
if ( ! found )
return ;
return ;
if ( qm - > ncurses_menu ) {
qm - > current_item = id ;
set_current_item ( qm - > ncurses_menu , found ) ;
qm - > needs_reprint = 1 ;
} else {
qm - > initial_item = found ;
}
}
int qmenu_current_item ( Qmenu * qm ) {
ITEM * item = NULL ;
if ( qm - > ncurses_menu )
item = current_item ( qm - > ncurses_menu ) ;
if ( ! item )
item = qm - > initial_item ;
if ( ! item )
return 0 ;
struct Qmenu_item_extra * extras = qmenu_item_extras_ptr ( qm ) ;
return qmenu_itemextra ( extras , item ) - > user_id ;
}
void qmenu_set_displayed_active ( Qmenu * qm , bool active ) {
// Could add a flag in the Qmenu to avoid redundantly changing this stuff.
set_menu_fore ( qm - > ncurses_menu , active ? A_BOLD : A_DIM ) ;
set_menu_back ( qm - > ncurses_menu , active ? A_NORMAL : A_DIM ) ;
set_menu_grey ( qm - > ncurses_menu , active ? A_DIM : A_DIM ) ;
}
}
int qmenu_current_item ( Qmenu * qm ) { return qm - > current_item ; }
void qmenu_push_to_nav ( Qmenu * qm ) {
void qmenu_push_to_nav ( Qmenu * qm ) {
// new_menu() will get angry if there are no items in the menu. We'll get a
// Probably a programming error if there are no items. Make the menu visible
// null pointer back, and our code will get angry. Instead, just add an empty
// so the programmer knows something went wrong.
// spacer item. This will probably only ever occur as a programming error,
// but we should try to avoid having to deal with qmenu_push_to_nav()
// returning a non-ignorable error for now.
if ( qm - > items_count = = 0 )
if ( qm - > items_count = = 0 )
qmenu_add_spacer ( qm ) ;
qmenu_add_spacer ( qm ) ;
// Allocating items always leaves an extra available item at the end. This is
Usz n = qm - > items_count ;
// so we can assign a NULL to it here, since ncurses requires the array to be
Qmenu_item * items = qm - > items ;
// null terminated instead of using a count.
int menu_min_h = ( int ) n , menu_min_w = 0 ;
qm - > ncurses_items [ qm - > items_count ] = NULL ;
for ( Usz i = 0 ; i < n ; + + i ) {
qm - > ncurses_menu = new_menu ( qm - > ncurses_items ) ;
int item_w = ( int ) strlen ( items [ i ] . text ) ;
set_menu_mark ( qm - > ncurses_menu , " > " ) ;
if ( item_w > menu_min_w )
set_menu_fore ( qm - > ncurses_menu , A_BOLD ) ;
menu_min_w = item_w ;
set_menu_grey ( qm - > ncurses_menu , A_DIM ) ;
}
set_menu_format ( qm - > ncurses_menu , 30 , 1 ) ; // temp to allow large Y
menu_min_w + = 3 + 1 ; // left " > " plus 1 empty space on right
int menu_min_h , menu_min_w ;
scale_menu ( qm - > ncurses_menu , & menu_min_h , & menu_min_w ) ;
if ( ! qm - > has_submenu_item )
menu_min_w + = 1 ; // temp hack
if ( qm - > qblock . title ) {
if ( qm - > qblock . title ) {
// Stupid lack of wcswidth() means we can't know how wide this string is
// Stupid lack of wcswidth() means we can't know how wide this string is
// actually displayed. Just fake it for now, until we have Unicode strings
// actually displayed. Just fake it for now, until we have Unicode strings
@ -529,58 +467,50 @@ void qmenu_push_to_nav(Qmenu *qm) {
if ( title_w > menu_min_w )
if ( title_w > menu_min_w )
menu_min_w = title_w ;
menu_min_w = title_w ;
}
}
if ( qm - > initial_item )
set_current_item ( qm - > ncurses_menu , qm - > initial_item ) ;
qnav_stack_push ( & qm - > qblock , menu_min_h , menu_min_w ) ;
qnav_stack_push ( & qm - > qblock , menu_min_h , menu_min_w ) ;
set_menu_win ( qm - > ncurses_menu , qm - > qblock . outer_window ) ;
set_menu_sub ( qm - > ncurses_menu , qm - > qblock . content_window ) ;
// TODO use this to set how "big" the menu is, visually, for scrolling.
// (ncurses can't figure that out on its own, aparently...)
// We'll need to split apart some work chunks so that we calculate the size
// beforehand.
// set_menu_format(qm->ncurses_menu, 5, 1);
post_menu ( qm - > ncurses_menu ) ;
}
}
void qmenu_free ( Qmenu * qm ) {
void qmenu_free ( Qmenu * qm ) {
unpost_menu ( qm - > ncurses_menu ) ;
Qmenu_item * items = qm - > items ;
free_menu ( qm - > ncurses_menu ) ;
for ( Usz i = 0 , n = qm - > items_count ; i < n ; + + i ) {
struct Qmenu_item_extra * extras = qmenu_item_extras_ptr ( qm ) ;
if ( items [ i ] . owns_string )
for ( Usz i = 0 ; i < qm - > items_count ; + + i ) {
free ( ( void * ) items [ i ] . text ) ;
ITEM * item = qm - > ncurses_items [ i ] ;
struct Qmenu_item_extra * extra = qmenu_itemextra ( extras , item ) ;
char const * freed_str = NULL ;
if ( extra - > owns_string )
freed_str = item_name ( item ) ;
free_item ( qm - > ncurses_items [ i ] ) ;
if ( freed_str )
free ( ( void * ) freed_str ) ;
}
}
free ( qm - > ncurses_ items) ;
free ( qm - > items ) ;
free ( qm ) ;
free ( qm ) ;
}
}
ORCA_NOINLINE
ORCA_NOINLINE static void qmenu_drive_upordown ( Qmenu * qm , bool downwards ) {
static void qmenu_drive_upordown ( Qmenu * qm , int req_up_or_down ) {
Qmenu_item * items = qm - > items ;
struct Qmenu_item_extra * extras = qmenu_item_extras_ptr ( qm ) ;
Usz n = qm - > items_count ;
ITEM * starting = current_item ( qm - > ncurses_menu ) ;
if ( n < = 1 )
menu_driver ( qm - > ncurses_menu , req_up_or_down ) ;
return ;
ITEM * cur = current_item ( qm - > ncurses_menu ) ;
int cur_id = qm - > current_item ;
Usz starting = 0 ;
for ( ; starting < n ; + + starting ) {
if ( items [ starting ] . id = = cur_id )
goto found ;
}
return ;
found : ;
Usz current = starting ;
for ( ; ; ) {
for ( ; ; ) {
if ( ! cur | | cur = = starting )
if ( downwards & & current < n - 1 )
break ;
current + + ;
if ( ! qmenu_itemextra ( extras , cur ) - > is_spacer )
else if ( ! downwards & & current > 0 )
current - - ;
if ( current = = starting )
break ;
break ;
ITEM * prev = cur ;
if ( ! items [ current ] . is_spacer )
menu_driver ( qm - > ncurses_menu , req_up_or_down ) ;
cur = current_item ( qm - > ncurses_menu ) ;
if ( cur = = prev )
break ;
break ;
}
}
if ( current ! = starting ) {
qm - > current_item = items [ current ] . id ;
qm - > needs_reprint = 1 ;
}
}
}
bool qmenu_drive ( Qmenu * qm , int key , Qmenu_action * out_action ) {
bool qmenu_drive ( Qmenu * qm , int key , Qmenu_action * out_action ) {
struct Qmenu_item_extra * extras = qmenu_item_extras_ptr ( qm ) ;
switch ( key ) {
switch ( key ) {
case 27 : {
case 27 : {
out_action - > any . type = Qmenu_action_type_canceled ;
out_action - > any . type = Qmenu_action_type_canceled ;
@ -588,17 +518,15 @@ bool qmenu_drive(Qmenu *qm, int key, Qmenu_action *out_action) {
}
}
case ' ' :
case ' ' :
case ' \r ' :
case ' \r ' :
case KEY_ENTER : {
case KEY_ENTER :
ITEM * cur = current_item ( qm - > ncurses_menu ) ;
out_action - > picked . type = Qmenu_action_type_picked ;
out_action - > picked . type = Qmenu_action_type_picked ;
out_action - > picked . id = cur ? qmenu_itemextra ( extras , cur ) - > user_id : 0 ;
out_action - > picked . id = qm - > current_item ;
return true ;
return true ;
}
case KEY_UP :
case KEY_UP :
qmenu_drive_upordown ( qm , REQ_UP_ITEM ) ;
qmenu_drive_upordown ( qm , false ) ;
return false ;
return false ;
case KEY_DOWN :
case KEY_DOWN :
qmenu_drive_upordown ( qm , REQ_DOWN_ITEM ) ;
qmenu_drive_upordown ( qm , true ) ;
return false ;
return false ;
}
}
return false ;
return false ;