Commit 88ff0ffa authored by Randolph Chung's avatar Randolph Chung

Task selection UI Initial CVS

parents
PROGRAM = tasksel
VERSION=\"0.1\"
CC = gcc
CFLAGS = -g -Wall
DEFS = -DVERSION=$(VERSION) -DPACKAGE=\"$(PROGRAM)\" -DLOCALEDIR=\"/usr/share/locale\" #-DDEBUG
LIBS = -lslang
OBJS = tasksel.o slangui.o data.o util.o strutl.o
COMPILE = $(CC) $(CFLAGS) $(DEFS) -c
LINK = $(CC) $(CFLAGS) $(DEFS) -o
all: $(PROGRAM)
%.o: %.c
$(COMPILE) $<
po/build_stamp:
$(MAKE) -C po
$(PROGRAM): $(OBJS) po/build_stamp
$(LINK) $(PROGRAM) $(OBJS) $(LIBS)
test:
$(MAKE) -C scratch
clean:
rm -f $(PROGRAM) *.o *~
$(MAKE) -C po clean
$Id: README,v 1.1 1999/11/21 22:01:04 tausq Exp $
Task Selection UI v0.1
Nov 20, 1999
Randolph Chung <tausq@debian.org>
Here's a first cut at a task selection user interface.
The interface GUI is based on libslang. It has no other special library
dependencies.
On startup, the tasksel program will read /var/lib/dpkg/available to
identify task packages (matching "task-*"). These packages will be
presented in a simple list selection screen with their short descriptions.
Users can drill down into the task packages to see detailed descriptions and
some information about the packages in the task.
On exit, tasksel executes the appropriate apt-get command to install the
selected packages. If the -t option is given, then tasksel prints out the
command line to use to stdout instead. All other messages are printed to
stderr.
The appropriate display strings in the source have been marked with gettext
macros, though I have never done any other i18n work and am not sure if it
is done correctly at the moment.
A word on building.... use make CFLAGS="-Os" to get a smaller binary. The
default build rule produces binaries with debugging symbols. You can also
turn on -DDEBUG to get extra debugging info.
Comments and suggestions are most welcome.
$Id: TODO,v 1.1 1999/11/21 22:01:04 tausq Exp $
- fix screen resize code
/* $Id: data.c,v 1.1 1999/11/21 22:01:04 tausq Exp $ */
/* data.c - encapsulates functions for reading a package listing like dpkg's available file
* Internally, packages are stored in a binary tree format to faciliate search operations
*/
#include "data.h"
#include <stdio.h>
#include <stdlib.h>
#include <search.h>
#include <string.h>
#include <ctype.h>
#include "util.h"
#include "macros.h"
#define PACKAGEFIELD "Package: "
#define DEPENDSFIELD "Depends: "
#define RECOMMENDSFIELD "Recommends: "
#define SUGGESTSFIELD "Suggests: "
#define DESCRIPTIONFIELD "Description: "
#define AVAILABLEFILE "/var/lib/dpkg/available"
#define BUF_SIZE 1024
#define MATCHFIELD(buf, s) (strncmp(buf, s, strlen(s)) == 0)
#define FIELDDATA(buf, s) (buf + strlen(s))
#define CHOMP(s) if (s[strlen(s)-1] == '\n') s[strlen(s)-1] = 0
/* module variables */
static struct package_t **_packages_enumbuf = NULL;
static int _packages_enumcount = 0;
static void *_packages_root = NULL;
/* private functions */
static int packagecompare(const void *p1, const void *p2)
{
/* compares two packages by name; used for binary tree routines */
char *s1, *s2;
s1 = ((struct package_t *)p1)->name;
s2 = ((struct package_t *)p2)->name;
return strcmp(s1, s2);
}
static void packages_walk_enumerate(const void *nodep, const VISIT order, const int depth)
{
/* adds nodep to list of nodes if we haven't visited this node before */
struct package_t *datap = *(struct package_t **)nodep;
if (order == leaf || order == endorder) {
_packages_enumcount++;
_packages_enumbuf[_packages_enumcount - 1] = datap;
}
}
static void packages_walk_delete(const void *nodep, const VISIT order, const int depth)
{
/* deletes memory associated with nodep */
struct package_t *datap = *(struct package_t **)nodep;
int i;
if (order == leaf || order == endorder) {
tdelete((void *)datap, &_packages_root, packagecompare);
if (datap->name) FREE(datap->name);
if (datap->shortdesc) FREE(datap->shortdesc);
if (datap->longdesc) FREE(datap->longdesc);
if (datap->depends) {
for (i = 0; i < datap->dependscount; i++) FREE(datap->depends[i]);
FREE(datap->depends);
}
if (datap->suggests) {
for (i = 0; i < datap->suggestscount; i++) FREE(datap->suggests[i]);
FREE(datap->suggests);
}
if (datap->recommends) {
for (i = 0; i < datap->recommendscount; i++) FREE(datap->recommends[i]);
FREE(datap->recommends);
}
FREE(datap);
}
}
int splitlinkdesc(const char *desc, char ***array)
{
/* given a comma separate list of names, returns an array with the names split into elts of the array */
char *p;
char *token;
int i = 0, elts = 1;
VERIFY(array != NULL);
*array = NULL;
if (desc == NULL) return 0;
p = (char *)desc;
while (*p != 0) if (*p++ == ',') elts++;
*array = MALLOC(sizeof(char *) * elts);
memset(*array, 0, sizeof(char *) * elts);
p = (char *)desc;
while ((token = strsep(&p, ","))) {
while (isspace(*token)) token++;
(*array)[i++] = STRDUP(token);
}
return elts;
}
static void addpackage(struct packages_t *pkgs,
const char *name, const char *dependsdesc, const char *recommendsdesc,
const char *suggestsdesc, const char *shortdesc, const char *longdesc)
{
/* Adds package to the package list binary tree */
struct package_t *node = NEW(struct package_t);
void *p;
VERIFY(name != NULL);
/* DPRINTF("Adding package %s to list\n", name); */
memset(node, 0, sizeof(struct package_t));
node->name = STRDUP(name);
node->shortdesc = STRDUP(shortdesc);
node->longdesc = STRDUP(longdesc);
if (dependsdesc) node->dependscount = splitlinkdesc(dependsdesc, &node->depends);
if (recommendsdesc) node->recommendscount = splitlinkdesc(recommendsdesc, &node->recommends);
if (suggestsdesc) node->suggestscount = splitlinkdesc(suggestsdesc, &node->suggests);
p = tsearch((void *)node, &pkgs->packages, packagecompare);
VERIFY(p != NULL);
pkgs->count++;
}
/* public functions */
struct package_t *packages_find(const struct packages_t *pkgs, const char *name)
{
/* Given a package name, returns a pointer to the appropriate package_t structure
* or NULL if none is found */
struct package_t pkg;
void *result;
pkg.name = (char *)name;
result = tfind((void *)&pkg, &pkgs->packages, packagecompare);
if (result == NULL)
return NULL;
else
return *(struct package_t **)result;
}
struct package_t **packages_enumerate(const struct packages_t *packages)
{
/* Converts the packages binary tree into a array. Do not modify the returned array! It
* is a static variable managed by this module */
if (_packages_enumbuf != NULL) {
FREE(_packages_enumbuf);
}
_packages_enumbuf = MALLOC(sizeof(struct package_t *) * packages->count);
if (_packages_enumbuf == NULL)
DIE("Cannot allocate memory for enumeration buffer");
memset(_packages_enumbuf, 0, sizeof(struct package_t *) * packages->count);
_packages_enumcount = 0;
twalk((void *)packages->packages, packages_walk_enumerate);
ASSERT(_packages_enumcount == packages->count);
return _packages_enumbuf;
}
void packages_readlist(struct packages_t *taskpkgs, struct packages_t *pkgs)
{
/* Populates internal data structures with information for an available file */
FILE *f;
char buf[BUF_SIZE];
char *name, *shortdesc, *longdesc;
char *dependsdesc, *recommendsdesc, *suggestsdesc;
VERIFY(taskpkgs != NULL); VERIFY(pkgs != NULL);
/* Initialization */
memset(taskpkgs, 0, sizeof(struct packages_t));
memset(pkgs, 0, sizeof(struct packages_t));
if ((f = fopen(AVAILABLEFILE, "r")) == NULL) PERROR(AVAILABLEFILE);
while (!feof(f)) {
fgets(buf, BUF_SIZE, f);
CHOMP(buf);
if (MATCHFIELD(buf, PACKAGEFIELD)) {
/*DPRINTF("Package = %s\n", FIELDDATA(buf, PACKAGEFIELD)); */
name = shortdesc = longdesc = dependsdesc = recommendsdesc = suggestsdesc = NULL;
name = STRDUP(FIELDDATA(buf, PACKAGEFIELD));
VERIFY(name != NULL);
/* look for depends/suggests and shotdesc/longdesc */
while (!feof(f)) {
fgets(buf, BUF_SIZE, f);
CHOMP(buf);
if (buf[0] == 0) break;
if (MATCHFIELD(buf, DEPENDSFIELD)) {
dependsdesc = STRDUP(FIELDDATA(buf, DEPENDSFIELD));
VERIFY(dependsdesc != NULL);
} else if (MATCHFIELD(buf, RECOMMENDSFIELD)) {
recommendsdesc = STRDUP(FIELDDATA(buf, RECOMMENDSFIELD));
VERIFY(recommendsdesc != NULL);
} else if (MATCHFIELD(buf, SUGGESTSFIELD)) {
suggestsdesc = STRDUP(FIELDDATA(buf, SUGGESTSFIELD));
VERIFY(suggestsdesc != NULL);
} else if (MATCHFIELD(buf, DESCRIPTIONFIELD)) {
shortdesc = STRDUP(FIELDDATA(buf, DESCRIPTIONFIELD));
VERIFY(shortdesc != NULL);
do {
fgets(buf, BUF_SIZE, f);
if (buf[0] != ' ') break;
if (buf[1] == '.') buf[1] = ' ';
if (longdesc == NULL) {
longdesc = (char *)MALLOC(strlen(buf) + 1);
strcpy(longdesc, buf + 1);
} else {
longdesc = realloc(longdesc, strlen(longdesc) + strlen(buf) + 1);
strcat(longdesc, buf + 1);
}
} while (buf[0] != '\n' && !feof(f));
break;
}
}
addpackage(pkgs, name, NULL, NULL, NULL, shortdesc, NULL);
if (strncmp(name, "task-", 5) == 0)
addpackage(taskpkgs, name, dependsdesc, recommendsdesc, suggestsdesc, shortdesc, longdesc);
if (name != NULL) FREE(name);
if (dependsdesc != NULL) FREE(dependsdesc);
if (recommendsdesc != NULL) FREE(recommendsdesc);
if (suggestsdesc != NULL) FREE(suggestsdesc);
if (shortdesc != NULL) FREE(shortdesc);
if (longdesc != NULL) FREE(longdesc);
}
};
fclose(f);
}
void packages_free(struct packages_t *taskpkgs, struct packages_t *pkgs)
{
/* Frees up memory allocated by packages_readlist */
_packages_root = taskpkgs->packages;
twalk(taskpkgs->packages, packages_walk_delete);
_packages_root = pkgs->packages;
twalk(pkgs->packages, packages_walk_delete);
}
/* $Id: data.h,v 1.1 1999/11/21 22:01:04 tausq Exp $ */
#ifndef _DATA_H
#define _DATA_H
struct package_t {
char *name;
char *shortdesc;
char *longdesc;
int dependscount;
char **depends;
int recommendscount;
char **recommends;
int suggestscount;
char **suggests;
int selected;
};
struct packages_t {
int count;
void *packages;
};
/* Reads in a list of package and package descriptions */
void packages_readlist(struct packages_t *taskpackages, struct packages_t *packages);
/* free memory allocated to store packages */
void packages_free(struct packages_t *taskpackages, struct packages_t *packages);
struct package_t *packages_find(const struct packages_t *packages, const char *name);
struct package_t **packages_enumerate(const struct packages_t *packages);
#endif
/* $Id: help.h,v 1.1 1999/11/21 22:01:04 tausq Exp $ */
#ifndef _HELP_H
#define _HELP_H
#define HELPTXT _("Task packages are \"metapackages\" that 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 package at the cursor. You can also press " \
"A to select all packages, or N to deselect all packages. Pressing Q will exit this " \
"program and begin installation of your selected tasks.\n\n\nThank you for using Debian." \
"\n\nPress enter to return to the task selection screen" \
)
#endif
/* $Id: macros.h,v 1.1 1999/11/21 22:01:04 tausq Exp $ */
#ifndef _MACROS_H
#define _MACROS_H
#include <stdio.h>
#include <errno.h>
#ifdef DEBUG
#define DPRINTF(fmt, arg...) \
do { \
fprintf(stderr, "%s:%d ", __FILE__, __LINE__); \
fprintf(stderr, fmt, ##arg); \
fprintf(stderr, "\r\n"); \
} while (0);
#define ASSERT(cond) \
if (!(cond)) { \
fprintf(stderr, "ASSERTION FAILED at %s:%d! (%s)\n", __FILE__, __LINE__, #cond); \
exit(255); \
}
#define VERIFY(cond) ASSERT(cond)
#define ABORT abort()
#define STRDUP(s) safe_strdup(s)
#define MALLOC(sz) safe_malloc(sz)
#define FREE(p) safe_free((void **)&p);
#else
#define DPRINTF(fmt, arg...)
#define ASSERT(cond)
#define VERIFY(cond)
#define ABORT exit(255)
#define STRDUP(s) (s ? strdup(s) : NULL)
#define MALLOC(sz) malloc(sz)
#define FREE(p) if (p) free(p)
#endif
/* Do you see a perl influence? :-) */
#define DIE(fmt, arg...) \
do { \
fprintf(stderr, "Fatal error encountered at %s:%d\r\n\t", __FILE__, __LINE__); \
fprintf(stderr, fmt, ##arg); \
fprintf(stderr, "\r\n"); \
ABORT; \
} while (0);
#define PERROR(ctx) \
do { \
fprintf(stderr, "I/O error at %s:%d\r\n\t", __FILE__, __LINE__); \
fprintf(stderr, "%s: %s\r\n", ctx, strerror(errno)); \
ABORT; \
} while (0);
#define NEW(S) (S *)MALLOC(sizeof(S))
#define _(s) gettext(s)
#endif
XGETTEXT = xgettext --keyword=_
%.po:
$(XGETTEXT) ../*.c
all: messages.po
touch build_stamp
clean:
-rm -f messages.po build_stamp
CC = gcc
CFLAGS = -g -Wall -I..
DEFS = -DVERSION=$(VERSION) -DPACKAGE=\"$(PROGRAM)\" -DLOCALEDIR=\"/usr/share/locale\" -DDEBUG
LIBS = -lslang
COMPILE = $(CC) $(CFLAGS) $(DEFS) -c
LINK = $(CC) $(CFLAGS) $(DEFS) -o
all: testdata flowtest
../%.o: ../%.c
$(COMPILE) $< -o $@
%.o: %.c
$(COMPILE) $<
testdata: ../data.o ../util.o testdata.o
$(LINK) testdata $^
flowtest: ../strutl.o ../util.o flowtest.o
$(LINK) flowtest $^
clean:
rm -f *.o *~ testdata flowtest
#include <stdio.h>
#include "strutl.h"
#include <stdlib.h>
int main(int argc, char**argv)
{
char *buf;
char *S1 = "99 bottles of beer on the wall, 99 bottles of beer, take one down, pass it around";
char *S2 = "99bottlesofbeeronthewall,99bottlesofbeer,takeonedown,passitaround";
buf = reflowtext(30, S1);
printf("Original text:\n%s\n---\nReflowed text:\n%s\n\n", S1, buf);
free(buf);
buf = reflowtext(30, S2);
printf("Original text:\n%s\n---\nReflowed text:\n%s\n\n", S2, buf);
free(buf);
return 0;
}
#include <stdio.h>
#include "data.h"
#include "util.h"
int main(int argc, char *argv[])
{
struct packages_t taskpkgs, pkgs;
struct package_t **taskpackages, *package;
char *pkgname;
int i, j;
#ifdef DEBUG
atexit(memleak_check);
#endif
packages_readlist(&taskpkgs, &pkgs);
taskpackages = packages_enumerate(&taskpkgs);
for (i = 0; i < taskpkgs.count; i++) {
printf("Task package %d: %s\n", i+1, taskpackages[i]->name);
if (taskpackages[i]->dependscount > 0) {
printf("\tDepends:\n");
for (j = 0; j < taskpackages[i]->dependscount; j++)
pkgname = taskpackages[i]->depends[j];
package = packages_find(&pkgs, pkgname);
printf("\t\t%s: %s\n", pkgname, (package ? package->shortdesc : "(no description available)"));
}
printf("\tDescription: %s\n%s\n\n",
taskpackages[i]->shortdesc, taskpackages[i]->longdesc);
/* ... */
}
packages_free(&taskpkgs, &pkgs);
return 0;
}
/* $Id: slangui.c,v 1.1 1999/11/21 22:01:04 tausq Exp $ */
/* slangui.c - SLang user interface routines */
/* TODO: the redraw code is a bit broken */
#include "slangui.h"
#include <slang.h>
#include <libintl.h>
#include <string.h>
#include <ctype.h>
#include "data.h"
#include "strutl.h"
#include "macros.h"
#include "help.h"
/* Slang object number mapping */
#define DEFAULTOBJ 0
#define CHOOSEROBJ 1
#define DESCOBJ 2
#define STATUSOBJ 3
#define DIALOGOBJ 4
#define POINTEROBJ 5
#define BUTTONOBJ 6
#define ROWS SLtt_Screen_Rows
#define COLUMNS SLtt_Screen_Cols
/* Module private variables */
static int _chooser_coloffset = 5;
static int _chooser_rowoffset = 3;
static int _chooser_height = 0;
static int _chooser_width = 0;
static int _chooser_topindex = 0;
static int _resized = 0;
static struct packages_t *_packages = NULL;
static struct packages_t *_taskpackages = NULL;
static struct package_t **_taskpackagesary = NULL;
static void write_centered_str(int row, int coloffset, int width, char *txt)
{
int col;
int len = strlen(txt);
if (txt == NULL) return;
SLsmg_fill_region(row, coloffset, 1, width, ' ');
if (coloffset + len >= width) col = 0;
else col = (width - len) / 2;
SLsmg_gotorc(row, coloffset + col);
SLsmg_write_nstring(txt, width);
}
void ui_init(int argc, char * const argv[], struct packages_t *taskpkgs, struct packages_t *pkgs)
{
_taskpackages = taskpkgs;
_taskpackagesary = packages_enumerate(taskpkgs);
_packages = pkgs;
SLang_set_abort_signal(NULL);
/* assign attributes to objects */
SLtt_set_color(DEFAULTOBJ, NULL, "white", "black");
SLtt_set_color(CHOOSEROBJ, NULL, "black", "cyan");
SLtt_set_color(POINTEROBJ, NULL, "brightblue", "cyan");
SLtt_set_color(DESCOBJ, NULL, "black", "cyan");
SLtt_set_color(STATUSOBJ, NULL, "yellow", "blue");
SLtt_set_color(DIALOGOBJ, NULL, "black", "white");
SLtt_set_color(BUTTONOBJ, NULL, "white", "red");
ui_resize();
}
int ui_shutdown(void)
{
SLsmg_reset_smg();
SLang_reset_tty();
return 0;
}
void ui_resize(void)
{
/* 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"));
}
_chooser_height = ROWS - 2 * _chooser_rowoffset;
_chooser_width = COLUMNS - 2 * _chooser_coloffset;
ui_drawscreen();
_resized = 1;
}
int ui_eventloop(void)
{
int done = 0;
int ret = 0;
int pos = 0;
int c, i;
_chooser_topindex = 0;
ui_redrawcursor(0);
while (!done) {
c = SLkp_getkey();
switch (c) {
case SL_KEY_UP:
case SL_KEY_LEFT:
if (pos > 0) pos--; else pos = _taskpackages->count - 1;
ui_redrawcursor(pos);
break;
case SL_KEY_DOWN:
case SL_KEY_RIGHT:
if (pos < _taskpackages->count - 1) pos++; else pos = 0;
ui_redrawcursor(pos);
break;
case SL_KEY_PPAGE:
pos -= _chooser_height;
if (pos < 0) pos = 0;
ui_redrawcursor(pos);
break;
case SL_KEY_NPAGE:
pos += _chooser_height;
if (pos >= _taskpackages->count - 1) pos = _taskpackages->count - 1;
ui_redrawcursor(pos);
break;
case SL_KEY_ENTER: case '\r': case '\n':
case ' ': ui_toggleselection(pos); break;
case 'A': case 'a':
for (i = 0; i < _taskpackages->count; i++) _taskpackagesary[i]->selected = 1;
ui_drawscreen();
break;
case 'N': case 'n':
for (i = 0; i < _taskpackages->count; i++) _taskpackagesary[i]->selected = 0;
ui_drawscreen();
break;
case 'H': case 'h': case SL_KEY_F(1): ui_showhelp(); break;
case 'I': case 'i': ui_showpackageinfo(pos); break;
case 'Q': case 'q': done = 1; break;
}
}
return ret;
}
int ui_drawbox(int obj, int r, int c, unsigned int dr, unsigned int dc)
{
SLsmg_set_color(obj);
SLsmg_draw_box(r, c, dr, dc);
return 0;
}
int ui_drawscreen(void)
{
int i;
char buf[160];
SLsmg_cls();
/* Show version and status lines */
SLsmg_set_color(STATUSOBJ);
snprintf(buf, 160, "%s v%s - %s",
_(" Debian Installation Task Selector"), VERSION,
_("(c) 1999 SPI and others "));
write_centered_str(0, 0, COLUMNS, buf);
write_centered_str(ROWS-1, 0, COLUMNS,
_(" h - Help SPACE - Toggle selection i - Task info q - Exit"));
/* Draw the chooser screen */
SLsmg_set_color(DEFAULTOBJ);
write_centered_str(1, 0, COLUMNS,
_("Select the task package(s) appropriate for your system:"));
ui_drawbox(CHOOSEROBJ, _chooser_rowoffset - 1, _chooser_coloffset - 1, _chooser_height + 2, _chooser_width + 2);
for (i = 0; i < _taskpackages->count && i < _chooser_height; i++)
ui_drawchooseritem(i);
ui_redrawcursor(0);
SLsmg_refresh();
_resized = 0;
return 0;
}
void ui_button(int row, int col, char *txt)
{
SLsmg_set_color(BUTTONOBJ);
SLsmg_gotorc(row, col);
SLsmg_write_char(' ');
SLsmg_write_string(txt);
SLsmg_write_char(' ');
}
void ui_dialog(int row, int col, int height, int width, char *title, char *msg)
{
char *reflowbuf;
int ri, c, pos;
char *line, *txt;
reflowbuf = reflowtext(width - 4, msg);
SLsmg_set_color(DIALOGOBJ);
ui_drawbox(DIALOGOBJ, row, col, height, width);
SLsmg_fill_region(row+1, col+1, height-2, width-2, ' ');
if (title) {
pos = col + (width - strlen(title))/2;
SLsmg_gotorc(row, pos);
SLsmg_write_char(' ');
SLsmg_write_string(title);
SLsmg_write_char(' ');
}
if (reflowb