/* windCmdAM.c -
 *
 *	This file contains Magic command routines for those commands
 *	that are valid in all windows.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/windows/windCmdAM.c,v 1.2 2008/12/11 04:20:15 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#include <sys/types.h>
#include <sys/times.h>
#include <sys/stat.h>
#include <errno.h>
#include <math.h>		/* for round() function */

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "textio/textio.h"
#include "utils/geometry.h"
#include "windows/windows.h"
#include "utils/malloc.h"
#include "utils/runstats.h"
#include "utils/macros.h"
#include "utils/signals.h"
#include "graphics/graphics.h"
#include "utils/styles.h"
#include "textio/txcommands.h"
#include "graphics/glyphs.h"
#include "windows/windInt.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "dbwind/dbwind.h"
#include "utils/utils.h"
#include "cif/cif.h"

/* Forward declarations */
void windDoMacro();

/*
 * ----------------------------------------------------------------------------
 *
 * windBorderCmd --
 *
 *	Change the flag which says whether new windows will have a border.
 *
 * Usage:
 *	windborder [on|off]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A flag is changed.
 *	In Tcl, if no options are presented, the border status is returned
 *	in the command exit status.
 *
 * ----------------------------------------------------------------------------
 */

void
windBorderCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    int place;
    bool value;
    static const char * const onoff[] = {"on", "off", 0};
    static const bool truth[] = {TRUE, FALSE};

    if (cmd->tx_argc > 2) goto usage;
    else if (cmd->tx_argc == 1)
    {
	if (w == (MagWindow *)NULL)
	{
	    TxError("No window specified for caption command\n");
	    goto usage;
	}

	value = (w->w_flags & WIND_BORDER) ? 0 : 1;

#ifdef MAGIC_WRAPPER
        /* this cast to remove const is ok, TCL_STATIC conveys the storage handling of this string */
	Tcl_SetResult(magicinterp, (char *)onoff[value], TCL_STATIC);
#else
	TxPrintf("Window border is %s\n", onoff[value]);
#endif
	return;
    }

    place = Lookup(cmd->tx_argv[1], onoff);
    if (place < 0)
    goto usage;

    if (truth[place])
    {
        WindDefaultFlags |= WIND_BORDER;
	TxPrintf("New windows will have a border.\n");
    }
    else {
        WindDefaultFlags &= ~WIND_BORDER;
	TxPrintf("New windows will not have a border.\n");
    }
    return;

    usage:
	TxError("Usage: %s [on|off]\n", cmd->tx_argv[0]);
	return;
}
/*
 * ----------------------------------------------------------------------------
 *
 * windCaptionCmd --
 *
 *	Change the flag which says whether new windows will have a title caption.
 *
 * Usage:
 *	windcaption [on|off]
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A flag is changed.
 *	In Tcl, if no options are presented, the window title caption is
 *	returned in the command status.
 *
 * ----------------------------------------------------------------------------
 */

void
windCaptionCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    int place;
    Rect ts;
    static const char * const onoff[] = {"on", "off", 0};
    static const bool truth[] = {TRUE, FALSE};

    if (cmd->tx_argc > 2) goto usage;
    else if (cmd->tx_argc == 1)
    {
	if (w == (MagWindow *)NULL)
	{
	    TxError("No window specified for caption command\n");
	    goto usage;
	}

#ifdef MAGIC_WRAPPER
	Tcl_SetResult(magicinterp, w->w_caption, TCL_STATIC);
#else
	TxPrintf("Window caption is \"%s\"\n", w->w_caption);
#endif
	return;
    }

    place = Lookup(cmd->tx_argv[1], onoff);
    if (place < 0)
    goto usage;

    if (truth[place])
    {
        WindDefaultFlags |= WIND_CAPTION;
	TxPrintf("New windows will have a title caption.\n");
    }
    else {
        WindDefaultFlags &= ~WIND_CAPTION;
	TxPrintf("New windows will not have a title caption.\n");
    }
    return;

    usage:
	TxError("Usage: %s [on|off]\n", cmd->tx_argv[0]);
	return;
}

/*
 * ----------------------------------------------------------------------------
 *
 * windCenterCmd --
 *
 * Implement the "center" command.
 * Move a window's view to center the point underneath the cursor, or to
 * the specified coordinate (in surface units).
 *
 * Usage:
 *	center [x y]
 *	center horizontal|vertical f
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The view in the window underneath the cursor is changed
 *	to center the point underneath the cursor.
 *
 * ----------------------------------------------------------------------------
 */

void
windCenterCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    Point rootPoint;
    Rect newArea, oldArea;

    if (w == NULL)
    {
	TxError("Point to a window first.\n");
	return;
    }

    if (cmd->tx_argc == 1)
    {
	if ((w->w_flags & WIND_SCROLLABLE) == 0)
	{
	    TxError("Sorry, can't scroll this window.\n");
	    return;
	}
	WindPointToSurface(w, &cmd->tx_p, &rootPoint, (Rect *) NULL);
    }
    else if (cmd->tx_argc == 3)
    {
	if ((w->w_flags & WIND_SCROLLABLE) == 0)
	{
	    TxError("Sorry, can't scroll this window.\n");
	    return;
	}
	if (cmd->tx_argv[1][0] == 'h' || cmd->tx_argv[1][0] == 'v')
	{
	    double frac;

	    if (!StrIsNumeric(cmd->tx_argv[2]))
	    {
		TxError("Must specify a fractional value.\n");
		return;
	    }
	    frac = atof(cmd->tx_argv[2]);
	    if (cmd->tx_argv[1][0] == 'h')
	    {
		rootPoint.p_y = 0;
		rootPoint.p_x = w->w_bbox->r_xbot + frac *
			(w->w_bbox->r_xtop - w->w_bbox->r_xbot) -
			(w->w_surfaceArea.r_xtop + w->w_surfaceArea.r_xbot)/2;
	    }
	    else
	    {
		rootPoint.p_x = 0;
		rootPoint.p_y = w->w_bbox->r_ybot + frac *
			(w->w_bbox->r_ytop - w->w_bbox->r_ybot) -
			(w->w_surfaceArea.r_ytop + w->w_surfaceArea.r_ybot)/2;
	    }
	    WindScroll(w, &rootPoint, (Point *)NULL);
	    return;
	}
        else
	{
	    if (!StrIsInt(cmd->tx_argv[1]) || !StrIsInt(cmd->tx_argv[2]))
	    {
		TxError("Coordinates must be integer values\n");
		return;
	    }
	    rootPoint.p_x = atoi(cmd->tx_argv[1]);
	    rootPoint.p_y = atoi(cmd->tx_argv[2]);
	}
    }
    else
    {
	TxError("Usage: center [x y]\n");
	TxError("       center horizontal|vertical f\n");
	return;
    }

    oldArea = w->w_surfaceArea;
    newArea.r_xbot = rootPoint.p_x - (oldArea.r_xtop - oldArea.r_xbot)/2;
    newArea.r_xtop = newArea.r_xbot - oldArea.r_xbot + oldArea.r_xtop;
    newArea.r_ybot = rootPoint.p_y - (oldArea.r_ytop - oldArea.r_ybot)/2;
    newArea.r_ytop = newArea.r_ybot - oldArea.r_ybot + oldArea.r_ytop;

    WindMove(w, &newArea);
}


/*
 * ----------------------------------------------------------------------------
 * windCloseCmd --
 *
 *	Close the window that is pointed at.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The window is closed, and the client is notified.  The client may
 *	refuse to have the window closed, in which case nothing happens.
 * ----------------------------------------------------------------------------
 */

void
windCloseCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    if ((cmd->tx_argc == 2) && GrWindowNamePtr)
    {
	char *mwname;

	for (w = windTopWindow; w != (MagWindow *)NULL; w = w->w_nextWindow)
	{
	    mwname = (*GrWindowNamePtr)(w);
	    if (!strcmp(mwname, cmd->tx_argv[1]))
		break;
	}
	if (w == NULL)
	{
	    TxError("Window named %s cannot be found\n", cmd->tx_argv[1]);
	    return;
	}
    }

    if (w == (MagWindow *) NULL)
    {
	TxError("Point to a window first\n");
	return;
    }
    if (!WindDelete(w))
    {
	TxError("Unable to close that window\n");
	return;
    }
}

#ifdef MAGIC_WRAPPER
/*
 * ----------------------------------------------------------------------------
 * windBypassCmd --
 *
 *	Run a magic command independently of the command line.  That is,
 *	if a command is being typed on the command line, the input
 *	redirection will not be reset by the execution of this command.
 *	To avoid having such commands interfere with the selection
 *	mechanism, save and restore the command count.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *
 *
 * ----------------------------------------------------------------------------
 */

void
windBypassCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    int saveCount;

    if (cmd->tx_argc == 1)
    {
	TxError("Usage:  *bypass <command>\n");
	return;
    }

    /* Dispatch the referenced command */
    saveCount = TxCommandNumber;
    TxTclDispatch((ClientData)w, cmd->tx_argc - 1, cmd->tx_argv + 1, FALSE);
    TxCommandNumber = saveCount;
    if (TxInputRedirect == TX_INPUT_PENDING_RESET)
	TxInputRedirect = TX_INPUT_REDIRECTED;
}

#endif	/* MAGIC_WRAPPER */

/*
 * ----------------------------------------------------------------------------
 * windCrashCmd --
 *
 *	Generate a core dump.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Dumps core by calling niceabort().
 *
 * ----------------------------------------------------------------------------
 */

void
windCrashCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    if (cmd->tx_argc != 1)
    {
	TxError("Usage:  *crash\n");
	return;
    }

    TxPrintf("OK -- crashing...\n");
    TxFlush();
    niceabort();
}


/*
 * ----------------------------------------------------------------------------
 * windCursorCmd --
 *
 *	Report the cursor position in Magic (internal) coordinates
 *	If an argument of a number is given, then the cursor icon
 *	is changed to the glyph of that number.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Prints coordinates (non-Tcl version)
 *	Return value set to the cursor position as a list (Tcl version)
 *
 * NOTE: "box position {*}[cursor]" will produce the wrong result if
 * "units" have been left as default, because "cursor" will generate
 * internal values and "box position" will expect lambda values.  Use
 * "box move bl cursor" instead.
 * ----------------------------------------------------------------------------
 */

#define CURSOR_INTERNAL	0
#define CURSOR_LAMBDA	1
#define CURSOR_USER	2
#define CURSOR_GRID	3
#define CURSOR_MICRONS	4
#define CURSOR_WINDOW	5
#define CURSOR_SCREEN	6

void
windCursorCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    Point p_in, p_out;
    int  resulttype, saveunits, idx;
    double cursx, cursy, oscale;
    char *dispx, *dispy;
    DBWclientRec *crec;

#ifdef MAGIC_WRAPPER
    Tcl_Obj *listxy;
#endif

    static const char * const cmdCursorOption[] =
	{ "internal", "lambda", "user", "grid", "microns", "window", "screen",
	  "units", 0 };

    /* The original behavior was to use internal
     * units by default.  This remains the case
     * unless units are set with the "units"
     * command, in which case units follow the
     * specified units.
     */
    if (DBWUnits == DBW_UNITS_DEFAULT)
	resulttype = DBW_UNITS_INTERNAL;
    else
	resulttype = DBWUnits;

    if (cmd->tx_argc == 2)
    {
	if (StrIsInt(cmd->tx_argv[1]))
	{
	    if (GrSetCursorPtr != NULL)
		(*GrSetCursorPtr)(atoi(cmd->tx_argv[1]));
	    return;
	}
	else {
	    idx = Lookup(cmd->tx_argv[1], cmdCursorOption);
	    switch (idx)
	    {
		case CURSOR_INTERNAL:
		    resulttype = DBW_UNITS_INTERNAL;
		    break;
		case CURSOR_LAMBDA:
		    resulttype = DBW_UNITS_LAMBDA;
		    break;
		case CURSOR_USER:
		case CURSOR_GRID:
		    resulttype = DBW_UNITS_USER;
		    break;
		case CURSOR_MICRONS:
		    resulttype = DBW_UNITS_MICRONS;
		    break;
		case CURSOR_WINDOW:
		    resulttype = -1;	// Use this value for "window"
		    break;
		case CURSOR_SCREEN:
		    resulttype = -2;	// Use this value for "screen"
		    break;
		default:
		    TxError("Usage: cursor glyphnum\n");
		    TxError(" (or): cursor [internal | lambda | microns | user"
				" | window | units]\n");
		    return;
	    }
	}
    }

    if (GrGetCursorPosPtr == NULL)
	return;

    if (resulttype == -2)
	GrGetCursorRootPos(w, &p_in);
    else
	GrGetCursorPos(w, &p_in);

    if (resulttype >= 0)
    {
	WindPointToSurface(w, &p_in, &p_out, (Rect *)NULL);

	/* Snap the cursor position if snap is in effect */
	if (DBWSnapToGrid != DBW_UNITS_INTERNAL)
	    ToolSnapToGrid(w, &p_out, (Rect *)NULL);
    }

    /* Transform the result to declared units with option "lambda" or "grid" */
    saveunits = DBWUnits;
    if (resulttype < 0)
    {
	/* Not really internal units, but that prints integer units verbatim */
	DBWUnits = DBW_UNITS_INTERNAL;
	cursx = (double)p_in.p_x;
	cursy = (double)p_in.p_y;
    }
    else
    {
	DBWUnits = resulttype;
	cursx = (double)p_out.p_x;
	cursy = (double)p_out.p_y;
    }

    dispx = DBWPrintValue(cursx, w, TRUE);
    dispy = DBWPrintValue(cursy, w, FALSE);
    DBWUnits = saveunits;

#ifdef MAGIC_WRAPPER
    listxy = Tcl_NewListObj(0, NULL);
    Tcl_ListObjAppendElement(magicinterp, listxy, Tcl_NewStringObj(dispx, -1));
    Tcl_ListObjAppendElement(magicinterp, listxy, Tcl_NewStringObj(dispy, -1));
    Tcl_SetObjResult(magicinterp, listxy);
#else
    TxPrintf("%s %s\n", dispx, dispy);
#endif
}

/*
 * ----------------------------------------------------------------------------
 * windDebugCmd --
 *
 *	Change to a new debugging mode.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
windDebugCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    if (cmd->tx_argc != 1) goto usage;
    windPrintCommands = !windPrintCommands;
    TxError("Window command debugging set to %s\n",
	(windPrintCommands ? "TRUE" : "FALSE"));
    return;

usage:
    TxError("Usage:  *winddebug\n");
}

/*
 * ----------------------------------------------------------------------------
 * windDumpCmd --
 *
 *	Dump out debugging info.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
windDumpCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    (void) windDump();
}

#ifndef MAGIC_WRAPPER

/*
 * ----------------------------------------------------------------------------
 *
 * windEchoCmd --
 *
 *	Echo the arguments
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Text may appear on the terminal
 *
 * ----------------------------------------------------------------------------
 */

void
windEchoCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    int i;
    bool newline = TRUE;

    for (i = 1; i < cmd->tx_argc; i++)
    {
	if (i != 1)
	    TxPrintf(" ");
	if ( (i == 1) && (strcmp(cmd->tx_argv[i], "-n") == 0) )
	   newline = FALSE;
	else
	    TxPrintf("%s", cmd->tx_argv[i]);
    }

    if (newline)
	TxPrintf("\n");
    TxFlush();
}

#endif


/*
 * ----------------------------------------------------------------------------
 *
 * windFilesCmd --
 *
 *	Find out what files are currently open.
 *
 * Usage:
 *	*files
 *
 * Side Effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */
 /*ARGSUSED*/

void
windFilesCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
#define NUM_FD	20	/* max number of open files per process */
    int fd;
    struct stat buf;
    int unopen, open;

    open = unopen = 0;
    for (fd = 0; fd < NUM_FD; fd++) {
	if (fstat(fd, &buf) != 0) {
	    if (errno == EBADF)
		unopen++;
	    else
		TxError("file descriptor %d: %s\n", fd, strerror(errno));
	}
	else {
	    char *type;
	    switch (buf.st_mode & S_IFMT) {
		case S_IFDIR: {type = "directory"; break;}
		case S_IFCHR: {type = "character special"; break;}
		case S_IFBLK: {type = "block special"; break;}
		case S_IFREG: {type = "regular"; break;}
		case S_IFLNK: {type = "symbolic link"; break;}
		case S_IFSOCK: {type = "socket"; break;}
		default: {type = "unknown"; break;}
	    }
	    TxError("file descriptor %d: open  (type: '%s', inode number %ld)\n",
		fd, type, buf.st_ino);
	    open++;
	}
    }
    TxError("%d open files, %d unopened file descriptors left\n", open, unopen);
}


/*
 * ----------------------------------------------------------------------------
 *
 * windGrowCmd --
 *
 *	Grow a window to full-screen size or back to previous size.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Text may appear on the terminal
 *
 * ----------------------------------------------------------------------------
 */

void
windGrowCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    if (w == NULL)
    {
	TxError("Point to a window first.\n");
	return;
    };

    WindFullScreen(w);
}


/*
 * ----------------------------------------------------------------------------
 * windGrstatsCmd --
 *
 *	Take statistics on the graphics code.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
windGrstatsCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    char *RunStats(), *rstatp;
    static struct tms tlast, tdelta;
    int i, style, count;
    int us;
    extern int GrNumClipBoxes;
    int usPerRect, rectsPerSec;

    if (cmd->tx_argc < 2 || cmd->tx_argc > 3)
    {
	TxError("Usage: grstats num [ style ]\n");
	return;
    }

    if (!StrIsInt(cmd->tx_argv[1]) ||
	(cmd->tx_argc == 3 && !StrIsInt(cmd->tx_argv[2])))
    {
	TxError("Count & style must be numeric\n");
	return;
    }
    if (w == (MagWindow *) NULL)
    {
	TxError("Point to a window first.\n");
	return;
    }

    count = atoi(cmd->tx_argv[1]);
    if (cmd->tx_argc == 3)
	style = atoi(cmd->tx_argv[2]);
    else
	style = -1;

    WindUpdate();

    if (style >= 0)
	GrLock(w, TRUE);

    (void) RunStats(RS_TINCR, &tlast, &tdelta);
    GrNumClipBoxes = 0;
    for (i = 0; i < count; i++)
    {
	if (SigInterruptPending)
	    break;
	if (style < 0)
	{
	    WindAreaChanged(w, (Rect *) NULL);
	    WindUpdate();
	}
	else
	{
	    Rect r;
#define GRSIZE	15
#define GRSPACE 20
	    r.r_xbot = w->w_screenArea.r_xbot -  GRSIZE/2;
	    r.r_ybot = w->w_screenArea.r_ybot -  GRSIZE/2;
	    r.r_xtop = r.r_xbot + GRSIZE - 1;
	    r.r_ytop = r.r_ybot + GRSIZE - 1;
	    GrClipBox(&w->w_screenArea, STYLE_ERASEALL);
	    GrSetStuff(style);
	    while (r.r_xbot <= w->w_screenArea.r_xtop)
	    {
		while (r.r_ybot <= w->w_screenArea.r_ytop)
		{
		    GrFastBox(&r);
		    r.r_ybot += GRSPACE;
		    r.r_ytop += GRSPACE;
		}
		r.r_xbot += GRSPACE;
		r.r_xtop += GRSPACE;
		r.r_ybot = w->w_screenArea.r_ybot -  GRSIZE/2;
		r.r_ytop = r.r_ybot + GRSIZE - 1;
	    }
	}
    }
    rstatp = RunStats(RS_TINCR, &tlast, &tdelta);

    us = tdelta.tms_utime * (1000000 / 60);
    usPerRect = us / MAX(1, GrNumClipBoxes);
    rectsPerSec = 1000000 / MAX(1, usPerRect);
    TxPrintf("[%s]\n%d rectangles, %d uS, %d uS/rectangle, %d rects/sec\n",
	rstatp, GrNumClipBoxes, us, usPerRect, rectsPerSec);

    if (style >= 0)
	GrUnlock(w);
}


/*
 * ----------------------------------------------------------------------------
 * windHelpCmd --
 *
 *	Just a dummy proc.  (Only for this particular, global, client)
 *	This is just here so that there is an entry in our help table!
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
windHelpCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    ASSERT(FALSE, windHelpCmd);
}

/*
 * ----------------------------------------------------------------------------
 * windLogCommandsCmd --
 *
 *	Log the commands and button pushes in a file.
 *
 * Syntax:
 *	logcommands start <filename>	Open and start a new command log file
 *	logcommands stop		End and close a command log file
 *	logcommands update		Refresh display after each log command
 *	logcommands suspend		Suspend logging of commands
 *	logcommands resume		Resume logging of commands
 *
 * Legacy syntax:
 *	logcommands <filename> [update]	Start a new command log file with updating
 *	logcommands			End and close a command log file
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

#define LOG_CMD_START	0	// Create a new log file (default behavior)
#define LOG_CMD_STOP	1	// End a log file and stop logging.
#define LOG_CMD_UPDATE	2	// Update display after every logged command.
#define LOG_CMD_SUSPEND	3	// Suspend command logging.
#define LOG_CMD_RESUME 	4	// Resume command logging.

void
windLogCommandsCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    char *fileName = NULL;
    unsigned char flags = 0;
    int idx = LOG_CMD_STOP;

    static const char * const logKeywords[] = {"start", "stop", "update", "suspend", "resume", 0};

    if ((cmd->tx_argc < 1) || (cmd->tx_argc > 3)) goto usage;

    if (cmd->tx_argc > 1)
    {
	idx = Lookup(cmd->tx_argv[1], logKeywords);
	if (idx < 0) fileName = cmd->tx_argv[1];
    }

    if (cmd->tx_argc == 3)
    {
	int i;

	if (fileName == NULL)
	    fileName = cmd->tx_argv[2];
	else
	{
	    /* Legacy behavior:  Allow "logcommands <filename> update"
	     * to mean "logcommands start <filename> ; logcommands update"
	     */
	    i = Lookup(cmd->tx_argv[2], logKeywords);
	    if (i != LOG_CMD_UPDATE) goto usage;
	    flags = TX_LOG_UPDATE;
	    idx = LOG_CMD_START;
	}
    }

    switch (idx)
    {
	case LOG_CMD_START:
	    if (fileName == NULL) break;
	    TxLogStart(fileName, w);
	    if (flags & TX_LOG_UPDATE)
	    	TxLogUpdate();
	    return;
	case LOG_CMD_STOP:
	    TxLogStop();
	    return;
	case LOG_CMD_UPDATE:
	    TxLogUpdate();
	    return;
	case LOG_CMD_SUSPEND:
	    TxLogSuspend();
	    return;
	case LOG_CMD_RESUME:
	    TxLogResume();
	    return;
    }	

usage:
    TxError("Usage: %s [start|stop|update|suspend|resume [filename]]\n",
		cmd->tx_argv[0]);
}

/*
 * ----------------------------------------------------------------------------
 *
 * windIntMacroCmd --
 *
 *	Define a new interactive macro.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Calls windDoMacro.
 *
 * ----------------------------------------------------------------------------
 */

void
windIntMacroCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    windDoMacro(w, cmd, TRUE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * windMacroCmd --
 *
 *	Define a new macro.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Calls windDoMacro
 *
 * ----------------------------------------------------------------------------
 */

void
windMacroCmd(w, cmd)
    MagWindow *w;
    TxCommand *cmd;
{
    windDoMacro(w, cmd, FALSE);
}

/*
 * ----------------------------------------------------------------------------
 *
 * windDoMacro --
 *
 *	Working function for CmdIntMacro and CmdMacro
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Causes the macro package to define a new macro.
 *
 * ----------------------------------------------------------------------------
 */

void
windDoMacro(w, cmd, interactive)
    MagWindow *w;
    TxCommand *cmd;
    bool interactive;
{
    char *cp, *cn;
    char nulltext[] = "";
    char ch;
    int ct, argstart, verbose;
    bool any, iReturn;
    bool do_list = FALSE;
    bool do_help = FALSE;
    bool do_reverse = FALSE;
    char *searchterm = NULL;
    char *clientName = NULL;
    macrodef *cMacro;
    HashTable *clienttable;
    HashEntry *h;
    HashSearch hs;
    WindClient wc;

    /* If the first argument is a window name, we attempt to	*/
    /* retrieve a client ID from it.  This overrides the actual	*/
    /* window the command was called from, so technically we	*/
    /* can define macros for clients from inside other clients.	*/
    /* The main use, though, is to define macros for a client	*/
    /* from a script, rc file, or command-line.			*/
    /* Default to the layout window if the command has no	*/
    /* associated window.					*/

    argstart = 1;
    if (cmd->tx_argc == 1)
	wc = DBWclientID;	/* Default client */
    else if (cmd->tx_argc > 1)
    {
	wc = WindGetClient(cmd->tx_argv[1], TRUE);
	if (wc != NULL)
	{
	    clientName = cmd->tx_argv[1];
	    argstart++;
	}
	else
	{
	    /* Check if argument is a known layout button handler */
	    if (DBWButtonHandlerIndex(cmd->tx_argv[1]) != -1)
	    {
		clientName = cmd->tx_argv[1];
		argstart++;
	    }
	}
    }

    while (cmd->tx_argc > argstart)
    {
	if (!strcmp(cmd->tx_argv[argstart], "list"))
	{
	    do_list = TRUE;
	    argstart++;
	}
	else if (!strcmp(cmd->tx_argv[argstart], "help"))
	{
	    do_help = TRUE;
	    argstart++;
	}
	else if (!strcmp(cmd->tx_argv[argstart], "search"))
	{
	    if (cmd->tx_argc > (argstart + 1))
	    {
		argstart++;
		searchterm = cmd->tx_argv[argstart];
	    }
	    argstart++;
	}
	else if (!strcmp(cmd->tx_argv[argstart], "-reverse"))
	{
	    do_reverse = TRUE;
	    argstart++;
	}
	else if (!strcmp(cmd->tx_argv[argstart], "copy"))
	{
	    if (cmd->tx_argc > (argstart + 1))
	    {
		argstart++;
		if (wc == (WindClient)NULL)
		{
		    if (w != NULL)
			wc = w->w_client;
		    else
			wc = DBWclientID;
		}
		MacroCopy(wc, cmd->tx_argv[argstart]);

		/* If tool name did not previously exist, then add
		 * it to the list of known tool names, so that it
		 * can be found later by the "macro" command.
		 */
		if (DBWButtonHandlerIndex(cmd->tx_argv[argstart]) == -1)
		    DBWAddButtonHandler(cmd->tx_argv[argstart],	
				(const cb_database_buttonhandler_t)NULL,
				0, (const char *)NULL);
	    }
	    return;
	}
	else break;
    }

    /* If client wasn't specified, use window default, else use	*/
    /* DBW client.						*/

    if (wc == (WindClient)NULL)
    {
	if (w != NULL)
	    wc = w->w_client;
	else
	    wc = DBWclientID;

	if (cmd->tx_argc > (argstart + 1))
	{
	    /* The first argument, if there is one after resolving	*/
	    /* all of the optional arugments, should be a key.		*/
	    /* If it doesn't look like one, then check if the		*/
	    /* next argument looks like a key, which would indicate	*/
	    /* an unregistered client as the first argument.  A		*/
	    /* macro retrieved from an unregistered client returns	*/
	    /* nothing but does not generate an error.  This allows	*/
	    /* the default macro set to declare macros for, e.g., the	*/
	    /* wind3d client and fail quietly if magic was compiled	*/
	    /* without OpenGL support.					*/

	    if (MacroKey(cmd->tx_argv[argstart], &verbose) == 0)
		if (MacroKey(cmd->tx_argv[argstart + 1], &verbose) != 0)
		    return;
	}
    }

    /* If a clientName wasn't given, but wc is DBWclientID, then get
     * the clientName from the default button handler.
     */

    if ((clientName == NULL) && (wc == DBWclientID))
	clientName = DBWGetButtonHandler();

    if (cmd->tx_argc == argstart)
    {
	if (clientName == NULL)
	    h = NULL;
	else
	    h = HashLookOnly(&MacroClients, (char *)clientName);

	if (h == NULL)
	{
#ifdef MAGIC_WRAPPER
	    Tcl_Obj *lobj;
	    lobj = Tcl_NewListObj(0, NULL);
#endif
	    TxError("Cannot get macro list from current window.\n");
#ifndef MAGIC_WRAPPER
	    TxError("List of known macro clients:\n");
#endif
	    /* If clientName was not in MacroClients, then what is? */
	    HashStartSearch(&hs);
	    while ((h = HashNext(&MacroClients, &hs)) != NULL)
	    {
		char *clientName = h->h_key.h_name;
#ifdef MAGIC_WRAPPER
		Tcl_ListObjAppendElement(magicinterp, lobj,
				Tcl_NewStringObj(clientName, -1));
#else
		TxError("%s ", clientName);
#endif
	    }
#ifdef MAGIC_WRAPPER
	    Tcl_SetObjResult(magicinterp, lobj);
#else
	    TxError("\n");
#endif
	    return;
	}
	else
	{
	    clienttable = (HashTable *)HashGetValue(h);
	    if (clienttable == (HashTable *)NULL)
	    {
		TxError("No such client.\n");
		return;
	    }
	}

	cn = NULL;
	any = FALSE;
	ch = 0;
	HashStartSearch(&hs);
	while (TRUE)
	{
	    if (cn)
	    {
		/* this loop uses 'continue' after 'cn' was assigned from malloc below, so
		 *  this ensures it is freed up at the start of the next iteration.
		 */
		freeMagic(cn);
		cn = NULL;
	    }
	    h = HashNext(clienttable, &hs);
	    if (h == NULL) break;
	    cMacro = (macrodef *) HashGetValue(h);
	    if (cMacro == (macrodef *)NULL) break;
	    cn = MacroName((spointertype)h->h_key.h_ptr);

	    /* "imacro list" returns only interactive macros. */
	    if (interactive && !cMacro->interactive) continue;

	    if (do_help)
		cp = (cMacro->helptext == NULL) ? cMacro->macrotext : cMacro->helptext;
	    else
		cp = cMacro->macrotext;

	    if (cp == (char *)NULL) cp = (char *)(&nulltext[0]);

	    if (searchterm != NULL)
	    {
		/* Refine results by keyword search */
		if (!strstr(cp, searchterm)) continue;
	    }

	    if (do_list)
	    {
#ifdef MAGIC_WRAPPER
		// NOTE:  Putting cp before cn makes it easier to
		// generate a reverse lookup hash table for matching
		// against menu items, to automatically generate
		// the "accelerator" text.
		if (do_reverse)
		    Tcl_AppendElement(magicinterp, cp);
		Tcl_AppendElement(magicinterp, cn);
		if (!do_reverse)
		    Tcl_AppendElement(magicinterp, cp);
#else
		TxPrintf("%s = \"%s\"\n", cn, cp);
#endif
	    }
	    else
	    {
	        if (cMacro->interactive)
		    TxPrintf("Interactive macro '%s' %s \"%s\"\n",
			     cn, (do_help) ? "" : "contains", cp);
		else
		    TxPrintf("Macro '%s' %s \"%s\"\n",
			     cn, (do_help) ? "" : "contains", cp);
	    }
	    any = TRUE;
	}
	if (!any)
	{
	    if (!do_list) TxPrintf("No macros are defined for this client.\n");
	}
	return;
    }
    else if (cmd->tx_argc == (argstart + 1))
    {
	ct = MacroKey(cmd->tx_argv[argstart], &verbose);
	if (ct == 0)
	{
	    if (verbose)
		TxError("Unrecognized macro name %s\n", cmd->tx_argv[argstart]);
	    return;
	}
	if (do_help)
	    cp = MacroRetrieveHelp(wc, ct);
	else
	    cp = MacroRetrieve(wc, ct, &iReturn);
	if (cp != NULL)
	{
	    cn = MacroName(ct);
	    if (do_list)
	    {
#ifdef MAGIC_WRAPPER
		Tcl_SetResult(magicinterp, cp, TCL_VOLATILE);
#else
		TxPrintf("%s\n", cp);
#endif
	    }
	    else
	    {
		if (iReturn)
		{
		    TxPrintf("Interactive macro '%s' contains \"%s\"\n",
			 	cn, cp);
		}
		else
		{
		    TxPrintf("Macro '%s' contains \"%s\"\n",
				cn, cp);
		}
	    }
	    freeMagic(cp);
	    freeMagic(cn);
	}
	return;
    }
    else if (cmd->tx_argc == (argstart + 2))
    {
	int verbose;
	ct = MacroKey(cmd->tx_argv[argstart], &verbose);
	if (ct == 0)
	{
	    if (verbose)
		TxError("Unrecognized macro name %s\n", cmd->tx_argv[argstart]);
	    return;
	}
	argstart++;
	if (cmd->tx_argv[argstart][0] == '\0') MacroDelete(wc, ct);
	else if (do_help)
	    MacroDefineHelp(wc, ct, cmd->tx_argv[argstart]);
	else if (interactive)
	    MacroDefine(wc, ct, cmd->tx_argv[argstart], NULL, TRUE);
	else
	    MacroDefine(wc, ct, cmd->tx_argv[argstart], NULL, FALSE);
	return;
    }
    else if (cmd->tx_argc == (argstart + 3))
    {
	int verbose;
	ct = MacroKey(cmd->tx_argv[argstart], &verbose);
	if (ct == 0)
	{
	    if (verbose)
		TxError("Unrecognized macro name %s\n", cmd->tx_argv[argstart]);
	    return;
	}
	argstart++;
	if (cmd->tx_argv[argstart][0] == '\0') MacroDelete(wc, ct);
	else if (interactive) MacroDefine(wc, ct, cmd->tx_argv[argstart],
		cmd->tx_argv[argstart + 1], TRUE);
	else MacroDefine(wc, ct, cmd->tx_argv[argstart],
		cmd->tx_argv[argstart + 1], FALSE);
	return;
    }

    TxError("Usage: %s [client] [macro_name [string] [help_text]]\n", cmd->tx_argv[0]);
}
