From 88ff0ffa41d70d054f4aba9cacc6af93a36c1706 Mon Sep 17 00:00:00 2001 From: Randolph Chung Date: Sun, 21 Nov 1999 22:01:05 +0000 Subject: [PATCH] Task selection UI Initial CVS --- Makefile | 29 ++++ README | 31 ++++ TODO | 2 + data.c | 248 ++++++++++++++++++++++++++++++++ data.h | 31 ++++ help.h | 14 ++ macros.h | 54 +++++++ po/Makefile | 11 ++ scratch/Makefile | 25 ++++ scratch/flowtest.c | 20 +++ scratch/testdata.c | 35 +++++ slangui.c | 348 +++++++++++++++++++++++++++++++++++++++++++++ slangui.h | 24 ++++ strutl.c | 57 ++++++++ strutl.h | 7 + tasksel.c | 92 ++++++++++++ tasksel.h | 7 + util.c | 57 ++++++++ util.h | 10 ++ 19 files changed, 1102 insertions(+) create mode 100644 Makefile create mode 100644 README create mode 100644 TODO create mode 100644 data.c create mode 100644 data.h create mode 100644 help.h create mode 100644 macros.h create mode 100644 po/Makefile create mode 100644 scratch/Makefile create mode 100644 scratch/flowtest.c create mode 100644 scratch/testdata.c create mode 100644 slangui.c create mode 100644 slangui.h create mode 100644 strutl.c create mode 100644 strutl.h create mode 100644 tasksel.c create mode 100644 tasksel.h create mode 100644 util.c create mode 100644 util.h diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..4c3d4da2 --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +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 + diff --git a/README b/README new file mode 100644 index 00000000..4e7e68eb --- /dev/null +++ b/README @@ -0,0 +1,31 @@ +$Id: README,v 1.1 1999/11/21 22:01:04 tausq Exp $ +Task Selection UI v0.1 +Nov 20, 1999 +Randolph Chung + +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. + diff --git a/TODO b/TODO new file mode 100644 index 00000000..c8ae4437 --- /dev/null +++ b/TODO @@ -0,0 +1,2 @@ +$Id: TODO,v 1.1 1999/11/21 22:01:04 tausq Exp $ +- fix screen resize code diff --git a/data.c b/data.c new file mode 100644 index 00000000..b6f0dcd2 --- /dev/null +++ b/data.c @@ -0,0 +1,248 @@ +/* $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 +#include +#include +#include +#include + +#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); +} + diff --git a/data.h b/data.h new file mode 100644 index 00000000..3911c8dc --- /dev/null +++ b/data.h @@ -0,0 +1,31 @@ +/* $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 diff --git a/help.h b/help.h new file mode 100644 index 00000000..99f7c638 --- /dev/null +++ b/help.h @@ -0,0 +1,14 @@ +/* $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 diff --git a/macros.h b/macros.h new file mode 100644 index 00000000..35b684cf --- /dev/null +++ b/macros.h @@ -0,0 +1,54 @@ +/* $Id: macros.h,v 1.1 1999/11/21 22:01:04 tausq Exp $ */ +#ifndef _MACROS_H +#define _MACROS_H + +#include +#include + +#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 diff --git a/po/Makefile b/po/Makefile new file mode 100644 index 00000000..bb862c6a --- /dev/null +++ b/po/Makefile @@ -0,0 +1,11 @@ +XGETTEXT = xgettext --keyword=_ + +%.po: + $(XGETTEXT) ../*.c + +all: messages.po + touch build_stamp + +clean: + -rm -f messages.po build_stamp + diff --git a/scratch/Makefile b/scratch/Makefile new file mode 100644 index 00000000..be58fee2 --- /dev/null +++ b/scratch/Makefile @@ -0,0 +1,25 @@ +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 + diff --git a/scratch/flowtest.c b/scratch/flowtest.c new file mode 100644 index 00000000..77a69af7 --- /dev/null +++ b/scratch/flowtest.c @@ -0,0 +1,20 @@ +#include +#include "strutl.h" +#include + +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; +} diff --git a/scratch/testdata.c b/scratch/testdata.c new file mode 100644 index 00000000..14e1364c --- /dev/null +++ b/scratch/testdata.c @@ -0,0 +1,35 @@ +#include +#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; +} diff --git a/slangui.c b/slangui.c new file mode 100644 index 00000000..79b55a6a --- /dev/null +++ b/slangui.c @@ -0,0 +1,348 @@ +/* $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 +#include +#include +#include + +#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 (reflowbuf != NULL) { + txt = reflowbuf; + ri = 0; + while ((line = strsep(&txt, "\n")) && (ri < height - 3)) { + SLsmg_gotorc(row + 1 + ri, col + 1); + SLsmg_write_nstring(line, width - 4); + ri++; + } + } + + ui_button(row+height-2, col+(width-4)/2, " OK "); + + SLsmg_refresh(); + do { + c = SLkp_getkey(); + } while (!(c == '\n' || c == '\r' || c == SL_KEY_ENTER || isspace(c)) && (_resized == 0)); +} + +void ui_drawchooseritem(int index) +{ + char buf[1024]; + ASSERT(_taskpackages != NULL); + if (index >= _taskpackages->count) DIE("Index out of bounds: %d >= %d", index, _taskpackages->count); + if (index < _chooser_topindex) return; + + SLsmg_set_color(CHOOSEROBJ); + SLsmg_gotorc(_chooser_rowoffset + index - _chooser_topindex, _chooser_coloffset); + + snprintf(buf, 1024, " (%c) %s: %s", + (_taskpackagesary[index]->selected == 0 ? ' ' : '*'), + _taskpackagesary[index]->name, _taskpackagesary[index]->shortdesc); + SLsmg_write_nstring(buf, _chooser_width); +} + +void ui_toggleselection(int index) +{ + ASSERT(_taskpackages != NULL); + if (index >= _taskpackages->count) DIE("Index out of bounds: %d >= %d", index, _taskpackages->count); + + if (_taskpackagesary[index]->selected == 0) + _taskpackagesary[index]->selected = 1; + else + _taskpackagesary[index]->selected = 0; + + SLsmg_set_color(CHOOSEROBJ); + SLsmg_gotorc(_chooser_rowoffset + index - _chooser_topindex, _chooser_coloffset + 3); + + if (_taskpackagesary[index]->selected == 0) + SLsmg_write_string("( )"); + else + SLsmg_write_string("(*)"); + SLsmg_refresh(); +} + +void ui_showhelp(void) +{ + ui_dialog(3, 3, ROWS-6, COLUMNS-6, "Help", HELPTXT); + ui_drawscreen(); +} + +void ui_redrawchooser(int index) +{ + int i; + for (i = _chooser_topindex; i < _chooser_topindex + _chooser_height; i++) + if (i < _taskpackages->count) ui_drawchooseritem(i); +} + +void ui_redrawcursor(int index) +{ + + /* Check to see if we have to scroll the selection */ + if (index + 1 - _chooser_height > _chooser_topindex) { + _chooser_topindex = index + 1 - _chooser_height; + ui_redrawchooser(index); + } else if (index < _chooser_topindex) { + _chooser_topindex = 0; + ui_redrawchooser(index); + } + + SLsmg_set_color(POINTEROBJ); + SLsmg_fill_region(_chooser_rowoffset, _chooser_coloffset, _chooser_height, 3, ' '); + SLsmg_gotorc(_chooser_rowoffset + index - _chooser_topindex, _chooser_coloffset); + SLsmg_write_string("->"); + SLsmg_refresh(); +} + +void ui_showpackageinfo(int index) +{ + struct package_t *pkg, *deppkg; + int i; + int width; + char buf[4096]; + char shortbuf[256]; + int bufleft; + + ASSERT(_taskpackages != NULL); + if (index >= _taskpackages->count) DIE("Index out of bounds: %d >= %d", index, _taskpackages->count); + + pkg = _taskpackagesary[index]; + + /* pack buf with package info */ + snprintf(buf, sizeof(buf), "Name: %s\nDescription:\n%s\n\nDependent packages:\n", pkg->name, pkg->longdesc); + bufleft = sizeof(buf) - strlen(buf) - 1; + + width = COLUMNS - 5; + ASSERT(width < sizeof(shortbuf)); + for (i = 0; i < pkg->dependscount; i++) { + deppkg = packages_find(_packages, pkg->depends[i]); + snprintf(shortbuf, width, "%s - %s\n", pkg->depends[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, pkg->name, buf); + ui_drawscreen(); +} + diff --git a/slangui.h b/slangui.h new file mode 100644 index 00000000..1c0632ce --- /dev/null +++ b/slangui.h @@ -0,0 +1,24 @@ +/* $Id */ +#ifndef _SLANGUI_H +#define _SLANGUI_H + +struct packages_t; + +void ui_init(int argc, char * const argv[], struct packages_t *taskpkgs, struct packages_t *packages); +int ui_shutdown(void); +void ui_resize(void); +int ui_eventloop(void); + +int ui_drawbox(int obj, int x, int y, unsigned int dx, unsigned int dy); +void ui_button(int row, int col, char *txt); +void ui_dialog(int row, int col, int height, int width, char *title, char *msg); + +int ui_drawscreen(void); +void ui_drawchooseritem(int index); +void ui_toggleselection(int index); +void ui_showhelp(void); +void ui_showpackageinfo(int index); +void ui_redrawchooser(int index); +void ui_redrawcursor(int index); + +#endif diff --git a/strutl.c b/strutl.c new file mode 100644 index 00000000..a2e08b47 --- /dev/null +++ b/strutl.c @@ -0,0 +1,57 @@ +/* $Id: strutl.c,v 1.1 1999/11/21 22:01:04 tausq Exp $ */ +#include "strutl.h" + +#include +#include + +#include "macros.h" + +char *reflowtext(int width, char *txt) +{ + /* A simple greedy text formatting algorithm. Tries to put as many characters as possible + * on a line without going over width + * + * Returns a malloc'ed string buffer that should be freed by the caller + */ + char *buf; + char *begin, *end; + + if (txt == NULL) return NULL; + buf = MALLOC(strlen(txt) + strlen(txt) / width + 2); + buf[0] = 0; + + begin = txt; + while (*begin != 0) { + end = begin; + while (*end != 0 && *end != '\n' && end - begin < width) end++; + + if (end - begin < width) { + /* don't need to wrap -- saw a newline or EOS */ + if (*end == 0) { + strncat(buf, begin, end - begin); + break; + } else { + strncat(buf, begin, end - begin); + strcat(buf, "\n"); + begin = end + 1; + } + } else { + /* wrap the text */ + end--; + while (*end != ' ' && end > begin) end--; + if (end != begin) { + strncat(buf, begin, end - begin); + strcat(buf, "\n"); + begin = end + 1; + } else { + /* this is where it gets gross.. nowhere to break the line */ + end = begin + width - 1; + strncat(buf, begin, end - begin); + strcat(buf, "\n"); + begin = end; + } + } + } + return buf; +} + diff --git a/strutl.h b/strutl.h new file mode 100644 index 00000000..f7e53ce5 --- /dev/null +++ b/strutl.h @@ -0,0 +1,7 @@ +/* $Id: strutl.h,v 1.1 1999/11/21 22:01:04 tausq Rel $ */ +#ifndef _STRUTL_H +#define _STRUTL_H + +char *reflowtext(int width, char *txt); + +#endif diff --git a/tasksel.c b/tasksel.c new file mode 100644 index 00000000..3db4d8a4 --- /dev/null +++ b/tasksel.c @@ -0,0 +1,92 @@ +/* $Id: tasksel.c,v 1.1 1999/11/21 22:01:04 tausq Exp $ */ +#include "tasksel.h" + +#include +#include +#include +#include +#include +#include + +#include "slangui.h" +#include "data.h" +#include "macros.h" + +static void signalhandler(int sig) +{ + switch (sig) { + case SIGWINCH: + ui_resize(); + break; + default: + DPRINTF("%s\n", _("Unknown signal seen")); + } + +} + +void help(void) +{ + fprintf(stderr, "tasksel [-t]\n\t"); + fprintf(stderr, "%s\n\n", _("-t -- test mode; don't actually run apt-get on exit")); + exit(0); +} + +int main(int argc, char * const argv[]) +{ + int i, c, r, testmode = 0; + struct packages_t taskpkgs, packages; + struct package_t **pkglist; + char buf[2048]; + + signal(SIGWINCH, signalhandler); + + setlocale(LC_ALL, ""); + bindtextdomain(PACKAGE, LOCALEDIR); + textdomain(PACKAGE); + + while (1) { + c = getopt(argc, argv, "t"); + if (c == -1) break; + + switch (c) { + case 't': testmode = 1; break; + default: help(); + } + } + + packages_readlist(&taskpkgs, &packages); + ui_init(argc, argv, &taskpkgs, &packages); + ui_drawscreen(); + r = ui_eventloop(); + ui_shutdown(); + + pkglist = packages_enumerate(&taskpkgs); + + if (r == 0) { + sprintf(buf, "apt-get install "); + c = 0; + for (i = 0; i < taskpkgs.count; i++) { + if (pkglist[i]->selected > 0) { + /* TODO check buffer overflow; not likely, but still... */ + strcat(buf, pkglist[i]->name); + strcat(buf, " "); + c++; + } + } + + if (c > 0) { + if (testmode == 1) + printf("%s\n", buf); + else + system(buf); + } else { + fprintf(stderr, "No packages selected\n"); + r = 1; + } + } + + packages_free(&taskpkgs, &packages); + + return r; +} + diff --git a/tasksel.h b/tasksel.h new file mode 100644 index 00000000..ab9ee48c --- /dev/null +++ b/tasksel.h @@ -0,0 +1,7 @@ +/* $Id: tasksel.h,v 1.1 1999/11/21 22:01:04 tausq Exp $ */ +#ifndef _TASKSEL_H +#define _TASKSEL_H + +void help(void); + +#endif diff --git a/util.c b/util.c new file mode 100644 index 00000000..b6a3d3c9 --- /dev/null +++ b/util.c @@ -0,0 +1,57 @@ +/* $Id: util.c,v 1.1 1999/11/21 22:01:04 tausq Exp $ */ +#include "util.h" + +#include +#include + +#include "macros.h" + +#ifdef DEBUG +static int _num_mallocs = 0; + +char *safe_strdup(const char *s) +{ + char *p; + + if (s != NULL) { + p = strdup(s); + _num_mallocs++; + if (p == NULL) DIE("Cannot allocate memory for strdup"); + return p; + } else { + return NULL; + } +} + +void *safe_malloc(int size) +{ + void *p; + + if (size == 0) { + DPRINTF("Attempting to allocate 0 bytes!"); + return NULL; + } + + p = malloc(size); + _num_mallocs++; + if (p == NULL) DIE("Cannot allocate %d bytes of memory", size); + return p; +} + +void safe_free(void **p) +{ + if (p == NULL || *p == NULL) { + DPRINTF("Attempting to dereference NULL pointer"); + } else { + free(*p); + } + if (p) *p = NULL; + _num_mallocs--; +} + +void memleak_check(void) +{ + DPRINTF("Outstanding mallocs : %d\n", _num_mallocs); +} + +#endif diff --git a/util.h b/util.h new file mode 100644 index 00000000..674985ce --- /dev/null +++ b/util.h @@ -0,0 +1,10 @@ +/* $Id: util.h,v 1.1 1999/11/21 22:01:04 tausq Exp $ */ +#ifndef _UTIL_H +#define _UTIL_H + +char *safe_strdup(const char *); +void *safe_malloc(int); +void safe_free(void **); +void memleak_check(void); + +#endif