9 Replies Latest reply on Oct 12, 2009 9:39 PM by anrkeytek

    Creating a Stepper with ScriptUI?

    arlodesign

      Has anyone created a stepper using ScriptUI? I'm working on a palette that provides an alternative to using the Margins and Columns window that also helps build strong typographic grids (kind of a combination Margins, Columns and Create Guides window). I don't want the palette to be too different than the existing Margins and Columns window, but ScriptUI doesn't seem to provide a simple way to create a stepper widget like is currently used in the Margins and Columns windows:

      Margins and Columns.jpg

      Am I just missing it in the scripting guide books, or does this type of widget require scripting it from scratch?

       

      Thanks for any help you can provide.

        • 1. Re: Creating a Stepper with ScriptUI?
          Bob Stucky Adobe Employee

          You pretty much have to script it from scratch. Sorry.

           

          I am attaching a script that I wrote for CS4 that makes a ScriptUI edit text act like a measurement edit box. It allows you to use arrow keys to increment/decrement. That should at least help get you started.

           

          Regards

           

          Bob

          • 2. Re: Creating a Stepper with ScriptUI?
            Bob Stucky Adobe Employee

            Attempting to attach the file again...

             

            Tried to upload twice, failed twice. Email me for the code...

             

            bostucky@adobe.com

             

             

            Bob

            1 person found this helpful
            • 3. Re: Creating a Stepper with ScriptUI?
              Marc Autret Level 4

              I'm working on a similar script and I've created an eXtendedWidget class to encapsulate the component you need

               

              In the rough sample code below, checkout the usage of ScrollEditText.

               

              ScrollEditText can even emulates the behavior of a measurementEditBox, with specified units and min/max :

               

              myStepper: XW.ScrollEditText({title:"Top:",minvalue:0,maxvalue:'8640pt',decimals:3,units:'mm'}),

               

              To see how it works, try this:

               

              var toUIString = function()
                   { // <this> : ui object
                   var r = this.toSource().
                        replace(/\\u([0-9a- f]{4})/gi, function(_,$1){return String.fromCharCode(Number('0x'+$1));} ).
                        replace(/(?:\{_([^:]+):)/g,'$1').
                        replace(/\}\}/g,'}').
                        replace(/(^\()|(\)$ )/g,'');
                   return r;
                   };
               
              Window.prototype.focus = (function()
              { //Window.prototype.focus
              var getControls = function()
                   {
                   var r = [];
                   for ( var i=0, c ; i<this.children.length ; i++ )
                        {
                        c = this.children[i];
                        if (c.constructor == StaticText) continue;      // exclude StaticText focus
                        if (c.constructor == Scrollbar) continue;     // exclude Scrollbar focus
                        if ('active' in c) {r.push(c);continue;}
                        r = r.concat(arguments.callee.call(c));
                        }
                   return r;
                   };
              return function(/*int*/step)
                   {
                   this.controls = this.controls || getControls.call(this);
                   var sz = this.controls.length;
                   for (var i=0 ; i<sz ; i++)
                        {
                        if (this.controls[i].active == true) break;
                        }
                   if (!step) return this.controls[i];
                   i = (i+step+sz)%sz;
                   step = (step<0)?-1:1;
                   while(!this.controls[i].enabled) i+=step;
                   this.controls[i].active = true;
                   return this.controls[i];
                   };
              })();
               
               
              var XW = (function()
              // Extended Widgets
              //------------------------------------------------
              {
              var widgets = {}, xWidgets = {};
                   
              var xWidget = function(/*str*/xType)
                   { // constructor
                   this.xType = xType;
                   this.widget = widgets[this.xType];
                   };
              xWidget.prototype.ui = function(settings_)
                   {
                   var ui = this.widget.ui(settings_);
                   var ac = this.widget.access;
                   ui[ac].settings = settings_;
                   ui[ac].xType = this.xType;
                   return ui;
                   };
              xWidget.prototype.connect = function(parent)
                   {
                   var c = parent.children;
                   for (var i = c.length-1 ; i>=0 ; i-- )
                        {
                        if ( c[i].xType && (c[i].xType == this.xType) )
                           {
                           this.widget.connect.call(c[i]);
                           continue;
                           }
                        if (c[i].children.length) this.connect(c[i]);
                        }
                   };
               
               
              // DropListBox
              // --------
              widgets.DropListBox =
                   {
                   access: '_Group',
                   ui: function(settings_)
                        {return {_Group:{
                           margins:0, spacing:0, orientation: 'row', alignChildren: ['left','center'],
                           lTit: (settings_.title)?{_StaticText:{characters:12,text:settings_.title,justify:'right'}}:null,
                           lSpa: (settings_.title)?{_StaticText:{characters:1}}:null,
                           ddList: {_DropDownList: {maximumSize:[100,25]}}
                           }};
                        },
                   connect: function()
                        { // <this> UI Group
                        (function(items_)
                           { // ddList : load items
                           for(var i=0, tp ; i<items_.length ; i++)
                                {
                                tp = (items_[i] == '-') ? 'separator' : 'item';
                                this.add(tp,items_[i]);
                                }
                           }).call(this.ddList,this.settings.items);
                        }
                   };
               
               
              // ScrollEditText  (this is your 'stepper')
              // --------
              widgets.ScrollEditText =
                   {
                   access: '_Group',
                   ui: function(settings_)
                        {return {_Group:{
                           margins:0, spacing:0, orientation: 'row', alignChildren: ['left','center'],
                           lTit: (settings_.title)?{_StaticText:{characters:12,text:settings_.title,justify:'right'}}:null,
                           lSpa: (settings_.title)?{_StaticText:{characters:1}}:null,
                           sBar: {_Scrollbar:{size:[16,24]}},
                           eTxt: {_EditText:{characters:settings_.characters||8}}
                           }};
                        },
                   connect: function()
                        { // <this> Group
                        (function()
                           {
                           this.units = this.units||'';
                           this.decimals = (typeof this.decimals=='undefined')?-1:this.decimals;
                           this.characters = this.characters||6;
                           this.step = this.step||1;
                           this.jump = this.jump||this.step*5;
                           }).call(this.settings);
               
                        this.parseUnit = (this.settings.units)?
                           function(/*str*/s)
                                {
                                var m = s.match(/^-?(\d+)([cp])([\d\.]+)/i); // #p# ou #c#
                                if (m && m.length) s = ((m[0][0]=='-')?'-':'') + ((m[1]-0)+(m[3]/12)) + ((m[2]=='c')?'ci':'pc');
                                m = s.match(/agt|cm|ci|in|mm|pc|pt/i);
                                return (m && m.length) ?
                                     UnitValue (s).as(this.settings.units) :
                                     Number(s. replace(/[^0-9\.-]/g,''));
                                }:
                           function(/*str*/s) {return Number(s.replace(/[^0-9\.-]/g,''));};
               
                        this.parseStrValue = function(/*str*/s)
                           {
                           var v = this.parseUnit(s.replace(',','.'));
                           return (isNaN(v)) ? null : v; 
                           };
               
                        this.fixDecimals = (this.settings.decimals >=0 )?
                           function(/*num*/v){return v.toFixed(this.settings.decimals)-0;}:
                           function(/*num*/v){return v;};
               
                        this.displayValue = (this.settings.units)?
                           function(/*num*/v)
                                {
                                var u = this.settings.units;
                                if ( u=='pc' || u=='ci' )
                                     { // #,#pc -> $p$  ou  #,#ci -> $c$
                                     var sg = (v<0)?-1:1;
                                     v = v*sg;
                                     var sx = Math.floor(v);
                                     var sy = (v-sx)*12;
                                     return sx*sg + u[0] + sy.toLocaleString().substr(0,this.settings.characters);
                                     }
                                else
                                     {
                                     var s = v.toLocaleString().substr(0,this.settings.characters);
                                     return s + ' ' + u;
                                     }
                                } :
                           function(/*num*/v)
                                {
                                return v.toLocaleString().substr(0,this.settings.characters);
                                };
               
                        this.settings.minvalue = (this.settings.minvalue)?this.parseStrValue(''+this.settings.minvalue):0;
                        this.settings.maxvalue = (this.settings.maxvalue)?this.parseStrValue(''+this.settings.maxvalue):100;
                        if (typeof this.settings.value == 'undefined') this.settings.value = this.fixDecimals(this.settings.minvalue);
               
                        this.offsetValue = function(/*num*/delta)
                           {
                           this.changeValue(this.eTxt.text,'NO_DISPLAY');
                           this.changeValue(Math.round(this.settings.value + delta));
                           };
                        
                        this.changeValue = function(/*var*/vs,/*var*/noDisplay,/*var*/noEvent)
                           {
                           var v = (typeof vs == 'string') ? this.parseStrValue(vs) : vs;
                           v = ( typeof v == 'number' ) ? this.fixDecimals(v) : this.settings.value;
                           if ( v < this.settings.minvalue ) v = this.settings.minvalue;
                           if ( v > this.settings.maxvalue ) v = this.settings.maxvalue;
                           var noChange = (this.settings.value == v);
                           if (!noChange) this.settings.value = v;
                           if (!noDisplay) this.eTxt.text = this.displayValue(this.settings.value);
                           if ((!noChange) && (!noEvent)) this.eTxt.notify();
                           };
               
                        (function()
                           { // scrollbar
                           this.minvalue = -1;
                           this.maxvalue = 1;
                           this.value = 0;
                           this.onChanging = function()
                                {
                                this.parent.offsetValue(-this.value*this.parent.settings.step);
                                this.value = 0;
                                };
                           }).call(this.sBar);
                        
                        (function()
                           { // edittext
                           this.addEventListener('blur', function(ev)
                                { // lost focus
                                this.parent.changeValue(this.text);
                                });
                           this.addEventListener('keydown', function(ev)
                                { // up/down keys
                                var delta = 1;
                                switch(ev.keyName)
                                     {
                                     case 'Down' : delta = -1;
                                     case 'Up' :
                                      delta *= this.parent.settings.step;
                                      if (ev.shiftKey) delta *= this.parent.settings.jump;
                                      this.parent.offsetValue(delta);
                                      break;
                                     default:;
                                     }
                                });
                           }).call(this.eTxt);
                        
                        this.changeValue();
                        }
                   };
               
              // XW interface
              // --------
              var r = {};
              for(var w in widgets)
                   {
                   r[w] = (function()
                        {
                        var w_ = w;
                        return function(/*obj*/ settings)
                           {
                           xWidgets[w_] = xWidgets[w_] || new xWidget(w_);
                           return xWidgets[w_].ui(settings);
                           };
                        })();
                   };
              r.connectAll = function(/*Window*/parent)
                   {
                   for each(var xw in xWidgets) xw.connect(parent);
                   }
              return r;
              })();
               
               
               
              //==================================================
              // sample code
              //==================================================
               
              // if you need a palette Window, replace _dialog by _palette
              // (and use #targetengine)
               
              var ui = toUIString.call({_dialog:{
                   text: "Column Rules",
                   properties: {closeOnKey: 'OSCmnd+W'},
                   orientation: 'row', alignChildren: ['fill','top'],
                   gTextFrame: {_Group:{
                        margins:0, spacing:10, orientation: 'column',
                        pInsetSpacing: {_Panel:{
                           margins:10, spacing:2, alignChildren: ['fill','fill'],
                           text: "Inset Spacing",
                           seTop: XW.ScrollEditText({title:"Top:",minvalue:0,maxvalue:'8640pt',decimals:3,units:'mm'}),
                           seBot: XW.ScrollEditText({title:"Bottom:",minvalue:0,maxvalue:'8640pt',decimals:3,units:'mm'}),
                           cLinked: {_Checkbox:{text: "Linked"}},
                           seLeft: XW.ScrollEditText({title:"Left:",minvalue:0,maxvalue:'8640pt',decimals:3,units:'mm'}),
                           seRight: XW.ScrollEditText({title:"Right:",minvalue:0,maxvalue:'8640pt',decimals:3,units:'mm'}),
                           }},
                        pColumns: {_Panel:{
                           margins:10, spacing:2, alignChildren: ['fill','fill'],
                           text: "Columns",
                           seNumber: XW.ScrollEditText({title:"Number:",minvalue:1,maxvalue:40}),
                           seGutter: XW.ScrollEditText({title:"Gutter:",minvalue:0,maxvalue:'8640pt',decimals:3,units:'mm'}),
                           }},
                        cIgnoreTextWrap: {_Checkbox:{text: "Ignore Text Wrap", alignment:'left'}},
                        }},
                   gProcess: {_Group:{
                        margins:10, spacing:8, alignChildren: ['center','fill'],
                        orientation: 'column',
                        gValid: {_Group:{
                           margins:10, spacing:10, orientation: 'row',
                           bOK: {_Button: {text:"OK"}},
                           bCancel: {_Button: {text:"Cancel"}},
                           }},
                        }},
                   }});
               
              // create the main window
              // ---------------------------
              var w = new Window(ui);
               
              // connect XWidgets
              // ---------------------------
              XW.connectAll(w);
               
              // manage the tab key
              // ---------------------------
              (function(){
                   this.addEventListener('keydown', function(ev)
                        { // Tab
                        if (ev.keyName == 'Tab')
                           {
                           ev.preventDefault();
                           this.focus( ((ev.shiftKey)?-1:1 ) );
                           }
                        });
                   }).call(w);
               
              // controls event manager
              // ...
              // here you add the event listeners for w.gTextFrame.pInsetSpacing, etc.
              // ...
               
              w.show();
              
              

               

              You will notice I use a special format rather than UI string resource. This allows to create compact object declaration, using finally the toUIString helper function. The XW object invokes also that stuff.

               

              The ScrollEditText component encapsulates the stepper+editText association. You don't have to manage the interaction. Each time the value changes, the component notify a change event on the editText target.

               

              Hope it could hep you.

               

              @+

              Marc

              1 person found this helpful
              • 4. Re: Creating a Stepper with ScriptUI?
                arlodesign Level 1

                Marc:

                 

                Thanks so much for the code. If anything, your approach to building a UI is awesome and will surely be helpful to not only me but others. Great work.

                 

                However, on my Mac (running CS4 on Leopard), your code produced this:

                 

                Picture 1.png

                Instead of a scrollbar, should it be two buttons?

                 

                You've given me a lot to play with, though, so thank you.

                 

                I'm surprised this sort of widget, given how prevalent it is throughout the CS interface, is not standard within ScriptUI.

                • 5. Re: Creating a Stepper with ScriptUI?
                  Marc Autret Level 4

                  Well, interesting result

                   

                  Here is the capture for the same code on Win OS :

                   

                  dialog.png

                   

                  As said, my script is still a work-in-progress. Apparently, I'll have to deal with cross-platform issues --always the same old story with scriptUI layout...

                   

                  Using one scrollbar for each up/down stepper may not be the good solution, but perhaps that's just a "cosmetic" problem and the underlying implementation could stay the same...

                   

                  I'm surprised this sort of widget, given how prevalent it is throughout the CS interface, is not standard within ScriptUI.

                   


                  You're so right !

                   

                  @+

                  Marc

                  • 6. Re: Creating a Stepper with ScriptUI?
                    Marc Autret Level 4

                    Bob (Adobe Engineer) wrote:

                     

                    You pretty much have to script it from scratch. Sorry.

                     

                    I am attaching a script that I wrote for CS4 that makes a ScriptUI edit text act like a measurement edit box. It allows you to use arrow keys to increment/decrement. That should at least help get you started.

                     

                    Regards

                     

                    Bob

                     

                    Hi Bob,

                     

                    What ScriptUI widget(s) do you use as the base of the stepper? Buttons?

                     

                    Thanks.

                     

                    Marc

                    • 7. Re: Creating a Stepper with ScriptUI?
                      Bob Stucky Adobe Employee

                      That script wasn't a "stepper" control. It was an approximation if ID's measurement edit control.

                       

                      My experience was that when I tried to write custom controls (including drawing the controls), things that worked in the ESTK did not always work in InDesign. I've since been using PatchPanel and CSXS, and pretty much stopped using ScriptUI.

                       

                       

                      Regards

                       

                      Bob

                      • 8. Re: Creating a Stepper with ScriptUI?
                        Steven.. Level 3

                         

                         

                        I'm surprised this sort of widget, given how prevalent it is throughout the CS interface, is not standard within ScriptUI.

                         

                        Then I guess you'll really be surprised to know that even in the SDK this widget doesn't exist. Only by combining a EditBoxWidget with a NudgeControlWidget and placing them right next to each other and linking them do you get your stepper widget.

                         

                        I f anything the question is if we can get the NudgeControlWidget for scriptUI.

                         

                        Steven Bryant

                        http://scriptui.com

                        • 9. Re: Creating a Stepper with ScriptUI?
                          anrkeytek

                          Hey Marc,

                           

                          can you give me some pointers on moving an object (or group) incrementally based on location within a book.

                           

                          ie a graphic element that moves down the page as book progresses.

                           

                          new to scripting in InDesign but have basic code skills.

                           

                          on a MAC but could run this on PC. Noticed the cross platfrom issues before.

                           

                          Cheers

                          D