diff --git a/VERSION b/VERSION index 11e1c8a9..be769205 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -8.3.408 +8.3.409 diff --git a/doc/html/logcommands.html b/doc/html/logcommands.html index 7c15ec74..63a86358 100644 --- a/doc/html/logcommands.html +++ b/doc/html/logcommands.html @@ -20,10 +20,20 @@

logcommands


-Log all commands into a file +Log all commands and mouse button actions into a file

Usage:

+
+ logcommands option [file]

+
+ where option is one of: start, stop, + update, suspend, or resume, and + file is the name of the log file to write to. +
+ + or (legacy usage): +
logcommands [file [update]]

@@ -33,10 +43,53 @@ Log all commands into a file

Summary:

- The logcommands command tells magic to write all - command-line commands and button pushes to the log file - named file. If update is specified, a - screen update is generated after each command executes. + The logcommands command creates and manages a file + containing all of the command-line commands and button actions + as they occur while file writing is active. This creates a + recording of the layout session which can be "played back" by + sourcing the log file. Behavior is controlled by one of the + command options, as follows: +
+
start filename +
This opens the log file named filename, writes + a header and issues a few rudimentary commands to + align the file with the current window size and zoom + factor, and the cursor box position. Then it starts + recording commands, not including the logcommands + command itself. +
stop +
This command closes any open log file and stops recording + commands. +
suspend +
This command stops recording commands until the + logcommands resume command is issued, without + ending or closing the file. This is used by the wrapper + script code to prevent some commands such as those that + track pointer movement from being added to the log file. +
resume +
This command resumes recording after it has been suspended. +
update +
This command sets a flag that issues a display update + command after every command recorded in the log file. + This usage is largely deprecated, since sourcing the + log file for playback will refresh the display as usual. +
+ + The legacy syntax "logcommands [file [update]]" + will open a new command log file if file is given, optionally + with the "update" feature (see above) if the update keyword + is added. With no arguments, the log file is ended and closed. +
+ +

Caveats:

+
+ The initial header and setup in the log file is rudimentary. It + will not attempt to load a technology file, load a layout, or + edit some part of a layout to match what is in the layout window + at the time that the log file was created. The best usage is to + start the log file immediately after starting magic, and only + play back the log file in the tech file environment in which it + was created.

Implementation Notes:

diff --git a/tcltk/wrapper.tcl b/tcltk/wrapper.tcl index c5abe70b..f028cf12 100644 --- a/tcltk/wrapper.tcl +++ b/tcltk/wrapper.tcl @@ -243,10 +243,14 @@ proc magic::drcupdate { option } { } proc magic::drcstate { status } { + logcommands suspend set winlist [*bypass windownames layout] foreach lwin $winlist { set framename [winfo parent $lwin] - if {$framename == "."} {return} + if {$framename == "."} { + logcommands resume + return + } switch $status { idle { set dct [*bypass drc list count total] @@ -260,6 +264,7 @@ proc magic::drcstate { status } { busy { ${framename}.titlebar.drcbutton configure -selectcolor yellow } } } + logcommands resume } # Create the menu of windows. This is kept separate from the cell manager, @@ -502,6 +507,7 @@ proc magic::captions {{subcommand {}}} { if {$subcommand != {} && $subcommand != "writeable" && $subcommand != "load"} { return } + logcommands suspend set winlist [magic::windownames layout] foreach winpath $winlist { set framename [winfo parent $winpath] @@ -521,6 +527,7 @@ proc magic::captions {{subcommand {}}} { "Loaded: ${subcaption1} Editing: ${subcaption2} Tool: $Opts(tool) \ Technology: ${techname}" } + logcommands resume } # Allow captioning in the title window by tagging the "load" and "edit" commands @@ -655,8 +662,12 @@ proc magic::cursorview {win} { if {$win == {}} { return } + logcommands suspend set framename [winfo parent $win] - if {[catch {set cr [*bypass cif scale out]}]} {return} + if {[catch {set cr [*bypass cif scale out]}]} { + logcommands resume + return + } if {$cr == 0} {return} set olst [${win} cursor internal] @@ -671,7 +682,10 @@ proc magic::cursorview {win} { if {[catch { set olstx [expr {$olstx * $cr}] set olsty [expr {$olsty * $cr}] - }]} {return} + }]} { + logcommands resume + return + } if {[${win} box exists]} { set dlst [${win} box position] @@ -685,20 +699,24 @@ proc magic::cursorview {win} { set titletext [format "(%+g %+g) microns" $olstx $olsty] ${framename}.titlebar.pos configure -text $titletext } + logcommands resume } proc magic::toolupdate {win {yesno "yes"} {layerlist "none"}} { global Winopts if {[magic::display] == "NULL"} {return} + logcommands suspend if {$win == {}} { set win [magic::windownames] } # Wind3d has a "see" function, so make sure this is not a 3d window if {$win == [magic::windownames wind3d]} { + logcommands resume return } + logcommands resume set topname [winfo toplevel $win] set framename [winfo parent $win] @@ -939,11 +957,15 @@ proc magic::techrebuild {winpath {cmdstr ""}} { proc magic::setscrollvalues {win} { global Opts + logcommands suspend set svalues [${win} view get] set bvalues [${win} view bbox] set framename [winfo parent ${win}] - if {$framename == "."} {return} + if {$framename == "."} { + logcommands resume + return + } set bwidth [expr {[lindex $bvalues 2] - [lindex $bvalues 0]}] set bheight [expr {[lindex $bvalues 3] - [lindex $bvalues 1]}] @@ -985,6 +1007,7 @@ proc magic::setscrollvalues {win} { proc magic::scrollupdate {win} { + logcommands suspend if {[magic::display] == "NULL"} {return} if {[info level] <= 1} { @@ -1001,6 +1024,7 @@ proc magic::scrollupdate {win} { magic::setscrollvalues $win } } + logcommands resume } # scrollview: update the magic display to match the diff --git a/textio/textio.h b/textio/textio.h index e10f18df..d14c7b90 100644 --- a/textio/textio.h +++ b/textio/textio.h @@ -38,6 +38,9 @@ extern unsigned char TxInputRedirect; #endif +#define TX_LOG_UPDATE 1 /* Update display after every log command */ +#define TX_LOG_SUSPEND 2 /* Suspend output logging */ + extern int TxCurButtons; /* These should really be defined by the application, not hard-coded */ diff --git a/textio/txCommands.c b/textio/txCommands.c index 0a43c0df..b3cac8b6 100644 --- a/textio/txCommands.c +++ b/textio/txCommands.c @@ -28,6 +28,7 @@ static char rcsid[] __attribute__ ((unused)) ="$Header: /usr/cvsroot/magic-8.0/t #include #include #include +#include #include "tcltk/tclmagic.h" #include "utils/magsgtty.h" @@ -614,11 +615,11 @@ TxClearPoint() } static FILE *txLogFile = NULL; -bool txLogUpdate; +unsigned char txLogFlags; /* * ---------------------------------------------------------------------------- - * TxLogCommands -- + * TxLogStart -- * * Log all further commands to the given file name. If the file is NULL, * turn off logging. @@ -632,21 +633,165 @@ bool txLogUpdate; */ void -TxLogCommands(fileName, update) +TxLogStart(fileName, mw) char *fileName; - bool update; /* Request a screen update after each command */ + MagWindow *mw; /* Window commands are logged from */ { if (txLogFile != NULL) { - (void) fclose(txLogFile); - txLogFile = NULL; + TxError("There is already a log file (%s) open!\n", txLogFile); + return; } - if (fileName == NULL) return; - txLogUpdate = update; + txLogFlags = 0; txLogFile = fopen(fileName, "w"); if (txLogFile == NULL) TxError("Could not open file '%s' for writing.\n", fileName); + else + { + time_t t_stamp = time((time_t *)NULL); + struct tm *clock = localtime(&t_stamp); + char *now = ctime(&t_stamp); + + TxPrintf("Logging commands to file \"%s\"\n", fileName); + /* Write comment line header into command file and log the current + * window view position so that cursor positions in the file match + * the layout. If the cursor box is defined, then issue a "box + * values" command so that relative box adjustments are correct. + */ +#ifdef MAGIC_WRAPPER + fprintf(txLogFile, "# Magic command log file\n"); + fprintf(txLogFile, "# Using technology: %s\n", DBTechName); + if (mw != NULL) + fprintf(txLogFile, "# Title: %s\n", mw->w_caption); + fprintf(txLogFile, "# Date: %s", now); +#endif + if (mw != NULL) + { + CellDef *rootBoxDef; + Rect rootBox; + +#ifndef MAGIC_WRAPPER + // Colon preceeds commands in the non-Tcl verson of the log file. + fprintf(txLogFile, ":"); +#endif + fprintf(txLogFile, "view %di %di %di %di\n", + mw->w_surfaceArea.r_xbot, mw->w_surfaceArea.r_ybot, + mw->w_surfaceArea.r_xtop, mw->w_surfaceArea.r_ytop); + if (ToolGetBox(&rootBoxDef, &rootBox)) + { + fprintf(txLogFile, "box values %di %di %di %di\n", + rootBox.r_xbot, rootBox.r_ybot, + rootBox.r_xtop, rootBox.r_ytop); + } + } + } +} + +/* + * ---------------------------------------------------------------------------- + * TxLogStop -- + * + * Turn off logging. + * + * Results: + * None. + * + * Side effects: + * File IO. + * ---------------------------------------------------------------------------- + */ + +void +TxLogStop() +{ + if (txLogFile != NULL) + { + TxPrintf("Ending command logging to file.\n"); + fclose(txLogFile); + txLogFile = NULL; + txLogFlags = 0; + } +} + +/* + * ---------------------------------------------------------------------------- + * TxLogUpdate -- + * + * Set the log file flags to force a display refresh after logged commands + * + * Results: + * None. + * + * Side effects: + * File IO. + * ---------------------------------------------------------------------------- + */ + +void +TxLogUpdate() +{ + if (txLogFile == NULL) + { + TxError("There is no log file to set an update flag on.\n"); + return; + } + if (txLogFlags & TX_LOG_UPDATE) + { + txLogFlags &= ~TX_LOG_UPDATE; + TxPrintf("No display refresh after logged commands.\n"); + } + else + { + txLogFlags |= TX_LOG_UPDATE; + TxPrintf("Forcing display refresh after logged commands.\n"); + } +} + +/* + * ---------------------------------------------------------------------------- + * TxLogSuspend -- + * + * Suspend command logging + * + * Results: + * None. + * + * Side effects: + * File IO. + * ---------------------------------------------------------------------------- + */ + +void +TxLogSuspend() +{ + if (txLogFile == NULL) + return; + + txLogFlags |= TX_LOG_SUSPEND; +} + +/* + * ---------------------------------------------------------------------------- + * TxLogResume -- + * + * Resume command logging + * + * Results: + * None. + * + * Side effects: + * File IO. + * ---------------------------------------------------------------------------- + */ + +void +TxLogResume() +{ + if (txLogFile == NULL) + return; + + txLogFlags &= ~TX_LOG_SUSPEND; } /* @@ -681,25 +826,61 @@ txLogCommand(cmd) 0 }; +#ifdef MAGIC_WRAPPER + char *pfix = ""; +#else + char *pfix = ":"; +#endif + + /* Do not do anything if there is no log file */ if (txLogFile == (FILE *) NULL) return; - if (cmd->tx_wid >= 0) { - /* Command has a window associated with it. */ - fprintf(txLogFile, ":setpoint %d %d %d\n", - cmd->tx_p.p_x, cmd->tx_p.p_y, cmd->tx_wid); - } else { - /* No window associated with the command. */ - fprintf(txLogFile, ":setpoint %d %d\n", - cmd->tx_p.p_x, cmd->tx_p.p_y); - } + /* Do not write anything if the log file is suspended */ + if (txLogFlags & TX_LOG_SUSPEND) return; if (cmd->tx_argc > 0) { int i; - fprintf(txLogFile, ":%s", cmd->tx_argv[0]); + char *postns; + + /* Do not output "logcommand" commands to the log file. + * Do not output "*bypass" commands to the log file. + */ + postns = strstr(cmd->tx_argv[0], "::"); + if (postns == NULL) + postns = cmd->tx_argv[0]; + else + postns += 2; + + if (!strncmp(postns, "logc", 4)) + return; + else if (!strcmp(postns, "*bypass")) + return; + + /* Commands ending in "cursor" should be preceeded by a set point */ + /* to indicate where the pointer was at the time of the command. */ + + if (!strcmp(cmd->tx_argv[cmd->tx_argc - 1], "cursor")) + { + if (cmd->tx_wid >= 0) + { + /* Command has a window associated with it. */ + fprintf(txLogFile, "%ssetpoint %d %d %d\n", + pfix, cmd->tx_p.p_x, cmd->tx_p.p_y, cmd->tx_wid); + } + else + { + /* No window associated with the command. */ + fprintf(txLogFile, "%ssetpoint %d %d\n", + pfix, cmd->tx_p.p_x, cmd->tx_p.p_y); + } + } + else if (!strcmp(postns, "setpoint")) return; + + fprintf(txLogFile, "%s%s", pfix, cmd->tx_argv[0]); for (i = 1; i < cmd->tx_argc; i++) { - fprintf(txLogFile, " '%s'", cmd->tx_argv[i]); + fprintf(txLogFile, " %s", cmd->tx_argv[i]); } fprintf(txLogFile, "\n"); } @@ -722,12 +903,13 @@ txLogCommand(cmd) default: {ASSERT(FALSE, "txLogCommand"); break; }; } - fprintf(txLogFile, ":pushbutton %s %s\n", - txButTable[but], txActTable[act]); + fprintf(txLogFile, "%spushbutton %s %s\n", + pfix, txButTable[but], txActTable[act]); } - if (txLogUpdate) - fprintf(txLogFile, ":updatedisplay\n"); - (void) fflush(txLogFile); + if (txLogFlags & TX_LOG_UPDATE) + fprintf(txLogFile, "%supdatedisplay\n", pfix); + + fflush(txLogFile); } /* @@ -1183,6 +1365,8 @@ TxTclDispatch(clientData, argc, argv, quiet) result = WindSendCommand((MagWindow *)clientData, tclcmd, quiet); + if (txLogFile != NULL) txLogCommand(tclcmd); + TxFreeCommand(tclcmd); // Don't update the command number on bypass commands, or else diff --git a/textio/txcommands.h b/textio/txcommands.h index d1834ba0..831cb652 100644 --- a/textio/txcommands.h +++ b/textio/txcommands.h @@ -130,9 +130,13 @@ extern void TxSetPoint(); extern int TxGetPoint(); extern void TxClearPoint(); -/* Routine to set up command logging. +/* Routines to handle command logging. */ -extern void TxLogCommands(); +extern void TxLogStart(); +extern void TxLogStop(); +extern void TxLogUpdate(); +extern void TxLogSuspend(); +extern void TxLogResume(); /* Routines for handling input events. A typical device driver in the diff --git a/windows/windCmdAM.c b/windows/windCmdAM.c index ecf784f1..e2ce0639 100644 --- a/windows/windCmdAM.c +++ b/windows/windCmdAM.c @@ -863,18 +863,23 @@ windHelpCmd(w, cmd) ASSERT(FALSE, windHelpCmd); } -static char *logKeywords[] = - { - "update", - 0 - }; - /* * ---------------------------------------------------------------------------- * windLogCommandsCmd -- * * Log the commands and button pushes in a file. * + * Syntax: + * logcommands start 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 [update] Start a new command log file with updating + * logcommands End and close a command log file + * * Results: * None. * @@ -883,35 +888,74 @@ static char *logKeywords[] = * ---------------------------------------------------------------------------- */ +#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; - bool update; + char *fileName = NULL; + unsigned char flags = 0; + int idx = LOG_CMD_STOP; + + static char *logKeywords[] = {"start", "stop", "update", "suspend", "resume", 0}; if ((cmd->tx_argc < 1) || (cmd->tx_argc > 3)) goto usage; - update = FALSE; - - if (cmd->tx_argc == 1) - fileName = NULL; - else - fileName = cmd->tx_argv[1]; - - if (cmd->tx_argc == 3) { - int i; - i = Lookup(cmd->tx_argv[cmd->tx_argc - 1], logKeywords); - if (i != 0) goto usage; - update = TRUE; + if (cmd->tx_argc > 1) + { + idx = Lookup(cmd->tx_argv[1], logKeywords); + if (idx < 0) fileName = cmd->tx_argv[1]; } - TxLogCommands(fileName, update); - return; + if (cmd->tx_argc == 3) + { + int i; + + if (fileName == NULL) + fileName = cmd->tx_argv[2]; + else + { + /* Legacy behavior: Allow "logcommands update" + * to mean "logcommands start ; 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 [filename [update]]\n", cmd->tx_argv[0]); + TxError("Usage: %s [start|stop|update|suspend|resume [filename]]\n", + cmd->tx_argv[0]); } /* diff --git a/windows/windSend.c b/windows/windSend.c index a086be5a..0954aa31 100644 --- a/windows/windSend.c +++ b/windows/windSend.c @@ -311,6 +311,24 @@ WindSendCommand(w, cmd, quiet) if ((WindNewButtons == 0) && (windGrabber != (WindClient) NULL)) WindReleaseInput((WindClient) rc); + /* This is a bit of a hack, because it shouldn't be done in so many + * places, but the command logging needs to regenerate the setpoint + * positions as commands, and it needs the command's Y value to be + * correct for the command. It is okay to modify the command's + * recorded position value, because logging the command is the only + * other thing done with the structure. + */ + if (w != NULL) + { + switch (WindPackageType) + { + case WIND_X_WINDOWS: + /* Windows have origin at lower-left corner */ + cmd->tx_p.p_y = w->w_allArea.r_ytop - cmd->tx_p.p_y; + break; + } + } + return 0; }