ncmenu: reject double menu

pull/319/head
nick black 4 years ago
parent 039a390877
commit 10a269c963
No known key found for this signature in database
GPG Key ID: 5F43400C21CBFACC

@ -2232,7 +2232,7 @@ int ncmenu_unroll(struct ncmenu* n, int sectionidx);
int ncmenu_rollup(struct ncmenu* n);
// Destroy a menu created with ncmenu_create().
int ncmenu_destroy(struct ncmenu* n);
int ncmenu_destroy(struct notcurses* nc, struct ncmenu* n);
```
### Channels

@ -38,7 +38,7 @@ typedef struct ncmenu_options {
**int ncmenu_rollup(struct ncmenu* n);**
**int ncmenu_destroy(struct ncmenu* n);**
**int ncmenu_destroy(struct notcurses* nc, struct ncmenu* n);**
# DESCRIPTION

@ -2254,7 +2254,7 @@ API int ncmenu_nextitem(struct ncmenu* n);
API int ncmenu_previtem(struct ncmenu* n);
// Destroy a menu created with ncmenu_create().
API int ncmenu_destroy(struct ncmenu* n);
API int ncmenu_destroy(struct notcurses* nc, struct ncmenu* n);
#undef API

@ -213,6 +213,27 @@ write_header(ncmenu* ncm){ ncm->ncp->channels = ncm->headerchannels;
return 0;
}
// lock the notcurses object, and try to set up the new menu. return -1 if a
// menu already exists in this position.
static int
set_menu(notcurses* nc, ncmenu* ncm){
int ret = -1;
pthread_mutex_lock(&nc->lock);
if(ncm->bottom){
if(!nc->bottommenu){
nc->bottommenu = ncm;
ret = 0;
}
}else{
if(!nc->topmenu){
nc->topmenu = ncm;
ret = 0;
}
}
pthread_mutex_unlock(&nc->lock);
return ret;
}
ncmenu* ncmenu_create(notcurses* nc, const ncmenu_options* opts){
if(opts->sectioncount <= 0 || !opts->sections){
return NULL;
@ -238,7 +259,9 @@ ncmenu* ncmenu_create(notcurses* nc, const ncmenu_options* opts){
ret->headerchannels = opts->headerchannels;
ret->sectionchannels = opts->sectionchannels;
if(write_header(ret) == 0){
return ret;
if(set_menu(nc, ret) == 0){
return ret;
}
}
ncplane_destroy(ret->ncp);
}
@ -402,12 +425,20 @@ int ncmenu_previtem(ncmenu* n){
return ncmenu_unroll(n, n->unrolledsection);
}
int ncmenu_destroy(ncmenu* n){
int ncmenu_destroy(notcurses* nc, ncmenu* n){
int ret = 0;
if(n){
free_menu_sections(n);
ncplane_destroy(n->ncp);
free(n);
pthread_mutex_lock(&nc->lock);
if(nc->topmenu == n){
nc->topmenu = NULL;
}
if(nc->bottommenu == n){
nc->bottommenu = NULL;
}
pthread_mutex_unlock(&nc->lock);
}
return ret;
}

@ -980,6 +980,12 @@ int notcurses_stop(notcurses* nc){
int ret = 0;
if(nc){
ret |= notcurses_stop_minimal(nc);
if(nc->topmenu){
ncmenu_destroy(nc, nc->topmenu);
}
if(nc->bottommenu){
ncmenu_destroy(nc, nc->bottommenu);
}
while(nc->top){
ncplane* p = nc->top;
nc->top = p->z;

@ -44,12 +44,12 @@ run_menu(struct notcurses* nc, struct ncmenu* ncm){
break;
}
}else if(keypress == 'q'){
ncmenu_destroy(ncm);
ncmenu_destroy(nc, ncm);
return 0;
}
notcurses_render(nc);
}
ncmenu_destroy(ncm);
ncmenu_destroy(nc, ncm);
return -1;
}

@ -23,7 +23,7 @@ TEST_CASE("MenuTest") {
struct ncmenu* ncm = ncmenu_create(nc_, &opts);
REQUIRE(nullptr == ncm);
CHECK(0 == notcurses_render(nc_));
ncmenu_destroy(ncm);
ncmenu_destroy(nc_, ncm);
}
SUBCASE("EmptyMenuBottomReject") {
@ -32,7 +32,7 @@ TEST_CASE("MenuTest") {
struct ncmenu* ncm = ncmenu_create(nc_, &opts);
REQUIRE(nullptr == ncm);
CHECK(0 == notcurses_render(nc_));
ncmenu_destroy(ncm);
ncmenu_destroy(nc_, ncm);
}
// an empty section ought be rejected
@ -46,7 +46,7 @@ TEST_CASE("MenuTest") {
struct ncmenu* ncm = ncmenu_create(nc_, &opts);
REQUIRE(nullptr == ncm);
CHECK(0 == notcurses_render(nc_));
ncmenu_destroy(ncm);
ncmenu_destroy(nc_, ncm);
}
// a section with only separators ought be rejected
@ -63,7 +63,7 @@ TEST_CASE("MenuTest") {
struct ncmenu* ncm = ncmenu_create(nc_, &opts);
REQUIRE(nullptr == ncm);
CHECK(0 == notcurses_render(nc_));
ncmenu_destroy(ncm);
ncmenu_destroy(nc_, ncm);
}
SUBCASE("MenuOneSection") {
@ -79,7 +79,60 @@ TEST_CASE("MenuTest") {
struct ncmenu* ncm = ncmenu_create(nc_, &opts);
REQUIRE(nullptr != ncm);
CHECK(0 == notcurses_render(nc_));
ncmenu_destroy(ncm);
ncmenu_destroy(nc_, ncm);
}
SUBCASE("RejectDoubleTopMenu") {
struct ncmenu_item file_items[] = {
{ .desc = strdup("I would like a new file"), .shortcut = {}, },
};
struct ncmenu_section sections[] = {
{ .name = strdup("File"), .itemcount = sizeof(file_items) / sizeof(*file_items), .items = file_items, .shortcut{}, },
};
struct ncmenu_options opts{};
opts.sections = sections;
opts.sectioncount = sizeof(sections) / sizeof(*sections);
struct ncmenu* ncm = ncmenu_create(nc_, &opts);
REQUIRE(nullptr != ncm);
struct ncmenu* ncmdup = ncmenu_create(nc_, &opts);
REQUIRE(nullptr == ncmdup);
CHECK(0 == notcurses_render(nc_));
ncmenu_destroy(nc_, ncm);
}
SUBCASE("RejectDoubleBottomMenu") {
struct ncmenu_item file_items[] = {
{ .desc = strdup("I would like a new file"), .shortcut = {}, },
};
struct ncmenu_section sections[] = {
{ .name = strdup("File"), .itemcount = sizeof(file_items) / sizeof(*file_items), .items = file_items, .shortcut{}, },
};
struct ncmenu_options opts{};
opts.bottom = true;
opts.sections = sections;
opts.sectioncount = sizeof(sections) / sizeof(*sections);
struct ncmenu* ncm = ncmenu_create(nc_, &opts);
REQUIRE(nullptr != ncm);
struct ncmenu* ncmdup = ncmenu_create(nc_, &opts);
REQUIRE(nullptr == ncmdup);
CHECK(0 == notcurses_render(nc_));
ncmenu_destroy(nc_, ncm);
}
// don't call ncmenu_destroy(), invoking destruction in notcurses_stop()
SUBCASE("MenuNoFree") {
struct ncmenu_item file_items[] = {
{ .desc = strdup("I would like a new file"), .shortcut = {}, },
};
struct ncmenu_section sections[] = {
{ .name = strdup("File"), .itemcount = sizeof(file_items) / sizeof(*file_items), .items = file_items, .shortcut{}, },
};
struct ncmenu_options opts{};
opts.sections = sections;
opts.sectioncount = sizeof(sections) / sizeof(*sections);
struct ncmenu* ncm = ncmenu_create(nc_, &opts);
REQUIRE(nullptr != ncm);
CHECK(0 == notcurses_render(nc_));
}
SUBCASE("VeryLongMenu") {
@ -99,7 +152,7 @@ TEST_CASE("MenuTest") {
struct ncmenu* ncm = ncmenu_create(nc_, &opts);
REQUIRE(nullptr != ncm);
CHECK(0 == notcurses_render(nc_));
ncmenu_destroy(ncm);
ncmenu_destroy(nc_, ncm);
}
CHECK(0 == notcurses_stop(nc_));

Loading…
Cancel
Save