14 Replies Latest reply on Mar 4, 2010 10:59 PM by John Hawkinson

    keyboard shortcuts for ScriptMenuActions that change names?

    John Hawkinson Level 5

      CS3 Javascript.

       

      For a long time, I've had a script that toggles the visibility of tracking highlighting. This is great when you want to check tracking in a story (or, in our case, if our preflight script reports that it is out of the bounds dictated by our stylebook). It's horribly cumbersome to Preferences->Composition->Custom Tracking/Kerning. But really easy to hit one keystroke, do whatever, and then change it back.

       

      The old version of my script was simple, and I just bound the script invokation to Control-T:

       

      // $Id: toggle-track.jsx 1336 2009-10-11 11:56:36Z jhawk $
      try { var p=app.activeDocument.textPreferences; }
      catch (e) { var p = app.textPreferences; }
      with (p) highlightCustomSpacing  = ! highlightCustomSpacing;
      

      ( The try() is so it changes the global preference if you have no docuements open.)

       

      But then I realized it wasn't very discoverable, and it was annoying to train our production editors to use it. So I thought I'd revise it to modify the View menu and change its name as appropriate. This works fine:

      // toggle-track2.jsx
      #target "incopy"
      #targetengine "session"
      
      function toggle_track(ev) {
        try { var p=app.activeDocument.textPreferences; }  
        catch (e) { var p = app.textPreferences;  }
        with (p) highlightCustomSpacing  = ! highlightCustomSpacing;
      }
      
      function update_track(ev) {
        try { var p=app.activeDocument.textPreferences; }
        catch (e) { var p = app.textPreferences; }
        if (p.highlightCustomSpacing) {
           trackAction.title="Hide Tracking";
      //     trackAction.checked=false;
        } else {
           trackAction.title="Show Tracking";
      //     trackAction.checked=true;
        }
      }
      
      // debug: // app.scriptMenuActions.everyItem().remove();
      
      var trackAction = app.scriptMenuActions.add("Toggle tracking");
      var l1 = trackAction.eventListeners.add("onInvoke", toggle_track);
      var l2 = trackAction.eventListeners.add("beforeDisplay", update_track);
      var togItem = app.menus.item("Main").submenus.item("View").menuItems.add(trackAction);
      

       

      This works great, but I can't figure out how to assign a keyboard shortcut to it! For a given session it works fine. I can Edit -> Keyboard Shortcuts and assign a Control-T to whichever of "Show Tracking" or "Hide Tracking" is visible (or "Toggle tracking" if you run it before the beforeDisplay handler runs). And it properly keeps the keybinding when the action's name changes.

       

      But if you quit and restart InDesign (script is in the Startup Scripts) folder, it doesn't keep the keybinding. What to do?

       

      There doesn't seem to be a scripting binding for adding a keybinding. I also tried changing trackAction.name instead of .title. Didn't help.

       

      (while I'm here, how does one find the locale-independent submenu name for the View menu? findKeyStrings doesn't seem to work:

       

      app.findKeyStrings("View").join()
      Result: $ID/0xAF900kS_ViewButtonKey,$ID/0xAF900kViewButtonKey [NT]

      but it should return something like $ID/View, right?)

       

      A horrid workaround would be to keep my original script and assign the keybinding to it. But that makes the keybinding non-discoverable, which is poor. Help?

       

      I guess I could leave the name alone and update the .checked property. But I'd rather not do it that way, since that's now how the other Show/Hide items in the View menu work.

        • 1. Re: keyboard shortcuts for ScriptMenuActions that change names?
          Dirk Becker Level 4

          This is the first time I heard about "findKeyStrings", and I have no suggestion how you can discover them for scripting. I use a list created via plugin, where we have the same problem but fortunately also a solution. Following that list, the key string for the View menu should be something like "$ID/&View" - several of the menus have the ampersand inserted where Windows will display the underline. Same goes for "$ID/&Type", "$ID/&Font", "$ID/Displa&y Performance", "$ID/&Object", "$ID/Displa&y" and more.

           

          Again, I do not know how to assign a sticky keyboard shortcut to a scriptMenuAction.

          Maybe it works (I did not try) to assign the shortcut to your script and use the shortcut's associated "script action" (a different beast) rather than "script menu action" as starting point for your menu. E.g.

          var trackAction = app.menuActions.itemByName("test.jsx");

           

          Dirk

          • 2. Re: keyboard shortcuts for ScriptMenuActions that change names?
            Harbs. Level 6

            Yes. Dirk is correct, that the ampersands can throw things off.

             

            Find your menu item, and look at the title property. Use findKeyStrings() on that, and it'll give you the local-independent name.

             

            Dirk, check out app.findKeyStrings() and app.translateKeyStrings() for

            converting to and from local independent names...

             

            Harbs

            http://www.in-tools.com

            Innovations in Automation

            • 4. Re: keyboard shortcuts for ScriptMenuActions that change names?
              Harbs. Level 6

              The problem with the KBSC is with this line:

               

              var trackAction = app.scriptMenuActions.add("Toggle tracking");

               

              When you create a new scriptMenuAction, it overwrites the old one, and you lose the shortcuts.

               

              You need to do something like this:

               

              var trackAction = app.scriptMenuActions.item("Toggle tracking");
              if(!trackAction){
               
              trackAction = app.scriptMenuActions.add("Toggle tracking");
              }
              • 5. Re: keyboard shortcuts for ScriptMenuActions that change names?
                John Hawkinson Level 5

                Umm...none of these answers seem to work

                 

                Dirk, the ampersands only matter in the .title string, not the .name string.

                Also, findKeyStrings() is documented in the Menus chapter of the InDesign Scripting Guide.

                Setting trackAction to an existing script via menuActions.itemByName() doesn't seem to work, because it returns a MenuAction Object, and not a ScriptMenuAction object, and I don't think that can be added to a menuItems list. (When I try, I get a confusing "Invalid value for parameter 'assocaiedMenuAction' of event 'add'. Expected MenuACtion but received (MenuAction, MenuAction).

                 

                Peter, Marc's article is nice, but it doesn't discuss keyboard shortcuts or related issues.

                 

                Harbs, I don't think your analysis is correct...certainly your sample code doesn't work.

                My script never gets run more than once. It runs in the Startup Scripts folder, and never again, so there's no concern about trackAction getting overwritten. Its created once and its .title property is changed.

                 

                In any case, your example makes the script error out... scriptMenuActions.item() returns an invalid ScriptMenuAction Object initially, which fails the (!trackAction) test, so trying to reference it (later) breaks hard. Something like:

                var trackAction = app.scriptMenuActions.add("Toggle tracking");
                try { trackAction.name }
                catch (e) { trackAction = app.scriptMenuActions.add("Toggle tracking"); }

                runs successfully, but gets me no closer to solving the keyboard shortcuts problem.

                 

                Does nobody have an example of a script with a KBSC where the script changes its menu title dynamically?

                • 6. Re: keyboard shortcuts for ScriptMenuActions that change names?
                  Harbs. Level 6

                  Did you try my code?

                   

                  I don't think you understood what I wrote. It works for me, and the KBSC is preserved.

                   

                  InDesign keeps the scriptMenuActions cached even after InDesign is closed, so if you create the scriptMenuAction and then close InDesign, when you restart InDesign, that scriptMenuAction IS STILL VALID! This preserves the the KBSC. The only time you'd need to create the scriptMenuAction, is when the previous session of InDesign did not have the scriptMenuAction installed.

                   

                  Of course, this only applies to the scriptMenuAction. The actual script menu, you need to recreate on each restart.

                   

                  Harbs

                  • 7. Re: keyboard shortcuts for ScriptMenuActions that change names?
                    Harbs. Level 6

                    Dirk, the ampersands only matter in the .title string, not the .name string.

                    It's actually the TITLE property which is the one you need!!! (and the ampersands are quite important, like I wrote above....)

                     

                    If you would have followed my suggestion, you would have come up with $ID/&View as the correct locale-independent string...

                     

                    Harbs

                    • 8. Re: keyboard shortcuts for ScriptMenuActions that change names?
                      Harbs. Level 6

                      Actually, there was a small mistake in my code. Instead of:

                      var trackAction = app.scriptMenuActions.item("Toggle tracking");
                      if(!trackAction){
                       
                      trackAction = app.scriptMenuActions.add("Toggle tracking");
                      }

                       

                      It should have been:

                      var trackAction = app.scriptMenuActions.item("Toggle tracking");
                      if(trackAction == null){
                       
                      trackAction = app.scriptMenuActions.add("Toggle tracking");
                      }

                      or

                      var trackAction = app.scriptMenuActions.item("Toggle tracking");
                      if(!trackAction.isValid){
                       
                      trackAction = app.scriptMenuActions.add("Toggle tracking");
                      }

                       

                      Harbs

                      • 9. Re: keyboard shortcuts for ScriptMenuActions that change names?
                        liedzeit Level 2

                        Thank you, Harbs!!

                         

                        I had tried a million things to make the KBSCs stick with no luck. I always took for granted that I had to recreate the scriptMenuActions at every start of InDesign.

                         

                        Ralf

                        • 10. Re: keyboard shortcuts for ScriptMenuActions that change names?
                          John Hawkinson Level 5

                          Sorry for the delay. This still doesn't work.

                           

                          If I comment out these lines from update_track():

                               // trackAction.title="Hide Tracking";
                          ...
                               // trackAction.title="Show Tracking";
                          

                          then it works fine. It seems that the KBSC gets lost when the action's title is changed, and I can't find a way to reestablish it.

                           

                          Did you actually test this script? Or another one where the name or title changes dynamically?

                           

                          Anyhow, yes, I did try your script. That's why I said "In any case, your example makes the script error out..."

                           

                          Ironically I had the KBSC sticking around initially, even when I was adding a new ScriptMenuAction every time. Now that you've explained that menus and ScriptMenuActions persist (thanks! That was enlightening), I don't really understand how that worked...but it did.

                           

                          in any event, this still leaves me stuck.

                          • 11. Re: keyboard shortcuts for ScriptMenuActions that change names?
                            Harbs. Level 6

                            No. I did not notice that you were changing the title of the menu 

                            item. (I tend to read the forums rather quickly...)

                             

                            You're probably out of luck. You can try creating two menu items and 

                            enabling/disabling them as needed. I would just use APID, which has 

                            persistent menu items even when they are removed...

                             

                            Harbs

                            • 12. Re: keyboard shortcuts for ScriptMenuActions that change names?
                              jonathanwbrown

                              On shutdown, all menu items and event handlers associated with scriptMenuActions are deleted, but the scriptMenuActions themselves are persisted, as are their associated KBSCs. On restart, after startup scripts run, any scriptMenuActions that don’t have any associated menu items or event handlers are deleted, which will also delete their associated KBSCs.

                               

                              Your script is retrieving the existing scriptMenuAction by name; if it doesn’t find it—for example, if the name has been changed—then a new one is created and the old one (along with the KBSC) is deleted. The problem is solved by checking for it under all possible names. Something like this should do it:

                               

                              var trackAction = app.scriptMenuActions.item("Toggle tracking");

                              if(trackAction == null) {

                                  trackAction = app.scriptMenuActions.item("Hide Tracking");

                                  if (trackAction == null) {

                                      trackAction = app.scriptMenuActions.item("Show Tracking");

                                      if (trackAction == null) {

                                          trackAction = app.scriptMenuActions.add("Toggle tracking");

                                       }

                                   }

                              }

                               

                              Note that if multiple scriptMenuActions have the same name, the startup process may not retrieve the correct one. Conceivably you could work around this by assigning the scriptMenuAction a label, and then retrieving it via the label instead.

                               

                              var myLabel = "myUniqueToggleTrackActionLabel" ;

                              var trackAction = null ;

                              var SMAs = app.scriptMenuActions;

                              for ( var i = 0 ; i < SMAs.length ; i++ ) {

                                 if ( SMAs[i].label == myLabel ) {

                                      trackAction = SMAs[i] ;

                                      break ;

                                 }

                              }

                              if(trackAction == null) {

                                 trackAction = app.scriptMenuActions.add("Toggle tracking", {label:myLabel});

                              }

                               

                              However, that’s a relatively inefficient way to retrieve an object. Here’s a more clever way: store the scriptMenuAction’s unique id in a label on the Application object.

                               

                              var myLabel = "myUniqueToggleTrackActionLabel" ;

                              var trackAction = app.scriptMenuActions.itemByID( Number(app.extractLabel( myLabel )) ) ;

                              if(trackAction == null) {

                                 trackAction = app.scriptMenuActions.add("Toggle tracking");

                                 app.insertLabel( myLabel, String(trackAction.id) ) ;

                              }

                               

                              Jonathan

                              • 13. Re: keyboard shortcuts for ScriptMenuActions that change names?
                                Harbs. Level 6

                                Jonathan, thank you very much for your response on this! (We have to get you one of those fancy Employee badges...)

                                 

                                For those that don't know, Jonathan is the man behind an awful lot of the scripting support in InDesign. We should all be honored with his presence here...

                                 

                                Harbs

                                • 14. Re: keyboard shortcuts for ScriptMenuActions that change names?
                                  John Hawkinson Level 5

                                  Hi, Jonathan.

                                   

                                  Thanks for the view under the hood — it wasn't quite sufficient it, but it got me close enough.

                                   

                                  I suppose I don't understand enough of Javascript's scoping rules, but the update_ev() function doesn't seem to have access to the trackAction variable all the time. Sometimes yes, but sometimes no. It also doesn't appear in $.global in those cases. So I have to do the same checking there in order to make things work.

                                   

                                  I did simplify by getting rid of the "Toggle tracking" name so now there are only two: "Show Tracking" and "Hide Tracking".

                                  In what is probably a questionable stylistic move, I changed your:

                                  if (trackAction == null) {
                                      trackAction = app.scriptMenuActions.item("Hide Tracking");
                                      if (trackAction == null) {
                                          trackAction = app.scriptMenuActions.item("Show Tracking");
                                          if (trackAction == null) {
                                              trackAction = app.scriptMenuActions.add("Toggle tracking");
                                           }
                                       }
                                  }
                                  

                                  to use short-circuiting.

                                  if (trackAction==null &&
                                    (trackAction = app.scriptMenuActions.item("Show Tracking"))==null &&
                                    (trackAction = app.scriptMenuActions.item("Hide Tracking"))==null)
                                     trackAction = app.scriptMenuActions.add("Toggle Tracking");
                                  

                                  Except I got rid of the "Toggle Tracking" to simply things...

                                   

                                  Here's the final script. I hope it is useful to others!

                                  #target incopy
                                  #targetengine session
                                  
                                  // $Id: toggle-track.jsx 1336 2009-10-11 11:56:36Z jhawk $
                                  
                                  function toggle_track(ev) {
                                    try { var p=app.activeDocument.textPreferences; }  
                                    catch (e) { var p = app.textPreferences;  }
                                    with (p) highlightCustomSpacing  = ! highlightCustomSpacing;
                                  }
                                  
                                  function update_track(ev, param) {
                                    var xAction;
                                    if (xAction==null &&
                                       (xAction = app.scriptMenuActions.item("Show Tracking"))==null)
                                         xAction = app.scriptMenuActions.item("Hide Tracking");
                                    try { var p=app.activeDocument.textPreferences; }
                                    catch (e) { var p = app.textPreferences; }
                                    xAction.name=(p.highlightCustomSpacing?"Hide":"Show")+" Tracking";
                                  }
                                  
                                  var trackAction;
                                  if (trackAction==null &&
                                    (trackAction = app.scriptMenuActions.item("Show Tracking"))==null &&
                                    (trackAction = app.scriptMenuActions.item("Hide Tracking"))==null)
                                     trackAction = app.scriptMenuActions.add("Show Tracking");
                                  
                                  trackAction.eventListeners.add("onInvoke", toggle_track);
                                  trackAction.eventListeners.add("beforeDisplay", update_track);
                                  if (viewItem==null)
                                    var viewItem = app.menus.item("$ID/Main").submenus.item("$ID/View").menuItems.add(trackAction);
                                  var tcMenu = app.menus.item("$ID/RtMouseText") // Text Context Menu
                                  var tcHC;
                                  tcHC = tcMenu.menuElements.item("$ID/ShowHiddenCharactersCmdStr");
                                  if (tcHC==null)
                                    tcHC = tcMenu.menuElements.item("$ID/ShowHiddenCharactersCmdStr");
                                  if (tcHC != null)
                                    tcMenu.menuItems.add(trackAction, LocationOptions.AFTER, tcHC);
                                  else
                                    tcMenu.menuItems.add(trackAction);