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
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.
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.
_________________ Interested in Time Magazine's Best Invention of 2008 Unix-powered laptop? No crashes or lockups. In fact, that series of articles has two such machines. The other is a Dell netbook.
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).
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.
Code:
<!--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.
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.
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
Code:
<!--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()-->
<!--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: { [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.-->
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!
_________________ Interested in Time Magazine's Best Invention of 2008 Unix-powered laptop? No crashes or lockups. In fact, that series of articles has two such machines. The other is a Dell netbook.
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.
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.
Code:
<!-- 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) ] } ] }]
<!-- '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) ]
_________________ Interested in Time Magazine's Best Invention of 2008 Unix-powered laptop? No crashes or lockups. In fact, that series of articles has two such machines. The other is a Dell netbook.
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!
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.
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot post attachments in this forum
Who is online
In total there are 0 users online :: 0 registered, 0 hidden and 0 guests (based on users active over the past 5 minutes) Most users ever online was 243 on Sun Nov 04, 2012 6:14 am
Users browsing this forum: No registered users and 0 guests