/* $Id: slangui.c,v 1.24 2001/11/22 17:53:48 tausq Rel $ */ /* slangui.c - SLang user interface routines */ /* TODO: the redraw code is a bit broken, also this module is using way too many * global vars */ #include "tasksel.h" #include "slangui.h" #include #include #include #include #include #include "data.h" #include "util.h" #include "strutl.h" #include "macros.h" #define TASK_SHORTDESC(t) ((t)->task_pkg ? (t)->task_pkg->shortdesc : _("(no description)")) #define TASK_LONGDESC(t) ((t)->task_pkg ? (t)->task_pkg->longdesc : _("(no description)")) #define TASK_SECTION(t) ((t)->task_pkg && (t)->task_pkg->section ? (t)->task_pkg->section : "misc") /* Slang object number mapping */ #define DEFAULTOBJ 0 #define SHADOWOBJ 2 /* must be 2 due to slang weirdness */ #define CHOOSEROBJ 1 #define DESCOBJ 3 #define STATUSOBJ 4 #define DIALOGOBJ 5 #define CURSOROBJ 6 #define SELBUTTONOBJ 7 #define BUTTONOBJ 8 #define SELHIGHLIGHT 9 #define HIGHLIGHT 10 #define SCROLLBAR 11 #define CHOOSERWINDOW 0 #define DESCWINDOW 1 #define HELPWINDOW 2 #define NUM_BUTTONS 3 #define BUTTON_NONE 0 #define BUTTON_FINISH 1 #define BUTTON_INFO 2 #define BUTTON_HELP 3 #define SCROLLBAR_NONE 0 #define SCROLLBAR_HORIZ 1 #define SCROLLBAR_VERT 2 #define ROWS SLtt_Screen_Rows #define COLUMNS SLtt_Screen_Cols struct _chooserinfo_t { int coloffset; int rowoffset; int height; int width; int index; int topindex; int whichwindow; }; /* Module private variables */ static struct _chooserinfo_t _chooserinfo = {3, 3, 0, 0, 0, 0, 0}; static int _resizing = 0; static int _initialized = 0; static struct packages_t *_packages = NULL; static struct tasks_t *_tasks = NULL; static struct task_t **_tasksary = NULL; static int *_displayhint = NULL; static int _displaylines; struct { char *section, *desc; } sectiondesc[] = { { "user", N_("End-user") }, { "server", N_("Servers") }, { "devel", N_("Development") }, { "l10n", N_("Localization") }, { "hware", N_("Hardware Support") }, { "misc", N_("Miscellaneous") }, {0} }; static char *getsectiondesc(char *sec) { int i; for (i = 0; sectiondesc[i].section; i++) if (strcmp(sec, sectiondesc[i].section) == 0) return _(sectiondesc[i].desc); return sec; } static void initdisplayhints(void) { int i, j, k; char *lastsec = NULL; _displaylines = 0; for (i = 0; i < _tasks->count; i++) { char *sec = TASK_SECTION(_tasksary[i]); if (lastsec == NULL || strcmp(lastsec, sec) != 0) { _displaylines++; lastsec = sec; } _displaylines++; } _displayhint = MALLOC(sizeof(int)*_displaylines); k = 0; for (i = 0; sectiondesc[i].section; i++) { for (j = 0; j < _tasks->count; j++) { char *sec = TASK_SECTION(_tasksary[j]); if (strcmp(sec, sectiondesc[i].section) == 0) { _displayhint[k++] = -1; for (; j < _tasks->count; j++) { char *sec = TASK_SECTION(_tasksary[j]); if (strcmp(sec, sectiondesc[i].section) == 0) { _displayhint[k++] = j; } } break; } } } lastsec = NULL; for (i = 0; i < _tasks->count; i++) { char *sec = TASK_SECTION(_tasksary[i]); for (j = 0; sectiondesc[j].section; j++) { if (strcmp(sectiondesc[j].section, sec) == 0) { j = -1; break; } } if (j == -1) continue; if (lastsec == NULL || strcmp(lastsec, sec) != 0) { _displayhint[k++] = -1; lastsec = sec; } _displayhint[k++] = i; } } int taskseccompare(const void *pl, const void *pr) { const struct task_t *l = *((const struct task_t **)pl); const struct task_t *r = *((const struct task_t **)pr); char *ls = NULL, *rs = NULL; int y = strcmp(l->name, r->name); if (y == 0) return 0; if (l->task_pkg && l->task_pkg->section) ls = l->task_pkg->section; if (r->task_pkg && r->task_pkg->section) rs = r->task_pkg->section; if (ls && rs) { int x = strcmp(ls, rs); if (x != 0) return x; } else if (ls) { return -1; } else if (rs) { return 1; } return y; } void ui_init(int argc, char * const argv[], struct tasks_t *tasks, struct packages_t *pkgs) { _tasks = tasks; _tasksary = tasks_enumerate(tasks); qsort(_tasksary, _tasks->count, sizeof(_tasksary[0]), taskseccompare); initdisplayhints(); _packages = pkgs; SLang_set_abort_signal(tasksel_signalhandler); /* assign attributes to objects */ SLtt_set_color(DEFAULTOBJ, NULL, "white", "blue"); SLtt_set_color(SHADOWOBJ, NULL, "gray", "black"); SLtt_set_color(CHOOSEROBJ, NULL, "black", "lightgray"); SLtt_set_color(CURSOROBJ, NULL, "blue", "lightgray"); SLtt_set_color(DESCOBJ, NULL, "black", "cyan"); SLtt_set_color(STATUSOBJ, NULL, "yellow", "blue"); SLtt_set_color(DIALOGOBJ, NULL, "black", "lightgray"); SLtt_set_color(SELBUTTONOBJ, NULL, "lightgray", "blue"); SLtt_set_color(BUTTONOBJ, NULL, "lightgray", "red"); SLtt_set_color(SELHIGHLIGHT, NULL, "white", "blue"); SLtt_set_color(HIGHLIGHT, NULL, "white", "red"); SLtt_set_color(SCROLLBAR, NULL, "black", "lightgray"); ui_resize(); _initialized = 1; } int ui_shutdown(void) { SLsmg_reset_smg(); SLang_reset_tty(); _initialized = 0; return 0; } int ui_running(void) { return _initialized; } void ui_resize(void) { char buf[160]; _resizing = 1; /* SIGWINCH handler */ if (-1 == SLang_init_tty(-1, 0, 1)) DIE(_("Unable to initialize the terminal")); SLtt_get_terminfo(); if (-1 == SLsmg_init_smg()) DIE(_("Unable to initialize screen output")); if (-1 == SLkp_init()) DIE(_("Unable to initialize keyboard interface")); /* if (SLtt_Screen_Rows < 20 || SLtt_Screen_Cols < 70) { DIE(_("Sorry, tasksel needs a terminal with at least 70 columns and 20 rows")); } */ _chooserinfo.height = ROWS - 2 * _chooserinfo.rowoffset - 3; _chooserinfo.width = COLUMNS - 2 *_chooserinfo.coloffset; SLsmg_cls(); /* Show version and status lines */ SLsmg_set_color(STATUSOBJ); snprintf(buf, 160, _("Debian Task Installer v%s - (c) 1999-2001 SPI and others"), VERSION); SLsmg_gotorc(0, 0); SLsmg_write_nstring(buf, strlen(buf)); _resizing = 0; switch (_chooserinfo.whichwindow) { case CHOOSERWINDOW: ui_drawscreen(); break; case HELPWINDOW: ui_showhelp(); break; case DESCWINDOW: ui_showpackageinfo(); break; } } int ui_eventloop(void) { int done = 0; int ret = 0; int c, i; int onitem = 0; _chooserinfo.topindex = 0; _chooserinfo.index = 0; ui_redrawcursor(0); while (!done) { c = SLkp_getkey(); switch (c) { case SL_KEY_LEFT: onitem = ui_cycleselection(-1); SLsmg_refresh(); break; case SL_KEY_UP: ui_clearcursor(_chooserinfo.index); if (_chooserinfo.index > 0) _chooserinfo.index--; else _chooserinfo.index = _displaylines - 1; ui_redrawcursor(_chooserinfo.index); break; case SL_KEY_RIGHT: onitem = ui_cycleselection(1); SLsmg_refresh(); break; case SL_KEY_DOWN: ui_clearcursor(_chooserinfo.index); if (_chooserinfo.index < _displaylines - 1) _chooserinfo.index++; else _chooserinfo.index = 0; ui_redrawcursor(_chooserinfo.index); break; case SL_KEY_PPAGE: ui_clearcursor(_chooserinfo.index); _chooserinfo.index -= _chooserinfo.height; if (_chooserinfo.index < 0) _chooserinfo.index = 0; ui_redrawcursor(_chooserinfo.index); break; case SL_KEY_NPAGE: ui_clearcursor(_chooserinfo.index); _chooserinfo.index += _chooserinfo.height; if (_chooserinfo.index > _displaylines - 1) _chooserinfo.index = _displaylines - 1; ui_redrawcursor(_chooserinfo.index); break; case SL_KEY_ENTER: case '\r': case '\n': case ' ': if (onitem == BUTTON_NONE) { ui_toggleselection(_chooserinfo.index); ui_redrawcursor(_chooserinfo.index); } else if (onitem == BUTTON_FINISH) { done = 1; } else if (onitem == BUTTON_INFO) { ui_showpackageinfo(); } else if (onitem == BUTTON_HELP) { ui_showhelp(); } break; case '\t': onitem = ui_cycleselection(1); SLsmg_refresh(); break; case 'A': case 'a': for (i = 0; i < _tasks->count; i++) _tasksary[i]->selected = 1; ui_drawscreen(); break; case 'N': case 'n': for (i = 0; i < _tasks->count; i++) _tasksary[i]->selected = 0; ui_drawscreen(); break; case 'H': case 'h': case SL_KEY_F(1): ui_showhelp(); break; case 'I': case 'i': ui_showpackageinfo(); break; case 'F': case 'f': case 'Q': case 'q': done = 1; break; } } return ret; } void ui_shadow(int y, int x, unsigned int dy, unsigned int dx) { int c; unsigned short ch; if (SLtt_Use_Ansi_Colors) { for (c=0;c NUM_BUTTONS) whichsel = 0; else if (whichsel < 0) whichsel = NUM_BUTTONS; if (whichsel > 0) _drawbutton(whichsel, 1); ui_redrawcursor(_chooserinfo.index); return whichsel; } int ui_drawscreen(void) { int i; /* Draw the chooser screen */ SLsmg_set_color(DEFAULTOBJ); ui_drawbox(CHOOSEROBJ, _chooserinfo.rowoffset - 1, _chooserinfo.coloffset - 1, _chooserinfo.height + 5, _chooserinfo.width + 2, 1); ui_title(_chooserinfo.rowoffset - 1, _chooserinfo.coloffset - 1, COLUMNS - 3, _("Select tasks to install")); for (i = _chooserinfo.topindex; i < _chooserinfo.topindex + _chooserinfo.height; i++) ui_drawchooseritem(i); ui_vscrollbar(_chooserinfo.rowoffset, _chooserinfo.coloffset + _chooserinfo.width - 1, _chooserinfo.height, 0); for (i = 0; i <= NUM_BUTTONS; i++) _drawbutton(i, 0); ui_cycleselection(0); SLsmg_refresh(); return 0; } /* Widgets */ void ui_vscrollbar(int row, int col, int height, double percent) { int i; /* fudge the percent a bit -- this makes sure it shows up properly */ percent -= 0.05; if (percent < 0.01) percent = 0.01; if (percent > 100.0) percent = 100.0; SLsmg_set_color(SCROLLBAR); for (i = 0; i < height; i++) { SLsmg_gotorc(row+i, col); if (((double)i)/height < percent && ((double)i+1)/height >= percent) { SLsmg_set_char_set(1); SLsmg_write_char(SLSMG_DIAMOND_CHAR); SLsmg_set_char_set(0); /* SLsmg_write_char('#'); */ } else { SLsmg_set_char_set(1); SLsmg_write_char(SLSMG_CKBRD_CHAR); SLsmg_set_char_set(0); } } } void ui_hscrollbar(int row, int col, int width, double percent) { int i; /* fudge the percent a bit -- this makes sure it shows up properly */ percent -= 0.05; if (percent < 0.01) percent = 0.01; if (percent > 100.0) percent = 100.0; SLsmg_set_color(SCROLLBAR); for (i = 0; i < width; i++) { SLsmg_gotorc(row, col+i); if (((double)i)/width < percent && ((double)i+1)/width >= percent) { SLsmg_write_char('#'); } else { SLsmg_set_char_set(1); SLsmg_write_char(SLSMG_CKBRD_CHAR); SLsmg_set_char_set(0); } } } void ui_button(int row, int col, char *txt, int selected) { char *p; if (selected) SLsmg_set_color(SELBUTTONOBJ); else SLsmg_set_color(BUTTONOBJ); SLsmg_gotorc(row, col); SLsmg_write_char('<'); /* Anything following a ^ in txt is highlighted, and the ^ removed. */ p = strchr(txt, '^'); if (p) { if (p > txt) { SLsmg_write_nstring(txt, p - txt); } p++; if (selected) SLsmg_set_color(SELHIGHLIGHT); else SLsmg_set_color(HIGHLIGHT); SLsmg_write_char(p[0]); p++; if (selected) SLsmg_set_color(SELBUTTONOBJ); else SLsmg_set_color(BUTTONOBJ); SLsmg_write_string(p); } else SLsmg_write_string(txt); SLsmg_write_char('>'); } void ui_title(int row, int col, int width, char *title) { int pos = col + (width - strlen(title))/2; SLsmg_gotorc(row, pos - 1); SLsmg_set_char_set(1); SLsmg_write_char(SLSMG_RTEE_CHAR); SLsmg_set_char_set(0); SLsmg_write_char(' '); SLsmg_write_string(title); SLsmg_write_char(' '); SLsmg_set_char_set(1); SLsmg_write_char(SLSMG_LTEE_CHAR); SLsmg_set_char_set(0); } static void ui_dialog_drawlines(int row, int col, int height, int width, char **buf, int topline, int leftcol, int numlines, int scroll) { /* helper function for ui_dialog */ int ri; int hoffset = ((scroll & SCROLLBAR_HORIZ) ? 6 : 4); int woffset = ((scroll & SCROLLBAR_VERT) ? 5 : 3); VERIFY(buf != NULL); SLsmg_set_color(DIALOGOBJ); SLsmg_fill_region(row+1, col+1, height-2, width-2, ' '); for (ri = topline; ri < numlines && ri - topline < height - hoffset; ri++) { SLsmg_gotorc(row + 1 + ri-topline, col + 1); if (strlen(buf[ri]) > leftcol) SLsmg_write_nstring(buf[ri]+leftcol, width - woffset); } if (scroll & SCROLLBAR_VERT && numlines > height-hoffset) ui_vscrollbar(row+1, col+width-2, height-hoffset, ((double)topline+1)/numlines); if (scroll & SCROLLBAR_HORIZ) ui_hscrollbar(row+height-4, col+1, width-woffset, ((double)leftcol+1)/(width-woffset)); ui_button(row+height-2, col+(width-4)/2, _("Ok"), 1); SLsmg_refresh(); } void ui_dialog(int row, int col, int height, int width, char *title, char *msg, int reflow, int scroll) { char *reflowbuf; int ri, c, topline = 0, leftcol = 0, numlines = 0, done = 0, redraw; char *line, *txt = NULL, **buf = NULL; if (reflow) reflowbuf = reflowtext(width - 6, msg); else reflowbuf = msg; SLsmg_set_color(DIALOGOBJ); ui_drawbox(DIALOGOBJ, row, col, height, width, 1); if (title) ui_title(row, col, width, title); if (reflowbuf != NULL) { txt = reflowbuf; while ((line = strchr(txt, '\n'))) { numlines++; txt = line+1; } numlines++; buf = MALLOC(numlines * sizeof(char *)); ri = 0; txt = reflowbuf; while ((line = strsep(&txt, "\n"))) { buf[ri++] = line; } } ui_dialog_drawlines(row, col, height, width, buf, topline, leftcol, numlines, scroll); /* local event loop */ while (!done) { redraw = 0; c = SLkp_getkey(); switch (c) { case '\r': case '\n': case SL_KEY_ENTER: done = 1; break; case SL_KEY_UP: if (topline > 0) { topline--; redraw = 1; } break; case SL_KEY_DOWN: if (topline < numlines - 1) { topline++; redraw = 1; } break; case SL_KEY_PPAGE: topline -= (height-5); if (topline < 0) topline = 0; redraw = 1; break; case SL_KEY_NPAGE: topline += (height-5); if (topline > numlines - 1) topline = numlines - 1; redraw = 1; break; } if (redraw) { ui_dialog_drawlines(row, col, height, width, buf, topline, leftcol, numlines, scroll); } } if (buf) FREE(buf); if (reflow) FREE(reflowbuf); } void ui_drawsection(int row, int index) { char buf[1024]; int spot; ASSERT(_tasks != NULL); if (index >= _tasks->count) DIE("Index out of bounds: %d >= %d", index, _tasks->count); SLsmg_set_color(CHOOSEROBJ); SLsmg_gotorc(row, _chooserinfo.coloffset + 1); SLsmg_draw_hline( 2 ); SLsmg_gotorc(row, _chooserinfo.coloffset + 1 + 2); snprintf(buf, 1024, " %s ", getsectiondesc(TASK_SECTION(_tasksary[index]))); SLsmg_write_nstring(buf, _chooserinfo.width - 1 - 2); spot = 1 + 2 + strlen(buf); if (spot > _chooserinfo.width / 2 - 3) spot = _chooserinfo.width / 2 - 3; SLsmg_gotorc(row, _chooserinfo.coloffset + spot); SLsmg_draw_hline( _chooserinfo.width / 2 - spot ); } void ui_drawtask(int row, int index) { char buf[1024]; ASSERT(_tasks != NULL); if (index >= _tasks->count) DIE(_("Index out of bounds: %d >= %d"), index, _tasks->count); SLsmg_set_color(CHOOSEROBJ); SLsmg_gotorc(row, _chooserinfo.coloffset + 1); snprintf(buf, 1024, "[%c] %s", (_tasksary[index]->selected == 0 ? ' ' : '*'), TASK_SHORTDESC(_tasksary[index])); SLsmg_write_nstring(buf, _chooserinfo.width - 1); } void ui_toggletask(int row, int index) { ASSERT(_tasks != NULL); if (index >= _tasks->count) DIE(_("Index out of bounds: %d >= %d"), index, _tasks->count); if (_tasksary[index]->selected == 0) _tasksary[index]->selected = 1; else _tasksary[index]->selected = 0; if (row >= _chooserinfo.rowoffset + _chooserinfo.height) { return; } SLsmg_set_color(CHOOSEROBJ); SLsmg_gotorc(row, _chooserinfo.coloffset + 1); if (_tasksary[index]->selected == 0) SLsmg_write_string("[ ]"); else SLsmg_write_string("[*]"); SLsmg_gotorc(row, _chooserinfo.coloffset + 3); SLsmg_refresh(); } void ui_redrawchooser(void) { int i; for (i = _chooserinfo.topindex; i < _chooserinfo.topindex + _chooserinfo.height; i++) ui_drawchooseritem(i); } void ui_redrawcursor(int index) { /* Check to see if we have to scroll the selection */ if (index + 1 - _chooserinfo.height > _chooserinfo.topindex) { _chooserinfo.topindex = index + 1 - _chooserinfo.height; ui_redrawchooser(); } else if (index < _chooserinfo.topindex) { _chooserinfo.topindex = index; ui_redrawchooser(); } ui_vscrollbar(_chooserinfo.rowoffset, _chooserinfo.coloffset + _chooserinfo.width - 1, _chooserinfo.height, ((double)index+1)/_displaylines); if (_displayhint[index] >= 0) { SLsmg_gotorc(_chooserinfo.rowoffset + index - _chooserinfo.topindex, _chooserinfo.coloffset + 2); SLsmg_set_color(CURSOROBJ); if (_tasksary[_displayhint[index]]->selected) { SLsmg_write_string("*"); SLsmg_gotorc(_chooserinfo.rowoffset + index - _chooserinfo.topindex, _chooserinfo.coloffset + 2); } else { SLsmg_write_string("#"); SLsmg_gotorc(_chooserinfo.rowoffset + index - _chooserinfo.topindex, _chooserinfo.coloffset + 2); SLsmg_write_string(" "); SLsmg_gotorc(_chooserinfo.rowoffset + index - _chooserinfo.topindex, _chooserinfo.coloffset + 2); } } else { SLsmg_gotorc(_chooserinfo.rowoffset + index - _chooserinfo.topindex, _chooserinfo.coloffset + 4); } SLsmg_refresh(); } void ui_clearcursor(int index) { if (_displayhint[index] >= 0) { SLsmg_set_color(DIALOGOBJ); SLsmg_gotorc(_chooserinfo.rowoffset + index - _chooserinfo.topindex, _chooserinfo.coloffset + 2); SLsmg_write_string(_tasksary[_displayhint[index]]->selected == 0 ? " " : "*"); } } void ui_showhelp(void) { _chooserinfo.whichwindow = HELPWINDOW; ui_dialog(3, 3, ROWS - 7, COLUMNS - 10, _("Help"), _("Tasks allow you to quickly install " \ "a selection of packages that performs a given task.\n\nThe main " \ "chooser list shows a list of tasks that you can choose to " \ "install. The arrow keys moves the cursor. Pressing ENTER or the " \ "SPACEBAR toggles the selection of the task at the cursor. You can " \ "also press A to select all tasks, or N to deselect all tasks. " \ "Pressing Q will exit this program and begin installation of your " \ "selected tasks.\n\n" \ "Thank you for using Debian.\n\n" \ "Press enter to return to the task selection screen" /* TRANS: don't wrap lines because of different screen sizes! */), 1, SCROLLBAR_VERT); _chooserinfo.whichwindow = CHOOSERWINDOW; ui_drawscreen(); } void ui_showpackageinfo(void) { struct package_t *deppkg; struct task_t *tsk; int i; int width = COLUMNS - 10; char buf[4096]; char shortbuf[256]; char *desc = NULL; int bufleft; int index = _displayhint[_chooserinfo.index]; if (index < 0) return; ASSERT(_tasks != NULL); _chooserinfo.whichwindow = DESCWINDOW; if (index >= _tasks->count) DIE(_("Index out of bounds: %d >= %d"), index, _tasks->count); tsk = _tasksary[index]; ASSERT(tsk != NULL); desc = reflowtext(width, TASK_LONGDESC(tsk)); /* pack buf with package info */ snprintf(buf, sizeof(buf), _("Description:\n%s\n\nIncluded packages:\n"), desc); FREE(desc); bufleft = sizeof(buf) - strlen(buf) - 1; ASSERT(width < sizeof(shortbuf)); for (i = 0; i < tsk->packagescount; i++) { deppkg = packages_find(_packages, tsk->packages[i]); snprintf(shortbuf, sizeof(shortbuf), "%s - %s\n", tsk->packages[i], ((deppkg && deppkg->shortdesc) ? deppkg->shortdesc : _("(no description available)"))); strncat(buf, shortbuf, bufleft); bufleft = sizeof(buf) - strlen(buf) - 1; } ui_dialog(2, 2, ROWS-4, COLUMNS-4, tsk->name, buf, 0, SCROLLBAR_VERT); _chooserinfo.whichwindow = CHOOSERWINDOW; ui_drawscreen(); } void ui_drawchooseritem(int index) { int realrow = _chooserinfo.rowoffset + index - _chooserinfo.topindex; int task; if (index >= _displaylines) return; task = _displayhint[index]; if (task == -1) { task = _displayhint[index+1]; ui_drawsection(realrow, task); } else { ui_drawtask(realrow, task); } } void ui_toggleselection(int index) { int task = _displayhint[index]; if (task >= 0) { ui_toggletask(_chooserinfo.rowoffset + index - _chooserinfo.topindex, task); } else { int first = index+1, last, setto = 0; for(;;) { index++; if (index >= _displaylines) break; task = _displayhint[index]; if (task == -1) break; setto = setto || _tasksary[task]->selected == 0; } last = index; for (index = first; index < last; index++) { task = _displayhint[index]; if ((!_tasksary[task]->selected) ^ (!setto)) { ui_toggletask(_chooserinfo.rowoffset + index - _chooserinfo.topindex, task); } } } }