MegaGlest/mk/linux/mojosetup/gui_ncurses.c

1488 lines
44 KiB
C

/**
* MojoSetup; a portable, flexible installation application.
*
* Please see the file LICENSE.txt in the source's root directory.
*
* This file written by Ryan C. Gordon.
*/
#if !SUPPORT_GUI_NCURSES
#error Something is wrong in the build system.
#endif
#define BUILDING_EXTERNAL_PLUGIN 1
#include "gui.h"
MOJOGUI_PLUGIN(ncurses)
#if !GUI_STATIC_LINK_NCURSES
CREATE_MOJOGUI_ENTRY_POINT(ncurses)
#endif
#include <unistd.h>
#include <ctype.h>
// CMake searches for a whole bunch of different possible curses includes
#if defined(HAVE_NCURSESW_NCURSES_H)
#include <ncursesw/ncurses.h>
#elif defined(HAVE_NCURSESW_CURSES_H)
#include <ncursesw/curses.h>
#elif defined(HAVE_NCURSESW_H)
#include <ncursesw.h>
#else
#error ncurses gui enabled, but no known header file found
#endif
#include <locale.h>
// This was built to look roughly like dialog(1), but it's not nearly as
// robust. Also, I didn't use any of dialog's code, as it is GPL/LGPL,
// depending on what version you start with. There _is_ a libdialog, but
// it's never something installed on any systems, and I can't link it
// statically due to the license.
//
// ncurses is almost always installed as a shared library, though, so we'll
// just talk to it directly. Fortunately we don't need much of what dialog(1)
// offers, so rolling our own isn't too painful (well, compared to massive
// head trauma, I guess).
//
// Pradeep Padala's ncurses HOWTO was very helpful in teaching me curses
// quickly: http://tldp.org/HOWTO/NCURSES-Programming-HOWTO/index.html
// !!! FIXME: this should all be UTF-8 and Unicode clean with ncursesw, but
// !!! FIXME: it relies on the terminal accepting UTF-8 output (we don't
// !!! FIXME: attempt to convert) and assumes all characters fit in one
// !!! FIXME: column, which they don't necessarily for some Asian languages,
// !!! FIXME: etc. I'm not sure how to properly figure out column width, if
// !!! FIXME: it's possible at all, but for that, you should probably
// !!! FIXME: go to a proper GUI plugin like GTK+ anyhow.
typedef enum
{
MOJOCOLOR_BACKGROUND=1,
MOJOCOLOR_BORDERTOP,
MOJOCOLOR_BORDERBOTTOM,
MOJOCOLOR_BORDERTITLE,
MOJOCOLOR_TEXT,
MOJOCOLOR_TEXTENTRY,
MOJOCOLOR_BUTTONHOVER,
MOJOCOLOR_BUTTONNORMAL,
MOJOCOLOR_BUTTONBORDER,
MOJOCOLOR_TODO,
MOJOCOLOR_DONE,
} MojoColor;
typedef struct
{
WINDOW *mainwin;
WINDOW *textwin;
WINDOW **buttons;
char *title;
char *text;
char **textlines;
char **buttontext;
int buttoncount;
int textlinecount;
int hoverover;
int textpos;
boolean hidecursor;
boolean ndelay;
int cursval;
} MojoBox;
static char *lastProgressType = NULL;
static char *lastComponent = NULL;
static boolean lastCanCancel = false;
static uint32 percentTicks = 0;
static char *title = NULL;
static MojoBox *progressBox = NULL;
static void drawButton(MojoBox *mojobox, int button)
{
const boolean hover = (mojobox->hoverover == button);
int borderattr = 0;
WINDOW *win = mojobox->buttons[button];
const char *str = mojobox->buttontext[button];
int w, h;
getmaxyx(win, h, w);
if (!hover)
wbkgdset(win, COLOR_PAIR(MOJOCOLOR_BUTTONNORMAL));
else
{
borderattr = COLOR_PAIR(MOJOCOLOR_BUTTONBORDER) | A_BOLD;
wbkgdset(win, COLOR_PAIR(MOJOCOLOR_BUTTONHOVER));
} // else
werase(win);
wmove(win, 0, 0);
waddch(win, borderattr | '<');
wmove(win, 0, w-1);
waddch(win, borderattr | '>');
wmove(win, 0, 2);
if (!hover)
waddstr(win, str);
else
{
wattron(win, COLOR_PAIR(MOJOCOLOR_BUTTONHOVER) | A_BOLD);
waddstr(win, str);
wattroff(win, COLOR_PAIR(MOJOCOLOR_BUTTONHOVER) | A_BOLD);
} // else
} // drawButton
static void drawText(MojoBox *mojobox)
{
int i;
const int tcount = mojobox->textlinecount;
int pos = mojobox->textpos;
int w, h;
WINDOW *win = mojobox->textwin;
getmaxyx(win, h, w);
werase(mojobox->textwin);
for (i = 0; (pos < tcount) && (i < h); i++, pos++)
mvwaddstr(win, i, 0, mojobox->textlines[pos]);
if (tcount > h)
{
const int pct = (int) ((((double) pos) / ((double) tcount)) * 100.0);
win = mojobox->mainwin;
wattron(win, COLOR_PAIR(MOJOCOLOR_BORDERTITLE) | A_BOLD);
mvwprintw(win, h+1, w-5, "(%3d%%)", pct);
wattroff(win, COLOR_PAIR(MOJOCOLOR_BORDERTITLE) | A_BOLD);
} // if
} // drawText
static void drawBackground(WINDOW *win)
{
wclear(win);
if (title != NULL)
{
int w, h;
getmaxyx(win, h, w);
wattron(win, COLOR_PAIR(MOJOCOLOR_BACKGROUND) | A_BOLD);
mvwaddstr(win, 0, 0, title);
mvwhline(win, 1, 1, ACS_HLINE, w-2);
wattroff(win, COLOR_PAIR(MOJOCOLOR_BACKGROUND) | A_BOLD);
} // if
} // drawBackground
static void confirmTerminalSize(void)
{
int scrw = 0;
int scrh = 0;
char *msg = NULL;
int len = 0;
int x = 0;
int y = 0;
while (1) // loop until the window meets a minimum dimension requirement.
{
getmaxyx(stdscr, scrh, scrw);
scrh--; // -1 to save the title at the top of the screen...
if (scrw < 30) // too thin
msg = xstrdup(_("[Make the window wider!]"));
else if (scrh < 10) // too short
msg = xstrdup(_("[Make the window taller!]"));
else
break; // we're good, get out.
len = utf8len(msg);
y = scrh / 2;
x = ((scrw - len) / 2);
if (y < 0) y = 0;
if (x < 0) x = 0;
wclear(stdscr);
wmove(stdscr, y, x);
wrefresh(stdscr);
wmove(stdscr, y, x);
wattron(stdscr, COLOR_PAIR(MOJOCOLOR_BACKGROUND) | A_BOLD);
waddstr(stdscr, msg);
wattroff(stdscr, COLOR_PAIR(MOJOCOLOR_BACKGROUND) | A_BOLD);
nodelay(stdscr, 0);
wrefresh(stdscr);
free(msg);
while (wgetch(stdscr) != KEY_RESIZE) { /* no-op. */ }
} // while
} // confirmTerminalSize
static MojoBox *makeBox(const char *title, const char *text,
char **buttons, int bcount,
boolean ndelay, boolean hidecursor)
{
MojoBox *retval = NULL;
WINDOW *win = NULL;
int scrw, scrh;
int len = 0;
int buttonsw = 0;
int x = 0;
int y = 0;
int h = 0;
int w = 0;
int texth = 0;
int i;
confirmTerminalSize(); // blocks until window is large enough to continue.
getmaxyx(stdscr, scrh, scrw);
scrh--; // -1 to save the title at the top of the screen...
retval = (MojoBox *) xmalloc(sizeof (MojoBox));
retval->hidecursor = hidecursor;
retval->ndelay = ndelay;
retval->cursval = ((hidecursor) ? curs_set(0) : ERR);
retval->title = xstrdup(title);
retval->text = xstrdup(text);
retval->buttoncount = bcount;
retval->buttons = (WINDOW **) xmalloc(sizeof (WINDOW*) * bcount);
retval->buttontext = (char **) xmalloc(sizeof (char*) * bcount);
for (i = 0; i < bcount; i++)
retval->buttontext[i] = xstrdup(buttons[i]);
retval->textlines = splitText(retval->text, scrw-4,
&retval->textlinecount, &w);
len = utf8len(title);
if (len > scrw-4)
{
len = scrw-4;
strcpy(&retval->title[len-3], "..."); // !!! FIXME: not Unicode safe!
} // if
if (len > w)
w = len;
if (bcount > 0)
{
for (i = 0; i < bcount; i++)
buttonsw += utf8len(buttons[i]) + 5; // '<', ' ', ' ', '>', ' '
if (buttonsw > w)
w = buttonsw;
// !!! FIXME: what if these overflow the screen?
} // if
w += 4;
h = retval->textlinecount + 2;
if (bcount > 0)
h += 2;
if (h > scrh-2)
h = scrh-2;
x = (scrw - w) / 2;
y = ((scrh - h) / 2) + 1;
// can't have negative coordinates, so in case we survived the call to
// confirmTerminalSize() but still need more, just draw as much as
// possible from the top/left to fill the window.
if (x < 0) x = 0;
if (y < 0) y = 0;
win = retval->mainwin = newwin(h, w, y, x);
keypad(win, TRUE);
nodelay(win, ndelay);
wbkgdset(win, COLOR_PAIR(MOJOCOLOR_TEXT));
wclear(win);
waddch(win, ACS_ULCORNER | A_BOLD | COLOR_PAIR(MOJOCOLOR_BORDERTOP));
whline(win, ACS_HLINE | A_BOLD | COLOR_PAIR(MOJOCOLOR_BORDERTOP), w-2);
wmove(win, 0, w-1);
waddch(win, ACS_URCORNER | COLOR_PAIR(MOJOCOLOR_BORDERBOTTOM));
wmove(win, 1, 0);
wvline(win, ACS_VLINE | A_BOLD | COLOR_PAIR(MOJOCOLOR_BORDERTOP), h-2);
wmove(win, 1, w-1);
wvline(win, ACS_VLINE | COLOR_PAIR(MOJOCOLOR_BORDERBOTTOM), h-2);
wmove(win, h-1, 0);
waddch(win, ACS_LLCORNER | A_BOLD | COLOR_PAIR(MOJOCOLOR_BORDERTOP));
whline(win, ACS_HLINE | COLOR_PAIR(MOJOCOLOR_BORDERBOTTOM), w-2);
wmove(win, h-1, w-1);
waddch(win, ACS_LRCORNER | COLOR_PAIR(MOJOCOLOR_BORDERBOTTOM));
len = utf8len(retval->title);
wmove(win, 0, ((w-len)/2)-1);
wattron(win, COLOR_PAIR(MOJOCOLOR_BORDERTITLE) | A_BOLD);
waddch(win, ' ');
waddstr(win, retval->title);
wmove(win, 0, ((w-len)/2)+len);
waddch(win, ' ');
wattroff(win, COLOR_PAIR(MOJOCOLOR_BORDERTITLE) | A_BOLD);
if (bcount > 0)
{
const int buttony = (y + h) - 2;
int buttonx = (x + w) - ((w - buttonsw) / 2);
wmove(win, h-3, 1);
whline(win, ACS_HLINE | A_BOLD | COLOR_PAIR(MOJOCOLOR_BORDERTOP), w-2);
for (i = 0; i < bcount; i++)
{
len = utf8len(buttons[i]) + 4;
buttonx -= len+1;
win = retval->buttons[i] = newwin(1, len, buttony, buttonx);
keypad(win, TRUE);
nodelay(win, ndelay);
} // for
} // if
texth = h-2;
if (bcount > 0)
texth -= 2;
win = retval->textwin = newwin(texth, w-4, y+1, x+2);
keypad(win, TRUE);
nodelay(win, ndelay);
wbkgdset(win, COLOR_PAIR(MOJOCOLOR_TEXT));
drawText(retval);
drawBackground(stdscr);
wnoutrefresh(stdscr);
wnoutrefresh(retval->mainwin);
wnoutrefresh(retval->textwin);
for (i = 0; i < bcount; i++)
{
drawButton(retval, i);
wnoutrefresh(retval->buttons[i]);
} // for
doupdate(); // push it all to the screen.
return retval;
} // makeBox
static void freeBox(MojoBox *mojobox, boolean clearscreen)
{
if (mojobox != NULL)
{
int i;
const int bcount = mojobox->buttoncount;
const int tcount = mojobox->textlinecount;
if (mojobox->cursval != ERR)
curs_set(mojobox->cursval);
for (i = 0; i < bcount; i++)
{
free(mojobox->buttontext[i]);
delwin(mojobox->buttons[i]);
} // for
free(mojobox->buttontext);
free(mojobox->buttons);
delwin(mojobox->textwin);
delwin(mojobox->mainwin);
free(mojobox->title);
free(mojobox->text);
for (i = 0; i < tcount; i++)
free(mojobox->textlines[i]);
free(mojobox->textlines);
free(mojobox);
if (clearscreen)
{
wclear(stdscr);
wrefresh(stdscr);
} // if
} // if
} // freeBox
static int upkeepBox(MojoBox **_mojobox, int ch)
{
static boolean justResized = false;
MEVENT mevt;
int i;
int w, h;
MojoBox *mojobox = *_mojobox;
if (mojobox == NULL)
return -2;
if (justResized) // !!! FIXME: this is a kludge.
{
justResized = false;
if (ch == ERR)
return -1;
} // if
switch (ch)
{
case ERR:
return -2;
case '\r':
case '\n':
case KEY_ENTER:
case ' ':
return (mojobox->buttoncount <= 0) ? -1 : mojobox->hoverover;
case '\e':
return mojobox->buttoncount-1;
case KEY_UP:
if (mojobox->textpos > 0)
{
mojobox->textpos--;
drawText(mojobox);
wrefresh(mojobox->textwin);
} // if
return -1;
case KEY_DOWN:
getmaxyx(mojobox->textwin, h, w);
if (mojobox->textpos < (mojobox->textlinecount-h))
{
mojobox->textpos++;
drawText(mojobox);
wrefresh(mojobox->textwin);
} // if
return -1;
case KEY_PPAGE:
if (mojobox->textpos > 0)
{
getmaxyx(mojobox->textwin, h, w);
mojobox->textpos -= h;
if (mojobox->textpos < 0)
mojobox->textpos = 0;
drawText(mojobox);
wrefresh(mojobox->textwin);
} // if
return -1;
case KEY_NPAGE:
getmaxyx(mojobox->textwin, h, w);
if (mojobox->textpos < (mojobox->textlinecount-h))
{
mojobox->textpos += h;
if (mojobox->textpos > (mojobox->textlinecount-h))
mojobox->textpos = (mojobox->textlinecount-h);
drawText(mojobox);
wrefresh(mojobox->textwin);
} // if
return -1;
case KEY_LEFT:
if (mojobox->buttoncount > 1)
{
if (mojobox->hoverover < (mojobox->buttoncount-1))
{
mojobox->hoverover++;
drawButton(mojobox, mojobox->hoverover-1);
drawButton(mojobox, mojobox->hoverover);
wrefresh(mojobox->buttons[mojobox->hoverover-1]);
wrefresh(mojobox->buttons[mojobox->hoverover]);
} // if
} // if
return -1;
case KEY_RIGHT:
if (mojobox->buttoncount > 1)
{
if (mojobox->hoverover > 0)
{
mojobox->hoverover--;
drawButton(mojobox, mojobox->hoverover+1);
drawButton(mojobox, mojobox->hoverover);
wrefresh(mojobox->buttons[mojobox->hoverover+1]);
wrefresh(mojobox->buttons[mojobox->hoverover]);
} // if
} // if
return -1;
case 12: // ctrl-L...redraw everything on the screen.
redrawwin(stdscr);
wnoutrefresh(stdscr);
redrawwin(mojobox->mainwin);
wnoutrefresh(mojobox->mainwin);
redrawwin(mojobox->textwin);
wnoutrefresh(mojobox->textwin);
for (i = 0; i < mojobox->buttoncount; i++)
{
redrawwin(mojobox->buttons[i]);
wnoutrefresh(mojobox->buttons[i]);
} // for
doupdate(); // push it all to the screen.
return -1;
case KEY_RESIZE:
mojobox = makeBox(mojobox->title, mojobox->text,
mojobox->buttontext, mojobox->buttoncount,
mojobox->ndelay, mojobox->hidecursor);
mojobox->cursval = (*_mojobox)->cursval; // keep this sane.
mojobox->hoverover = (*_mojobox)->hoverover;
freeBox(*_mojobox, false);
if (mojobox->hidecursor)
curs_set(0); // make sure this stays sane.
*_mojobox = mojobox;
justResized = true; // !!! FIXME: kludge.
return -1;
case KEY_MOUSE:
if ((getmouse(&mevt) == OK) && (mevt.bstate & BUTTON1_CLICKED))
{
int i;
for (i = 0; i < mojobox->buttoncount; i++)
{
int x1, y1, x2, y2;
getbegyx(mojobox->buttons[i], y1, x1);
getmaxyx(mojobox->buttons[i], y2, x2);
x2 += x1;
y2 += y1;
if ( (mevt.x >= x1) && (mevt.x < x2) &&
(mevt.y >= y1) && (mevt.y < y2) )
return i;
} // for
} // if
return -1;
} // switch
return -1;
} // upkeepBox
static uint8 MojoGui_ncurses_priority(boolean istty)
{
if (!istty)
return MOJOGUI_PRIORITY_NEVER_TRY; // need a terminal for this!
else if (getenv("DISPLAY") != NULL)
return MOJOGUI_PRIORITY_TRY_LAST; // let graphical stuff go first.
return MOJOGUI_PRIORITY_TRY_NORMAL;
} // MojoGui_ncurses_priority
static boolean MojoGui_ncurses_init(void)
{
setlocale(LC_CTYPE, ""); // !!! FIXME: we assume you have a UTF-8 terminal.
if (initscr() == NULL)
{
logInfo("ncurses: initscr() failed, use another UI.");
return false;
} // if
cbreak();
keypad(stdscr, TRUE);
noecho();
start_color();
mousemask(BUTTON1_CLICKED, NULL);
init_pair(MOJOCOLOR_BACKGROUND, COLOR_CYAN, COLOR_BLUE);
init_pair(MOJOCOLOR_BORDERTOP, COLOR_WHITE, COLOR_WHITE);
init_pair(MOJOCOLOR_BORDERBOTTOM, COLOR_BLACK, COLOR_WHITE);
init_pair(MOJOCOLOR_BORDERTITLE, COLOR_YELLOW, COLOR_WHITE);
init_pair(MOJOCOLOR_TEXT, COLOR_BLACK, COLOR_WHITE);
init_pair(MOJOCOLOR_TEXTENTRY, COLOR_WHITE, COLOR_BLUE);
init_pair(MOJOCOLOR_BUTTONHOVER, COLOR_YELLOW, COLOR_BLUE);
init_pair(MOJOCOLOR_BUTTONNORMAL, COLOR_BLACK, COLOR_WHITE);
init_pair(MOJOCOLOR_BUTTONBORDER, COLOR_WHITE, COLOR_BLUE);
init_pair(MOJOCOLOR_DONE, COLOR_YELLOW, COLOR_RED);
init_pair(MOJOCOLOR_TODO, COLOR_CYAN, COLOR_BLUE);
wbkgdset(stdscr, COLOR_PAIR(MOJOCOLOR_BACKGROUND));
wclear(stdscr);
wrefresh(stdscr);
percentTicks = 0;
return true; // always succeeds.
} // MojoGui_ncurses_init
static void MojoGui_ncurses_deinit(void)
{
freeBox(progressBox, false);
progressBox = NULL;
endwin();
delwin(stdscr); // not sure if this is safe, but valgrind said it leaks.
stdscr = NULL;
free(title);
title = NULL;
free(lastComponent);
lastComponent = NULL;
free(lastProgressType);
lastProgressType = NULL;
} // MojoGui_ncurses_deinit
static void MojoGui_ncurses_msgbox(const char *title, const char *text)
{
char *localized_ok = xstrdup(_("OK"));
MojoBox *mojobox = makeBox(title, text, &localized_ok, 1, false, true);
while (upkeepBox(&mojobox, wgetch(mojobox->mainwin)) == -1) {}
freeBox(mojobox, true);
free(localized_ok);
} // MojoGui_ncurses_msgbox
static boolean MojoGui_ncurses_promptyn(const char *title, const char *text,
boolean defval)
{
char *localized_yes = xstrdup(_("Yes"));
char *localized_no = xstrdup(_("No"));
char *buttons[] = { localized_yes, localized_no };
MojoBox *mojobox = makeBox(title, text, buttons, 2, false, true);
int rc = 0;
// set the default to "no" instead of "yes"?
if (defval == false)
{
mojobox->hoverover = 1;
drawButton(mojobox, 0);
drawButton(mojobox, 1);
wrefresh(mojobox->buttons[0]);
wrefresh(mojobox->buttons[1]);
} // if
while ((rc = upkeepBox(&mojobox, wgetch(mojobox->mainwin))) == -1) {}
freeBox(mojobox, true);
free(localized_yes);
free(localized_no);
return (rc == 0);
} // MojoGui_ncurses_promptyn
static MojoGuiYNAN MojoGui_ncurses_promptynan(const char *title,
const char *text,
boolean defval)
{
char *loc_yes = xstrdup(_("Yes"));
char *loc_no = xstrdup(_("No"));
char *loc_always = xstrdup(_("Always"));
char *loc_never = xstrdup(_("Never"));
char *buttons[] = { loc_yes, loc_always, loc_never, loc_no };
MojoBox *mojobox = makeBox(title, text, buttons, 4, false, true);
int rc = 0;
// set the default to "no" instead of "yes"?
if (defval == false)
{
mojobox->hoverover = 3;
drawButton(mojobox, 0);
drawButton(mojobox, 3);
wrefresh(mojobox->buttons[0]);
wrefresh(mojobox->buttons[3]);
} // if
while ((rc = upkeepBox(&mojobox, wgetch(mojobox->mainwin))) == -1) {}
freeBox(mojobox, true);
free(loc_yes);
free(loc_no);
free(loc_always);
free(loc_never);
switch (rc)
{
case 0: return MOJOGUI_YES;
case 1: return MOJOGUI_ALWAYS;
case 2: return MOJOGUI_NEVER;
case 3: return MOJOGUI_NO;
} // switch
assert(false && "BUG: unhandled case in switch statement!");
return MOJOGUI_NO;
} // MojoGui_ncurses_promptynan
static boolean MojoGui_ncurses_start(const char *_title,
const MojoGuiSplash *splash)
{
free(title);
title = xstrdup(_title);
drawBackground(stdscr);
wrefresh(stdscr);
return true;
} // MojoGui_ncurses_start
static void MojoGui_ncurses_stop(void)
{
free(title);
title = NULL;
drawBackground(stdscr);
wrefresh(stdscr);
} // MojoGui_ncurses_stop
static int MojoGui_ncurses_readme(const char *name, const uint8 *data,
size_t datalen, boolean can_back,
boolean can_fwd)
{
MojoBox *mojobox = NULL;
char *buttons[3] = { NULL, NULL, NULL };
int bcount = 0;
int backbutton = -99;
int fwdbutton = -99;
int rc = 0;
int i = 0;
if (can_fwd)
{
fwdbutton = bcount++;
buttons[fwdbutton] = xstrdup(_("Next"));
} // if
if (can_back)
{
backbutton = bcount++;
buttons[backbutton] = xstrdup(_("Back"));
} // if
buttons[bcount++] = xstrdup(_("Cancel"));
mojobox = makeBox(name, (char *) data, buttons, bcount, false, true);
while ((rc = upkeepBox(&mojobox, wgetch(mojobox->mainwin))) == -1) {}
freeBox(mojobox, true);
for (i = 0; i < bcount; i++)
free(buttons[i]);
if (rc == backbutton)
return -1;
else if (rc == fwdbutton)
return 1;
return 0; // error? Cancel?
} // MojoGui_ncurses_readme
static int toggle_option(MojoGuiSetupOptions *parent,
MojoGuiSetupOptions *opts, int *line, int target)
{
int rc = -1;
if ((opts != NULL) && (target > *line))
{
const char *desc = opts->description;
boolean blanked = false;
blanked = ( (opts->is_group_parent) && ((!desc) || (!(*desc))) );
if ((!blanked) && (++(*line) == target))
{
const boolean toggled = ((opts->value) ? false : true);
if (opts->is_group_parent)
return 0;
// "radio buttons" in a group?
if ((parent) && (parent->is_group_parent))
{
if (toggled) // drop unless we weren't the current toggle.
{
// set all siblings to false...
MojoGuiSetupOptions *i = parent->child;
while (i != NULL)
{
i->value = false;
i = i->next_sibling;
} // while
opts->value = true; // reset us to be true.
} // if
} // if
else // individual "check box" was chosen.
{
opts->value = toggled;
} // else
return 1; // we found it, bail.
} // if
if (opts->value) // if option is toggled on, descend to children.
rc = toggle_option(opts, opts->child, line, target);
if (rc == -1)
rc = toggle_option(parent, opts->next_sibling, line, target);
} // if
return rc;
} // toggle_option
// This code is pretty scary.
static void build_options(MojoGuiSetupOptions *opts, int *line, int level,
int maxw, char **lines)
{
if (opts != NULL)
{
const char *desc = opts->description;
char *spacebuf = (char *) xmalloc(maxw + 1);
char *buf = (char *) xmalloc(maxw + 1);
int len = 0;
int spacing = level * 2;
if ((desc != NULL) && (*desc == '\0'))
desc = NULL;
if (spacing > (maxw-5))
spacing = 0; // oh well.
if (spacing > 0)
memset(spacebuf, ' ', spacing); // null-term'd by xmalloc().
if (opts->is_group_parent)
{
if (desc != NULL)
len = snprintf(buf, maxw-2, "%s%s", spacebuf, desc);
} // if
else
{
(*line)++;
len = snprintf(buf, maxw-2, "%s[%c] %s", spacebuf,
opts->value ? 'X' : ' ', desc);
} // else
free(spacebuf);
if (len >= maxw-1)
strcpy(buf+(maxw-4), "..."); // !!! FIXME: Unicode issues!
if (len > 0)
{
const size_t newlen = strlen(*lines) + strlen(buf) + 2;
*lines = (char*) xrealloc(*lines, newlen);
strcat(*lines, buf);
strcat(*lines, "\n"); // I'm sorry, Joel Spolsky!
} // if
if ((opts->value) || (opts->is_group_parent))
{
int newlev = level + 1;
if ((opts->is_group_parent) && (desc == NULL))
newlev--;
build_options(opts->child, line, newlev, maxw, lines);
} // if
build_options(opts->next_sibling, line, level, maxw, lines);
} // if
} // build_options
static int optionBox(const char *title, MojoGuiSetupOptions *opts,
boolean can_back, boolean can_fwd)
{
MojoBox *mojobox = NULL;
char *buttons[4] = { NULL, NULL, NULL, NULL };
boolean ignoreerr = false;
int lasthoverover = 0;
int lasttextpos = 0;
int bcount = 0;
int backbutton = -99;
int fwdbutton = -99;
int togglebutton = -99;
int cancelbutton = -99;
int selected = 0;
int ch = 0;
int rc = -1;
int i = 0;
if (can_fwd)
{
fwdbutton = bcount++;
buttons[fwdbutton] = xstrdup(_("Next"));
} // if
if (can_back)
{
backbutton = bcount++;
buttons[backbutton] = xstrdup(_("Back"));
} // if
lasthoverover = togglebutton = bcount++;
buttons[togglebutton] = xstrdup(_("Toggle"));
cancelbutton = bcount++;
buttons[cancelbutton] = xstrdup(_("Cancel"));
do
{
if (mojobox == NULL)
{
int y = 0;
int line = 0;
int maxw, maxh;
getmaxyx(stdscr, maxh, maxw);
char *text = xstrdup("");
build_options(opts, &line, 0, maxw-6, &text);
mojobox = makeBox(title, text, buttons, bcount, false, true);
free(text);
getmaxyx(mojobox->textwin, maxh, maxw);
if (lasthoverover != mojobox->hoverover)
{
const int orighover = mojobox->hoverover;
mojobox->hoverover = lasthoverover;
drawButton(mojobox, orighover);
drawButton(mojobox, lasthoverover);
wrefresh(mojobox->buttons[orighover]);
wrefresh(mojobox->buttons[lasthoverover]);
} // if
if (lasttextpos != mojobox->textpos)
{
mojobox->textpos = lasttextpos;
drawText(mojobox);
} // if
if (selected >= (mojobox->textlinecount - 1))
selected = mojobox->textlinecount - 1;
if (selected >= mojobox->textpos+maxh)
selected = (mojobox->textpos+maxh) - 1;
y = selected - lasttextpos;
wattron(mojobox->textwin, COLOR_PAIR(MOJOCOLOR_BUTTONHOVER) | A_BOLD);
mvwhline(mojobox->textwin, y, 0, ' ', maxw);
mvwaddstr(mojobox->textwin, y, 0, mojobox->textlines[selected]);
wattroff(mojobox->textwin, COLOR_PAIR(MOJOCOLOR_BUTTONHOVER) | A_BOLD);
wrefresh(mojobox->textwin);
} // if
lasttextpos = mojobox->textpos;
lasthoverover = mojobox->hoverover;
ch = wgetch(mojobox->mainwin);
if (ignoreerr) // kludge.
{
ignoreerr = false;
if (ch == ERR)
continue;
} // if
if (ch == KEY_RESIZE)
{
freeBox(mojobox, false); // catch and rebuild without upkeepBox,
mojobox = NULL; // so we can rebuild the text ourself.
ignoreerr = true; // kludge.
} // if
else if (ch == KEY_UP)
{
if (selected > 0)
{
WINDOW *win = mojobox->textwin;
int maxw, maxh;
int y = --selected - mojobox->textpos;
getmaxyx(win, maxh, maxw);
if (selected < mojobox->textpos)
{
upkeepBox(&mojobox, ch); // upkeepBox does scrolling
y++;
} // if
else
{
wattron(win, COLOR_PAIR(MOJOCOLOR_TEXT));
mvwhline(win, y+1, 0, ' ', maxw);
mvwaddstr(win, y+1, 0, mojobox->textlines[selected+1]);
wattroff(win, COLOR_PAIR(MOJOCOLOR_TEXT));
} // else
wattron(win, COLOR_PAIR(MOJOCOLOR_BUTTONHOVER) | A_BOLD);
mvwhline(win, y, 0, ' ', maxw);
mvwaddstr(win, y, 0, mojobox->textlines[selected]);
wattroff(win, COLOR_PAIR(MOJOCOLOR_BUTTONHOVER) | A_BOLD);
wrefresh(win);
} // if
} // else if
else if (ch == KEY_DOWN)
{
if (selected < (mojobox->textlinecount-1))
{
WINDOW *win = mojobox->textwin;
int maxw, maxh;
int y = ++selected - mojobox->textpos;
getmaxyx(win, maxh, maxw);
if (selected >= mojobox->textpos+maxh)
{
upkeepBox(&mojobox, ch); // upkeepBox does scrolling
y--;
} // if
else
{
wattron(win, COLOR_PAIR(MOJOCOLOR_TEXT));
mvwhline(win, y-1, 0, ' ', maxw);
mvwaddstr(win, y-1, 0, mojobox->textlines[selected-1]);
wattroff(win, COLOR_PAIR(MOJOCOLOR_TEXT));
} // else
wattron(win, COLOR_PAIR(MOJOCOLOR_BUTTONHOVER) | A_BOLD);
mvwhline(win, y, 0, ' ', maxw);
mvwaddstr(win, y, 0, mojobox->textlines[selected]);
wattroff(win, COLOR_PAIR(MOJOCOLOR_BUTTONHOVER) | A_BOLD);
wrefresh(win);
} // if
} // else if
else if ((ch == KEY_NPAGE) || (ch == KEY_NPAGE))
{
// !!! FIXME: maybe handle this when I'm not so lazy.
// !!! FIXME: For now, this if statement is to block
// !!! FIXME: upkeepBox() from scrolling and screwing up state.
} // else if
else // let upkeepBox handle other input (button selection, etc).
{
rc = upkeepBox(&mojobox, ch);
if (rc == togglebutton)
{
int line = 0;
rc = -1; // reset so we don't stop processing input.
if (toggle_option(NULL, opts, &line, selected+1) == 1)
{
freeBox(mojobox, false); // rebuild to reflect new options...
mojobox = NULL;
} // if
} // if
} // else
} while (rc == -1);
freeBox(mojobox, true);
for (i = 0; i < bcount; i++)
free(buttons[i]);
if (rc == backbutton)
return -1;
else if (rc == fwdbutton)
return 1;
return 0; // error? Cancel?
} // optionBox
static int MojoGui_ncurses_options(MojoGuiSetupOptions *opts,
boolean can_back, boolean can_fwd)
{
char *title = xstrdup(_("Options"));
int rc = optionBox(title, opts, can_back, can_fwd);
free(title);
return rc;
} // MojoGui_ncurses_options
static char *inputBox(const char *prompt, int *command, boolean can_back,
const char *defval)
{
char *text = NULL;
int w, h;
int i;
int ch;
int rc = -1;
MojoBox *mojobox = NULL;
size_t retvalalloc = 64;
size_t retvallen = 0;
char *retval = NULL;
char *buttons[3] = { NULL, NULL, NULL };
int drawpos = 0;
int drawlen = 0;
int bcount = 0;
int backbutton = -1;
int cancelbutton = -1;
if (defval == NULL)
retval = (char *) xmalloc(retvalalloc);
else
{
const size_t defvallen = strlen(defval);
if ((defvallen * 2) > retvalalloc)
retvalalloc = defvallen * 2;
retval = (char *) xmalloc(retvalalloc);
retvallen = defvallen;
strcpy(retval, defval);
} // else
buttons[bcount++] = xstrdup(_("OK"));
if (can_back)
{
backbutton = bcount++;
buttons[backbutton] = xstrdup(_("Back"));
} // if
cancelbutton = bcount++;
buttons[cancelbutton] = xstrdup(_("Cancel"));
getmaxyx(stdscr, h, w);
w -= 10;
text = (char *) xmalloc(w+4);
text[0] = '\n';
memset(text+1, ' ', w);
text[w+1] = '\n';
text[w+2] = ' ';
text[w+3] = '\0';
mojobox = makeBox(prompt, text, buttons, bcount, false, false);
free(text);
text = NULL;
do
{
getmaxyx(mojobox->textwin, h, w);
w -= 2;
if (drawpos >= retvallen)
drawpos = 0;
while ((drawlen = (retvallen - drawpos)) >= w)
drawpos += 5;
wattron(mojobox->textwin, COLOR_PAIR(MOJOCOLOR_TEXTENTRY) | A_BOLD);
mvwhline(mojobox->textwin, 1, 1, ' ', w); // blank line...
mvwaddstr(mojobox->textwin, 1, 1, retval + drawpos);
wattroff(mojobox->textwin, COLOR_PAIR(MOJOCOLOR_TEXTENTRY) | A_BOLD);
wrefresh(mojobox->textwin);
ch = wgetch(mojobox->mainwin);
if ( (ch > 0) && (ch < KEY_MIN) && (isprint(ch)) ) // regular key.
{
if (retvalalloc <= retvallen)
{
retvalalloc *= 2;
retval = xrealloc(retval, retvalalloc);
} // if
retval[retvallen++] = (char) ch;
retval[retvallen] = '\0';
} // if
else if (ch == KEY_BACKSPACE)
{
if (retvallen > 0)
retval[--retvallen] = '\0';
} // else if
else if (ch == KEY_RESIZE)
{
wrefresh(stdscr);
getmaxyx(stdscr, h, w);
w -= 10;
text = (char *) xrealloc(mojobox->text, w+4);
text[0] = '\n';
memset(text+1, ' ', w);
text[w+1] = '\n';
text[w+2] = ' ';
text[w+3] = '\0';
mojobox->text = text;
text = NULL;
upkeepBox(&mojobox, KEY_RESIZE); // let real resize happen...
} // else if
else
{
rc = upkeepBox(&mojobox, ch);
} // else
} while (rc == -1);
freeBox(mojobox, true);
for (i = 0; i < bcount; i++)
free(buttons[i]);
if (rc == backbutton)
*command = -1;
else if (rc == cancelbutton)
*command = 0;
else
*command = 1;
if (*command <= 0)
{
free(retval);
retval = NULL;
} // if
return retval;
} // inputBox
static char *MojoGui_ncurses_destination(const char **recommends, int recnum,
int *command, boolean can_back,
boolean can_fwd)
{
char *retval = NULL;
while (true)
{
const char *localized = NULL;
char *title = NULL;
char *choosetxt = NULL;
int rc = 0;
if (recnum > 0) // recommendations available.
{
int chosen = -1;
MojoGuiSetupOptions opts;
MojoGuiSetupOptions *prev = &opts;
MojoGuiSetupOptions *next = NULL;
MojoGuiSetupOptions *opt = NULL;
memset(&opts, '\0', sizeof (MojoGuiSetupOptions));
int i;
for (i = 0; i < recnum; i++)
{
opt = (MojoGuiSetupOptions *) xmalloc(sizeof (*opt));
opt->description = recommends[i];
opt->size = -1;
prev->next_sibling = opt;
prev = opt;
} // for
choosetxt = xstrdup(_("(I want to specify a path.)"));
opt = (MojoGuiSetupOptions *) xmalloc(sizeof (*opt));
opt->description = choosetxt;
opt->size = -1;
prev->next_sibling = opt;
prev = opt;
opts.child = opts.next_sibling; // fix this field.
opts.next_sibling = NULL;
opts.value = opts.child->value = true; // make first default.
opts.is_group_parent = true;
opts.size = -1;
title = xstrdup(_("Destination"));
rc = optionBox(title, &opts, can_back, can_fwd);
free(title);
for (i = 0, next = opts.child; next != NULL; i++)
{
if (next->value)
chosen = i;
prev = next;
next = prev->next_sibling;
free(prev);
} // for
free(choosetxt);
*command = rc;
if (rc <= 0) // back or cancel.
return NULL;
else if ((chosen >= 0) && (chosen < recnum)) // a specific entry
return xstrdup(recommends[chosen]);
} // if
// either no recommendations or user wants to enter own path...
localized = _("Enter path where files will be installed.");
title = xstrdup(localized);
retval = inputBox(title, &rc, (can_back) || (recnum > 0), NULL);
free(title);
// user cancelled or entered text, or hit back and we aren't falling
// back to the option list...return.
if ( (rc >= 0) || ((rc == -1) && (recnum == 0)) )
{
*command = rc;
return retval;
} // if
// falling back to the option list again...loop.
} // while
// Shouldn't ever hit this, but just in case...
*command = 0;
return NULL;
} // MojoGui_ncurses_destination
static int MojoGui_ncurses_productkey(const char *desc, const char *fmt,
char *buf, const int buflen,
boolean can_back, boolean can_fwd)
{
// !!! FIXME: need text option for (desc).
// !!! FIXME: need max text entry of (buflen)
// !!! FIXME: need to disable "next" button if code is invalid.
char *prompt = xstrdup(_("Please enter your product key"));
boolean getout = false;
int retval = 0;
while (!getout)
{
char *text = inputBox(prompt, &retval, can_back, buf);
if (retval != 1)
getout = true;
else
{
snprintf(buf, buflen, "%s", text);
if (isValidProductKey(fmt, text))
getout = true;
else
{
// !!! FIXME: just improve inputBox.
// We can't check the input character-by-character, so reuse
// the failed-verification localized string.
char *failtitle = xstrdup(_("Invalid product key"));
char *failstr = xstrdup(_("That key appears to be invalid. Please try again."));
MojoGui_ncurses_msgbox(failtitle, failstr);
free(failstr);
free(failtitle);
} // else
} // else
free(text);
} // while
free(prompt);
return retval;
} // MojoGui_ncurses_productkey
static boolean MojoGui_ncurses_insertmedia(const char *medianame)
{
char *fmt = xstrdup(_("Please insert '%0'"));
char *text = format(fmt, medianame);
char *localized_ok = xstrdup(_("OK"));
char *localized_cancel = xstrdup(_("Cancel"));
char *buttons[] = { localized_ok, localized_cancel };
MojoBox *mojobox = NULL;
int rc = 0;
mojobox = makeBox(_("Media change"), text, buttons, 2, false, true);
while ((rc = upkeepBox(&mojobox, wgetch(mojobox->mainwin))) == -1) {}
freeBox(mojobox, true);
free(localized_cancel);
free(localized_ok);
free(text);
free(fmt);
return (rc == 0);
} // MojoGui_ncurses_insertmedia
static void MojoGui_ncurses_progressitem(void)
{
// no-op in this UI target.
} // MojoGui_ncurses_progressitem
static boolean MojoGui_ncurses_progress(const char *type, const char *component,
int percent, const char *item,
boolean can_cancel)
{
const uint32 now = ticks();
boolean rebuild = (progressBox == NULL);
int ch = 0;
int rc = -1;
if ( (lastComponent == NULL) ||
(strcmp(lastComponent, component) != 0) ||
(lastProgressType == NULL) ||
(strcmp(lastProgressType, type) != 0) ||
(lastCanCancel != can_cancel) )
{
free(lastProgressType);
free(lastComponent);
lastProgressType = xstrdup(type);
lastComponent = xstrdup(component);
lastCanCancel = can_cancel;
rebuild = true;
} // if
if (rebuild)
{
int w, h;
char *text = NULL;
char *localized_cancel = (can_cancel) ? xstrdup(_("Cancel")) : NULL;
char *buttons[] = { localized_cancel };
const int buttoncount = (can_cancel) ? 1 : 0;
char *spacebuf = NULL;
getmaxyx(stdscr, h, w);
w -= 10;
text = (char *) xmalloc((w * 3) + 16);
if (snprintf(text, w, "%s", component) > (w-4))
strcpy((text+w)-4, "..."); // !!! FIXME: Unicode problem.
strcat(text, "\n\n");
spacebuf = (char *) xmalloc(w+1);
memset(spacebuf, ' ', w); // xmalloc provides null termination.
strcat(text, spacebuf);
free(spacebuf);
strcat(text, "\n\n ");
freeBox(progressBox, false);
progressBox = makeBox(type, text, buttons, buttoncount, true, true);
free(text);
free(localized_cancel);
} // if
// limit update spam... will only write every one second, tops.
if ((rebuild) || (percentTicks <= now))
{
static boolean unknownToggle = false;
char *buf = NULL;
WINDOW *win = progressBox->textwin;
int w, h;
getmaxyx(win, h, w);
w -= 2;
buf = (char *) xmalloc(w+1);
if (percent < 0)
{
const boolean origToggle = unknownToggle;
int i;
wmove(win, h-3, 1);
for (i = 0; i < w; i++)
{
if (unknownToggle)
waddch(win, ' ' | COLOR_PAIR(MOJOCOLOR_TODO));
else
waddch(win, ' ' | COLOR_PAIR(MOJOCOLOR_DONE));
unknownToggle = !unknownToggle;
} // for
unknownToggle = !origToggle; // animate by reversing next time.
} // if
else
{
int cells = (int) ( ((double) w) * (((double) percent) / 100.0) );
snprintf(buf, w+1, "%d%%", percent);
mvwaddstr(win, h-3, ((w+2) - utf8len(buf)) / 2, buf);
mvwchgat(win, h-3, 1, cells, A_BOLD, MOJOCOLOR_DONE, NULL);
mvwchgat(win, h-3, 1+cells, w-cells, A_BOLD, MOJOCOLOR_TODO, NULL);
} // else
wtouchln(win, h-3, 1, 1); // force reattributed cells to update.
if (snprintf(buf, w+1, "%s", item) > (w-4))
strcpy((buf+w)-4, "..."); // !!! FIXME: Unicode problem.
mvwhline(win, h-2, 1, ' ', w);
mvwaddstr(win, h-2, ((w+2) - utf8len(buf)) / 2, buf);
free(buf);
wrefresh(win);
percentTicks = now + 1000;
} // if
// !!! FIXME: check for input here for cancel button, resize, etc...
ch = wgetch(progressBox->mainwin);
if (ch == KEY_RESIZE)
{
freeBox(progressBox, false);
progressBox = NULL;
} // if
else if (ch == ERR) // can't distinguish between an error and a timeout!
{
// do nothing...
} // else if
else
{
rc = upkeepBox(&progressBox, ch);
} // else
return (rc == -1);
} // MojoGui_ncurses_progress
static void MojoGui_ncurses_final(const char *msg)
{
char *title = xstrdup(_("Finish"));
MojoGui_ncurses_msgbox(title, msg);
free(title);
} // MojoGui_ncurses_final
// end of gui_ncurses.c ...