Feature Requests: Comments on Triggers, Rearranging Triggers

In general, I agree. But I have a situation that is prompting me in the automated direction.

I have a list of triggers, ⌥⌘A through ⌥⌘Z that I originally started with, as potential hotkeys to switch to different desktops. I deliberately left out ⌥⌘Q and ⌥⌘W because of their nearly universal and annoying side effects if they aren't caught by KBM. I also wanted the keys to be mnemonic, so I still haven't used "J" but "K" was one of my first ones, for my KBM desktop. Over time I have been adding shifted versions of them, e.g.,⌥⇧⌘K became my KBM "meta" desk, for Forum research and AppleScript testing.

Because of the nature of how additions are made to the KBM Triggers list, all the shifted triggers are now at the bottom of the list and they are not in alphabetical order. Recently I added ⌥⇧⌘A for a second Admin desktop so now ⌥⌘A is at the top and ⌥⇧⌘A is at the bottom of a list of 36 triggers. My aesthetics rebell. Clean code should be readable, organized, and even beautiful, if possible. To clean it up would mean over ten minutes of tedium, retyping the list in the new order, and future changes would have to be done all over again, so I'm spending up to 10 hours seeing if I can program it, because for me the programming — and the learning that comes along with getting the programming to work — is fun (most of the time).

For instance, to replace the list of triggers, I am pretty sure that I have to first delete the existing list of triggers, one by one. In AppleScript, I got the list of triggers from KBM and stepped through the list, deleting them one at a time. But there was a knotty problem. If I had a test list of four triggers, the loop would delete two of them. If I made a test list of eight triggers, the loop would delete four of them. WTH?

I finally figured out that AppleScript was creating the list of triggers and then stepping through its internal list to execute the deletions. So with four triggers, it deletes trigger 1 and then the list shifts so that when it deletes trigger 2, it's actually deleting trigger 3. Then it attempts to delete trigger 3, but there is no trigger 3, so it quits. In a list of 36 triggers, it would presumably delete every odd trigger until it had deleted 18 of them — not my intent.

I am sure there is a simple AppleScript option/flag to process the list in reverse order, so as soon as I find that, I presume this step will be done and I can move onto the next step.

I think both of those will be simple, although actually coding the dictionary/look-up table for the key codes may take some creative use of VIM and SED or some such.

I am definitely not going to bother with this stuff in the first (or even second) pass.

Well that was quick. Here's the AppleScript that empties the Trigger List, before repopulating it:

-- identify the currently selected macro
tell application "Keyboard Maestro"
	set theSelectedMacro to macro id (item 1 of (get selectedMacros))
	
	-- get list of triggers
	set theTriggerList to triggers of theSelectedMacro
	
	-- for each trigger in the reversed list, delete it.
	repeat with theTrigger in (reverse of theTriggerList)
		delete theTrigger
	end repeat
	
end tell

Totally understand the desire -- the urge, even -- to reorganise.

If all you want to do is reverse the current list, try

tell application "Keyboard Maestro"
	set selectedMacro to macro id (item 1 of (get selectedMacros))
	
	set theTriggers to reverse of (get triggers of selectedMacro)
	repeat with eachItem in theTriggers
		copy eachItem to end of selectedMacro's triggers
	end repeat
	repeat count of theTriggers times
		delete first trigger of selectedMacro
	end repeat
end tell

If you want to sort the triggers, however, you are going to have to determine your desired sort order and then, perhaps, roll your own code for that. As an example of the possible problems -- if you want "alphabetically by the non-modifier key" then even if you only have hot key triggers an ASCII sort won't do it since:

The Hot Key ⌃⌥⌘A is pressed
The Hot Key ⌃⌘B is pressed

...sorts to:

The Hot Key ⌃⌘B is pressed
The Hot Key ⌃⌥⌘A is pressed

No, you just blat the lot!

tell application "Keyboard Maestro"
	set selectedMacro to macro id (item 1 of (get selectedMacros))
	delete every trigger of selectedMacro
end tell

Remember that you are dealing with references to triggers, not the triggers themselves. So when you delete item 1 of the triggers then go to delete item 2, it's item 2 of the current list -- i.e. item 3 of the original. That's why you were nuking every other trigger -- and why, in my script above, I added all the triggers to the end again then nuked the originals.

As usual with me, none of this is gospel. TBH, AS lists and references and so on do my head in, I don't fully understand them, but do know enough to keep poking around until things work :wink: So consider the above as pragmatic solutions/truths that could be improved on by someone who actually knows what they are doing!

1 Like

Thanks for all the detailed help, @Nige_S. I've been approaching the sorting problem in a different thread (How to sort a list by first non-modifier character?) and @ComplexPoint came up with the Shell commands to do that — IF I first insert delimiters around the modifier symbols so that the input line can be broken into distinct fields. It's some pretty basic Regex editing to do that and then remove them again. The tricky part is how to determine where they go if the trigger has no modifiers. But I don't think that's the case in my first usage so I can leave that as a special case to deal with later, if ever.

Well isn't that a whole lot easier!

Unfortunately, that approach doesn't work when...

I do want to sort the triggers, and in the other thread mentioned above I've been getting help working out the sorting order. But to actually sort the triggers themselves, in the macro, I think I need to:

  • Save the XML of each trigger and name it based on the description of the trigger.
  • Generate a list of all the names.
  • Sort the list of names using first whatever follows the modifier symbols (see other thread).
  • Insert the saved trigger XML into the macro in the order determined by the sorted list of names.

Which is why we add the "new" triggers to the end of the trigger list, then delete the "old" ones from the beginning of the list.

Rather than mess around with XML etc, I'd take a similar approach for what you want to do.

  1. Get a list of trigger descriptions
  2. Create a "sorted list", based on description, of trigger indices
  3. Work through that sorted list, appending triggers by index
  4. Delete original triggers

Let me get this straight and clear. You're suggesting that the sorted list doesn't need to point to saved files of the XML for each trigger, or to KBM named clipboards or anything else that saves the XML code for each trigger, it only needs to have the index number of where that entry was/is in the original list. Just the original index number, nothing else.

Then you would go through the sorted list and add new triggers, simply by copying the original trigger by its index number as a new trigger, and do this for the whole sorted list. At this point the trigger list is twice as long as it should be, containing both the unsorted and the sorted triggers, in that order.

Then you delete the first half of the list by deleting items 1 through count, where count is a number you saved somewhere along the way, the size of the original list.

That's pretty cool, and I don't think that I would have thought of that, I was really focussed on how to save the XML to use it to create new triggers after I had deleted the originals. Your method, by keeping the originals around to copy them, is much cleaner and there's no temporary files or named clipboards or whatever to have to go back and clean up.

Thanks.

All that can be done in AppleScript with variations on what I've already done — except the Regex editing and the Shell-based sort. Using the Shell's sort is really convenient because it handles sorting by different fields so easily, but it requires inserting and removing delimiters into the items. I suppose I could pop back to KBM to do that and then go into AppleScript again to finish up.

You wouldn't happen to have slick tricks for how to run a Shell script from inside AppleScript, would you?

This, perhaps, demonstrates what's going on. Run it from your favourite script editor and it will bring KME to the front, then pause for 2 seconds within each loop so you can watch the trigger list changing in the UI:

tell application "Keyboard Maestro"
	activate
	set selectedMacro to macro id (item 1 of (get selectedMacros))
	delay 2
	set theTriggers to reverse of (get triggers of selectedMacro)
	repeat with eachItem in theTriggers
		copy eachItem to end of selectedMacro's triggers
		delay 2
	end repeat
	repeat count of theTriggers times
		delete first trigger of selectedMacro
		delay 2
	end repeat
end tell

One clarification:

count is a property of a list -- the number of items in it. You can also use length, which might be more in line with other languages, but AS or more English-like (you don't "length" the number of things in a box, you "count" them!).

set myList to {"one", "two", "three", "four"}
count of myList
--> 4

So we don't need to save the value, we just ask theTriggers how many items it contains.

Check the do shell script verb in the Standard Additions dictionary. But I think that, in this case, you're better off bouncing to KM, text processing there, then coming back to AS to make the trigger changes.

Don't forget that, as users of both AS and KM, we can call the KME's regex engine with AppleScript!

Also, a length is a measurement, a "real" number with potentially infinite decimals and roundoff error, a count is a whole number, an integer, no rounding, no error (unless you miscount).