No internet connection
  1. Home
  2. How to

Confirmation Button Dialog not working when used within a wider script

By Pinewood Studios @Pinewood_Studios
    2024-12-20 09:56:07.149Z

    I have a script which clears un-used clips from the clip-list. As a part of this I have a few clicks on the confirmation dialog windows which pop up:

    sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick();
    
            sf.ui.proTools.confirmationButtonDialog.elementWaitFor({
                waitType: "Disappear",
                timeout: 2000,
                });
    
            if(sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists){
                
                //sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.elementClick();
    
                //In order to hold Option (to recursively select Yes if the undo queue has multiple entries) 
                //I need to do a mouseClick rather than an element click
                sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({
                    isOption: true,
                });
            }
    

    This seems to work well when used on it's own (within my script which only clears the un-used clips). However, when used as part of a much larger script I get the following error for this line:

    sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick();
    

    ClickButtonAction requires UIElement (scriptname: Line 228)
    Couldn't get item #0 as the array length was 0 - sf.ui.app('com.avid.ProTools')..buttons.whoseTitle.is('Remove').first (AxElementArrayIndexedItem)

    If I 'pick' the Remove button I get this:

    sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick();
    

    But if I run just that command nothing happens. Through trial and error I found that in this scenario, only the following will work:

    sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Remove").first.elementClick();
    

    So I'm wondering what might be going on here. Any ideas?

    Thanks,

    -Fergus

    Solved in post #9, click to view
    • 12 replies

    There are 12 replies. Estimated reading time: 48 minutes

    1. Kitch Membery @Kitch2024-12-21 01:04:06.198Z

      Hi @Pinewood_Studios,

      Can you share the lines of code prior to the script failing? Preferably a script that recreates the issue in its simplest form.

      Hopefully, this information will help me troubleshoot the issue. :-)

      1. Pinewood Studios @Pinewood_Studios
          2024-12-21 10:16:14.792Z

          Hi Kitch,

          At the moment the script that causes it to fail is very much a WIP, but I've cut it down to a point where it reliably breaks. This is below.

          Steps to re-create issue:

          1. Start with a Pro Tools session with start time 00:00:00:00

          2. Make 6 7.1 audio tracks

          3. Render 6 7.1 tone clips (cmd+opt+shift+3) that start at 01:00:00:00 (doesn’t matter their duration)

          4. Render 6 more 7.1 clips that start at 02:00:00:00.

          5. Delete (backspace) the clips that start at 02:00:00:00

          6. Run script below:

          // Pro Tools Start Script
              if (!sf.ui.proTools.isRunning) throw 'Pro Tools is not running';
              sf.ui.proTools.appActivateMainWindow();
              sf.ui.proTools.mainWindow.invalidate();
          
          
          //go to Session Start
              function goToSessionStart() {
              sf.app.proTools.setTimelineSelection({
                  inTime: String(Number(0)),
                  outTime: String(Number(0)),
              });
              }
          
          //CHANGE SESSION START TO FIRST CLIP IN SESSION
              goToSessionStart();
          
              // Select all tracks
              let ptTracks = sf.app.proTools.tracks.invalidate().allItems.map(track => track.name);
              sf.app.proTools.selectTracksByName({trackNames: ptTracks, selectionMode: "Replace" });
          
              // Select first clip on the timeline and store it's location
              sf.ui.proTools.clipSelectNextFullClip();
              var currentTimelineSelection = sf.app.proTools.getTimelineSelection({timeScale:"Samples"});
          
                  sf.app.proTools.setTimelineSelection({
                      inTime: String(Number(currentTimelineSelection.inTime)),
                      outTime: String(Number(currentTimelineSelection.inTime)),
                  });
          
              currentTimelineSelection = sf.app.proTools.getTimelineSelection({timeScale:"TimeCode"});
          
              //Brings up session setup window
              if(!sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists){
                  sf.ui.proTools.menuClick({
                      menuPath: ["Setup","Session"],
                  });
          
                  sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({
                      waitType:"Appear",
                      timeout: 500
                  });
              }
          
          
              //If it already is correct do nothing
              if(sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime){
                  
                  sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.elementClick()
                  sf.keyboard.type({ text: currentTimelineSelection.inTime});
          
                  //Wait until the correct timecode has been pasted into the field
                  while(sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) {
                      sf.wait({
                          intervalMs: 50,
                      })
                  }
          
                  sf.keyboard.press({ keys: 'return '});
          
          
                  sf.ui.proTools.confirmationButtonDialog.elementWaitFor({
                      waitType: "Appear",
                      timeout: 5000
                  })
          
                  sf.keyboard.press({ keys: 'return '});
              }
          
              //Close the window
              if(sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists){
                  sf.ui.proTools.menuClick({
                      menuPath: ["Setup","Session"],
                  });
          
                  sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({
                      waitType:"Disappear",
                      timeout: 500
                  });
              }
              //DESELECT ALL TRACKS
              sf.app.proTools.selectTracksByName({trackNames: ["THIS WILL DESELECT EVERYTHING"],selectionMode: "Replace"});
          
          //CLEAR UN-USED CLIPS --- BROKEN -- Same Code Works on it's own?
              //FUNCTIONS------------------------------------------------------------------------------------------------
              //Clear Clips List Filter
              function clearClipsListFilter() {
                  const clipsFilterPlate = sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Plate");
                  const clearClipsFilterButton = clipsFilterPlate.allItems.whoseValue.is("Clear Search Text").first;
          
                  if (clearClipsFilterButton.exists) {
                      clearClipsFilterButton.elementClick({}, `Failed clearing the Clips List filter text field.`);
                  }
              }
              //FUNCTIONS------------------------------------------------------------------------------------------------
              
              sf.ui.proTools.menuClick({
                      menuPath: ["View","Other Displays","Clip List"],
                      targetValue: "Enable"
                  });
          
              clearClipsListFilter();
          
              //Adjust Clip List Filters (From @Chris_Shaw)
          
                  const itemsToCheck = {
                      "Audio": true,
                      "MIDI": true,
                      "Video": true,
                      "Groups": true,
                      "Auto-Created": true,
                      "Include subsequently added clips": false,
                  }
          
                  // Set Clip List filter checkboxes
                  const clipListFilterOptionsPopup = sf.ui.proTools.mainWindow.buttons.whoseValue.is("Show Filter Options").first;
          
                  const filterCheckBoxes = clipListFilterOptionsPopup.popupMenuFetchAllItems().menuItems;
          
                  const checkBoxes = Object.keys(itemsToCheck)
              
                  checkBoxes.forEach(cb => {
                      const checkBox = filterCheckBoxes.filter(mi => mi.path[0].endsWith(cb)).map(i => i.element)[0];
          
                      const shouldClick =
                          checkBox.isMenuChecked !== itemsToCheck[cb] ||
                          typeof itemsToCheck[cb] !== "boolean"
                          
                      if (shouldClick) checkBox.elementClick()
                  })
          
          
              //Clears up to a maximum of 15 times
              for (var i=0; i<15; i++){
                  sf.ui.proTools.mainWindow.popupButtons.first.popupMenuSelect({
                      menuPath: ["Select","Unused"],
                      targetValue: "Enable"
                  });
          
                  sf.ui.proTools.mainWindow.popupButtons.first.popupMenuSelect({
                      menuPath: ["Clear..."],
                  })
          
                  sf.wait({
                      intervalMs: 300
                  });
          
                  if(!sf.ui.proTools.confirmationButtonDialog.exists){
                      //log("Nothing to do")
                      sf.ui.proTools.appActivateMainWindow()
                      break;
                  };
          
          
                  //Wait for window
              /*  sf.ui.proTools.confirmationButtonDialog.elementWaitFor({
                      waitType: "Appear",
                      timeout: 500,
                      }); */
          
                  sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick();
          
                  sf.ui.proTools.confirmationButtonDialog.elementWaitFor({
                      waitType: "Disappear",
                      timeout: 2000,
                      });
          
                  if(sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists){
                      
                      //sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.elementClick();
          
                      //In order to hold Option (to recursively select Yes if the undo queue has multiple entries) 
                      //I need to do a mouseClick rather than an elementClick
                      sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({
                          isOption: true,
                      });
                  }
              }
          

          Thanks in advance for your help.

          --Fergus

          1. Chad Wahlbrink @Chad2024-12-21 17:04:33.614Z

            Thanks, @Pinewood_Studios,

            I believe this is related to a bug in which Pro Tools creates temporary windows with null values discussed a bit here:
            A Better Temp Group System #post-3

            There may be a bug with addressing this specific confirmation dialog that I'll log after I post this.

            Regardless, a fix seems to be to use our handy friend invalidate().

            If you use:

                sf.ui.proTools.confirmationButtonDialog.invalidate().buttons.whoseTitle.is("Remove").first.elementClick();
            
            

            Instead of

            sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Remove").first.elementClick();
            

            It should work as expected.

            I also found that using this line to address the popup menu for the clip list in your script was failing for me:

            sf.ui.proTools.mainWindow.popupButtons.first.popupMenuSelect({
                        menuPath: ["Select","Unused"],
                        targetValue: "Enable"
                    });
            
                    sf.ui.proTools.mainWindow.popupButtons.first.popupMenuSelect({
                        menuPath: ["Clear..."],
                    })
            

            So I would address it like this instead, to be a bit more explicit:

            sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({
                    menuPath: ["Select", "Unused"],
                    targetValue: "Enable"
                });
            
                sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({
                    menuPath: ["Clear..."],
                })
            
            

            Finally, I also found that doing a quick

            sf.ui.proTools.appActivateMainWindow();
            

            call at about line 131 (just after you set the checkboxes for the cliplist filter), refreshed the UI a bit for that next step of using the "Clip List" popupmenu.

            The full updated script would be this:

            // Pro Tools Start Script
            if (!sf.ui.proTools.isRunning) throw 'Pro Tools is not running';
            sf.ui.proTools.appActivateMainWindow();
            sf.ui.proTools.mainWindow.invalidate();
            
            
            //go to Session Start
            function goToSessionStart() {
                sf.app.proTools.setTimelineSelection({
                    inTime: String(Number(0)),
                    outTime: String(Number(0)),
                });
            }
            
            //CHANGE SESSION START TO FIRST CLIP IN SESSION
            goToSessionStart();
            
            // Select all tracks
            let ptTracks = sf.app.proTools.tracks.invalidate().allItems.map(track => track.name);
            sf.app.proTools.selectTracksByName({ trackNames: ptTracks, selectionMode: "Replace" });
            
            // Select first clip on the timeline and store it's location
            sf.ui.proTools.clipSelectNextFullClip();
            var currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" });
            
            sf.app.proTools.setTimelineSelection({
                inTime: String(Number(currentTimelineSelection.inTime)),
                outTime: String(Number(currentTimelineSelection.inTime)),
            });
            
            currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "TimeCode" });
            
            //Brings up session setup window
            if (!sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) {
                sf.ui.proTools.menuClick({
                    menuPath: ["Setup", "Session"],
                });
            
                sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({
                    waitType: "Appear",
                    timeout: 500
                });
            }
            
            //If it already is correct do nothing
            if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) {
            
                sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.elementClick()
                sf.keyboard.type({ text: currentTimelineSelection.inTime });
            
                //Wait until the correct timecode has been pasted into the field
                while (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) {
                    sf.wait({
                        intervalMs: 50,
                    })
                }
            
                sf.keyboard.press({ keys: 'return ' });
            
            
                sf.ui.proTools.confirmationButtonDialog.elementWaitFor({
                    waitType: "Appear",
                    timeout: 5000
                })
            
                sf.keyboard.press({ keys: 'return ' });
            }
            
            //Close the window
            if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) {
                sf.ui.proTools.menuClick({
                    menuPath: ["Setup", "Session"],
                });
            
                sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({
                    waitType: "Disappear",
                    timeout: 500
                });
            }
            //DESELECT ALL TRACKS
            sf.app.proTools.selectTracksByName({ trackNames: ["THIS WILL DESELECT EVERYTHING"], selectionMode: "Replace" });
            
            //CLEAR UN-USED CLIPS --- BROKEN -- Same Code Works on it's own?
            //FUNCTIONS------------------------------------------------------------------------------------------------
            //Clear Clips List Filter
            function clearClipsListFilter() {
                const clipsFilterPlate = sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Plate");
                const clearClipsFilterButton = clipsFilterPlate.allItems.whoseValue.is("Clear Search Text").first;
            
                if (clearClipsFilterButton.exists) {
                    clearClipsFilterButton.elementClick({}, `Failed clearing the Clips List filter text field.`);
                }
            }
            //FUNCTIONS------------------------------------------------------------------------------------------------
            
            sf.ui.proTools.menuClick({
                menuPath: ["View", "Other Displays", "Clip List"],
                targetValue: "Enable"
            });
            
            clearClipsListFilter();
            
            //Adjust Clip List Filters (From @Chris_Shaw)
            
            const itemsToCheck = {
                "Audio": true,
                "MIDI": true,
                "Video": true,
                "Groups": true,
                "Auto-Created": true,
                "Include subsequently added clips": false,
            }
            
            // Set Clip List filter checkboxes
            const clipListFilterOptionsPopup = sf.ui.proTools.mainWindow.buttons.whoseValue.is("Show Filter Options").first;
            
            const filterCheckBoxes = clipListFilterOptionsPopup.popupMenuFetchAllItems().menuItems;
            
            const checkBoxes = Object.keys(itemsToCheck)
            
            checkBoxes.forEach(cb => {
                const checkBox = filterCheckBoxes.filter(mi => mi.path[0].endsWith(cb)).map(i => i.element)[0];
            
                const shouldClick =
                    checkBox.isMenuChecked !== itemsToCheck[cb] ||
                    typeof itemsToCheck[cb] !== "boolean"
            
                if (shouldClick) checkBox.elementClick()
            })
            
            sf.ui.proTools.appActivateMainWindow();
            
            //Clears up to a maximum of 15 times
            for (var i = 0; i < 15; i++) {
                sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({
                    menuPath: ["Select", "Unused"],
                    targetValue: "Enable"
                });
            
                sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({
                    menuPath: ["Clear..."],
                })
            
                sf.wait({
                    intervalMs: 300
                });
            
                if (!sf.ui.proTools.confirmationButtonDialog.exists) {
                    //log("Nothing to do")
                    sf.ui.proTools.appActivateMainWindow()
                    break;
                };
            
                //Wait for window
                /*  sf.ui.proTools.confirmationButtonDialog.elementWaitFor({
                        waitType: "Appear",
                        timeout: 500,
                        }); */
                sf.ui.proTools.confirmationButtonDialog.invalidate().buttons.whoseTitle.is("Remove").first.elementClick();
            
                sf.ui.proTools.confirmationButtonDialog.elementWaitFor({
                    waitType: "Disappear",
                    timeout: 2000,
                });
            
                if (sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists) {
            
                    //sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.elementClick();
            
                    //In order to hold Option (to recursively select Yes if the undo queue has multiple entries) 
                    //I need to do a mouseClick rather than an elementClick
                    sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({
                        isOption: true,
                    });
                }
            }
            
            1. Chad Wahlbrink @Chad2024-12-21 17:05:40.545Z

              Finally, here's a version that's a bit cleaner and optimized.

              I'm not doing a for loop at the end as I wasn't sure of what that was trying to accomplish.

              // Pro Tools Start Script
              if (!sf.ui.proTools.isRunning) throw 'Pro Tools is not running';
              sf.ui.proTools.appActivateMainWindow();
              sf.ui.proTools.mainWindow.invalidate();
              
              //FUNCTIONS------------------------------------------------------------------------------------------------
              //Clear Clips List Filter
              function clearClipsListFilter() {
                  const clipsFilterPlate = sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Plate");
                  const clearClipsFilterButton = clipsFilterPlate.allItems.whoseValue.is("Clear Search Text").first;
              
                  if (clearClipsFilterButton.exists) {
                      clearClipsFilterButton.elementClick({}, `Failed clearing the Clips List filter text field.`);
                  }
              }
              
              // Go to Session Start
              function goToSessionStart() {
                  sf.app.proTools.setTimelineSelection({
                      inTime: String(Number(0)),
                      outTime: String(Number(0)),
                  });
              }
              
              //------------------------------------------------------------------------------------------------
              
              
              function main() {
              
                  // CHANGE SESSION START TO FIRST CLIP IN SESSION
                  goToSessionStart();
              
                  // Select all tracks
                  let ptTracks = sf.app.proTools.tracks.invalidate().allItems.map(track => track.name);
                  sf.app.proTools.selectTracksByName({ trackNames: ptTracks, selectionMode: "Replace" });
              
                  // Select first clip on the timeline and store it's location
                  sf.ui.proTools.clipSelectNextFullClip();
                  let currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" });
              
                  // Go to the start of the first clip
                  sf.app.proTools.setTimelineSelection({
                      inTime: String(Number(currentTimelineSelection.inTime)),
                      outTime: String(Number(currentTimelineSelection.inTime)),
                  });
              
                  // Get Current Timeline Selection as Timecode
                  currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "TimeCode" });
              
                  // Open Session Setup
                  if (!sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) {
                      sf.ui.proTools.menuClick({
                          menuPath: ["Setup", "Session"],
                      });
              
                      sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor();
                  }
              
                  // Set the Session Start time to the timecode position of the first clip. If it's already set, then move on. 
                  if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) {
                      // Select the text field for "Session Start"
                      sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.elementClick()
              
                      // We have to simulate typing for the "Session Start" field of the session setup window
                      sf.keyboard.type({ text: currentTimelineSelection.inTime });
              
                      //Wait until the correct timecode has been typed into the field
                      while (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) {
                          sf.wait({
                              intervalMs: 50,
                          })
                      }
              
                      // Press Return to Complete Entering the Timecode in the Session Start Field
                      sf.keyboard.press({ keys: 'return ' });
              
                      // Wait for the confirmation dialog button
                      sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Maintain timecode").first.elementWaitFor();
              
                      // Click for the confirmation dialog button
                      sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Maintain timecode").first.elementClick();
                  }
              
                  // If the Session Setup window is open, close the window
                  if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) {
              
                      sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.getElement("AXCloseButton").elementClick();
              
                      sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({
                          waitType: "Disappear",
                      });
                  }
              
                  // DESELECT ALL TRACKS
                  sf.app.proTools.selectTracksByName({ trackNames: [], selectionMode: "Replace" });
              
                  // Make sure the clip list is shown
                  sf.ui.proTools.menuClick({
                      menuPath: ["View", "Other Displays", "Clip List"],
                      targetValue: "Enable"
                  });
              
                  //CLEAR UN-USED CLIPS --- BROKEN -- Same Code Works on it's own?
              
                  clearClipsListFilter();
              
                  //Adjust Clip List Filters (From @Chris_Shaw)
              
                  // Create an Object of items to for the clip list filter to include
                  const itemsToCheck = {
                      "Audio": true,
                      "MIDI": true,
                      "Video": true,
                      "Groups": true,
                      "Auto-Created": true,
                      "Include subsequently added clips": false,
                  }
              
                  // Set Clip List filter checkboxes
                  const clipListFilterOptionsPopup = sf.ui.proTools.mainWindow.buttons.whoseValue.is("Show Filter Options").first;
              
                  const filterCheckBoxes = clipListFilterOptionsPopup.popupMenuFetchAllItems().menuItems;
              
                  const checkBoxes = Object.keys(itemsToCheck)
              
                  checkBoxes.forEach(cb => {
                      const checkBox = filterCheckBoxes.filter(mi => mi.path[0].endsWith(cb)).map(i => i.element)[0];
              
                      const shouldClick =
                          checkBox.isMenuChecked !== itemsToCheck[cb] ||
                          typeof itemsToCheck[cb] !== "boolean"
              
                      if (shouldClick) checkBox.elementClick();
                  })
              
                  // Quick Little UI refresh
                  sf.ui.proTools.appActivateMainWindow();
              
                  sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({
                      menuPath: ["Select", "Unused"],
                      targetValue: "Enable"
                  });
              
                  sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({
                      menuPath: ["Clear..."],
                  })
              
                  if (!sf.ui.proTools.confirmationButtonDialog.exists) {
                      // Skip if No Dialogs are Present
                      sf.ui.proTools.appActivateMainWindow();
                  } else {
              
                      // We need to invalidate the confirmationButtonDialog cache
                      // Also, We can wait for an element and then click it in one line.
                      sf.ui.proTools.confirmationButtonDialog.invalidate().buttons.whoseTitle.is("Remove").first.elementWaitFor().element.elementClick();
              
                      // Clear All
                      if (sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists) {
                          sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({
                              isOption: true,
                          });
                      }
              
                      // This is a simple way we can wait for all dialogs to be gone
                      sf.ui.proTools.waitForNoModals();
                  }
              }
              
              main();
              
              1. Pinewood Studios @Pinewood_Studios
                  2024-12-22 00:21:15.284Z

                  Thanks as ever Chad, for the detailed explanation and script optimisation. I'll give it a whirl tomorrow.

                  The for loop in my script is because you quite often have to perform the 'select unused + clear clips' process multiple times to get them all (we're very used to spamming cmd+shift+u, cmd+shift+b, repeatedly until you get no further pop-ups).

                  Specifically, this happens if you delete a clip group which contains other clips, the first time you select un-used you only get the clip group. If you run the same thing again you get the next 'layer' and so on and so on..

                  I figured a maximum of 15 times is a fairly safe bet, and you can see in my original script I used a quick wait to see if no dialogs popped up, at which point I assume there are no clips left to clear, and I break the loop. As I said.. just a first draft! :)

                  And if I may ask - as a new-user I'm intrigued how you are able to find the more specific names of the popupButtons. If I 'pick' them I don't seem to get the detail I need (e.g. "Show Options menu" or "Show Filter Options"). I would love to know how that is figured out!

                  Thanks again,
                  Fergus.

                  1. Chad Wahlbrink @Chad2024-12-22 13:41:32.403Z

                    Hi @Pinewood_Studios,

                    Here's a quick video on how I'm grabbing this alternate UI path:

                    In short, using a macro action "Click UI Element" and the pick action allows you to formulate some alternate paths and then copy that action as javascript:

                  2. In reply toChad:
                    Pinewood Studios @Pinewood_Studios
                      2024-12-22 11:34:50.025Z

                      Hey Chad,

                      A quick follow-up. Interestingly, for me your:

                      sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first.popupMenuSelect({
                              menuPath: ["Clear..."],
                          })
                      

                      doesn't work - I get "Could not open popup menu, popupMenu.open.fromElement requires an element". Perhaps due to a Pro Tools version mismatch. We're on PT 2024.6 at the moment.

                      Fergus

                      1. Chad Wahlbrink @Chad2024-12-22 13:38:52.479Z

                        Ah, yes. The Clip List ui went through some changes on Pro Tools 2024.10 when they made it detachable.

                        We can address the UI differently for different versions with this bit of code:

                        const ptVersion = sf.ui.proTools.appVersionAsNumber;
                        let clipListPopupMenu;
                        if (ptVersion < 241000) {
                            clipListPopupMenu = sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Clip List").first;
                        } else {
                            clipListPopupMenu = sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first;
                        }
                        

                        This version of the script should work on both 2024.6 and 2024.10 and includes the for loop for extra clearing:

                        // Pro Tools Start Script
                        if (!sf.ui.proTools.isRunning) throw 'Pro Tools is not running';
                        sf.ui.proTools.appActivateMainWindow();
                        sf.ui.proTools.mainWindow.invalidate();
                        
                        const ptVersion = sf.ui.proTools.appVersionAsNumber;
                        let clipListPopupMenu;
                        if (ptVersion < 241000) {
                            clipListPopupMenu = sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Clip List").first;
                        } else {
                            clipListPopupMenu = sf.ui.proTools.mainWindow.popupButtons.whoseTitle.is("Show Options menu").first;
                        }
                        
                        //FUNCTIONS------------------------------------------------------------------------------------------------
                        //Clear Clips List Filter
                        function clearClipsListFilter() {
                            const clipsFilterPlate = sf.ui.proTools.mainWindow.buttons.whoseTitle.is("Plate");
                            const clearClipsFilterButton = clipsFilterPlate.allItems.whoseValue.is("Clear Search Text").first;
                        
                            if (clearClipsFilterButton.exists) {
                                clearClipsFilterButton.elementClick({}, `Failed clearing the Clips List filter text field.`);
                            }
                        }
                        
                        // Go to Session Start
                        function goToSessionStart() {
                            sf.app.proTools.setTimelineSelection({
                                inTime: String(Number(0)),
                                outTime: String(Number(0)),
                            });
                        }
                        
                        //------------------------------------------------------------------------------------------------
                        
                        
                        function main() {
                        
                            // CHANGE SESSION START TO FIRST CLIP IN SESSION
                            goToSessionStart();
                        
                            // Select all tracks
                            let ptTracks = sf.app.proTools.tracks.invalidate().allItems.map(track => track.name);
                            sf.app.proTools.selectTracksByName({ trackNames: ptTracks, selectionMode: "Replace" });
                        
                            // Select first clip on the timeline and store it's location
                            sf.ui.proTools.clipSelectNextFullClip();
                            let currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "Samples" });
                        
                            // Go to the start of the first clip
                            sf.app.proTools.setTimelineSelection({
                                inTime: String(Number(currentTimelineSelection.inTime)),
                                outTime: String(Number(currentTimelineSelection.inTime)),
                            });
                        
                            // Get Current Timeline Selection as Timecode
                            currentTimelineSelection = sf.app.proTools.getTimelineSelection({ timeScale: "TimeCode" });
                        
                            // Open Session Setup
                            if (!sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) {
                                sf.ui.proTools.menuClick({
                                    menuPath: ["Setup", "Session"],
                                });
                        
                                sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor();
                            }
                        
                            // Set the Session Start time to the timecode position of the first clip. If it's already set, then move on. 
                            if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) {
                                // Select the text field for "Session Start"
                                sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.elementClick()
                        
                                // We have to simulate typing for the "Session Start" field of the session setup window
                                sf.keyboard.type({ text: currentTimelineSelection.inTime });
                        
                                //Wait until the correct timecode has been typed into the field
                                while (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.groups.whoseTitle.is("Session Format").first.textFields.whoseTitle.is("NumericEntryText").first.value.invalidate().value !== currentTimelineSelection.inTime) {
                                    sf.wait({
                                        intervalMs: 50,
                                    })
                                }
                        
                                // Press Return to Complete Entering the Timecode in the Session Start Field
                                sf.keyboard.press({ keys: 'return ' });
                        
                                // Wait for the confirmation dialog button
                                sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Maintain timecode").first.elementWaitFor();
                        
                                // Click for the confirmation dialog button
                                sf.ui.proTools.confirmationButtonDialog.buttons.whoseTitle.is("Maintain timecode").first.elementClick();
                            }
                        
                            // If the Session Setup window is open, close the window
                            if (sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.exists) {
                        
                                sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.getElement("AXCloseButton").elementClick();
                        
                                sf.ui.proTools.windows.whoseTitle.is("Session Setup").first.elementWaitFor({
                                    waitType: "Disappear",
                                });
                            }
                        
                            // DESELECT ALL TRACKS
                            sf.app.proTools.selectTracksByName({ trackNames: [], selectionMode: "Replace" });
                        
                            // Make sure the clip list is shown
                            sf.ui.proTools.menuClick({
                                menuPath: ["View", "Other Displays", "Clip List"],
                                targetValue: "Enable"
                            });
                        
                            //CLEAR UN-USED CLIPS --- BROKEN -- Same Code Works on it's own?
                        
                            clearClipsListFilter();
                        
                            //Adjust Clip List Filters (From @Chris_Shaw)
                        
                            // Create an Object of items to for the clip list filter to include
                            const itemsToCheck = {
                                "Audio": true,
                                "MIDI": true,
                                "Video": true,
                                "Groups": true,
                                "Auto-Created": true,
                                "Include subsequently added clips": false,
                            }
                        
                            // Set Clip List filter checkboxes
                            const clipListFilterOptionsPopup = sf.ui.proTools.mainWindow.buttons.whoseValue.is("Show Filter Options").first;
                        
                            const filterCheckBoxes = clipListFilterOptionsPopup.popupMenuFetchAllItems().menuItems;
                        
                            const checkBoxes = Object.keys(itemsToCheck)
                        
                            checkBoxes.forEach(cb => {
                                const checkBox = filterCheckBoxes.filter(mi => mi.path[0].endsWith(cb)).map(i => i.element)[0];
                        
                                const shouldClick =
                                    checkBox.isMenuChecked !== itemsToCheck[cb] ||
                                    typeof itemsToCheck[cb] !== "boolean"
                        
                                if (shouldClick) checkBox.elementClick();
                            })
                        
                            // Quick Little UI refresh
                            sf.ui.proTools.appActivateMainWindow();
                        
                            //Clears up to a maximum of 15 times
                            for (var i = 0; i < 15; i++) {
                                clipListPopupMenu.popupMenuSelect({
                                    menuPath: ["Select", "Unused"],
                                    targetValue: "Enable"
                                });
                        
                                clipListPopupMenu.popupMenuSelect({
                                    menuPath: ["Clear..."],
                                })
                        
                                sf.wait({
                                    intervalMs: 300
                                });
                        
                                if (!sf.ui.proTools.confirmationButtonDialog.exists) {
                                    //log("Nothing to do")
                                    sf.ui.proTools.appActivateMainWindow()
                                    break;
                                } else {
                        
                                    // We need to invalidate the confirmationButtonDialog cache
                                    // Also, We can wait for an element and then click it in one line.
                                    sf.ui.proTools.confirmationButtonDialog.invalidate().buttons.whoseTitle.is("Remove").first.elementWaitFor().element.elementClick();
                        
                                    // Clear All
                                    if (sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.exists) {
                                        sf.ui.proTools.confirmationDialog.buttons.whoseTitle.is("Yes").first.mouseClickElement({
                                            isOption: true,
                                        });
                                    }
                        
                                    // This is a simple way we can wait for all dialogs to be gone
                                    sf.ui.proTools.waitForNoModals();
                                }
                        
                            }
                        
                        }
                        
                        main();
                        
                        
                        Reply1 LikeSolution
                        1. Pinewood Studios @Pinewood_Studios
                            2024-12-22 15:26:59.309Z

                            Great stuff. I don't seem to have the appVersionAsNumber option, but the logic is sound. Thanks mate!

                            1. Kitch Membery @Kitch2024-12-23 21:34:56.518Z

                              Hi @Pinewood_Studios.

                              The appVersionAsNumber property was introduced in SoundFlow 5.10.1. Be sure to install the latest version of SoundFlow from https://soundflow.org/account/install

                              :-)

                              1. Chad Wahlbrink @Chad2024-12-23 22:45:19.729Z

                                Thanks @Kitch!

                                Yes, that is some new code for us.

                                In times past, we would do this:

                                const ptVersion = sf.ui.proTools.appVersion.split('.').map(Number).slice(0, 2).reduce((p, c, i) => p + Math.pow(100, 2 - i) * c, 0);
                                
                                if (ptVersion < 230900) {
                                } else {
                                }
                                
                • S
                  SoundFlow Bot @soundflowbot
                    2024-12-21 17:54:51.069Z

                    This report was now added to the internal issue tracked by SoundFlow as SF-1546