1 /*
2  * CmdSubrs.c --
3  *
4  * The functions in this file are local to the commands module
5  * and not intended to be used by its clients.
6  *
7  *     *********************************************************************
8  *     * Copyright (C) 1985, 1990 Regents of the University of California. *
9  *     * Permission to use, copy, modify, and distribute this              *
10  *     * software and its documentation for any purpose and without        *
11  *     * fee is hereby granted, provided that the above copyright          *
12  *     * notice appear in all copies.  The University of California        *
13  *     * makes no representations about the suitability of this            *
14  *     * software for any purpose.  It is provided "as is" without         *
15  *     * express or implied warranty.  Export of this software outside     *
16  *     * of the United States of America may require an export license.    *
17  *     *********************************************************************
18  */
19 
20 #ifndef lint
21 static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/commands/CmdSubrs.c,v 1.2 2010/06/24 12:37:15 tim Exp $";
22 #endif  /* not lint */
23 
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <ctype.h>
28 #include <math.h>		/* For round() function */
29 
30 #include "utils/magic.h"
31 #include "utils/geometry.h"
32 #include "utils/utils.h"
33 #include "database/database.h"
34 #include "tiles/tile.h"
35 #include "utils/hash.h"
36 #include "utils/malloc.h"
37 #include "windows/windows.h"
38 #include "dbwind/dbwind.h"
39 #include "utils/main.h"
40 #include "commands/commands.h"
41 #include "textio/textio.h"
42 #include "cif/cif.h"
43 #include "drc/drc.h"
44 #include "textio/txcommands.h"
45 #include "utils/undo.h"
46 #include "utils/macros.h"
47 #include "sim/sim.h"
48 #include "select/select.h"
49 #ifdef SCHEME_INTERPRETER
50 #include "lisp/lisp.h"
51 #endif
52 
53 /* Forward declarations */
54 
55 extern char *cmdCheckNewName();
56 extern int cmdSaveWindSet();
57 extern void CmdSetWindCaption();
58 
59 TileTypeBitMask CmdYMLabel;
60 TileTypeBitMask CmdYMCell;
61 TileTypeBitMask CmdYMAllButSpace;
62 
63 /*
64  * ----------------------------------------------------------------------------
65  *
66  * cmdScaleCoord --
67  *
68  * Replaces the use of atoi() in command parsing of coordinates.  Allows
69  * coordinates to be declared in internal units, lambda, user units, or
70  * microns, and translates to internal units accordingly.  It also allows
71  * coordinates to be specified in floating-point.
72  *    A suffix of "i" indicates internal units, a suffix of "l" indicates
73  * lambda, a suffix of "g" indicates the user grid, and a suffix in metric
74  * notation ("nm", "um", "mm", "cm") indicates natural units.  Other valid
75  * units are "cu" or "centimicrons" for centimicrons, or "microns" for um.
76  *    Units without any suffix are assumed to be in lambda if "snap"
77  * (DBWSnapToGrid) is set to lambda, grid units if "snap" is set to the
78  * user grid, and internal units otherwise.
79  *
80  *    MagWindow argument w is used only with grid-based snapping, to find
81  * the value of the grid for the given window.  In this case, because the
82  * grid specifies an offset, "is_relative" specifies whether the given
83  * coordinate is a relative measurement (ignore offset) or an absolute
84  * position.  Because the grid can be different in X and Y, "is_x"
85  * specifies whether the given distance is in the X (TRUE) or Y (FALSE)
86  * direction.
87  *
88  *    This is the "general-purpose" routine, taking a value "scale" so
89  * that returned units can be a fraction of internal units, e.g., to
90  * represent wire centerlines (scale = 2) or highlights (any scale).
91  *
92  * Results:
93  *      Integer representing the given coordinate in internal units,
94  *	multiplied up by "scale".
95  *
96  * Side effects:
97  *	None
98  *
99  * ----------------------------------------------------------------------------
100  */
101 
102 int
cmdScaleCoord(w,arg,is_relative,is_x,scale)103 cmdScaleCoord(w, arg, is_relative, is_x, scale)
104     MagWindow *w;
105     char *arg;
106     bool is_relative, is_x;
107     int scale;
108 {
109     char *endptr;
110     double dval = 0;
111     int mscale = 1;
112     DBWclientRec *crec;
113 
114     if (*arg == '{') arg++;
115     while (isspace(*arg)) arg++;
116 
117     dval = strtod(arg, &endptr);
118     dval *= (double)scale;
119 
120     if (endptr == arg)
121     {
122         /* strtod() error condition */
123 	TxError("Coordinate value cannot be parsed:  assuming 0\n");
124 	return 0;
125     }
126 
127     else if ((*endptr == 'l')
128 		|| ((*endptr == '\0') && (DBWSnapToGrid == DBW_SNAP_LAMBDA)))
129     {
130 	/* lambda or default units */
131 	dval *= (double)DBLambda[1];
132 	dval /= (double)DBLambda[0];
133 	return round(dval);
134     }
135     else if ((*endptr == 'i')
136 		|| ((*endptr == '\0') && (DBWSnapToGrid == DBW_SNAP_INTERNAL)))
137     {
138 	/* internal units */
139 	return round(dval);
140     }
141     else if ((*endptr == 'g')
142 		|| ((*endptr == '\0') && (DBWSnapToGrid == DBW_SNAP_USER)))
143     {
144 	/* grid units */
145 	if (w == (MagWindow *)NULL)
146 	{
147 	    windCheckOnlyWindow(&w, DBWclientID);
148 	    if (w == (MagWindow *)NULL)
149 		return round(dval);		/* Default, if window is unknown */
150 	}
151 	crec = (DBWclientRec *) w->w_clientData;
152 	if (is_x)
153 	{
154 	    dval *= (double)(crec->dbw_gridRect.r_xtop
155 			- crec->dbw_gridRect.r_xbot);
156 	    if (!is_relative)
157 		dval += (double)crec->dbw_gridRect.r_xbot;
158 	}
159 	else
160 	{
161 	    dval *= (double)(crec->dbw_gridRect.r_ytop
162 			- crec->dbw_gridRect.r_ybot);
163 	    if (!is_relative)
164 		dval += (double)crec->dbw_gridRect.r_ybot;
165 	}
166 	return round(dval);
167     }
168     else
169     {
170         /* natural units referred to the current cifoutput style */
171 	if (*(endptr + 1) == 'm')
172 	{
173 	    switch (*endptr)
174 	    {
175 		case 'n':
176 		    mscale = 1;
177 		    break;
178 		case 'u':
179 		    mscale = 1000;
180 		    break;
181 		case 'm':
182 		    mscale = 1000000;
183 		    break;
184 		case 'c':
185 		    mscale = 10000000;
186 		    break;
187 		default:
188 		    TxError("Unknown metric prefix \"%cm\"; assuming internal units\n",
189 				*endptr);
190 		    return round(dval);
191 	    }
192 	}
193 	else if (!strncmp(endptr, "micron", 6))
194 	    mscale = 1000;
195 	else if (!strncmp(endptr, "centimicron", 11) || !strcmp(endptr, "cu"))
196 	    mscale = 10;
197 	else if (!isspace(*endptr))
198 	{
199 	    TxError("Unknown coordinate type \"%s\"; assuming internal units\n",
200 			endptr);
201 	    return round(dval);
202 	}
203     }
204     dval /= CIFGetOutputScale(mscale);
205     return round(dval);
206 }
207 
208 /*
209  * ----------------------------------------------------------------------------
210  *
211  * cmdParseCoord ---
212  *
213  *	This is the "normal" usage, calling cmdScaleCoord at a scale of 1.
214  *	This routine should be used in all circumstances where the result
215  *	is expected in internal units.
216  *
217  * Results:
218  *      Integer representing the given coordinate in internal units
219  *
220  * Side Effects:
221  *	None.
222  *
223  * ----------------------------------------------------------------------------
224  */
225 
226 int
cmdParseCoord(w,arg,is_relative,is_x)227 cmdParseCoord(w, arg, is_relative, is_x)
228     MagWindow *w;
229     char *arg;
230     bool is_relative, is_x;
231 {
232     return cmdScaleCoord(w, arg, is_relative, is_x, 1);
233 }
234 
235 /*
236  * ----------------------------------------------------------------------------
237  *
238  * CmdInit --
239  *
240  * Initialization for the commands module.
241  * All we do now is set up the TileTypeBitMasks CmdYMLabel, CmdYMCell
242  * and CmdYMAllButSpace.
243  *
244  * Results:
245  *	None.
246  *
247  * Side effects:
248  *	See above.
249  *
250  * ----------------------------------------------------------------------------
251  */
252 
253 void
CmdInit()254 CmdInit()
255 {
256     TTMaskZero(&CmdYMLabel);
257     TTMaskSetType(&CmdYMLabel, L_LABEL);
258 
259     TTMaskZero(&CmdYMCell);
260     TTMaskSetType(&CmdYMCell, L_CELL);
261 
262     CmdYMAllButSpace = DBAllButSpaceBits;
263     TTMaskClearType(&CmdYMAllButSpace, L_CELL);
264 }
265 
266 /*
267  * ----------------------------------------------------------------------------
268  *
269  * cmdFlushCell --
270  *
271  * Throw away all changes made within Magic to the specified cell,
272  * and re-read it from disk.  If no cell is specified, the default
273  * is the current edit cell.
274  *
275  * Results:
276  *	None.
277  *
278  * Side effects:
279  *	THIS IS NOT UNDO-ABLE!
280  *	Modifies the specified CellDef, but marks it as being unmodified.
281  *	All parents of the CellDef are re-DRC'ed over both the old and
282  *	new areas of the cell.
283  *
284  * ----------------------------------------------------------------------------
285  */
286 
287 void
cmdFlushCell(def,force_deref)288 cmdFlushCell(def, force_deref)
289     CellDef *def;
290     bool force_deref;
291 {
292     CellUse *parentUse;
293     bool dereference;
294 
295     if (def == NULL) return;
296 
297     /* Disallow flushing a cell that contains the edit cell as a child */
298     if (EditCellUse && (EditCellUse->cu_parent == def))
299     {
300 	TxError("Cannot flush cell whose subcell is being edited.\n");
301 	TxError("%s not flushed\n", def->cd_name);
302 	return;
303     }
304 
305     UndoFlush();
306 
307     if (force_deref)
308     {
309 	/* Force dereferencing */
310 	def->cd_flags |= CDDEREFERENCE;
311 	freeMagic(def->cd_file);
312 	def->cd_file = NULL;
313     }
314 
315     DBWAreaChanged(def, &def->cd_bbox, DBW_ALLWINDOWS,
316 	(TileTypeBitMask *) NULL);
317     for (parentUse = def->cd_parents; parentUse != NULL;
318 	parentUse = parentUse->cu_nextuse)
319     {
320 	if (parentUse->cu_parent == NULL) continue;
321 	DRCCheckThis(parentUse->cu_parent, TT_CHECKSUBCELL,
322 	    &parentUse->cu_bbox);
323     }
324     DBCellClearDef(def);
325     DBCellClearAvail(def);
326     dereference = (def->cd_flags & CDDEREFERENCE) ? TRUE : FALSE;
327     (void) DBCellRead(def, (char *) NULL, TRUE, dereference, NULL);
328     DBCellSetAvail(def);
329     DBReComputeBbox(def);
330     DBCellSetModified(def, FALSE);
331     DBWAreaChanged(def, &def->cd_bbox, DBW_ALLWINDOWS,
332 	&DBAllButSpaceBits);
333     for (parentUse = def->cd_parents; parentUse != NULL;
334 	parentUse = parentUse->cu_nextuse)
335     {
336 	if (parentUse->cu_parent == NULL) continue;
337 	DRCCheckThis(parentUse->cu_parent, TT_CHECKSUBCELL,
338 	    &parentUse->cu_bbox);
339     }
340 }
341 
342 /*
343  * ----------------------------------------------------------------------------
344  *
345  * CmdParseLayers --
346  *
347  * Convert a string specifying a collection of layers into a TileTypeBitMask
348  * representing the layers specified.
349  *
350  * A special layer, '$', refers to all tile types underneath the point
351  * tool, except for the DRC "CHECKxxx" types.
352  *
353  * The layer '*' refers to all tile types except for "check-this" and
354  * the label and cell pseudo-types.
355  *
356  * Results:
357  *	TRUE on success, FALSE if any layers are unrecognized.
358  *
359  * Side effects:
360  *	Prints an error message if any layers are unrecognized.
361  *	Sets bits in 'mask' according to layers in layer specification.
362  *	Leaves 'mask' set to 0 if any layers are unrecognized.
363  *
364  *	Eventually, this routine should return a "minimal" TileTypeBitMask,
365  *	ie, one with the minimum number of bits set consistent with the
366  *	string supplied it.
367  *
368  * ----------------------------------------------------------------------------
369  */
370 
371 bool
CmdParseLayers(s,mask)372 CmdParseLayers(s, mask)
373     char *s;
374     TileTypeBitMask *mask;
375 {
376     TileTypeBitMask newmask, tempmask;
377     char *dp, c;
378     char name[50];
379     TileType type;
380     Rect rootRect;
381     MagWindow *window;
382     DBWclientRec *crec;
383     bool adding = TRUE;
384     int which, i;
385 #define LN_CELL		0
386 #define LN_LABELS	1
387 #define LN_ALL		2
388 #define LN_DOLLAR	3
389 #define LN_ERRORS	4
390 #define LN_CONNECT	5
391     static struct
392     {
393 	char *layer_name;
394 	int layer_value;
395     }
396     special[] =
397     {
398 	"$",		LN_DOLLAR,
399 	"*",		LN_ALL,
400 	"errors",	LN_ERRORS,
401 	"labels",	LN_LABELS,
402 	"subcell",	LN_CELL,
403 	"connect",	LN_CONNECT,
404 	0,
405     };
406 
407 
408     TTMaskZero(mask);
409     while (c = *s++)
410     {
411 	switch (c)
412 	{
413 	    case '-':
414 		adding = FALSE;
415 		continue;
416 	    case '+':
417 		adding = TRUE;
418 		continue;
419 	    case ',':
420 	    case ' ':
421 		continue;
422 	}
423 
424 	dp = name; *dp++ = c;
425 	while (*s && *s != ',' && *s != '+' && *s != '-' && *s != ' ')
426 	    *dp++ = *s++;
427 	*dp = '\0';
428 	if (name[0] == '\0')
429 	    continue;
430 
431 	TTMaskZero(&newmask);
432 
433 	type = DBTechNameTypes(name, &newmask);
434 	if (type == -2)
435 	{
436 	    which = LookupStruct(name, (LookupTable *) special, sizeof special[0]);
437 	    if (which >= 0)
438 	    {
439 		switch (special[which].layer_value)
440 		{
441 		    case LN_LABELS:
442 			TTMaskSetType(&newmask, L_LABEL);
443 			break;
444 		    case LN_CELL:
445 			TTMaskSetType(&newmask, L_CELL);
446 			break;
447 		    /*
448 		     * All layers currently beneath the point tool.
449 		     * Currently, neither labels nor cells are ever included
450 		     * in this.
451 		     */
452     		    case LN_DOLLAR:
453 			window = CmdGetRootPoint((Point *) NULL, &rootRect);
454 			if ((window == (MagWindow *) NULL)
455 			    	|| (window->w_client != DBWclientID))
456 			    return (FALSE);
457 			crec = (DBWclientRec *) window->w_clientData;
458 			DBSeeTypesAll(((CellUse *)window->w_surfaceID),
459 				&rootRect, crec->dbw_bitmask, &newmask);
460 			TTMaskAndMask(&newmask, &crec->dbw_visibleLayers);
461 			tempmask = DBAllButSpaceAndDRCBits;
462 			TTMaskSetType(&tempmask, TT_SPACE);
463 			TTMaskAndMask(&newmask, &tempmask);
464 			break;
465 		    /*
466 		     * Everything but labels and subcells
467 		     */
468 		    case LN_ALL:
469 			newmask = DBAllButSpaceAndDRCBits;
470 			TTMaskClearType(&newmask, L_LABEL);
471 			TTMaskClearType(&newmask, L_CELL);
472 			break;
473 		    /*
474 		     * All DRC error layers.
475 		     */
476 		    case LN_ERRORS:
477 			TTMaskSetType(&newmask, TT_ERROR_P);
478 			TTMaskSetType(&newmask, TT_ERROR_S);
479 			TTMaskSetType(&newmask, TT_ERROR_PS);
480 			break;
481 		    /*
482 		     * Add in all layers connected to layers already parsed
483 		     */
484 		    case LN_CONNECT:
485 			for (type = TT_TECHDEPBASE; type < DBNumTypes; type++)
486 			    if (TTMaskHasType(mask, type))
487 			    {
488 				TileType ttype;
489 				for (ttype = TT_TECHDEPBASE; ttype < DBNumTypes; ttype++)
490 				    if (DBConnectsTo(type, ttype))
491 					TTMaskSetType(&newmask, ttype);
492 			    }
493 			break;
494 		}
495 	    }
496 	    else
497 	    {
498 		TxError("Unrecognized layer: %s\n", name);
499 printTypes:
500 		DBTechPrintTypes(&DBAllButSpaceAndDRCBits, FALSE);
501 		for (i = 0; ; i++)
502 		{
503 		    if (special[i].layer_name == NULL) break;
504 		    TxError("    %s\n", special[i].layer_name);
505 		}
506 		return (FALSE);
507 	    }
508 	}
509 	else if (type == -1)
510 	{
511 	    TxError("Ambiguous layer: %s\n", name);
512 	    goto printTypes;
513 	}
514 
515 	if (adding)
516 	{
517 	    TTMaskSetMask(mask, &newmask);
518 	}
519 	else
520 	{
521 	    TTMaskClearMask(mask, &newmask);
522 	}
523     }
524 
525     return (TRUE);
526 }
527 
528 /*
529  * ----------------------------------------------------------------------------
530  *
531  * cmdMaskToType --
532  *
533  * Convert a TileTypeBitMask into a TileType.
534  *
535  * Results:
536  *	Returns -1 if more than one type bit is set in the TileTypeBitMask;
537  *	otherwise, returns the TileType of the bit set.
538  *
539  * Side effects:
540  *	None.
541  *
542  * ----------------------------------------------------------------------------
543  */
544 
545 TileType
cmdMaskToType(mask)546 cmdMaskToType(mask)
547     TileTypeBitMask *mask;
548 {
549     TileType type, t;
550 
551     type = -1;
552     for (t = TT_SELECTBASE; t < DBNumTypes; t++)
553     {
554 	if (TTMaskHasType(mask, t))
555 	{
556 	    if (type >= 0)
557 		return (-1);
558 	    type = t;
559 	}
560     }
561 
562     if (type < 0)
563 	return (TT_SPACE);
564     return (type);
565 }
566 
567 /*
568  * ----------------------------------------------------------------------------
569  *
570  * cmdSaveCell --
571  *
572  * Save a given cell out to disk.
573  * If a filename is given, the cell is written out to that file;
574  * otherwise, the cell is written out to the file stored with the
575  * cellDef, or to a newly created file of the same name as the
576  * cellDef.  If there is no name associated with the cell, the
577  * save is disallowed.
578  *
579  * The name of the cell is set to the filename, if it is specified.
580  *
581  * Results:
582  *	None.
583  *
584  * Side effects:
585  *	Writes the cell out to a disk file.
586  *	Clears the modified bit in the cd_flags.
587  *
588  * ----------------------------------------------------------------------------
589  */
590 
591 void
cmdSaveCell(cellDef,newName,noninteractive,tryRename)592 cmdSaveCell(cellDef, newName, noninteractive, tryRename)
593     CellDef *cellDef;	/* Pointer to def of cell to be saved */
594     char *newName;	/* Pointer to name of file in which cell is to be
595 			 * saved.  May be NULL, in which case the name from
596 			 * the CellDef is taken.
597 			 */
598     bool noninteractive;/* If true, try hard but don't ask the user
599 			 * questions.
600 			 */
601     bool tryRename;	/* We should rename the cell to the name of the
602 			 * place where it was saved.
603 			 */
604 {
605     char *fileName = newName;
606 
607     /* Eliminate the phony labels added for use by rsim */
608 #ifndef NO_SIM_MODULE
609     SimEraseLabels();
610 #endif
611 
612     /*
613      * Whenever the "unnamed" cell is saved, the name of the
614      * cell changes to the name of the file in which it was
615      * saved.
616      */
617 
618     if (strcmp(cellDef->cd_name, UNNAMED) == 0)
619     {
620 	if (newName == NULL)
621 	    TxPrintf("Must specify name for cell %s.\n", UNNAMED);
622 	fileName = cmdCheckNewName(cellDef, newName, TRUE, noninteractive);
623 	if (fileName == NULL) return;
624     }
625     else if (newName != NULL)
626     {
627 	fileName = cmdCheckNewName(cellDef, newName, TRUE, noninteractive);
628 	if (fileName == NULL) return;
629     }
630     else
631     {
632 	if (cellDef->cd_file == NULL)
633 	{
634 	    fileName = cmdCheckNewName(cellDef, cellDef->cd_name,
635 		TRUE, noninteractive);
636 	    if (fileName == NULL) return;
637 	}
638     }
639 
640     DBUpdateStamps();
641     if (!DBCellWrite(cellDef, fileName))
642     {
643 	TxError("Could not write file.  Cell not written.\n");
644 	goto cleanup;
645     }
646 
647     if (!tryRename || (fileName == NULL) || (strcmp(cellDef->cd_name, fileName) == 0))
648 	goto cleanup;
649 
650     /* Rename the cell */
651     if (!DBCellRenameDef(cellDef, fileName))
652     {
653 	/* This should never happen */
654 	TxError("Magic error: there is already a cell named \"%s\"\n",
655 		    fileName);
656 	goto cleanup;
657     }
658 
659     if (EditCellUse && (cellDef == EditCellUse->cu_def))
660     {
661 	/*
662 	 * The cell is the edit cell.
663 	 * All windows with compatible roots should show
664 	 * a caption of "root EDITING edit"
665 	 */
666 	CmdSetWindCaption(EditCellUse, EditRootDef);
667     }
668     else
669     {
670 	/*
671 	 * The cell is not the edit cell.
672 	 * We want to find all windows for which this is
673 	 * the root cell and update their captions.
674 	 */
675 	(void) WindSearch(DBWclientID, (ClientData) NULL, (Rect *) NULL,
676 		cmdSaveWindSet, (ClientData) cellDef);
677     }
678 
679 cleanup:
680     if ((fileName != newName) && (fileName != cellDef->cd_name))
681 	freeMagic(fileName);
682     return;
683 }
684 
685 /*
686  * ----------------------------------------------------------------------------
687  *
688  * cmdCheckNewName --
689  *
690  * Get the name of the file in which the argument CellDef is to
691  * be saved, if a name was not already provided.
692  * If the name of the file is different from the name of the cell,
693  * check to make sure that the file doesn't already exist.
694  * If the CellDef is to be renamed after saving it, check to make
695  * sure that no cell already exists by the new name.
696  *
697  * Results:
698  *	Returns a pointer to a string holding the filename in which
699  *	the cell is to be saved, or NULL if the save should be aborted.
700  *
701  * Side effects:
702  *	May prompt the user for a new file name if one is required.
703  *	If the filename returned was one typed by the user in response to
704  *	this prompt, it overwrites the previous such filename returned,
705  *	as it is stored in a static array.
706  *
707  * ----------------------------------------------------------------------------
708  */
709 
710 char *
cmdCheckNewName(def,newName,tryRename,noninteractive)711 cmdCheckNewName(def, newName, tryRename, noninteractive)
712     CellDef *def;
713     char *newName;
714     bool tryRename;
715     bool noninteractive;
716 {
717     static char *yesno[] = { "no", "yes", 0 };
718     char *filename;
719     char *prompt;
720     char *returnname;
721     int code;
722     FILE *f;
723 
724     returnname = newName;
725 
726 again:
727     if (returnname == NULL)
728     {
729 	if (noninteractive) {
730 	    TxError("Can't write file named '%s'\n", def->cd_name);
731 	    return NULL;
732 	};
733 	TxPrintf("File for cell %s: [hit return to abort save] ", def->cd_name);
734 	returnname = (char *)mallocMagic(1024 * sizeof(char));
735 	if (TxGetLine(returnname, sizeof returnname) == NULL || returnname[0] == '\0')
736 	{
737 	    TxPrintf("Cell not saved.\n");
738 	    freeMagic(returnname);
739 	    return ((char *) NULL);
740 	}
741 	if (CmdIllegalChars(returnname, "[],", "Cell name"))
742 	{
743 	    freeMagic(returnname);
744 	    goto again;
745 	}
746     }
747 
748     /* Remove any ".mag" file extension from the name */
749     if (strlen(returnname) > 4)
750 	if (!strcmp(returnname + strlen(returnname) - 4, ".mag"))
751 	    *(returnname + strlen(returnname) - 4) = '\0';
752 
753     if (strcmp(returnname, def->cd_name) != 0)
754     {
755 	if (f = PaOpen(returnname, "r", DBSuffix, ".", (char *) NULL, &filename))
756 	{
757 	    (void) fclose(f);
758 	    if (noninteractive) {
759 		TxError("Overwriting file '%s' with cell '%s'\n", filename,
760 		    def->cd_name);
761 	    }
762 	    else {
763 		prompt = TxPrintString("File %s already exists.\n"
764 			"  Overwrite it with %s? ", filename, def->cd_name);
765 		code = TxDialog(prompt, yesno, 0);
766 
767 		if (code == 0)
768 		{
769 		    /* No -- don't overwrite */
770 		    if (returnname != newName) freeMagic(returnname);
771 		    returnname = NULL;
772 		    goto again;
773 		}
774 	    }
775 	}
776 
777 	if (tryRename && DBCellLookDef(returnname) != NULL)
778 	{
779 	    TxError("Can't rename cell '%s' to '%s' because that cell already exists.\n",
780 			def->cd_name, returnname);
781 	    if (returnname != newName) freeMagic(returnname);
782 	    if (noninteractive) return NULL;
783 	    returnname = NULL;
784 	    goto again;
785 	}
786     }
787     return (returnname);
788 }
789 
790 /*
791  * ----------------------------------------------------------------------------
792  *
793  * nameEllipsis ---
794  *
795  *  Truncate a string an append an ellipsis ("...") to the end if the string
796  *  will overflow a fixed array length.
797  *
798  * ----------------------------------------------------------------------------
799  */
800 
801 static char *
nameEllipsis(name,maxlen,prefix)802 nameEllipsis(name, maxlen, prefix)
803     char *name;
804     int maxlen;
805     char **prefix;
806 {
807     int l = strlen(name);
808 
809     if (l < maxlen)
810     {
811 	*prefix = "";
812 	return name;
813     }
814     else
815     {
816 	*prefix = "...";
817 	return &name[l - maxlen + 3];
818     }
819 }
820 
821 /*
822  * ----------------------------------------------------------------------------
823  *
824  * cmdSaveWindSet --
825  *
826  * Filter function for cmdSaveCell() above.
827  * Called by WindSearch() with each window.
828  *
829  * The idea is to change only those captions in windows whose root
830  * uses are instances of the def 'def'.  Sets the caption of each such
831  * window to:
832  *
833  *	def [NOT BEING EDITED]
834  *
835  * Results:
836  *	Always 0 to keep the search going.
837  *
838  * Side effects:
839  *	Modifies captions and clientData for the window
840  *
841  * ----------------------------------------------------------------------------
842  */
843 
844 int
cmdSaveWindSet(window,def)845 cmdSaveWindSet(window, def)
846     MagWindow *window;
847     CellDef *def;
848 {
849     char caption[200];
850     CellDef *rootDef;
851     char *name, *name_pfx;
852 
853     rootDef = ((CellUse *) window->w_surfaceID)->cu_def;
854     if (rootDef != def)
855 	return 0;
856 
857     name = nameEllipsis(def->cd_name, 175, &name_pfx);
858     (void) snprintf(caption, sizeof(caption), "%s%s [NOT BEING EDITED]", name_pfx, name);
859     (void) StrDup(&window->w_iconname, def->cd_name);
860     WindCaption(window, caption);
861     return 0;
862 }
863 
864 /*
865  * ----------------------------------------------------------------------------
866  *
867  * CmdSetWindCaption --
868  *
869  * Update the captions of all windows to reflect a new EditCell.
870  * The caption of each window having the same root cell def as the
871  * one in which the Edit Cell was selected is set to show that the
872  * window is subediting the new edit cell.  The captions in all
873  * other windows show that these windows are not subediting the
874  * edit cell.
875  *
876  * Results:
877  *	None.
878  *
879  * Side effects:
880  *	Modifies captions and clientData for each window.
881  *
882  * ----------------------------------------------------------------------------
883  */
884 
885 /*
886  * The following are used to pass information down to the filter
887  * function applied by WindSearch(), since it is not intended that
888  * CmdSetWindCaption() be re-entrant.
889  */
890 
891 static CellDef *newEditDef;	/* Pointer to new edit cell def */
892 static CellDef *newRootDef;	/* Pointer to root def of window in which
893 				 * new edit cell was selected.  This is
894 				 * used to determine whether the edit cell
895 				 * is being edited in a window or not.
896 				 */
897 
898 void
CmdSetWindCaption(newEditUse,rootDef)899 CmdSetWindCaption(newEditUse, rootDef)
900     CellUse *newEditUse;	/* Pointer to new edit cell use */
901     CellDef *rootDef;		/* Root cell def of the window in which the
902 				 * edit cell was selected.
903 				 */
904 {
905     int cmdWindSet();
906 
907     newEditDef = (newEditUse) ? newEditUse->cu_def : NULL;
908     newRootDef = rootDef;
909     (void) WindSearch(DBWclientID, (ClientData) NULL, (Rect *) NULL,
910 	    cmdWindSet, (ClientData) 0);
911 }
912 
913 /*
914  * ----------------------------------------------------------------------------
915  *
916  * cmdWindSet --
917  *
918  * Filter function for CmdSetWindCaption() above.
919  * Called by WindSearch() with each window.
920  *
921  * If the window is compatible with the new edit cell, sets the caption
922  * for that window to be
923  *
924  *	RootDef EDITING EditDef
925  *
926  * Otherwise, sets the caption to be
927  *
928  *	RootDef [NOT BEING EDITED]
929  *
930  * Results:
931  *	Always 0 to keep the function going.
932  *
933  * Side effects:
934  *	Modifies captions and clientData for the window
935  *
936  * ----------------------------------------------------------------------------
937  */
938 
939 int
cmdWindSet(window)940 cmdWindSet(window)
941     MagWindow *window;
942 {
943     char caption[200];
944     CellDef *wDef;
945     char *name[2], *name_pfx[2];
946 
947     wDef = ((CellUse *) window->w_surfaceID)->cu_def;
948 
949 
950 
951     if (wDef != newRootDef) {
952 	name[0] = nameEllipsis(wDef->cd_name, 175, &name_pfx[0]);
953 	(void) snprintf(caption, sizeof(caption), "%s%s [NOT BEING EDITED]",
954 		    name_pfx[0], name[0]);
955     } else {
956 	name[0] = nameEllipsis(wDef->cd_name, 90, &name_pfx[0]);
957 	name[1] = nameEllipsis(newEditDef->cd_name, 90, &name_pfx[1]);
958 	(void) snprintf(caption, sizeof(caption), "%s%s EDITING %s%s",
959 		name_pfx[0], name[0], name_pfx[1], name[1]);
960 
961 #ifdef SCHEME_INTERPRETER
962         /* Add a binding to scheme variable "edit-cell" */
963         LispSetEdit (newEditDef->cd_name);
964 #endif
965     }
966 
967     (void) StrDup(&window->w_iconname, wDef->cd_name);
968     WindCaption(window, caption);
969     return 0;
970 }
971 
972 
973 /*
974  * ----------------------------------------------------------------------------
975  *
976  * CmdGetRootPoint --
977  *
978  * Get the window containing the point tool, and return (in root cell
979  * coordinates for that window) the coordinates of the point, and of
980  * a minimum-grid-size rectangle enclosing the point.
981  *
982  * Results:
983  *	Pointer to window containing the point tool, or NULL if the
984  *	point tool is not present.
985  *
986  * Side effects:
987  *	Sets *point to be the coordinates of the point tool in root
988  *	coordinates, and *rect to be the minimum-grid-size enclosing
989  *	rectangle.
990  *
991  *	Prints an error message if the point is not found.
992  *
993  * ----------------------------------------------------------------------------
994  */
995 
996 MagWindow *
CmdGetRootPoint(point,rect)997 CmdGetRootPoint(point, rect)
998     Point *point;
999     Rect *rect;
1000 {
1001     MagWindow *window;
1002 
1003     window = ToolGetPoint(point, rect);
1004     if (window == (MagWindow *) NULL)
1005 	TxError("Crosshair not in a valid window for this command\n");
1006 
1007     return (window);
1008 }
1009 
1010 /*
1011  * ----------------------------------------------------------------------------
1012  *
1013  * CmdGetEditPoint --
1014  *
1015  * Get the window containing the point tool, and return (in edit cell
1016  * coordinates for that window) the coordinates of the point, and of
1017  * a minimum-grid-size rectangle enclosing the point.
1018  *
1019  * Results:
1020  *	Pointer to window containing the point tool, or NULL if the
1021  *	point tool is not present.
1022  *
1023  * Side effects:
1024  *	Sets *point to be the coordinates of the point tool in edit
1025  *	coordinates, and *rect to be the minimum-grid-size enclosing
1026  *	rectangle.
1027  *
1028  * ----------------------------------------------------------------------------
1029  */
1030 
1031 MagWindow *
CmdGetEditPoint(point,rect)1032 CmdGetEditPoint(point, rect)
1033     Point *point;
1034     Rect *rect;
1035 {
1036     MagWindow *window;
1037     Rect rootRect;
1038     Point rootPoint;
1039 
1040     window = CmdGetRootPoint(&rootPoint, &rootRect);
1041     if (window != (MagWindow *) NULL)
1042     {
1043 	GeoTransRect(&RootToEditTransform, &rootRect, rect);
1044 	GeoTransPoint(&RootToEditTransform, &rootPoint, point);
1045     }
1046 
1047     return (window);
1048 }
1049 
1050 /*
1051  * ----------------------------------------------------------------------------
1052  *
1053  * CmdWarnWrite --
1054  *
1055  * Check to see if there are modified and unwritten cells and ask the
1056  * user whether he wants to stay in magic or lose all these cells.
1057  *
1058  * Results:
1059  *	TRUE if the user wishes to continue anyway without writing the
1060  *	modified cells out to disk, FALSE if not.
1061  *
1062  * Side effects:
1063  *	None.
1064  *
1065  * ----------------------------------------------------------------------------
1066  */
1067 
1068 bool
CmdWarnWrite()1069 CmdWarnWrite()
1070 {
1071     int count, code;
1072     int cmdWarnWriteFunc();
1073     static char *yesno[] = { "no", "yes", 0 };
1074     char *prompt;
1075 
1076     count = 0;
1077     (void) DBCellSrDefs(CDMODIFIED|CDBOXESCHANGED|CDSTAMPSCHANGED,
1078 	cmdWarnWriteFunc, (ClientData) &count);
1079     if (count == 0)
1080 	return TRUE;
1081 
1082     prompt = TxPrintString("%d Magic cell%s been modified.\n  Do you"
1083 		" want to exit magic and lose %s? ", count,
1084 		count == 1 ? " has" : "s have",
1085 		count == 1 ? "it" : "them");
1086     code = TxDialog(prompt, yesno, 0);
1087     return (code) ? TRUE : FALSE;
1088 }
1089 
1090 int
cmdWarnWriteFunc(cellDef,pcount)1091 cmdWarnWriteFunc(cellDef, pcount)
1092     CellDef *cellDef;
1093     int *pcount;
1094 {
1095     if ((cellDef->cd_flags & CDINTERNAL) == 0)
1096 	(*pcount)++;
1097     return 0;
1098 }
1099 
1100 /*
1101  * ----------------------------------------------------------------------------
1102  * cmdExpandOneLevel --
1103  *
1104  *	Expand (unexpand) a cell, and unexpand all of its children.  This is
1105  *	called by commands such as getcell, expand current cell, and load.
1106  *	Don't bother to unexpand children if we are unexpanding this cell.
1107  *
1108  * Results:
1109  *	None.
1110  *
1111  * Side effects:
1112  *	None.
1113  * ----------------------------------------------------------------------------
1114  */
1115 
1116 void
cmdExpandOneLevel(cu,bitmask,expand)1117 cmdExpandOneLevel(cu, bitmask, expand)
1118     CellUse *cu;
1119     int bitmask;
1120     bool expand;
1121 {
1122     extern int cmdExpand1func();
1123 
1124     /* first, expand this cell use */
1125     DBExpand(cu, bitmask, expand);
1126 
1127     /* now, unexpand its direct children (ONE LEVEL ONLY) */
1128     if (expand)
1129 	(void) DBCellEnum(cu->cu_def, cmdExpand1func, (ClientData) bitmask);
1130 }
1131 
1132 int
cmdExpand1func(cu,bitmask)1133 cmdExpand1func(cu, bitmask)
1134     CellUse *cu;
1135     ClientData bitmask;
1136 {
1137     DBExpand(cu, (int) bitmask, FALSE);
1138     return 0;
1139 }
1140 
1141 /*
1142  * ----------------------------------------------------------------------------
1143  *
1144  * CmdGetSelectedCell --
1145  *
1146  * 	This procedure returns a pointer to the selected cell.
1147  *
1148  * Results:
1149  *	The return value is a pointer to the selected cell.  If more
1150  *	than one cell is selected, the upper-leftmost cell is returned.
1151  *	If no cell is selected, NULL is returned.
1152  *
1153  * Side effects:
1154  *	If pTrans isn't NULL, the area it points to is modified to hold
1155  *	the transform from coords of the selected cell to root coords.
1156  *
1157  * ----------------------------------------------------------------------------
1158  */
1159 
1160 Transform *cmdSelTrans;		/* Shared between CmdGetSelectedCell and
1161 				 * cmdGetCellFunc.
1162 				 */
1163 
1164 CellUse *
CmdGetSelectedCell(pTrans)1165 CmdGetSelectedCell(pTrans)
1166     Transform *pTrans;		/* If non-NULL, transform from selected
1167 				 * cell to root coords is stored here.
1168 				 */
1169 {
1170     CellUse *result = NULL;
1171     int cmdGetSelFunc();		/* Forward declaration. */
1172 
1173     cmdSelTrans = pTrans;
1174     (void) SelEnumCells(FALSE, (bool *) NULL, (SearchContext *) NULL,
1175 	cmdGetSelFunc, (ClientData) &result);
1176     return result;
1177 }
1178 
1179 	/* ARGSUSED */
1180 int
cmdGetSelFunc(selUse,realUse,transform,pResult)1181 cmdGetSelFunc(selUse, realUse, transform, pResult)
1182     CellUse *selUse;		/* Not used. */
1183     CellUse *realUse;		/* The first selected use. */
1184     Transform *transform;	/* Transform from coords of realUse to root. */
1185     CellUse **pResult;		/* Store realUse here. */
1186 {
1187     *pResult = realUse;
1188     if (cmdSelTrans != NULL)
1189 	*cmdSelTrans = *transform;
1190     return 1;			/* Skip any other selected cells. */
1191 }
1192 
1193 /*
1194  * ----------------------------------------------------------------------------
1195  *
1196  * CmdIllegalChars --
1197  *
1198  * 	Checks a string for any of a number of illegal characters.
1199  *	If any is found, it's printed in an error message.
1200  *
1201  * Results:
1202  *	TRUE is returned if any of the characters in "illegal" is
1203  *	also in "string", or if "string" contains any control or
1204  *	non-ASCII characters.	Otherwise, FALSE is returned.
1205  *
1206  * Side effects:
1207  *	None.
1208  *
1209  * ----------------------------------------------------------------------------
1210  */
1211 
1212 bool
CmdIllegalChars(string,illegal,msg)1213 CmdIllegalChars(string, illegal, msg)
1214     char *string;		/* String to check for illegal chars. */
1215     char *illegal;		/* String containing illegal chars. */
1216     char *msg;			/* String identifying what string is
1217 				 * supposed to represent, for ease in
1218 				 * printing error messages.
1219 				 */
1220 {
1221     char *p, *bad;
1222 
1223     for (p = string; *p != 0; p++)
1224     {
1225 	if (!isascii(*p)) goto error;
1226 	if (iscntrl(*p)) goto error;
1227 	for (bad = illegal; *bad != 0; bad++)
1228 	{
1229 	    if (*bad == *p) goto error;
1230 	}
1231 	continue;
1232 
1233 	error:
1234 	if (!isascii(*p) || iscntrl(*p))
1235 	{
1236 	    TxError("%s contains illegal control character 0x%x\n",
1237 		   msg, *p);
1238 	}
1239 	else TxError("%s contains illegal character \"%c\"\n",
1240 		msg, *p);
1241 	return TRUE;
1242     }
1243     return FALSE;
1244 }
1245 
1246