/* NMlabel.c -
 *
 *	This file contains routines that handle the label portion
 *	of net-list menus.  The routines do things like reading
 *	labels from the terminal, placing labels in the design,
 *	re-orienting label text, and incrementing numbers inside
 *	of labels.
 *
 *     ********************************************************************* 
 *     * 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[] = "$Header: /ufs/repository/magic/netmenu/NMlabel.c,v 1.2 2001/01/12 22:13:07 jsolomon Exp $";
#endif  not lint

#include <stdio.h>
#include <ctype.h>
#include "misc/magic.h"
#include "utils/geometry.h"
#include "utils/utils.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "windows/windows.h"
#include "main/main.h"
#include "dbwind/dbwind.h"
#include "netmenu/nmInt.h"
#include "textio/textio.h"
#include "textio/txcommands.h"
#include "commands/commands.h"
#include "misc/styles.h"
#include "utils/malloc.h"

/* The user can enter several labels at once, so he doesn't have
 * to keep going back and forth between the mouse and the keyboard.
 * We store the labels in a big array.  At any given time, only one
 * of them is visible in the menu;  only it can be placed in the
 * design.  There are menu buttons for going to the next or previous
 * label.
 */

#define MAXLABELS 100
char * nmLabelArray[MAXLABELS];	/* Holds pointers to all labels
				 * entered together.
				 */
int nmCurLabel;			/* Index of current label. */
int nmNum1 = -1;		/* First two numbers in current label. */
int nmNum2 = -1;
char nmNum1String[12];		/* String equivalents of nmNum1 and nmNum2. */
char nmNum2String[12];


/*
 * ----------------------------------------------------------------------------
 *
 * nmGetNums --
 *
 * 	Picks one or two positive decimal numbers out of a string of text.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The string is scanned for positive decimal numbers.  If any are
 *	found, the first one is put in num1 and the second one is put in
 *	num2.  If there aren't two numbers, num1 and/or num2 will be
 *	set to -1.  This routine ignores "-" signs, so the numbers are
 *	always positive.
 *
 * ----------------------------------------------------------------------------
 */

void
nmGetNums(string, num1, num2)
    char *string;		/* String to scan. */
    int *num1, *num2;		/* Pointers to places to store numbers. */
{
    register char *p;
    bool gotNum = FALSE;
    bool gotDigit = FALSE;
    int num = 0;

    *num1 = *num2 = -1;
    for (p = string; ; p++)
    {
	if (isdigit(*p))
	{
	    num = (10*num) + *p - '0';
	    gotDigit = TRUE;
	}
	else if (gotDigit)
	{
	    if (gotNum)
	    {
		*num2 = num;
		return;
	    }
	    *num1 = num;
	    gotDigit = FALSE;
	    gotNum = TRUE;
	    num = 0;
	}
	if (*p == 0) return;
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * nmPutNums --
 *
 * 	This routine generates a new string by replaces the first two
 *	numbers in an old string with new numbers.
 *
 * Results:
 *	The return value is a pointer to a string that is identical
 *	to src, except that if there are decimal numbers in src, the
 *	first two of those are replaced with the string equivalents
 *	of num1 and num2.  For example, if src is "bus[14,22]" and
 *	num1 is 3 and num2 is 177, the return value will be "bus[3,177]".
 *	Note:  the result is a statically allocated string, so the
 *	caller should copy it before calling this routine again.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

char *
nmPutNums(src, num1, num2)
    char *src;			/* String on which substitution is
				 * to be done. */
    int num1, num2;		/* Numbers to substitute. A number less
				 * than zero means keep the old value.
				 */

{
    static char *result = NULL;
    static int resultLength = 0;
    char num1String[12], num2String[12];
    int spaceNeeded;
    char *pSrc, *pResult;

    /* First, translate the numbers into strings.  Then, make sure
     * we have enough space to store the result.  Allocate a new
     * result area if there isn't enough space.  The space computation
     * is a bit conservative, but simpler that way.
     */
    
    (void) sprintf(num1String, "%d", num1);
    (void) sprintf(num2String, "%d", num2);
    spaceNeeded = strlen(num1String) + strlen(num2String) + strlen(src) + 1;
    if (resultLength < spaceNeeded)
    {
	if (result != NULL) freeMagic(result);
	result = mallocMagic((unsigned) spaceNeeded);
	resultLength = spaceNeeded;
    }

    /* Now scan through the source string.  Copy everything up
     * to the first number into the result.
     */
    
    pSrc = src;
    pResult = result;
    while (!isdigit(*pSrc))
	if ((*pResult++ = *pSrc++) == 0) return result;

    /* Now copy num1String into result and skip the number in
     * the original string.  Or, if num1 is less than zero,
     * then just copy the number from src.
     */
    
    if (num1 < 0)
    {
	while (isdigit(*pSrc)) *pResult++ = *pSrc++;
    }
    else
    {
	while (isdigit(*pSrc)) pSrc += 1;
	(void) strcpy(pResult, num1String);
	while (isdigit(*pResult)) pResult += 1;
    }

    /* Copy more non-digits from source to destination. */

    while (!isdigit(*pSrc))
	if ((*pResult++ = *pSrc++) == 0) return result;
    
    /* Copy the second number. */

    if (num2 < 0)
    {
	while (isdigit(*pSrc)) *pResult++ = *pSrc++;
    }
    else
    {
	while (isdigit(*pSrc)) pSrc += 1;
	(void) strcpy(pResult, num2String);
	while (isdigit(*pResult)) pResult += 1;
    }

    /* Copy the rest of the source to the destination. */

    while (TRUE)
	if ((*pResult++ = *pSrc++) == 0) return result;
}

/*
 * ----------------------------------------------------------------------------
 *
 * nmSetCurrentLabel --
 *
 * 	This is a utility procedure called when the current label changes.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Number information is extracted from the label, and the menu
 *	is redisplayed.
 *
 * ----------------------------------------------------------------------------
 */

void
nmSetCurrentLabel()
{
    nmGetNums(nmLabelArray[nmCurLabel], &nmNum1, &nmNum2);
    if (nmNum1 >= 0) (void) sprintf(nmNum1String, "%d", nmNum1);
    else nmNum1String[0] = 0;
    if (nmNum2 >= 0) (void) sprintf(nmNum2String, "%d", nmNum2);
    else nmNum2String[0] = 0;

    /* Set up the menu buttons to refer to this information, and
     * redisplay relevant stuff.
     */
    
    NMLabelButton.nmb_text = nmLabelArray[nmCurLabel];
    NMNum1Button.nmb_text = nmNum1String;
    NMNum2Button.nmb_text = nmNum2String;
    if (NMWindow != NULL)
    {
	(void) NMredisplay(NMWindow, &NMLabelButton.nmb_area, (Rect *) NULL);
	(void) NMredisplay(NMWindow, &NMNum1Button.nmb_area, (Rect *) NULL);
	(void) NMredisplay(NMWindow, &NMNum2Button.nmb_area, (Rect *) NULL);
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMGetLabels --
 *
 * 	Reads in a bunch of labels from the terminal, and makes the
 *	first of them "current".
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The old group of labels is re-allocated, and replaced by a new
 *	group read from the terminal.  The first of the new group is
 *	made the current label in the net-list menu.
 *
 * ----------------------------------------------------------------------------
 */

Void
NMGetLabels()
{
#define MAXLENGTH 200
    char line[MAXLENGTH];		/* Holds label temporarily. */
    int i;

    TxPrintf("Enter labels, one per line, terminated by a blank line:\n");
    for (i = 0; i < MAXLABELS; i++)
    {
	if (TxGetLine(line, MAXLENGTH) == NULL) line[0] = 0;
	if (line[0] == 0)
	{
	    /* All done.  If we got any labels at all, null out all
	     * the remaining old labels.
	     */
	    
	    if (i == 0)
	    {
		TxPrintf("No new labels given, so I'll keep the old ones.\n");
		return;
	    }
	    for ( ; i < MAXLABELS; i++)
		(void) StrDup(&(nmLabelArray[i]), (char *) NULL);
	    break;
	}
	(void) StrDup(&(nmLabelArray[i]), line);
    }

    /* Make the first label current, and extract its two numbers. */

    nmCurLabel = 0;
    nmSetCurrentLabel();
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMNextLabel and NMPrevLabel --
 *
 * 	These procedures are invoked in response to button pushes
 *	over the label button.  They make the next or previous label
 *	in the list to be the current one.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The current label is changed.
 *
 * ----------------------------------------------------------------------------
 */

Void
NMNextLabel()
{
    if (nmLabelArray[nmCurLabel] == NULL)
    {
	TxError("Use the left button to enter labels first.\n");
	return;
    }
    if ((nmCurLabel == MAXLABELS-1) || (nmLabelArray[nmCurLabel+1] == NULL))
	nmCurLabel = 0;
    else nmCurLabel += 1;
    nmSetCurrentLabel();
}

Void
NMPrevLabel()
{
    if (nmLabelArray[nmCurLabel] == NULL)
    {
	TxError("Use the left button to enter labels first.\n");
	return;
    }
    if (nmCurLabel == 0)
    {
	for (nmCurLabel = MAXLABELS-1; nmLabelArray[nmCurLabel] == NULL;
	    nmCurLabel -= 1) /* null loop body */;
    }
    else nmCurLabel -= 1;
    nmSetCurrentLabel();
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMChangeNum --
 *
 * 	This procedure increments or decrements one of the two numbers
 *	we've extracted from the current label.  It is called in response
 *	to a button push inside one of the number buttons.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	One of the numbers is incremented or decremented, and the label
 *	information gets redisplayed.
 *
 * ----------------------------------------------------------------------------
 */

	/* ARGSUSED */
Void
NMChangeNum(window,cmd, nmButton, point)
    MagWindow *window;		/* Window in which button was pushed. */
    NetButton *nmButton;	/* NetButton where button was pushed. */
    TxCommand *cmd;		/* Exact command invoked. */
    Point *point;		/* Cursor position in surface coords. */

{
    int *pNum;

    /* Figure out which number is involved. */

    if (nmButton == &NMNum1Button) pNum = &nmNum1;
    else pNum = &nmNum2;

    /* Increment or decrement the number. */

    if (*pNum < 0)
    {
	TxError("That number doesn't exist!\n");
	return;
    }
    if (cmd->tx_button != TX_LEFT_BUTTON) *pNum += 1;
    else
    {
	if (*pNum == 0)
	{
	    TxError("Can't decrement past zero.\n");
	    return;
	}
	*pNum -= 1;
    }

    /* Fill in the new numbers in the string, and update the screen. */

    (void) StrDup(&(nmLabelArray[nmCurLabel]),
	nmPutNums(nmLabelArray[nmCurLabel], nmNum1, nmNum2));
    nmSetCurrentLabel();
}

/*
 * ----------------------------------------------------------------------------
 *
 * nmGetPos --
 *
 * 	This is a utility procedure to determine which label position
 *	was selected on a button push.
 *
 * Results:
 *	The return value is a label position.  This is determined by
 *	dividing the button area into 9 sectors and using the sector
 *	number to select a label position.  The position is transformed
 *	into edit cell coordinates before returning.
 *
 * Side effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

int
nmGetPos(button, point)
    NetButton *button;		/* Button where the mouse was clicked. */
    Point *point;		/* Exact surface position within the button. */
{
    /* The following table maps from sectors to label positions. */

    static int pos[] =
    {
	GEO_SOUTHWEST, 	GEO_SOUTH,	GEO_SOUTHEAST,
	GEO_WEST,	GEO_CENTER,	GEO_EAST,
	GEO_NORTHWEST,	GEO_NORTH,	GEO_NORTHEAST
    };
    int x, y, tmp;

    /* Divide the button area into thirds in x and y, and figure out
     * which sector contains the point.
     */
    
    tmp = (button->nmb_area.r_xtop - button->nmb_area.r_xbot + 1)/3;
    if (point->p_x <= button->nmb_area.r_xbot + tmp) x = 0;
    else if (point->p_x >= button->nmb_area.r_xtop - tmp) x = 2;
    else x = 1;
    tmp = (button->nmb_area.r_ytop - button->nmb_area.r_ybot + 1)/3;
    if (point->p_y <= button->nmb_area.r_ybot + tmp) y = 0;
    else if (point->p_y >= button->nmb_area.r_ytop - tmp) y = 2;
    else y = 1;
    return GeoTransPos(&RootToEditTransform, pos[3*y + x]);
}


/*
 * ----------------------------------------------------------------------------
 *
 * NMPutLabel --
 *
 * 	This procedure is invoked when the left button is pushed over
 *	the button for placing labels.  It places the current label
 *	in the design at the position of the box, and with an orientation
 *	determined by the place where the button was pushed.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	A label is added to the edit cell.
 *
 * ----------------------------------------------------------------------------
 */

	/* ARGSUSED */
Void
NMPutLabel(window, cmd, nmButton, point)
    MagWindow *window;		/* Window where mouse button was pushed.
				 * (ignored) */
    NetButton *nmButton;	/* Menu button activated. */
    TxCommand *cmd;		/* Complete info about command. (ignored) */
    Point *point;		/* Cursor position in surface coords. */
{
    int pos;
    char *text;

    text = nmLabelArray[nmCurLabel];
    if ((text == NULL) || (*text == 0))
    {
	TxError("Enter some text first (left-button the label entry).\n");
	return;
    }

    pos = nmGetPos(nmButton, point);
    CmdLabelProc(text, pos, -1);
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMReOrientLabel --
 *
 * 	This procedure is invoked by button pushes inside the net-list
 *	menu.  It resets the text positions of all labels touching the
 *	box.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Label positions are changed.
 *
 * ----------------------------------------------------------------------------
 */

	/* ARGSUSED */
Void
NMReOrientLabel(window, cmd, nmButton, point)
    MagWindow *window;		/* Window that was buttoned. (ignored) */
    NetButton *nmButton;	/* Button that was clicked. */
    TxCommand *cmd;		/* Detailed info on command (ignored). */
    Point *point;		/* Cursor position in surface coords. */
{
    int pos;
    Rect editArea;

    /* Make sure that the box exists. */

    if (!ToolGetEditBox(&editArea)) return;
    pos = nmGetPos(nmButton, point);
    DBReOrientLabel(EditCellUse->cu_def, &editArea, pos);
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMFindLabel --
 *
 * 	Use the current label as a search pattern and create feedback
 *	areas for all instances of labels with that pattern.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	New feedback areas get created.
 *
 * ----------------------------------------------------------------------------
 */

Void
NMFindLabel()
{
    char *pattern;

    pattern = nmLabelArray[nmCurLabel];
    if ((pattern == NULL) || (*pattern == 0))
    {
	TxError("Enter some text first (left-button the label entry).\n");
	return;
    }
    NMShowLabel(pattern, (TileTypeBitMask *) NULL);
}

/*
 * ----------------------------------------------------------------------------
 *
 * NMShowLabel --
 *
 * 	This procedure creates feedback for each instance of a label
 *	matching pattern that lies underneath the box.  This procedure
 *	looks at all cells in the hierarchy, expanded or not.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Feedback areas are created and displayed.
 *
 * ----------------------------------------------------------------------------
 */

void
NMShowLabel(pattern, pMask)
    char *pattern;			/* Pattern to be searched for. */
    TileTypeBitMask *pMask;
{
    SearchContext scx;
    MagWindow *w;
    int nmlLabelFunc();

    w = ToolGetBoxWindow(&scx.scx_area, (int *) NULL);
    if (w == NULL)
    {
	TxError("There's no box!  Please use the box to select\n");
	TxError("the area to search for a label match.\n");
	return;
    }

    scx.scx_use = (CellUse *) w->w_surfaceID;
    scx.scx_trans = GeoIdentityTransform;
    if (pMask == NULL)
	pMask = &DBAllTypeBits;

    (void) DBSearchLabel(&scx, pMask, 0, pattern,
	nmlLabelFunc, (ClientData) scx.scx_use->cu_def);
}

	/* ARGSUSED */
int
nmlLabelFunc(scx, label, tpath, rootDef)
    SearchContext *scx;			/* Describes state of search. */
    Label *label;			/* Pointer to label found. */
    TerminalPath *tpath;		/* Not used. */
    CellDef *rootDef;			/* Root def for search. */
{
    char mesg[2048];
    Rect rootArea;
    int left;

    /* Finish generating the full hierarchical name */
    left = tpath->tp_last - tpath->tp_next - 1;
    (void) strncpy(tpath->tp_next, label->lab_text, left);
    tpath->tp_next[left] = '\0';
    (void) sprintf(mesg, "%s;%s",
	DBTypeShortName(label->lab_type), tpath->tp_first);

    GeoTransRect(&scx->scx_trans, &label->lab_rect, &rootArea);
    GEO_EXPAND(&rootArea, 1, &rootArea);
    DBWFeedbackAdd(&rootArea, mesg, rootDef, 1,
	STYLE_PALEHIGHLIGHTS);
    return 0;
}
