// Copyright 2021, Paul Mosier
//
// This file is part of Scriptura, a ncurses-based Bible study
// software for the libsword backend.
//
// Scriptura is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, under version 2 of the License.
//
// Scriptura 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 General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Scriptura. If not, see ", 1)) && (p == 0)) {
// paragraph break - replace
= bufsizex) { text[lastspaceidx] = '\n'; numlines++; // start counting from the beginning of the last word linelength = printable - lastprintspace; } else { lastspaceidx = i; lastprintspace = printable; } } // reset for newlines if (text[i] == '\n') { /* check to see if we're still over the length but hit a * newline before a space */ if (linelength >= bufsizex) { text[lastspaceidx] = '\n'; numlines++; } numlines++; linelength = 0; lastspaceidx = i; lastprintspace = printable; } linelength++; // various word wrapping debugging statements //fprintf(stderr, "[ %c ] : 0x%X\n", text[i], text[i]); //fprintf(stderr, "Char: %c I: %d Numlines: %d " // "Linelength: %d Lastspaceidx: %d\n", // text[i], printable, numlines, linelength, lastspaceidx); } else { // printing -- pull out the single character we care about wattrset(pad, COLOR_PAIR(makered) | (makeital ? A_ITALIC : 0)); if (((int) charlen == -1) && (offset == 1)) { /* This is an extended ascii character (probably Latin-1) and not * UTF-8. In setting up ncurses for UTF-8 we set a locale and seem to * make these characters unprintable. To avoid massive rendering * errors we have to substitute them with something else. */ waddstr(pad, "?"); } else waddnstr(pad, &text[i], charlen); } printable++; i += ((int) charlen == -1 ? offset : (int) charlen); } // text loop // text rewriting debugging //fwprintf(stderr, L"\n(F): %ls\n\n\n", text); if (p == 0) { // now that we have the number of newlines, create the pad to fit bufsizey = numlines; pad = newpad(bufsizey + 1, bufsizex); if (! pad) wrapup(1, "Error constructing pad in renderText.\n"); scrollok(pad, true); wmove(pad, 0, 0); } } // workhorse free(text); redraw(); } void pane::setTitle(const char* newtitle) { // NOTE -- hardcoded length for title string if (strlen(newtitle) < 40) { strcpy(titlebar, newtitle); } else { strncpy(titlebar, newtitle, 36); strcpy(titlebar+36, "..."); } } void pane::retitle() { (hasFocus ? wattrset(win, A_STANDOUT) : standend()); mvwprintw(win, 0, 1, "%s%s%s", " ", titlebar, " "); wstandend(win); } void pane::redraw(bool borders) { if (borders) { box(win, 0, 0); retitle(); } wrefresh(win); if (is_pad(pad)) { prefresh(pad, buflocy, buflocx, pady, padx, pady + height - 3, padx + width - 1); } else { wrefresh(pad); } } void pane::nextPage() { buflocy = (bufsizey - buflocy > 2 * padrows ? buflocy + padrows - 1 : bufsizey - padrows); redraw(); } void pane::scrollDown() { if (bufsizey - buflocy > padrows) buflocy++; redraw(); } void pane::scrollUp() { if (buflocy > 0) buflocy--; redraw(); } void pane::prevPage() { buflocy = (buflocy > padrows ? buflocy - padrows : 0); redraw(); } void pane::toggleFocus() { hasFocus = !hasFocus; retitle(); redraw(); } int pane::loadMenu(starray opts) { // instruction text const char* inst = "(Up/Down - scroll PgUp/PgDn - page up/down" " Enter - select ESC - cancel)"; pady = printInstructions(2, inst); // construct list of items ITEM **items = (ITEM**) malloc(sizeof(ITEM*) * (opts.length+1)); if (! items) wrapup(1, "Error allocating memory in loadMenu.\n"); for (int i = 0; i < opts.length; i++) items[i] = new_item((opts.strings)[i], (opts.strings)[i]); items[opts.length] = (ITEM*) NULL; // pad is a ncurses subwindow - set coords relative to win's X & Y pady++; padx = 4; padrows = height - 6; padcols = width - 6; pad = derwin(win, padrows, padcols, pady, padx); keypad(pad, true); // create the menu & header MENU *menu = new_menu((ITEM**) items); menu_opts_off(menu, O_SHOWDESC); set_menu_win(menu, win); set_menu_sub(menu, pad); set_menu_mark(menu, "-> "); set_menu_format(menu, padrows, 1); // set scrolling post_menu(menu); redraw(); // hit escape to exit without a choice int ch; while (((ch = getch()) != 27) && (ch != KEY_RESIZE)) { switch (ch) { case KEY_PPAGE: menu_driver(menu, REQ_SCR_UPAGE); break; case KEY_UP: menu_driver(menu, REQ_UP_ITEM); break; case KEY_DOWN: menu_driver(menu, REQ_DOWN_ITEM); break; case KEY_NPAGE: menu_driver(menu, REQ_SCR_DPAGE); break; case '\n': // enter int retval = item_index(current_item(menu)); menuClean(menu, items, opts.length); return retval; break; } redraw(); } // user backed out, return nothing menuClean(menu, items, opts.length); return (ch == KEY_RESIZE ? -2 : -1); } int pane::printInstructions(int y, const char* inst) { // NOTE -- starting indentation is hardcoded at two characters in long unsigned int strctr = 0; long unsigned int line = width - 3; while (strlen(&inst[strctr]) > line) { // set write length to be up to the last space, for word wrapping while ((inst[line] != ' ') && (line > 0)) line--; if (line == 0) line = width - 3; mvwaddnstr(win, y++, 2, &inst[strctr], line); strctr += line; line = width - 3; } if (strlen(inst) != strctr) mvwaddnstr(win, y++, 2, &inst[strctr], -1); return y; } starray pane::loadForm(starray inputs, const char* secondinst) { /* instruction text -- add text using mvwaddnstr because we don't know how * large the window is */ const char* firstinst = "(Up/Down - scroll Enter - select ESC - cancel)"; pady = printInstructions(2, firstinst); pady = printInstructions(pady, secondinst); // pad is a ncurses subwindow - set coords relative to win's X & Y padx = 40; padrows = height - 5; padcols = width - 45; pad = derwin(win, padrows, padcols, pady, padx); keypad(win, true); curs_set(1); // find out how many valid fields we have int validfields = 1; for (int i = 0; i < inputs.length; i++) { // find out how many extra lines we'll need to put in the form if (strcmp((inputs.strings)[i], "SPACE") != 0) validfields++; } FIELD *fields[validfields]; // construct list of fields int yoffset = 1; int fieldnum = 0; for (int i = 0; i < inputs.length; i++) { if (strcmp((inputs.strings)[i], "SPACE") == 0) { // put in the space yoffset++; continue; } fields[fieldnum] = new_field(1, 30, yoffset, 1, 0, 0); mvwprintw(win, yoffset + pady, 2, "%s", (inputs.strings)[i]); yoffset++; set_field_back(fields[fieldnum], A_UNDERLINE); field_opts_off(fields[fieldnum], O_AUTOSKIP); fieldnum++; } fields[validfields-1] = NULL; // create the form FORM *form = new_form(fields); set_form_win(form, win); set_form_sub(form, pad); post_form(form); // show everything redraw(); // hit escape or resize terminal to exit without a choice int ch; starray retval; retval = stinit(retval); while (((ch = wgetch(win)) != 27) && (ch != KEY_RESIZE)) { switch (ch) { case KEY_UP: form_driver(form, REQ_PREV_FIELD); form_driver(form, REQ_END_LINE); break; case KEY_DOWN: form_driver(form, REQ_NEXT_FIELD); form_driver(form, REQ_END_LINE); break; case KEY_BACKSPACE: case 127: // these are all backspace - it's terminfo dependent case '\b': form_driver(form, REQ_DEL_PREV); break; case '\n': // enter key - exit out here // force validation to ensure last field is written to buffer form_driver(form, REQ_VALIDATION); // get inputs for (int i = 0; i < validfields-1; i++) { char* inp = strdup(field_buffer(fields[i], 0)); trim(inp); retval = stappend(retval, inp); } // clean up formClean(form, fields, validfields); return retval; break; default: // add character to input form_driver(form, ch); break; } } // user backed out, return nothing formClean(form, fields, validfields); if (ch == KEY_RESIZE) retval.length = -1; return retval; } void pane::menuClean(MENU* menu, ITEM** items, int numitems) { unpost_menu(menu); free_menu(menu); for (int i = 0; i < numitems + 1; i++) free_item(items[i]); free(items); } void pane::formClean(FORM* form, FIELD** fields, int numfields) { unpost_form(form); free_form(form); curs_set(0); for (int i = 0; i < numfields; i++) free_field(fields[i]); } void pane::setModule(const char* newtitle, const char* newmod, int keytype) { mod.modname = newmod; mod.keytype = keytype; setTitle(newtitle); // update titlebar redraw(true); } void pane::setKey(const char* newkey) { mod.searchkey = newkey; setSearch(DEFSEARCH, "Gen 1:1 - Rev 22:21"); } void pane::setSearch(int type, const char* scope) { mod.searchtype = type; mod.scope = scope; } void pane::setModkey(modkey newmod, const char* newtitle) { setModule(newtitle, newmod.modname, newmod.keytype); setKey(newmod.searchkey); setSearch(newmod.searchtype, newmod.scope); }