scriptura/pane.cpp

591 lines
16 KiB
C++
Raw Normal View History

/* Part of the Scriptura software
* pane.cpp - class to handle pane layout */
#include <stdlib.h>
#include <ncurses.h>
#include <menu.h>
#include <form.h>
#include <string.h>
#include <wchar.h>
#include "pane.h"
// constructor
pane::pane(int starty, int startx, int lines, int cols, const char* title) {
buflocx = buflocy = 0;
hasFocus = false;
rawtext = NULL;
// set some default search parameters
mod.modname = config["defaults"]["module"];
mod.searchkey = config["defaults"]["searchkey"];
mod.keytype = 0;
mod.searchtype = 0;
mod.scope = config["defaults"]["scope"];
setTitle(title);
// set up the window -- resize will give us some initializing
pad = NULL;
resize(starty, startx, lines, cols);
redraw(true);
}
// handle pane resizing
void pane::resize(int starty, int startx, int lines, int cols) {
y = starty;
x = startx;
height = lines;
width = cols;
win = newwin(height, width, y, x);
/* if pad is a curses subwindow (made by derwin) then we close it rather than
* resize it, so we only check for pads proper to manage text handling */
if (is_pad(pad)) renderText();
}
/* trap new text to render, and then pass along to our render function */
void pane::loadText(const char* stext) {
free(rawtext);
rawtext = (char*) malloc(sizeof(char*) * strlen(stext) + 1);
if (! rawtext) wrapup(1, "Error allocating memory in loadText.\n");
memset(rawtext, '\0', strlen(stext) + 1);
strncpy(rawtext, stext, strlen(stext));
free((void*) stext); // alloc'd in scabbard::getSpan
renderText();
}
/* create a new pad with the boundaries tightly fitting around a block of
* formatted text */
void pane::renderText() {
// pad is an ncurses pad - set what dimensions we know right now
pady = y + 1;
padx = x + 1;
padrows = height - 2;
padcols = width - 2;
bufsizex = padcols;
buflocx = 0;
buflocy = 0;
// here we define some counters & flags
int length = strlen(rawtext);
int numlines = 0; // number of newlines
int linelength = 0; // how long the current line is
int lastspaceidx = 0; // where was the last space relative to the string
int printable = 0; // index of printed characters
int lastprintspace = 0; // where was the last space relative to printed chars
int inmarkup = 0; // are we in a non-printed markup block
int makered = 0; // redletter flag
int makeital = 0; // italic flag
// pull config variables
int rawonly = parseConf(config["markup"]["rawtext"]);
int redletter = parseConf(config["markup"]["redletters"]);
int strongs = parseConf(config["markup"]["strongs"]);
int nontextual = parseConf(config["markup"]["nontextual"]);
// int footnotes = 0; not implemented yet
// convert input to wide characters for proper rendering
wchar_t* text = (wchar_t*) malloc(sizeof(wchar_t) * (length + 1));
if (! text) wrapup(1, "Error allocating memory in renderText.\n");
wmemset(text, L'\0', length + 1);
// some typographical quotes won't go through mbstowcs, so alter those
for (int i = 0; i < length; i++) {
int converted = mbstowcs(text, rawtext, i);
if (converted == -1) strncpy(&(rawtext[i-1]), "'", 1);
}
/*
mbstowcs(text, rawtext, length + 1);
for (int foo = 0; foo < length + 2; foo++) {
int converted = mbstowcs(text, rawtext, foo);
if (converted == -1) {
fprintf(stderr, "codes: %x\n", rawtext[foo-1]);
break;
}
}*/
//fprintf(stderr, "length: %d\n", length);
//fprintf(stderr, "rawtext: %s\n", rawtext);
//fprintf(stderr, "text: %ls\n\n", text);
// kept for debugging
//fwprintf(stderr, L"(O): %ls\n\n", text);
/* here's the workhorse - loop through the text twice: first to alter the
* text for word wrapping and to count the number of lines we have so we
* can define the pad size; second to print the text with the correct
* markup */
for (int p = 0; p < 2; p++) {
// loop through the text
for (int i = 0; i < length; i++) {
/* check if we're in markup - it's not printed and it doesn't
* affect our line lengths, so spin through it */
if (inmarkup) {
if (text[i] == '>') inmarkup = 0;
continue;
}
// check if we're entering markup - if so, figure out what
if ((text[i] == '<') && (! rawonly)) {
inmarkup = 1;
if ((! strmatch(&text[i], L"</q>", 1)) && (p == 1)) {
// end of redletter bracket
makered = 0;
} else if ((! strmatch(&text[i], L"</transChange>", 1))
&& (p == 1)) {
// end of interpretive text bracket
makeital = 0;
} else if ((! strmatch(&text[i], L"<p>", 1)) && (p == 0)) {
// paragraph break - replace </p> with </>'\n'
int endindex = strmatch(&text[i], L"</p>", 0);
if (endindex != -1)
wmemcpy(&text[i + endindex + 2], L">\n", 2);
} else if ((! strmatch(&text[i], L"<w savlm=", 1))
&& (p == 0) && (strongs == 1)) {
/* Strong's number - the format is below, but note
* there may be 1+ strong:[G|H]NNNN(N)'s to find,
* and other junk to ignore both within the same
* parameter, and before the word
* <w savlm="strong:[G|H]NNNN(N)">word</w> */
// 1. get boundary of open tag
int endbracket = strmatch(&text[i], L">", 0);
// 2. get Strong's parameters
wchar_t* num = (wchar_t*) malloc(sizeof(wchar_t*)
* 100);
if (! num) wrapup(1,
"Error declaring memory in renderText.\n");
int numidx = 0;
int strnum = strmatch(&text[i], L"strong:", 0) + 7;
while ((strnum < endbracket) && (strnum != -1)) {
int nextspace = strmatch(&text[i+strnum], L" ", 0);
int space1 = strmatch(&text[i+strnum], L"\"", 0);
int len = ((nextspace == -1) || (space1 < nextspace)
? space1
: nextspace);
if (numidx != 0) wmemcpy(&num[numidx++], L" ", 1);
wmemcpy(&num[numidx], &text[i+strnum], len);
numidx += len;
int nextnum =
strmatch(&text[i+strnum], L"strong:", 0);
strnum = (nextnum != -1
? strnum + nextnum + 7
: -1);
}
/* 3. determine if tag is empty closed-element,
* if so, rewrite in place by making wordlen
* below zero, otherwise get word boundaries */
int wordstart = endbracket + 1;
int endtag =
(endbracket > strmatch(&text[i], L"/", 0)
? endbracket + 1
: strmatch(&text[i], L"</w>", 0));
// 4. determine word boundaries & rewrite
int wordlen = endtag - wordstart;
wchar_t* word = (wchar_t*) malloc(sizeof(wchar_t*)
* (wordlen == 0 ? 1 : wordlen));
if (! word) wrapup(1,
"Error rewriting markup in renderText.\n");
wmemcpy(word, &text[i+wordstart], wordlen);
// rewrite
int start = i + endtag - wordlen - numidx - 4;
wmemcpy(&text[start], L"\"", 1);
wmemcpy(&text[start + 1], L">", 1);
wmemcpy(&text[start + 2], word, wordlen);
wmemcpy(&text[start + 2 + wordlen], L"[", 1);
wmemcpy(&text[start + 3 + wordlen], num, numidx);
wmemcpy(&text[start + 3 + wordlen + numidx], L"]", 1);
// clean up so we can do this again
free(num);
free(word);
// kept for debugging
//fwprintf(stderr, L"(I): %ls\n", text);
} else if ((! strmatch(&text[i], L"<q marker", 1))
&& (p == 1) && (redletter == 1)) {
// start of redletter bracket
makered = 1;
} else if ((! strmatch(&text[i], L"<transChange type=\"added\"", 1))
&& (p == 1) && (nontextual == 1)) {
// start of interpretive text bracket
makeital = 1;
}
// don't print the angle bracket
continue;
} // markup check
if (p == 0) {
// handle word wrapping
if (text[i] == ' ') {
/* if under the buffer width, note index & continue
* if over, set the preceeding space to a newline */
if (linelength >= 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
wchar_t single[] = L"\0\0";
wcsncpy(&single[0], &text[i], 1);
wattrset(pad, COLOR_PAIR(makered)
| (makeital ? A_ITALIC : 0));
waddwstr(pad, single);
}
printable++;
} // 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", " ");
mvwprintw(win, 0, 3, "%s", titlebar);
mvwprintw(win, 0, 3+strlen(titlebar), "%s", " ");
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
int strctr = 0;
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(0, "Gen 1:1 - Rev 22:21");
}
void pane::setSearch(int type, const char* scope) {
mod.searchtype = type;
mod.scope = scope;
}
modkey pane::getModkey() {
return mod;
}