/**
*** XPG - Graphical User Interface for Postgres
*** Copyright (C) 1993  Ranen Goren (ranen@cs.huji.ac.il).

*** This program is free software; you can redistribute it and/or modify
*** it under the terms of the GNU General Public License as published by
*** the Free Software Foundation; either version 2 of the License, or
*** (at your option) any later version.

*** This program is distributed in the hope that it will be useful,
*** but WITHOUT ANY WARRANTY; without even the implied warranty of
*** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*** GNU General Public License for more details.

*** You should have received a copy of the GNU General Public License
*** along with this program; if not, write to the Free Software
*** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
**/


#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <varargs.h>
#include <X11/StringDefs.h>
#include <X11/Intrinsic.h> 
#include <X11/cursorfont.h>
#include <Xm/Xm.h>
#include <Xm/MainW.h>
#include <Xm/Form.h>
#include <Xm/RowColumn.h>
#include <Xm/List.h>
#include <Xm/PushB.h>
#include <Xm/PushBG.h>
#include <Xm/LabelG.h>
#include <Xm/Label.h>
#include <Xm/Text.h>
#include <Xm/SelectioB.h>
#include "Bitmaps/xpgIcon.xbm"
#include "Bitmaps/nobusy.xbm"
#include "Bitmaps/retr.xbm"
#include "Bitmaps/send.xbm"
#include "Bitmaps/print.xbm"
#include "Bitmaps/script.xbm"
#include "Bitmaps/play.xbm"
#include "Bitmaps/search.xbm"
#include "xpg.h"

#define String JUST_A_DUMB_STRING
#include "tmp/libpq.h"       /* postgres */
#undef String

#ifdef MEM_DEBUG
#include "/CS/system/ranen/Src/Lib/Malloc/malloc.h"
#endif

extern void _XEditResCheckMessages();     /* for editres */


char *xpgPQexec();
char *execAndWarn();
Widget createMenuBar();
void fileMenuHandler();
void classMenuHandler();
void dataMenuHandler();
void printMenuHandler();
void helpMenuHandler();
void closeView();
void status();
int  updateClassList();
void popupClassSelection();
int  queryClassList();
int  getFieldInfo();
int  displayClass();
void CBclassOk();
void CBclassNew();
void CBclassNoMatch();
void CBrecScrollH();
void CBrecScrollV();
void windowResized();
int  initClass();
int  retrieveClass();
void fieldSelected();
void fieldDisarm();
void keyDeselect();
void listItemDoubleClicked();
void makeKeyList();
extern void browseArrayField();
Pixmap createIcon();
void setBusy();
void busyIconPressed();
void playIconPressed();
void searchIconPressed();
void floatFormat();
void equalizeLists();
void datePopup();
char *namePortal();
char *createUniqueName();
void deletePortal();
void openView();
void keyAppearance();
void editresRegisterShell();
void getClassResources();
void treatClassResources();
char *removeSpacesFromName();
void popupExecDialog();
void popupLogDialog();
void helpView();
Boolean xpgQuit();



APP_RESOURCE_STRUCT appResources;


static XtResource AppResources[] = {
{ "helpSwitch",    "HelpSwitch",     XtRBoolean,  sizeof(Boolean),
      XtOffset(APP_RESOURCE_STRUCT_PTR, helpSwitch),  XtRString, "False"     },
{ "database",      "Database",       XtRString,   sizeof(String),
      XtOffset(APP_RESOURCE_STRUCT_PTR, database),    XtRString, "XPG_NO_DB" },
{ "class",         "Class",          XtRString,   sizeof(String),
      XtOffset(APP_RESOURCE_STRUCT_PTR, class),       XtRString, ""          },
{ "showPgClasses", "ShowPgClasses",  XtRBoolean,  sizeof(Boolean),
      XtOffset(APP_RESOURCE_STRUCT_PTR, showPg),      XtRString, "False"     },
};


static XtResource resources[] = {
{ "font",          "Font",           XmRFontList, sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, font),           XtRString, "fixed"     },
{ "key",           "Key",            XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, key),            XtRString, ""          },
{ "separator",     "Separator",      XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, separator),      XtRString, "  "        },
{ "printCmd",      "PrintCmd",       XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, printCmd),       XtRString, "cat"       },
{ "autoSort",      "AutoSort",       XtRBoolean,  sizeof(Boolean),
      XtOffset(RESOURCE_STRUCT_PTR, autoSort),       XtRString, "True"      },
{ "floatPrecision","FloatPrecision",XtRInt,       sizeof(int),
      XtOffset(RESOURCE_STRUCT_PTR, floatPrecision), XtRString, "-1"        },
{ "arrayCmd1",     "ArrayCmd",       XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, arrayCmd1),      XtRString, ""          },
{ "arrayCmd2",     "ArrayCmd",       XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, arrayCmd2),      XtRString, ""          },
{ "arrayCmd3",     "ArrayCmd",       XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, arrayCmd3),      XtRString, ""          },
{ "arrayCmdPath1", "ArrayCmdPath",   XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, arrayCmdPath1),  XtRString, "."         },
{ "arrayCmdPath2", "ArrayCmdPath",   XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, arrayCmdPath2),  XtRString, "."         },
{ "arrayCmdPath3", "ArrayCmdPath",   XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, arrayCmdPath3),  XtRString, "."         },
{ "arrayCmdName1", "ArrayCmdName",   XmRXmString, sizeof(XmString),
      XtOffset(RESOURCE_STRUCT_PTR, arrayCmdName1),  XtRString, "Command 1" },
{ "arrayCmdName2", "ArrayCmdName",   XmRXmString, sizeof(XmString),
      XtOffset(RESOURCE_STRUCT_PTR, arrayCmdName2),  XtRString, "Command 2" },
{ "arrayCmdName3", "ArrayCmdName",   XmRXmString, sizeof(XmString),
      XtOffset(RESOURCE_STRUCT_PTR, arrayCmdName3),  XtRString, "Command 3" },
{ "printSum",      "PrintTotals",    XtRBoolean,  sizeof(Boolean),
      XtOffset(RESOURCE_STRUCT_PTR, printTotals)+SUM,     XtRString, "False"},
{ "printProduct",  "PrintTotals",    XtRBoolean,  sizeof(Boolean),
      XtOffset(RESOURCE_STRUCT_PTR, printTotals)+PRODUCT, XtRString, "False"},
{ "printCount",    "PrintTotals",    XtRBoolean,  sizeof(Boolean),
      XtOffset(RESOURCE_STRUCT_PTR, printTotals)+COUNT,   XtRString, "False"},
{ "printAverage",  "PrintTotals",    XtRBoolean,  sizeof(Boolean),
      XtOffset(RESOURCE_STRUCT_PTR, printTotals)+AVERAGE, XtRString, "False"},
{ "printStdDev",   "PrintTotals",    XtRBoolean,  sizeof(Boolean),
      XtOffset(RESOURCE_STRUCT_PTR, printTotals)+STDDEV,  XtRString, "False"},
{ "printMin",      "PrintTotals",    XtRBoolean,  sizeof(Boolean),
      XtOffset(RESOURCE_STRUCT_PTR, printTotals)+MMIN,    XtRString, "False"},
{ "printMax",      "PrintTotals",    XtRBoolean,  sizeof(Boolean),
      XtOffset(RESOURCE_STRUCT_PTR, printTotals)+MMAX,    XtRString, "False"},
{ "yearRangeFrom", "YearRange",      XtRString,    sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, yearRangeFrom),  XtRString, "-2"        },
{ "yearRangeTo",   "YearRange",      XtRString,    sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, yearRangeTo),    XtRString, "+2"        },
{ "helpFile",      "HelpFile",       XtRString,   sizeof(String),
      XtOffset(RESOURCE_STRUCT_PTR, helpFile),       XtRString, NULL        },
};



static String fallbackRes[] = {
    "Xpg*fontList: courbo14",
    "!Xpg*background: tan",
    "Xpg*background: grey75",
    "Xpg*foreground: black",
    "Xpg*borderColor: gold",
    "Xpg*borderWidth: 0",
    "Xpg*shadowColor: gold",
    "!Xpg*shadowThickness: 2",

    "Xpg*topSh.title: xpg",
    "Xpg*dragInitiatorProtocolStyle: XmDRAG_NONE",
    "Xpg*dragReceiverProtocolStyle: XmDRAG_NONE",
    "Xpg*topSh.geometry: 500x250",
    "Xpg*appendSh.geometry:  340x400",
    "Xpg*replaceSh.geometry: 340x400",
    "Xpg*menuBar*shadowThickness: 2",
    "Xpg*menuBar*foreground: black",
    "!Xpg*menuBar*background: peru",
    "Xpg*menuBar*background: LightSteelBlue3",
    "Xpg*classList*applyLabelString: New window",
    "Xpg*statLine*foreground: black",
    "!Xpg*statLine*background: wheat",
    "Xpg*fieldNames*highlightThickness: 0",
    "Xpg*fieldNames*marginWidth: 4",
    "Xpg*fieldNames*marginHeight: 2",
    "Xpg*fieldNames*borderWidth: 0",
    "Xpg*fieldNames*shadowThickness: 0",
    "Xpg*fieldNames*foreground: wheat",
    "!Xpg*fieldNames*background: firebrick",
    "Xpg*fieldNames*background: DimGrey",
    "Xpg*fieldNames*fontList: fixed",
    "Xpg*fieldNames*sensitive: True",
    "Xpg*fieldNames*selectThreshold: 10000",
    "Xpg*fieldNames*autoShowCursorPosition: False",
    "Xpg*records*fontList: fixed",
    "Xpg*records*increment: 10",
    "Xpg*classList*listLabelString: Classes",
    "Xpg*warnDialog*background: orange",
    "Xpg*warnDialog*foreground: red4",
    "Xpg*keyAttr*foreground: steelBlue4",
    "!Xpg*keyAttr*background: wheat",
    "Xpg*keyAttr*fontList: fixed",
    "!Xpg*keyAttr*sensitive: False",
    "Xpg*keyAttrSW*spacing: 0",
    "Xpg*leftBox*borderWidth: 0",
    "Xpg*keyAttr*shadowColor: green",
    "Xpg*keyTitle*foreground: wheat",
    "Xpg*keyTitle*background: steelBlue4",
    "Xpg*keyTitle*fontList: fixed",
    "Xpg*keyTitle*shadowThickness: 0",
    "Xpg*keyTitle*highlightThickness: 0",
    "Xpg*keyTitle*marginHeight: 2",
    "Xpg*keyTitle*marginWidth: 4",
    "Xpg*highlightOnEnter: False",
    "Xpg*classHelpExitButton.labelString: Close",
    "Xpg*classHelpText.rows: 15",
    "Xpg*classHelpText.columns: 40",
    "!Xpg*aboutXpg.background: LightSteelBlue3",
    "Xpg*helpDialog*helpIndexLabel.labelString: Help Index",
    "Xpg*helpDialog*helpIndexLabel.marginHeight: 5",
    "Xpg*helpDialog*helpIndexLabel.marginWidth:  10",
    "Xpg*helpDialog*helpIcon.marginHeight: 10",
    "Xpg*helpDialog*helpIcon.marginWidth:  20",
    "Xpg*helpDialog*helpText.rows: 15",
    "Xpg*helpDialog*helpText.columns: 80",
    "Xpg*searchText*columns: 25",
    "Xpg*searchSelButton.labelString: Select",
    "Xpg*searchDeselButton.labelString: Deselect",
    "Xpg*searchSelAllButton.labelString: Select all",
    "Xpg*searchDeselAllButton.labelString: Deselect all",
    "Xpg*searchFindNextButton.labelString: Find next selected",
    "Xpg*searchCancelButton.labelString: Cancel",
    "Xpg*copLabel*shadowThickness: 0",
    "Xpg*copLabel.borderWidth: 0",
    "!Xpg*copSeparator.shadowThickness: 4",
    "Xpg*copSeparator.height: 20",
    "Xpg*copOk.labelString: Apply",
    "Xpg*copCancel.labelString: Close",
    "Xpg*copCancel.marginHeight: 8",
    "Xpg*copSelLabel.marginHeight: 5",
    "Xpg*copSelLabel.alignment: XmALIGNMENT_BEGINNING",
    "Xpg*copSelLabel.labelString: Apply commands:",
    "Xpg*copIgnore.labelString: Ignore\\nselected",
    "Xpg*copSel.labelString: Apply to\\nselected",
    "Xpg*copSelDup.labelString: Apply to\\nduplicates",
    "Xpg*scriptList*listLabelString: Scripts",
    "Xpg*busyIcon.foreground:   green4",
    "Xpg*playIcon.foreground:   royalBlue",
    "Xpg*searchIcon.foreground: steelBlue4",
    "Xpg*mathTotalLabel.labelString: Total: ",
    "Xpg*mathUpdate.labelString: Update",
    "Xpg*mathSum.labelString: Sum",
    "Xpg*mathProd.labelString: Product",
    "Xpg*mathCount.labelString: Count",
    "Xpg*mathAvg.labelString: Average",
    "Xpg*mathSd.labelString: Std. Deviation",
    "Xpg*mathMin.labelString: Minimum",
    "Xpg*mathMax.labelString: Maximum",
    "Xpg*mathAllAttr.labelString: All fields",
    "Xpg*mathKeyAttr.labelString: Key fields",
    "Xpg*mathAllTuples.labelString: All tuples",
    "Xpg*mathSelectedTuples.labelString: Selected tuples",
    "Xpg*totalActionsLabel.labelString: Print Totals",
    "Xpg*totalActionsLabel.height: 40",
    "Xpg*selectColor: royalBlue",
    "Xpg*dateTodayBut.labelString: Today",
    "Xpg*dateNowBut.labelString: Now",
    "Xpg*dateSep.height: 10",
    "Xpg*dateOkBut.labelString: Ok",
    "Xpg*dateCancelBut.labelString: Cancel",
    "!Xpg*copTextDate.background: cyan3",
    "Xpg*copTextDate.background: LightSteelBlue3",
    "Xpg*copTextDate.Translations: #override\\n<Btn3Down>: datePopup()",
    "Xpg*execDialog*execCommand.promptString: Enter POSTGRES command:",
    "Xpg*execDialog*execCommand.textColumns: 80",
    "Xpg*execDialog*execCommand.historyVisibleItemCount: 5",
    "Xpg*execHistoryLabel.labelString: Command History",
    "Xpg*logDialog*logText.columns: 80",
    "Xpg*logDialog*logText.rows: 10",
    "Xpg*logTextLabel.labelString: Message Log",
};



static XrmOptionDescRec options[] = {
    { "-help",            "helpSwitch",       XrmoptionNoArg,  "True"},
    { "-db",              "database",         XrmoptionSepArg, NULL},
    { "-class",           "class",            XrmoptionSepArg, NULL},
    { "-showPgClasses",   "showPgClasses",    XrmoptionNoArg,  "True"},
    { "-hidePgClasses",   "showPgClasses",    XrmoptionNoArg,  "False"},
    { "-key",             "*key",             XrmoptionSepArg, NULL},
    { "-printCmd",        "*printCmd",        XrmoptionSepArg, NULL},
    { "-autoSort",        "*autoSort",        XrmoptionNoArg,  "True"},
    { "-noAutoSort",      "*autoSort",        XrmoptionNoArg,  "False"},
    { "-floatPrecision",  "*floatPrecision",  XrmoptionNoArg,  NULL},
};


typedef struct listAtomStruct
{
    char *name;
    struct listAtomStruct *next;
} listAtom;


extern char *PQhost, *PQport;

XtAppContext app;
Widget toplevel;
Widget classTop;
Widget classListDialog;
int    numViews = 0;
Pixmap icon, nobusyIconPixmap=NULL, sendIconPixmap=NULL, retrIconPixmap=NULL;
Pixmap playIconPixmap=NULL, printIconPixmap=NULL;
Pixmap scriptIconPixmap=NULL, searchIconPixmap=NULL, xpgIcon=NULL;
listAtom *list = NULL;


/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
main(argc, argv)
  int argc;
  char *argv[];
{
    char *initialClass=NULL;
    XtActionsRec  actions;

    toplevel = XtAppInitialize(&app, "Xpg", options, XtNumber(options), 
			       &argc, argv, fallbackRes, NULL, 0);
    XtGetApplicationResources(toplevel, &appResources, AppResources,
			      XtNumber(AppResources), NULL, 0);
    if (appResources.helpSwitch)
    {
	commandLineHelp();
	exit(0);
    }
    classTop = XtVaCreatePopupShell("class", topLevelShellWidgetClass, 
                                    toplevel, NULL);
    if (!strcmp(appResources.database, "XPG_NO_DB"))
	appResources.database = strdup(cuserid(NULL));
    PQsetdb(appResources.database);
#ifdef XPG_DEBUG
    printf("Running Motif %d.%d\n", XmVERSION, XmREVISION);
    PQtrace();
#endif
    initPermissions();

    namePortal("blank");    /* reserves the blank portal to xpg's work */

       /* register the translations */
    actions.string =  "datePopup";
    actions.proc   =  datePopup;
    XtAppAddActions(app, &actions, 1);

    if (strcmp(appResources.class, ""))
	initialClass = removeSpacesFromName(appResources.class);

    classListDialog = XmCreateSelectionDialog(toplevel, "classList", NULL, 0);

    openView(initialClass, initialClass);
    XtAppMainLoop(app);
    return 0;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void openView(className, portalName)
  char   *className;
  char   *portalName;
{ 
    relInfo *rel;
    Widget statLine, mainWin, mainBox;
    Widget records, fieldNames, key, keyTitle, leftBox, rightBox;
    Widget recHorizontalSB, recVerticalSB, fieldNamesSB, FNSB;
    Widget iconBox, busyIcon, playIcon, searchIcon;
    Arg wargs[20];
    int n;
    XtActionsRec  actions;
    char          translations[1000];
    char          versionBuf[50];
    XmTextScanType selectionArray[5];

    if ((rel = (relInfo *)malloc(sizeof(relInfo))) == NULL)
	XtError("Mem error!");
    
    if (className)
	rel->class = strdup(className);
    else
	rel->class = NULL;
    if (portalName)
	rel->portal = strdup(namePortal(portalName));
    else
	rel->portal = NULL;

    rel->keyAttr  = NULL;
    rel->attrInfo = NULL;
    rel->searchSh = NULL;
    rel->arrayName = NULL;
    rel->arraySh = NULL;
    for (n=0; n<NUM_MATH_ACTIONS; n++)
	rel->printOptions.totalActions[n] = False;
    rel->top = XtVaCreatePopupShell("topSh", topLevelShellWidgetClass, 
				    toplevel, NULL);
    numViews++;
    XtAddCallback(rel->top, XmNdestroyCallback, closeView, rel);
    /* set up the application icon */
    if (!xpgIcon)
	xpgIcon = XCreateBitmapFromData(XtDisplay(toplevel),
					RootWindowOfScreen(XtScreen(toplevel)),
					xpgIcon_bits, xpgIcon_width, 
					xpgIcon_height);
    XtVaSetValues(rel->top, XmNiconPixmap, xpgIcon, NULL);
    editresRegisterShell(rel->top);

    rel->mainW=mainWin = XtVaCreateManagedWidget("mainWindow",
				      xmMainWindowWidgetClass, rel->top,
				      NULL);
    XtAddCallback(mainWin, XmNhelpCallback, helpView, "the viewer");

    rel->mainBox=mainBox = XtVaCreateWidget("mainBox", xmFormWidgetClass, 
				      mainWin, 
				      NULL);

    iconBox = XtVaCreateManagedWidget("iconBox", xmRowColumnWidgetClass,
				      mainBox,
				      XmNorientation,  XmHORIZONTAL,
				      XmNbottomAttachment,  XmATTACH_FORM,
				      XmNrightAttachment,   XmATTACH_FORM,
				      NULL);
    searchIcon = XtVaCreateManagedWidget("searchIcon", 
					 xmPushButtonWidgetClass, iconBox, 
					 XmNlabelType, XmPIXMAP,
					 NULL);
    XtAddCallback(searchIcon, XmNactivateCallback, searchIconPressed, rel);
    XtAddCallback(searchIcon, XmNhelpCallback, helpView, "the Search dialog");
    if (!searchIconPixmap)
	searchIconPixmap = createIcon(searchIcon, search_bits, search_width, 
				      search_height);
    XtVaSetValues(searchIcon, XmNlabelPixmap, searchIconPixmap, NULL);
    playIcon = XtVaCreateManagedWidget("playIcon", 
				       xmPushButtonWidgetClass, iconBox, 
				       XmNlabelType, XmPIXMAP,
				       NULL);
    XtAddCallback(playIcon, XmNactivateCallback, playIconPressed, rel);
    XtAddCallback(playIcon, XmNhelpCallback, helpView, "scripts");
    if (!playIconPixmap)
	playIconPixmap = createIcon(playIcon, play_bits, play_width, 
				    play_height);
    XtVaSetValues(playIcon, XmNlabelPixmap, playIconPixmap, NULL);
    rel->busyIcon = busyIcon = XtVaCreateManagedWidget("busyIcon", 
					xmPushButtonWidgetClass, iconBox,
				        XmNlabelType, XmPIXMAP,
				        NULL);
    XtAddCallback(busyIcon, XmNactivateCallback, busyIconPressed, rel);
    if (!retrIconPixmap)
	retrIconPixmap = createIcon(busyIcon, retr_bits, retr_width, 
				    retr_height);
    if (!sendIconPixmap)
	sendIconPixmap = createIcon(busyIcon, send_bits, send_width, 
				    send_height);
    if (!scriptIconPixmap)
	scriptIconPixmap = createIcon(busyIcon, script_bits, script_width, 
				      script_height);
    if (!printIconPixmap)
	printIconPixmap = createIcon(busyIcon, print_bits, print_width, 
				     print_height);
    if (!nobusyIconPixmap)
	nobusyIconPixmap = createIcon(busyIcon, nobusy_bits, nobusy_width,
				      nobusy_height);
    setBusy(rel, NOBUSY);

    statLine = XtVaCreateManagedWidget("statLine", xmLabelWidgetClass, mainBox,
                                       XmNalignment, XmALIGNMENT_BEGINNING,
				       XmNbottomAttachment,  XmATTACH_FORM,
				       XmNleftAttachment,    XmATTACH_FORM,
				       XmNrightAttachment,   XmATTACH_WIDGET,
				       XmNrightWidget,       iconBox,
				       XmNtopAttachment,     
				       XmATTACH_OPPOSITE_WIDGET,
				       XmNtopWidget,         iconBox,
                                       NULL);
    rel->statLine = statLine;
    
    rel->leftBox=leftBox = XtVaCreateWidget("leftBox", 
				      xmFormWidgetClass, mainBox, 
				      XmNbottomAttachment,  XmATTACH_WIDGET,
				      XmNbottomWidget,      statLine,
				      XmNtopAttachment,     XmATTACH_FORM,
				      XmNleftAttachment,    XmATTACH_FORM,
				      NULL);
    XtAddCallback(leftBox, XmNhelpCallback, helpView, "key attributes");
    
    selectionArray[0] = XmSELECT_WORD;
    rel->keyTitle=keyTitle = XtVaCreateManagedWidget("keyTitle",
				       xmTextWidgetClass, leftBox,
				       XmNrows,              1,
				       XmNeditable,          False,
				       XmNeditMode,       XmSINGLE_LINE_EDIT,
				       XmNcursorPositionVisible,  False,
				       XmNtopAttachment,     XmATTACH_FORM,
				       XmNrightAttachment,   XmATTACH_FORM,
				       XmNleftAttachment,    XmATTACH_FORM,
				       NULL);
    XtVaSetValues(keyTitle, XmNwidth, 0, NULL);
/*
    XtVaSetValues(keyTitle, XmNselectionArrayCount, 0, NULL);
    XtVaSetValues(keyTitle, XmNselectionArray, selectionArray, NULL);
*/
    XtAddCallback(keyTitle, XmNgainPrimaryCallback, keyDeselect, rel);
       /* register the translations */
    actions.string =  "fieldDisarm";
    actions.proc   =  fieldDisarm;
    XtAppAddActions(app, &actions, 1);
    sprintf(translations, "<Btn1Up>: fieldDisarm()");
    XtOverrideTranslations(keyTitle, XtParseTranslationTable(translations));
    XtManageChild(leftBox);
    
    n=0;
    XtSetArg(wargs[n], XmNselectionPolicy,          XmEXTENDED_SELECT); n++;
    XtSetArg(wargs[n], XmNlistSizePolicy,           XmVARIABLE); n++;
    XtSetArg(wargs[n], XmNscrollingPolicy,          XmAUTOMATIC);     n++;
    XtSetArg(wargs[n], XmNleftAttachment,           XmATTACH_FORM);   n++;
    XtSetArg(wargs[n], XmNbottomAttachment,         XmATTACH_FORM);   n++;
    XtSetArg(wargs[n], XmNtopAttachment,            XmATTACH_WIDGET); n++;
    XtSetArg(wargs[n], XmNtopWidget,                       keyTitle); n++; 
    XtSetArg(wargs[n], XmNrightAttachment,          XmATTACH_FORM);   n++;
    XtSetArg(wargs[n], XmNverticalScrollBar,        NULL);            n++;
    rel->key=key = XmCreateList(leftBox, "keyAttr", wargs, n);
    XtVaSetValues(key,
		  XmNrightAttachment,   XmATTACH_FORM,	
		  XmNleftAttachment,    XmATTACH_FORM,
		  XmNbottomAttachment,  XmATTACH_FORM,
		  XmNtopAttachment,     XmATTACH_WIDGET,
		  XmNtopWidget,         keyTitle,
		  NULL);
    XtManageChild(key);
    XtAddCallback(key, XmNextendedSelectionCallback, equalizeLists, rel);

    rel->rightBox=rightBox = XtVaCreateManagedWidget("rightBox", 
				       xmFormWidgetClass, mainBox, 
				       XmNbottomAttachment,  XmATTACH_WIDGET,
				       XmNbottomWidget,      statLine,
				       XmNtopAttachment,     XmATTACH_FORM,
				       XmNrightAttachment,   XmATTACH_FORM,
				       XmNleftAttachment,    XmATTACH_WIDGET,
				       XmNleftWidget,        leftBox,
                                       NULL);

    /*******************************************************************/
    /*     FIELD NAMES                                                 */
    /*******************************************************************/
    selectionArray[0] = XmSELECT_WORD;
    n=0;
    XtSetArg(wargs[n], XmNrows,                   1);              n++;
    XtSetArg(wargs[n], XmNeditable,               False);          n++; 
    XtSetArg(wargs[n], XmNeditMode,          XmSINGLE_LINE_EDIT);  n++;
    XtSetArg(wargs[n], XmNcursorPositionVisible,  False);          n++;
    XtSetArg(wargs[n], XmNscrollBarDisplayPolicy, XmAS_NEEDED);    n++;
    XtSetArg(wargs[n], XmNscrollingPolicy,        XmAUTOMATIC);    n++;
    XtSetArg(wargs[n], XmNtopAttachment,          XmATTACH_FORM);  n++; 
    XtSetArg(wargs[n], XmNleftAttachment,         XmATTACH_FORM);  n++;
    XtSetArg(wargs[n], XmNrightAttachment,        XmATTACH_FORM);  n++; 
    fieldNames = XmCreateScrolledText(rightBox, "fieldNames", wargs, n);
    rel->fieldNames = fieldNames;
/*
    XtVaSetValues(fieldNames, XmNselectionArrayCount,    1, NULL);
    XtVaSetValues(fieldNames, XmNselectionArray,    selectionArray, NULL);
*/
    XtManageChild(fieldNames);

    XtAddCallback(fieldNames, XmNgainPrimaryCallback, fieldSelected, rel);
       /* register the translations */
    actions.string =  "fieldDisarm";
    actions.proc   =  fieldDisarm;
    XtAppAddActions(app, &actions, 1);
    sprintf(translations, "<Btn1Up>: fieldDisarm()");
    XtOverrideTranslations(fieldNames, XtParseTranslationTable(translations));

    /*******************************************************************/
    /*     RECORDS                                                     */
    /*******************************************************************/
    n=0;
    XtSetArg(wargs[n], XmNselectionPolicy,          XmEXTENDED_SELECT); n++;
    XtSetArg(wargs[n], XmNlistSizePolicy,           XmRESIZE_IF_POSSIBLE); n++;
    XtSetArg(wargs[n], XmNscrollingPolicy,          XmAUTOMATIC);     n++;
    XtSetArg(wargs[n], XmNleftAttachment,           XmATTACH_FORM);   n++;
    XtSetArg(wargs[n], XmNrightAttachment,          XmATTACH_FORM);   n++;
    XtSetArg(wargs[n], XmNbottomAttachment,         XmATTACH_FORM);   n++;
    XtSetArg(wargs[n], XmNtopAttachment,            XmATTACH_WIDGET); n++;
    XtSetArg(wargs[n], XmNtopWidget,           XtParent(fieldNames)); n++;
    rel->records=records = XmCreateScrolledList(rightBox, "records", wargs, n);
    XtManageChild(records);
    XtAddCallback(records, XmNdefaultActionCallback, 
		  listItemDoubleClicked, rel);
    XtAddCallback(records, XmNextendedSelectionCallback, equalizeLists, rel);
    XtAddCallback(records, XmNextendedSelectionCallback, browseArrayField,rel);
    XtVaSetValues(records, XmNuserData, rel, NULL);
    XtVaGetValues(XtParent(records),
		  XmNhorizontalScrollBar, &recHorizontalSB,
		  XmNverticalScrollBar,   &recVerticalSB,
		  NULL);
    XtVaGetValues(XtParent(fieldNames),
		  XmNhorizontalScrollBar, &fieldNamesSB,
		  NULL);
    FNSB = fieldNamesSB;
    insertCallback(recHorizontalSB,XmNvalueChangedCallback,CBrecScrollH,FNSB);
    insertCallback(recHorizontalSB, XmNdragCallback, CBrecScrollH, FNSB);
    insertCallback(recHorizontalSB, XmNincrementCallback, CBrecScrollH, FNSB);
    insertCallback(recHorizontalSB, XmNdecrementCallback, CBrecScrollH, FNSB);
    insertCallback(recHorizontalSB, XmNpageIncrementCallback,
		  CBrecScrollH, FNSB);
    insertCallback(recHorizontalSB, XmNpageDecrementCallback, 
		  CBrecScrollH, FNSB);
    insertCallback(recHorizontalSB, XmNtoBottomCallback, CBrecScrollH, FNSB);
    insertCallback(recHorizontalSB, XmNtoTopCallback, CBrecScrollH, FNSB);

    XtAddCallback(recVerticalSB,XmNvalueChangedCallback,CBrecScrollV,key);
    XtAddCallback(recVerticalSB, XmNdragCallback, CBrecScrollV, key);
    XtAddCallback(recVerticalSB, XmNincrementCallback, CBrecScrollV, key);
    XtAddCallback(recVerticalSB, XmNdecrementCallback, CBrecScrollV, key);
    XtAddCallback(recVerticalSB, XmNpageIncrementCallback,
		  CBrecScrollV, key);
    XtAddCallback(recVerticalSB, XmNpageDecrementCallback, 
		  CBrecScrollV, key);
    XtAddCallback(recVerticalSB, XmNtoBottomCallback, CBrecScrollV, key);
    XtAddCallback(recVerticalSB, XmNtoTopCallback, CBrecScrollV, key);

    XmMainWindowSetAreas(mainWin, createMenuBar(rel), NULL, NULL,
			 NULL, mainBox);
    XtManageChild(mainBox);
    XtRealizeWidget(rel->top);
    XtVaGetValues(XtParent(fieldNames),
		  XmNhorizontalScrollBar, &fieldNamesSB,
		  NULL);
    XtUnrealizeWidget(fieldNamesSB);
    XtAddEventHandler(records, StructureNotifyMask, False,
		      windowResized, rel);
    /* set default title and icon names. Also set the icon pixmap */
    sprintf(versionBuf, "xpg %s", VERSION);
    XtVaSetValues(rel->top, XmNtitle, versionBuf, NULL);
    XSetIconName(XtDisplay(rel->top), XtWindow(rel->top), versionBuf);

    if (className)
    {
	if (retrieveClass(rel, False, False, False) && numViews>1)
	    return;
    }
    else if (portalName)
    {	
	/* display portal with no class */
	if ((initClass(rel) || displayClass(rel, False, False))&& numViews>1)
	{
	    if (rel->portal)
		deletePortal(rel);
	    return;
	}
    }
    keyAppearance(rel);
    status(rel, "Current database: %s", appResources.database);
    XtPopup(rel->top, XtGrabNone);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
Widget createMenuBar(rel)
  relInfo *rel;
{
    int menuPos = 0;
    Widget bar, fileMenu, classMenu, dataMenu, printMenu, helpMenu;
    XmString file, class, data, print, help;
    XmString quit, choose, closeCl, script, exec, log;
    XmString append, replace, del;
    XmString search, math, sort;
    XmString prClass, prSelClass, prKey, prSelKey, prOptions;
    XmString clHelp, contextHelp, helpIndex, about;

    XmString quitX, chooseX, closeClX, scriptX, execX, logX;
    XmString appendX, replaceX, delX;
    XmString searchX, mathX, sortX;
    XmString prClassX, prSelClassX, prKeyX, prSelKeyX, prOptionsX;
    XmString clHelpX, contextHelpX, helpIndexX, aboutX;

    Widget w;

    
    file   = XmStringCreateSimple("File");
    class  = XmStringCreateSimple("Class");
    data   = XmStringCreateSimple("Data");
    print  = XmStringCreateSimple("Print");
    help   = XmStringCreateSimple("Help");
    bar = XmVaCreateSimpleMenuBar(rel->mainW, "menuBar", 
				     XmVaCASCADEBUTTON, file,   'F',
				     XmVaCASCADEBUTTON, class,  'C',
				     XmVaCASCADEBUTTON, data,   'D',
				     XmVaCASCADEBUTTON, print,  'P',
				     XmVaCASCADEBUTTON, help,   'H',
				     NULL);
    if ((w = XtNameToWidget(bar, "button_4")) != NULL)
	XtVaSetValues(bar, XmNmenuHelpWidget, w, NULL);

    choose  = XmStringCreateSimple("View class");
    closeCl = XmStringCreateSimple("Close viewer");
    script  = XmStringCreateSimple("Script play");
    exec    = XmStringCreateSimple("Execute commands");
    log     = XmStringCreateSimple("message Log");
    quit    = XmStringCreateSimple("Quit xpg");
    chooseX   = XmStringCreateSimple("Ctrl+V");
    closeClX  = XmStringCreateSimple("Ctrl+C");
    scriptX   = XmStringCreateSimple("Ctrl+I");
    execX     = XmStringCreateSimple("Ctrl+E");
    logX      = XmStringCreateSimple("Ctrl+L");
    quitX     = XmStringCreateSimple("Ctrl+X");
    fileMenu = XmVaCreateSimplePulldownMenu(bar, "fileMenu", menuPos++, 
                 fileMenuHandler,
                 XmVaPUSHBUTTON, choose,  'V', "Ctrl<Key>v", chooseX,
                 XmVaPUSHBUTTON, closeCl, 'C', "Ctrl<Key>c", closeClX,
                 XmVaPUSHBUTTON, script,  'S', "Ctrl<Key>i", scriptX,
                 XmVaPUSHBUTTON, exec,    'E', "Ctrl<Key>e", execX,
                 XmVaPUSHBUTTON, log,     'L', "Ctrl<Key>l", logX,
                 XmVaSEPARATOR,
                 XmVaPUSHBUTTON, quit,    'Q', "Ctrl<Key>x", quitX, 
                 NULL);

    append  = XmStringCreateSimple("Append");
    replace = XmStringCreateSimple("Replace");
    del     = XmStringCreateSimple("Delete");
    appendX    = XmStringCreateSimple("Ctrl+A");
    replaceX   = XmStringCreateSimple("Ctrl+R");
    delX       = XmStringCreateSimple("Ctrl+D");
    classMenu = XmVaCreateSimplePulldownMenu(bar, "classMenu", menuPos++,
                 classMenuHandler,
                 XmVaPUSHBUTTON,  append,  'A', "Ctrl<Key>a", appendX,
                 XmVaPUSHBUTTON,  replace, 'R', "Ctrl<Key>r", replaceX,
                 XmVaPUSHBUTTON,  del,     'D', "Ctrl<Key>d", delX,
                 NULL);
    if (!rel->class && rel->portal)
    {
	XtSetSensitive(XtNameToWidget(classMenu,"button_0"), False);
	XtSetSensitive(XtNameToWidget(classMenu,"button_1"), False);
	XtSetSensitive(XtNameToWidget(classMenu,"button_2"), False);
    }    

    search  = XmStringCreateSimple("Search");
    math    = XmStringCreateSimple("Math");
    sort    = XmStringCreateSimple("sorT");
    searchX   = XmStringCreateSimple("Ctrl+S");
    mathX     = XmStringCreateSimple("Ctrl+M");
    sortX     = XmStringCreateSimple("Ctrl+T");
    dataMenu = XmVaCreateSimplePulldownMenu(bar, "dataMenu", menuPos++,
                 dataMenuHandler,
                 XmVaPUSHBUTTON,  search,  'S', "Ctrl<Key>s", searchX,
                 XmVaPUSHBUTTON,  math  ,  'M', "Ctrl<Key>m", mathX,
                 XmVaPUSHBUTTON,  sort  ,  'T', "Ctrl<Key>t", sortX,
                 NULL);
    if (!rel->class && rel->portal)
	XtSetSensitive(XtNameToWidget(dataMenu,"button_2"), False);

    prClass    = XmStringCreateSimple("All tuples");
    prSelClass = XmStringCreateSimple("Selected tuples");
    prKey      = XmStringCreateSimple("Key tuples");
    prSelKey   = XmStringCreateSimple("selected keY tuples");
    prOptions  = XmStringCreateSimple("Options");

    prClassX    = XmStringCreateSimple("Ctrl+P");
    prSelClassX = XmStringCreateSimple("Ctrl+Shift+P");
    prKeyX      = XmStringCreateSimple("Ctrl+K");
    prSelKeyX   = XmStringCreateSimple("Ctrl+Shift+K");
    prOptionsX  = XmStringCreateSimple("Ctrl+O");
    printMenu = XmVaCreateSimplePulldownMenu(bar, "printMenu", menuPos++, 
             printMenuHandler,
             XmVaPUSHBUTTON, prClass,    'A', "Ctrl<Key>p", prClassX,
             XmVaPUSHBUTTON, prSelClass, 'S', "Ctrl Shift<Key>p", prSelClassX,
             XmVaPUSHBUTTON, prKey,      'K', "Ctrl<Key>k", prKeyX,
             XmVaPUSHBUTTON, prSelKey,   'Y', "Ctrl Shift<Key>k", prSelKeyX,
             XmVaSEPARATOR,
             XmVaPUSHBUTTON, prOptions,  'O', "Ctrl<Key>o", prOptionsX,
             NULL);

    clHelp      = XmStringCreateSimple("Help on class");
    contextHelp = XmStringCreateSimple("On Context");
    helpIndex   = XmStringCreateSimple("Index");
    about       = XmStringCreateSimple("On Version");
    clHelpX      =XmStringCreateSimple("Ctrl+H");
    helpMenu = XmVaCreateSimplePulldownMenu(bar, "helpMenu", menuPos++, 
                 helpMenuHandler,
                 XmVaPUSHBUTTON,  clHelp,      'H', "Ctrl<Key>h", clHelpX,
		 XmVaPUSHBUTTON,  contextHelp, 'C', NULL, NULL,
                 XmVaPUSHBUTTON,  helpIndex,   'I', NULL, NULL,
                 XmVaSEPARATOR,
                 XmVaPUSHBUTTON,  about,       'V', NULL, NULL,
                 NULL);
    
    XtVaSetValues(bar, XmNuserData, rel, NULL);
    XtManageChild(bar);

    /* now free all the XmString's we used */
    XmStringFree(file);
    XmStringFree(class);
    XmStringFree(data);
    XmStringFree(print);    
    XmStringFree(help);

    XmStringFree(choose);
    XmStringFree(closeCl);
    XmStringFree(script);
    XmStringFree(exec);
    XmStringFree(log);
    XmStringFree(quit);
    XmStringFree(chooseX);
    XmStringFree(closeClX);
    XmStringFree(scriptX);
    XmStringFree(execX);
    XmStringFree(logX);
    XmStringFree(quitX);

    XmStringFree(append);
    XmStringFree(replace);
    XmStringFree(del);
    XmStringFree(appendX);
    XmStringFree(replaceX);
    XmStringFree(delX);

    XmStringFree(search);
    XmStringFree(math);
    XmStringFree(sort);
    XmStringFree(searchX);
    XmStringFree(mathX);
    XmStringFree(sortX);

    XmStringFree(prClass);
    XmStringFree(prSelClass);
    XmStringFree(prKey);
    XmStringFree(prSelKey);
    XmStringFree(prOptions);
    XmStringFree(prClassX);
    XmStringFree(prSelClassX);
    XmStringFree(prKeyX);
    XmStringFree(prSelKeyX);
    XmStringFree(prOptionsX);

    XmStringFree(clHelp);
    XmStringFree(helpIndex);
    XmStringFree(about);
    XmStringFree(clHelpX);

    return bar;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void fileMenuHandler(w, item, cbs)
  Widget w;
  int item;
  XtPointer cbs;
{
    relInfo *rel;
    
    XtVaGetValues(XtParent(XtParent(XtParent(w))), XmNuserData, &rel, NULL);
    switch(item)
    {
      case 0:
	popupClassSelection(rel);
	break;
      case 1:
	XtDestroyWidget(rel->top);
	break;
      case 2:
	scriptPlay(rel);
	break;
      case 3:
	popupExecDialog(rel);
	break;
      case 4:
	popupLogDialog(rel);
	break;
      case 5:
	if (xpgQuit(rel->top))
	{
	    PQfinish();
	    exit(0);
	}
	break;
      default:
	warn(rel->top, "Sorry, this option is not implemented yet!");
    }
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void classMenuHandler(w, item, cbs)
  Widget w;
  int item;
  XtPointer cbs;
{
    relInfo *rel;
    
    XtVaGetValues(XtParent(XtParent(XtParent(w))), XmNuserData, &rel, NULL);
    if (rel->portal == NULL)
    {
	warn(rel->top, "No class in viewer!");
	return;
    }
    switch(item)
    {
      case 0:
	classAppend(rel);
	break;
      case 1:
	classReplace(rel);
	break;
      case 2:
	classDelete(rel);
	break;
      default:
	warn(rel->top, "Sorry, this option is not implemented yet!");
    }
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void dataMenuHandler(w, item, cbs)
  Widget w;
  int item;
  XtPointer cbs;
{
    relInfo *rel;
    
    XtVaGetValues(XtParent(XtParent(XtParent(w))), XmNuserData, &rel, NULL);
    if (rel->portal == NULL)
    {
	warn(rel->top, "No class in viewer!");
	return;
    }
    switch(item)
    {
      case 0:
	searchPopup(rel);
	break;
      case 1:
	mathOps(rel);
	break;
      case 2:
	retrieveClass(rel, False, False, True);
	break;
      default:
	warn(rel->top, "Sorry, this option is not implemented yet!");
    }
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void helpMenuHandler(w, item, cbs)
  Widget w;
  int item;
  XtPointer cbs;
{
    relInfo *rel;
    
    XtVaGetValues(XtParent(XtParent(XtParent(w))), XmNuserData, &rel, NULL);
    switch(item)
    {
      case 0:
	classHelp(rel);
	break;
      case 1:
	ContextHelp(rel->top);
	break;
      case 2:
	helpView(rel->top, NULL);
	break;
      case 3:
	aboutXpg(rel);
	break;
      default:
	warn(rel->top, "Sorry, this option is not implemented yet!");
    }
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void closeView(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XtPointer cbs;
{
    if (--numViews == 0)   /* we're the last class displayed, so go home */
    {
	PQfinish();
	exit(0);
    }
    if (rel->portal)
	deletePortal(rel);
    XtPopdown(rel->top);
    XtUnmanageChild(rel->top);
    XtDestroyWidget(rel->top);
    XtFree((char *)rel);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void status(rel, va_alist)
  relInfo *rel;
  va_dcl
{
    char *format;
    va_list args;
    char buf[BUFSIZ];

    va_start(args);
    format = va_arg(args, char *);
    vsprintf(buf, format, args);
    xs_wprintf(rel->statLine, buf);
    XFlush(XtDisplay(toplevel));
    XmUpdateDisplay(toplevel);
    va_end(args);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/* returns 0 iff succeeded */
int updateClassList(rel)
  relInfo *rel;
{
    PortalBuffer *p;
    int i,n,m;
    XmString *compStrList;
    Widget list;
    int numItems;
    
    status(rel, "retrieving class list...");
    setBusy(rel, RETR);
    if (queryClassList())
    {
	setBusy(rel, NOBUSY);
	return 1;
    }
    XtVaGetValues(classListDialog, XmNlistItemCount, &numItems, NULL);
    list = XmSelectionBoxGetChild(classListDialog, XmDIALOG_LIST);
    if (numItems > 0)
	XmListDeleteAllItems(list);
    if ((p = PQparray("blank")) == NULL)
    {
	warn(rel->top, "Cannot read portal!\nOperation stopped");
	setBusy(rel, NOBUSY);
	return 1;
    }
    n = PQntuples(p);
    if (!n)
    {
	warn(rel->top, "Database contains no classes!");
	setBusy(rel, NOBUSY);
	return 1;
    }
    m = PQnfields(p,0);
    compStrList = (XmString *)XtMalloc(n * sizeof(XmString));
    for(i = 0; i < n; i++) 
	compStrList[i] = XmStringCreateSimple(PQgetvalue(p, i, 0));
    XtVaSetValues(classListDialog,
		  XmNdialogStyle, XmDIALOG_FULL_APPLICATION_MODAL,
		  XmNlistItems, compStrList,
		  XmNlistItemCount, n,
		  XmNmustMatch, False, /* True forces selection from dialog */
		  NULL);
    while(--n >= 0)
	XmStringFree(compStrList[n]);
    XtFree((char *)compStrList);
    status(rel, "retrieving class list... done");
    setBusy(rel, NOBUSY);
    return 0;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void popupClassSelection(rel)
  relInfo *rel;
{
    Widget helpButton, applyButton;

    if (updateClassList(rel))     /* class-list retrieval failed */
	return;
    helpButton = XmSelectionBoxGetChild(classListDialog, 
					XmDIALOG_HELP_BUTTON);
    XtUnmanageChild(helpButton);
    applyButton = XmSelectionBoxGetChild(classListDialog, 
					 XmDIALOG_APPLY_BUTTON);
    XtRemoveAllCallbacks(classListDialog, XmNokCallback);
    XtRemoveAllCallbacks(classListDialog, XmNapplyCallback);
    XtRemoveAllCallbacks(classListDialog, XmNnoMatchCallback);
    XtAddCallback(classListDialog, XmNokCallback, CBclassOk, rel);
    XtAddCallback(classListDialog, XmNapplyCallback, CBclassNew, rel);
    XtAddCallback(classListDialog, XmNnoMatchCallback, CBclassNoMatch, rel);
    XtManageChild(classListDialog);
}

    




/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void CBclassOk(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XmSelectionBoxCallbackStruct *cbs;
{
    if (rel->portal)
	deletePortal(rel);
    XmStringGetLtoR(cbs->value, XmSTRING_DEFAULT_CHARSET, &rel->class);
    if (removeSpacesFromName(rel->class) == NULL)
	return;
    rel->portal = strdup(namePortal(rel->class));
    retrieveClass(rel, False, False, False);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void CBclassNew(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XmSelectionBoxCallbackStruct *cbs;
{
    char *class;

    XtUnmanageChild(classListDialog);
    XmStringGetLtoR(cbs->value, XmSTRING_DEFAULT_CHARSET, &class);
    setBusy(rel, RETR);
    status(rel, "Creating a new viewer...");
    openView(class, class);
    XtFree(class);
    status(rel, "Creating a new viewer... done");
    setBusy(rel, NOBUSY);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
int initClass(rel)
  relInfo *rel;
{
    char *objType;
    char *objName;
    char buf[BUFSIZ];
    
    if ((objName = rel->class) != NULL)
	objType = "class";
    else if ((objName = rel->portal) != NULL)
	objType = "portal";
    else
	return 1;
    checkPermissions(rel, objName, &rel->perms);
    if (! rel->perms.read)
    {
	warn(rel->top, "Read access to class denied!");
	status(rel, "Retrieving %s... failed!", objType);
	if (rel->portal)
	    free(rel->portal);
	rel->portal = NULL;
	return 1;
    }
    getClassResources(appResources.database, objName, &rel->res);
    treatClassResources(rel);
    XtVaSetValues(rel->key,        XmNfontList, rel->res.font, NULL);
    XtVaSetValues(rel->keyTitle,   XmNfontList, rel->res.font, NULL);
    XtVaSetValues(rel->records,    XmNfontList, rel->res.font, NULL);
    XtVaSetValues(rel->fieldNames, XmNfontList, rel->res.font, NULL);
    status(rel, "%s: %s", objType, objName);
    strcpy(buf, objName);
    if (!rel->class)
	strcat(buf, "  (portal)");
    XtVaSetValues(rel->top, XmNtitle, buf, NULL);
    XSetIconName(XtDisplay(rel->top), XtWindow(rel->top), objName);
    return 0;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/* returns 0 iff class retrieval succeeded */
int retrieveClass(rel, keepPos, keepSelected, tmpSort)
  relInfo *rel;
  Boolean keepPos;      /* whether to keep the list top at the current pos */
  Boolean keepSelected; /* whether to keep the selected tuples selected */
  Boolean tmpSort;      /* special sort request (for the Sort key) */
{
    char *className = rel->class;
    char buf[BUFSIZ];
    char sortBuf[BUFSIZ];
    Boolean error = False;
    attributeInfo *attrInfo;
    int i, m, pos;
    Boolean sort;
    char *pgResult;
    
    if (rel->class == NULL)
	return 1;
    if (initClass(rel))
	return 1;
    setBusy(rel, RETR);
    sort = rel->res.autoSort || tmpSort;
    if (sort)
    {
	sort = False;     /* until a sort key is really found */
	status(rel, "Retrieving class info...");
	if (rel->attrInfo)        /* update the field info */
	    free((char *)rel->attrInfo);
	if (!(m = getFieldInfo(rel, &rel->attrInfo, FIND_LENGTH,
			       CONSIDER_FIELD_NAMES)))
	{
	    setBusy(rel, NOBUSY);
	    return 1;
	}
	attrInfo = rel->attrInfo;
	strcpy(sortBuf, " sort by ");
	for (i=0; i<m; i++)
	{
	    pos = attrInfo[i].keyList;
	    if (pos == -1)
		break;       /* end of key list */
	    if (sort)
		strcat(sortBuf, ", ");    /* prepend comma before new attr */
	    strcat(sortBuf, attrInfo[pos].name);
	    sort = True;     /* found a sort key, so include in retr. comm. */
	}
    }
    status(rel, "Retrieving class...");
    execAndWarn(rel->top, "begin", "BEGIN cmd failed!");
    sprintf(buf, "retrieve portal %s (%s.oid, %s.all)", rel->portal, 
	    className, className);
    if (sort)
	strcat(buf, sortBuf);
    if (rel->attrInfo)
    {
	free((char *)rel->attrInfo);
	rel->attrInfo = NULL;
    }
#ifdef XPG_DEBUG
    printf("RETR command: %s\n", buf);
#endif
    pgResult = execAndWarn(rel->top, buf, "Failed retrieve command");
    if (pgResult[0] == 'R')
	error = True;
    sprintf(buf, "fetch all in %s", rel->portal);
    pgResult = execAndWarn(rel->top, buf, NULL);
    if (pgResult[0] == 'R')
	error = True;
    execAndWarn(rel->top, "end", "END cmd failed!");
    status(rel, "Retrieving class... %s", (error ? "failed!" : "done"));
    if (!error)
	displayClass(rel, keepPos, keepSelected);
    else
    {
	warn(rel->top, "Error retrieving class '%s'!", className);
	if (rel->portal)
	    free(rel->portal);
	rel->portal = NULL;
    }
    setBusy(rel, NOBUSY);
    return (error==True);
}
    




/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void CBclassNoMatch(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XmSelectionBoxCallbackStruct *cbs;
{
    warn(rel->top, "Class does not exist!");
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/* returns 0 iff succeeded */
int queryClassList()
{
    char *result;
    char buf[BUFSIZ];
    
    sprintf(buf, "retrieve (pg_class.relname)");
    strcat (buf, " where pg_class.relkind = \"r\"::char");
    if (! appResources.showPg)
	strcat(buf, " and pg_class.relname!~\"pg_\"");
    strcat(buf, " sort by relname");
    result = xpgPQexec(buf);
    if (!strcmp(result, "R"))
    {
	warn(toplevel, "Error while retrieving class list!");
	return 1;
    }
    return 0;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
int displayClass(rel, keepPos, keepSelected)
  relInfo *rel;
  Boolean keepPos;      /* whether to keep the list top at the current pos */
  Boolean keepSelected; /* whether to keep the selected tuples selected */
{
    PortalBuffer *p;
    int i,j,n,m, pos;
    attributeInfo *attrInfo;
    char buf[BUFSIZ];
    char fieldBuf[BUFSIZ];
    char keyBuf[BUFSIZ];
    XmString compStr;
    int numItems;
    int topItem;
    char *val;
    int posCount, *posList;
    unsigned char selectionPolicy;
   
    status(rel, "Displaying: %s", (rel->class ? rel->class : rel->portal));
    if ((p = PQparray(rel->portal)) == NULL)
    {
	warn(rel->top, "Cannot read portal!\nOperation stopped");
	return 1;
    }
    n = PQntuples(p);
    if (!n)
	status(rel, "Class is empty");
    setBusy(rel, RETR);
    if (keepPos)
	XtVaGetValues(rel->records, XmNtopItemPosition, &topItem, NULL);
    if (keepSelected)
    {
	XtVaGetValues(rel->records,XmNselectionPolicy, &selectionPolicy,NULL);
	if (!XmListGetSelectedPos(rel->records, &posList, &posCount))
	    keepSelected = False;
    }
    XtUnmanageChild(rel->records);
    XtUnmanageChild(rel->key);
    XmTextClearSelection(rel->fieldNames, CurrentTime);
    XmTextClearSelection(rel->keyTitle, CurrentTime);
    if (rel->attrInfo)
	free((char *)rel->attrInfo);
    if (!(m = getFieldInfo(rel, &rel->attrInfo, FIND_LENGTH, 
			   CONSIDER_FIELD_NAMES)))
	return 1;
    attrInfo = rel->attrInfo;
    XtVaGetValues(rel->records, XmNitemCount, &numItems, NULL);
    if (numItems > 0)
	XmListDeleteAllItems(rel->records);
    XtVaGetValues(rel->key, XmNitemCount, &numItems, NULL);
    if (numItems > 0)
	XmListDeleteAllItems(rel->key);
    /* prepare the attr. title */
    buf[0] = '\0';
    for(i = ATTR_1ST(rel); i < m; i++)
    {
	sprintf(fieldBuf, "%-*s", attrInfo[i].len, attrInfo[i].name);
	strcat(buf, fieldBuf);
	if (i < m-1)
	    strcat(buf, rel->res.separator);
    } 
    /* prepare the key title */
    keyBuf[0] = '\0';
    for(i = 0; i < m; i++)
    {
	if ((pos = attrInfo[i].keyList) == -1)
	    break;      /* end of key list reached */
	sprintf(fieldBuf, "%-*s", attrInfo[pos].len, attrInfo[pos].name);
	if (keyBuf[0] != '\0')     /* prepend a separator */
	    strcat(keyBuf, rel->res.separator);
	strcat(keyBuf, fieldBuf);
    } 
    XmTextSetString(rel->keyTitle, keyBuf);
    if (n)
    {
	memset(fieldBuf, ' ', 10);
	fieldBuf[10] = '\0';
	strcat(buf, fieldBuf);   /* dirty - ensures that the names
				    line is longer than the attributes
				    themselves, including vert. Scr.Bar */
    }
    XmTextSetString(rel->fieldNames, buf);

    for(i = 0; i < n; i++) 
    {
	/* prepare the data line */
	buf[0] = '\0';
	for(j = ATTR_1ST(rel); j < m; j++)
	{
	    val = PQgetvalue(p,i,j);
	    if (val && rel->res.floatPrecision>=0 &&
		(attrInfo[j].pgType==700 || attrInfo[j].pgType==701))
		floatFormat(fieldBuf, val, attrInfo[j].len, 
			    rel->res.floatPrecision);
	    else
		sprintf(fieldBuf, "%-*s", attrInfo[j].len, 
			(val ? val : NULL_VAL));
	    strcat(buf, fieldBuf);
	    if (j < m-1)
		strcat(buf, rel->res.separator);
	}
	/* prepare the key line */
	keyBuf[0] = '\0';
	for(j = 0; j < m; j++)
	{
	    if ((pos = attrInfo[j].keyList) == -1)
		break;      /* end of key list reached */
	    val = PQgetvalue(p, i, pos);
	    if (val && rel->res.floatPrecision>=0 &&
		(attrInfo[pos].pgType==700||attrInfo[pos].pgType==701))
		floatFormat(fieldBuf, val, attrInfo[pos].len, 
			    rel->res.floatPrecision);
	    else
		sprintf(fieldBuf, "%-*s", attrInfo[pos].len, 
			(val ? val : NULL_VAL));
	    if (keyBuf[0] != '\0')     /* prepend a separator */
		strcat(keyBuf, rel->res.separator);
	    strcat(keyBuf, fieldBuf);
	} 
	compStr = XmStringCreateSimple(buf);
	XmListAddItemUnselected(rel->records, compStr, 0);
	XmStringFree(compStr);
	if (keyBuf[0] != '\0')
	{
	    compStr = XmStringCreateSimple(keyBuf);
	    XmListAddItemUnselected(rel->key, compStr, 0);
	    XmStringFree(compStr);
	}
    }
    keyAppearance(rel);
    if (keepSelected)
    {
	XtVaSetValues(rel->records,XmNselectionPolicy, XmMULTIPLE_SELECT,NULL);
	for (pos=0; pos<posCount; pos++)
	    XmListSelectPos(rel->records, posList[pos], True);
	XtFree(posCount);
        XtVaSetValues(rel->records, XmNselectionPolicy, selectionPolicy, NULL);
	equalizeLists(rel->records, rel, NULL);
    }
    if (keepPos)
    {
	XtVaSetValues(rel->records, XmNtopItemPosition, topItem, NULL);
	XtVaSetValues(rel->key, XmNtopItemPosition, topItem, NULL);
    }
    XtManageChild(rel->records);
    XtManageChild(rel->key);
    XtUnmanageChild(rel->mainBox);    /* avoid flashes to the screen */
    /* convince the mainBox form it needs some resizing, the hard way... */
    XtVaSetValues(rel->leftBox, XmNleftAttachment, XmATTACH_NONE, NULL);
    XtVaSetValues(rel->leftBox, XmNleftAttachment, XmATTACH_FORM, NULL);
    XtManageChild(rel->mainBox);      /* restore control */
    windowResized(NULL, rel, NULL);
    setBusy(rel, NOBUSY);
    return 0;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
int getFieldInfo(rel, attrInfoPtr, findFieldLength, considerFieldName)
  relInfo *rel;
  attributeInfo *attrInfoPtr[];
  Boolean findFieldLength, considerFieldName;
{
    PortalBuffer *p;
    int i,j,n,m,l;
    char *val;
    char buf[MAX_PG_CMD];
    char fieldBuf[BUFSIZ];
    attributeInfo *attrInfo;
    int offset = ATTR_1ST(rel);
    char *pgResult;

    if (rel->portal != NULL  &&  (p = PQparray(rel->portal)) != NULL)
	n = PQntuples(p);
    else
	n=0;
    if (n==0)      /* portal's empty or not existing, so ask postgres */
    {
	if (rel->class==NULL)
	{
	    warn(rel->top, "Portal is empty - nothing to view");
	    return 0;
	}
	sprintf(buf, "retrieve (ATT.attname, ATT.atttypid) from ATT in pg_attribute, TYP in pg_type  where TYP.typname=\"%s\" and TYP.typrelid=ATT.attrelid and ATT.attnum>0", rel->class);
	pgResult = execAndWarn(rel->top, buf, 
			       "Cannot retrieve field info! Retrieve failed");
	if (pgResult[0] == 'R')
	    return 0;
	if ((p = PQparray("blank")) == NULL)
	{
	    warn(rel->top, "Cannot read portal!\nOperation stopped");
	    return 0;
	}
	n = PQntuples(p);
	if ((*attrInfoPtr = 
	     (attributeInfo *)malloc((n+1)*sizeof(attributeInfo))) == NULL)
	{
	    fprintf(stderr, "getFieldInfo error: not enough memory!\n");
	    exit(1);
	}
	attrInfo = *attrInfoPtr;
	for(i = 0; i < n; i++)
	{
	    attrInfo[i+offset].name = PQgetvalue(p,i,0);
	    attrInfo[i+offset].len = strlen(attrInfo[i+offset].name);
	    attrInfo[i+offset].pgType = atoi(PQgetvalue(p,i,1));
	}
	makeKeyList(rel, attrInfo, n+offset);
	rel->numFields = n+offset;
	return n+offset;
    }
    m = PQnfields(p,0);
    if ((*attrInfoPtr = (attributeInfo *)malloc(m*sizeof(attributeInfo)))
	== NULL)
    {
	fprintf(stderr, "getFieldInfo error: not enough memory!\n");
	exit(1);
    } 
    attrInfo = *attrInfoPtr;
    for(i = ATTR_1ST(rel); i < m; i++)
    {
	attrInfo[i].name = PQfname(p,0,i);
	attrInfo[i].len = (considerFieldName ? strlen(PQfname(p,0,i)) : 0);
	attrInfo[i].pgType = PQftype(p,0,i);
    }
    makeKeyList(rel, attrInfo, m);
    if (!findFieldLength)
	return m;
    for(j = ATTR_1ST(rel); j < m; j++)
	for(i = 0; i < n; i++)
	{
	    val = PQgetvalue(p,i,j);
	    if (val && rel->res.floatPrecision>=0 &&
		(attrInfo[j].pgType==700 || attrInfo[j].pgType==701))
	    {
		floatFormat(fieldBuf, val, 0, rel->res.floatPrecision);
		l = strlen(fieldBuf);
	    }
	    else if (val == NULL)
		l = NULL_VAL_LEN;
	    else
		l = strlen(val);
	    if (l > attrInfo[j].len)
		attrInfo[j].len = l;
	}
    rel->numFields = m;
    return m;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void CBrecScrollH(w, fieldNamesSB, cbs)
  Widget w;
  Widget fieldNamesSB;
  XmScrollBarCallbackStruct *cbs;
{
    cbs->reason = XmCR_VALUE_CHANGED;
    cbs->event = NULL;

    XtCallCallbacks(fieldNamesSB, XmNvalueChangedCallback, cbs);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void CBrecScrollV(w, key, cbs)
  Widget w;
  Widget key;
  XmScrollBarCallbackStruct *cbs;
{
    XmListSetPos (key, cbs->value+1);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void windowResized(w, rel, event, cont)
  Widget w;
  XConfigureEvent *event;
  relInfo *rel;
  Boolean *cont;
{
    XmScrollBarCallbackStruct cbs;
    int visible;
    Widget recHorizontalSB, fieldNamesSB;
   
    XtVaGetValues(rel->records,
		  XmNvisibleItemCount,    &visible,
		  NULL);
    XtVaSetValues(rel->key,
		  XmNvisibleItemCount,    visible,
		  NULL);
    XtVaGetValues(XtParent(rel->records),
		  XmNhorizontalScrollBar, &recHorizontalSB,
		  NULL);
    XtVaGetValues(XtParent(rel->fieldNames),
		  XmNhorizontalScrollBar, &fieldNamesSB,
		  NULL);
    XtVaGetValues(recHorizontalSB, XmNvalue, &(cbs.value), NULL);
    CBrecScrollH(NULL, fieldNamesSB, &cbs);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void fieldSelected(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XmAnyCallbackStruct *cbs;
{
    char *selection, *ptr;
    char buf[BUFSIZ];
    int i;
    
    selection = XmTextGetSelection(w);
    if (!selection  ||  !strcmp(selection, "")  || strchr(selection, ' ') ||
	       strstr(rel->res.separator, selection))
    {
	XmTextClearSelection(w, CurrentTime);
	return;
    }
    /* check if an array field was selected. if so, call the array broswer */
    for (i=ATTR_1ST(rel);  i<rel->numFields; i++)
	if (!strcmp(rel->attrInfo[i].name, selection))
	{
	    if (rel->attrInfo[i].pgType >= 1000)    /* field is an array */
	    {
		popupArrayBroswer(rel, selection);
		return;
	    }
	    break;
	}
    strcpy(buf, rel->keyAttr);
    ptr = strtok(buf, " ,");
    while (ptr != NULL)
    {
	if (!strcmp(ptr, selection))
	{
	    keyDeselect(w, rel, cbs);
	    return;
	}
	ptr = strtok(NULL, " ,");
    }
    strcpy(buf, rel->keyAttr);
    if (strcmp(rel->keyAttr, ""))
	strcat(buf, rel->res.separator);
    strcat(buf, selection);
    if (!rel->keyAttr)
	free(rel->keyAttr);
    rel->keyAttr = strdup(buf);
    status(rel, "Key added: %s", selection);
    if (rel->class && rel->res.autoSort)
        retrieveClass(rel, True, False, False);
    else
	displayClass(rel, True, True);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void keyAppearance(rel)
  relInfo *rel;
{
    int num;
    
    XtVaGetValues(rel->key, XmNitemCount, &num, NULL);
    if (num == 0)
	XtUnmanageChild(rel->leftBox);
    else
	XtManageChild(rel->leftBox);
/*
    XtVaSetValues(XtParent(rel->key), XmNleftAttachment, XmATTACH_NONE, NULL);
    XtVaSetValues(XtParent(rel->key), XmNleftAttachment, XmATTACH_FORM, NULL);
*/
    XtVaSetValues(rel->rightBox,
		  XmNleftAttachment,    
		  (num!=0) ? XmATTACH_WIDGET : XmATTACH_FORM,
		  XmNleftWidget,  rel->leftBox,
		  NULL);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void fieldDisarm(widget, event, args, num_args)
  Widget widget;
  XButtonEvent *event;
  String *args;
  int *num_args;
{
    XmTextClearSelection(widget, CurrentTime);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void keyDeselect(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XmAnyCallbackStruct *cbs;
{
    char *selection, *ptr;
    char buf[BUFSIZ];
    
    selection = XmTextGetSelection(w);
    if (!selection  ||  !strcmp(selection, "")  || 
	       strstr(rel->res.separator, selection))
    {
	XmTextClearSelection(w, CurrentTime);
	return;
    }
    strcpy(buf, "");
    ptr = strtok(rel->keyAttr, " ,");
    while (ptr != NULL)
    {
	if (strcmp(ptr, selection) && !strstr(rel->res.separator, ptr))
	{
	    if (buf[0] != '\0')     /* prepend a separator */
		strcat(buf, rel->res.separator);
	    strcat(buf, ptr);
	}
	ptr = strtok(NULL, " ,");
    }
    if (!rel->keyAttr)
	free(rel->keyAttr);
    rel->keyAttr = strdup(buf);
    status(rel, "Key removed: %s", selection);
    if (rel->class && rel->res.autoSort)
        retrieveClass(rel, True, False, False);
    else
	displayClass(rel, True, True);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void listItemDoubleClicked(w, rel, cbs)
  XtPointer w, cbs;
  relInfo *rel;
{
    if (rel->class)
	classReplace(rel);
    else 
	warn(rel->top, "Cannot do REPLACE commands on portals!");
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void editresRegisterShell(shellW)
  Widget shellW;
{
    XtAddEventHandler(shellW, (EventMask)0, True,
		      _XEditResCheckMessages, NULL);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void makeKeyList(rel, attrInfo, m)
  relInfo *rel;
  attributeInfo attrInfo[];
  int m;
{
    int i, numKeys=0;
    char *buf, *tok;
    
    buf = strdup(rel->keyAttr);
    tok = strtok(buf, " ,");
    while (tok != NULL)
    {
	for (i=ATTR_1ST(rel); i<m; i++)
	{
	    if (!strcmp(tok, attrInfo[i].name))
	    {
		attrInfo[numKeys++].keyList = i;
		break;
	    }
	}
	tok = strtok(NULL, " ,");
    }
    attrInfo[numKeys].keyList = -1;      /* mark end-of-list */
    free(buf);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void setBusy(rel, state)
  relInfo *rel;
  int state;
{
    if (state == SEND)
	XtVaSetValues(rel->busyIcon, XmNlabelPixmap, sendIconPixmap, NULL);
    else if (state == RETR)
	XtVaSetValues(rel->busyIcon, XmNlabelPixmap, retrIconPixmap, NULL);
    else if (state == PRINT)
	XtVaSetValues(rel->busyIcon, XmNlabelPixmap, printIconPixmap, NULL);
    else if (state == SCRIPT)
	XtVaSetValues(rel->busyIcon, XmNlabelPixmap, scriptIconPixmap, NULL);
    else if (state == NOBUSY  ||  state == BUSY)
	XtVaSetValues(rel->busyIcon, XmNlabelPixmap, nobusyIconPixmap, NULL);

    setCursor(rel->top, (state==NOBUSY ? None : XC_watch));
    XmUpdateDisplay(rel->top);     /* want immediate response */
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void busyIconPressed(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XtPointer cbs;
{
    retrieveClass(rel, True, True, False);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void playIconPressed(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XtPointer cbs;
{
    scriptPlay(rel);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void searchIconPressed(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XtPointer cbs;
{
    searchPopup(rel);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void floatFormat(buf, val, len, prec)
  char *buf, *val;
  int len, prec;
{
    double floatVal;

    sscanf(val, "%lg", &floatVal);
    sprintf(buf, "%*.*f", len, prec, floatVal);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void equalizeLists(w, rel, cbs)
  Widget w;
  relInfo *rel;
  XmListCallbackStruct *cbs;
{
    Widget otherList;
    int i;
    unsigned char selectionPolicy;
    int posCount, *posList, topItem;

    if (w == rel->records)
	otherList = rel->key;
    else if (w == rel->key)
	otherList = rel->records;
    else
	return;    /* in case of trouble... */
    XmListDeselectAllItems(otherList);
    XtVaGetValues(w, XmNtopItemPosition, &topItem, NULL);
    XmListSetPos(otherList, topItem);
    if (!XmListGetSelectedPos(w, &posList, &posCount))
	return;
    XtVaGetValues(otherList, XmNselectionPolicy, &selectionPolicy, NULL);
    XtVaSetValues(otherList, XmNselectionPolicy, XmMULTIPLE_SELECT, NULL);
    for (i=0;  i < posCount;  i++)
	XmListSelectPos(otherList, posList[i], False);
    XtVaSetValues(otherList, XmNselectionPolicy, selectionPolicy, NULL);
    XtFree((char *)posList);
    status(rel, "%d tuples selected", posCount);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void getClassResources(database, class, resStruct)
  String database, class;
  RESOURCE_STRUCT_PTR resStruct;
{
    Widget dbW, classW;
    
    dbW = XtVaCreateWidget(database, widgetClass, classTop, NULL);
    classW = XtVaCreateWidget(class, widgetClass, dbW, NULL);
    XtGetApplicationResources(classW, resStruct, resources,
			      XtNumber(resources), NULL, 0);
    XtDestroyWidget(classW);
    XtDestroyWidget(dbW);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void treatClassResources(rel)
  relInfo *rel;
{
    char buf[BUFSIZ];
    int n;
    
    /* check the separator resource */
    if (strcspn(rel->res.separator, " ") > 0) /* string contains non-spc */
    {
	int before=0, after=0;
	
	strcpy(buf, rel->res.separator);
	/* ensure a space after the sep., so that fields are found easily */
	if (buf[0] != ' ')
	    before=1;
	   /* ensure a space after the sep., so that fields are found easily */
	if ((buf[0] == '\0') || (buf[strlen(buf)-1] != ' '))
	    after=1;
	sprintf(buf, "%s%s%s", (before ? " " : ""), rel->res.separator,
		(after  ? " " : ""));
	rel->res.separator = strdup(buf);  
    }

    /* create an instance of the original key pointer, so it can be free'd */
    if (rel->keyAttr == NULL)
	rel->keyAttr = strdup(rel->res.key);

    /* set the print options */
    for (n=0; n<NUM_MATH_ACTIONS; n++)
	rel->printOptions.totalActions[n] = (&rel->res.printTotals)[n];
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
char *namePortal(name)
  char *name;
{
    char *unique;
    listAtom *atom;
    
    unique = createUniqueName(name);
    atom = XtNew(listAtom);
    atom->name = unique;
    atom->next = list;
    list = atom;
    return atom->name;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/* this routine is responsible for creating a unique portal name,
   based on the original name passed as its argument. The caller is
   responsible to free the returned pointer.*/
char *createUniqueName(name)
  char *name;
{
    char buf[17];
    char numBuf[4];
    int num, offset;
    listAtom *atom;
    
    strcpy(numBuf, "");
    for (num=1; ; num++)
    {
	strcpy(buf, name);
	offset = strlen(buf);
	if (offset+strlen(numBuf) > 15)   /* strange, but you can use  */
	    offset = 15-strlen(numBuf);   /* 15 or less chars, not 16! */
	strcpy(buf+offset, numBuf);
	for (atom=list; atom!=NULL && strcmp(buf, atom->name); atom=atom->next)
	    ;
	if (atom == NULL)
	    break;
	sprintf(numBuf, "%d", num);
    }
#ifdef XPG_DEBUG
    printf("Unique portal created: %s\n", atom->name);
#endif
    return strdup(buf);
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
void deletePortal(rel)
  relInfo *rel;
{
    listAtom *atom, *prevAtom=NULL;
    
    /* delete the portal name in the portal list */
    for (atom=list; atom!=NULL && strcmp(rel->portal, atom->name); 
	 atom=atom->next)
	prevAtom = atom;
    if (atom == NULL)
	XtError("xpg internal error: portal name not in list");
    if (prevAtom != NULL)
	prevAtom->next = atom->next;
    else
	list = atom->next;    /* to-be-deleted atom is first in the list */
    XtFree(atom->name);
    XtFree((char *)atom);

    /* free objects in rel */
    if (rel->class)
	XtFree(rel->class);
    if (rel->portal)
    {
	if (PQparray(rel->portal))
	    PQclear(rel->portal);
	XtFree(rel->portal);
    }
    if (rel->keyAttr)
	XtFree(rel->keyAttr);
    rel->keyAttr = NULL;
#ifdef XPG_DEBUG
    printf("Portal deleted!\n");
#endif
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
/* returns name if succeeded (same as received arg), NULL otherwise */
char *removeSpacesFromName(name)
  char *name;
{
    char *ptr, *firstNonSpace=NULL;

    if (name == NULL)
	return;
    /* remove trailing spaces silently */
    for (ptr=name+strlen(name)-1;  ptr >= name;  ptr--)
	if (isspace(*ptr))
	    *ptr = '\0';
	else
	    break;
    /* remove leading spaces silently, shout if spaces are found
       inside the name (like in "cla ss") */
    for (ptr=name;  *ptr!='\0';  ptr++)
	if (firstNonSpace==NULL && !isspace(*ptr))
	    firstNonSpace = ptr;
	else if (firstNonSpace!=NULL && isspace(*ptr))
	{
	    fprintf(stderr, "White-space character found inside class/portal name - tail truncated!\nName was: %s\n", name);
	    *ptr = '\0';
	    break;
	}
    if (firstNonSpace != NULL)
	strcpy(name, firstNonSpace);
    else
    {
	fprintf(stderr, "Empty class/portal name!\n");
	return NULL;
    }
    return name;
}





/*%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%*/
Boolean xpgQuit(parent)
  Widget parent;
{
    return (yesNo(parent, "Really quit xpg?", "Yes", "No", 1) == 1);
}
