This is gui.c in view mode; [Download] [Up]
/* $Id: gui.c,v 2.8 1993/06/28 09:31:07 klute Exp klute $ */
/*
* Copyright 1993 Rainer Klute <klute@irb.informatik.uni-dortmund.de>
*
* Permission to use, copy, modify, distribute, and sell this software and
* its documentation for any purpose is hereby granted without fee, provided
* that the above copyright notice appear in all copies and that both that
* copyright notice and this permission notice appear in supporting
* documentation. The author makes no representations about the suitability
* of this software for any purpose. It is provided "as is" without express
* or implied warranty.
*
*/
#include <malloc.h>
#include <string.h>
#include <Xm/Xm.h>
#include <Xm/Text.h>
#include "gui.h"
#include "ui.h"
#include "gui_batch.h"
#include "gui_build.h"
#include "gui_utils.h"
#include "convert.h"
#include "message.h"
#include "parse.h"
#include "strada.h"
#include "utils.h"
#if defined (HANDLE_BROKEN_DATAFILES)
#define IbmToIso IbmAndBrokenToIso
#endif
static char *messageBuffer = (char *) 0;
XtCallbackProc StrassenverzeichnisCallback
(Widget widget, XtPointer clientData, XtPointer callData)
{
ConversionData *cd = (ConversionData *) clientData;
PlzConversion *pc = cd->pc;
GuiComponents *gc = cd->gc;
SetBusyCursor (gc->plz_neu);
LabelSetString (gc->plz_neu, "");
if (pc->orte->entries > 0)
pc->strassen = Strassenverzeichnis (pc->orte->list[0].alort);
else if (pc->archivOrte->entries > 0)
pc->strassen = Strassenverzeichnis (pc->archivOrte->list[0].alort);
StradaSetToList (gc->list, pc->strassen, pc->ort);
XtAddCallback (gc->list, XmNsingleSelectionCallback,
(XtCallbackProc) ListStreetSelected, cd);
UnsetBusyCursor (gc->plz_neu);
}
static void GuiMsgFlush (UiComponents *uc)
{
GuiComponents *gc = (GuiComponents *) uc;
InformationDialog (TopLevelShell (gc->messages), "Hinweis:", messageBuffer);
free (messageBuffer);
messageBuffer = (char *) 0;
}
static void GuiMsgWrite (UiComponents *uc, char *msg)
{
char *help;
if (messageBuffer == (char *) 0)
{
messageBuffer = (char *) malloc (1);
*messageBuffer = '\0';
}
help = messageBuffer;
messageBuffer = (char *) malloc (strlen (messageBuffer) + strlen (msg) + 1);
strcpy (messageBuffer, help);
strcat (messageBuffer, msg);
free (help);
}
static void GuiNoteWrite (UiComponents *uc, char *msg)
{
GuiComponents *gc = (GuiComponents *) uc;
XmTextInsert (gc->messages, XmTextGetLastPosition (gc->messages), msg);
XmTextSetInsertionPosition (gc->messages,
XmTextGetLastPosition (gc->messages));
XmUpdateDisplay (gc->messages);
}
static void GuiNoteClear (UiComponents *uc)
{
GuiComponents *gc = (GuiComponents *) uc;
XtVaSetValues (gc->messages, XmNvalue, "", NULL);
}
void InitDisplay (ConversionData *cd, char **strasse, char **hausnummer, char**postfach, char **plz_alt, char **ort, char **postanstalt, char **ortsteil)
{
GuiComponents *gc = cd->gc;
LabelSetString (gc->plz_neu, "");
XtVaSetValues (gc->list,
XmNitems, (XmString *) 0,
XmNitemCount, 0L,
NULL);
}
XtCallbackProc ConvertCallback (Widget widget, XtPointer clientData,
XtPointer callData)
{
char string[300];
char *help;
ConversionData *conversionData = (ConversionData *) clientData;
PlzConversion *pc = conversionData->pc;
char *strasse, *hausnummer, *postfach, *plz_alt, *ort, *postanstalt,
*ortsteil;
static XtCallbackRec emptyCallbackList[] =
{{(XtCallbackProc) 0, (XtPointer) 0}};
SetBusyCursor (widget);
LabelSetString (conversionData->gc->plz_neu, "");
XtVaSetValues (conversionData->gc->list,
XmNitems, (XmString *) 0,
XmNitemCount, 0L,
NULL);
help = XmTextGetString (conversionData->gc->strasse);
ParseStrasse (help, &strasse, &hausnummer, &postfach);
XtFree (help);
help = XmTextGetString (conversionData->gc->ort);
ParseOrt (help, &plz_alt, &ort, &postanstalt, &ortsteil);
XtFree (help);
ConvertPlz (pc, strasse, hausnummer, postfach, plz_alt, ort, postanstalt,
ortsteil);
free (strasse);
free (hausnummer);
free (postfach);
free (plz_alt);
free (ort);
free (postanstalt);
free (ortsteil);
Message (ConversionStatusString (pc->status));
MessageFlush ();
XtVaSetValues (conversionData->gc->list,
XmNitems, (XmString *) 0,
XmNitemCount, 0L,
XmNsingleSelectionCallback, emptyCallbackList,
NULL);
XtVaSetValues (conversionData->gc->menuStrassenverzeichnis,
XmNsensitive, False, NULL);
if (IsOrtGefunden (pc))
{
if (IsOrtEindeutig (pc))
{
if ((pc->orte->entries > 0 && pc->orte->list[0].nplzo_z[0] == '2')
|| (pc->archivOrte && pc->archivOrte->entries == 1
&& pc->archivOrte->list[0].nplz_z[0] == '2'))
XtVaSetValues (conversionData->gc->menuStrassenverzeichnis,
XmNsensitive, True, NULL);
XmProcessTraversal (conversionData->gc->strasse,
XmTRAVERSE_CURRENT);
}
else
{
/* Mehrere PLZ, möglicherweise auch mehrere Orte */
OrteToList (conversionData->gc->list, pc->orte, pc->archivOrte);
XtAddCallback (conversionData->gc->list,
XmNsingleSelectionCallback,
(XtCallbackProc) ListPlaceSelected, conversionData);
XmProcessTraversal (conversionData->gc->list, XmTRAVERSE_CURRENT);
}
if (pc->status == PlzPlzNeuGefunden)
LabelSetString (conversionData->gc->plz_neu, pc->plz_neu);
strcpy (string, OrtString (pc->plz_alt, pc->ort, pc->postanstalt,
pc->ortsteil));
XmTextSetString (conversionData->gc->ort, string);
}
else
XmProcessTraversal (conversionData->gc->ort, XmTRAVERSE_CURRENT);
if (IsStrasseGefunden (pc))
{
strcpy (string, StrasseString (pc->strasse, pc->hausnummer,
pc->postfach));
XmTextSetString (conversionData->gc->strasse, string);
if (!IsStrasseEindeutig (pc))
{
StradaSetToList (conversionData->gc->list, pc->strassen, pc->ort);
XtAddCallback (conversionData->gc->list,
XmNsingleSelectionCallback,
(XtCallbackProc) ListStreetSelected,
conversionData);
}
}
if (IsPostfachGefunden (pc))
{
if (IsPostfachEindeutig (pc))
{
strcpy (string, "Postfach ");
strcat (string, pc->postfach);
XmTextSetString (conversionData->gc->strasse, string);
}
else
{
PofadaSetToList (conversionData->gc->list, pc->postfaecher,
pc->orte->list->ortname_a);
XtAddCallback (conversionData->gc->list,
XmNsingleSelectionCallback,
(XtCallbackProc) ListPoboxSelected, conversionData);
}
}
UnsetBusyCursor (widget);
}
XtCallbackProc ListActivated (Widget widget, XtPointer clientData,
XtPointer callData)
{
XtCallCallbacks (widget, XmNsingleSelectionCallback, callData);
ConvertCallback (widget, clientData, callData);
}
/*
* "ListPlaceSelected" holt den Orts-String aus dem ersten Segment des
* CompountString und überträgt ihn in das Ortseingabefeld. Falls das zweite
* Segment als erste Zeichen " -> " enthält, ist dies der aktuelle Ortsname,
* und der Inhalt des ersten Segments historisch. Er wird als Ortsteil
* eingesetzt.
*/
XtCallbackProc ListPlaceSelected (Widget widget, XtPointer clientData,
XtPointer callData)
{
XmListCallbackStruct *cd = (XmListCallbackStruct *) callData;
ConversionData *conversionData = (ConversionData *) clientData;
XmStringContext context;
Boolean success;
XmStringCharSet tag;
XmStringDirection direction;
Boolean separator;
char *item;
char *plz, *ort, *zustellamt, *ortsteil;
if (cd->reason != XmCR_SINGLE_SELECT && cd->reason != XmCR_DEFAULT_ACTION)
return;
success = XmStringInitContext (&context, cd->item);
success = XmStringGetNextSegment (context, &item, &tag, &direction,
&separator);
ParseOrt (item, &plz, &ort, &zustellamt, &ortsteil);
success = XmStringGetNextSegment (context, &item, &tag, &direction,
&separator);
if (success && strncmp (item, " -> ", 4) == 0)
{
free (ortsteil);
ortsteil = ort;
ort = (char *) malloc (strlen (item + 4) + 1);
strcpy (ort, item + 4);
}
item = OrtString (plz, ort, zustellamt, ortsteil);
free (plz);
free (ort);
free (zustellamt);
free (ortsteil);
XmTextSetString (conversionData->gc->ort, item);
conversionData->pc->status = PlzOrtEindeutig;
}
XtCallbackProc ListPoboxSelected (Widget widget, XtPointer clientData,
XtPointer callData)
{
XmListCallbackStruct *cd = (XmListCallbackStruct *) callData;
ConversionData *conversionData = (ConversionData *) clientData;
char *item;
XmStringContext context;
XmStringCharSet tag;
XmStringDirection direction;
Boolean separator;
char *plz;
char *ort;
char *zustellamt;
char *ortsteil;
if (cd->reason != XmCR_SINGLE_SELECT && cd->reason != XmCR_DEFAULT_ACTION)
return;
XmStringInitContext (&context, cd->item);
XmStringGetNextSegment (context, &item, &tag, &direction, &separator);
ParseOrt (item, &plz, &ort, &zustellamt, &ortsteil);
item = OrtString (plz, ort, zustellamt, ortsteil);
XmTextSetString (conversionData->gc->ort, item);
conversionData->pc->status = PlzSuchePostfach;
}
/*
* "ListStreetSelected" holt den Straßennamen und gegebenenfalls den
* Orts-String aus dem CompoundString. Dieser ist wie folgt aufgebaut:
* <PLZ>< ><Straßenname>[<, ><Hausnummernbereich>][<SEP><Orts-String>]
*/
XtCallbackProc ListStreetSelected (Widget widget, XtPointer clientData,
XtPointer callData)
{
XmListCallbackStruct *cbs = (XmListCallbackStruct *) callData;
ConversionData *cd = (ConversionData *) clientData;
char *item;
XmStringContext context;
Boolean success;
XmStringCharSet tag;
XmStringDirection direction;
Boolean separator;
XmStringComponentType unknown_tag;
unsigned short unknown_length;
unsigned char *unknown_value;
char *postfach;
char *plz;
char *ort;
char *zustellamt;
char *ortsteil;
if (cbs->reason != XmCR_SINGLE_SELECT && cbs->reason != XmCR_DEFAULT_ACTION)
return;
success = XmStringInitContext (&context, cbs->item);
/* Postleitzahl: */
success = XmStringGetNextSegment (context, &item, &tag, &direction,
&separator);
UpdateString (&cd->pc->plz_neu, item);
LabelSetString (cd->gc->plz_neu, item);
cd->pc->status = PlzPlzNeuGefunden;
success = XmStringGetNextSegment (context, &item, &tag, &direction,
&separator);
/* Straßenname: */
success = XmStringGetNextSegment (context, &item, &tag, &direction,
&separator);
UpdateString (&cd->pc->strasse, item);
item = StrasseString (item, cd->pc->hausnummer, postfach);
XmTextSetString (cd->gc->strasse, item);
/* Hausnummernbereich: */
if (!separator)
{
success = XmStringGetNextSegment (context, &item, &tag, &direction,
&separator);
success = XmStringGetNextSegment (context, &item, &tag, &direction,
&separator);
}
/* Orts-String: */
if (separator)
{
XmStringGetNextComponent (context, &item, &tag, &direction,
&unknown_tag, &unknown_length,
&unknown_value);
success = XmStringGetNextSegment (context, &item, &tag, &direction,
&separator);
ParseOrt (item, &plz, &ort, &zustellamt, &ortsteil);
item = OrtString (plz, ort, zustellamt, ortsteil);
UpdateString (&cd->pc->plz_alt, plz);
UpdateString (&cd->pc->ort, ort);
UpdateString (&cd->pc->postanstalt, zustellamt);
UpdateString (&cd->pc->ortsteil, ortsteil);
free (plz);
free (ort);
free (zustellamt);
free (ortsteil);
XmTextSetString (cd->gc->ort, item);
}
else
{
UpdateString (&cd->pc->postanstalt, "");
UpdateString (&cd->pc->ortsteil, "");
item = OrtString (cd->pc->plz_alt, cd->pc->ort, "", "");
XmTextSetString (cd->gc->ort, item);
}
XmStringFreeContext (context);
}
void PofadaSetToList (Widget list, PofadaSet *pofadaSet, char *ortsname)
{
Pofada *l;
Pofada *newPobox;
int i, j;
XmString *stringList = (XmString *) calloc ((int) pofadaSet->entries,
sizeof (XmString *));
char msg[300];
char *isoMsg;
int entriesLeft = pofadaSet->entries;
char postanstalt[5];
newPobox = pofadaSet->list;
postanstalt[0] = '\0';
for (l = pofadaSet->list, i = j = 0; i < pofadaSet->entries; i++, l++)
{
msg[0] = '\0';
if (strncmp (l->npanst, postanstalt, sizeof (l->npanst)) != 0)
{
strncpy (postanstalt, l->npanst, sizeof (l->npanst));
strncat (msg, l->plz_w_o, sizeof (l->plz_w_o));
strncat (msg, l->plzalt, sizeof (l->plzalt));
strcat (msg, " ");
strncat (msg, ortsname, sizeof (((Umsda *) 0)->ortname_a));
StripBlanks (msg);
strcat (msg, " ");
strncat (msg, l->npanst, sizeof (l->npanst));
}
else
{
entriesLeft--;
continue;
}
#if defined (HANDLE_BROKEN_DATAFILES)
isoMsg = IbmAndBrokenToIso (msg);
#else
isoMsg = IbmToIso (msg);
#endif
stringList[j++] = XmStringCreateLtoR (isoMsg, XmFONTLIST_DEFAULT_TAG);
free (isoMsg);
}
XtVaSetValues (list, XmNitems, stringList, XmNitemCount,
(long) entriesLeft, NULL);
for (i = 0; i < entriesLeft; i++)
XmStringFree (stringList[i]);
free (stringList);
}
static void AppendHn (char *s, char *tag, int length)
{
char *c = tag;
while (*c == ' ' && c - tag < length)
c++;
if (c - tag < length)
{
strncat (s, c, length - (c - tag));
StripBlanks (s);
}
}
void StradaSetToList (Widget list, StradaSet *stradaSet, char *ortsname)
{
Strada *l;
Strada *newStreet;
int i, j;
XmString s1;
XmString *stringList = (XmString *) calloc ((int) stradaSet->entries,
sizeof (XmString *));
char msg[300];
char *isoMsg;
char ortsteil[sizeof (((Strada *) 0)->ortsteil)];
char sname[sizeof (((Strada *) 0)->sname)];
char strlfdnr[sizeof (((Strada *) 0)->strlfdnr)];
int entriesLeft = stradaSet->entries;
newStreet = stradaSet->list;
ortsteil[0] = sname[0] = strlfdnr[0] = '\0';
for (l = stradaSet->list, i = j = 0; i < stradaSet->entries; i++, l++)
{
/* neue Postleitzahl: */
msg[0] = '\0';
strncat (msg, l->plz_z, sizeof (l->plz_z));
s1 = XmStringCreate (msg, XmFONTLIST_DEFAULT_TAG);
s1 = StringCat (s1, XmStringCreate (" ", XmFONTLIST_DEFAULT_TAG));
/* Straßenname: */
msg[0] = '\0';
strncat (msg, l->sname, sizeof (l->sname));
StripBlanks (msg);
isoMsg = IbmToIso (msg);
s1 = StringCat (s1, XmStringCreate (isoMsg, XmFONTLIST_DEFAULT_TAG));
free (isoMsg);
/* Hausnummernbereich (falls vorhanden): */
if (strntoi (l->nplz_z, sizeof (l->nplz_z)) > 1)
{
s1 = StringCat (s1, XmStringCreate (", ", XmFONTLIST_DEFAULT_TAG));
msg[0] = '\0';
AppendHn (msg, l->hnr1von, sizeof (l->hnr1von));
AppendHn (msg, l->hnr2von, sizeof (l->hnr2von));
strcat (msg, "-");
AppendHn (msg, l->hnr1bis, sizeof (l->hnr1bis));
AppendHn (msg, l->hnr2bis, sizeof (l->hnr2bis));
isoMsg = IbmToIso (msg);
s1 = StringCat (s1, XmStringCreate (isoMsg,
XmFONTLIST_DEFAULT_TAG));
free (isoMsg);
}
else
{
if (strncmp (l->sname, sname, sizeof (l->sname)) != 0
|| strncmp (l->strlfdnr, strlfdnr, sizeof (l->strlfdnr)) != 0)
{
strncpy (sname, l->sname, sizeof (l->sname));
strncpy (strlfdnr, l->strlfdnr, sizeof (l->strlfdnr));
}
else
{
entriesLeft--;
XmStringFree (s1);
continue;
}
}
/* Orts-String (bei mehreren gleichnamigen Straßen): */
if (strntoi (l->anzgleist, sizeof (l->anzgleist)) > 1)
{
if (strncmp (l->ortsteil, ortsteil, sizeof (l->ortsteil)) != 0)
{
s1 = StringCat (s1, XmStringSeparatorCreate ());
strncpy (ortsteil, l->ortsteil, sizeof (l->ortsteil));
strcpy (msg, " ");
strncat (msg, l->plz_w_o, sizeof (l->plz_w_o));
StripBlanks (msg);
strncat (msg, l->plzalt, sizeof (l->plzalt));
strcat (msg, " ");
strncat (msg, ortsname, sizeof (((Umsda *) 0)->ortname_a));
StripBlanks (msg);
strcat (msg, " ");
strncat (msg, l->npanst, sizeof (l->npanst));
StripBlanks (msg);
strcat (msg, " (");
strncat (msg, l->ortsteil, sizeof (l->ortsteil));
StripBlanks (msg);
strcat (msg, ")");
StripBlanks (msg);
isoMsg = IbmToIso (msg);
s1 = StringCat (s1, XmStringCreate (isoMsg,
XmFONTLIST_DEFAULT_TAG));
free (isoMsg);
}
else
{
entriesLeft--;
XmStringFree (s1);
continue;
}
}
stringList[j++] = s1;
}
XtVaSetValues (list, XmNitems, stringList, XmNitemCount,
(long) entriesLeft, NULL);
for (i = 0; i < entriesLeft; i++)
XmStringFree (stringList[i]);
free (stringList);
}
void OrteToList (Widget list, UmsdaSet *orte, OrtardaSet *archivOrte)
{
Umsda *lu;
Ortarda *lo;
int i;
XmString *stringList = (XmString *) calloc ((int) orte->entries
+ (int) archivOrte->entries, sizeof (XmString *));
XmString s1;
char msg[300];
char *isoMsg;
char *c;
for (lu = orte->list, i = 0; i < orte->entries; i++)
{
/* PLZ alt, Ortsname, Postanstalt: */
msg[0] = '\0';
strncat (msg, lu->plz_w_o, sizeof (lu->plz_w_o));
strncat (msg, lu->plzalt, sizeof (lu->plzalt));
strcat (msg, " ");
strncat (msg, lu->ortname_a, sizeof (lu->ortname_a));
StripBlanks (msg);
strcat (msg, " ");
strncat (msg, lu->npanst_a, sizeof (lu->npanst_a));
StripBlanks (msg);
isoMsg = IbmToIso (msg);
s1 = XmStringCreate (isoMsg, XmFONTLIST_DEFAULT_TAG);
free (isoMsg);
/* Ortszusatz: */
*msg = '\0';
c = lu->ortzusa;
if (*c != ',' && *c != '-')
strcat (msg, " ");
while (*c == ' ' && c - lu->ortzusa < sizeof (lu->ortzusa))
c++;
if (c - lu->ortzusa < sizeof (lu->ortzusa))
strncat (msg, c, sizeof (lu->ortzusa) - (c - lu->ortzusa));
StripBlanks (msg);
isoMsg = IbmToIso (msg);
stringList[i] = StringCat (s1, XmStringCreate (isoMsg, "italic"));
free (isoMsg);
lu++;
}
for (lo = archivOrte->list; i < orte->entries + archivOrte->entries; i++)
{
/* PLZ alt, Ortsname, Postanstalt: */
msg[0] = '\0';
strncat (msg, lo->plz_w_o, sizeof (lo->plz_w_o));
strncat (msg, lo->plzalt, sizeof (lo->plzalt));
strcat (msg, " ");
strncat (msg, lo->ortname_a, sizeof (lo->ortname_a));
StripBlanks (msg);
strcat (msg, " ");
strncat (msg, lo->npanst_a, sizeof (lo->npanst_a));
StripBlanks (msg);
isoMsg = IbmToIso (msg);
s1 = XmStringCreate (isoMsg, XmFONTLIST_DEFAULT_TAG);
free (isoMsg);
/* aktueller Ortsname: */
strcpy (msg, " -> ");
strncat (msg, lo->ortname_n, sizeof (lo->ortname_n));
StripBlanks (msg);
isoMsg = IbmToIso (msg);
stringList[i] = StringCat (s1, XmStringCreate (isoMsg, "italic"));
free (isoMsg);
lo++;
}
XtVaSetValues (list, XmNitems, stringList, XmNitemCount,
(long) (orte->entries + archivOrte->entries), NULL);
for (i = 0; i < orte->entries + archivOrte->entries; i++)
XmStringFree (stringList[i]);
free (stringList);
}
XtCallbackProc QuitCallback (Widget widget, XtPointer clientData,
XtPointer callData)
{
XtDestroyApplicationContext (XtWidgetToApplicationContext (widget));
exit (0);
}
static Boolean RecordRead (char *record)
{
Note (".");
return True;
}
static Boolean PofadaRecordRead (char *record)
{
return RecordRead (record);
}
static Boolean StradaRecordRead (char *record)
{
return RecordRead (record);
}
static Boolean UmsdaRecordRead (char *record)
{
return RecordRead (record);
}
static Boolean OrtardaRecordRead (char *record)
{
return RecordRead (record);
}
void MotifInterface (Display *display)
{
static ConversionData cd;
static PlzConversion pc;
static GuiComponents gc;
Widget applicationShell;
cd.pc = &pc;
cd.gc = &gc;
cd.batch = (Batch *) 0;
gc.strasse = gc.ort = gc.plz_neu = (Widget) 0;
cd.pc->plz_alt = (char *) 0;
cd.pc->ort = (char *) 0;
cd.pc->postanstalt = (char *) 0;
cd.pc->ortsteil = (char *) 0;
cd.pc->strasse = (char *) 0;
cd.pc->hausnummer = (char *) 0;
cd.pc->postfach = (char *) 0;
cd.pc->plz_neu = (char *) 0;
cd.pc->verkehrsgebiet = 'W';
cd.pc->orte = (UmsdaSet *) 0;
cd.pc->strassen = (StradaSet *) 0;
cd.pc->postfaecher = (PofadaSet *) 0;
/* kleiner Hack für Motif 1.2.1: */
XDeleteProperty (display,
RootWindowOfScreen (DefaultScreenOfDisplay (display)),
XInternAtom (display, "_MOTIF_DRAG_WINDOW", False));
applicationShell = CreateMainWindow (display, &cd);
userInterfaceMethods.uiComponents = (UiComponents *) &gc;
userInterfaceMethods.NoteClear = GuiNoteClear;
userInterfaceMethods.NoteWrite = GuiNoteWrite;
userInterfaceMethods.MsgWrite = GuiMsgWrite;
userInterfaceMethods.MsgFlush = GuiMsgFlush;
userInterfaceMethods.PofadaRecordRead = PofadaRecordRead;
userInterfaceMethods.StradaRecordRead = StradaRecordRead;
userInterfaceMethods.UmsdaRecordRead = UmsdaRecordRead;
userInterfaceMethods.OrtardaRecordRead = OrtardaRecordRead;
XmProcessTraversal (cd.gc->ort, XmTRAVERSE_CURRENT);
XtAppMainLoop (XtWidgetToApplicationContext (applicationShell));
}
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.