/*----------------------------------------------------------------------*/
/* xcircuit.c --- An X-windows program for drawing circuit diagrams	*/
/* Copyright (c) 2002  R. Timothy Edwards				*/
/*									*/
/* 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:				*/
/*	Free Software Foundation, Inc.					*/
/*	59 Temple Place, Suite 330					*/
/*	Boston, MA  02111-1307  USA					*/
/*									*/
/* See file ./README for contact information				*/
/*----------------------------------------------------------------------*/

/*----------------------------------------------------------------------*/
/*      Uses the Xw widget set (see directory Xw)			*/
/*      Xcircuit written by Tim Edwards beginning 8/13/93 		*/
/*----------------------------------------------------------------------*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <limits.h>
#include <locale.h>
#ifndef XC_WIN32
#include <unistd.h>   /* for unlink() */

#include <X11/Intrinsic.h>
#include <X11/StringDefs.h>
#include <X11/Shell.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/Xproto.h>
#include <X11/Xatom.h>
#endif

#ifdef TCL_WRAPPER
#include <tk.h>
#else
#ifndef XC_WIN32
#include "Xw/Xw.h"
#include "Xw/Form.h"
#include "Xw/WorkSpace.h"
#include "Xw/PButton.h"
#include "Xw/SText.h"
#include "Xw/Cascade.h"
#include "Xw/PopupMgr.h"
#include "Xw/MenuBtn.h"
#include "Xw/BBoard.h"
#include "Xw/TextEdit.h"
#include "Xw/Toggle.h"
#endif
#endif

#if defined(XC_WIN32) && defined(TCL_WRAPPER)
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/cursorfont.h>
#include <X11/Xatom.h>
#endif

#ifndef P_tmpdir
#define P_tmpdir TMPDIR
#endif

/*----------------------------------------------------------------------*/
/* Local includes							*/
/*----------------------------------------------------------------------*/

#include "xcircuit.h"
#include "cursors.h"
#include "colordefs.h"
#include "menudep.h"

/*----------------------------------------------------------------------*/
/* Function prototype declarations                                      */
/*----------------------------------------------------------------------*/
#include "prototypes.h"

#ifdef HAVE_XPM
#ifndef XC_WIN32
#include <X11/xpm.h>
#endif
#include "lib/pixmaps/xcircuit.xpm"
#endif

/*----------------------------------------------------------------------*/
/* Global Variable definitions						*/
/*----------------------------------------------------------------------*/

char	 _STR2[250];  /* Specifically for text returned from the popup prompt */
char	 _STR[150];   /* Generic multipurpose string */
short	 popups;      /* total number of popup widgets on the screen */
Boolean	 pressmode;   /* Whether we are in a press & hold state */

#ifndef TCL_WRAPPER
xcWidget   message1, message2, message3, top, toolbar, overlay = NULL;
xcWidget   menuwidgets[MaxMenuWidgets];
#endif

xcWidget   wschema, wsymb, netbutton;
XtAppContext app;

Display  *dpy;	             /* Works well to make this globally accessible */
Colormap cmap;
Pixmap   STIPPLE[STIPPLES];  /* Polygon fill-style stipple patterns */
Atom	 wprot, wmprop[2];

#ifndef OPENGL
static
#endif
char STIPDATA[STIPPLES][4] = {
   "\000\004\000\001",
   "\000\005\000\012",
   "\001\012\005\010",
   "\005\012\005\012",
   "\016\005\012\007",
   "\017\012\017\005",
   "\017\012\017\016",
   "\000\000\000\000"
};

Cursor	appcursors[NUM_CURSORS];
ApplicationData appdata;
Clientdata areastruct;
Globaldata xobjs;
int *appcolors;
int number_colors;
colorindex *colorlist;
short menusize;

xcIntervalId printtime_id;
short beeper;
short fontcount;
fontinfo *fonts;

extern short eventmode, textpos;

#ifndef TCL_WRAPPER
extern short help_up;
extern menustruct TopButtons[];
#ifdef HAVE_XPM
extern toolbarstruct ToolBar[];
extern short toolbuttons;
#endif
extern short maxbuttons;
#endif

extern Pixmap helppix;
extern aliasptr aliastop;
extern float version;

#ifdef TCL_WRAPPER
extern Tcl_Interp *xcinterp;
#endif

#ifdef DOUBLEBUFFER
extern Pixmap dbuf;
#endif

/* Bad hack for problems with the DECstation. . . don't know why */
#ifdef UniqueContextProblem
#undef XUniqueContext
XContext XUniqueContext()
{
   return XrmUniqueQuark();
}
#endif
/* End of bad hack. . . */

/*----------------------------------------------------------------------*/
/* Initial Resource Management						*/
/*----------------------------------------------------------------------*/

#ifndef TCL_WRAPPER

#ifndef XC_WIN32

static XtResource resources[] = {

  /* schematic layout colors */

  { "globalpincolor", "GlobalPinColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, globalcolor), XtRString, "Orange2"},
  { "localpincolor", "LocalPinColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, localcolor), XtRString, "Red"},
  { "infolabelcolor", "InfoLabelColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, infocolor), XtRString, "SeaGreen"},
  { "ratsnestcolor", "RatsNestColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, ratsnestcolor), XtRString, "Tan4"},

  /* non-schematic layout colors */

  { "bboxcolor", "BBoxColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, bboxpix), XtRString, "greenyellow"},

  /* GUI Color scheme 1 */

  { XtNforeground, XtCForeground, XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, fg), XtRString, "Black"},
  { XtNbackground, XtCBackground, XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, bg), XtRString, "White"},
  { "gridcolor", "GridColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, gridpix), XtRString, "Gray95"},
  { "snapcolor", "SnapColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, snappix), XtRString, "Red"},
  { "selectcolor", "SelectColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, selectpix), XtRString, "Gold3"},
  { "querycolor", "QueryColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, querypix), XtRString, "Turquoise"},
  { "filtercolor", "FilterColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, filterpix), XtRString, "SteelBlue3"},
  { "axescolor", "AxesColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, axespix), XtRString, "Antique White"},
  { "offbuttoncolor", "OffButtonColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, buttonpix), XtRString, "Gray85"},
  { "auxiliarycolor", "AuxiliaryColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, auxpix), XtRString, "Green3"},
  { "barcolor", "BarColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, barpix), XtRString, "Tan"},
  { "paramcolor", "ParamColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, parampix), XtRString, "Plum3"},

  /* GUI Color scheme 2 */

  { "foreground2", XtCForeground, XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, fg2), XtRString, "White"},
  { "background2", XtCBackground, XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, bg2), XtRString, "DarkSlateGray"},
  { "gridcolor2", "GridColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, gridpix2), XtRString, "Gray40"},
  { "snapcolor2", "SnapColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, snappix2), XtRString, "Red"},
  { "selectcolor2", "SelectColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, selectpix2), XtRString, "Gold"},
  { "querycolor2", "QueryColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, querypix2), XtRString, "Turquoise"},
  { "filtercolor2", "FilterColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, filterpix2), XtRString, "SteelBlue1"},
  { "axescolor2", "AxesColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, axespix2), XtRString, "NavajoWhite4"},
  { "offbuttoncolor2", "OffButtonColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, buttonpix2), XtRString, "Gray50"},
  { "auxiliarycolor2", "AuxiliaryColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, auxpix2), XtRString, "Green"},
  { "barcolor2", "BarColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, barpix2), XtRString, "Tan"},
  { "paramcolor2", "ParamColor", XtRPixel, sizeof(Pixel),
      XtOffset(ApplicationDataPtr, parampix2), XtRString, "Plum3"},

  /* Other XDefaults-set properties */

  { XtNfont, XtCFont, XtRFontStruct, sizeof(XFontStruct *),
      XtOffset(ApplicationDataPtr, xcfont), XtRString,
		"-*-times-bold-r-normal--14-*"},
  { "helpfont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
      XtOffset(ApplicationDataPtr, helpfont), XtRString,
		"-*-helvetica-medium-r-normal--10-*"},
  { "filelistfont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
      XtOffset(ApplicationDataPtr, filefont), XtRString,
		"-*-helvetica-medium-r-normal--14-*"},
  { "textfont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
      XtOffset(ApplicationDataPtr, textfont), XtRString,
		"-*-courier-medium-r-normal--14-*"},
  { "titlefont", XtCFont, XtRFontStruct, sizeof(XFontStruct *),
      XtOffset(ApplicationDataPtr, titlefont), XtRString,
		"-*-times-bold-i-normal--14-*"},
  { XtNwidth, XtCWidth, XtRInt, sizeof(int),
      XtOffset(ApplicationDataPtr, width), XtRString, "950"},
  { XtNheight, XtCHeight, XtRInt, sizeof(int),
      XtOffset(ApplicationDataPtr, height), XtRString, "760"},
  { "timeout", "TimeOut", XtRInt, sizeof(int),
      XtOffset(ApplicationDataPtr, timeout), XtRString, "10"}
};

#endif

#endif /* !TCL_WRAPPER */

#ifdef OPENGL

/*----------------------------------------------------------------------*/
/* Get RGB values for a color index.					*/
/* Note:  We need a true reverse mapping here. . .			*/
/*----------------------------------------------------------------------*/

void getRGBcolor(int cval, int *red, int *green, int *blue)
{
   int i;
   for (i = 0; i < number_colors; i++) {
      if (colorlist[i].color.pixel == cval) {
	 *red = (int)colorlist[i].color.red; 
	 *green = (int)colorlist[i].color.green; 
	 *blue = (int)colorlist[i].color.blue; 
	 break;
      }
   }

   /* No color found---fill in values with black */
   *red = 0;
   *green = 0;
   *blue = 0;
}

#endif /* OPENGL */

/*----------------------------------------------------------------------*/
/* Update the list of colors in the colormap				*/
/*----------------------------------------------------------------------*/

void addtocolorlist(xcWidget button, int cvalue)
{
   number_colors++;
   colorlist = (colorindex *)realloc(colorlist, number_colors *
		sizeof(colorindex));
   colorlist[number_colors - 1].cbutton = button;
   colorlist[number_colors - 1].color.pixel = cvalue; 

   /* Get and store the RGB values of the new color */
   
   XQueryColors(dpy, cmap, &(colorlist[number_colors - 1].color), 1);
}

/*----------------------------------------------------------------------*/
/* Add a new color button to the color menu				*/
/* 	called if new color button needs to be made 			*/
/*----------------------------------------------------------------------*/

int addnewcolorentry(int ccolor)
{
   xcWidget colormenu, newbutton;
#ifndef TCL_WRAPPER
   Arg wargs[2];
#endif
   int i, n = 0;

   /* check to see if entry is already in the color list */

   for (i = 0; i < number_colors; i++)
      if (colorlist[i].color.pixel == ccolor) break;

   /* make new entry in the menu */

   if (i == number_colors) {

#ifndef TCL_WRAPPER
      colormenu = xcParent(ColorAddNewColorButton);
      XtnSetArg(XtNlabelType, XwRECT);
      XtnSetArg(XtNrectColor, ccolor);

      newbutton = XtCreateWidget("NewColor", XwmenubuttonWidgetClass,
         colormenu, wargs, n);
      XtAddCallback (newbutton, XtNselect, (XtCallbackProc)setcolor, NULL);
      XtManageChild(newbutton);
#endif

      addtocolorlist(newbutton, ccolor);

#ifdef TCL_WRAPPER
      sprintf(_STR2,
	"xcircuit::newcolorbutton %d %d %d %d",
	    colorlist[i].color.red, colorlist[i].color.green,
	    colorlist[i].color.blue, i);
      Tcl_Eval(xcinterp, _STR2);
#endif

   }
   return i;
}

/*----------------------------------------------------------------------*/
/* This recursive function looks down the button menu hierarchy and     */
/*   creates the necessary buttons and submenus.			*/
/*   Menu entries are marked if the corresponding "size" entry in the	*/
/*   menu structure is > 0.						*/
/*----------------------------------------------------------------------*/

#ifndef TCL_WRAPPER

void makesubmenu(char *menuname, char *attachname, menuptr buttonmenu,
		int arraysize, xcWidget manager)
{
#ifndef XC_WIN32
   short i, n = 0;
   int cval;
   xcWidget popupshell, cascade;
   Arg	wargs[6];
   menuptr p;
   char popupname[30];

   sprintf(popupname, "popup%s", menuname);
   popupshell = XtCreatePopupShell (popupname, transientShellWidgetClass,
	manager,  NULL, 0);

   XtnSetArg(XtNattachTo, attachname);
   XtnSetArg(XtNfont, appdata.titlefont);
   cascade = XtCreateManagedWidget (menuname, XwcascadeWidgetClass,
	popupshell, wargs, n);
   
   for (p = buttonmenu, i = 0; p < buttonmenu + arraysize; p++, i++) {
      n = 0;
      if (p->size > 0 && p->submenu == NULL) { /* This denotes a marked entry */
	 XtnSetArg(XtNsetMark, True);
      }
      XtnSetArg(XtNfont, appdata.xcfont);

      if (p->submenu != NULL) {
         xcWidget newbutton = XtCreateWidget(p->name, XwmenubuttonWidgetClass,
	   	cascade, wargs, n);
	 makesubmenu(p->name, p->name, p->submenu, p->size, manager);
         XtManageChild (newbutton);
      }
      else if (p->name[0] == ' ') {
         /* This is a separator, made from a PushButton widget */

         xcWidget newbutton = XtCreateWidget(p->name, XwmenuButtonWidgetClass,
	   cascade, wargs, n); n = 0;
         XtManageChild (newbutton);

         XtnSetArg(XtNheight, 5);
	 XtnSetArg(XtNsensitive, False);
         XtSetValues(newbutton, wargs, n);
      }
      else {
         if (p->name[0] == '_') {  /* Color button */
	    cval = xc_alloccolor(p->name + 1);
	    XtnSetArg(XtNlabelType, XwRECT);
	    XtnSetArg(XtNrectColor, cval);
         }
         else if (p->name[0] == ':') {  /* Stipple button */
	    XtnSetArg(XtNlabelType, XwRECT);
            if (((pointertype)(p->passeddata) == (OPAQUE | FILLED | FILLSOLID))) {
	       XtnSetArg(XtNrectColor, BlackPixel(dpy,DefaultScreen(dpy)));
	    }
	    else {
	       XtnSetArg(XtNrectStipple, STIPPLE[((pointertype)(p->passeddata) &
	   	     FILLSOLID) >> 5]);
	    }
         }
         menuwidgets[++menusize] = XtCreateWidget(p->name, XwmenubuttonWidgetClass,
	      cascade, wargs, n);
	 XtAddCallback (menuwidgets[menusize], XtNselect, (XtCallbackProc)p->func,
		   p->passeddata);
	 if (p->name[0] == '_') {
            /* For color buttons, maintain a list of Widgets and color values */
            addtocolorlist(menuwidgets[menusize], cval);
	 }

         if (strcmp(p->name, "Enable XSchema") || !areastruct.schemon)
         XtManageChild (menuwidgets[menusize]);
      }
   }
#endif
}

#endif /* !TCL_WRAPPER */

#ifdef HAVE_XPM

/*----------------------------------------------------------------------*/
/* Toolbar Creator							*/
/*----------------------------------------------------------------------*/

#ifndef TCL_WRAPPER

void createtoolbar (xcWidget abform, Widget aform)
{
#ifndef XC_WIN32
   int i, n = 0;
   Arg	wargs[12];
   XImage *iret;
   XpmAttributes attr;

   XtnSetArg(XtNxRefWidget, aform);
   XtnSetArg(XtNxAddWidth, True);
   XtnSetArg(XtNxAttachRight, True);
   XtnSetArg(XtNyAttachBottom, True);
   XtnSetArg(XtNborderWidth, 0);
   XtnSetArg(XtNxOffset, 2);
   XtnSetArg(XtNyOffset, 2);
   XtnSetArg(XtNxResizable, False);
   XtnSetArg(XtNyResizable, True);

   XtnSetArg(XtNlayout, XwIGNORE);
   toolbar = XtCreateManagedWidget("ToolBar", XwbulletinWidgetClass, abform,
	wargs, n); n = 0;

   /* Fix for limited-color capability video.  Thanks to */
   /* Frankie Liu <frankliu@stanford.edu> 		*/

   attr.valuemask = XpmSize | XpmCloseness;
   attr.closeness = 65536;

   for (i = 0; i < toolbuttons; i++) {
      XpmCreateImageFromData(dpy, ToolBar[i].icon_data, &iret, NULL, &attr);
      XtnSetArg(XtNlabelType, XwIMAGE);
      XtnSetArg(XtNlabelImage, iret);
      XtnSetArg(XtNwidth, attr.width + 4);
      XtnSetArg(XtNheight, attr.height + 4);
      XtnSetArg(XtNborderWidth, TBBORDER);
      XtnSetArg(XtNnoPad, True);
      XtnSetArg(XtNhint, ToolBar[i].hint);
      XtnSetArg(XtNhintProc, Wprintf);

      menuwidgets[++menusize] = XtCreateManagedWidget(ToolBar[i].name,
	 XwmenuButtonWidgetClass, toolbar, wargs, n); n = 0;
      XtAddCallback(menuwidgets[menusize], XtNselect,
		(XtCallbackProc)ToolBar[i].func, ToolBar[i].passeddata);
   }
#endif
}

#endif /* !TCL_WRAPPER */

/*----------------------------------------------------------------------*/
/* Toolbar Resize							*/
/*----------------------------------------------------------------------*/

#ifdef TCL_WRAPPER

void resizetoolbar()
{
}

#else

void resizetoolbar()
{
   int i, n = 0, bcol = 0; 
   int max_width = 0, max_height = 0, tot_width = 0;
   Arg	wargs[4];
   xcWidget bwptr, lbwptr, bmax, lbmax;
   Dimension t_height, bheight, bwidth;
   xcWidget *tool_list = NULL;
   int pytools = 0;

   if (!xcIsRealized(toolbar)) return;

   /* Avoid recursive calls to self and extra calls to the main draw routine */

   XtRemoveCallback(areastruct.area, XtNresize, (XtCallbackProc)resizetoolbar, NULL);
   XtRemoveCallback(areastruct.area, XtNresize, (XtCallbackProc)resizearea, NULL);

   /* Find the height of the toolbar from the parent widget */

   XtnSetArg(XtNheight, &t_height);
   XtGetValues(xcParent(toolbar), wargs, n); n = 0;

#ifdef HAVE_PYTHON
   tool_list = pytoolbuttons(&pytools);
#endif

   /* Realign the tool buttons inside the fixed space */

   for (i = 0; i < toolbuttons + pytools; i++) {
#ifdef HAVE_PYTHON
      if (i >= toolbuttons)
	 bwptr = tool_list[i - toolbuttons];
      else
#endif
         bwptr = XtNameToWidget(toolbar, ToolBar[i].name);
      if (bwptr == (Widget)NULL) break;

      XtnSetArg(XtNheight, &bheight);
      XtnSetArg(XtNwidth, &bwidth);
      XtGetValues(bwptr, wargs, n); n = 0;
      bheight += (TBBORDER << 1);
      max_height += bheight;
      if (max_height > t_height) {
	 bcol++;
         lbmax = bmax;
	 tot_width += max_width;
	 max_width = 0;
	 max_height = (int)bheight;
      }
      if (bwidth > max_width) {
	 max_width = (int)bwidth;
	 bmax = bwptr;
      }
      
      XtnSetArg(XtNx, tot_width);
      XtnSetArg(XtNy, max_height - bheight);
      XtSetValues(bwptr, wargs, n); n = 0;
      lbwptr = bwptr;
   }

   XtnSetArg(XtNwidth, tot_width + max_width);
   XtSetValues(toolbar, wargs, n); n = 0;

   /* Reinstate callbacks */
   XtAddCallback(areastruct.area, XtNresize, (XtCallbackProc)resizetoolbar, NULL);
   XtAddCallback(areastruct.area, XtNresize, (XtCallbackProc)resizearea, NULL);

#ifdef HAVE_PYTHON
   if (tool_list != NULL) free(tool_list);
#endif
}

#endif /* HAVE_XPM */
#endif /* !TCL_WRAPPER */

/*----------------------------------------------------------------------*/
/* Hierarchical Menu Creator						*/
/*   This function creates the top level of buttons which are arranged  */
/*   across the top starting at the left edge.  For each button 	*/
/*   that has a submenu, a Popup manager is created, and then menu	*/
/*   panes are attached to the manager in a hierarchical fashion.	*/
/*   Note: Returns widget for last button on top level			*/
/*----------------------------------------------------------------------*/

#ifdef TCL_WRAPPER

void createmenus (xcWidget form, xcWidget *firstbutton, xcWidget *lastbutton)
{
}

#else

void createmenus (xcWidget form, xcWidget *firstbutton, xcWidget *lastbutton)
{
#ifndef XC_WIN32
   int i, maxmgrs = 0, n = 0, j = 0;
   WidgetList buttonw, mgr_shell, menu_mgr;
   Arg	wargs[6];

   menusize = -1;

   for (i = 0; i < maxbuttons; i++) 
      if (TopButtons[i].submenu != NULL) maxmgrs++;

   buttonw = (WidgetList) XtMalloc(maxbuttons * sizeof(Widget));
   mgr_shell = (WidgetList) XtMalloc(maxmgrs * sizeof(Widget));
   menu_mgr = (WidgetList) XtMalloc(maxmgrs * sizeof(Widget));

   for (i = 0; i < maxbuttons; i++) {

      XtnSetArg(XtNheight, ROWHEIGHT);
      XtnSetArg(XtNlabel, TopButtons[i].name);
      XtnSetArg(XtNfont, appdata.xcfont);
      if (i > 0) {
	 XtnSetArg(XtNxRefWidget, buttonw[i - 1]);
	 XtnSetArg(XtNxAddWidth, True);
      }
      buttonw[i] = XtCreateWidget(TopButtons[i].name, 
	 	XwmenuButtonWidgetClass, form, wargs, n); n = 0;

      if (!strcmp(TopButtons[i].name, "Netlist")) {
	 netbutton = buttonw[i];
	 if (areastruct.schemon)
            XtManageChild(buttonw[i]);
      }
      else
         XtManageChild(buttonw[i]);

      if(TopButtons[i].submenu == NULL) 
	 XtAddCallback(buttonw[i], XtNselect,
		(XtCallbackProc)TopButtons[i].func, NULL);
      else {
         mgr_shell[j] = XtCreatePopupShell("mgr_shell", shellWidgetClass,
	    buttonw[i], NULL, 0);
         menu_mgr[j] = XtCreateManagedWidget("menu_mgr", XwpopupmgrWidgetClass,
	    mgr_shell[j], NULL, 0);
	 makesubmenu(TopButtons[i].name, "menu_mgr", TopButtons[i].submenu, 
	    TopButtons[i].size, menu_mgr[j]);
	 j++;
      }
   }
   *firstbutton = buttonw[0];
   *lastbutton = buttonw[i - 1];
#endif
}

#endif /* !TCL_WRAPPER */

/*---------------*/
/* Quit xcircuit */
/*---------------*/

void quit(xcWidget w, caddr_t nulldata)
{
   int i;
   Matrixptr curmatrix, dmatrix;

   /* free up CTM Matrix Stack */
   curmatrix = areastruct.MatStack;
   while (curmatrix != NULL) {
      dmatrix = curmatrix->nextmatrix;
      free(curmatrix);
      curmatrix = dmatrix;
   }
   areastruct.MatStack = NULL;

   /* free the colormap if a new one has been installed */

   if (dpy != NULL)
      if (cmap != DefaultColormap(dpy, DefaultScreen(dpy)))
         XFreeColormap(dpy, cmap);

   /* exit ghostscript if background rendering was enabled */

   exit_gs();

#ifdef TCL_WRAPPER
   /* exit ngspice if the simulator is still running */

   exit_spice();
#endif

   /* remove any temporary files created for background rendering */

   for (i = 0; i < xobjs.pages; i++) {
      if (xobjs.pagelist[i]->pageinst != NULL)
         if (xobjs.pagelist[i]->background.name != (char *)NULL)
	    if (*(xobjs.pagelist[i]->background.name) == '@')
	       unlink(xobjs.pagelist[i]->background.name + 1);
   }

   /* remove the temporary file and free the filename memory	*/
   /* if w = NULL, quit() was reached from Ctrl-C.  Don't 	*/
   /* remove the temporary file;  instead, notify user of the 	*/
   /* filename.							*/

   if (xobjs.tempfile != NULL) {
      if (w != NULL) {
#ifndef XC_WIN32
         if (unlink(xobjs.tempfile) < 0)
	    Fprintf(stderr, "Error %d unlinking file \"%s\"\n", errno,
			xobjs.tempfile);
#else
         if (DeleteFile(xobjs.tempfile) != TRUE)
	    Fprintf(stderr, "Error %d unlinking file \"%s\"\n",
			GetLastError(), xobjs.tempfile);
#endif
      }
      else
	 Fprintf(stderr, "Ctrl-C exit:  reload workspace from \"%s\"\n",
		xobjs.tempfile);
   }
   free(xobjs.tempfile);

#if defined(HAVE_PYTHON)
   /* exit by exiting the Python interpreter, if enabled */
   exit_interpreter();
#elif defined(TCL_WRAPPER)
   exit(0);	/* For now, exit.  Later, clear all pages and return to interp. */
#elif defined(XC_WIN32)
   PostQuitMessage(0);
#else
   exit(0);
#endif

}

/*--------------------------------------------------------------*/
/* Check to see if any objects in xcircuit have been modified	*/
/* without saving.						*/
/*--------------------------------------------------------------*/

u_short countchanges(char **promptstr)
{
   int slen = 1, i, j;
   u_short changes = 0, words = 1;
   objectptr thisobj;

   if (promptstr != NULL) slen += strlen(*promptstr);

   for (i = 0; i < xobjs.pages; i++) {
      if (xobjs.pagelist[i]->pageinst != NULL) {
	 thisobj = xobjs.pagelist[i]->pageinst->thisobject;
         if (thisobj->changes > 0) {
	    if (promptstr != NULL) {
	       slen += strlen(thisobj->name) + 2;
	       *promptstr = (char *)realloc(*promptstr, slen);
	       if ((words % 8) == 0) strcat(*promptstr, ",\n");
	       else if (changes > 0) strcat(*promptstr, ", ");
	       strcat(*promptstr, thisobj->name);
	       words++;
	    }
	    changes += thisobj->changes;
	 }
      }
   }

   /* Check all library objects for unsaved changes */

   for (i = 0; i < xobjs.numlibs; i++) {
      for (j = 0; j < xobjs.userlibs[i].number; j++) {
	 thisobj = *(xobjs.userlibs[i].library + j);
	 if (thisobj->changes > 0) {
	    if (promptstr != NULL) {
	       slen += strlen(thisobj->name) + 2;
	       *promptstr = (char *)realloc(*promptstr, slen);
	       if ((words % 8) == 0) strcat(*promptstr, ",\n");
	       else if (changes > 0) strcat(*promptstr, ", ");
	       strcat(*promptstr, thisobj->name);
	       words++;
	    }
	    changes += thisobj->changes;
	 }
      }
   }
   return changes;
}

/*----------------------------------------------*/
/* Check for conditions to approve program exit */
/*----------------------------------------------*/

void quitcheck(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   char *promptstr;
   Boolean doprompt = False;
   buttonsave *savebutton;

   /* enable default interrupt signal handler during this time, so that */
   /* a double Control-C will ALWAYS exit.				*/

   signal(SIGINT, SIG_DFL);

#ifdef TCL_WRAPPER
   promptstr = (char *)malloc(60);
   strcpy(promptstr, ".query.title.field configure -text \"Unsaved changes in: ");
#else
   promptstr = (char *)malloc(22);
   strcpy(promptstr, "Unsaved changes in: ");
#endif

   /* Check all page objects for unsaved changes */

   doprompt = (countchanges(&promptstr) > 0) ? True : False;

   /* If any changes have not been saved, generate a prompt */

   if (doprompt) {
      promptstr = (char *)realloc(promptstr, strlen(promptstr) + 15);
      strcat(promptstr, "\nQuit anyway?");

#ifdef TCL_WRAPPER
      strcat(promptstr, "\"");
      Tcl_Eval(xcinterp, promptstr);
      Tcl_Eval(xcinterp, ".query.bbar.okay configure -command {quitnocheck}");
      Tcl_Eval(xcinterp, "wm deiconify .query");
#else
      savebutton = (buttonsave *)malloc(sizeof(buttonsave));
      getgeneric(savebutton, w, quitcheck, NULL);
      popupprompt(w, promptstr, NULL, quit, savebutton, NULL);
#endif
      free(promptstr);
   }
   else {
      free(promptstr);
      quit(areastruct.area, NULL);
   }
}

/*--------------------------------------*/
/* A gentle Ctrl-C shutdown		*/
/*--------------------------------------*/

void dointr(int signum)
{
   quitcheck(NULL, NULL, NULL);
}

/*--------------*/
/* Null routine */
/*--------------*/

void DoNothing(xcWidget w, caddr_t clientdata, caddr_t calldata)
{
   /* Nothing here! */
}

/*-------------------------------------------------------------------------*/
/* Popup dialog box routines						   */
/*-------------------------------------------------------------------------*/
/* Propogate any key event from the dialog box into the textedit widget    */
/*-------------------------------------------------------------------------*/

#ifndef TCL_WRAPPER
#ifndef XC_WIN32

void propevent(xcWidget w, xcWidget editwidget, XEvent *event)
{
   Window ewin = xcWindow(editwidget);

   event->xany.window = ewin;
   XSendEvent(dpy, ewin, False, KeyPressMask, event);
}

#endif
#endif

/*-------------------------------------------------------------------------*/
/* Destroy an interactive text-editing popup box 			   */
/*-------------------------------------------------------------------------*/

#ifdef TCL_WRAPPER

/* Just pop it down. . . */
void destroypopup(xcWidget button, popupstruct *callstruct, caddr_t calldata)
{
   Tk_UnmapWindow(callstruct->popup);
   popups--;

   /* free the allocated structure space */

   free(callstruct->buttonptr);
   if (callstruct->filter != NULL) free(callstruct->filter);
   free(callstruct);

   /* in the case of "quitcheck", we want to make sure that the signal	*/
   /* handler is reset to default behavior if the quit command is	*/
   /* canceled from inside the popup prompt window.			*/

   signal(SIGINT, dointr);
}

#else

void destroypopup(xcWidget button, popupstruct *callstruct, caddr_t calldata)
{
#ifndef XC_WIN32
   Arg	wargs[1];

   if(XtNameToWidget(callstruct->popup, "help2") != NULL) {
      help_up = False;
      XFreePixmap(dpy, helppix);
      helppix = (Pixmap)NULL;
   }

   if (callstruct->buttonptr->button != NULL) {  

      /* return the button to its normal state */

      XtSetArg(wargs[0], XtNforeground, callstruct->buttonptr->foreground);
      XtSetValues(callstruct->buttonptr->button, wargs, 1);
   
      XtAddCallback(callstruct->buttonptr->button, XtNselect, 
	  (XtCallbackProc)callstruct->buttonptr->buttoncall,
	  callstruct->buttonptr->dataptr);
   }

   XtDestroyWidget(callstruct->popup);
   popups--;

   /* free the allocated structure space */

   free(callstruct->buttonptr);
   if (callstruct->filter != NULL) free(callstruct->filter);
   free(callstruct);

   /* in the case of "quitcheck", we want to make sure that the signal	*/
   /* handler is reset to default behavior if the quit command is	*/
   /* canceled from inside the popup prompt window.			*/

   signal(SIGINT, dointr);
#endif
}

/*----------------------------------------------------------------------*/
/* Toggle button showing number of pages of output.			*/
/* Erase the filename and set everything accordingly.			*/
/*----------------------------------------------------------------------*/

void linkset(xcWidget button, propstruct *callstruct, caddr_t calldata)
{
   Arg wargs[1];

   free(xobjs.pagelist[areastruct.page]->filename);
   xobjs.pagelist[areastruct.page]->filename = (char *)malloc(1);
   xobjs.pagelist[areastruct.page]->filename[0] = '\0';

   XwTextClearBuffer(callstruct->textw);
   getproptext(button, callstruct, calldata);

   /* Change the select button back to "Apply" */
   XtSetArg(wargs[0], XtNlabel, "Apply");
   XtSetValues(callstruct->buttonw, wargs, 1);

   /* Pop down the toggle button */
   XtUnmanageChild(button);
}

/*----------------------------------------------------------------------*/
/* Pull text from the popup prompt buffer into a global string variable */
/*----------------------------------------------------------------------*/

#ifndef XC_WIN32

void xcgettext(xcWidget button, popupstruct *callstruct, caddr_t calldata)
{
   if (callstruct->textw != NULL) {
      sprintf(_STR2, "%.249s", XwTextCopyBuffer(callstruct->textw)); 

      /* functions which use the file selector should look for directory name */
      /* in string rather than a file name, and take appropriate action.      */

      if (callstruct->filew != NULL) {
         if (lookdirectory(_STR2)) {
	    newfilelist(callstruct->filew, callstruct);
	    return;
         }
      }
   }

   /* Pop down the widget now (for functions like execscript() which    */
   /* may want to interactively control the window contents), but do    */
   /* not destroy it until the function has returned.                   */

   XtPopdown(callstruct->popup);

   /* call the function which sets the variable according to type */
   /* This is in format (function)(calling-widget, ptr-to-data)   */

   (*(callstruct->setvalue))(callstruct->buttonptr->button,
	 callstruct->buttonptr->dataptr);

   if (callstruct->filew != NULL)
      newfilelist(callstruct->filew, callstruct);

   destroypopup(button, callstruct, calldata);
}

#endif

/*-------------------------------------------------------------------------*/
/* Grab text from the "output properties" window			   */
/*-------------------------------------------------------------------------*/

void getproptext(xcWidget button, propstruct *callstruct, caddr_t calldata)
{
   /* xobjs.pagelist[areastruct.page]->filename can be realloc'd by the */
   /* call to *(callstruct->setvalue), so callstruct->dataptr may no    */
   /* longer be pointing to the data.					*/

#ifndef TCL_WRAPPER
   Arg wargs[1];
#endif
   short file_yes = (callstruct->setvalue == setfilename);

   sprintf(_STR2, "%.249s", XwTextCopyBuffer(callstruct->textw)); 
   (*(callstruct->setvalue))(button, callstruct->dataptr);

   /* special stuff for filename changes */

   if (file_yes) {
      char blabel[1024];
      short num_linked;
      xcWidget wrbutton, ltoggle;
      struct stat statbuf;

      /* get updated file information */

      if (strstr(xobjs.pagelist[areastruct.page]->filename, ".") == NULL)
         sprintf(blabel, "%s.ps", xobjs.pagelist[areastruct.page]->filename);
      else sprintf(blabel, "%s", xobjs.pagelist[areastruct.page]->filename);
      if (stat(blabel, &statbuf) == 0) {
         sprintf(blabel, " Overwrite File ");
         if (beeper) XBell(dpy, 100);
         Wprintf("    Warning:  File exists");
      }
      else {
         sprintf(blabel, " Write File ");
         if (errno == ENOTDIR)
            Wprintf("Error:  Incorrect pathname");
         else if (errno == EACCES)
            Wprintf("Error:  Path not readable");
	 Wprintf("  ");
      }

      wrbutton = XtNameToWidget(xcParent(button), "Write File");
      XtSetArg(wargs[0], XtNlabel, blabel);
      XtSetValues(wrbutton, wargs, 1);

      num_linked = pagelinks(areastruct.page);
      if (num_linked > 1) {
	 ltoggle = XtNameToWidget(xcParent(button), "LToggle");
	 sprintf(blabel, "%d Pages", num_linked);
	 XtSetArg(wargs[0], XtNlabel, blabel);
	 XtSetValues(ltoggle, wargs, 1);
	 XtManageChild(ltoggle);
      }
   }

   /* topobject->name is not malloc'd, so is not changed by call to */
   /* *(callstruct->setvalue).					   */

   else if (callstruct->dataptr == topobject->name) {
      printname(topobject);
      renamepage(areastruct.page);
   }

   /* Button title changes from "Apply" to "Okay" */

   XtSetArg(wargs[0], XtNlabel, "Okay");
   XtSetValues(callstruct->buttonw, wargs, 1);
}

/*----------------------------------------------------------------------*/
/* Update scale, width, and height in response to change of one of them	*/
/*----------------------------------------------------------------------*/

void updatetext(xcWidget button, xcWidgetList callstruct, caddr_t calldata)
{
   float oscale, psscale;
   char  edit[3][50];
   short i, n, posit;
   char  *pdptr;
   Arg	 wargs[2];
   int   width, height;

   /* auto-fit may override any changes to the scale */

   autoscale(areastruct.page);
   writescalevalues(edit[0], edit[1], edit[2]);
   for (i = 0; i < 3; i++) {
      n = 0;
      XtnSetArg(XtNstring, edit[i]);
      pdptr = strchr(edit[i], '.');
      posit = (pdptr != NULL) ? (short)(pdptr - edit[i]) : strlen(edit[i]);
      XtnSetArg(XtNinsertPosition, posit);
      XtSetValues(callstruct[i + 2], wargs, n);
   }
}

#endif /* !TCL_WRAPPER */

/*-------------------------------------------------------------------------*/
/* Update the object name in response to a change in filename		   */
/*-------------------------------------------------------------------------*/

void updatename(xcWidget button, xcWidgetList callstruct, caddr_t calldata)
{
   short n, posit;
   char  *rootptr;
#ifndef TCL_WRAPPER
   Arg   wargs[2]; 
#endif
      
   if (strstr(topobject->name, "Page ") != NULL || strstr(topobject->name,
	"Page_") != NULL || topobject->name[0] == '\0') {

      rootptr = strrchr(xobjs.pagelist[areastruct.page]->filename, '/');
      if (rootptr == NULL) rootptr = xobjs.pagelist[areastruct.page]->filename;
      else rootptr++;

      sprintf(topobject->name, "%.79s", rootptr);
  
      n = 0;
      posit = strlen(topobject->name);
#ifndef TCL_WRAPPER
      XtnSetArg(XtNstring, topobject->name);
      XtnSetArg(XtNinsertPosition, posit);
      XtSetValues(callstruct[1], wargs, n);
#endif
      printname(topobject);
      renamepage(areastruct.page);
   }
}

/*-------------------------------------------------------------------------*/
/* Create a popup window with "OK" and "Cancel" buttons,		   */
/* and text and label fields.						   */
/*-------------------------------------------------------------------------*/

#ifdef TCL_WRAPPER

void popupprompt(xcWidget button, char *request, char *current, void (*function)(),
	buttonsave *datastruct, const char *filter)
{
    Tk_Window popup;

    popup = Tk_NameToWindow(xcinterp, ".dialog", Tk_MainWindow(xcinterp));
    Tk_MapWindow(popup);
}

#else

#ifndef XC_WIN32

void popupprompt(xcWidget button, char *request, char *current, void (*function)(),
	buttonsave *datastruct, const char *filter)
{
    Arg         wargs[9];
    xcWidget      popup, dialog, okbutton, cancelbutton, entertext;
    xcWidget	staticarea;
    XWMHints	*wmhints;	/* for proper input focus */
    Position    xpos, ypos;
    short	n = 0;
    Dimension	height, width, areawidth, areaheight, bwidth, owidth;
    static char defaultTranslations[] = "<Key>Return:	execute()";
    popupstruct	*okaystruct;

    height = (current == NULL) ? ROWHEIGHT * 4 : ROWHEIGHT * 5;
    if (filter) height += LISTHEIGHT;

    width = XTextWidth(appdata.xcfont, request, strlen(request)) + 20;
    bwidth = XTextWidth(appdata.xcfont, "Cancel", strlen("Cancel")) + 50;
    owidth = XTextWidth(appdata.xcfont, "Okay", strlen("Okay")) + 50;
    if (width < 400) width = 400;

    XtnSetArg(XtNwidth, &areawidth);
    XtnSetArg(XtNheight, &areaheight);
    XtGetValues(areastruct.area, wargs, n); n = 0;
    XtTranslateCoords(areastruct.area, (Position) (areawidth / 2 - width 
	/ 2 + popups * 20), (Position) (areaheight / 2 - height / 2 +
	popups * 20), &xpos, &ypos);
    XtnSetArg(XtNx, xpos);
    XtnSetArg(XtNy, ypos);
    popup = XtCreatePopupShell("prompt", transientShellWidgetClass,
        button == NULL ? areastruct.area : button, wargs, n); n = 0;
    popups++;

    XtnSetArg(XtNlayout, XwIGNORE);
    XtnSetArg(XtNwidth, width);
    XtnSetArg(XtNheight, height);
    dialog = XtCreateManagedWidget("dialog", XwbulletinWidgetClass,
        popup, wargs, n); n = 0;

    XtnSetArg(XtNx, 20);
    XtnSetArg(XtNy, ROWHEIGHT - 10 + (filter ? LISTHEIGHT : 0));
    XtnSetArg(XtNstring, request);
    XtnSetArg(XtNborderWidth, 0);
    XtnSetArg(XtNgravity, WestGravity);
    XtnSetArg(XtNfont, appdata.xcfont);
    staticarea = XtCreateManagedWidget("static", XwstaticTextWidgetClass,
	dialog, wargs, n); n = 0;

    XtnSetArg(XtNx, 20);
    XtnSetArg(XtNy, height - ROWHEIGHT - 10);
    XtnSetArg(XtNwidth, owidth); 
    XtnSetArg(XtNfont, appdata.xcfont);
    okbutton = XtCreateManagedWidget("Okay", XwmenuButtonWidgetClass, 
	dialog, wargs, n); n = 0;

    okaystruct = (popupstruct *) malloc(sizeof(popupstruct));
    okaystruct->buttonptr = datastruct;
    okaystruct->popup = popup;
    okaystruct->filter = (filter == NULL) ? NULL : strdup(filter);
    okaystruct->setvalue = function;
    okaystruct->textw = NULL;
    okaystruct->filew = NULL;

    XtnSetArg(XtNx, width - bwidth - 20);
    XtnSetArg(XtNy, height - ROWHEIGHT - 10);
    XtnSetArg(XtNwidth, bwidth);
    XtnSetArg(XtNfont, appdata.xcfont);
    cancelbutton = XtCreateManagedWidget("Cancel", XwmenuButtonWidgetClass, 
	   dialog, wargs, n); n = 0; 

    XtAddCallback(cancelbutton, XtNselect, (XtCallbackProc)destroypopup, okaystruct);

    /* Event handler for WM_DELETE_WINDOW message. */
    XtAddEventHandler(popup, NoEventMask, True, (XtEventHandler)delwin, okaystruct);

    if (current != NULL) {   /* A Text Edit widget is required */
       char		*pdptr;
       short		posit;

       XtnSetArg(XtNx, 20);
       XtnSetArg(XtNy, ROWHEIGHT + 10 + (filter ? LISTHEIGHT : 0));
       XtnSetArg(XtNheight, ROWHEIGHT + 5);
       XtnSetArg(XtNwidth, width - 40);
       XtnSetArg(XtNstring, current);
       pdptr = strchr(current, '.');
       posit = (pdptr != NULL) ? (short)(pdptr - current) : strlen(current);
       XtnSetArg(XtNinsertPosition, posit);
       XtnSetArg(XtNscroll, XwAutoScrollHorizontal);
       XtnSetArg(XtNwrap, XwWrapOff);
       XtnSetArg(XtNfont, appdata.textfont);
       entertext = XtCreateManagedWidget("Edit", XwtextEditWidgetClass,
	   dialog, wargs, n); n = 0; 

       okaystruct->textw = entertext;

       XtAddEventHandler(dialog, KeyPressMask, False,
	  (XtEventHandler)propevent, entertext);
       XtAddEventHandler(staticarea, KeyPressMask, False,
	  (XtEventHandler)propevent, entertext);
       XtOverrideTranslations(entertext, XtParseTranslationTable
	  (defaultTranslations));
       XtAddCallback(entertext, XtNexecute, (XtCallbackProc)xcgettext, okaystruct);

       /* Generate file prompting widget */

       if (filter) genfilelist(dialog, okaystruct, width);
    }
    XtAddCallback(okbutton, XtNselect, (XtCallbackProc)xcgettext, okaystruct);

    XtPopup(popup, XtGrabNone);

    /* set the input focus for the window */

    wmhints = XGetWMHints(dpy, xcWindow(popup));
    wmhints->flags |= InputHint;
    wmhints->input = True;
    XSetWMHints(dpy, xcWindow(popup), wmhints);
    XSetTransientForHint(dpy, xcWindow(popup), xcWindow(top));
    XFree(wmhints);

    if (current != NULL) XDefineCursor(dpy, xcWindow(entertext), 
	TEXTPTR);
}

#endif

#endif

/*-----------------------------------------------------------------------*/
/* Write page scale (overall scale, and X and Y dimensions) into strings */
/*-----------------------------------------------------------------------*/

void writescalevalues(char *scdest, char *xdest, char *ydest)
{
   float oscale, psscale;
   int width, height;
   Pagedata    *curpage;

   curpage = xobjs.pagelist[areastruct.page];
   oscale = curpage->outscale;
   psscale = getpsscale(oscale, areastruct.page);

   width = toplevelwidth(curpage->pageinst, NULL);
   height = toplevelheight(curpage->pageinst, NULL);

   sprintf(scdest, "%6.5f", oscale);
   if (curpage->coordstyle == CM) {
      sprintf(xdest, "%6.5f", (width * psscale) / IN_CM_CONVERT);
      sprintf(ydest, "%6.5f", (height * psscale) / IN_CM_CONVERT);
   }
   else {
      sprintf(xdest, "%6.5f", (width * psscale) / 72.0);
      sprintf(ydest, "%6.5f", (height * psscale) / 72.0);
   }
}

/*-------------------------------------------------------------------------*/
/* Create a popup window for property changes				   */
/*-------------------------------------------------------------------------*/

#define MAXPROPS 7
#define MARGIN 15

propstruct okstruct[MAXPROPS], fpokstruct;

#ifdef TCL_WRAPPER

void outputpopup(xcWidget button, caddr_t clientdata, caddr_t calldata)
{
    Tcl_Eval(xcinterp, "wm deiconify .output");
}

#else

#ifndef XC_WIN32

void outputpopup(xcWidget button, caddr_t clientdata, caddr_t calldata)
{
   buttonsave  *savebutton;
   Arg         wargs[9];
   xcWidget      popup, dialog, okbutton, titlearea, wrbutton;
   xcWidget      fpentertext, fpokay, autobutton, allpages;
   xcWidgetList  staticarea, entertext, okays;
   XWMHints    *wmhints;	/* for proper input focus */
   short       num_linked;
   Position    xpos, ypos;
   short       n = 0;
   Dimension   height, width, areawidth, areaheight, bwidth, owidth, wwidth;
   Pagedata    *curpage;
   char	       *pdptr;
   short       posit, i;
   popupstruct *donestruct;
   void		(*function[MAXPROPS])();
   void		(*update[MAXPROPS])();
   char	statics[MAXPROPS][50], edit[MAXPROPS][75], request[150];
   char fpedit[75], outname[75], pstr[20];
   void	*data[MAXPROPS];
   struct stat statbuf;
   static char defaultTranslations[] = "<Key>Return:	execute()";

   if (is_page(topobject) == -1) {
      Wprintf("Can only save a top-level page!");
      return;
   }
   savebutton = (buttonsave *)malloc(sizeof(buttonsave));

   if (button == NULL) button = FileWriteXcircuitPSButton;
   getgeneric(savebutton, button, outputpopup, NULL); 

   curpage = xobjs.pagelist[areastruct.page];

   sprintf(request, "PostScript output properties (Page %d):", 
	areastruct.page + 1);
   sprintf(statics[0], "Filename:");
   sprintf(statics[1], "Page label:");
   sprintf(statics[2], "Scale:");
   if (curpage->coordstyle == CM) {
      sprintf(statics[3], "X Size (cm):");
      sprintf(statics[4], "Y Size (cm):");
   }
   else {
      sprintf(statics[3], "X Size (in):");
      sprintf(statics[4], "Y Size (in):");
   }
   sprintf(statics[5], "Orientation:");
   sprintf(statics[6], "Mode:");

   sprintf(edit[0], "%s", curpage->filename);
   sprintf(edit[1], "%s", topobject->name);

   /* recompute bounding box and auto-scale, if set */
   calcbbox(areastruct.topinstance);
   if (curpage->pmode & 2) autoscale(areastruct.page);
   writescalevalues(edit[2], edit[3], edit[4]);
   sprintf(edit[5], "%s", (curpage->orient == 0) ? "Portrait" : "Landscape");
   sprintf(edit[6], "%s", (curpage->pmode & 1)
	? "Full page" : "Embedded (EPS)");
   function[0] = setfilename;
   function[1] = setpagelabel;
   function[2] = setfloat;
   function[3] = setscalex;
   function[4] = setscaley;
   function[5] = setorient;
   function[6] = setpmode;
   update[0] = updatename;
   update[1] = update[6] = NULL;
   update[2] = updatetext;
   update[3] = updatetext;
   update[4] = updatetext;
   update[5] = updatetext;
   data[0] = &(curpage->filename);
   data[1] = topobject->name;
   data[2] = data[3] = data[4] = &(curpage->outscale);
   data[5] = &(curpage->orient);
   data[6] = &(curpage->pmode);

   entertext = (xcWidgetList) XtMalloc (7 * sizeof (xcWidget));
   staticarea = (xcWidgetList) XtMalloc (7 * sizeof (xcWidget));
   okays = (xcWidgetList) XtMalloc (6 * sizeof (xcWidget));

   /* get file information */

   if (strstr(edit[0], ".") == NULL)
      sprintf(outname, "%s.ps", edit[0]);  
   else sprintf(outname, "%s", edit[0]);
   if (stat(outname, &statbuf) == 0) {
      sprintf(outname, "Overwrite File");
      Wprintf("  Warning:  File exists");
   }
   else {
      sprintf(outname, "Write File");
      if (errno == ENOTDIR)
	 Wprintf("Error:  Incorrect pathname");
      else if (errno == EACCES)
	 Wprintf("Error:  Path not readable");
      else
         Wprintf("  ");
   }

   height = ROWHEIGHT * 17;  /* 3 + (2 * MAXPROPS) */
   width = XTextWidth(appdata.xcfont, request, strlen(request)) + 20;
   bwidth = XTextWidth(appdata.xcfont, "Close", strlen("Close")) + 50;
   owidth = XTextWidth(appdata.xcfont, "Apply", strlen("Apply")) + 50;
   wwidth = XTextWidth(appdata.xcfont, outname, strlen(outname)) + 80;
   if (width < 500) width = 500;

   XtnSetArg(XtNwidth, &areawidth);
   XtnSetArg(XtNheight, &areaheight);
   XtGetValues(areastruct.area, wargs, n); n = 0;
   XtTranslateCoords(areastruct.area, (Position) (areawidth / 2 - width 
	/ 2 + popups * 20), (Position) (areaheight / 2 - height / 2 +
	popups * 20), &xpos, &ypos);
   XtnSetArg(XtNx, xpos);
   XtnSetArg(XtNy, ypos);
   popup = XtCreatePopupShell("prompt", transientShellWidgetClass,
        areastruct.area, wargs, n); n = 0;
   popups++;

   XtnSetArg(XtNlayout, XwIGNORE);
   XtnSetArg(XtNwidth, width);
   XtnSetArg(XtNheight, height);
   dialog = XtCreateManagedWidget("dialog", XwbulletinWidgetClass,
        popup, wargs, n); n = 0;

   XtnSetArg(XtNx, 20);
   XtnSetArg(XtNy, ROWHEIGHT - 10);
   XtnSetArg(XtNstring, request);
   XtnSetArg(XtNborderWidth, 0);
   XtnSetArg(XtNgravity, WestGravity);
   XtnSetArg(XtNbackground, BARCOLOR);
   XtnSetArg(XtNfont, appdata.xcfont);
   titlearea = XtCreateManagedWidget("title", XwstaticTextWidgetClass,
	dialog, wargs, n); n = 0;

   XtnSetArg(XtNx, 20);
   XtnSetArg(XtNy, height - ROWHEIGHT - 10);
   XtnSetArg(XtNwidth, owidth); 
   XtnSetArg(XtNfont, appdata.xcfont);
   okbutton = XtCreateManagedWidget("Close", XwmenuButtonWidgetClass, 
	dialog, wargs, n); n = 0;

   XtnSetArg(XtNx, width - wwidth - 20);
   XtnSetArg(XtNy, height - ROWHEIGHT - 10);
   XtnSetArg(XtNwidth, wwidth);
   XtnSetArg(XtNfont, appdata.xcfont);
   XtnSetArg(XtNlabel, outname);
   wrbutton = XtCreateManagedWidget("Write File", XwmenuButtonWidgetClass,
	dialog, wargs, n); n = 0;

   for (i = 0; i < MAXPROPS; i++) {
      XtnSetArg(XtNx, 20);
      XtnSetArg(XtNy, ROWHEIGHT + MARGIN + 5 + (i * 2 * ROWHEIGHT));
      XtnSetArg(XtNstring, statics[i]);
      XtnSetArg(XtNborderWidth, 0);
      XtnSetArg(XtNgravity, WestGravity);
      XtnSetArg(XtNfont, appdata.xcfont);
      staticarea[i] = XtCreateManagedWidget("static", XwstaticTextWidgetClass,
	   dialog, wargs, n); n = 0;

      XtnSetArg(XtNx, 150);
      XtnSetArg(XtNy, ROWHEIGHT + MARGIN + (i * 2 * ROWHEIGHT));
      if (i < 5) {
         XtnSetArg(XtNheight, ROWHEIGHT + 5);
         XtnSetArg(XtNstring, edit[i]);
         XtnSetArg(XtNwidth, width - owidth - 190);
         pdptr = strchr(edit[i], '.');
         posit = (pdptr != NULL) ? (short)(pdptr - edit[i]) : strlen(edit[i]);
         XtnSetArg(XtNinsertPosition, posit);
         XtnSetArg(XtNscroll, XwAutoScrollHorizontal);
         XtnSetArg(XtNwrap, XwWrapOff);
         XtnSetArg(XtNfont, appdata.textfont);
         entertext[i] = XtCreateManagedWidget("Edit", XwtextEditWidgetClass,
	     dialog, wargs, n); n = 0; 

         XtnSetArg(XtNx, width - owidth - 20);
         XtnSetArg(XtNy, ROWHEIGHT + MARGIN + (i * 2 * ROWHEIGHT));
         XtnSetArg(XtNwidth, owidth); 
         XtnSetArg(XtNfont, appdata.xcfont);
         okays[i] = XtCreateManagedWidget("Apply", XwmenuButtonWidgetClass,
			dialog, wargs, n); n = 0;

         okstruct[i].textw = entertext[i];
	 okstruct[i].buttonw = okays[i];
         okstruct[i].setvalue = function[i];
         okstruct[i].dataptr = data[i];

         XtAddCallback(okays[i], XtNselect, (XtCallbackProc)getproptext, &okstruct[i]);
	 if (update[i] != NULL)
            XtAddCallback(okays[i], XtNselect, (XtCallbackProc)update[i], entertext);
         XtOverrideTranslations(entertext[i], XtParseTranslationTable
              	(defaultTranslations));
         XtAddCallback(entertext[i], XtNexecute, (XtCallbackProc)getproptext,
		&okstruct[i]);
         if (update[i] != NULL) XtAddCallback(entertext[i], XtNexecute,
		(XtCallbackProc)update[i], entertext);

      }
      else {
	 XtnSetArg(XtNlabel, edit[i]);
         XtnSetArg(XtNfont, appdata.xcfont);
         entertext[i] = XtCreateManagedWidget("Toggle", XwpushButtonWidgetClass,
	    dialog, wargs, n); n = 0;
	 XtAddCallback(entertext[i], XtNselect, (XtCallbackProc)function[i], data[i]);
	 if (update[i] != NULL)
            XtAddCallback(entertext[i], XtNselect, (XtCallbackProc)update[i], entertext);
      }
   }

   /* If this filename is linked to other pages (multi-page output), add a button */
   /* which will unlink the page name from the other pages when toggled.	  */

   num_linked = pagelinks(areastruct.page);
   XtnSetArg(XtNx, width - wwidth - 20);
   XtnSetArg(XtNy, ROWHEIGHT - 10);
   XtnSetArg(XtNset, True);
   XtnSetArg(XtNsquare, True);
   XtnSetArg(XtNborderWidth, 0);
   XtnSetArg(XtNfont, appdata.xcfont);
   sprintf(pstr, "%d Pages", num_linked);
   XtnSetArg(XtNlabel, pstr);
   allpages = XtCreateWidget("LToggle", XwtoggleWidgetClass, dialog, wargs, n); n = 0;
   XtAddCallback(allpages, XtNrelease, (XtCallbackProc)linkset, &okstruct[0]);

   /* If full-page pmode is chosen, there is an additional text structure.
      Make this text structure always but allow it to be managed and
      unmanaged as necessary. */

   XtnSetArg(XtNx, 240);
   XtnSetArg(XtNy, ROWHEIGHT + MARGIN + (10 * ROWHEIGHT));
   XtnSetArg(XtNset, (curpage->pmode & 2) ? True : False);
   XtnSetArg(XtNsquare, True);
   XtnSetArg(XtNborderWidth, 0);
   XtnSetArg(XtNfont, appdata.xcfont);
   autobutton = XtCreateWidget("Auto-fit", XwtoggleWidgetClass,
       dialog, wargs, n); n = 0;

   if (curpage->coordstyle == CM) {
      sprintf(fpedit, "%3.2f x %3.2f cm",
	 (float)curpage->pagesize.x / IN_CM_CONVERT,
	 (float)curpage->pagesize.y / IN_CM_CONVERT);
   }
   else {
      sprintf(fpedit, "%3.2f x %3.2f in",
	 (float)curpage->pagesize.x / 72.0,
	 (float)curpage->pagesize.y / 72.0);
   }
   XtnSetArg(XtNx, 240);
   XtnSetArg(XtNy, ROWHEIGHT + MARGIN + (12 * ROWHEIGHT));
   XtnSetArg(XtNheight, ROWHEIGHT + 5);
   XtnSetArg(XtNstring, fpedit);
   XtnSetArg(XtNwidth, width - owidth - 280);
   pdptr = strchr(fpedit, '.');
   posit = (pdptr != NULL) ? (short)(pdptr - fpedit) : strlen(fpedit);
   XtnSetArg(XtNscroll, XwAutoScrollHorizontal);
   XtnSetArg(XtNwrap, XwWrapOff);
   XtnSetArg(XtNfont, appdata.textfont);
   XtnSetArg(XtNinsertPosition, posit);
   fpentertext = XtCreateWidget("fpedit", XwtextEditWidgetClass,
       dialog, wargs, n); n = 0;

   XtnSetArg(XtNx, width - owidth - 20);
   XtnSetArg(XtNy, ROWHEIGHT + MARGIN + (12 * ROWHEIGHT));
   XtnSetArg(XtNwidth, owidth);
   XtnSetArg(XtNfont, appdata.xcfont);
   XtnSetArg(XtNlabel, "Apply");
   fpokay = XtCreateWidget("fpokay", XwmenuButtonWidgetClass,
      dialog, wargs, n); n = 0;

   fpokstruct.textw = fpentertext;
   fpokstruct.buttonw = fpokay;
   fpokstruct.setvalue = setpagesize;
   fpokstruct.dataptr = &(curpage->pagesize);

   XtAddCallback(fpokay, XtNselect, (XtCallbackProc)getproptext, &fpokstruct);
   XtAddCallback(fpokay, XtNselect, (XtCallbackProc)updatetext, entertext);
   XtOverrideTranslations(fpentertext, XtParseTranslationTable
        (defaultTranslations));
   XtAddCallback(fpentertext, XtNexecute, (XtCallbackProc)getproptext, &fpokstruct);
   XtAddCallback(fpentertext, XtNexecute, (XtCallbackProc)updatetext, entertext);
   XtAddCallback(autobutton, XtNselect, (XtCallbackProc)autoset, entertext);
   XtAddCallback(autobutton, XtNrelease, (XtCallbackProc)autostop, NULL);

   if (curpage->pmode & 1) {
      XtManageChild(fpentertext);
      XtManageChild(fpokay);
      XtManageChild(autobutton);
   }

   if (num_linked > 1) {
      XtManageChild(allpages);
   }

   /* end of pagesize extra Widget definitions */

   donestruct = (popupstruct *) malloc(sizeof(popupstruct));
   donestruct->popup = popup;
   donestruct->buttonptr = savebutton;
   donestruct->filter = NULL;
   XtAddCallback(okbutton, XtNselect, (XtCallbackProc)destroypopup, donestruct);

   /* Send setfile() the widget entertext[0] in case because user sometimes
      forgets to type "okay" but buffer contains the expected filename */

   XtAddCallback(wrbutton, XtNselect, (XtCallbackProc)setfile, entertext[0]);

   /* Begin Popup */

   XtPopup(popup, XtGrabNone);

   /* set the input focus for the window */

   wmhints = XGetWMHints(dpy, xcWindow(popup));
   wmhints->flags |= InputHint;
   wmhints->input = True;
   XSetWMHints(dpy, xcWindow(popup), wmhints);
   XSetTransientForHint(dpy, xcWindow(popup), xcWindow(top));
   XFree(wmhints);

   for (i = 0; i < 5; i++)
      XDefineCursor(dpy, xcWindow(entertext[i]), TEXTPTR);
}

#endif

#endif /* !TCL_WRAPPER */

/*-------------------------------------------------*/
/* Print a string to the message widget. 	   */ 
/* Note: Widget message must be a global variable. */
/* For formatted strings, format first into _STR   */
/*-------------------------------------------------*/

#ifdef TCL_WRAPPER
xcTimeOutProc clrmessage(caddr_t clientdata)
#else
xcTimeOutProc clrmessage(caddr_t clientdata, xcIntervalId *id)
#endif
{
   char buf1[50], buf2[50];

   /* Don't write over the report of the edit string contents,	*/
   /* if we're in one of the label edit modes 			*/

   if (eventmode == TEXT_MODE || eventmode == ETEXT_MODE)
      charreport(TOLABEL(EDITPART));
   else {
      measurestr(xobjs.pagelist[areastruct.page]->gridspace, buf1);
      measurestr(xobjs.pagelist[areastruct.page]->snapspace, buf2);
      sprintf(_STR, "Grid %.50s : Snap %.50s", buf1, buf2);
      Wprintf(_STR);
   }
}

#ifndef TCL_WRAPPER

/*------------------------------------------------------------------------------*/

void W1printf(char *string)
{
   Arg	wargs[1];

   XtSetArg(wargs[0], XtNstring, string);
   XtSetValues(message1, wargs, 1);
   XtSetArg(wargs[0], XtNheight, ROWHEIGHT);
   XtSetValues(message1, wargs, 1);
}   

/*------------------------------------------------------------------------------*/

void W2printf(char *string)
{
   Arg	wargs[1];

   XtSetArg(wargs[0], XtNstring, string);
   XtSetValues(message2, wargs, 1);
   XtSetArg(wargs[0], XtNheight, ROWHEIGHT);
   XtSetValues(message2, wargs, 1);
}   

/*------------------------------------------------------------------------------*/

void Wprintf(char *string)
{
   Arg	wargs[1];

   if (printtime_id != 0) {
      xcRemoveTimeOut(printtime_id);
      printtime_id = 0;
   }
   XtSetArg(wargs[0], XtNstring, string);
   XtSetValues(message3, wargs, 1);
   XtSetArg(wargs[0], XtNheight, ROWHEIGHT);
   XtSetValues(message3, wargs, 1);

   /* 10 second timeout */
   printtime_id = xcAddTimeOut(app, 10000, (xcTimeOutProc)clrmessage, NULL);
}   

#endif /* !TCL_WRAPPER */

/*------------------------------------------------------------------------------*/
/* diagnostic tool for translating the event mode into a string and printing	*/
/* to stderr (for debugging only).						*/
/*------------------------------------------------------------------------------*/

void printeventmode() {
   Fprintf(stderr, "eventmode is \'");
   switch(eventmode) {
      case NORMAL_MODE:
	 Fprintf(stderr, "NORMAL");
	 break;
      case MOVE_MODE:
	 Fprintf(stderr, "MOVE");
	 break;
      case COPY_MODE:
	 Fprintf(stderr, "COPY");
	 break;
      case SELAREA_MODE:
	 Fprintf(stderr, "SELAREA");
	 break;
      case CATALOG_MODE:
	 Fprintf(stderr, "CATALOG");
	 break;
      case CATTEXT_MODE:
	 Fprintf(stderr, "CATTEXT");
	 break;
      case FONTCAT_MODE:
	 Fprintf(stderr, "FONTCAT");
	 break;
      case EFONTCAT_MODE:
	 Fprintf(stderr, "EFONTCAT");
	 break;
      case TEXT_MODE:
	 Fprintf(stderr, "TEXT");
	 break;
      case ETEXT_MODE:
	 Fprintf(stderr, "ETEXT");
	 break;
      case WIRE_MODE:
	 Fprintf(stderr, "WIRE");
	 break;
      case BOX_MODE:
	 Fprintf(stderr, "BOX");
	 break;
      case EPOLY_MODE:
	 Fprintf(stderr, "EPOLY");
	 break;
      case ARC_MODE:
	 Fprintf(stderr, "ARC");
	 break;
      case EARC_MODE:
	 Fprintf(stderr, "EARC");
	 break;
      case SPLINE_MODE:
	 Fprintf(stderr, "SPLINE");
	 break;
      case ESPLINE_MODE:
	 Fprintf(stderr, "ESPLINE");
	 break;
      case EPATH_MODE:
	 Fprintf(stderr, "EPATH");
	 break;
      case ASSOC_MODE:
	 Fprintf(stderr, "ASSOC");
	 break;
      case RESCALE_MODE:
	 Fprintf(stderr, "RESCALE");
	 break;
      case PAN_MODE:
	 Fprintf(stderr, "PAN");
	 break;
      default:
	 Fprintf(stderr, "(unknown)");
	 break;
   }
   Fprintf(stderr, "_MODE\'\n");
}

/*------------------------------------------------------------------------------*/

#ifndef TCL_WRAPPER

#ifndef XC_WIN32

void getcommand(xcWidget cmdw, caddr_t clientdata, caddr_t calldata)
{
   sprintf(_STR2, "%.249s", XwTextCopyBuffer(cmdw));
#ifdef HAVE_PYTHON
   execcommand(0, _STR2 + 4);
#else
   execcommand(0, _STR2 + 2);
#endif
   XtRemoveEventHandler(areastruct.area, KeyPressMask, False,
	(XtEventHandler)propevent, cmdw);
   XtAddCallback(areastruct.area, XtNkeyDown, (XtCallbackProc)keyhandler, NULL);
   XtAddCallback(areastruct.area, XtNkeyUp, (XtCallbackProc)keyhandler, NULL);
   XtUnmanageChild(cmdw);
}

#endif

#endif /* !TCL_WRAPPER */

#ifdef TCL_WRAPPER

/*------------------------------------------------------------------------------*/
/* "docommand" de-iconifies the TCL console.					*/
/*------------------------------------------------------------------------------*/

void docommand()
{
   Tcl_Eval(xcinterp, "catch xcircuit::raiseconsole");
}

#else  /* !TCL_WRAPPER */

/*------------------------------------------------------------------------------*/
/* "docommand" overlays the message3 widget temporarily with a TextEdit widget. */
/*------------------------------------------------------------------------------*/

#ifndef XC_WIN32

void docommand()
{
   Arg wargs[12];
   int n = 0;
   Dimension w;
   Position x, y;
   static char defaultTranslations[] = "<Key>Return:   execute()";

   if (overlay == NULL) {
      XtnSetArg(XtNy, &y);
      XtnSetArg(XtNx, &x);
      XtnSetArg(XtNwidth, &w);
      XtGetValues(message3, wargs, n); n = 0;

      XtnSetArg(XtNy, y);
      XtnSetArg(XtNx, x);
      XtnSetArg(XtNheight, ROWHEIGHT);
      XtnSetArg(XtNwidth, w);

      XtnSetArg(XtNfont, appdata.xcfont);
      XtnSetArg(XtNwrap, XwWrapOff);
      overlay = XtCreateManagedWidget("Command", XwtextEditWidgetClass,
	   top, wargs, n); n = 0;

      XtOverrideTranslations(overlay, XtParseTranslationTable(defaultTranslations));
      XtAddCallback(overlay, XtNexecute, (XtCallbackProc)getcommand, NULL);
   }
   else {
      XtManageChild(overlay);
   }

   XwTextClearBuffer(overlay);
#ifdef HAVE_PYTHON
   XwTextInsert(overlay, ">>> ");
#else
   XwTextInsert(overlay, "? ");
#endif

   /* temporarily redirect all text into the overlay widget */

   XtRemoveCallback(areastruct.area, XtNkeyDown, (XtCallbackProc)keyhandler, NULL);
   XtRemoveCallback(areastruct.area, XtNkeyUp, (XtCallbackProc)keyhandler, NULL);
   XtAddEventHandler(areastruct.area, KeyPressMask, False,
	(XtEventHandler)propevent, overlay);
}

#endif /* !XC_WIN32 */

#endif /* !TCL_WRAPPER */

/*------------------------------------------------------------------------------*/
/* When all else fails, install your own colormap			 	*/
/*------------------------------------------------------------------------------*/

#if !defined(XC_WIN32) || defined(TCL_WRAPPER)

int installowncmap()
{
   Colormap newcmap;

   Fprintf(stdout, "Installing my own colormap\n");

   /* allocate a new colormap */

   newcmap = XCopyColormapAndFree(dpy, cmap);

   if (newcmap == (Colormap)NULL) return (-1);

   cmap = newcmap;

#ifndef TCL_WRAPPER
   if (areastruct.area != (xcWidget)NULL) {
      if (xcIsRealized(areastruct.area)) {
         xcWidget colormenu = xcParent(ColorAddNewColorButton);

         XSetWindowColormap(dpy, xcWindow(top), cmap);
         XSetWindowColormap(dpy, areastruct.areawin, cmap);
         if (colormenu != (xcWidget)NULL)
            XSetWindowColormap(dpy, xcWindow(colormenu), cmap);
      }
   }
#endif

   return(1);
}

/*------------------------------------------------------------------------------*/
/* Find the nearest color in the colormap to cvexact and return its pixel value */
/*------------------------------------------------------------------------------*/

int findnearcolor(XColor *cvexact)
{
   /* find the nearest-matching color in the colormap */
 
   int i, ncolors = DisplayCells(dpy, DefaultScreen(dpy));
   XColor *cmcolors;
   long rdist, bdist, gdist;
   u_long dist, mindist;
   int minidx;

   cmcolors = (XColor *)malloc(ncolors * sizeof(XColor));

   for (i = 0; i < ncolors; i++) {
      cmcolors[i].pixel = i;
      cmcolors[i].flags = DoRed | DoGreen | DoBlue;
   }
   XQueryColors(dpy, cmap, cmcolors, ncolors); 

   mindist = ULONG_MAX;
   for (i = 0; i < ncolors; i++) {
      rdist = (cmcolors[i].red - cvexact->red);
      bdist = (cmcolors[i].blue - cvexact->blue);
      gdist = (cmcolors[i].green - cvexact->green);
      dist = rdist * rdist + bdist * bdist + gdist * gdist;
      if (dist < mindist) {
	 mindist = dist;
	 minidx = i;
      }
   }
   free(cmcolors);

   /* if we can't get the right color or something reasonably close, */
   /* then allocate our own colormap.  If we've allocated so many    */
   /* colors that the new colormap is full, then we give up and use  */
   /* whatever color was closest, no matter how far away it was.     */

   /* findnearcolor already used 512 per component, so to match that, */
   /* 3 * (512 * 512) = 786432	~= 750000			      */

   if (dist > 750000) {
      if (installowncmap() > 0) {
         if (XAllocColor(dpy, cmap, cvexact) != 0)
	    minidx = cvexact->pixel;
      }
   }

   return minidx;
}

#endif

/*------------------------------------------------------------------------------*/
/* Layout colors which are defined from the Xdefaults (BBOXCOLOR, schematic	*/
/* label colors) may be duplicated in the colormap.  This routine takes a color	*/
/* index of a color and returns the index of any color in the layout colormap	*/
/* which matches it within the tolerance used by "rgb_alloccolor".  This will	*/
/* be used to remap the Xdefault-supplied colors to the layout color table, so 	*/
/* we don't get duplicate entries.						*/
/*------------------------------------------------------------------------------*/

int xc_getlayoutcolor(int cidx)
{
   XColor loccolor;
   int i, locidx;

   loccolor.pixel = cidx;
   loccolor.flags = DoRed | DoGreen | DoBlue;

   XQueryColors(dpy, cmap, &loccolor, 1);

   locidx = rgb_alloccolor(loccolor.red, loccolor.green, loccolor.blue);
   return locidx;
}

/*-------------------------------------------------------------------------*/
/* Hack on the resource database Color allocation			   */
/*									   */
/* This overrides the built-in routine.  The difference is that if a	   */
/* color cell cannot be allocated (colormap is full), then the color	   */
/* map is searched for the closest RGB value to the named color.	   */
/*									   */
/* This code depends on Display *dpy being set:  Do not install the	   */
/* converter until after XtInitialize().				   */
/*-------------------------------------------------------------------------*/

#if defined(TCL_WRAPPER) || !defined(XC_WIN32)

#ifdef TCL_WRAPPER
caddr_t
#else
XtConverter
#endif

CvtStringToPixel(XrmValuePtr args, int *nargs, XrmValuePtr fromVal,
	XrmValuePtr toVal)
{
   static XColor cvcolor;
   XColor cvexact;

   if (dpy == NULL) return NULL;

   if (*nargs != 0)
      Fprintf(stderr, "String to Pixel conversion takes no arguments");

   /* Color defaults to black if name is not found */

   if (XAllocNamedColor(dpy, cmap, (char *)fromVal->addr, &cvcolor, &cvexact)
	 == 0) {
      if (XLookupColor(dpy, cmap, (char *)fromVal->addr, &cvexact, &cvcolor) == 0)
	 cvcolor.pixel = BlackPixel(dpy, DefaultScreen(dpy));
      else
	 cvcolor.pixel = findnearcolor(&cvexact);
   }

   toVal->size = sizeof(u_long);
   toVal->addr = (caddr_t) &(cvcolor.pixel);
   return NULL;
}

#endif

/*-------------------------------------------------------------------------*/
/* Allocate new color using the CvtStringToPixel routine		   */
/*-------------------------------------------------------------------------*/

int xc_alloccolor(char *name)
{
#if defined(XC_WIN32) && !defined(TCL_WRAPPER)
   return WinNamedColor(name);
#else
   XrmValue fromC, toC;
   int zval = 0, pixval;

   fromC.size = strlen(name);
   fromC.addr = name;

   CvtStringToPixel(NULL, &zval, &fromC, &toC);
   pixval = (int)(*((u_long *)toC.addr));

   return pixval;
#endif
}

/*-------------------------------------------------------------------------*/
/* Allocate new color from RGB values (e.g., from PS file "scb" command)   */
/*-------------------------------------------------------------------------*/

int rgb_alloccolor(int red, int green, int blue)
{
#if defined(XC_WIN32) && !defined(TCL_WRAPPER)
   return RGB(red>>8, green>>8, blue>>8);
#else
   XColor newcolor;
   int i, pixval = -1;

   /* first check if color within RGB roundoff error exists in the list */
   /* Assume 24-bit color, in which resolution can be no less than 256  */
   /* for each color component.  Visual acuity is a bit less than 24-	*/
   /* bit color, so assume difference should be no less than 512 per	*/
   /* component for colors to be considered "different".		*/
   /* Psychologically, we should really find the just-noticable-diff.	*/
   /* for each color component separately!  But that's too complicated	*/
   /* for this simple routine.						*/

   for (i = 0; i < number_colors; i++) {
      if (abs(colorlist[i].color.red - red) < 512 &&
	     abs(colorlist[i].color.green - green) < 512 &&
	     abs(colorlist[i].color.blue - blue) < 512) {
	 pixval = colorlist[i].color.pixel;
	 break;
      }
   }

   /* if color is not already in list, try to allocate it; if allocation */
   /* fails, grab the closest match in the colormap.			 */

   if (pixval < 0) {
      newcolor.red = red;
      newcolor.green = green;
      newcolor.blue = blue;
      newcolor.flags = DoRed | DoGreen | DoBlue;
      if (XAllocColor(dpy, cmap, &newcolor) == 0)
         pixval = findnearcolor(&newcolor);
      else
	 pixval = newcolor.pixel;
   }
   return pixval;
#endif
}

/*-------------------------------------------------------------------------*/
/* Make the cursors from the cursor bit data				   */
/*-------------------------------------------------------------------------*/

void makecursors()
{
   XColor fgcolor, bgcolor;
   Window win = areastruct.areawin;
#ifdef TCL_WRAPPER
   Tk_Uid fg_uid, bg_uid;
#endif

   bgcolor.pixel = BACKGROUND;
   fgcolor.pixel = FOREGROUND;
   XQueryColors(dpy, cmap, &fgcolor, 1);
   XQueryColors(dpy, cmap, &bgcolor, 1);

#ifdef TCL_WRAPPER
   fg_uid = Tk_GetUid(Tk_NameOfColor(&fgcolor));
   bg_uid = Tk_GetUid(Tk_NameOfColor(&bgcolor));

#ifdef _MSC_VER
#define Tk_GetCursorFromData CreateW32Cursor
#endif
   ARROW = (Cursor)Tk_GetCursorFromData(xcinterp, Tk_IdToWindow(dpy, win),
	arrow_bits, arrowmask_bits, arrow_width, arrow_height, arrow_x_hot, arrow_y_hot,
	fg_uid, bg_uid);
   CROSS = (Cursor)Tk_GetCursorFromData(xcinterp, Tk_IdToWindow(dpy, win),
	cross_bits, crossmask_bits, cross_width, cross_height, cross_x_hot, cross_y_hot,
	fg_uid, bg_uid);
   SCISSORS = (Cursor)Tk_GetCursorFromData(xcinterp, Tk_IdToWindow(dpy, win),
	scissors_bits, scissorsmask_bits, scissors_width, scissors_height, scissors_x_hot, scissors_y_hot,
	fg_uid, bg_uid);
   EDCURSOR = (Cursor)Tk_GetCursorFromData(xcinterp, Tk_IdToWindow(dpy, win),
	exx_bits, exxmask_bits, exx_width, exx_height, exx_x_hot, exx_y_hot,
	fg_uid, bg_uid);
   COPYCURSOR = (Cursor)Tk_GetCursorFromData(xcinterp, Tk_IdToWindow(dpy, win),
	copy_bits, copymask_bits, copy_width, copy_height, copy_x_hot, copy_y_hot,
	fg_uid, bg_uid);
   ROTATECURSOR = (Cursor)Tk_GetCursorFromData(xcinterp, Tk_IdToWindow(dpy, win),
	rot_bits, rotmask_bits, rot_width, rot_height, circle_x_hot, circle_y_hot,
	fg_uid, bg_uid);
   QUESTION = (Cursor)Tk_GetCursorFromData(xcinterp, Tk_IdToWindow(dpy, win),
	question_bits, questionmask_bits, question_width, question_height, question_x_hot, question_y_hot,
	fg_uid, bg_uid);
   CIRCLE = (Cursor)Tk_GetCursorFromData(xcinterp, Tk_IdToWindow(dpy, win),
	circle_bits, circlemask_bits, circle_width, circle_height, circle_x_hot, circle_y_hot,
	fg_uid, bg_uid);
   HAND = (Cursor)Tk_GetCursorFromData(xcinterp, Tk_IdToWindow(dpy, win),
	hand_bits, handmask_bits, hand_width, hand_height, hand_x_hot, hand_y_hot,
	fg_uid, bg_uid);
#ifdef _MSC_VER
#undef Tk_GetCursorFromData
#endif
#else
   ARROW = XCreatePixmapCursor(dpy, XCreateBitmapFromData(dpy, win, arrow_bits,
	arrow_width, arrow_height), XCreateBitmapFromData(dpy, win, arrowmask_bits,
	arrow_width, arrow_height), &fgcolor, &bgcolor, arrow_x_hot, arrow_y_hot);
   CROSS = XCreatePixmapCursor(dpy, XCreateBitmapFromData(dpy, win, cross_bits,
	cross_width, cross_height), XCreateBitmapFromData(dpy, win, crossmask_bits,
	cross_width, cross_height), &fgcolor, &bgcolor, cross_x_hot, cross_y_hot);
   SCISSORS = XCreatePixmapCursor(dpy, XCreateBitmapFromData(dpy, win, scissors_bits,
	scissors_width, scissors_height), XCreateBitmapFromData(dpy, win,
	scissorsmask_bits, scissors_width, scissors_height), &fgcolor,
	&bgcolor, scissors_x_hot, scissors_y_hot);
   EDCURSOR = XCreatePixmapCursor(dpy, XCreateBitmapFromData(dpy, win, exx_bits,
	exx_width, exx_height), XCreateBitmapFromData(dpy, win, exxmask_bits,
	exx_width, exx_height), &fgcolor, &bgcolor, exx_x_hot, exx_y_hot);
   COPYCURSOR = XCreatePixmapCursor(dpy, XCreateBitmapFromData(dpy, win, copy_bits,
	copy_width, copy_height), XCreateBitmapFromData(dpy, win, copymask_bits,
	copy_width, copy_height), &fgcolor, &bgcolor, copy_x_hot, copy_y_hot);
   ROTATECURSOR = XCreatePixmapCursor(dpy, XCreateBitmapFromData(dpy, win, rot_bits,
	rot_width, rot_height), XCreateBitmapFromData(dpy, win, rotmask_bits,
	rot_width, rot_height), &fgcolor, &bgcolor, circle_x_hot, circle_y_hot);
   QUESTION = XCreatePixmapCursor(dpy, XCreateBitmapFromData(dpy, win, question_bits,
	question_width, question_height), XCreateBitmapFromData(dpy, win,
	questionmask_bits, question_width, question_height),
	&fgcolor, &bgcolor, question_x_hot, question_y_hot);
   CIRCLE = XCreatePixmapCursor(dpy, XCreateBitmapFromData(dpy, win, circle_bits,
	circle_width, circle_height), XCreateBitmapFromData(dpy, win, circlemask_bits,
	circle_width, circle_height), &fgcolor, &bgcolor, circle_x_hot, circle_y_hot);
   HAND = XCreatePixmapCursor(dpy, XCreateBitmapFromData(dpy, win, hand_bits,
	hand_width, hand_height), XCreateBitmapFromData(dpy, win, handmask_bits,
	hand_width, hand_height), &fgcolor, &bgcolor, hand_x_hot, hand_y_hot);
#endif /* TCL_WRAPPER */

   TEXTPTR = XCreateFontCursor(dpy, XC_xterm);
   WAITFOR = XCreateFontCursor(dpy, XC_watch);

   XRecolorCursor(dpy, TEXTPTR, &fgcolor, &bgcolor);
}

#ifndef TCL_WRAPPER

/*----------------------------------------------------------------------*/
/* Event handler for input focus					*/
/*----------------------------------------------------------------------*/

#ifdef INPUT_FOCUS

void mappinghandler(xcWidget w, caddr_t clientdata, XEvent *event)
{
   if (!xcIsRealized(w)) return;
   switch(event->type) {
      case MapNotify:
	 /* Fprintf(stderr, "Window top was mapped.  Setting input focus\n"); */
	 areastruct.mapped = True;
         XSetInputFocus(dpy, xcWindow(w), RevertToPointerRoot, CurrentTime);  
	 break;
      case UnmapNotify:
	 /* Fprintf(stderr, "Window top was unmapped\n"); */
	 areastruct.mapped = False;
	 break;
   }
}

#endif

/*----------------------------------------------------------------------*/

void clientmessagehandler(xcWidget w, caddr_t clientdata, XEvent *event)
{
   if (!xcIsRealized(w)) return;

   if (event->type == ClientMessage) {
      if (render_client(event) == False)
	 return;

#ifdef INPUT_FOCUS
      if (areastruct.mapped == True) {
         /* Fprintf(stderr, "Forcing input focus\n"); */
         XSetInputFocus(dpy, xcWindow(w), RevertToPointerRoot, CurrentTime);  
      }
#endif
   }
}

/*----------------------------------------------------------------------*/
/* Event handler for WM_DELETE_WINDOW message.                          */
/*----------------------------------------------------------------------*/

void delwin(xcWidget w, popupstruct *bstruct, XClientMessageEvent *event)
{
   if (event->type != ClientMessage)
      return;

   if((event->message_type == wprot) && (event->data.l[0] == wmprop[0])) {
      if (w == top)
	 quitcheck(w, NULL, NULL);
      else
         destroypopup(w, bstruct, NULL);
   }
}

#endif /* !TCL_WRAPPER */

/*----------------------------------------------------------------------*/
/* Preparatory initialization (to be run before setting up the GUI)	*/
/*----------------------------------------------------------------------*/

void pre_initialize()
{
   short i, page;

   /*-------------------------------------------------------------*/
   /* Force LC_NUMERIC locale to en_US for decimal point = period */
   /* notation.  The environment variable LC_NUMERIC overrides if */
   /* it is set explicitly, so it has to be unset first to allow  */
   /* setlocale() to work.					  */
   /*-------------------------------------------------------------*/

#ifdef HAVE_PUTENV
   putenv("LC_ALL=en_US");
   putenv("LC_NUMERIC=en_US");
   putenv("LANG=POSIX");
#else
   unsetenv("LC_ALL");
   unsetenv("LC_NUMERIC");
   setenv("LANG", "POSIX", 1);
#endif
   setlocale(LC_ALL, "en_US");

#ifdef HAVE_XPM
   areastruct.toolbar_on = True;
#endif

   /*---------------------------*/
   /* initialize user variables */
   /*---------------------------*/

   version = PROG_VERSION;
   aliastop = NULL;
   xobjs.pagelist = (Pagedata **) malloc(PAGES * sizeof(Pagedata *));
   for (page = 0; page < PAGES; page++) {
      xobjs.pagelist[page] = (Pagedata *) malloc(sizeof(Pagedata));
      xobjs.pagelist[page]->pageinst = NULL;
   }
   /* Set values for the first page */
   xobjs.pagelist[0]->wirewidth = 2.0;
   xobjs.pagelist[0]->outscale = 1.0;
   xobjs.pagelist[0]->background.name = (char *)NULL;
   xobjs.pagelist[0]->pmode = 0;
   xobjs.pagelist[0]->orient = 0;
   xobjs.pagelist[0]->gridspace = DEFAULTGRIDSPACE;
   xobjs.pagelist[0]->snapspace = DEFAULTSNAPSPACE;
   xobjs.pagelist[0]->drawingscale.x = xobjs.pagelist[0]->drawingscale.y = 1;
   xobjs.pagelist[0]->coordstyle = FRAC_INCH;
   xobjs.pagelist[0]->pagesize.x = 612;
   xobjs.pagelist[0]->pagesize.y = 792;

   xobjs.new_changes = 0;
   xobjs.do_replace = -1;	/* default: replace NONE */
   xobjs.tempfile = NULL;
   signal(SIGINT, dointr);
   printtime_id = 0;

   xobjs.undostack = NULL;
   xobjs.redostack = NULL;

   /* Set the temporary directory name as compiled, unless overridden by */
   /* environment variable "TMPDIR".					 */

   xobjs.tempdir = getenv("TMPDIR");
   if (xobjs.tempdir == NULL) xobjs.tempdir = strdup(TEMP_DIR);

   areastruct.area = (xcWidget)NULL;
   areastruct.mapped = False;
   areastruct.psfont = 0;
   areastruct.justify = FLIPINV;
   areastruct.page = 0;
   areastruct.MatStack = NULL;
   areastruct.textscale = 1.0;
   areastruct.linewidth = 1.0;
   areastruct.zoomfactor = SCALEFAC;
   areastruct.style = UNCLOSED;
   areastruct.invert = False;
   areastruct.axeson = True;
   areastruct.snapto = True;
   areastruct.gridon = True;
   areastruct.center = True;
   areastruct.bboxon = False;
   areastruct.filter = ALL_TYPES;
   areastruct.filefilter = True;
   areastruct.editinplace = True;
   areastruct.selects = 0;
   areastruct.selectlist = NULL;
   areastruct.lastlibrary = 0;
   areastruct.manhatn = False;
   areastruct.boxedit = MANHATTAN;
   areastruct.lastbackground = NULL;
   areastruct.editstack = (objectptr) malloc(sizeof(object));
   areastruct.stack = NULL;   /* at the top of the hierarchy */
   areastruct.hierstack = NULL;
   initmem(areastruct.editstack);
   areastruct.pinpointon = False;
   areastruct.schemon = False;
   areastruct.buschar = '(';	/* Vector notation for buses */
   areastruct.defaultcursor = &CROSS;

   xobjs.numlibs = LIBS - LIBRARY - 1;
   xobjs.fontlib.number = 0;
   xobjs.userlibs = (Library *) malloc(xobjs.numlibs * sizeof(Library));
   for (i = 0; i < xobjs.numlibs; i++) {
      xobjs.userlibs[i].filename = NULL;
      xobjs.userlibs[i].library = (objectptr *) malloc(sizeof(objectptr));
      xobjs.userlibs[i].instlist = NULL;
      xobjs.userlibs[i].number = 0;
   }
   xobjs.imagelist = NULL;
   xobjs.images = 0;
   xobjs.pages = PAGES;

   fontcount = 0;
   fonts = (fontinfo *) malloc(sizeof(fontinfo));

   /* Initialization of objects requires values for the window width and height, */
   /* so set up the widgets and realize them first.				 */

   popups = 0;        /* no popup windows yet */
   beeper = 1;        /* Ring bell on certain warnings or errors */
   pressmode = FALSE; /* not in a button press & hold mode yet */
   initsplines();	/* create lookup table of spline parameters */
}

#ifndef TCL_WRAPPER

/*----------------------------------------------------------------------*/
/* GUI_init() --- generate the widget structures, allocate colormaps	*/
/* and graphics contexts, generate menus and toolbar, and assign	*/
/* callback functions and event handlers.				*/
/*----------------------------------------------------------------------*/

#ifndef XC_WIN32

void GUI_init(int argc, char *argv[])
{

#ifdef HAVE_XPM
   xcWidget abform;
#endif
   xcWidget form, aform, firstbutton, lastbutton, corner;
   XGCValues    values;
   XWMHints	*wmhints;	/* for proper input focus */
   Arg		wargs[12];
   Pixmap	icon;
   Window	win;
   short i, n = 0;

   char *argfb[] = {			/* Fallback resources */
	"xcircuit*foreground : brown4", /* These are the values that 	*/
	"xcircuit*background : beige",  /* not or cannot be explicitly	*/
	"xcircuit.foreground : black",	/* initialized by		*/
	"xcircuit.background : white",  /* XtGetApplicationResources()	*/
	"xcircuit*borderWidth : 2",	/* below.			*/
	"xcircuit*borderColor : Red",
	NULL				/* Sentinel */
   };

   XtSetLanguageProc(NULL, NULL, NULL);

   /*-------------------------------------------------------------------*/
   /* Set pointer to own XDefaults file, but allow an external override */
   /*-------------------------------------------------------------------*/

#ifdef HAVE_PUTENV
   if (getenv("XAPPLRESDIR") == NULL)
      putenv("XAPPLRESDIR=" RESOURCES_DIR);
#else
   setenv("XAPPLRESDIR", RESOURCES_DIR, 0);
#endif

   /*-----------------------------*/
   /* Create the widget hierarchy */
   /*-----------------------------*/

   top = XtOpenApplication(&app, "XCircuit", NULL, 0, &argc, argv,
		argfb, applicationShellWidgetClass, NULL, 0);

   dpy = XtDisplay(top);
   win = DefaultRootWindow(dpy);
   cmap = DefaultColormap(dpy, DefaultScreen(dpy));

   /*-------------------------*/
   /* Create stipple patterns */
   /*-------------------------*/

   for (i = 0; i < STIPPLES; i++)
      STIPPLE[i] = XCreateBitmapFromData(dpy, win, STIPDATA[i], 4, 4);

   /*----------------------------------------*/
   /* Allocate space for the basic color map */
   /*----------------------------------------*/

   number_colors = 0;
   colorlist = (colorindex *)malloc(sizeof(colorindex));
   appcolors = (int *) malloc(NUMBER_OF_COLORS * sizeof(int));

   /*-----------------------------------------------------------*/
   /* Xw must add these translations for the popup manager      */
   /*-----------------------------------------------------------*/
   XwAppInitialize(app);

   /*-------------------------------*/
   /* Get the application resources */
   /*-------------------------------*/

   XtAppAddConverter(app, XtRString, XtRPixel, (XtConverter)CvtStringToPixel, NULL, 0);
   XtGetApplicationResources(top, &appdata, resources, XtNumber(resources), 
	NULL, 0);

   n = 0;
   XtnSetArg(XtNwidth, appdata.width);
   XtnSetArg(XtNheight, appdata.height);
   XtnSetArg(XtNforeground, appdata.fg);
   XtnSetArg(XtNbackground, appdata.bg);
   XtnSetArg(XtNcolormap, cmap);
   XtSetValues(top, wargs, n); n = 0;

   form = XtCreateManagedWidget("Form", XwformWidgetClass, top, NULL, 0);

   /* Set up the buttons and Graphics drawing area */

   createmenus(form, &firstbutton, &lastbutton);

   XtnSetArg(XtNxRefWidget, lastbutton);
   XtnSetArg(XtNyRefWidget, form);
   XtnSetArg(XtNxAddWidth, True);
   XtnSetArg(XtNxAttachRight, True);
   XtnSetArg(XtNheight, ROWHEIGHT);
   sprintf(_STR, "   Welcome to Xcircuit Version %2.1f", PROG_VERSION);
   XtnSetArg(XtNstring, _STR);
   XtnSetArg(XtNxResizable, True);
   XtnSetArg(XtNgravity, WestGravity);
   XtnSetArg(XtNfont, appdata.xcfont);
   XtnSetArg(XtNwrap, False);
   XtnSetArg(XtNstrip, False);
   message1 = XtCreateManagedWidget("Message1", XwstaticTextWidgetClass,
	form, wargs, n); n = 0;

#ifdef HAVE_XPM
   /*-------------------------------------------------------------------*/
   /* An extra form divides the main window from the toolbar		*/
   /*-------------------------------------------------------------------*/

   XtnSetArg(XtNyRefWidget, firstbutton);
   XtnSetArg(XtNyAddHeight, True);
   XtnSetArg(XtNyOffset, 2);
   XtnSetArg(XtNxAttachRight, True);
   XtnSetArg(XtNyResizable, True);
   XtnSetArg(XtNxResizable, True);
   XtnSetArg(XtNborderWidth, 0);
   abform = XtCreateManagedWidget("ABForm", XwformWidgetClass, form, wargs, n);
   n = 0;

   /*-------------------------------------------------------------------*/
   /* The main window and its scrollbars rest in a separate form window */
   /*-------------------------------------------------------------------*/

   XtnSetArg(XtNyResizable, True);
   XtnSetArg(XtNxResizable, True);
   XtnSetArg(XtNyAttachBottom, True);
   aform = XtCreateManagedWidget("AForm", XwformWidgetClass, abform, wargs, n);
   n = 0;
#else
#define abform aform

   /*-------------------------------------------------------------------*/
   /* The main window and its scrollbars rest in a separate form window */
   /*-------------------------------------------------------------------*/

   XtnSetArg(XtNyRefWidget, firstbutton);
   XtnSetArg(XtNyAddHeight, True);
   XtnSetArg(XtNxAttachRight, True);
   XtnSetArg(XtNyResizable, True);
   XtnSetArg(XtNxResizable, True);
   aform = XtCreateManagedWidget("AForm", XwformWidgetClass, form, wargs, n);
   n = 0;

#endif

   /*------------------------*/
   /* add scrollbar widget   */
   /*------------------------*/

   XtnSetArg(XtNxResizable, False);
   XtnSetArg(XtNyResizable, True);
   XtnSetArg(XtNwidth, SBARSIZE);
   XtnSetArg(XtNborderWidth, 1);
   areastruct.scrollbarv = XtCreateManagedWidget("SBV", XwworkSpaceWidgetClass, 
	aform, wargs, n); n = 0;
   
   /*----------------------------------------------------------*/
   /* A button in the scrollbar corner for the sake of beauty. */
   /*----------------------------------------------------------*/

   XtnSetArg(XtNyRefWidget, areastruct.scrollbarv);
   XtnSetArg(XtNyAddHeight, True);
   XtnSetArg(XtNyAttachBottom, True);
   XtnSetArg(XtNheight, SBARSIZE);
   XtnSetArg(XtNwidth, SBARSIZE);
   XtnSetArg(XtNborderWidth, 1);
   XtnSetArg(XtNlabel, "");
   corner = XtCreateManagedWidget("corner", XwpushButtonWidgetClass,
        aform, wargs, n); n = 0;

   /*-------------------------*/
   /* The main drawing window */
   /*-------------------------*/

   XtnSetArg(XtNxOffset, SBARSIZE);
   XtnSetArg(XtNxResizable, True);
   XtnSetArg(XtNyResizable, True);
   XtnSetArg(XtNxAttachRight, True);
   areastruct.area = XtCreateManagedWidget("Area", XwworkSpaceWidgetClass, 
	aform, wargs, n); n = 0;

   /*-------------------------*/
   /* and the other scrollbar */
   /*-------------------------*/

   XtnSetArg(XtNyRefWidget, areastruct.area);
   XtnSetArg(XtNyAddHeight, True);
   XtnSetArg(XtNxRefWidget, areastruct.scrollbarv);
   XtnSetArg(XtNxAddWidth, True);
   XtnSetArg(XtNxAttachRight, True);
   XtnSetArg(XtNheight, SBARSIZE);
   XtnSetArg(XtNyResizable, False);
   XtnSetArg(XtNxResizable, True);
   XtnSetArg(XtNborderWidth, 1);
   areastruct.scrollbarh = XtCreateManagedWidget("SBH", XwworkSpaceWidgetClass, 
	aform, wargs, n); n = 0;

   /*------------------------------------------------*/
   /* Supplementary message widgets go at the bottom */
   /*------------------------------------------------*/

   XtnSetArg(XtNxResizable, False);
   XtnSetArg(XtNyRefWidget, abform);
   XtnSetArg(XtNyAddHeight, True);
   XtnSetArg(XtNyOffset, -5);
   XtnSetArg(XtNheight, ROWHEIGHT);
   XtnSetArg(XtNfont, appdata.xcfont);
   XtnSetArg(XtNforeground, appdata.buttonpix);
   XtnSetArg(XtNbackground, appdata.buttonpix);
   wsymb = XtCreateWidget("Symbol", XwpushButtonWidgetClass,
	form, wargs, n); n = 0;
   if (areastruct.schemon)
      XtManageChild(wsymb);

   XtnSetArg(XtNxRefWidget, wsymb);
   XtnSetArg(XtNxAddWidth, True);
   XtnSetArg(XtNxResizable, False);
   XtnSetArg(XtNyRefWidget, abform);
   XtnSetArg(XtNyAddHeight, True);
   XtnSetArg(XtNyOffset, -5);
   XtnSetArg(XtNheight, ROWHEIGHT);
   XtnSetArg(XtNfont, appdata.xcfont);
   XtnSetArg(XtNforeground, appdata.bg);
   XtnSetArg(XtNbackground, appdata.snappix);
   wschema = XtCreateWidget("Schematic", XwpushButtonWidgetClass,
	form, wargs, n); n = 0;
   if (areastruct.schemon)
      XtManageChild(wschema);


   XtnSetArg(XtNxRefWidget, wschema);
   XtnSetArg(XtNxAddWidth, True);

   XtnSetArg(XtNyRefWidget, abform);
   XtnSetArg(XtNyAddHeight, True);
   XtnSetArg(XtNyOffset, -5);
   XtnSetArg(XtNheight, ROWHEIGHT);
   XtnSetArg(XtNstring, "Editing: Page 1");
   XtnSetArg(XtNxResizable, False);
   XtnSetArg(XtNgravity, WestGravity);
   XtnSetArg(XtNfont, appdata.xcfont);
   XtnSetArg(XtNwrap, False);
   message2 = XtCreateManagedWidget("Message2", XwstaticTextWidgetClass,
	form, wargs, n); n = 0;

   XtnSetArg(XtNyRefWidget, abform);
   XtnSetArg(XtNyAddHeight, True);
   XtnSetArg(XtNyOffset, -5);
   XtnSetArg(XtNxAttachRight, True);
   XtnSetArg(XtNxRefWidget, message2);
   XtnSetArg(XtNxAddWidth, True);
   XtnSetArg(XtNheight, ROWHEIGHT);
   XtnSetArg(XtNxResizable, True);
   XtnSetArg(XtNfont, appdata.xcfont);
   XtnSetArg(XtNwrap, False);
   XtnSetArg(XtNgravity, WestGravity);
   XtnSetArg(XtNstring, "Don't Panic");
   message3 = XtCreateManagedWidget("Message3", XwstaticTextWidgetClass,
	form, wargs, n); n = 0;

   /*-------------------------------*/
   /* optional Toolbar on the right */
   /*-------------------------------*/

#ifdef HAVE_XPM
   createtoolbar(abform, aform);
   XtAddCallback(areastruct.area, XtNresize, (XtCallbackProc)resizetoolbar, NULL);
#endif

   /* Setup callback routines for the area widget */
   /* Use Button1Press event to add the callback which tracks motion;  this */
   /*   will reduce the number of calls serviced during normal operation */ 

   XtAddCallback(areastruct.area, XtNexpose, (XtCallbackProc)drawarea, NULL);
   XtAddCallback(areastruct.area, XtNresize, (XtCallbackProc)resizearea, NULL);

   XtAddCallback(areastruct.area, XtNselect, (XtCallbackProc)buttonhandler, NULL);
   XtAddCallback(areastruct.area, XtNrelease, (XtCallbackProc)buttonhandler, NULL);
   XtAddCallback(areastruct.area, XtNkeyDown, (XtCallbackProc)keyhandler, NULL);
   XtAddCallback(areastruct.area, XtNkeyUp, (XtCallbackProc)keyhandler, NULL);

   XtAddEventHandler(areastruct.area, Button1MotionMask | Button2MotionMask,
		False, (XtEventHandler)xlib_drag, NULL);

   /* Setup callback routines for the scrollbar widgets */

   XtAddEventHandler(areastruct.scrollbarh, ButtonMotionMask, False,
	(XtEventHandler)panhbar, NULL);
   XtAddEventHandler(areastruct.scrollbarv, ButtonMotionMask, False,
	(XtEventHandler)panvbar, NULL);

   XtAddCallback(areastruct.scrollbarh, XtNrelease, (XtCallbackProc)endhbar, NULL);
   XtAddCallback(areastruct.scrollbarv, XtNrelease, (XtCallbackProc)endvbar, NULL);

   XtAddCallback(areastruct.scrollbarh, XtNexpose, (XtCallbackProc)drawhbar, NULL);
   XtAddCallback(areastruct.scrollbarv, XtNexpose, (XtCallbackProc)drawvbar, NULL);
   XtAddCallback(areastruct.scrollbarh, XtNresize, (XtCallbackProc)drawhbar, NULL);
   XtAddCallback(areastruct.scrollbarv, XtNresize, (XtCallbackProc)drawvbar, NULL);

   /* Event handler for WM_DELETE_WINDOW message. */
   XtAddEventHandler(top, NoEventMask, True, (XtEventHandler)delwin, NULL);

   XtAddCallback(corner, XtNselect, (XtCallbackProc)zoomview, Number(1));
   XtAddCallback (wsymb, XtNselect, (XtCallbackProc)xlib_swapschem, Number(0));
   XtAddCallback (wschema, XtNselect, (XtCallbackProc)xlib_swapschem, Number(0));

   /*--------------------*/
   /* Realize the Widget */
   /*--------------------*/

   XtRealizeWidget(top);

   wprot = XInternAtom(dpy, "WM_PROTOCOLS", False);
   wmprop[0] = XInternAtom(dpy, "WM_DELETE_WINDOW", False);
   wmprop[1] = XInternAtom(dpy, "WM_TAKE_FOCUS", False); 
   XSetWMProtocols(dpy, xcWindow(top), wmprop, 2);

   /*----------------------------------------------------*/
   /* Let the window manager set the input focus for the */
   /* window and inform the window manager of the icon   */
   /* pixmap (which may or may not be useful, depending  */
   /* on the particular window manager).		 */
   /*----------------------------------------------------*/

   wmhints = XGetWMHints(dpy, xcWindow(top));
   wmhints->input = True;

#ifdef HAVE_XPM
   /* Create the xcircuit icon pixmap */
   XpmCreatePixmapFromData(dpy, win, xcircuit_xpm, &icon, NULL, NULL);

   wmhints->flags |= InputHint | IconPixmapHint;
   wmhints->icon_pixmap = icon;
#else
   wmhints->flags |= InputHint;
#endif

   XSetWMHints(dpy, xcWindow(top), wmhints);
   XFree(wmhints);

/* Don't know why this is necessary, but otherwise keyboard input focus    */
/* is screwed up under the WindowMaker window manager and possibly others. */

#ifdef INPUT_FOCUS
   XtAddEventHandler(top, SubstructureNotifyMask,
	TRUE, (XtEventHandler)mappinghandler, NULL);
#endif

   XtAddEventHandler(top, NoEventMask, TRUE,
	(XtEventHandler)clientmessagehandler, NULL);

   /*---------------------------------------------------*/
   /* Define basic display variables 			*/
   /* Redefine win to be just the drawing area window   */
   /*---------------------------------------------------*/

   areastruct.areawin = xcWindow(areastruct.area);

   /*-----------------------------*/
   /* Create the Graphics Context */
   /*-----------------------------*/

   values.foreground = BlackPixel(dpy, DefaultScreen(dpy));
   values.background = WhitePixel(dpy, DefaultScreen(dpy));
   values.font = appdata.xcfont->fid;

   areastruct.gc = XCreateGC(dpy, areastruct.areawin,
		GCForeground | GCBackground | GCFont, &values);

   /* set the area widget width and height, center userspace (0,0) on screen */

   XtSetArg(wargs[0], XtNwidth, &areastruct.width);
   XtSetArg(wargs[1], XtNheight, &areastruct.height);
   XtGetValues(areastruct.area, wargs, 2);

}

#endif

#endif
/* #endif for #ifndef TCL_WRAPPER				*/

#ifdef TCL_WRAPPER

/*----------------------------------------------------------------------*/
/* Create a new Handle object in Tcl */
/*----------------------------------------------------------------------*/

static void UpdateStringOfHandle _ANSI_ARGS_((Tcl_Obj *objPtr));
static int SetHandleFromAny _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *objPtr));

static Tcl_ObjType tclHandleType = {
    "handle",				/* name */
    (Tcl_FreeInternalRepProc *) NULL,	/* freeIntRepProc */
    (Tcl_DupInternalRepProc *) NULL,	/* dupIntRepProc */
    UpdateStringOfHandle,		/* updateStringProc */
    SetHandleFromAny			/* setFromAnyProc */
};

/*----------------------------------------------------------------------*/

static void
UpdateStringOfHandle(objPtr)
    register Tcl_Obj *objPtr;   /* Int object whose string rep to update. */
{
    char buffer[TCL_INTEGER_SPACE];
    register int len;

    sprintf(buffer, "H%08lX", objPtr->internalRep.longValue);
    len = strlen(buffer);

    objPtr->bytes = Tcl_Alloc((u_int)len + 1);
    strcpy(objPtr->bytes, buffer);
    objPtr->length = len;
}

/*----------------------------------------------------------------------*/

static int
SetHandleFromAny(interp, objPtr)
    Tcl_Interp *interp;         /* Used for error reporting if not NULL. */
    register Tcl_Obj *objPtr;   /* The object to convert. */
{  
    Tcl_ObjType *oldTypePtr = objPtr->typePtr;
    char *string, *end;
    int length;
    register char *p;
    long newLong;
    pushlistptr newstack = NULL;

    string = Tcl_GetStringFromObj(objPtr, &length);
    errno = 0;
    for (p = string;  isspace((u_char)(*p));  p++);

nexthier:

    if (*p++ != 'H') {
	if (interp != NULL) {
            Tcl_ResetResult(interp);
            Tcl_AppendToObj(Tcl_GetObjResult(interp), 
		"handle is identified by leading H and hexidecimal value only", -1);
            /* TclCheckBadOctal(interp, string); */
        }
        free_stack(&newstack);
        return TCL_ERROR;
    } else {
        newLong = strtoul(p, &end, 16);
    }
    if (end == p) {
        badHandle:
        if (interp != NULL) {
            /*
             * Must copy string before resetting the result in case a caller
             * is trying to convert the interpreter's result to an int.
             */

            char buf[100];
            sprintf(buf, "expected handle but got \"%.50s\"", string);
            Tcl_ResetResult(interp);
            Tcl_AppendToObj(Tcl_GetObjResult(interp), buf, -1);
            /* TclCheckBadOctal(interp, string); */
        }
        free_stack(&newstack);
        return TCL_ERROR;
    }
    if (errno == ERANGE) {
        if (interp != NULL) {
            char *s = "handle value too large to represent";
            Tcl_ResetResult(interp);
            Tcl_AppendToObj(Tcl_GetObjResult(interp), s, -1);
            Tcl_SetErrorCode(interp, "ARITH", "IOVERFLOW", s, (char *) NULL);
        }
        free_stack(&newstack);
        return TCL_ERROR;
    }
    /*
     * Make sure that the string has no garbage after the end of the handle.
     */
   
    while ((end < (string+length)) && isspace((u_char)(*end))) end++;
    if (end != (string+length)) {
       /* Check for handles separated by slashes.  If present,	*/      
       /* then generate a hierstack.				*/

	if ((end != NULL) && (*end == '/')) {
	   objinstptr refinst, chkinst;
	   genericptr *rgen;

	   *end = '\0';
           newLong = strtoul(p, &end, 16);
	   p = end + 1;
	   *end = '/';
	   refinst = (newstack == NULL) ? areastruct.topinstance : newstack->thisinst;
	   chkinst = (objinstptr)((pointertype)(newLong));
	   /* Ensure that chkinst is in the plist of			*/
	   /* refinst->thisobject, and that it is type objinst.	*/
	   for (rgen = refinst->thisobject->plist; rgen < refinst->thisobject->plist
			+ refinst->thisobject->parts; rgen++) {
	      if ((objinstptr)(*rgen) == chkinst) {
		 if (ELEMENTTYPE(*rgen) != OBJINST) {
		    free_stack(&newstack);
		    Tcl_SetResult(interp, "Hierarchical element handle "
				"component is not an object instance.", NULL);
		    return TCL_ERROR;
		 }
		 break;
	      }
	   }
	   if (rgen == refinst->thisobject->plist + refinst->thisobject->parts) {
               Tcl_SetResult(interp, "Bad component in hierarchical "
			"element handle.", NULL);
	       free_stack(&newstack);
	       return TCL_ERROR;
	   }
	   push_stack(&newstack, chkinst);
	   goto nexthier;
        }
	else
	   goto badHandle;
    }
   
    /* Note that this check won't prevent a hierarchical selection from	*/
    /* being added to a non-hierarchical selection.			*/

    if (areastruct.hierstack != NULL) {
       if ((newstack == NULL) || (newstack->thisinst !=
		areastruct.hierstack->thisinst)) {
	  Tcl_SetResult(interp, "Attempt to select components in different "
			"objects.", NULL);
          free_stack(&newstack);
	  return TCL_ERROR;
       }
    }
    free_stack(&areastruct.hierstack);
    areastruct.hierstack = newstack;

    /*
     * The conversion to handle succeeded. Free the old internalRep before
     * setting the new one. We do this as late as possible to allow the
     * conversion code, in particular Tcl_GetStringFromObj, to use that old
     * internalRep.
     */
   
    if ((oldTypePtr != NULL) && (oldTypePtr->freeIntRepProc != NULL)) {
        oldTypePtr->freeIntRepProc(objPtr);
    }
   
    objPtr->internalRep.longValue = newLong;
    objPtr->typePtr = &tclHandleType;
    return TCL_OK;
}  

/*----------------------------------------------------------------------*/

Tcl_Obj *
Tcl_NewHandleObj(optr)
    register void *optr;      /* Int used to initialize the new object. */
{
    register Tcl_Obj *objPtr;

    objPtr = Tcl_NewObj();
    objPtr->bytes = NULL;

    objPtr->internalRep.longValue = (long)(optr);
    objPtr->typePtr = &tclHandleType;
    return objPtr;
}

/*----------------------------------------------------------------------*/

int
Tcl_GetHandleFromObj(interp, objPtr, handlePtr)
    Tcl_Interp *interp;		/* Used for error reporting if not NULL. */
    register Tcl_Obj *objPtr;	/* The object from which to get a int. */
    register void **handlePtr;	/* Place to store resulting int. */
{
    register long l;
    int result;

    if (objPtr->typePtr != &tclHandleType) {
        result = SetHandleFromAny(interp, objPtr);
        if (result != TCL_OK) {
            return result;
        }
    }
    l = objPtr->internalRep.longValue;
    if (((long)((int)l)) == l) {
        *handlePtr = (void *)objPtr->internalRep.longValue;
        return TCL_OK;
    }
    if (interp != NULL) {
        Tcl_ResetResult(interp);
        Tcl_AppendToObj(Tcl_GetObjResult(interp),
                "value too large to represent as handle", -1);
    }
    return TCL_ERROR;
}


#endif

/*----------------------------------------------------------------------*/
/* Routine to initialize variables after the GUI has been set up	*/
/*----------------------------------------------------------------------*/

void post_initialize()
{
   short i;

   /*--------------------------------------------------*/
   /* Setup the (simple) colormap and make the cursors */
   /*--------------------------------------------------*/

   setcolorscheme(True);
   areastruct.color = DEFAULTCOLOR;

   LOCALPINCOLOR = appdata.localcolor;
   GLOBALPINCOLOR = appdata.globalcolor;
   INFOLABELCOLOR = appdata.infocolor;
   RATSNESTCOLOR = appdata.ratsnestcolor;
   BBOXCOLOR = appdata.bboxpix;

   /* Now that we have values for the window width and height, we can initialize */
   /* the page objects.								 */

   xobjs.libtop = (objinstptr *)malloc(LIBS * sizeof(objinstptr));
   for (i = 0; i < LIBS; i++) {
      objectptr newlibobj = (objectptr) malloc(sizeof(object));
      initmem(newlibobj);
      xobjs.libtop[i] = newpageinst(newlibobj);
   }

   /* Give names to the five default libraries */
   strcpy(xobjs.libtop[FONTLIB]->thisobject->name, "Font Character List");
   strcpy(xobjs.libtop[PAGELIB]->thisobject->name, "Page Directory");
   strcpy(xobjs.libtop[LIBLIB]->thisobject->name,  "Library Directory");
   strcpy(xobjs.libtop[USERLIB]->thisobject->name, "User Library");
   renamelib(USERLIB);

   changepage(0);

   /* Centering the view is not required here because the default values */
   /* set in initmem() should correctly position the empty page in the	 */
   /* middle of the viewing window.					 */

#ifdef DOUBLEBUFFER
   if (dbuf == (Pixmap)NULL)
      dbuf = XCreatePixmap(dpy, areastruct.areawin, areastruct.width,
		areastruct.height, DefaultDepthOfScreen(xcScreen(areastruct.area)));
#endif

#ifdef TCL_WRAPPER

   /* Set up fundamentally necessary colors black and white */

   addnewcolorentry(xc_alloccolor("Black"));
   addnewcolorentry(xc_alloccolor("White"));

   /* Set up new Tcl type "handle" for element handles */

   Tcl_RegisterObjType(&tclHandleType);

#endif

   /*-----------------------------------------------------*/
   /* Set the cursor as a crosshair for the area widget.  */
   /*-----------------------------------------------------*/

   XDefineCursor (dpy, areastruct.areawin, DEFAULTCURSOR);

   /*---------------------------------------------------*/
   /* Set up a timeout for automatic save to a tempfile */
   /*---------------------------------------------------*/

   xobjs.save_interval = appdata.timeout;
   xobjs.timeout_id = xcAddTimeOut(app, (u_long)60000 * xobjs.save_interval,
	(xcTimeOutProc)savetemp, NULL);
}

#ifndef TCL_WRAPPER

/*----------------------------------------------------------------------*/
/* When not using ToolScript, this is the standard X loop (XtMainLoop())*/
/*----------------------------------------------------------------------*/

int local_xloop()
{
   XtAppMainLoop(app);
   return EXIT_SUCCESS;
}

/*----------------------------------------------------------------------*/
/* Main entry point when used as a standalone program			*/
/*----------------------------------------------------------------------*/

int main(int argc, char **argv)
{
   char  *argv0;		/* find root of argv[0] */
   short initargc = argc;	/* because XtInitialize() absorbs the     */
				/* -schem flag and renumbers argc! (bug?) */
   short k = 0;

   /*-----------------------------------------------------------*/
   /* Find the root of the command called from the command line */
   /*-----------------------------------------------------------*/

   argv0 = strrchr(argv[0], '/');
   if (argv0 == NULL)
      argv0 = argv[0];
   else
      argv0++;

   pre_initialize();

   /*---------------------------*/
   /* Check for schematic flag  */
   /*---------------------------*/

   for (k = argc - 1; k > 0; k--) {
      if (!strncmp(argv[k], "-schem", 6)) {
	 areastruct.schemon = True;
	 break;
      }
      if (!strncmp(argv[k], "-2", 2)) {
	 pressmode = True;	/* 2-button mouse indicator */
	 break;
      }
   }

   GUI_init(argc, argv);
   post_initialize();

   /*-------------------------------------*/
   /* Initialize the ghostscript renderer */
   /*-------------------------------------*/

   ghostinit();

   /*----------------------------------------------------------*/
   /* Check home directory for initial settings	& other loads; */
   /* Load the (default) built-in set of objects 	       */
   /*----------------------------------------------------------*/

#ifdef HAVE_PYTHON
   init_interpreter();
#endif

   loadrcfile();
   pressmode = False;	/* Done using this to mark 2-button mouse mode */

   composelib(PAGELIB);	/* make sure we have a valid page list */
   composelib(LIBLIB);	/* and library directory */

   /*----------------------------------------------------*/
   /* Parse the command line for initial file to load.   */
   /* Otherwise, look for possible crash-recovery files. */
   /*----------------------------------------------------*/

   if (argc == 2 + (k != 0) || initargc == 2 + (k != 0)) {
      strcpy(_STR2, argv[(k == 1) ? 2 : 1]);
      startloadfile();
   }
   else {
      findcrashfiles();
   }

   return local_xloop();   /* No return---exit through quit() callback */
}

#endif
/* #endif for #ifndef TCL_WRAPPER					*/
/*----------------------------------------------------------------------*/
