Macro Deletion Utility

These are tools and utilities that make it easier to run games. This includes Lib: macro tokens dropped into MapTool to manage the game, a conversion file for CharacterTool to allow use in MapTool, or just about anything else you can think of -- except graphics with macros and anything specific to a particular campaign framework. Those are already covered by the Tilesets subforum and the Links and External Resources forum.

Moderators: dorpond, trevor, Azhrei, giliath, Gamerdude, jay, Mr.Ice

User avatar
Rumble
Deity
Posts: 6235
Joined: Tue Jul 01, 2008 7:48 pm

Macro Deletion Utility

Post by Rumble »

I think this belongs in "User Creations," but if not, Azhrei please feel free to move it.

I work with a framework that has a "standard set" of macros, but frequently not all macros are needed for each token (say, NPCs don't always need all the same macros PCs need). So I'm often deleting unnecessary macros. Doing it occasionally is no big deal, but if you have to delete a bunch, it can get laborious with right-click, delete, confirm.

In any case, I figured I'd write a macro to do it, and found out that - as far as I could find - there were no functions to:

a) easily get the labels of a macro in a particular macro group
b) get the names of the macro groups on a particular token, or
c) get the label of a macro from its index

(of course, there could be undocumented functions that do this, in which case, this would have been WAY simpler). In any case, I hacked at it for a while and what resulted is a macro that presents a tabbed dialog containing all the macros on the token, sorted into tabs for each group, with a checkbox next to them, like so:

See post below for updates to Code and Undo Delete macro

Download Delete Macros Utility: Delete Macros

Download Undo Delete utility: Undo Delete Macros
Attachments
delmacros-shot.jpg
delmacros-shot.jpg (22.46 KiB) Viewed 8074 times
Last edited by Rumble on Tue Jun 30, 2009 3:18 pm, edited 7 times in total.

User avatar
Rumble
Deity
Posts: 6235
Joined: Tue Jul 01, 2008 7:48 pm

Re: Macro Deletion Utility

Post by Rumble »

Ah, one thing I forgot to add: this breaks (at the moment) if there are multiple macros with the same label on the token. I will fix that!

Edit: fixed. It will now handle multiple duplicate macros.

User avatar
Haman
Cave Troll
Posts: 75
Joined: Wed Sep 17, 2008 2:29 pm

Re: Macro Deletion Utility

Post by Haman »

Wow. Thank you! Learning as I go with macros, but this was one thing that was already getting a little tedious. Looks like it solves it brilliantly.

User avatar
Rumble
Deity
Posts: 6235
Joined: Tue Jul 01, 2008 7:48 pm

Re: Macro Deletion Utility

Post by Rumble »

Haman wrote:Wow. Thank you! Learning as I go with macros, but this was one thing that was already getting a little tedious. Looks like it solves it brilliantly.
I'm glad it'll come in handy. I'm going to upload the latest version (I think the one here may ignore macros that aren't in a named group, which is a bit of an oversight). The filename will be the same and all, just some tweaks to the code (it also will include tooltips from the macro, in case you have any configured).

I'm toying with the notion of a version that lets you edit a macro. There are, I think, enough functions to let you check off a macro, edit it, and save it (well, delete and recreate it). Might not work, but it'd be interesting to play with.

User avatar
Azhrei
Site Admin
Posts: 12086
Joined: Mon Jun 12, 2006 1:20 pm
Location: Tampa, FL

Re: Macro Deletion Utility

Post by Azhrei »

Rumble, I think this is a fine place for it. :) Perhaps a link here from the wiki? Or the other way around? This is useful enough that I'm going to at least sticky it for now...

And I agree that something like this is sorely needed. I, too, have felt the sting of repetitive stress syndrome with all the right-click-and-left-click to delete individual macros. ;)

User avatar
Rumble
Deity
Posts: 6235
Joined: Tue Jul 01, 2008 7:48 pm

Re: Macro Deletion Utility

Post by Rumble »

Azhrei wrote:Rumble, I think this is a fine place for it. :) Perhaps a link here from the wiki? Or the other way around? This is useful enough that I'm going to at least sticky it for now...

And I agree that something like this is sorely needed. I, too, have felt the sting of repetitive stress syndrome with all the right-click-and-left-click to delete individual macros. ;)
Hey, thanks. I'll put it in the code cookbook on the Wiki, and links can go both ways. Long as everybody knows there's no "undo" (although...you know...I could make one - just save all the macro properties to a Lib:Token, and then you could recreate the deleted macro...fascinating).

User avatar
Rumble
Deity
Posts: 6235
Joined: Tue Jul 01, 2008 7:48 pm

Re: Macro Deletion Utility

Post by Rumble »

Okay, an update: I realized that with this macro, it was in fact entirely possible to create an "undo" option. It's a separate macro, but basically, it lets you undo the most recent set of deletions (only the most recent set! If you run Delete Macros, and then run it again, only the latest set of "undo's" is saved). But, it will remember all the macros you delete in a single go, so it's handy if you goof up.
DeleteMacros

Code: Select all

<!--General method to get macro groups and labels on a token. This version is to delete unwanted or unneeded macros-->
<!--but it is possible to use this information for whatever purpose you might think of-->

<!--Get a list of all macros on a token-->
[h,if(getMacros() != ""): fullMacList = getMacros(); assert(0, "There are no macros on the token <b>"+token.name+"</b>", 0)]
[h:fullMacList = listSort(fullMacList,"N+")]
[h:fullMacList = json.fromList(fullMacList))]
[h:macList = json.unique(fullMacList)]

<!--Initialize a few variables that will be used later on-->
[h:inputParams = ""]
[h:groupArray = "[]"]
[h:macrosInGroup = "{}"]

<!--Loop through the list of macros, and extract the GROUP property from the result of getMacroProps()-->

[h:tmpArray = ""]

<!--Get the list of all groups that belong to all macros on the token-->
[h,foreach(macro,macList),CODE:
{
    [indexes = getMacroIndexes(macro)]
    [foreach(index,indexes): tmpArray = if(getStrProp(getMacroProps(index),"group") == "", json.append(tmpArray, "Top Level"), json.append(tmpArray, getStrProp(getMacroProps(index), "group")))]
}]

<!--Since we need only the names of each unique group, use json.unique() to return a list of the unique entries in groupArray-->
[h:uniqueGroupArray = json.unique(tmpArray)]
[h:uniqueGroupList = json.toList(uniqueGroupArray)]
[h:uniqueGroupList = listSort(uniqueGroupList, "N+")]

<!--A stupidly complex group of loops and ifs to sort macros out into groups and avoid duplication-->
[h,foreach(uniqueGroup, uniqueGroupList),CODE:
{
    [tempObj = ""]
    [h,foreach(macro,macList),CODE:
    {
       [indexes = getMacroIndexes(macro)]
       [h,foreach(index, indexes): tempObj = 
              if(
                  if(getStrProp(getMacroProps(index),"group")=="", "Top Level", getStrProp(getMacroProps(index),"group")) == uniqueGroup,
        listAppend(tempObj, macro), tempObj)]
    }]
    [h:macrosInGroup = json.set(macrosInGroup, uniqueGroup, tempObj)]
}]

<!--Build the input string for the tabs and checkboxes-->
[h,foreach(uniqueGroup, uniqueGroupList),CODE:
{  
   [macroLabels = listSort(json.get(macrosInGroup, uniqueGroup), "N+")]
   [h:inputParams = json.append(inputParams, "group_"+roll.count+"|"+uniqueGroup+"||TAB")]
   [h:inputParams = json.append(inputParams, ".|<html><b>Select the Macros you wish to delete. Note: There is no undo!</b></html>||LABEL|SPAN=TRUE")]
   [foreach(macroLabel, macroLabels),CODE:
   {
      [idx = getMacroIndexes(macroLabel)]
      [foreach(index, idx): inputParams =if(if(getStrProp(getMacroProps(index),"group")=="", "Top Level", getStrProp(getMacroProps(index),"group")) == uniqueGroup, json.append(inputParams, "macro_"+index+"|0|<html>"+macroLabel+" "+if(getStrProp(getMacroProps(index),"tooltip")=="", "(no tooltip available)", "("+getStrProp(getMacroProps(index),"tooltip")+")")+"</html>|CHECK"), inputParams)]
   }]
}]

<!--Convert inputParams from a JSON string array to a string list, using ## as the list delimiter (required for this trick)-->
[h:inputParams = json.toList(inputParams, "##")]

<!--pass inputParams to the input() function to generate an input dialog with tabs for each macro group, and the macros listed-->
[h:status = input(inputParams)]
[h:abort(status)]


<!--Finally, go through each macro on the token (again!) and, if its box is checked, delete it.-->
[h:delMacroUndo = "{}"]
[h,foreach(macro,macList),CODE:
{
    [macroIndexes =getMacroIndexes(macro)]
    [foreach(index, macroIndexes),code:
    {
       [checked = eval("macro_"+index)] 
       [if(checked): delMacroUndo = json.set(delMacroUndo, macro, getMacroProps(index, "json"))]
       [if(checked): removeMacro(index)]
     }]
}]

<!--Set a hidden property on the token that will hold the undo list-->
[h:setProperty("Rumble.DeleteMacroUndo", delMacroUndo)]
Macro(s) removed.
 
UndoDelete

Code: Select all

[h:deletedMacros = getProperty("Rumble.DeleteMacroUndo")]

[h:deletedMacroList = json.fields(deletedMacros)]

[h:status = input("macToRestore|"+deletedMacroList+"|Choose Macro to Restore|LIST|SELECT=0 VALUE=STRING")]
[h:abort(status)]

[h:restoreProps = json.get(deletedMacros, macToRestore)]

[h:createMacro(restoreProps)]
 
These are standalone macros - they require no library token or anything. They should get all token groups, and be framework- and campaign-agnostic; I put them in my Global Macros panel.
Last edited by Rumble on Wed Jul 01, 2009 8:35 am, edited 1 time in total.

User avatar
Rumble
Deity
Posts: 6235
Joined: Tue Jul 01, 2008 7:48 pm

Re: Macro Deletion Utility

Post by Rumble »

Another modification: Macro Copier. With this utility, you:

1. Select a destination token
2. Add a suffix - if desired - to distinguish copied macros from originals on the destination token.
3. Select one or more macros to be copied from the source token to the destination token.

Basically, it's just the macro deletion utility, with the delete part stripped out, and a "macro copying" procedure in its place. I made it so that I could copy multiple macros to another token without macro import/export or dragging and dropping one at a time. For moving a single macro, drag and drop is still quicker. And besides, I don't sleep much...:p
Spoiler

Code: Select all

<!--General method to get macro groups and labels on a token. This version is to delete unwanted or unneeded macros-->
<!--but it is possible to use this information for whatever purpose you might think of-->

<!--Get a list of all macros on a token-->
[h,if(getMacros() != ""): fullMacList = getMacros(); assert(0, "There are no macros on the token <b>"+token.name+"</b>", 0)]
[h:fullMacList = listSort(fullMacList,"N+")]
[h:fullMacList = json.fromList(fullMacList))]
[h:macList = json.unique(fullMacList)]

<!--Initialize a few variables that will be used later on-->
[h:inputParams = ""]
[h:groupArray = "[]"]
[h:macrosInGroup = "{}"]

<!--Loop through the list of macros, and extract the GROUP property from the result of getMacroProps()-->

[h:tmpArray = ""]

[h,foreach(macro,macList),CODE:
{
    [indexes = getMacroIndexes(macro)]
    [foreach(index,indexes): tmpArray = if(getStrProp(getMacroProps(index),"group") == "", json.append(tmpArray, "Top Level"), json.append(tmpArray, getStrProp(getMacroProps(index), "group")))]
}]

<!--Since we need only the names of each unique group, use json.unique() to return a list of the unique entries in groupArray-->
[h:uniqueGroupArray = json.unique(tmpArray)]
[h:uniqueGroupList = json.toList(uniqueGroupArray)]
[h:uniqueGroupList = listSort(uniqueGroupList, "N+")]

[h,foreach(uniqueGroup, uniqueGroupList),CODE:
{
    [tempObj = ""]
    [h,foreach(macro,macList),CODE:
    {
       [indexes = getMacroIndexes(macro)]
       [h,foreach(index, indexes): tempObj = 
              if(
                  if(getStrProp(getMacroProps(index),"group")=="", "Top Level", getStrProp(getMacroProps(index),"group")) == uniqueGroup,
        listAppend(tempObj, macro), tempObj)]
    }]
    [h:macrosInGroup = json.set(macrosInGroup, uniqueGroup, tempObj)]
}]

[h,foreach(uniqueGroup, uniqueGroupList),CODE:
{  
   [macroLabels = listSort(json.get(macrosInGroup, uniqueGroup), "N+")]
   [h:inputParams = json.append(inputParams, "group_"+roll.count+"|"+uniqueGroup+"||TAB")]
   [h:inputParams = json.append(inputParams, ".|<html>Select the Macros you wish to copy. Make sure to configure the copy settings in the <b>Copy Settings</b> tab.</html>||LABEL|SPAN=TRUE")]
   [foreach(macroLabel, macroLabels),CODE:
   {
      [idx = getMacroIndexes(macroLabel)]
      [foreach(index, idx): inputParams =if(if(getStrProp(getMacroProps(index),"group")=="", "Top Level", getStrProp(getMacroProps(index),"group")) == uniqueGroup, json.append(inputParams, "macro_"+index+"|0|<html>"+macroLabel+" "+if(getStrProp(getMacroProps(index),"tooltip")=="", "(no tooltip available)", "("+getStrProp(getMacroProps(index),"tooltip")+")")+"</html>|CHECK"), inputParams)]
   }]
}]

<!--Add a Tab to hold the Copy Target info-->
[h:inputParams = json.append(inputParams, "copyTab|Copy Settings||TAB")]
[h:inputParams = json.append(inputParams, "copyTo|"+getTokenNames()+"|Select destination token|LIST|SELECT=0 VALUE=STRING")]
[h:inputParams = json.append(inputParams, "appendInc|0|Add suffix to macro label (so that duplicate macros are not created)|CHECK")]
[h:inputParams = json.append(inputParams, "suffix|(copy)|Enter suffix to append")]

<!--Convert inputParams from a JSON string array to a string list, using ## as the list delimiter (required for this trick)-->
[h:inputParams = json.toList(inputParams, "##")]

<!--pass inputParams to the input() function to generate an input dialog with tabs for each macro group, and the macros listed-->
[h:status = input(inputParams)]
[h:abort(status)]


<!--Finally, go through each macro on the token (again!) and, if its box is checked, copy it to the new token.-->

[h,foreach(macro,macList),CODE:
{
    [macroIndexes =getMacroIndexes(macro)]
    [foreach(index, macroIndexes),code:
    {
       [macProps = getMacroProps(index, "json")]
       [currLabel = json.get(macProps, "label")]
       [newLabel = currLabel + suffix]
       [checked = eval("macro_"+index)] 
       [if(appendInc): macProps = json.set(macProps, "label", newLabel)]
       [if(checked),token(copyTo): createMacro(macProps)]
     }]
}]

Macros copied.
 

User avatar
Azhrei
Site Admin
Posts: 12086
Joined: Mon Jun 12, 2006 1:20 pm
Location: Tampa, FL

Re: Macro Deletion Utility

Post by Azhrei »

Rumble wrote:Another modification: Macro Copier.
Now this I can use right now. Thank you very much!

Tomorrow night I implement the latest lmarkus' campaign file and I'm including my spell macros. But it's currently a pain if you're playing a spellcaster who receives an entire level of spells all at once to drag-n-drop each one onto your own token. But I can add this macro to the "holding token" (the one with all the different macros on it) and add a few prompts for which spell level, then have the entire group copied over.

Or since each spell level is in its own group, just a high-level checkbox for the group? That could bring up a second Wiki: input() with the macros from that group with all of them selected by default (for divine casters) or unselected (for arcane casters).

Thanks again, Rumble!

User avatar
Rumble
Deity
Posts: 6235
Joined: Tue Jul 01, 2008 7:48 pm

Re: Macro Deletion Utility

Post by Rumble »

Azhrei wrote:
Rumble wrote:Another modification: Macro Copier.
Now this I can use right now. Thank you very much!

Tomorrow night I implement the latest lmarkus' campaign file and I'm including my spell macros. But it's currently a pain if you're playing a spellcaster who receives an entire level of spells all at once to drag-n-drop each one onto your own token. But I can add this macro to the "holding token" (the one with all the different macros on it) and add a few prompts for which spell level, then have the entire group copied over.

Or since each spell level is in its own group, just a high-level checkbox for the group? That could bring up a second Wiki: input() with the macros from that group with all of them selected by default (for divine casters) or unselected (for arcane casters).

Thanks again, Rumble!
You're welcome!

I was thinking about a "Copy Entire Group" option; just never got around to it. It would need tweaking to do so; at this point the copy routine just runs through the whole list and copies every checked macro, but it's possible.

User avatar
Azhrei
Site Admin
Posts: 12086
Joined: Mon Jun 12, 2006 1:20 pm
Location: Tampa, FL

Re: Macro Deletion Utility

Post by Azhrei »

Okay, I've been doing some playing around with your code. :)

First, there appears to be a problem with Wiki: createMacro(): if I pass it a macro index that already exists on the target token, it overwrites that existing macro. :( So I'm modifying your code to check first and if the index already exists, it'll assign a new one. (I'm going to try just deleting the "index" out of the JSON object and see if MapTool will assign a new one. Edit: Yes, this works. So just calling json.remove(..., "index") fixes the issue. I'll go update the wiki.)

Second, it appears that the foreach (roll option) doesn't work with JSON objects in all cases. I wanted to give the foreach loop an array of JSON objects and have the variable be an object each time through the loop, but it doesn't seem to always work. I'm trying to pin down the conditions. (Note: it's not documented to work this way, it just makes sense for it to. And yes, I tried using "json" as the delimiter. ;))

Third, I changed as much code as I could to use JSON arrays and objects hoping to speed things up, but I don't think it helps that much.

Last, I moved the Copy Tab to the front so that it can't be missed and added some formatting to the macro names.

TODO1: I'm thinking of adding a precursor input() that displays a list of all groups and asks which ones should be preselected on the next input().

TODO2: I'm also thinking of adding a checkbox that says not to copy macros with the same name and group on the existing token. Not sure how I want to handle this yet.
Macro Copy

Code: Select all

<!-- General method to get macro groups and labels on a token.
     This version is for copying groups of macros -->

<!-- Get a list of all macros on a token -->
[h: fullMacList = getMacros("json") ]
[h, if(json.isEmpty(fullMacList)):
    assert(0, "There are no macros on the token <b>"+token.name+"</b>") ]
[h: fullMacList = json.fromList(fullMacList) ]
[h: macList = json.unique(fullMacList)]

<!-- Put macros into a JSON object using the group name as the key
     and a JSON array as the value. -->
[h: groupObject = "{}"]
[h, foreach(macro,macList), CODE: {
    [indexes = getMacroIndexes(macro)]
    [foreach(index,indexes), CODE: {
    [ macroGroup = getStrProp(getMacroProps(index), "group") ]
    [ groupName = if(macroGroup == "", "Top Level", macroGroup) ]
    [ tmpArray = json.get(groupObject, groupName) ]
    <!-- newObject is equal to 'name: "MySpellMacro", index: "41"' -->
    [ newObject = json.set("{}", "name", macro, "index", index) ]
    [ tmpArray = json.append(tmpArray, newObject) ]
    [ groupObject = json.set(groupObject, groupName, tmpArray) ]
    } ]
}]

[h: uniqueGroups = json.sort(json.fields(groupObject, "json"), "asc") ]
[h: uniqueGroupList = json.toList(uniqueGroups) ]

<!-- 'uniqueGroupList' is a list of groups in alphabetical order. -->
<!-- We loop through them and build one tab for each group, then
     populate the tab with checkboxes for each macro. -->

<!-- Add a Tab to hold the Copy Target info -->
[h: inputParams = "" ]
[h: inputParams = json.append(inputParams, "copyTab|Copy Settings||TAB") ]
[h: inputParams = json.append(inputParams, "copyTo|"+getTokenNames()+"|Select destination token|LIST|VALUE=STRING SELECT=0") ]
[h: inputParams = json.append(inputParams, "appendInc|0|Add suffix to macro label (so that duplicate macros are not created)|CHECK") ]
[h: inputParams = json.append(inputParams, "suffix|(copy)|Enter suffix to append") ]

[h, foreach(oneGroup, uniqueGroupList), CODE: {
   [ macroArray = json.get(groupObject, oneGroup) ]
   [ macroArray = json.sort(macroArray, "asc", "name") ]
   [ groupObject = json.set(groupObject, oneGroup, macroArray) ]
   [ inputParams = json.append(inputParams, "group_"+roll.count+"|"+oneGroup+"||TAB")]
   [ inputParams = json.append(inputParams, ".|<html>Select the Macros you wish to copy. Make sure to configure the copy settings in the <b>Copy Settings</b> tab.</html>||LABEL|SPAN=TRUE")]
   [ foreach(oneMacro, macroArray), CODE: {
      [ name = json.get(oneMacro, "name") ]
      [ index = json.get(oneMacro, "index") ]
      [ props = getMacroProps(index) ]
      [ tooltip = getStrProp(props, "tooltip") ]
      [ if(tooltip==""): tooltip = "no tooltip available" ]
      [ inputParams = json.append(inputParams,
          "macro_" + index + "|0|<html><b>" + name + "</b> " +
          "(" + tooltip + ")" + "</html>|CHECK") ]
   } ]
} ]

<!-- Convert inputParams from a JSON string array to a string list, using ## as the list delimiter (required for this trick) -->
[h: inputParams = json.toList(inputParams, "##") ]

<!-- pass inputParams to the input() function to generate an input dialog with tabs for each macro group, and the macros listed -->
[h: status = input(inputParams) ]
[h: abort(status) ]

[h: indexList = "[]" ]
[h, foreach(oneGroup, uniqueGroupList), CODE: {
    [ macroArray = json.get(groupObject, oneGroup) ]
    [ foreach(obj, macroArray), CODE: {
        [ index = json.get(obj, "index") ]
        [ checked = eval("macro_" + index) ]
        [ if(checked): indexList = json.append(indexList, index) ]
    } ]
} ]

[h, token(copyTo): tokenId = currentToken() ]
[h, foreach(index, indexList), CODE: {
    [ props = getMacroProps(index, "json") ]
    [ props = json.remove(props, "index") ]
    [ label = json.get(props, "label") ]
    [ if(appendInc): props = json.set(props, "label", label + suffix) ]
    [ createMacro(props, tokenId) ]
} ]

Macros copied.  

User avatar
Rumble
Deity
Posts: 6235
Joined: Tue Jul 01, 2008 7:48 pm

Re: Macro Deletion Utility

Post by Rumble »

That's much cleaner - I got lost in triply-nested CODE blocks on my first pass, and it became very gruesome. It works, but I kinda "brute-forced" it. I don't know that it will be much faster, though; there's a lot of looping going on.

As for the foreach(): thing not handling JSON objects, I just ran into that problem myself. It sometimes works, and sometimes doesn't. Haven't figured out why. But anyway, very nice!

User avatar
RPTroll
TheBard
Posts: 3159
Joined: Tue Mar 21, 2006 7:26 pm
Location: Austin, Tx
Contact:

Re: Macro Deletion Utility

Post by RPTroll »

You guys rock. Thanks for sharing.
ImageImage ImageImageImageImage
Support RPTools by shopping
Image
Image

User avatar
Rumble
Deity
Posts: 6235
Joined: Tue Jul 01, 2008 7:48 pm

Re: Macro Deletion Utility

Post by Rumble »

Azhrei, that is so much more elegant than mine. I have learned from it!

Also, I think it's slightly faster on the same token as my code. Not much, but a little.

User avatar
biodude
Dragon
Posts: 444
Joined: Sun Jun 15, 2008 2:40 pm
Location: Montréal, QC

Re: Macro Deletion Utility

Post by biodude »

RPTroll wrote:You guys rock. Thanks for sharing.
Ditto.

Do you mind if I ask a totally naïve question?
How do you get the code in your posts to get all that coloured syntax? It doesn't do it for everyone, but all you macro-gurus seem to have it in your posts. Just curious.
"The trouble with communicating is believing you have achieved it"
[ d20 StatBlock Importer ] [ Batch Edit Macros ] [ Canned Speech UI ] [ Lib: Math ]

Post Reply

Return to “Drop-In Macro Resources”