5 Replies Latest reply on Oct 20, 2011 6:32 AM by John Hawkinson

    Scripting Select Next object Below?

    John Hawkinson Level 5

      What's the recommended way to achieve the function of "Select Next Object Below"  in scripting? (CS5)

       

      I have a group (g), consisting of a GraphicLine and a TextFrame. That group is placed on top of a larger TextFrame, and wholly contained within the bounds of the larger TextFrame. If my script is called with the group selected, I would like to identify the larger TextFrame.

       

      In the past when I've had similar tasks, I have taken the centerpoint of the group, and then iterated over all TextFrames on the page and compared the point to the visible bounds of each frame, checking to see if it falls within. This works, but feels unimaginative.

       

      Last night, a different approach ocurred to me, which was to use g.parentPage.pageItems.previousItem(). This approach seems intriguing, but doesn't seem to work right in all cases. So the modifed version was to iteratively call pageItems.previousItem(), resolve the specifier and check if constructor.name is a TextFrame, and check for the center point falling within the bounds. If not, call .previousItem() again.

       

      The Page.pageItems.previousItem() function appears to flip flop between items on both facing pages on the current spread.

      I guess this is because the specifier doesn't seem to involve the page:

       

      >> x.parentPage.pageItems.previousItem(x).toSpecifier()
      /document[@id=20]//group[@id=6218]/page-item[@location=previous]
      

       

      Is there a cleverer or cleaner solution here? Maybe I should just go back to iterating over all textframes on the page.

        • 1. Re: Scripting Select Next object Below?
          Harbs. Level 6

          I tend to avoid previousItem() and nextItem() because they very often have performance problems.

           

          Take a look here at ways of dealing with stacking order:

          http://forums.adobe.com/message/2497459#2497459

          1 person found this helpful
          • 2. Re: Scripting Select Next object Below?
            John Hawkinson Level 5

            Thanks, Harbs, that's interesting. But my issues wasn't the performance question and rather the fact that the behavior of parentPage.pageItems includes stuff on the other page of the same spread. I guess I could use the referenced approach of pageItems.everyItem().id and then walking the array, but it won't help me narrow the field.

             

            I guess the thing to do is to take .parentPage.pageItems.everyItem().id, and narrow it to the union of my object's ID and  .parentPage.textFrames.everyItem().id and then walk in order. Still leaves me having to check the visible bounds to find the object beneath, since the next item down the Z order is not necessarily the item immediately behind my object, it's just lower in the z order, but possibly somewhere else entirely on the page.

             

            So...is there a way to avoid checking the bounds to find the item immediately beneath my own?

            • 3. Re: Scripting Select Next object Below?
              John Hawkinson Level 5

              Anyhow, here's what I ended up with. I didn't bother with narrowing to .parentPage.textFrames instead of .pageItems.

              Also, I was in a Harry Potter mood, so there we go on the variable names:

               

              // Given something to search for (the quaffle),
              // find the quaffle in the page item array, then search
              // beneath it for an object (the snitch) that meets
              // the criteria defined by the supplied callback functionn.
              // The callback function will be called as callback(snitch)
              function findBelow(quaffle, callback) {
              
              var
                  quafflepos,
                  page = quaffle.parentPage,
                  pageItemIDs = page.pageItems.everyItem().id,
                  snitch,
                  i, j;
                  
                  // Start at the top of the page and look back to find our item
                  // (we suspct it is near the top)
                  for (i=pageItemIDs.length-1; i>=0; i--) {
                      if (pageItemIDs[i] === quaffle.id) {
                          quafflepos = i;
                          break;
                      }
                  }
                  if (!quafflepos) { alert("Failed to find "+quaffle+" on page."); }
                  j=0;
                  for (i=quafflepos-1; i>=0; i--) {
                      j++;
                      snitch = page.pageItems.itemByID(pageItemIDs[i]);
                      if (callback(snitch)) {
                          // $.writeln("Found it in "+j);
                          return snitch;
                      }
                  }
              }
              

               

              And then I invoke it with an anonymous callback to do the positional check:

               

               

              sqb = fromBounds(sq.geometricBounds);
              sqx = (sqb.x1+sqb.x2)/2; // average x pos.
              sqy = (sqb.y1+sqb.y2)/2;
              
              // find an object below whose bounds contain our centerpoint.
              p = findBelow(sq, function(snitch) {
                      var
                          snb = fromBounds(snitch.geometricBounds);
                      return between(snb.x1, sqx, snb.x2) &&
                          between(snb.y1, sqy, snb.y2);
              });
              

               

              Oh, I guess that depends on some custom helper functions:

               

              function between(lo, x, hi) {
                      return (x > lo && x < hi);
              }
              function fromBounds(gB) {
                  return { y1: gB[0], x1: gB[1], y2: gB[2], x2: gB[3],
                      height: gB[2]-gB[0], width: gB[3]-gB[1]};
              }
              

               

              Hopefully that's useful to someone...

              • 4. Re: Scripting Select Next object Below?
                Marc Autret Level 4

                Hi John,

                 

                Nice code, thanks for sharing.

                 

                Just to quibble around your original message:

                 

                1) I don't think previousItem() / nextItem() provide a relevant approach to search a pageitem by "spatial proximity", because these methods only rely on the objects' z-order.

                 

                2) "the specifier doesn't seem to involve the page" Yes, for two reasons. First, the Page object is a pure artefact, and especially since CS5. The parent of a top-level pageitem is a Spread. In addition, since the resolved specifier of any pageitem only relies on its ID, it doesn't involve page or even spread, it is directly accessible from the document instance. This is an interesting fact by the way, as when you know the ID of an object you can still access that object using the ID-resolved specifier even if the user has cutted/pasted the object on another spread.

                 

                @+

                Marc

                • 5. Re: Scripting Select Next object Below?
                  John Hawkinson Level 5

                  1) I don't think previousItem() / nextItem() provide a relevant approach to search a pageitem by "spatial proximity", because these methods only rely on the objects' z-order.

                  Right! I didn't mean to imply that they do have spatial proximity! That was, by the way, the whole point of the question -- is there some way to achieve in scripting what you can achieve in a single command in the UI?

                  I had hoped someone would point out something I was missing in the scripting API/DOM...

                   

                  2) "the specifier doesn't seem to involve the page" Yes, for two reasons. First, the Page object is a pure artefact, and especially since CS5. The parent of a top-level pageitem is a Spread. In addition, since the resolved specifier of any pageitem only relies on its ID, it doesn't involve page or even spread, it is directly accessible from the document instance.

                  Mostly I was asking whether it is a bug that Page.pageItems.previousItem() can return items off of the page. I am sitll not sure. The details on the specifier give insight into why the behavior is what it is, but that's really an implementation detail...