Macro Deletion Utility
Moderators: dorpond, trevor, Azhrei, giliath, Gamerdude, jay, Mr.Ice
Macro Deletion Utility
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
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 (22.46 KiB) Viewed 8074 times
Last edited by Rumble on Tue Jun 30, 2009 3:18 pm, edited 7 times in total.
Re: Macro Deletion Utility
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.
Edit: fixed. It will now handle multiple duplicate macros.
Re: Macro Deletion Utility
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.
Re: Macro Deletion Utility
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).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 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.
Re: Macro Deletion Utility
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.
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.
Re: Macro Deletion Utility
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).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.
Re: Macro Deletion Utility
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.
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.
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)]
Last edited by Rumble on Wed Jul 01, 2009 8:35 am, edited 1 time in total.
Re: Macro Deletion Utility
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
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.
Re: Macro Deletion Utility
Now this I can use right now. Thank you very much!Rumble wrote:Another modification: Macro Copier.
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!
Re: Macro Deletion Utility
You're welcome!Azhrei wrote:Now this I can use right now. Thank you very much!Rumble wrote:Another modification: Macro Copier.
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!
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.
Re: Macro Deletion Utility
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.
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.
Re: Macro Deletion Utility
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!
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!
Re: Macro Deletion Utility
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.
Also, I think it's slightly faster on the same token as my code. Not much, but a little.
Re: Macro Deletion Utility
Ditto.RPTroll wrote:You guys rock. Thanks for sharing.
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 ]
[ d20 StatBlock Importer ] [ Batch Edit Macros ] [ Canned Speech UI ] [ Lib: Math ]