MegaGlest/mk/linux/mojosetup/gui_ncurses.c

1511 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.
*
Copyright (c) 2006-2010 Ryan C. Gordon and others.
This software is provided 'as-is', without any express or implied warranty.
In no event will the authors be held liable for any damages arising from
the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software in a
product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Ryan C. Gordon <icculus@icculus.org>
*
*/
#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 ...