Substantially revised the "macro" command callback functions and

the "tool" implementation.  Previously, the "tool" implementation
would overwrite the button bindings for the mouse.  The problem
with that is that if the user customizes one or more of the
bindings, such as using the mouse wheel for zooming instead of
panning, then the custom macro gets obliterated when the tool
changes.  The reimplementation creates multiple macro sets which
are unique to each tool.  The "enable_tools" function sets up
the initial unique default bindings for each tool.  The user
can then customize the bindings for any tool, and the
implementation no longer requires the constant changing of key
bindings.  Note that the new implementation is slightly less
efficient because the macro tables are found by string hash
based on the name of the tool or client type, not the integer
client ID.  The reduction in efficiency is balanced by the
increased flexibility of the macros.
This commit is contained in:
R. Timothy Edwards 2025-10-09 15:43:54 -04:00
parent 53e7dfe04c
commit 27c423c2ed
10 changed files with 283 additions and 48 deletions

View File

@ -1 +1 @@
8.3.559
8.3.560

View File

@ -671,6 +671,15 @@ CmdTool(
if (strcmp(cmd->tx_argv[1], "info") == 0)
DBWPrintButtonDoc();
else if (strcmp(cmd->tx_argv[1], "type") == 0)
{
char *toolType = DBWGetButtonHandler();
#ifdef MAGIC_WRAPPER
Tcl_SetObjResult(magicinterp, Tcl_NewStringObj(toolType, -1));
#else
TxPrintf("Current tool is \"%s\".\n", toolType);
#endif /* MAGIC_WRAPPER */
}
else (void) DBWChangeButtonHandler(cmd->tx_argv[1]);
}

View File

@ -27,6 +27,7 @@ static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/
#include <stdio.h>
#include <string.h>
#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "utils/geometry.h"
#include "tiles/tile.h"
@ -240,6 +241,38 @@ DBWChangeButtonHandler(name)
return oldName;
}
/*
* ----------------------------------------------------------------------------
*
* DBWGetButtonHandler --
*
* Return the name of the current button handler. This does the same
* thing as DBWChangeButtonHandler(name) with an invalid name, but
* without printing the error messages. However, if magic is running
* with the Tcl interpreter wrapper, then return the string value of
* $Opts(tool), if it exists.
*
* Results:
* A pointer to the name of the current button handler.
*
* Side effects:
* None.
*
* ----------------------------------------------------------------------------
*/
char *
DBWGetButtonHandler()
{
#ifdef MAGIC_WRAPPER
char *toolName;
toolName = (char *)Tcl_GetVar2(magicinterp, "Opts", "tool", TCL_GLOBAL_ONLY);
if (toolName != NULL) return toolName;
#endif
return dbwButtonHandlers[dbwButtonCurrentIndex];
}
/*
* ----------------------------------------------------------------------------
*

View File

@ -145,6 +145,7 @@ extern void (*DBWButtonCurrentProc)();
typedef void (*cb_database_buttonhandler_t)(MagWindow *w, TxCommand *cmd);
extern void DBWAddButtonHandler(const char *name, const cb_database_buttonhandler_t proc,
int cursor, const char *doc);
extern char *DBWGetButtonHandler();
extern char *DBWChangeButtonHandler();
extern void DBWPrintButtonDoc();
extern void DBWBoxHandler();

View File

@ -516,8 +516,55 @@ proc magic::enable_tools {} {
magic::macro space {magic::tool}
magic::macro Shift_space {magic::tool box}
# Set these first because the magic::tool command defined
# in this script depends on them being valid.
set Opts(tool) box
set Opts(motion) {}
# Set up unique key macros for each individual tool. This
# effectively defines what the tools are, since each tool
# is really just a collection of unique key bindings. The
# default bindings are copied from the "box" tool, and
# then replacement bindings for button actions are applied.
# The user can change these bindings at will by using the
# "macro" command when the tool is active.
magic::macro copy wiring
magic::macro copy netlist
magic::macro copy pick
magic::tool wiring
macro Button1 "magic::trackwire %W pick"
macro Button2 "magic::trackwire %W done"
macro Button3 "magic::trackwire %W cancel"
macro Shift_Button1 "wire incr type ; wire show"
macro Shift_Button2 "wire switch"
macro Shift_Button3 "wire decr type ; wire show"
macro Button4 "wire incr width ; wire show"
macro Button5 "wire decr width ; wire show"
magic::tool netlist
macro Button1 "netlist select"
macro Button2 "netlist join"
macro Button3 "netlist terminal"
# Remove shift-button bindings
macro Shift_Button1 ""
macro Shift_Button2 ""
macro Shift_Button3 ""
macro Button4 "scroll u .05 w"
macro Button5 "scroll d .05 w"
magic::tool pick
macro Button1 "magic::keepselect %W"
macro Shift_Button2 "magic::startselect %W copy"
macro Button2 "magic::startselect %W pick"
macro Button3 "magic::cancelselect %W"
macro Shift_Button1 "box corner bl cursor"
macro Shift_Button3 "box move ur cursor"
macro Button4 "scroll u .05 w"
macro Button5 "scroll d .05 w"
magic::tool box
set Opts(origin) {0 0}
set Opts(backupinterval) 60000
magic::crashbackups start
@ -644,6 +691,9 @@ proc magic::tool {{type next}} {
}
}
switch $type {
type {
return $Opts(tool)
}
info {
# print information about the current tool.
puts stdout "Current tool is $Opts(tool)."
@ -675,63 +725,32 @@ proc magic::tool {{type next}} {
if {[llength [macro Control_Button3]] > 0} {
macro Control_Button3
}
if {[llength [macro Button4]] > 0} {
macro Button4
}
if {[llength [macro Button5]] > 0} {
macro Button5
}
}
box {
puts stdout {Switching to BOX tool.}
set Opts(tool) box
cursor 0 ;# sets the cursor
macro Button1 "box move bl cursor; magic::boxview %W %1"
macro Shift_Button1 "box corner bl cursor; magic::boxview %W %1"
macro Button2 "paint cursor"
macro Shift_Button2 "erase cursor"
macro Button3 "box corner ur cursor"
macro Shift_Button3 "box move ur cursor; magic::boxview %W %1"
macro Button4 "scroll u .05 w; magic::boxview %W %1"
macro Button5 "scroll d .05 w; magic::boxview %W %1"
macro Shift_XK_Pointer_Button4 "scroll r .05 w; magic::boxview %W %1"
macro Shift_XK_Pointer_Button5 "scroll l .05 w; magic::boxview %W %1"
}
wiring {
puts stdout {Switching to WIRING tool.}
set Opts(tool) wiring
cursor 19 ;# sets the cursor
macro Button1 "magic::trackwire %W pick"
macro Button2 "magic::trackwire %W done"
macro Button3 "magic::trackwire %W cancel"
macro Shift_Button1 "wire incr type ; wire show"
macro Shift_Button2 "wire switch"
macro Shift_Button3 "wire decr type ; wire show"
macro Button4 "wire incr width ; wire show"
macro Button5 "wire decr width ; wire show"
}
netlist {
puts stdout {Switching to NETLIST tool.}
set Opts(tool) netlist
cursor 18 ;# sets the cursor
macro Button1 "netlist select"
macro Button2 "netlist join"
macro Button3 "netlist terminal"
# Remove shift-button bindings
macro Shift_Button1 ""
macro Shift_Button2 ""
macro Shift_Button3 ""
macro Button4 "scroll u .05 w"
macro Button5 "scroll d .05 w"
}
pick {
puts stdout {Switching to PICK tool.}
set Opts(tool) pick
cursor 22 ;# set the cursor
macro Button1 "magic::keepselect %W"
macro Shift_Button2 "magic::startselect %W copy"
macro Button2 "magic::startselect %W pick"
macro Button3 "magic::cancelselect %W"
macro Shift_Button1 "box corner bl cursor"
macro Shift_Button3 "box move ur cursor"
macro Button4 "scroll u .05 w"
macro Button5 "scroll d .05 w"
}
}

View File

@ -38,6 +38,7 @@ static char rcsid[] __attribute__ ((unused)) = "$Header: /usr/cvsroot/magic-8.0/
#include "utils/malloc.h"
#include "utils/macros.h"
#include "windows/windows.h"
#include "dbwind/dbwind.h"
/* C99 compat */
#include "textio/textio.h"
@ -58,20 +59,29 @@ HashTable MacroClients;
* Side Effects:
* Memory allocated for the hash table MacroClients.
*
* Note:
* Hash type changed from WORDKEYS to STRINGKEYS.
* This is slightly less efficient but allows the
* layout window client to be divided into separate
* macro clients for each tool type, accessed by
* name.
*
*---------------------------------------------------------
*/
void
MacroInit()
{
HashInit(&MacroClients, 4, HT_WORDKEYS);
HashInit(&MacroClients, 4, HT_STRINGKEYS);
}
/*
*---------------------------------------------------------
* MacroDefine ---
* MacroDefineByName ---
*
* This procedure defines a macro.
* This procedure defines a macro. The macro table is
* defined by name, not by client. MacroDefine() is a
* wrapper for this routine.
*
* Results:
* None.
@ -83,8 +93,8 @@ MacroInit()
*/
void
MacroDefine(client, xc, str, help, imacro)
WindClient client; /* window client type */
MacroDefineByName(clientName, xc, str, help, imacro)
char *clientName; /* window client name */
int xc; /* full (X11) keycode of macro with modifiers */
char *str; /* ...and the string to be attached to it */
char *help; /* ...and/or the help text for the macro */
@ -95,7 +105,7 @@ MacroDefine(client, xc, str, help, imacro)
macrodef *oldMacro, *newMacro;
/* If a macro exists, delete the old string and redefine it */
h = HashFind(&MacroClients, (char *)client);
h = HashFind(&MacroClients, (char *)clientName);
clienttable = (HashTable *)HashGetValue(h);
if (clienttable == NULL)
{
@ -127,6 +137,45 @@ MacroDefine(client, xc, str, help, imacro)
newMacro->helptext = NULL;
}
/*
*------------------------------------------------------------------
* MacroDefine ---
*
* This procedure is a simple wrapper for MacroDefineByName().
* The window client is given as a client ID. This limits
* macros to one set per client type. To allow a more
* flexible macro handling, if the client is a layout window
* (DBWclientID), then the name is instead taken from the
* name of the tool currently active. This allows macros
* to be uniquely defined for each tool.
*
* Results:
* None.
*
* Side Effects:
* The string passed is copied and considered to be the
* macro definition for the character.
*------------------------------------------------------------------
*/
void
MacroDefine(client, xc, str, help, imacro)
WindClient client; /* window client type */
int xc; /* full (X11) keycode of macro with modifiers */
char *str; /* ...and the string to be attached to it */
char *help; /* ...and/or the help text for the macro */
bool imacro; /* is this an interactive macro? */
{
char *clientName = NULL;
if (client == DBWclientID)
clientName = DBWGetButtonHandler();
if (clientName == NULL)
clientName = WindGetClientName(client);
MacroDefineByName(clientName, xc, str, help, imacro);
}
/*
*---------------------------------------------------------
* MacroDefineHelp ---
@ -152,9 +201,15 @@ MacroDefineHelp(client, xc, help)
HashEntry *h;
HashTable *clienttable;
macrodef *curMacro;
char *clientName = NULL;
if (client == DBWclientID)
clientName = DBWGetButtonHandler();
if (clientName == NULL)
clientName = WindGetClientName(client);
/* If a macro exists, delete the old string and redefine it */
h = HashFind(&MacroClients, (char *)client);
h = HashFind(&MacroClients, (char *)clientName);
clienttable = (HashTable *)HashGetValue(h);
if (clienttable == NULL) return;
@ -194,9 +249,17 @@ MacroRetrieve(client, xc, iReturn)
HashEntry *h;
HashTable *clienttable;
macrodef *cMacro;
char *clientName = NULL;
if (client == DBWclientID)
clientName = DBWGetButtonHandler();
if (clientName == NULL)
clientName = WindGetClientName(client);
if (clientName == NULL)
return NULL;
/* If a macro exists, delete the old string and redefine it */
h = HashLookOnly(&MacroClients, (char *)client);
h = HashLookOnly(&MacroClients, (char *)clientName);
if (h != NULL)
{
clienttable = (HashTable *)HashGetValue(h);
@ -241,9 +304,15 @@ MacroRetrieveHelp(client, xc)
HashEntry *h;
HashTable *clienttable;
macrodef *cMacro;
char *clientName = NULL;
if (client == DBWclientID)
clientName = DBWGetButtonHandler();
if (clientName == NULL)
clientName = WindGetClientName(client);
/* If a macro exists, delete the old string and redefine it */
h = HashLookOnly(&MacroClients, (char *)client);
h = HashLookOnly(&MacroClients, (char *)clientName);
if (h != NULL)
{
clienttable = (HashTable *)HashGetValue(h);
@ -340,8 +409,14 @@ MacroDelete(client, xc)
HashEntry *h;
HashTable *clienttable;
macrodef *cMacro;
char *clientName = NULL;
h = HashLookOnly(&MacroClients, (char *)client);
if (client == DBWclientID)
clientName = DBWGetButtonHandler();
if (clientName == NULL)
clientName = WindGetClientName(client);
h = HashLookOnly(&MacroClients, (char *)clientName);
if (h != NULL)
{
clienttable = (HashTable *)HashGetValue(h);
@ -365,6 +440,62 @@ MacroDelete(client, xc)
}
}
/*
* ----------------------------------------------------------------------------
* MacroCopy --
* Copy all macros from one table to another.
* This is used with the "tool" command to simplify the process
* of defining new macro sets for button and keypress actions
* for a new tool type. It copies all of the macros from the
* table for the client passed as (WindClient type) "client" to
* the table for the client named by (string) "clientkey".
*
* Results:
* None.
*
* Side effects:
* New hash entries are created.
* ----------------------------------------------------------------------------
*/
void
MacroCopy(client, clientkey)
WindClient client; /* Current window client */
char *clientkey; /* Name of client to copy macros to */
{
HashTable *clienttable;
HashTable *copytable;
HashEntry *h, *he;
HashSearch hs;
char *clientName;
int cKey;
macrodef *cMacro;
if (clientkey == NULL) return;
if (client == (WindClient)NULL) return;
if (client == DBWclientID)
clientName = DBWGetButtonHandler();
if (clientName == NULL)
clientName = WindGetClientName(client);
h = HashLookOnly(&MacroClients, (char *)clientName);
if (h == NULL) return; /* No clients, nothing can be done */
clienttable = (HashTable *)HashGetValue(h);
if (clienttable == NULL) return; /* Also nothing can be done */
HashStartSearch(&hs);
while (TRUE)
{
he = HashNext(clienttable, &hs);
if (he == NULL) break;
cMacro = (macrodef *)HashGetValue(he);
cKey = (int)CD2INT(he->h_key.h_ptr);
MacroDefineByName(clientkey, cKey, cMacro->macrotext,
cMacro->helptext, cMacro->interactive);
}
}
/*
* ----------------------------------------------------------------------------

View File

@ -48,6 +48,7 @@ extern char *MacroRetrieveHelp(); /* returns a malloc'ed string */
extern char *MacroSubstitute(); /* returns a malloc'ed string */
extern void MacroDelete();
extern char *MacroName(); /* returns a malloc'ed string */
extern void MacroCopy();
extern int MacroKey();
extern int MacroCode();

View File

@ -1087,6 +1087,22 @@ windDoMacro(w, cmd, interactive)
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]);
}
return;
}
else break;
}

View File

@ -323,6 +323,30 @@ WindGetClient(clientName, exact)
return (WindClient) found;
}
/*
* ----------------------------------------------------------------------------
* WindGetClientName --
*
* Looks up the name of a client from the unique client ID for a window.
*
* Results:
* A pointer to the client name is returned. If the client is NULL,
* then NULL is returned.
*
* Side effects:
* None.
* ----------------------------------------------------------------------------
*/
char *
WindGetClientName(client)
WindClient client;
{
clientRec *clientrec = (clientRec *)client;
if (client == (WindClient)NULL) return NULL;
return clientrec->w_clientName;
}
/*
* ----------------------------------------------------------------------------
* WindNextClient --

View File

@ -240,6 +240,7 @@ extern MagWindow *WindCreate();
extern WindClient WindGetClient();
extern WindClient WindNextClient();
extern WindClient WindAddClient();
extern char *WindGetClientName();
extern void WindInit();
extern void WindUpdate();
extern void WindDrawBorder();