|
|
|
#include "demo.h"
|
|
|
|
|
|
|
|
// open up changes.jpg, stretch it to fill, drop it to greyscale
|
|
|
|
static int
|
|
|
|
draw_background(struct notcurses* nc){
|
|
|
|
if(notcurses_canopen_images(nc)){
|
|
|
|
struct ncplane* n = notcurses_stdplane(nc);
|
|
|
|
char* path = find_data("changes.jpg");
|
|
|
|
struct ncvisual* ncv = ncvisual_from_file(path);
|
|
|
|
free(path);
|
|
|
|
if(!ncv){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
struct ncvisual_options vopts = {
|
|
|
|
.scaling = NCSCALE_STRETCH,
|
|
|
|
.n = n,
|
|
|
|
};
|
|
|
|
if(ncvisual_render(nc, ncv, &vopts) == NULL){
|
|
|
|
ncvisual_destroy(ncv);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
ncplane_greyscale(n);
|
|
|
|
ncvisual_destroy(ncv);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
// we list all distributions on which notcurses is known to exist
|
|
|
|
static struct ncselector_item select_items[] = {
|
|
|
|
#define SITEM(short, long) { short, long, 0, 0, }
|
|
|
|
SITEM("fbsd", "FreeBSD"),
|
|
|
|
SITEM("deb", "Debian Unstable Linux"),
|
|
|
|
SITEM("rpm", "Fedora Rawhide Linux"),
|
|
|
|
SITEM("pac", "Arch Linux"),
|
|
|
|
SITEM("apk", "Alpine Edge Linux"),
|
|
|
|
SITEM(NULL, NULL),
|
|
|
|
#undef SITEM
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct ncmselector_item mselect_items[] = {
|
|
|
|
{ "Pa231", "Protactinium-231 (162kg)", .selected = false, },
|
|
|
|
{ "U233", "Uranium-233 (15kg)", .selected = false, },
|
|
|
|
{ "U235", "Uranium-235 (50kg)", .selected = false, },
|
|
|
|
{ "Np236", "Neptunium-236 (7kg)", .selected = false, },
|
|
|
|
{ "Np237", "Neptunium-237 (60kg)", .selected = false, },
|
|
|
|
{ "Pu238", "Plutonium-238 (10kg)", .selected = false, },
|
|
|
|
{ "Pu239", "Plutonium-239 (10kg)", .selected = false, },
|
|
|
|
{ "Pu240", "Plutonium-240 (40kg)", .selected = false, },
|
|
|
|
{ "Pu241", "Plutonium-241 (13kg)", .selected = false, },
|
|
|
|
{ "Am241", "Americium-241 (100kg)", .selected = false, },
|
|
|
|
{ "Pu242", "Plutonium-242 (100kg)", .selected = false, },
|
|
|
|
{ "Am242", "Americium-242 (18kg)", .selected = false, },
|
|
|
|
{ "Am243", "Americium-243 (155kg)", .selected = false, },
|
|
|
|
{ "Cm243", "Curium-243 (10kg)", .selected = false, },
|
|
|
|
{ "Cm244", "Curium-244 (30kg)", .selected = false, },
|
|
|
|
{ "Cm245", "Curium-245 (13kg)", .selected = false, },
|
|
|
|
{ "Cm246", "Curium-246 (84kg)", .selected = false, },
|
|
|
|
{ "Cm247", "Curium-247 (7kg)", .selected = false, },
|
|
|
|
{ "Bk247", "Berkelium-247 (10kg)", .selected = false, },
|
|
|
|
{ "Cf249", "Californium-249 (6kg)", .selected = false, },
|
|
|
|
{ "Cf251", "Californium-251 (9kg)", .selected = false, },
|
|
|
|
{ NULL, NULL, .selected = false, },
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct ncmultiselector*
|
|
|
|
multiselector_demo(struct ncplane* n, struct ncplane* under, int y){
|
|
|
|
ncmultiselector_options mopts = {
|
|
|
|
.maxdisplay = 8,
|
|
|
|
.title = "multi-item selector",
|
|
|
|
.items = mselect_items,
|
|
|
|
.boxchannels = CHANNELS_RGB_INITIALIZER(0x20, 0xe0, 0xe0, 0x20, 0, 0),
|
|
|
|
.opchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0x80, 0x40, 0, 0, 0),
|
|
|
|
.descchannels = CHANNELS_RGB_INITIALIZER(0x80, 0xe0, 0x40, 0, 0, 0),
|
|
|
|
.footchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0, 0x40, 0x20, 0x20, 0),
|
|
|
|
.titlechannels = CHANNELS_RGB_INITIALIZER(0x80, 0x80, 0xff, 0, 0, 0x20),
|
|
|
|
};
|
|
|
|
uint64_t bgchannels = CHANNELS_RGB_INITIALIZER(0, 0x40, 0, 0, 0x40, 0);
|
|
|
|
channels_set_fg_alpha(&bgchannels, CELL_ALPHA_BLEND);
|
|
|
|
channels_set_bg_alpha(&bgchannels, CELL_ALPHA_BLEND);
|
|
|
|
struct ncplane* mseln = ncplane_new(n, 1, 1, y, 0, NULL, NULL);
|
|
|
|
if(mseln == NULL){
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
ncplane_set_base(mseln, "", 0, bgchannels);
|
|
|
|
struct ncmultiselector* mselect = ncmultiselector_create(mseln, &mopts);
|
|
|
|
if(mselect == NULL){
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
struct ncplane* mplane = ncmultiselector_plane(mselect);
|
|
|
|
ncplane_move_below(mplane, under);
|
|
|
|
return mselect;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct ncselector*
|
|
|
|
selector_demo(struct ncplane* n, struct ncplane* under, int dimx, int y){
|
|
|
|
ncselector_options sopts = {
|
|
|
|
.title = "single-item selector",
|
|
|
|
.items = select_items,
|
|
|
|
.defidx = 4,
|
|
|
|
.maxdisplay = 3,
|
|
|
|
.boxchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0x20, 0x40, 0x20, 0x20, 0x20),
|
|
|
|
.opchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0x80, 0x40, 0, 0, 0),
|
|
|
|
.descchannels = CHANNELS_RGB_INITIALIZER(0x80, 0xe0, 0x40, 0, 0, 0),
|
|
|
|
.footchannels = CHANNELS_RGB_INITIALIZER(0xe0, 0, 0x40, 0x20, 0, 0),
|
|
|
|
.titlechannels = CHANNELS_RGB_INITIALIZER(0xff, 0xff, 0x80, 0, 0, 0x20),
|
|
|
|
};
|
|
|
|
uint64_t bgchannels = CHANNELS_RGB_INITIALIZER(0, 0, 0x40, 0, 0, 0x40);
|
|
|
|
channels_set_fg_alpha(&bgchannels, CELL_ALPHA_BLEND);
|
|
|
|
channels_set_bg_alpha(&bgchannels, CELL_ALPHA_BLEND);
|
|
|
|
struct ncplane* seln = ncplane_new(n, 1, 1, y, dimx, NULL, NULL);
|
|
|
|
if(seln == NULL){
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
ncplane_set_base(seln, "", 0, bgchannels);
|
|
|
|
struct ncselector* selector = ncselector_create(seln, &sopts);
|
|
|
|
if(selector == NULL){
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
ncplane_move_below(seln, under);
|
|
|
|
return selector;
|
|
|
|
}
|
|
|
|
|
|
|
|
// wait one demodelay period, offering input to the multiselector, then fade
|
|
|
|
// out both widgets (if supported).
|
|
|
|
static int
|
|
|
|
reader_post(struct notcurses* nc, struct ncselector* selector, struct ncmultiselector* mselector){
|
|
|
|
int ret;
|
|
|
|
struct timespec ts;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
|
|
uint64_t cur = timespec_to_ns(&ts);
|
|
|
|
uint64_t targ = cur + timespec_to_ns(&demodelay);
|
|
|
|
do{
|
|
|
|
struct timespec rel;
|
|
|
|
ns_to_timespec(targ - cur, &rel);
|
|
|
|
ncinput ni;
|
|
|
|
char32_t wc = demo_getc(nc, &rel, &ni);
|
|
|
|
if(wc == (char32_t)-1){
|
|
|
|
return -1;
|
|
|
|
}else if(wc){
|
|
|
|
ncmultiselector_offer_input(mselector, &ni);
|
|
|
|
}
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
|
|
cur = timespec_to_ns(&ts);
|
|
|
|
if( (ret = demo_render(nc)) ){
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
}while(cur < targ);
|
|
|
|
if(notcurses_canfade(nc)){
|
|
|
|
if(ncplane_fadeout(ncselector_plane(selector), &demodelay, demo_fader, NULL)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if(ncplane_fadeout(ncmultiselector_plane(mselector), &demodelay, demo_fader, NULL)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
layout_next_text(struct ncreader* reader, const char* text, size_t* textpos){
|
|
|
|
size_t towrite = strcspn(text + *textpos, " \t\n");
|
|
|
|
towrite += strspn(text + *textpos + towrite, " \t\n");
|
|
|
|
if(towrite){
|
|
|
|
char* duped = strndup(text + *textpos, towrite);
|
|
|
|
size_t bytes;
|
|
|
|
if(ncplane_puttext(ncreader_plane(reader), -1, NCALIGN_LEFT, duped, &bytes) < 0 || bytes != strlen(duped)){
|
|
|
|
free(duped);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
free(duped);
|
|
|
|
int y, x, posy, posx;
|
|
|
|
struct ncplane* ncp = ncreader_plane(reader);
|
|
|
|
ncplane_cursor_yx(ncp, &y, &x);
|
|
|
|
ncplane_yx(ncp, &posy, &posx);
|
|
|
|
int stdy = ncplane_dim_y(notcurses_stdplane(ncplane_notcurses(ncp)));
|
|
|
|
if(y + posy < stdy - 1){
|
|
|
|
// don't check for failure -- not supported under vt100
|
|
|
|
notcurses_cursor_enable(ncplane_notcurses(ncp), y + posy, x + posx);
|
|
|
|
}
|
|
|
|
*textpos += towrite;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
run_out_text(struct ncreader* reader, const char* text, size_t* textpos,
|
|
|
|
const struct timespec* iterdelay){
|
|
|
|
while(text[*textpos]){
|
|
|
|
int ret = layout_next_text(reader, text, textpos);
|
|
|
|
if(ret){
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
demo_nanosleep(ncplane_notcurses(ncreader_plane(reader)), iterdelay);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
get_word_count(const char* text){
|
|
|
|
bool inspace = true;
|
|
|
|
int words = 0;
|
|
|
|
while(*text){
|
|
|
|
if(isspace(*text)){
|
|
|
|
if(!inspace){
|
|
|
|
++words;
|
|
|
|
inspace = true;
|
|
|
|
}
|
|
|
|
}else{
|
|
|
|
inspace = false;
|
|
|
|
}
|
|
|
|
++text;
|
|
|
|
}
|
|
|
|
return words;
|
|
|
|
}
|
|
|
|
|
|
|
|
// selector moves across to the left; reader moves up halfway to the center
|
|
|
|
static int
|
|
|
|
selector_run(struct notcurses* nc, struct ncreader* reader, struct ncselector* selector){
|
|
|
|
const char text[] =
|
|
|
|
"Notcurses provides several widgets to quickly build vivid TUIs.\n"
|
|
|
|
"This NCReader widget facilitates free-form text entry complete with readline-style bindings.\n"
|
|
|
|
"NCSelector allows a single option to be selected from a list.\n"
|
|
|
|
"NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output.\n";
|
|
|
|
int titers = get_word_count(text);
|
|
|
|
int ret = 0, dimy, dimx;
|
|
|
|
ncplane_dim_yx(notcurses_stdplane(nc), &dimy, &dimx);
|
|
|
|
const int centery = (dimy - ncplane_dim_y(ncreader_plane(reader))) / 2;
|
|
|
|
int ry, rx, sy, sx;
|
|
|
|
ncplane_yx(ncreader_plane(reader), &ry, &rx);
|
|
|
|
ncplane_yx(ncselector_plane(selector), &sy, &sx);
|
|
|
|
const int xiters = sx - 2;
|
|
|
|
const int yiters = (ry - centery) / 2;
|
|
|
|
int iters = yiters > xiters ? yiters : xiters;
|
|
|
|
if(titers > iters){
|
|
|
|
iters = titers;
|
|
|
|
}
|
|
|
|
const double eachy = (double)iters / yiters;
|
|
|
|
const double eachx = (double)iters / xiters;
|
|
|
|
const double eacht = (double)iters / titers;
|
|
|
|
int xi = 1;
|
|
|
|
int yi = 1;
|
|
|
|
int ti = 1;
|
|
|
|
struct timespec iterdelay, start;
|
|
|
|
timespec_div(&demodelay, iters / 4, &iterdelay);
|
|
|
|
size_t textpos = 0;
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &start);
|
|
|
|
for(int i = 0 ; i < iters ; ++i){
|
|
|
|
if(i == (int)(xi * eachx)){
|
|
|
|
if(ncplane_move_yx(ncselector_plane(selector), sy, --sx)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
++xi;
|
|
|
|
}
|
|
|
|
if(i == (int)(yi * eachy)){
|
|
|
|
if(ncplane_move_yx(ncreader_plane(reader), --ry, rx)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
++yi;
|
|
|
|
}
|
|
|
|
if(i == (int)(ti * eacht)){
|
|
|
|
if( (ret = layout_next_text(reader, text, &textpos)) ){
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
++ti;
|
|
|
|
}
|
|
|
|
struct timespec targettime, now;
|
|
|
|
timespec_mul(&iterdelay, i + 1, &targettime);
|
|
|
|
const uint64_t deadline_ns = timespec_to_ns(&start) + timespec_to_ns(&targettime);
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
while(timespec_to_ns(&now) < deadline_ns){
|
|
|
|
if( (ret = demo_render(nc)) ){
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
struct ncinput ni;
|
|
|
|
struct timespec inputtime;
|
|
|
|
ns_to_timespec(deadline_ns - timespec_to_ns(&now), &inputtime);
|
|
|
|
char32_t wc = demo_getc(nc, &inputtime, &ni);
|
|
|
|
if(wc == (char32_t)-1){
|
|
|
|
return -1;
|
|
|
|
}else if(wc){
|
|
|
|
ncselector_offer_input(selector, &ni);
|
|
|
|
}
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return run_out_text(reader, text, &textpos, &iterdelay);
|
|
|
|
}
|
|
|
|
|
|
|
|
// selector moves across to the right; reader moves up halfway to the center
|
|
|
|
static int
|
|
|
|
mselector_run(struct notcurses* nc, struct ncreader* reader, struct ncmultiselector* mselector){
|
|
|
|
const char text[] =
|
|
|
|
"NCMultiselector allows 0..n options to be selected from a list of n items.\n"
|
|
|
|
"A variety of plots are supported. "
|
|
|
|
"Menus can be placed along the top and/or bottom of any plane. "
|
|
|
|
"Widgets can be controlled with the keyboard and/or mouse. "
|
|
|
|
"They are implemented atop ncplanes, and these planes can be manipulated like all others.";
|
|
|
|
const int titers = get_word_count(text);
|
|
|
|
int ret = 0, dimy, dimx;
|
|
|
|
ncplane_dim_yx(notcurses_stdplane(nc), &dimy, &dimx);
|
|
|
|
const int centery = (dimy - ncplane_dim_y(ncreader_plane(reader))) / 2;
|
|
|
|
int ry, rx, sy, sx;
|
|
|
|
ncplane_yx(ncreader_plane(reader), &ry, &rx);
|
|
|
|
ncplane_yx(ncmultiselector_plane(mselector), &sy, &sx);
|
|
|
|
const int xiters = dimx - ncplane_dim_x(ncmultiselector_plane(mselector));
|
|
|
|
const int yiters = ry - centery;
|
|
|
|
int iters = yiters > xiters ? yiters : xiters;
|
|
|
|
if(titers > iters){
|
|
|
|
iters = titers;
|
|
|
|
}
|
|
|
|
const double eachy = (double)iters / yiters;
|
|
|
|
const double eachx = (double)iters / xiters;
|
|
|
|
const double eacht = (double)iters / titers;
|
|
|
|
int xi = 1;
|
|
|
|
int yi = 1;
|
|
|
|
int ti = 1;
|
|
|
|
struct timespec iterdelay, start;
|
|
|
|
timespec_div(&demodelay, iters / 4, &iterdelay);
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &start);
|
|
|
|
size_t textpos = 0;
|
|
|
|
for(int i = 0 ; i < iters ; ++i){
|
|
|
|
if(i == (int)(ti * eacht)){
|
|
|
|
if( (ret = layout_next_text(reader, text, &textpos)) ){
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
++ti;
|
|
|
|
}
|
|
|
|
if(i == (int)(xi * eachx)){
|
|
|
|
if(ncplane_move_yx(ncmultiselector_plane(mselector), sy, ++sx)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
++xi;
|
|
|
|
}
|
|
|
|
if(i == (int)(yi * eachy)){
|
|
|
|
if(ncplane_move_yx(ncreader_plane(reader), --ry, rx)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
++yi;
|
|
|
|
}
|
|
|
|
struct timespec targettime, now;
|
|
|
|
timespec_mul(&iterdelay, i + 1, &targettime);
|
|
|
|
const uint64_t deadline_ns = timespec_to_ns(&start) + timespec_to_ns(&targettime);
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
while(timespec_to_ns(&now) < deadline_ns){
|
|
|
|
if( (ret = demo_render(nc)) ){
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
struct ncinput ni;
|
|
|
|
struct timespec inputtime;
|
|
|
|
ns_to_timespec(deadline_ns - timespec_to_ns(&now), &inputtime);
|
|
|
|
char32_t wc = demo_getc(nc, &inputtime, &ni);
|
|
|
|
if(wc == (char32_t)-1){
|
|
|
|
return -1;
|
|
|
|
}else if(wc){
|
|
|
|
ncmultiselector_offer_input(mselector, &ni);
|
|
|
|
}
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return run_out_text(reader, text, &textpos, &iterdelay);
|
|
|
|
}
|
|
|
|
|
|
|
|
// creates an ncreader, ncselector, and ncmultiselector, and moves them into
|
|
|
|
// place. the latter two are then faded out. all three are then destroyed.
|
|
|
|
static int
|
|
|
|
reader_demo(struct notcurses* nc){
|
|
|
|
int ret = -1;
|
|
|
|
int dimy, dimx;
|
|
|
|
struct ncplane* std = notcurses_stddim_yx(nc, &dimy, &dimx);
|
|
|
|
const int READER_COLS = 64;
|
|
|
|
const int READER_ROWS = 8;
|
|
|
|
ncreader_options nopts = {
|
|
|
|
.tchannels = CHANNELS_RGB_INITIALIZER(0xa0, 0xe0, 0xe0, 0, 0, 0),
|
|
|
|
};
|
|
|
|
uint64_t echannels = CHANNELS_RGB_INITIALIZER(0x20, 0xe0, 0xe0, 0, 0, 0);
|
|
|
|
channels_set_bg_alpha(&echannels, CELL_ALPHA_BLEND);
|
|
|
|
const int x = ncplane_align(std, NCALIGN_CENTER, READER_COLS);
|
|
|
|
struct ncselector* selector = NULL;
|
|
|
|
struct ncmultiselector* mselector = NULL;
|
|
|
|
struct ncplane* rp = ncplane_new(std, READER_ROWS, READER_COLS, dimy, x, NULL, "read");
|
|
|
|
ncplane_set_base(rp, " ", 0, echannels);
|
|
|
|
struct ncreader* reader = ncreader_create(rp, &nopts);
|
|
|
|
if(reader == NULL){
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
ncplane_set_scrolling(ncreader_plane(reader), true);
|
|
|
|
// Bring the selector left across the top, while raising the exposition
|
|
|
|
// halfway to its target height.
|
|
|
|
selector = selector_demo(std, ncreader_plane(reader), dimx, 2);
|
|
|
|
if(selector == NULL){
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
if( (ret = selector_run(nc, reader, selector)) ){
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
// Bring the multiselector right across the top, while raising the exposition
|
|
|
|
// the remainder of its path to the center of the screen.
|
|
|
|
mselector = multiselector_demo(std, ncreader_plane(reader), 8);
|
|
|
|
if(mselector == NULL){
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
if( (ret = mselector_run(nc, reader, mselector)) ){
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
// Delay and fade
|
|
|
|
if( (ret = reader_post(nc, selector, mselector)) ){
|
|
|
|
goto done;
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
|
|
|
ncselector_destroy(selector, NULL);
|
|
|
|
ncmultiselector_destroy(mselector);
|
|
|
|
ncreader_destroy(reader, NULL);
|
|
|
|
// don't check for failure -- not supported under vt100
|
|
|
|
notcurses_cursor_disable(nc);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// a plane with exposition text rises from the bottom to the center of the
|
|
|
|
// screen. as it does so, two widgets (selector and multiselector) come in
|
|
|
|
// from the left and right, respectively. they then fade out.
|
|
|
|
int zoo_demo(struct notcurses* nc){
|
|
|
|
if(draw_background(nc)){
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
return reader_demo(nc);
|
|
|
|
}
|